pax_global_header00006660000000000000000000000064147712412220014514gustar00rootroot0000000000000052 comment=0529c7338212d41bf04c28b6c163479aa9ea1551 buteo-syncfw-0.11.10/000077500000000000000000000000001477124122200143015ustar00rootroot00000000000000buteo-syncfw-0.11.10/.gitignore000066400000000000000000000002331477124122200162670ustar00rootroot00000000000000Makefile Makefile.* *.o *.gcno *.so* *.prl moc_*.cpp *.moc pkgconfig/ *.list *.tag msyncd/libmsyncd.a msyncd/msyncd doc/html unittests/tests/sync-fw-tests buteo-syncfw-0.11.10/COPYING000066400000000000000000000635271477124122200153510ustar00rootroot00000000000000GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. 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 not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the 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 specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 2.1 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 Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! buteo-syncfw-0.11.10/buteo-sync.pro000066400000000000000000000004241477124122200171130ustar00rootroot00000000000000TEMPLATE = subdirs SUBDIRS += \ libbuteosyncfw \ declarative \ msyncd \ oopp-runner \ unittests \ doc declarative.depends = libbuteosyncfw msyncd.depends = libbuteosyncfw oopp-runner.depends = libbuteosyncfw unittests.depends = libbuteosyncfw msyncd buteo-syncfw-0.11.10/declarative/000077500000000000000000000000001477124122200165645ustar00rootroot00000000000000buteo-syncfw-0.11.10/declarative/declarative.pro000066400000000000000000000013361477124122200215740ustar00rootroot00000000000000TEMPLATE = lib TARGET = buteoprofiles PLUGIN_IMPORT_PATH = Buteo/Profiles QT -= gui QT += qml dbus CONFIG += qt plugin hide_symbols INCLUDEPATH += ../libbuteosyncfw ../libbuteosyncfw/common ../libbuteosyncfw/profile LIBS += -L../libbuteosyncfw -lbuteosyncfw5 target.path = $$[QT_INSTALL_QML]/$$PLUGIN_IMPORT_PATH SOURCES += plugin.cpp \ syncresultmodelbase.cpp \ syncresultmodel.cpp \ multisyncresultmodel.cpp \ syncprofilewatcher.cpp \ syncmanager.cpp HEADERS += syncresultmodelbase.h \ syncresultmodel.h \ multisyncresultmodel.h \ syncprofilewatcher.h \ profileentry.h \ syncmanager.h OTHER_FILES += qmldir qmldir.files += qmldir qmldir.path += $$target.path INSTALLS += target qmldir buteo-syncfw-0.11.10/declarative/multisyncresultmodel.cpp000066400000000000000000000101301477124122200235720ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2020-2021 Damien Caliste. * * Contact: Damien Caliste * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "multisyncresultmodel.h" #include using namespace Buteo; MultiSyncResultModel::MultiSyncResultModel(QObject *parent) : SyncResultModelBase(parent) , mSortOption(MultiSyncResultModel::ByDate) { const QList profiles = mManager.allSyncProfiles(); for (SyncProfile *profile : profiles) { QSharedPointer ptr(profile); addProfileResults(ptr); addProfileToFilter(*ptr); } sort(); sortFilterList(); connect(mSyncClient.data(), &SyncClientInterface::profileChanged, this, &MultiSyncResultModel::onProfileChanged); } MultiSyncResultModel::~MultiSyncResultModel() { } void MultiSyncResultModel::addProfileToFilter(const SyncProfile &profile) { if (!profile.isEnabled()) { return; } const Profile *client = profile.clientProfile(); mFilterList << ProfileEntry{profile.name(), profile.displayname(), client ? client->name() : QString()}; } QVariantList MultiSyncResultModel::filterList() const { QVariantList list; for (const ProfileEntry &entry : mFilterList) { list << QVariant::fromValue(entry); } return list; } void MultiSyncResultModel::sortFilterList() { std::sort(mFilterList.begin(), mFilterList.end()); } QString MultiSyncResultModel::filter() const { return mProfileName; } void MultiSyncResultModel::setFilter(const QString &filter) { if (filter == mProfileName) { return; } mProfileName = filter; emit filterChanged(); beginResetModel(); mResults.clear(); const QList profiles = mManager.allSyncProfiles(); for (SyncProfile *profile : profiles) { QSharedPointer ptr(profile); addProfileResults(ptr); } sort(); endResetModel(); } void MultiSyncResultModel::sort() { switch (mSortOption) { case MultiSyncResultModel::ByDate: SyncResultModelBase::sort(); break; case MultiSyncResultModel::ByAccount: std::sort(mResults.begin(), mResults.end(), [](const SyncResultEntry &a, const SyncResultEntry &b) { return a.profile->key("accountid") < b.profile->key("accountid"); }); break; } } MultiSyncResultModel::SortOptions MultiSyncResultModel::sorting() const { return mSortOption; } void MultiSyncResultModel::setSorting(MultiSyncResultModel::SortOptions option) { if (option == mSortOption) { return; } mSortOption = option; emit sortingChanged(); beginResetModel(); sort(); endResetModel(); } void MultiSyncResultModel::onProfileChanged(QString aProfileName, int aChangeType , QString aProfileAsXml) { Q_UNUSED(aProfileAsXml); Q_UNUSED(aChangeType); if (!mProfileName.isEmpty() && aProfileName != mProfileName) { return; } mFilterList.erase(std::remove_if(mFilterList.begin(), mFilterList.end(), [aProfileName] (const ProfileEntry &entry) {return entry.id == aProfileName;}), mFilterList.end()); SyncProfile* profile = mManager.syncProfile(aProfileName); if (profile) { addProfileToFilter(*profile); sortFilterList(); } delete profile; emit filterListChanged(); } buteo-syncfw-0.11.10/declarative/multisyncresultmodel.h000066400000000000000000000040031477124122200232410ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2021 Damien Caliste. * * Contact: Damien Caliste * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef MULTISYNCRESULTMODEL_H #define MULTISYNCRESULTMODEL_H #include "syncresultmodelbase.h" #include "profileentry.h" class MultiSyncResultModel: public SyncResultModelBase { Q_OBJECT Q_PROPERTY(QVariantList filterList READ filterList NOTIFY filterListChanged) Q_PROPERTY(QString filter READ filter WRITE setFilter NOTIFY filterChanged) Q_PROPERTY(SortOptions sorting READ sorting WRITE setSorting NOTIFY sortingChanged) public: enum SortOptions { ByDate, ByAccount }; Q_ENUM(SortOptions) MultiSyncResultModel(QObject *parent = nullptr); ~MultiSyncResultModel(); QVariantList filterList() const; QString filter() const; void setFilter(const QString &filter); SortOptions sorting() const; void setSorting(SortOptions option); signals: void filterListChanged(); void filterChanged(); void sortingChanged(); private slots: void onProfileChanged(QString aProfileName, int aChangeType, QString aProfileAsXml); private: void addProfileToFilter(const SyncProfile &profile); void sortFilterList(); virtual void sort(); SortOptions mSortOption; QList mFilterList; }; #endif buteo-syncfw-0.11.10/declarative/plugin.cpp000066400000000000000000000060501477124122200205670ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2019-2021 Damien Caliste. * * Contact: Damien Caliste * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include #include #include #include #include "profile/SyncResults.h" #include "profile/SyncSchedule.h" #include "syncmanager.h" #include "syncresultmodel.h" #include "multisyncresultmodel.h" #include "syncprofilewatcher.h" class Q_DECL_EXPORT ButeoProfilesPlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID "Buteo.Profiles") public: ButeoProfilesPlugin(){} virtual ~ButeoProfilesPlugin() {} void initializeEngine(QQmlEngine *engine, const char *uri) { Q_ASSERT(uri == QLatin1String("Buteo.Profiles")); Q_UNUSED(engine) Q_UNUSED(uri) } void registerTypes(const char *uri) { Q_ASSERT(uri == QLatin1String("Buteo.Profiles")); qmlRegisterType(uri, 0, 1, "SyncManager"); qmlRegisterUncreatableType(uri, 0, 1, "ProfileFilter", "ProfileFilter is used as a group property of SyncManager"); qmlRegisterType(uri, 0, 1, "SyncProfileWatcher"); qRegisterMetaType("SyncSchedule"); qmlRegisterUncreatableType(uri, 0, 1, "SyncSchedule", "SyncSchedule is retrieved from a SyncProfileWatcher"); qmlRegisterType(uri, 0, 1, "SyncResultModel"); qmlRegisterType(uri, 0, 1, "MultiSyncResultModel"); qRegisterMetaType(); qmlRegisterUncreatableType(uri, 0, 1, "SyncResults", "SyncResults are retrieved from a SyncResultModel or a MultiSyncResultModel"); qRegisterMetaType(); qmlRegisterUncreatableType(uri, 0, 1, "TargetResults", "TargetResults are retrieved from a SyncResults"); qRegisterMetaType(); qmlRegisterUncreatableType(uri, 0, 1, "ItemCounts", "ItemCounts are retrieved from a TargetResults"); } }; #include "plugin.moc" buteo-syncfw-0.11.10/declarative/profileentry.h000066400000000000000000000023231477124122200214570ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2024 Damien Caliste. * * Contact: Damien Caliste * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef PROFILEENTRY_H #define PROFILEENTRY_H #include struct ProfileEntry { Q_GADGET Q_PROPERTY(QString id MEMBER id) Q_PROPERTY(QString label MEMBER label) Q_PROPERTY(QString clientName MEMBER clientName) public: QString id; QString label; QString clientName; bool operator<(const struct ProfileEntry &other) { return label < other.label; } }; #endif buteo-syncfw-0.11.10/declarative/qmldir000066400000000000000000000000531477124122200177750ustar00rootroot00000000000000module Buteo.Profiles plugin buteoprofiles buteo-syncfw-0.11.10/declarative/syncmanager.cpp000066400000000000000000000221571477124122200216060ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2024 Damien Caliste. * * Contact: Damien Caliste * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "syncmanager.h" #include #include #include #include #include #include using namespace Buteo; SyncManager::SyncManager(QObject *parent) : QObject(parent) , mSyncClient(SyncClientInterface::sharedInstance()) , mFilterBy(new ProfileFilter(this)) { connect(mSyncClient.data(), &SyncClientInterface::isValidChanged, this, &SyncManager::serviceAvailableChanged); connect(mSyncClient.data(), &SyncClientInterface::syncStatus, this, &SyncManager::onSyncStatusChanged); connect(mSyncClient.data(), &SyncClientInterface::profileChanged, this, &SyncManager::onProfileChanged); connect(mFilterBy, &ProfileFilter::updated, this, &SyncManager::requestSyncProfiles); requestRunningSyncList(); } SyncManager::~SyncManager() { } void SyncManager::classBegin() { } void SyncManager::componentComplete() { mComponentCompleted = true; requestSyncProfiles(); } bool SyncManager::serviceAvailable() const { return mSyncClient->isValid(); } bool SyncManager::synchronizing() const { for (const ProfileEntry &entry : mProfiles) { if (mSyncingProfiles.contains(entry.id)) return true; } return false; } void SyncManager::onSyncStatusChanged(QString aProfileId, int aStatus, QString aMessage, int aStatusDetails) { Q_UNUSED(aMessage); Q_UNUSED(aStatusDetails); bool syncInProgress = synchronizing(); if (static_cast(aStatus) < Sync::SYNC_ERROR) mSyncingProfiles.insert(aProfileId); else mSyncingProfiles.remove(aProfileId); if (syncInProgress != synchronizing()) emit synchronizingChanged(); } void SyncManager::requestRunningSyncList() { connect(mSyncClient->requestRunningSyncList(this), &QDBusPendingCallWatcher::finished, [this] (QDBusPendingCallWatcher *call) { QDBusPendingReply reply = *call; if (reply.isError()) { qWarning() << "cannot get running sync list:" << reply.error().message(); } else { bool syncInProgress = synchronizing(); mSyncingProfiles.clear(); for (const QString profileId : reply.value()) { mSyncingProfiles.insert(profileId); } if (syncInProgress != synchronizing()) { emit synchronizingChanged(); } } call->deleteLater(); }); } void SyncManager::onProfileChanged(QString aProfileName, int aChangeType, QString aProfileAsXml) { Q_UNUSED(aProfileAsXml); ProfileManager::ProfileChangeType change = static_cast(aChangeType); bool changed = false; if (change == ProfileManager::PROFILE_MODIFIED || change == ProfileManager::PROFILE_REMOVED) { mProfiles.erase(std::remove_if(mProfiles.begin(), mProfiles.end(), [aProfileName] (const ProfileEntry &entry) {return entry.id == aProfileName;}), mProfiles.end()); changed = true; } if (change == ProfileManager::PROFILE_MODIFIED || change == ProfileManager::PROFILE_ADDED) { Profile *profile = ProfileManager::profileFromXml(aProfileAsXml); if (profile && addProfile(*profile)) { std::sort(mProfiles.begin(), mProfiles.end()); changed = true; } delete profile; } if (changed) { emit profilesChanged(); emit synchronizingChanged(); } } void SyncManager::requestSyncProfiles() { if (!mComponentCompleted) return; QDBusPendingCallWatcher *request; if (mFilterBy->isValid()) { request = mSyncClient->requestSyncProfilesByKey(mFilterBy->key, mFilterBy->value, this); } else if (!mFilterByAccount.isEmpty()) { request = mSyncClient->requestSyncProfilesByKey(KEY_ACCOUNT_ID, mFilterByAccount, this); } else if (mFilterHidden) { request = mSyncClient->requestAllVisibleSyncProfiles(this); } else { request = mSyncClient->requestProfilesByType(Profile::TYPE_SYNC, this); } connect(request, &QDBusPendingCallWatcher::finished, [this] (QDBusPendingCallWatcher *call) { QDBusPendingReply reply = *call; if (reply.isError()) { qWarning() << "cannot list profiles:" << reply.error().message(); } else { setProfilesFromXml(reply.value()); } call->deleteLater(); }); } QVariantList SyncManager::profiles() const { QVariantList list; for (const ProfileEntry &entry : mProfiles) { list << QVariant::fromValue(entry); } return list; } void SyncManager::setProfilesFromXml(const QStringList &profiles) { bool changed = !mProfiles.isEmpty(); mProfiles.clear(); for (const QString profileAsXml : profiles) { Profile *profile = ProfileManager::profileFromXml(profileAsXml); if (profile && addProfile(*profile)) { changed = true; } delete profile; } if (changed) { std::sort(mProfiles.begin(), mProfiles.end()); emit profilesChanged(); emit synchronizingChanged(); } } bool SyncManager::addProfile(const Profile &profile) { if (profile.type() != Profile::TYPE_SYNC) { return false; } if (mFilterDisabled && !profile.isEnabled()) { return false; } if (mFilterHidden && profile.isHidden()) { return false; } if (!mFilterByAccount.isEmpty() && profile.key(KEY_ACCOUNT_ID) != mFilterByAccount) { return false; } const Profile *client = static_cast(&profile)->clientProfile(); mProfiles << ProfileEntry{profile.name(), profile.displayname(), client ? client->name() : QString()}; return true; } bool SyncManager::filterDisabled() const { return mFilterDisabled; } bool SyncManager::filterHidden() const { return mFilterHidden; } QString SyncManager::filterByAccount() const { return mFilterByAccount; } ProfileFilter* SyncManager::filterBy() const { return mFilterBy; } void SyncManager::setFilterDisabled(bool value) { if (value == mFilterDisabled) return; mFilterDisabled = value; emit filterDisabledChanged(); requestSyncProfiles(); } void SyncManager::setFilterHidden(bool value) { if (value == mFilterHidden) return; mFilterHidden = value; emit filterHiddenChanged(); requestSyncProfiles(); } void SyncManager::setFilterByAccount(const QString &accountId) { if (accountId == mFilterByAccount) return; mFilterByAccount = accountId; emit filterByAccountChanged(); requestSyncProfiles(); } void SyncManager::synchronize() { for (const ProfileEntry &entry : mProfiles) { connect(mSyncClient->requestSync(entry.id, this), &QDBusPendingCallWatcher::finished, [this, entry] (QDBusPendingCallWatcher *call) { QDBusPendingReply reply = *call; if (reply.isError() || !reply.value()) { qWarning() << "cannot start sync for" << entry.id << ":" << (reply.isError() ? reply.error().message() : "no such profile"); mSyncingProfiles.remove(entry.id); emit synchronizingChanged(); } call->deleteLater(); }); mSyncingProfiles.insert(entry.id); } if (!mProfiles.isEmpty()) emit synchronizingChanged(); } void SyncManager::abort() { for (const ProfileEntry &entry : mProfiles) { mSyncClient->abortSync(entry.id); mSyncingProfiles.remove(entry.id); } if (!mProfiles.isEmpty()) emit synchronizingChanged(); } buteo-syncfw-0.11.10/declarative/syncmanager.h000066400000000000000000000074101477124122200212460ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2024 Damien Caliste. * * Contact: Damien Caliste * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCMANAGER_H #define SYNCMANAGER_H #include #include #include #include "clientfw/SyncClientInterface.h" #include "profileentry.h" using namespace Buteo; class ProfileFilter: public QObject { Q_OBJECT Q_PROPERTY(QString key MEMBER key NOTIFY updated) Q_PROPERTY(QString value MEMBER value NOTIFY updated) public: ProfileFilter(QObject *parent = nullptr) : QObject(parent) { } bool operator==(const struct ProfileFilter &other) const { return key == other.key && value == other.value; } bool isValid() const { return !key.isEmpty() && !value.isEmpty(); } QString key; QString value; signals: void updated(); }; class Q_DECL_EXPORT SyncManager: public QObject, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) Q_PROPERTY(bool serviceAvailable READ serviceAvailable NOTIFY serviceAvailableChanged) Q_PROPERTY(bool synchronizing READ synchronizing NOTIFY synchronizingChanged) Q_PROPERTY(bool filterDisabled READ filterDisabled WRITE setFilterDisabled NOTIFY filterDisabledChanged) Q_PROPERTY(bool filterHidden READ filterHidden WRITE setFilterHidden NOTIFY filterHiddenChanged) Q_PROPERTY(QString filterByAccount READ filterByAccount WRITE setFilterByAccount NOTIFY filterByAccountChanged) Q_PROPERTY(ProfileFilter* filterBy READ filterBy CONSTANT) Q_PROPERTY(QVariantList profiles READ profiles NOTIFY profilesChanged) public: SyncManager(QObject *parent = nullptr); ~SyncManager(); void classBegin() override; void componentComplete() override; bool serviceAvailable() const; bool synchronizing() const; bool filterDisabled() const; bool filterHidden() const; QString filterByAccount() const; ProfileFilter* filterBy() const; QVariantList profiles() const; void setFilterDisabled(bool value); void setFilterHidden(bool value); void setFilterByAccount(const QString &accountId); Q_INVOKABLE void synchronize(); Q_INVOKABLE void abort(); signals: void serviceAvailableChanged(); void synchronizingChanged(); void filterDisabledChanged(); void filterHiddenChanged(); void filterByAccountChanged(); void profilesChanged(); private: void requestSyncProfiles(); void onProfileChanged(QString aProfileName, int aChangeType, QString aProfileAsXml); void requestRunningSyncList(); void onSyncStatusChanged(QString aProfileId, int aStatus, QString aMessage, int aStatusDetails); bool addProfile(const Profile &profile); void setProfilesFromXml(const QStringList &profiles); QSharedPointer mSyncClient; QSet mSyncingProfiles; bool mComponentCompleted = false; bool mFilterDisabled = true; bool mFilterHidden = false; QString mFilterByAccount; ProfileFilter *mFilterBy; QList mProfiles; }; #endif buteo-syncfw-0.11.10/declarative/syncprofilewatcher.cpp000066400000000000000000000153721477124122200232130ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2019-2021 Damien Caliste. * * Contact: Damien Caliste * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "syncprofilewatcher.h" #include #include #include using namespace Buteo; SyncProfileWatcher::SyncProfileWatcher(QObject *parent) : QObject(parent) , mSyncClient(SyncClientInterface::sharedInstance()) , mSyncProfile(nullptr) , mSyncStatus(Done) { connect(&mManager, &ProfileManager::signalProfileChanged, this, &SyncProfileWatcher::onProfileChanged); connect(mSyncClient.data(), &SyncClientInterface::profileChanged, this, &SyncProfileWatcher::onProfileChanged); connect(mSyncClient.data(), &SyncClientInterface::syncStatus, this, &SyncProfileWatcher::onSyncStatus); } SyncProfileWatcher::~SyncProfileWatcher() { delete mSyncProfile; } void SyncProfileWatcher::setName(const QString &aName) { if (aName == name()) return; delete mSyncProfile; mSyncProfile = mManager.syncProfile(aName); setKeys(); Status status = Done; if (mSyncProfile && mSyncProfile->lastResults()) { switch (mSyncProfile->lastResults()->majorCode()) { case (SyncResults::SYNC_RESULT_INVALID): // Fallthrough case (SyncResults::SYNC_RESULT_SUCCESS): status = Done; break; case (SyncResults::SYNC_RESULT_FAILED): status = Error; break; case (SyncResults::SYNC_RESULT_CANCELLED): status = Cancelled; break; } } if (mSyncStatus != status) { mSyncStatus = status; emit syncStatusChanged(); } // These properties directly depend on mSyncProfile only. emit nameChanged(); emit displayNameChanged(); emit enabledChanged(); emit scheduleChanged(); emit logChanged(); } void SyncProfileWatcher::setKeys() { mKeys.clear(); if (mSyncProfile) { const QMap keys = mSyncProfile->allKeys(); for (QMap::ConstIterator it = keys.constBegin(); it != keys.constEnd(); ++it) { mKeys.insert(it.key(), it.value()); } Buteo::Profile *client = mSyncProfile->clientProfile(); if (client) { const QMap keys = client->allKeys(); for (QMap::ConstIterator it = keys.constBegin(); it != keys.constEnd(); ++it) { mKeys.insert(client->name() + "/" + it.key(), it.value()); } } } emit keysChanged(); } QString SyncProfileWatcher::name() const { return mSyncProfile ? mSyncProfile->name() : QString(); } QString SyncProfileWatcher::displayName() const { return mSyncProfile ? mSyncProfile->displayname() : QString(); } bool SyncProfileWatcher::enabled() const { return mSyncProfile ? mSyncProfile->isEnabled() : false; } QVariantList SyncProfileWatcher::log() { QVariantList out; if (mSyncProfile && mSyncProfile->log()) { const QList allResults(mSyncProfile->log()->allResults()); for (const SyncResults *result : allResults) { out << QVariant::fromValue(*result); } } return out; } SyncSchedule SyncProfileWatcher::schedule() const { return mSyncProfile ? mSyncProfile->syncSchedule() : SyncSchedule(); } QVariantMap SyncProfileWatcher::keys() const { return mKeys; } SyncProfileWatcher::Status SyncProfileWatcher::syncStatus() const { return mSyncStatus; } bool SyncProfileWatcher::synchronizing() const { return mSyncStatus < Error; } void SyncProfileWatcher::startSync() { if (mSyncProfile) { const QString profileId = mSyncProfile->name(); connect(mSyncClient->requestSync(profileId, this), &QDBusPendingCallWatcher::finished, [this, profileId] (QDBusPendingCallWatcher *call) { QDBusPendingReply reply = *call; if (reply.isError() || !reply.value()) { qWarning() << "cannot start sync for" << profileId << ":" << (reply.isError() ? reply.error().message() : "no such profile"); if (mSyncProfile && mSyncProfile->name() == profileId) { mSyncStatus = Error; emit syncStatusChanged(); } } call->deleteLater(); }); mSyncStatus = Queued; emit syncStatusChanged(); } } void SyncProfileWatcher::abortSync() const { if (mSyncProfile) { mSyncClient->abortSync(mSyncProfile->name()); } } void SyncProfileWatcher::onProfileChanged(QString aProfileName, int aChangeType , QString aProfileAsXml) { Q_UNUSED(aProfileAsXml); if (aProfileName.isEmpty() || aProfileName != name()) return; // Need to reload the profile because it may have been modified by // an external ProfileManager and we've been signaled by DBus. SyncProfile *previous = mSyncProfile; mSyncProfile = mManager.syncProfile(aProfileName); if (aChangeType == ProfileManager::PROFILE_MODIFIED) { if (!mSyncProfile || previous->displayname() != mSyncProfile->displayname()) { emit displayNameChanged(); } if (!mSyncProfile || previous->isEnabled() != mSyncProfile->isEnabled()) { emit enabledChanged(); } if (!mSyncProfile || !(previous->syncSchedule() == mSyncProfile->syncSchedule())) { emit scheduleChanged(); } setKeys(); } else if (aChangeType == ProfileManager::PROFILE_LOGS_MODIFIED) { emit logChanged(); } delete previous; } void SyncProfileWatcher::onSyncStatus(QString aProfileId, int aStatus, QString aMessage, int aStatusDetails) { Q_UNUSED(aMessage); Q_UNUSED(aStatusDetails); if (aProfileId.isEmpty() || aProfileId != name()) return; if (mSyncStatus != Status(aStatus)) { mSyncStatus = Status(aStatus); emit syncStatusChanged(); } } buteo-syncfw-0.11.10/declarative/syncprofilewatcher.h000066400000000000000000000061661477124122200226610ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2019-2021 Damien Caliste. * * Contact: Damien Caliste * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCPROFILEWATCHER_H #define SYNCPROFILEWATCHER_H #include #include #include "profile/ProfileManager.h" #include "clientfw/SyncClientInterface.h" #include "common/SyncCommonDefs.h" using namespace Buteo; class Q_DECL_EXPORT SyncProfileWatcher: public QObject { Q_OBJECT Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) Q_PROPERTY(QString displayName READ displayName NOTIFY displayNameChanged) Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged) Q_PROPERTY(QVariantList log READ log NOTIFY logChanged) Q_PROPERTY(SyncSchedule schedule READ schedule NOTIFY scheduleChanged) Q_PROPERTY(QVariantMap keys READ keys NOTIFY keysChanged) Q_PROPERTY(Status syncStatus READ syncStatus NOTIFY syncStatusChanged) Q_PROPERTY(bool synchronizing READ synchronizing NOTIFY syncStatusChanged) public: enum Status { Queued = Sync::SYNC_QUEUED, Started, Progress, Error, Done, Aborted, Cancelled, Stopping, NotPossible, AuthenticationFailure, DatabaseFailure, ConnectionError, ServerFailure, BadRequest, PluginError, PluginTimeout }; Q_ENUM(Status) SyncProfileWatcher(QObject *parent = nullptr); ~SyncProfileWatcher(); QString name() const; void setName(const QString &aName); QString displayName() const; bool enabled() const; QVariantList log(); SyncSchedule schedule() const; QVariantMap keys() const; Status syncStatus() const; bool synchronizing() const; Q_INVOKABLE void startSync(); Q_INVOKABLE void abortSync() const; signals: void nameChanged(); void displayNameChanged(); void enabledChanged(); void logChanged(); void scheduleChanged(); void keysChanged(); void syncStatusChanged(); private slots: void onProfileChanged(QString aProfileName, int aChangeType, QString aProfileAsXml); void onSyncStatus(QString aProfileId, int aStatus, QString aMessage, int aStatusDetails); private: void setKeys(); protected: ProfileManager mManager; QSharedPointer mSyncClient; SyncProfile *mSyncProfile; QVariantMap mKeys; Status mSyncStatus; }; #endif buteo-syncfw-0.11.10/declarative/syncresultmodel.cpp000066400000000000000000000027561477124122200225360ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2020-2021 Damien Caliste. * * Contact: Damien Caliste * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "syncresultmodel.h" #include using namespace Buteo; SyncResultModel::SyncResultModel(QObject *parent) : SyncResultModelBase(parent) { } SyncResultModel::~SyncResultModel() { } QString SyncResultModel::profile() const { return mProfileName; } void SyncResultModel::setProfile(const QString &profile) { if (profile == mProfileName) { return; } mProfileName = profile; emit profileChanged(); beginResetModel(); mResults.clear(); if (!profile.isEmpty()) { QSharedPointer syncProfile(mManager.syncProfile(mProfileName)); addProfileResults(syncProfile); sort(); } endResetModel(); } buteo-syncfw-0.11.10/declarative/syncresultmodel.h000066400000000000000000000023621477124122200221740ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2020-2021 Damien Caliste. * * Contact: Damien Caliste * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCRESULTMODEL_H #define SYNCRESULTMODEL_H #include class SyncResultModel: public SyncResultModelBase { Q_OBJECT Q_PROPERTY(QString profile READ profile WRITE setProfile NOTIFY profileChanged) public: SyncResultModel(QObject *parent = nullptr); ~SyncResultModel(); QString profile() const; void setProfile(const QString &profile); signals: void profileChanged(); }; #endif buteo-syncfw-0.11.10/declarative/syncresultmodelbase.cpp000066400000000000000000000103201477124122200233530ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2020-2021 Damien Caliste. * * Contact: Damien Caliste * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "syncresultmodelbase.h" #include using namespace Buteo; SyncResultModelBase::SyncResultModelBase(QObject *parent) : QAbstractListModel(parent) , mSyncClient(SyncClientInterface::sharedInstance()) { connect(mSyncClient.data(), &SyncClientInterface::profileChanged, this, &SyncResultModelBase::onProfileChanged); } SyncResultModelBase::~SyncResultModelBase() { } void SyncResultModelBase::onProfileChanged(QString aProfileName, int aChangeType, QString aProfileAsXml) { Q_UNUSED(aProfileAsXml); Q_UNUSED(aChangeType); if (!mProfileName.isEmpty() && aProfileName != mProfileName) { return; } QSharedPointer profile(mManager.syncProfile(aProfileName)); beginResetModel(); mResults.erase(std::remove_if(mResults.begin(), mResults.end(), [aProfileName] (const SyncResultEntry &entry) {return entry.profile->name() == aProfileName;}), mResults.end()); if (profile) { addProfileResults(profile); sort(); } endResetModel(); } void SyncResultModelBase::addProfileResults(QSharedPointer &profile) { if (!profile || !profile->isEnabled()) { return; } if (!mProfileName.isEmpty() && profile->name() != mProfileName) { return; } const QList allResults = profile->log()->allResults(); for (const SyncResults *results : allResults) { mResults.prepend(SyncResultEntry{profile, *results}); } } void SyncResultModelBase::sort() { std::sort(mResults.begin(), mResults.end(), [](const SyncResultEntry &a, const SyncResultEntry &b) { return a.results.syncTime() > b.results.syncTime(); }); } int SyncResultModelBase::rowCount(const QModelIndex& parent) const { if (parent.isValid()) return 0; return mResults.size(); } QVariant SyncResultModelBase::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= mResults.count()) return QVariant(); switch (role) { case SyncResultModelBase::ProfileNameRole: return QVariant::fromValue(mResults[index.row()].profile->name()); case SyncResultModelBase::ProfileDisplayNameRole: return QVariant::fromValue(mResults[index.row()].profile->displayname()); case SyncResultModelBase::ClientNameRole: { const Profile *client = mResults[index.row()].profile->clientProfile(); return client ? QVariant::fromValue(client->name()) : QVariant(); } case SyncResultModelBase::AccountIdRole: return QVariant::fromValue(mResults[index.row()].profile->key("accountid")); case SyncResultModelBase::SyncResultsRole: return QVariant::fromValue(mResults[index.row()].results); default: return QVariant(); } } QHash SyncResultModelBase::roleNames() const { static QHash names; if (names.size() == 0) { names.insert(SyncResultModelBase::ProfileNameRole, "profileName"); names.insert(SyncResultModelBase::ProfileDisplayNameRole, "profileDisplayName"); names.insert(SyncResultModelBase::ClientNameRole, "clientName"); names.insert(SyncResultModelBase::AccountIdRole, "accountId"); names.insert(SyncResultModelBase::SyncResultsRole, "syncResults"); } return names; } buteo-syncfw-0.11.10/declarative/syncresultmodelbase.h000066400000000000000000000040621477124122200230260ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2020-2021 Damien Caliste. * * Contact: Damien Caliste * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCRESULTMODELBASE_H #define SYNCRESULTMODELBASE_H #include #include #include #include #include #include using namespace Buteo; class SyncResultModelBase: public QAbstractListModel { Q_OBJECT public: enum Roles { ProfileNameRole = Qt::UserRole + 1, ProfileDisplayNameRole, ClientNameRole, AccountIdRole, SyncResultsRole, }; SyncResultModelBase(QObject *parent = nullptr); ~SyncResultModelBase(); virtual QVariant data(const QModelIndex &index, int role) const; virtual int rowCount(const QModelIndex &parent) const; virtual QHash roleNames() const; protected: void addProfileResults(QSharedPointer &profile); virtual void sort(); QSharedPointer mSyncClient; ProfileManager mManager; struct SyncResultEntry { QSharedPointer profile; SyncResults results; }; QList mResults; QString mProfileName; private slots: void onProfileChanged(QString aProfileName, int aChangeType, QString aProfileAsXml); }; #endif buteo-syncfw-0.11.10/doc/000077500000000000000000000000001477124122200150465ustar00rootroot00000000000000buteo-syncfw-0.11.10/doc/Doxyfile000066400000000000000000002043021477124122200165550ustar00rootroot00000000000000# Doxyfile 1.6.3 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project # # All text after a hash (#) is considered a comment and will be ignored # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" ") #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # http://www.gnu.org/software/libiconv for the list of possible encodings. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. PROJECT_NAME = Buteo Synchronization Framework # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = . # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, # Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, # Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English # messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, # Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, # Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = "The $name class" \ "The $name widget" \ "The $name file" \ is \ provides \ specifies \ contains \ represents \ a \ an \ the # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = NO # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful is your file systems # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like regular Qt-style comments # (thus requiring an explicit @brief command for a brief description.) JAVADOC_AUTOBRIEF = NO # If the QT_AUTOBRIEF tag is set to YES then Doxygen will # interpret the first line (until the first dot) of a Qt-style # comment as the brief description. If set to NO, the comments # will behave just like regular Qt-style comments (thus requiring # an explicit \brief command for a brief description.) QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for # Java. For instance, namespaces will be presented as packages, qualified # scopes will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources only. Doxygen will then generate output that is more tailored for # Fortran. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for # VHDL. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it parses. # With this tag you can assign which parser to use for a given extension. # Doxygen has a built-in mapping, but you can override or extend it using this tag. # The format is ext=language, where ext is a file extension, and language is one of # the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, # Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat # .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), # use: inc=Fortran f=C. Note that for custom extensions you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. EXTENSION_MAPPING = # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. # Doxygen will parse them like normal C++ but will assume all classes use public # instead of private inheritance when no explicit protection keyword is present. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate getter # and setter methods for a property. Setting this option to YES (the default) # will make doxygen to replace the get and set methods by a property in the # documentation. This will only work if the methods are indeed getting or # setting a simple type. If this is not the case, or you want to show the # methods anyway, you should set this option to NO. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES # When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum # is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically # be useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. TYPEDEF_HIDES_STRUCT = NO # The SYMBOL_CACHE_SIZE determines the size of the internal cache use to # determine which symbols to keep in memory and which to flush to disk. # When the cache is full, less often used symbols will be written to disk. # For small to medium size projects (<1000 input files) the default value is # probably good enough. For larger projects a too small cache size can cause # doxygen to be busy swapping symbols to and from disk most of the time # causing a significant performance penality. # If the system has enough physical memory increasing the cache will improve the # performance by keeping more symbols in memory. Note that the value works on # a logarithmic scale so increasing the size by one will rougly double the # memory usage. The cache size is given by this formula: # 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, # corresponding to a cache size of 2^16 = 65536 symbols SYMBOL_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = NO # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base # name of the file that contains the anonymous namespace. By default # anonymous namespace are hidden. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen # will list include files with double quotes in the documentation # rather than with sharp brackets. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the (brief and detailed) documentation of class members so that constructors and destructors are listed first. If set to NO (the default) the constructors will appear in the respective orders defined by SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the # hierarchy of group names into alphabetical order. If set to NO (the default) # the group names will appear in their defined order. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or define consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and defines in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES # If the sources in your project are distributed over multiple directories # then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy # in the documentation. The default is NO. SHOW_DIRECTORIES = NO # Set the SHOW_FILES tag to NO to disable the generation of the Files page. # This will remove the Files entry from the Quick Index and from the # Folder Tree View (if specified). The default is YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the # Namespaces page. # This will remove the Namespaces entry from the Quick Index # and from the Folder Tree View (if specified). The default is YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by # doxygen. The layout file controls the global structure of the generated output files # in an output format independent way. The create the layout file that represents # doxygen's defaults, run doxygen with the -l option. You can optionally specify a # file name after the option, if omitted DoxygenLayout.xml will be used as the name # of the layout file. LAYOUT_FILE = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = YES # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be abled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = src \ ../msyncd/ \ ../libbuteosyncfw/ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is # also the default input encoding. Doxygen uses libiconv (or the iconv built # into libc) for the transcoding. See http://www.gnu.org/software/libiconv for # the list of possible encodings. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx # *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 FILE_PATTERNS = *.c \ *.cc \ *.cxx \ *.c++ \ *.d \ *.java \ *.ii \ *.ixx \ *.ipp \ *.i++ \ *.inl \ *.h \ *.hh \ *.hxx \ *.hpp \ *.h++ \ *.idl \ *.odl \ *.cs \ *.php \ *.php3 \ *.inc \ *.m \ *.mm \ *.dox \ *.py \ *.f90 \ *.f \ *.vhd \ *.vhdl \ *.C \ *.CC \ *.C++ \ *.II \ *.I++ \ *.H \ *.HH \ *.H++ \ *.CS \ *.PHP \ *.PHP3 \ *.M \ *.MM \ *.PY \ *.F90 \ *.F \ *.VHD \ *.VHDL \ *.C \ *.H \ *.tlh \ *.diff \ *.patch \ *.moc \ *.xpm \ *.dox \ *.cpp # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = yes # The EXCLUDE tag can be used to specify files and/or directories that should # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix filesystem feature) are excluded # from the input. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. Note that the wildcards are matched # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = ../msyncd/moc_*.cpp \ ../libbuteosyncfw/moc_*.cpp # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test EXCLUDE_SYMBOLS = YES # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = * # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = src # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. # If FILTER_PATTERNS is specified, this tag will be # ignored. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. # Doxygen will compare the file name with each pattern and apply the # filter if there is a match. # The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER # is applied to all files. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C and C++ comments will always remain visible. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will # link to the source code. # Otherwise they will link to the documentation. REFERENCES_LINK_SOURCE = YES # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = NO # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # stylesheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting # this to NO can help when comparing the output of multiple runs. HTML_TIMESTAMP = NO # If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, # files or namespaces will be aligned in HTML using tables. If set to # NO a bullet list will be used. HTML_ALIGN_MEMBERS = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. For this to work a browser that supports # JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox # Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). HTML_DYNAMIC_SECTIONS = NO # If the GENERATE_DOCSET tag is set to YES, additional index files # will be generated that can be used as input for Apple's Xcode 3 # integrated development environment, introduced with OSX 10.5 (Leopard). # To create a documentation set, doxygen will generate a Makefile in the # HTML output directory. Running make will produce the docset in that # directory and running "make install" will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find # it at startup. # See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. GENERATE_DOCSET = NO # When GENERATE_DOCSET tag is set to YES, this tag determines the name of the # feed. A documentation feed provides an umbrella under which multiple # documentation sets from a single provider (such as a company or product suite) # can be grouped. DOCSET_FEEDNAME = "Doxygen generated docs" # When GENERATE_DOCSET tag is set to YES, this tag specifies a string that # should uniquely identify the documentation set bundle. This should be a # reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen # will append .docset to the name. DOCSET_BUNDLE_ID = org.doxygen.Project # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compiled HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING # is used to encode HtmlHelp index (hhk), content (hhc) and project file # content. CHM_INDEX_ENCODING = # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER # are set, an additional index file will be generated that can be used as input for # Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated # HTML documentation. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can # be used to specify the file name of the resulting .qch file. # The path specified is relative to the HTML output folder. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#namespace QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#virtual-folders QHP_VIRTUAL_FOLDER = doc # If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. # For more information please see # http://doc.trolltech.com/qthelpproject.html#custom-filters QHP_CUST_FILTER_NAME = # The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see # Qt Help Project / Custom Filters. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's # filter section matches. # Qt Help Project / Filter Attributes. QHP_SECT_FILTER_ATTRS = # If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can # be used to specify the location of Qt's qhelpgenerator. # If non-empty doxygen will try to run qhelpgenerator on the generated # .qhp file. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files # will be generated, which together with the HTML files, form an Eclipse help # plugin. To install this plugin and make it available under the help contents # menu in Eclipse, the contents of the directory containing the HTML and XML # files needs to be copied into the plugins directory of eclipse. The name of # the directory within the plugins directory should be the same as # the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before the help appears. GENERATE_ECLIPSEHELP = NO # A unique identifier for the eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have # this name. ECLIPSE_DOC_ID = org.doxygen.Project # The DISABLE_INDEX tag can be used to turn on/off the condensed index at # top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. DISABLE_INDEX = NO # This tag can be used to set the number of enum values (range [1..20]) # that doxygen will group on one line in the generated HTML documentation. ENUM_VALUES_PER_LINE = 4 # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. # If the tag value is set to YES, a side panel will be generated # containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). # Windows users are probably better off using the HTML help feature. GENERATE_TREEVIEW = NONE # By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, # and Class Hierarchy pages using a tree view instead of an ordered list. USE_INLINE_TREES = NO # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 # Use this tag to change the font size of Latex formulas included # as images in the HTML documentation. The default is 10. Note that # when you change the font size after a successful doxygen run you need # to manually remove any form_*.png images from the HTML output directory # to force them to be regenerated. FORMULA_FONTSIZE = 10 # When the SEARCHENGINE tag is enabled doxygen will generate a search box for the HTML output. The underlying search engine uses javascript # and DHTML and should work on any modern browser. Note that when using HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) there is already a search function so this one should # typically be disabled. For large projects the javascript based search engine # can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. SEARCHENGINE = NO # When the SERVER_BASED_SEARCH tag is enabled the search engine will be implemented using a PHP enabled web server instead of at the web client using Javascript. Doxygen will generate the search PHP script and index # file to put on the web server. The advantage of the server based approach is that it scales better to large projects and allows full text search. The disadvances is that it is more difficult to setup # and does not have live searching capabilities. SERVER_BASED_SEARCH = NO #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. # Note that when enabling USE_PDFLATEX this option is only used for # generating bitmaps for formulas in the HTML output, but not in the # Makefile that is written to the output directory. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, a4wide, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = a4wide # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = YES # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = YES # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO # If LATEX_SOURCE_CODE is set to YES then doxygen will include source code with syntax highlighting in the LaTeX output. Note that which sources are shown also depends on other settings such as SOURCE_BROWSER. LATEX_SOURCE_CODE = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load stylesheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = no # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. # This is useful # if you want to understand what is going on. # On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = NO # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # in the INCLUDE_PATH (see below) will be search if a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all function-like macros that are alone # on a line, have an all uppercase name, and do not end with a semicolon. Such # function macros are typically used for boiler-plate code, and will confuse # the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. # Optionally an initial location of the external documentation # can be added for each tagfile. The format of a tag file without # this location is as follows: # # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths or # URLs. If a location is present for each tag, the installdox tool # does not have to be run to correct the links. # Note that each tag file must have a unique name # (where the name does NOT include the path) # If a tag file is not located in the directory in which doxygen # is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = buteo-syncfw.tag # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option is superseded by the HAVE_DOT option below. This is only a # fallback. It is recommended to install and use dot, since it yields more # powerful graphs. CLASS_DIAGRAMS = YES # You can define message sequence charts within doxygen comments using the \msc # command. Doxygen will then run the mscgen tool (see # http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the # documentation. The MSCGEN_PATH tag allows you to specify the directory where # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. MSCGEN_PATH = # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = NO # By default doxygen will write a font called FreeSans.ttf to the output # directory and reference it in all dot files that doxygen generates. This # font does not include all possible unicode characters however, so when you need # these (or just want a differently looking font) you can specify the font name # using DOT_FONTNAME. You need need to make sure dot is able to find the font, # which can be done by putting it in a standard location or by setting the # DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory # containing the font. DOT_FONTNAME = FreeSans # The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. # The default size is 10pt. DOT_FONTSIZE = 10 # By default doxygen will tell dot to use the output directory to look for the # FreeSans.ttf font (which doxygen will put there itself). If you specify a # different font using DOT_FONTNAME you can set the path where dot # can find it using this tag. DOT_FONTPATH = # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # the CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT options are set to YES then # doxygen will generate a call dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable call graphs # for selected functions only using the \callgraph command. CALL_GRAPH = NO # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then # doxygen will generate a caller dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable caller # graphs for selected functions only using the \callergraph command. CALLER_GRAPH = NO # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are png, jpg, or gif # If left blank png will be used. DOT_IMAGE_FORMAT = png # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of # nodes that will be shown in the graph. If the number of nodes in a graph # becomes larger than this value, doxygen will truncate the graph, which is # visualized by representing a node as a red box. Note that doxygen if the # number of direct children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note # that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. DOT_GRAPH_MAX_NODES = 50 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. MAX_DOT_GRAPH_DEPTH = 1000 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is disabled by default, because dot on Windows does not # seem to support this out of the box. Warning: Depending on the platform used, # enabling this option may lead to badly anti-aliased labels on the edges of # a graph (i.e. they become hard to read). DOT_TRANSPARENT = YES # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES buteo-syncfw-0.11.10/doc/doc.pri000066400000000000000000000006661477124122200163370ustar00rootroot00000000000000DOXYGEN_BIN = $$system(command -v doxygen) isEmpty(DOXYGEN_BIN):error("Unable to detect doxygen in PATH") QMAKE_EXTRA_TARGETS += doc doc.target = doc doc.CONFIG = phony doc.commands = cd $${PWD} && $${DOXYGEN_BIN} Doxyfile doc.depends = FORCE QMAKE_CLEAN += $${PWD}/html/* $${PWD}/buteo-syncfw.tag htmldocs.files = $${PWD}/html/* htmldocs.path = /usr/share/doc/buteo-syncfw-doc/ htmldocs.CONFIG += no_check_exist INSTALLS += htmldocs buteo-syncfw-0.11.10/doc/doc.pro000066400000000000000000000000461477124122200163350ustar00rootroot00000000000000TEMPLATE = aux include($$PWD/doc.pri) buteo-syncfw-0.11.10/doc/src/000077500000000000000000000000001477124122200156355ustar00rootroot00000000000000buteo-syncfw-0.11.10/doc/src/Buteo-SyncFW-SystemContext.jpg000066400000000000000000004033541477124122200234240ustar00rootroot00000000000000ÿØÿàJFIFHHÿÛCÿÛCÿÀ^7"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?þà¾:|t‹àŒ_ ­­¾|Gø»â‹¿åøaàŸü0—ᥦ¿}¯Ú|4øñgP¼¼Ô>,üGøWàÝ;GÓ¼ð¯ÅwW^+KÙ¯SO°°Óï'¼?8ÿ†•øÍÿHùý¯ð´ý‚?ú7¨ý¥ä³Á>ìï|iÿ¬ûoWçïíáÿwø‰û!þÕ?¿f‡¿²F‹ñÑ~ÿÁ3|aÿ;øã}gö‹ÿ…Bmþ|8ø­â_‡ž<ð^‰áŸøR_Æ­â[ #Ãâ?Ý7ˆ,¡×õ+ø<9ue¡ÚE?‰”ô þWã7ý#çö½ÿÂÓöÿèÞ£þWã7ý#çö½ÿÂÓöÿèÞ¯ÊÁÀ_ 4±Ã/ |´ñ_€loþÅ>Ó¼AÅôÿ…ÇðÒëöñøg¦üKø)â¿|ðçÃOxÃ’ÛU]óJøñ÷á·Æ=vïCñgŠ|ðÆ? 4k?k?"þØð[¿ÚçâOüÛö¯ÿ‚ƒ~É¿uÏٓᾤøZý’?k‹ßˆ~/Ýø«ì·ÿ†ÿeoˆ>ø…û>øïÁo©|8ñî­á½3ÄÞ!ÒídðÏÅχ«áZÜEñ3Gøƒ§Å¡Ú€B?ðÒ¿¿é?µïþŸ°GÿFõðÒ¿¿é?µïþŸ°GÿFõ~hø7þ á²·ÄÚÛÀß<¤|<ƒöÆÔcÏ‚šÆ·ÃŸxóÅ:FŸ};ê~h¾!üYøwâÏê¶VÚ7ߟ ¾~Ñ|Mÿ ‡5ÝsÁ>ðÓxŸWð¯Í~ÿƒ‘“âçÁ?|QøûßøËÄ·ðNOÚkþ _ñ“Àþ;ý ì>Øü5ø1û2üeñ÷ìý¯èñ}ÁÿÿÂÏñ_ˆþ'|6ñ Ó`ŸÃžŽÏÁ‹câ)Ò}^ëþXÀ?p¿á¥~3Ò>kßü-?`þê?á¥~3Ò>kßü-?`þêü`Ñÿàâ=KÇ$ðw‡þÿÁ=¿hŽ×ZWì÷û |xý¢ô‚:ÇŒ^5ø_ÿ Çð§Âÿt/ ü:±øOû,øçÀ<†¿þÀ5O…þ6êÿ¶ï…> þÌþ>øûðÞçã…Ïìóð;â×Ãï|1øùû*þÏ·^ ñ7‹~ü øMðçÄ? þ$êµg‡|Eá3¥üÖ>4Øë|qáh¿üYãO|?Ò<'á'üÇö‰ø·ûO6‡qðá—þ |ýž¿oo~ÐÚ=ׯÏxsÄçÄ/Ù³Â?°_Åï x“á½ïí-û(þÌ—6>‡ÀÿµßÃé:ÏÆÿñ,?¾*üA×õ-'Áÿþk´êßü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_›Zÿü+â¿ÄÙ þ ¯¬h_¬>~П±ì/âïÚCáÿŠü7©üCø“àˉügð¯ö›¿øSâ]"OÚ#ö^ýωµŸxïötñ(ñ-Ãoˆo¦‡OÓt¿ˆ5•¥û|TøÑ{ñ›âíÄ5sá~…ã/…ž…á jzG†~øoVšÃ_²¼ÔaÒ|I©xªbt ý­MfÛ¯üø‹ð×á–‰àïøsÁ_~$ü_Ó5ÛÏx³QžÛ_±Ô4ï‹¿<©ËðØøBÒ|)ã/ i:“àñ6ã]RWÁ¶Pxo_ѼCm¡|6¾økN-(ɸÚIµiFRVm>h¦åu¢š‹jÒW‹LI¦ÚÖñ²wŒ’Õ_FÒRßWÒz;4Ñô×ü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_>ýª¿ntø‹ÄÿøÓö×o„wßðUoŠß²µÏ‰üUàø&¥ûÚ|"µÿ‚—|Cý¼ àIø{…ÿ"±ñ=Χø?á/‡üsâ¸n,lþ9]Åã_ÜøƒáCMy©úÿíÿSý¡åø ÿÎñ·ìÍð3á­·ÿoφŸ²·í«Ù|Pøá«hÂï|Gý­?à™u߃öú¦û:üK¸ñ½ÇÄ ¿o?áî§ñ#þ¯Þ|7ðž•â‹>𯌼_¤ø{᮫#?Mÿá¥~3Ò>kßü-?`þê?á¥~3Ò>kßü-?`þêø;Pÿ‚Ééº/įÚ-WöUøñuû8~ÏQþ×ö~"ý ü7ð¿ö–Ôt¨µŸØ“Aø¥¨üaºñˆµïه¿²¦›àÍWÄ|uðëáæ³áÛ â'Œu߈2xWÂþ øuá-CYÕ#ðïÜ¿³Wí%ñGâ‡ÄOŠÿ¾>|Ð~|iøWào‚ÿo<9à¿‹ããƒu_…ÿ/¾,h~ÔbñÄŸ~ÝZxÏNñWÀߊ>ñÏ„¿á»Ò´[Ý OÔ¼7ãx_Óµ†¹ÿ +ñ›þ‘óû^ÿáiûôoQÿ +ñ›þ‘óû^ÿáiûôoWçÏÁ/Œ·¼ká{ÚCÇŸ—ö…ño†¾+Gmû/|RøQû5èÿ±÷Ç_‹øWãOhÿ ÿcOÚ‹àG…¯>!ü:Žk Üø·ÃÓ~Óž+øµñ?Yø-áOßëŸ4ŸiÆ©à¿CðU gâF¯ðvÇö_ýžôÏŒZ_ÇŠ¿¾ x;Äž5øÏ/Â-#Oø‘ñCöøçûzx³MñH·øOñûLÓ~|&øð’ÛÅr鶚߈/5o‹^%Óì<0¾!øY†¾ }‡ÿ +ñ›þ‘óû^ÿáiûôoQÿ +ñ›þ‘óû^ÿáiûôoWÈÿ ¿à¤?¾9ÝÉáo‚Ÿ±†ãO‰¿ 4ëµ/€µOÚW@ðü z‡ƒÿjÏÚcö>¹ð—ÀOêÿ äðÿÇø—âì}ñ÷WðlÿnfÏ \x'Að®©ã/xZñ|Ó¾7ð¯ÇÛ×Ä~ý±i)~0~Üš?„ÿfo?·ßŠ|7k©ø/þ c}ûüVøOûþÛ~Áû>x7Gð¯Ãþ ‰u/¿ ¯ü+/‰üj|uuã¿ ø“Ä6_/-¯¼oâÐØøi_ŒßôŸÚ÷ÿ OØ#ÿ£zøi_ŒßôŸÚ÷ÿ OØ#ÿ£z¿;¼uû~übø_ñöý¤5_h~'ý‰õþÔ ?eÿ Ùhþ“ûSöƒý¾Ùxê]SNñV—bž%ñ,Ÿ¼kà_Ûáå–yâ OEŠ/Ùûálþ ±Mcâvµþ˜X|oøwû?xcöhøSûVþÓ?ôOÚ➃ᇾ—â?޾ü/ñWíñKÓüá¿?ÃÍwá[oøƒ^ñ·‰´Vo |9ðûÁ§ê>1ðþ‘¦èÖQêš=” ðÒ¿¿é?µïþŸ°GÿFõðÒ¿¿é?µïþŸ°GÿFõ|áûjëß¶‡ñ‹J»ðOŠ?j/‡ÿ³Â;P‹ÆŸ±§Â_Ù“ãáøÓ‰|bþ,±øßðããǃ>%üHÖþEà¸>Oà?öjð׌µvˆö^-ñ/‡¼¯&§{¿ðPx[Ä¿ ¾ ¦ŸáŒßþ+ÍûOð—âu‰ü!à/Úáí ðÓÆ_üoûMxS@º³ñ¥ßÃÿø7ÿiOX|0›ÄÞ?’ïOðgíPø™¦OñNÇSÐÀ>ÿ†•øÍÿHùý¯ð´ý‚?ú7¨ÿ†•øÍÿHùý¯ð´ý‚?ú7«ò+ÆŸðVïÛ'Ç^ »µøKû'|!øGñOCý¨ÿd‡~!ð?Æ?Ú“WÑ~%xÁ_?nïƒÿ³=ß„~5ü-Õdû¯xCÄŸÓÆñxwIñ÷Á;ö„øáoê¾#øW㿊þ |7øsðÃã7®üFÿ‚Òjþ Õÿk]Kß±ŸÆ_|$ý™¼)ûoKeñZÛÂ_´×‡ü#â?ˆ_°·ƒ>.ë_´|PÖ?dEý–| ðçÆ8ø)âÿ… ñï‚ÿiÏŒÞ5oÞøoJ×~é–¡©éz(èÇü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_:ü<ÿ‚„üJÖÿiþÏßÿfm3À0j´ï?d=oÆžøçmñþ¯Œö?± ÿ‚€|:°ŸÃ— <&©áß~ÌI=ÇüA¦ë2Åà‹²iÿto‰~¸¿ø“¥Z×µÚ§ãŸÇ?ÚÎ?…ß´£~Ξý¼ðëáÇþ^ü7ø_â‡uKàÂÚ+Å~4øõ®x×ÂçĨ¾jð|`‡áNˆ>øßá±á}GáÿŒüG{â^4Z˜ôü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_ üqÿ‚´x§àƒ>2ügÔ?f Moà„µ/Ûßá¿Á¯CñÊ+|SøóÿùøYûJ|HøŸàÏ|5ÿ…Q}kð—áÏŒ®d_žðÅ |NÖe»ð–™ªx‡áއcâ¿‹Üþßÿ¶Å·íÃû(~Ésþ;Þx¯ã¯Ã;¿Œ²\þÒú¯Ž£×ÿgOÿ³'üǾ¶Ñ&³ý—à:OÅ/ x—ö"øËâŸørÎþß@Å þü9ð÷Åøã÷޾&|ýÿ†•øÍÿHùý¯ð´ý‚?ú7¨ÿ†•øÍÿHùý¯ð´ý‚?ú7«áÙÛþ ßãŸÚKÂ~!x'ö#øÏÃý+àEïí3ðsźç…ÿi‡~ø§ð«HÖ<-%LJõˆ?¿d¿ƒ¿ì¾5x¿À'_|$ðgÁ¯Šÿ´7ÃÚhúÄZ—ÆhV£Åo×üýºþ=þѶ'ì}kðÛáçÃ}7ö ý¥?eOÛ#ãdžü[®|OÖ×â+ðâoì£á/…ÿcømÿ -í|%³oñFºð·ÃCâí¦¥­øãˆ¼Wñ+þÿÄO‚ÚŸ€}ÿ +ñ›þ‘óû^ÿáiûôoW!á¯ÛGÇ~0Ö¾!x{ÿ°wí{¨ë ü_eà/ÙÿÂMû Z`ø³Qðþ'Ùé_h¾ý´­muO;Àßü®}»FŸQÓcþÙþÌ–ñ5;UÓìnø³Ç?4/ø)'À/†¶|A'Á¿ŠŸ±íwãŸü ¹Ðþ¿…lþ&|øóûhø“£x–/ÃñZßÄ—…¿iOx_ÄÚ÷ÄmGáíæ¤øNÿKðNâKc]׺/Ù«þK7üþÎ÷ÁúÁ± ðÒ¿¿é?µïþŸ°GÿFõðÒ¿¿é?µïþŸ°GÿFõ}{E|…ÿ +ñ›þ‘óû^ÿáiûôoQÿ +ñ›þ‘óû^ÿáiûôoW×´PÈ_ðÒ¿¿é?µïþŸ°GÿFõðÒ¿¿é?µïþŸ°GÿFõ}{E|…ÿ +ñ›þ‘óû^ÿáiûôoQÿ +ñ›þ‘óû^ÿáiûôoW×´PÈ_ðÒ¿¿é?µïþŸ°GÿFõðÒ¿¿é?µïþŸ°GÿFõ}{E|…ÿ +ñ›þ‘óû^ÿáiûôoQÿ +ñ›þ‘óû^ÿáiûôoW×´PÈ_ðÒ¿¿é?µïþŸ°GÿFõðÒ¿¿é?µïþŸ°GÿFõ}{E|…ÿ +ñ›þ‘óû^ÿáiûôoQÿ +ñ›þ‘óû^ÿáiûôoW×´PÈ_ðÒ¿¿é?µïþŸ°GÿFõðÒ¿¿é?µïþŸ°GÿFõ}{E|…ÿ +ñ›þ‘óû^ÿáiûôoQÿ +ñ›þ‘óû^ÿáiûôoW×´PÈ_ðÒ¿¿é?µïþŸ°GÿFõðÒ¿¿é?µïþŸ°GÿFõ}{E|…ÿ +ñ›þ‘óû^ÿáiûôoQÿ +ñ›þ‘óû^ÿáiûôoW×´PÈ_ðÒ¿¿é?µïþŸ°GÿFõðÒ¿¿é?µïþŸ°GÿFõ}{E|…ÿ +ñ›þ‘óû^ÿáiûôoQÿ +ñ›þ‘óû^ÿáiûôoW×´PÈ_ðÒ¿¿é?µïþŸ°GÿFõðÒ¿¿é?µïþŸ°GÿFõ}{E|…ÿ +ñ›þ‘óû^ÿáiûôoQÿ +ñ›þ‘óû^ÿáiûôoW×´PÈ_ðÒ¿¿é?µïþŸ°GÿFõ{·Á߉úÆï„_ >3øRÏXÓü/ñwáÇþ'ønÃÄ6öVšýŽãï é~+ÑìõË]7PÕ´ëmbÛNÕ­¡Ôíì5]NÊÔž;]BòŽæOG¯¿àŸ?òa±ýšìÕÿªgÁtõíQ@!~Ò¿òY¿àŸ?öw¾4ÿÖý·«äÚ[þñðCö¾ý¾ïÿloÚ ÄúÏŒþêß°z~ĺ¿ìå¤]üEøršÄcãÆ§ñ–ëÇzÇÅφ<¯êÞÖ4gVøwâ?ƒú†®|/âmP»¸ñ«©ØLþ?H~Ú6^>Ô|wûYü0ñ/„<㙿kßaø‹Ç¾Ö¾#øONòÿa¯ÛJ]OûWÁžø…ð¯XÖ~×£¦¡ccö?è?ÙÚ•Õž«qý©kc>¨õÿð…þÞÿôr¿²þ!Æoþ˜5xÿ‹à’ðNÿüiðí¬~ͺ=ŸÅ_xëökø™á­cÂ^;ø¯àÚøëö>Ñõ?~Í#¿ø{à_xsáνyð‡Ã:Åÿ…ü5¿á=NÒO I†uk}KB²²Óíø­cþ—ÿ¼×|'ñÓÀÚ—ì§¢7„ÿhÝ+JðïÅ-ÓâGÆ.ÚO èßö²ðGÃÙ4¿‰w?<'ÇT?u/|Ÿá÷ƒu¯Ës¨k:ÿÚé_øBÿoú9_Ù ÿ‡ã7ÿLøBÿoú9_Ù ÿ‡ã7ÿL€8ÿÁ.ÿa_ˆ¿µ| KÛߊŸ´—ÃßÛÅ×6?~/h2GûNü-Ó#Ѽñ³ÁßðøÿK_†ž:³Òâ:ΧðÜxM<_nó§Œ ×¾ÓpeüØý¤?àÞ_Þ<ð7€¾~Ë^9ðçì»ðwÀ_³gÆÙAðV¯àŒ_õŸ x#ã÷|oñ↳áo‰z?íKðgâ'‰õ?xŸÇšŽ­‚?ioþÒŸ|?¬èþ×¼%ðwÃÚ½¾µu¯~³Âû{ÿÑÊþÈ_ø„?¿ú`ÔÂû{ÿÑÊþÈ_ø„?¿ú`ÔóŽ“ÿiÿ‚}¯‡> èþ3ø*ÿu?‚ÿÿg¬jþ8ø‰áý/â÷ƒÿe½+ÃÚÁ‘ûA|8ðG‹|3ð«ã¼Þ »ðÆ—¬è6ÿ|ã?DÔ`‹û"ÒÊÚÞÚÞ/п‹? | ñÇágÄ¿‚Ÿ´/øJ>|`øã/…¿|3ý§¬hŸð‘xâ‡5/ ø»BþÙðxƒHþ×ðþ¯¨iÿÚz«¦khûV™¨Y^ÅÌ^ÿ_íïÿG+û!âüfÿéƒQÿ_íïÿG+û!âüfÿéƒP)¥Á7?d 3Ÿ|)wàOøÅþ2ÿÂ> xûâíûIü^øß¨§ÂŸXø×áT'í ñOâçŒ>;ø&ÛáwŽ4ëOü6µð?Äo[x Çk7|'‘â‹«½Z|Mþ mû xn}fêÃàö·yqâ•øßާñ'Æ¿ž1—âu¯í'ð‹á_ÀßwÅÇñoÅ m¾.i?þüøL<]¤|K>*Ó5øBøµöHþ,C/¦ôoøBÿoú9_Ù ÿ‡ã7ÿLøBÿoú9_Ù ÿ‡ã7ÿL€5þÒ>5“ÃçÁ<ðç[×®þ7|Yñ/ˆ|['Ã/ø^>4—I³—Åú6¡©h ¤ø.ZxwÃ~·ðï®~Ô“k~ý–,n>%ø—ºˆôO~̳xÿÆ…wà/ßkzoÇ?„¯â¯èþñ‹¼q}à¿ Ý_êiú·ãÏ]øsHxtýKÅÞ šÎmfîÏü!·¿ý¯ì…ÿˆCñ›ÿ¦ Gü!·¿ý¯ì…ÿˆCñ›ÿ¦ @ÊáðF¹âëÍSǬ<1㯉>5ðWÃÝWã‰>*'ü³Æºüßô¿þÌ~ñT_o.5=fÆËM²ø!á¿ö¾Ѿ'>àÓ4Õ®¬WÇšÍõõ×êüÇZ¾Óü]ðᆱ㠭^ÇÀ_5à߇|YâÿÙûÄŸô‚ýŠüKáý6?Ùë]×|-=®ã„ñvŸa©ê7·ž#ŸM}!õûÉnn­Kþ°ÿÂû{ÿÑÊþÈ_ø„?¿ú`ÔÂû{ÿÑÊþÈ_ø„?¿ú`Õ¤ªs&¹)Æî.ñšå‡%“¾Òø¤ºÏÞЕ[Þ“²kW{ÝÞï»['Ñhs¾ÿ‚u~Ê^ø'ÄÝ+Ã?¯5Cñ‡Æ´¯‚¼QûPþÔ¾7ø£üiñÿÄüWñ?ÄŸ~Í~4øÏ¯þÏñDÿ¼[â?ˆUÿ‡~éðŒøÃU¸ñ†GÕ˜¼ÒÓþ ÿûÓÍÃéß¼S§N<$žðýæûC~ӇßY|bøKû@øoÁ_uOŒPß|ðÃïŒß~|AøQῃ×Ѿ뾺o…ö^´ñ7‹-uÏmÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨ÿ„/ö÷ÿ£•ý¿ñ~3ôÁ«2†Ø+öM‰Þ1ø·yð5xñ¼}qâ=_ñ·Äo|$“Wø­áÉüñOÅZ7À]wÅúÀß xçâo…o5?üEñï…¾èÞ4ñÆ­ø‡OñF½ªÛx‹\Píg¿ÙGà_ì¹câ[?ƒ>×´™üa&ÿ .¿ãO‰_~0xÏU°ð–˜ú/ƒ|;7Žþ1xÏǾ4‹Á¾Ò%¹Ó|à{mzø.Æ÷P¶ð¾‡¤Å¨^­Çÿ_íïÿG+û!âüfÿéƒQÿ_íïÿG+û!âüfÿéƒPÿ ?a¿Ù«à÷ôøÂ^4´Ô|Ý·Ã x‹ãÇ_|'ø=î‰}áy£øð?Ç¿¼MðgàfÏ êš¯ƒ­á€¼Ö Õu_ص·†õ;í.ã’ý›¿a/…¿þüð׈m´Oxÿá7ÇïŒßµm—Žü;¡ë? ´/øho¶?4ˆž-Ð<§øËÄPé¾·ð7í ñ#á¯<âÏxÿOðw€äðÅŒzާ¯øC@ñ%—Mÿ_íïÿG+û!âüfÿéƒQÿ_íïÿG+û!âüfÿéƒP1âø&ïìiã`—[øM©lÿ„ƒâçˆ5Û=âÇÆ éŸWã·ÆŸþÑ|%ñ{HðÇÄMKøÑð»Å_~ øÛÇà÷Å«?|(Ñ/ø+Ä_¼!ák};Á^-ñ—Æ?ø@»¿ÕõË][Å´ÄÏ|aø½¨Ü˯ê¥ßÙücñâ/Œõù´ˆ®#Ñtˆõ¦Ñ±Ó4›/ÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨ÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨GþöTÿ†wøGû(7Á¿Éû?|Ô¾k >M©x–kO jß¼Sáÿü,Ôâ×fÖßÅZ½þ‡â_ iZ†±w¯ëº¬þ7õ?Ç’x›Lñˆ¬õ_«+ä/øBÿoú9_Ù ÿ‡ã7ÿLøBÿoú9_Ù ÿ‡ã7ÿL€:_Œ¿²'ÀïŽþ'µñ¯Žl>$èþ/ƒAÓ|)}â„~?~Ï:÷Š<#¢êZÞ±¢ø;ÇÚ§À‰ß ï>#x;DÕ|OâCEð—fñ‡t›¯ø¢m;L¶kfÿ–°ýŽü ¦þÔÿh-6ÛÁú'‡gٓijwÀφ^𾆾°ñ§‰|%}âe¼Ko¯Kc{£ižøqàoü0ð~›àÝ|;Ó/~*Kÿ ˆí>$ŤøNOøBÿoú9_Ù ÿ‡ã7ÿLøBÿoú9_Ù ÿ‡ã7ÿL€<ºø%_ì3oiâ‹(>xÊ+oè¾ðÞœ‘þÐÿ´ª·Â¿ øâ·„~9xGÿ³œÃâø›ö[ðÿ‡¾.ü?ðÄ /Dý›¤øU¦[xÀ¾ ¼[_ø¥t°ê¼gÿÜýŒ¾!kß5ÿü$Ô5¦øãáÏ‹~ø‡áÙ>+üg´ø}<¼¨ü6øÑâ|/Óþ!Ú|7øañ3⇵oÞ5ø½ðþø©¯Øëšã_øÊ[gSžë§ÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨ÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨¾ÿ†\øÿ þÇü ßñp?á ?á¨ÿ·ÿá&ñü—oøeøbŸøN²¿á!þÅÿ“eÿ‹iÿÏöoü!ßó9Â=ÿ þ*ºÂø­û~Ï?> i>#ø3[ÕüQ¦Ùh:^¥“ñ3⧃|ãýºÅ߈<- üeøoà¯xwáÏÇOø_[Ôu-OÃ~øÍá_èº Þ§ªK¤ØYJûí÷ü!·¿ý¯ì…ÿˆCñ›ÿ¦ Gü!·¿ý¯ì…ÿˆCñ›ÿ¦ @ÇŒà›±‡üAñÄ^/øAw­ÏñÓ¿¼#ñCŸâ‡ÆIgñûÁ×^øÝâ|7²ømðûáOÄ_Šþ¿Ô4/|XøUáüPñ=¶¥©Ëªø¾æëQ½¸ŸÓ|û ~ÏŸ¾0xsãï‹ü«\|`ð–¯ð_[ð÷Ž´/ˆ¿¼ªé·¿³ô¿´ø_ CàÏxMŸI°°ý©ÿh?ø³@»±ŸBøŸàߊ^!ð?Å 7Æ^]7C°æá ý½ÿèåd/üBŒßý0j?á ý½ÿèåd/üBŒßý0jç¼7ÿäýŽ|'mãý?EøS©¦ñ'Ã×¾ ×ü9©üWøÍ¯øWGøw«ø¾ÓÇ^#øSðßšÿÄ=OþxËÄÖ6wž<ø;ð“KðOÃY[C¡øÏÂzî„‹¦HÑ?dÙóÃ?¼ñsÃ^ Õ<9㟇z÷Ç/øBïÃÿþ&hš™uûJIá«ÿŽZTþÓ/ø/Â_¾~ÉŸ¾7ƒµO\éZ…Ž¡ñ³âwìûñ3Çÿ|Uñ‚ûâ#hcÂú&‡û3ø#@ð÷m~麅–¡ªx³Ä:ŸÄj CFðÖ…?ì›e­jíûBüiÔ|9âè¿´‡Çk/‹>ðï‹ôæÑ|]kà]öø ð?CÕ|G Ë#Þx~ïÅò|½ñ¶Ÿ êñØø‡FÐ%ðÿ†5 í(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š+ä/ø'Ïü˜GìCÿf…û5ê™ð]}{_!Á>äÂ?bû4/Ù«ÿTÏ‚èëÚ(¢€>Bý¥ä³Á>ìï|iÿ¬ûoW×µòí+ÿ%›þ óÿg{ãOý`Ûz¾½ Š( Š( Š( Š( Š( Š( Š( Š( ÍÚö‡ý¦´/ÚkÄ_> x‹àO„¼/á/?þ'ßßüOø7ñ⎿¬kÿ~ ~Ð~º³³ºð§íðoNÒ4}#Nø7¥Moo6•¬^ÝÞë„’jÁ µ°àGÆ¿Ûàãþ.çì„ íÿ ñ›ÿ£š›ñ´göøø·è?d/ÙŸü<ß·7ùüëMÀŸóÞ¿pááÌχ2ì×{|]­ûZ¿[ÇRçöXüU~åU:qå¥N÷`¯ËÌï&ÛóqŠÐ­8ÆvŠå²å‹Þ1oW÷o©KþGíïÿE{öBÿÄEøÍÿÑÏN_Œß·»gþ.÷ì„?îÑ~3ôtV€8õ«¹Àì?Ïëþ5ïK€øMm”êÿê?2Óÿ/ ~µ_þ~ä°ÿäLÑñ‡ö÷8ÿ‹Áû!sÛþã7áÿ7ÓR‹Ÿ·¹8ÿ…Åû!â!üfÿèê­„ÿ/óþ{Õ¤^ž§üþëž\ ©Ùev¶ïëÙÿ6t­b?ççþIþDÂ_Šÿ·»gþ/ì„?îо3ôuÔÃâ—íîqÿ—öBç·ü2ÆoÃþo®º$^ƒ°ëþ}êÚÿ—ø×<¸+…ÕÚË4è¾»˜óYªÄV²¼õëîÃô‰Ë¯ÄßÛàœÂçý½ÿãþ3qÿ›ÙOÿ…—û{ÿÑhý¿ñ¾3ôv×^ƒÜÔŠ2}»ÿŸzç—ðÒÛ-ÕÿÔf?OüºÖ+?þKþDå~þÔ¿ü5ûM|6ø1ûEøëàOˆ|/ñsáÅ={ÂWÿ >üOøk¯Ú|Bð/Ä€Ðt+Ë­wãçÇ:ÿGññ{\šâI´¯®›{¡iòI­.f¶oÔ ü ø ?ã=ÿaSëáÏŸ§Æoؾ¿wëò^$Áá°Ö7 ƒ§ì°ô~¯ìéóÔ©ËÏ„¡R~ýYÎnõ'){Òv½•’IvÑ“8ÊNíÞîÉm&ºYl‚Š(¯ Ô(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š+ä/ø'Ïü˜GìCÿf…û5ê™ð]}{_!Á>äÂ?bû4/Ù«ÿTÏ‚èëÚ(¢€>Bý¥ä³Á>ìï|iÿ¬ûoW×µòí+ÿ%›þ óÿg{ãOý`Ûz¾½ Š( Š( Š( Š( Š( Š( Š( Š( įڳÀ¾(ñ¯íññxoã/ÄŸ„ŸÙ¿²ì­ößøWº_ Kþ¶|fý¶¾Ïý¯ÿ [áWÄß'û'ì³ýƒûûÌþÒ½þÔþÒòôï°q_ð£>'ù¼¯ÚKÿ Ùÿ¡N¾øÑÿ'ïñ{þÍ öEÿÕÍûsÖ ~Jþˆàl-*œ)•NRĦþ½gÆR†™Ž1iN•xSމ_–*îòw”›~F*MWš÷~Îñ‹z´Ùó‚| øŸ×þ/ö’ŸñK~Çÿ×öS?çëVSàOÅü6oí( ëÿ·ì}ýe.ßZúAò>¿ýoð«H½ûžŸOþ¿øWÒO GWÏŒò_ÚƒÿÝŸ+ýýÌ9ŸhÿàÿäO›ÓàGÅÿ7ŸûJ€?êVý?¯ì¡þ5e>üQëÿ £ûKOø¥c¯ëû'óõ¯¤‘zÄš¶‹Ï°ÿ#ò®Yá(­9ñwz¿öüÿ4õþ·. ·{FËorÿà'ÍkðâŸþKö—õÿŠWö9ÿèL=¿úÕi>üS?ózŸ´Èþ¥_Øßÿ¡,×Òˆ½ûžŸçÞ­(ÀÇâ~µË<%ÛŸeÿQØï×ÓüßSNgýßü?ååù÷gÌßð¡¾)ÿÑë~Ó_øJ~Æßý U2|ø©Àÿ†Öý¦Á?õ*~Æ¿×öIÏÿú]N{çþyÿõÕ¤ýxOóü«–xjZ¾|O’xìoÿ4|þþì9Ÿ÷ðÿ——çÝŸê±ÿˆõÏxâv§û`~Ó—>8øgm­Úx'\þÄý‘áþÄ·ñµà¿ë1ÿfÛþʱhú—Ûu‡ž¼ß«é÷ò[ÿc›{V‚×PÕ ½÷ÕðŸí FOíïûO{Åû ÿôžkÖQzç¯ùöà?´×†hïü8ƒMý˜<{áO‡¾=ÄšuÖ±¨ø¢ÒÑ.5EcªÇªøwÂ~*Õ|ñ[Ãÿ<[{©Í¢^XxãÄŸ>3hº}†Ÿªi“ü;¿ºÖ-5ÅÆdÙMGS_/Ž*¼’”å':ÕêrEF+š­[ͨÆ1Š”¾ò­’.5j+EO•tè—ܾý;÷gX¾ý£Î?ã=ÿiüŸúaŸþƒ_βuKŒú¡á']ÿ‚ŒþК.©ãmrãà Óuo ~Án¡âïÚxkÄ>3ºðï…ìï?cÈn5ývÛÁþñg‹.4*;»ø|5ák²[®—¢êWVßø£ágí³wà†qøzóâÄ t¯ ë–Ÿ¾Û~ÐþÑk?øÎOx¾÷Yñ•ûSj:'Œ´?øOÅUÞ‰yàø;ƲµàAo/Ä_ éÉoð‡Âßüyð×í‘®|dѯþx'ö¼×joþÔ?·ç‹|5âÏAâmKö.ð_†%ýˆà > ý€/¼‰5 >è¦ão„~ñ/ˆ¼£Cjÿõøsö×`ñF¿ðGKÕ¼LF]“Òå¶IeE;Ó©y{IAMSþyÆ2º„9.J®¬iò%=ê?ù{Ò]WM¯ëmõ]›»?xWÀß´Y?·ÏíEíÿì/ÿÐgþy§~ÑDãþçö¢çþ¤/Ø_ÿ Î¿üà_~,| ð7íwþ WáŸØßÄ¿~XxãYø×ãÚ—áïÇK_ŸØ“þ )?íšÇ¼y¤ügð§Á;߈Q~Ç6>±©¤|&±ý .n¦ýŸõM:çþûÛ)²?¶Þð‹Àƒâ¿·…öñ{ömÿ‚h|XøÛgíS«ë_ ~=üBøgûh[~Óú^ø¥EûSiÑüWðÛö^ðÿÄ__³oŠþx¯á¿‹<]á/ˆ2½Ñ<%â‹ ãîIa2„¹¿±é(¹TŠ’rp½8F\ÜÚ~ênPå©k8óɤ¢Ó¸Ê¦î£z-=mÓºë禭Ÿ¼vºGÆ;ÏjÞ µÿ‚þÐ×>1ðþ…áßkÞ·ðÏì7‰t_ x¿PñF“á?jÚ ±Óêšn‡âSÁ4Ó|;«^ÚçëWþñEž›qusáýZ;N~~Ðç“û}~Ô¸íÿì+Ïþi>³_ƒÿoŠ¿gütÖ?nß ü3¸ø‡ øgâ6©±ûG|‘¾x#Å?ðpf›§Eâx£Äú>$ð—‡`‹x³Uø³âßëvú·ì˯êüQã [áWµ´~ü1ÿ‚‚jð¾“ð¯Æ<âÿ‡ßi߆ÏñwöŸø‘/Œ>ø“àßÛ[öð‡ìûgâï/ð—Œ>(|nñ½ÏÀ}álj$ø»¦øÃöpñOůëþñ-ÿí/ã·[%q§ƒÊêAMdés)5 :š:^ï#i-*ÆÒ”ãË7îÚÅ9M6¹öïýzé¯Nú~¢/ÃÚã?·×íM“Ûþ?ØWÿ Â§_†´?òŸµ?¹ÿ„öÿè0¯ž¾øöº°ý¦üM⨼M«ø/öX›Åÿ®5¿†¿þ h¿ücã[ëù5ÕÑüMðq4O Ú럼}â)´Ÿèz'‹~<|WÓ-¼ xLø ðSQt¿ÓA‘x¹ÿ?§øÖ«*Ë&›þϧO–N/ž-sZ×”}ëòtNJ-´ß-šn}¤ÿž ¿ ÿh#’oÏÚ£ö"~Âýõ0øWû@ ý¿j®êDý„¿ú «ßÑy°ÿ?¯øÔÕŒ²¼µ]ýNº+?óþ•ÃÚOù™óâü)øþN?á¿j¯ø¡a/þ‚Ú~ü~9'öþýªñÿb/ì#ÿÐ[_A"ôõ?çôïVÑG°ëþ}ÿƹå–åël-+¿'§â u·3Ôùá~|| göþý«rêEý„ù²Ê~ üz'ðßÿµw¿üPß°‡ÿA]}ƒ¿åþ5ió?çôï\ÓËð)Ùa©«nìÿϧçse).­Ÿ8¯Á'¯üö¯Çýˆß°_üBª™~ üw#'þ ûXóéàØ?§þ!M}&‹ÐvϽO\ÒÁàÕßÕéÛ¢³ÿ?é\9¥Ýÿ_ðߟv|Î> |v$øxícïÿ?ìÓÿ¢¼_ö€¸øïû4xÃÿ¤ý´hÿˆ_‡>8þÌzŠ<âÏ~Æo¡x§Àÿ?i?„ÿ ¼}¢ÜŸ~ËüO Õ÷‚¼]¯Ã¥]è¾4ðåí–¬Ö7‰©B°8ÐdSŽœŸóÿׯ„ÿà¥Ã²6ºoޱßþ¶7À_êk‹†ÃÂWPRTæÓ[¦¢Ú{÷ØjNëWºý?¯¿»?J|/â /øgþ*Ó¢ºƒOñ6…¤x‚ÂÔŠ;Ølµ>ßRµŠî8&¹‚;¨à¹.‹ˆ’PëÒ 6íy—ÁOù#_ ?ì™x ÿQ]*½6¾|Ø(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š+ä/ø'Ïü˜GìCÿf…û5ê™ð]}{_!Á>äÂ?bû4/Ù«ÿTÏ‚èëÚ(¢€>Bý¥ä³Á>ìï|iÿ¬ûoW×µòí+ÿ%›þ óÿg{ãOý`Ûz¾½ Š( Š( Š( Š( Š( Š( Š( Š( Éoò~ÿ¿ìпd_ý\ß·=k¢ð=Oùçü++ã0Ïíññ{Ð~È_².ðó~Üÿçó­´ÿ/óþ{×ôg?øÄò•ÿa×ÿÞ2ßç÷>+ýâ§ý¹ÿ¤D• üëáoˆŸðSOØŸá'Å¿ˆ¿þ"|[Ö|5ãƒÚß¿|Yº›à×ÇKÿ‡ õú^™¬|%¶ø‰ñ£IøiðoÁQxòÃV³}÷Äž=Ól..VúÂK¨µ 'Uµ³û¹§©ÿ?§züÕÿà”þ,ý¥¿oø*?‰?iG⧃?cÏÚS\ÿ‚~ë~ð§€¼YðZ? þÓËû<|-µ"Ѿ$Euáügðމðóâ>¥Cii ëŸ'ñGö®¡r×^-Ó­t›½Óͱ9…áV[Bzµ±3£?m Ê•*kŒ­ •'N­/cNXš8z2«'5Õå§J­iÓ„³§IË´’MY¤ÛçŠi];¾W'm6»i&Ïèó?ä~ui üOõÿëWó7á?ø'í_iÿ'ñWÇÿˆšÆoý³öï¼øñðÿöˆðGÆÿÙkáßÃï þËè1Ç¢üñ§öïÁ¯ˆß¶‡Œ¡ðý„|4ÕgŸëž ýŸ%~ϺŠüs¤ø?ã‡ÆOÀ¶Þ8»ñž‹ñ7ö|ý¥|;á¿xëE×´ÿ€¼_á/‡ž',ºV¹ãß êºv¡ä<ç'[þ±1öpÇMsÊ´\ÞxXQ§$ã)âþ±9RQœ£É‡©(N³RPÝSŠŒ}õ¼WOµvßÅ{E­omúÕÂ/9ì:}úßáR×òàÿø'Wí±¦üý“toŒ?³/†~1|ø5ûS~×8ø­ûøkǾð»ü_øiñ[áå¶…û>xßÄ> ñÇǯþÌ?|Gð×â1ñ/޵¿ ~-AðgÀ¶þ"±›á®—u¯ZjuæšWüïöØÖôÛ­?öø¢þѾ)ð×ü?ã7ìÃàŸë¾<ðµ ¯í±}ûDüMø›ðáÖ˜þ-ñN­ÿoü-ðG‰¼!£xâÆ±¦ZøKÕ¼7i«AãKMNÎÖâ9ç8ö’ŽIм¡ 5)VŠ‹•:dþ¦Ú´«T¦¬œùðÕ½¬)FTeV½œ?çäí7’oÉ«_[ah½çýÀ~ñZÿÁG?cûÃÂÆOøÄÿ5sþçÅ^¿ðµáIȃÿ7ü,î>'Å3ÿ"_¦µÿ"÷üM«ùäºÿ‚}ÿÁP5¯Ú'þ Õñ]ø§¢þÏ~4ÿ‚PjÞ?ø¿£|Nø=ªüMø}ðBÏÁ·V…ñ?Çþ0ø›}ñ6âÃÃÞ/Ñ­­->| þ |TÓ1­E×£^~߇XoªR¡GJÎ¥xº1æ©USŒ¨ÔIJïÙÁrÞiÞPNÍ+Eß™½^Úk²Öèþ¨Pqžç§Óÿ¯þóïìÛûWüý­ü;­øËözñf·ñÀÚ&±y¡¯Äøoñ?Âß¿ýoð«H03Üÿ*þa¿àŸŸðKoÚ›á¯í¡û|Rý¶¼%áߎ:GÀø%6ð‚OŒ~#ñ/ƒüržý©´_ÛgÅŸ>xGEÓµ}bo^ø‹áÁ_éšñ6ßÃOᔟAΕâ–ÕšÚ#ó/ì=ÿMø·ðîßþÑiñËö?ð-•¯Ãý?þ ;£ÿÁL-µ|ñ%м}}®j²O‡>+E øßWOþ”Ô<;á¯/4/ß»ñ>Ÿ NúŒ“̱²+eubêÊÒŒêTN’u°T“©Ë…œWûÝJ²´šT°•æ¥$Ÿ+öqm¯hº¤šzIéïu/Y%ÔþÅÀÉÅ|õ¢~Ö_¼Ss¯ÛxÅšçĹ<ûG¿ì—ãVøOðÛâÅ›üyµÒô½_YðŸÄ φþ ñM§€ô?ÚkZ\^=ø“âùô_…ÿµK´Ñ#ÿÁ+ÿà°ž!ý€ÿeð~ÎÖZ¿Å߃¿³Ž­g£ø£þ/À}sãGÂÿ‚€ø‡Ç>ðÎ…ñÇß§ð·ÂïŸÙc]´ÕôÿþÎZ¶•ãÿßø{ß >)xª_x[ÃÞ ‡ì=þ -ûJü1ñô~øoûxcBðö—ÿ|ÿ‚ƒiß|¯~ÎðBþÀ~->𖦯ÄüLµ³Ûx‹ÄbÏáŸm4íþI¾Ûx“û[Ä‚ f¸Ú’‡.Y^œ=ÍÕ…fÔëAÉÓŠTb¹©8òÔ”“qVNI=¹"—Æ´ÓFº[»ê¯oøsûEïÜð>ŸýóÖ­"ôúŸóí_ÆŸ‡?à”ðQ{OxçÃ×>Ãñ†ÛöPÿ‚µ|4ý¨¿h¡ñcá•Ðÿ‚¢øÛö©ðô¯ÙDËãfñF¿ u]kA»Ç#áÖ“àC¦®•¡^ɤ\\ÜGú ÿÐÿ‚y|~ý’¿l¿ÙÏâÏÁ‹?…üözøûF^h>2øsa¯þßžø‘á[Ûø·ÃÞñ~§¬xËÆ:'€lu-2Ëâ•¶“¬ø1|?o†¼?ã7¶û&–Äs MIÁK/­J3”c)IÏÜæŒíìR|ŽN”cÍNnœ9%$â’~ò{üíóë¿Ý¥öý"Ñ¿à«ðOýnÿö¿ÓtÿÚů¿`ŸøI¿á«­nþü[Óeø]ÿwˆüCá q©x Óþö‰|-¬èËÿ ¼ø×í×±ØG§ý¬kz#j?RþÌß´¯Á?Ûà—‚ÿhÏÙׯ¿ð±> üDÿ„Œx;Æ?ðŽx·Â_ÛÂ^-×¼ âø§¼s øgÅ:öм3®iñ4Ðì~Õö·Xý§N¹´»¸þJ¡ÿ‚1~Üš·íKñ“ÆmðÊßÃ_ ¿kOø*çíY¨þÔ’ÏñámÔ¾-ÿ‚vøŸö…ý“¿iÏ„ž5Óì´ïÝjV÷ž"ñ7ÁOˆþ “¯cuñLÓüw}k¯ø7JеMU¯þeñgüþ åü»öVýž|'û,ø~‰¿?gï^iZÖñgöz»ø½ð»öþ Gã?Œ&—¡øóÆ¿[Áÿ ¼-®þΞ4¸ñœ¾>ý›µ='â?ŒüK§i~/xºëžм–óÂ.~Ó9Â4ªÊÔéWŒ§R5iû8Á¿h’tjkx·*¨âÔbâW$:M+µ»Z-o}WUKw?¾ÕýÿϵHƒ'ØWò/£ÿÁ(ÿkoüUŽ:ìùg¤ü`ø9ÓÄß¶ü,?â'Â{O'ü#ƳµÏŒ¼GµÒòxºsø§àÔ2'Ä¿ kõŸ‡—±XY$üPÿ‚~ÁP¾ü=ý¿¾/|KðN‘û(èÿÿàÚÏŸø¿á|¨|4ñ7Æý?þ à[Ãvד|6ñŽ~>xØüNý• Õ4ß~Ôµ ¶­â½ˆ>½øçñCžÒn×BUs ñMËY(©ÉÙÎÊ05å)RŒbÛÒͤ·æjöJ »s®‹¥îÚ[s^G÷Ôƒ¿åþ5m îzÿŸjþ~~Ê?m/ÿÁQµ_Øá=¿Â¿Ù×Vý§?àþ$¿ø'ð‹â7ìáâ¿ücø+ð'àÅ?h?Ùçá—Å 5?Š?±—|øEáß x»âo‚¯— Íî›á[ÈnyN·ÿXý»î?àž_µ×Áß~Ï´ÿŽ¿eŸÙ;áOˆ<)ãOÚoöÒ~|øíðÓö±ø=ñ3âÄŸ|8ø%ðÇáý®£6‹àßøËÄWí!ûVþÑþÑ>,ÑüYyðïÆ^ñ®µm¥x¶yc+r»á*©89Úó»ýÌ*¨¯Ý§Ìå'JWJÓ„’æ—º¯•2éùÛ¿ÏÓ±ýªüZøíðƒà-¯ÃÛ¯Œ?Ð<ÅŸ‹~øðÕu¹æ[Ÿü`ø¥ª¶à?‡þ³µ‚æïR×õû¸®¦Ž hl4½?V×uIìtM#SÔ-=yFO°äÿŸóÞ¿‹ßÚ³þ™ñÇĶoíñ'á·ìMðÿâì±áø(·üÃö—øAðšköyoøZÿ<ðGÇÿ‚‰ø#á&xïCðþ‡ªüWø‡'Ã[ÿŠÞøÃªü5ðÿÇÍGÂZF¹¬êž-oiW‘gºlÓ]ØØÝ\X]iwV¶÷7eóØË{§M<),¶’i—šŽ›%Õ›³[Ü>Ÿ¨_X¼Ñ;Z^\Àc™æ5jT•E:~ÍBMGâ|Ö”¢åÌãÓåR•ùe}šJË[÷û—ù—Ðwüùÿ=ëáø)€Ç쉯zŸŽ±Ù?ø˜ß?ÏÖ¾ôAϰÿ#üûWÁÿðS!ÿ…¯Ÿú®±Ö?ñ1þW6&W£Y¾´¦—þÿáßÌ#ºõ_™÷ÁOù#_ ?ì™x ÿQ]*½6¼Ëà§ü‘¯„ŸöL¼ÿ¨®•^›_6nQEQEQEQEQEQEQEQEQEòüçþL#ö!ÿ³Býš¿õLø.¾½¯¿àŸ?òa±ýšìÕÿªgÁtõíQ@!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^ÐEPEPEPEPEPEPEPEPä߯1ŸÛãâøÿ«Bý‘?õs~Ý¼Š öç‰ñ€Æ||`õÿ†Bý‘çñŸöè®… üI¯èŽvá<«»úõ¿ð匹ãâ¿Þ*ÛŸúDIwü¿Æ­¢ôÏ_óíQ¢óì?ÈüªÒ/~ç§ù÷¯¤›é÷þ†p]~ïó%Eéè?ÏëÞ­ ïùŸóÞ¢Eè;õ?çÚ¼ã/Æ[oƒ¶ÞÀ^:ø“®|IñÓü>ð§…~? Önµ˜< 㟈ח77Ÿ¼sð÷ÃZe—†>xŠy¦¸ñÝËt¶VvvWRÝ~XÁ9Êü©Æ1QŒ§)JrP„a)JsœåB1‹”¤ã›v4JÿðZKæÞˆö`2qëVQy°ÿ?¯ø×ɉûHüBê?cÚl’?èeýíb*Ê~Ò_ñûþÓ‡¹ÿŠ—ö;þ¿µqάìßÕ±÷õ.Ç«_× ¦OøpÓù¡ÿÃÿ’óüû3ëDÿþÏz¶‹ÀÏùý?ƾKOÚGâ'ýgí;ÛþoØãúþÖ£üõ«iûH|Gëÿ UûO“ÐcÄÿ±·õý­ºÿžõÉ:²ÛêøÕÞù~9zÌ?Ÿõf;_gü?çýkÙŸY¢Ža×üûÿ[Aßòÿ?ç½|–Ÿ´Äœ ~ŵÉÁ?ð”~Æ}ÚäU¥ý¤>%døb_Úˆû~Æ<ÿæÝ~uË:ÎúÐÆ$¶¾¿: åÝÿÀ£þ~Ÿf}h£ÜÿœTŠ2}‡_ð¯oÿk ÃÓøjOþÊ?´w|=â?ü8øv|]®k²Î«¢hšßÅ?øká§…nµ‹/~Ó^,ñSéoâ¯h¶ú„ú/†õ›»;I¦½û °Û˱ÑO¹ëþ}¿Æ¹]e'%j‘šQn5iT£$¥urU„$âùd”’kš-^é£HFÊúk¦>Úi×þ*ÿ—øÕ¤SŽœŸóÿרÑzzóú÷«H;þüÿžõÏ7Óïþ¿®…’¢ô‡_óïVÐsŸN×üÿ:‰€;Ÿóú[EäÃüþ¿ã\³–ý—õøôD{Ÿò?ϽYäÓwüùÿ=êP2që\““Õõ{^_ägŸNŸ_óüýªÚ/æÈüê$QÀì:ÿŸñ«h;þ_ã\“}>ÿëúè4®Òûý zÄŸóùU´^sØtúÿõ¿Â¢EüÏùý;Õ´^ƒ°ëþ}ë–r×}¾Äˆ¿™éôÿëÿ…ZÏëLAßòÿ?çô©Ï¹'-ßW·õäÐdçÓùÿŸéVqžç§Óÿ¯þ/AØuÿ>æ­ ç>œ¯ùþuÉ7Óïþ¿¯Ä zÏúÿ€ü+àïø) ÿŒBñì>8~Ç_úÙÿ§ô¯½Ñx§üóþðgüØcö@ñ¾8~Ç9ÿÄÈø3\8‰^^Êœ×Ï•ÿÃ;¯UùŸt|ÿ’5ð“þÉ—€¿õÒ«Ók̾ ÉøIÿdËÀ_úŠéUéµà›…Q@Q@Q@Q@Q@Q@Q@Q@Q@|…ÿùÿ“ýˆìпf¯ýS> ¯¯kä/ø'Ïü˜GìCÿf…û5ê™ð]}{EPÈ_´¯ü–oø'Ïýï?õ‚?mêúö¾Bý¥ä³Á>ìï|iÿ¬ûoW×´QEQEQEQEQEQEQEQEùCñtgöøøÂ}?d/ÙÿW7íÓ]"/æÈüëžø¶3û||bô²쇟ü<ß·Otè;þ_ã_Ð|í¹[}>º—þq‡‘‰WÄMƒîä‰"/AøŸëÿÖ«h¼ç°éõÿë…DŠ@÷?çm ì:ÿŸzú9ËïÓûű" ÷?ËüÿJùWö’ø…û_ÚkÄŸúÇßµ…}[_~×÷>0²×ÿdŸh^ñ/‹Sö›Ö¿²t_ø¯TðG‡owþÉ_µDwßÚ(ѼñRÓ>Ϧ½åݧټ#«}¶ú m:o°[ÝË©Ùyø™¨¼;|ÍC—IòÆS“¶? ÚŒ ¥9J×J1NRz(·dÿÃ//²ûŸE Àϯè?ÏéV¢^žç?€õúÿZù˜xŸö¿àŸ³o§ügÄÿþƒOέÇâÛ·À¿Ù¯žjïŠןøc#þGz÷*æýÌfº¿øOÇì¶ÿ˜oêÏ¡+ïüü‘ôüKÓóÿéúÖŒKÓ=¹?SÓè¾^Äÿ¶'o?³IÏý]‡ÅÀú~Åçþª¿Š?l~?âÃþÌç¿?µŸÅ!Ç¿üaaãükÏ«£Ö¾ïýƒòZáþïO#HAêïß|¼ý?ê8—€çêÏò«QŒœþêÏë_/¯Š¿l?äƒ~̼óÿ'kñOÿ ª­GâŸÛ,cþ,/ìÈqÏ?µ·ÅAÉéŸøÂƒÈþkÏ©¥ü¸®­ÿ±c¯où‡þ¯ê_+þïþóóüû3›ý¼Æ?fû¯û.²Hø–¿3þ~µö‡ÃUÿ‹yà3Üø3Âà}?°ìŸùë_ŸŸ|ûg|qøm/ÃÑð“öbðÇ™ã„~2¹ý¨þ*ëX? ~-øâ—öoØ?áôœÿnÿÂý…ö϶ì¿í/í?²ê?cþϺöÿ x—öÑðÿ‡´ þ'ì¿yý‡£iZ?ÚÏíuñZßíGM±‚Ëí>Gü1þGäù¾O/•»g›&ÝÇå1^ÒyÍL\hâ]–Pê ˆW­ V&¬¡Êé©é °|Î<®í)6¤–ÑiSån7çnÜËg«ïmÿ^ÇÚ(½~§üûU´^G ÿ#üÿ|ž:ý´ðOü3ßì»Ï¯íƒñ\ïŽóô«)ã¿ÛT€ìõû.~?¶ÅŸ^Ÿ°ÑúVsªÒ»Ä]»»b4ÿÊ_/øa­vkï_æ}rƒ¿¯éþ•ZEè;ž¿çÚ¾HOþÛ‚?gŸÙk¦@?¶/Å‘ÿ¾0 {UÈümûmŸ³Çì±’3Ïíñhqø~¦¹'UmÉYwÿg¯¿þ éýl;?/½™õ àTˆ;þ_çüþµòzøÇöÝoù·ŸÙ[¶sûd|\‡ü˜™æ¬'Œmâxýž?eSŒ`Ãdü]ü?æÄrδoðÖ·OÜVòÿ§×È,ü¾õþgÖh½s×üûU´_ȟ׽|›‹ÿn"xýÿe.p9ý²þ.ýñíVÓÅŸ·!ÙßöPä÷ý³~/þòa§ð®9מ•núûËó§Ûî5„l¯¥ßšÛOëþO¬Pwü¿ÏùïVÑO¹ëþ}¿Æ¾O_þÜüøgoÙ; ÿ†ÏøÁÛþì,Õ˜üWûtžŸ³¯ì›Èïûh|aø'¯ÉÌúÀ zTˆ;þüÿžõò¢ø›öìnŸ³¯ì–8Ï?¶Æéûš¶ž#ý»‰ý?dž8?¶ŸÆ1ôÿ›<ÿ…rOJîîV_ôî¢üã×åÒáoO½]>Ìú­€;Ÿóú[EäÃüþ¿ã_êŸh…þ<ø)¢~Ð?ÿg¿xã'Ä]káŒ^9ðGíKã_ßø[^Óþ üXø¿§Ýê~ñ¯ì­ðwF¸Ò5K…þ{…ñ½µí®§­érÁa¨F&„}º‹Ðw=ϵsûXÍIÅÝÞÏI&›Ií$žÍY÷Ó£³i­Ñ*ÿ—ùÿ=ëà¿ø)¸Çì}âSñÃö9Ïþ&OÀ:ûég§üÿõëàŸø)ÈÿŒ=ñÕpýŽqÿ‰•ð šä®ÿw5ýÉ_ÿvÿ?¸qWkË_¸ûà§ü‘¯„ŸöL¼ÿ¨®•^›^eðSþH×ÂOû&^ÿÔWJ¯M¯Ø(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š+ä/ø'Ïü˜GìCÿf…û5ê™ð]}{_!Á>äÂ?bû4/Ù«ÿTÏ‚èëÚ(¢€>Bý¥ä³Á>ìï|iÿ¬ûoW×µòí+ÿ%›þ óÿg{ãOý`Ûz¾½ Š( Š( Š( Š( Š( Š( Š( Š( Ê‹#?·ÇÆ0?èпd/ý\ß·UuH½=ùý{×1ñTÆ||dõÿ†BýGçñŸöꮵ üë÷þ ñ‹åk¢úïþ¬1g™]~þ£ïË÷rGúû‰wü¿ÏùïV”`{žOùÿ<æ£Eäzò?ÏøÔÕô3–ïî_×ß÷™Q“ì9?çü÷¯–iÿ ö0'þŽsÄ âþÖGù×Õh½s×üû ùcö”ñpcAûNx”æ~ÖxþUÃYûøUß—_¿ûþó’ÿ ¿ô–{ œúp>§üþµ£ÿ@>½Ïùõ5R%éžÜŸ©éô?áZ1.1íüÏùþUôµe½ºè½ÿ/ó9Ëq/ôéÜÿŸCZ1.qïÇà=?ÏaU"_ÓÄõçüõ­ÆÓñÿ?ZóªÊ÷óü—ô¿x«$¾ÿRe#ó?çô¯”¿jßÚâ7À-/ÁÓøàÆ«ñ×ÅWì$øwo¾|¶Ò£ÒZÏYø‹¤ü%ðÅ‹wÖÚÀÔ¯nt§Ð~ÚÅá½hüEø•ð¿N›JÖ¯~°‰søœ~¯ùö­—8÷þCüÿ*ò±Ju)Ê0ªèÎZªŠ1›š •ši8½¥fÜe%%JÉê®»^߉ùÇí‹ñ5¾'üøsq¡ü=ðç€|iÁ+˜?jÿèß.þ ~Ð÷_´o j—z/ìÝeàËx+Ã>4¿×.|%à$øíûBx[Æ–¾"6·>økñ·Ã©³«|‡ðëþ cûOEimsñ†/Ù‡FÕ¼KñölmÂú‡<}™Á¿ŒŸ·ßìýû-xƒâ/ÂÿŒzwÅ߈ ?jO†¶ß ¾0êšÍ¿Æ Vø©øÇéðþOþÏ—¾ñœšv—ûÕôüÿßçÞ¼;Ký’e](üS:_ìÍû>é§ã¥¥ÅÆãaðgáÍ™øÇcw5íÅÕ—ÅCoá¸ÿáaZ\Ï©jÜ[x»û^¦¾½’Dwº¤ðq41§ cÓÚß™8¨óÁ*jÑmTtäš‹š„ܦêÔŒdR”ñíç³WÕê®»uÓDÝÿ)|cûk|añ§ü#Å þükýüáï…:OíÝðÂ7ß xOÃß¼7eãÏ‹üUñ—ö„ðßizç„ï>ü9—ÇšÔÚF±¨xOÆÞñïSð÷þ CûDøÿãÀÝBûßþ~Ït/ØÉltÿè~6ñ¯‹-üuûR|8ð‡u¯…¿¾1øâÖŸ³ÇÆÝ øÊÊÇáGŸŠ³Uç†>4D|,¼yñ6Âÿ¥–ÿ±¯ì…ˆÞ‡öUý›¡ðÙµ–ɼ?À߆èmgqÂûYíIO ‹m5¯ÁƒòÀmü© øCð¾S€<(ºOª~ÎßÕµF Kx¸¤—/ÙK¥ï£wó{>Ý5z~_ëðPoÚoPø}ñò‡~ ø?ÆOÙoáêx;âõ—Ž/5/ø+Uý©|SûI^üøyáßÍâO‰~³m7Ä|âß‹~øKâOéž%ø›7ÆoÙ£Á6| /‰õj¦þÞßµ¯Æ(~+ø§Çÿ üaàÙïö,ÿ‚Ÿxÿâ÷ìëàO?þ|Bñ·Ä?Ùe?bÏYøgÆþøñ·Æ|øÇÿ¼7§x/Nµ?|3§|7>6ñ&…ã[þÒÞ“àí‚¿fo…þÓ¾=éšæ›ÿ B×ö—ø•â_‰? øŸ¤øGÄv/¸×ü7á¯é¾Ô´;? i¿ðGƒ~ø'Á_ |5¤êº>£¨\x[ÂÖ.ÕüUâ›í{ÄšÆç‚ÿeÙáõ·ÃË/þÎ<gðŽ÷ÆZ‡Â{O|!øá«o†ÿtë#âÿÃÈ4oYEà»Ïé7·š_Œ®¼6šlþ(Ó®®¬u¹/­®%‰ø+PÅK•¼CN*?jJÎ5T£.HÚ2n’´RrŒ§•£91Þ6ø«.»î»'Ôü›øyûwÿÁA¼_â?…?¼Cðçö~øeñ7ãÆÿx+Ã^3ñΙðçÄ:=·€>"~Ç?¶Ïí§k“|ý›?à ß´ÇˆßÃþ"ý˜<$|9ã¿übð‘ñÓž)ñ>“á?x&ÿA½ñn©ð·þ gûAx«ÀšWŒüQaû8hº—Ž?gØ#ö¸øOàÏøG>?]j¾?ðWí­àßÚZæÙ¶Æ×ᵗƈzÇ ë_³v±ãK|?øKñOÔ<¨ê^gºðÕ×ÄïÖ_‡ß³/ìßð¢ÛH°ø[û>üøkc xßRø™¡YøáG€ük¢üGÖ<)®øWø¤[øw@Ó¢Ó|oªøÅ&ðn¥â»4‡^¾ð§ˆµßÝ_ˤjú…Ç ø'á_Ã/‡cÿ…ðçÀžþÏð'~XÂáøcì? ~ÂCÿ Ïáͧö.Ÿcöoü;ÿ„»Åð‚øFžðü%!þÀÓ´ÿí­KíxóOðíµÇ‚,¿h­göV·±6ššx»â&“ㇾøYá×—ôãñ=yÿ=j¤KÓóÿéúÖŒKÓØgñ>¿Oé\òæ75G;Ù+¤¬’÷ž›¹=^ª*éF1I§®Äàtéþ­]‰^?ןóÒª ÉϧêÏëZ1/ôëÜÿŸS\5e{ù¿Á[óÐ Q.qïü‡ùþU£ôÏ~OÐtúñª±/ôéÜÿŸCZ1.qïÇà=?Ïa^uY~:¿$¿¯À¨«µ÷³òóþ ”1á¿Ùßö¹œÿæ¢þÖŸÒ¿L‘{÷=>Ÿýð¯Ìÿø*oü‹Ÿ²ýÌÿúÈ¿µ¥~›"÷ì:Ÿjç ýúò}©ÿíöETéóý QzÄÿ_þµ|ÿ<ñ‡ž#=‡ÇØàæå|¯¿Q3þGç_ÁO†?c¿ú®±Çþ¶WÀ*U¥îO»Œ¿-ÈpVWêÿ/ë_øcío‚ŸòF¾Ù2ðþ¢ºUzmy—ÁOù#_ ?ì™x ÿQ]*½6¼²ÂŠ( Š( Š( Š( Š( Š( Š( Š( Š( ¾Bÿ‚|ÿÉ„~Ä?öh_³Wþ©Ÿ××µòüçþL#ö!ÿ³Býš¿õLø.€>½¢Š(ä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{_!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚ(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Šü­ø¦3û||d>Ÿ²ìƒùÿÂæýºë¯EÀÏsÓéÿ×ÿ äþ( þß}?á¿d ÿáæý»?vh;þ_çüþ•û×?øÆ2Åÿa·ÿÆ*Ç›ˆþ4ÿíßý&#ÀÀóúÔˆ2séüÿÏô¦u«(½a×üûšú É_}õÿÄ‘îz}?úÿá_+þÒ«ÿö.õs¾%Ïþ!×ífM}\‹ß°à}úßç¥|­ûJ¯ü\Ø·ÔþÓÞ%ü¿áŽkOÿ_ÿª¸jËßÃ>¯—[ÿ ðßt—øeÿ¤³à?ø,o¼ãŸÙKÂPxëã~ð3@ðßíðcÆ ªüBøy㿉?³÷õ VÔ¿²¾~ÔúG€íÞ]#ötñ­ýÌxóÅ>+Ôt/hWvZ Ljuˆ¢{{K߯ÿ‡ÿðPOˆ_±ìóá¯Úgá'Áè~~ÈÓüQý´¿güøi«kŸgÿþÕ“Æ¿³íû'øÇÅú'ü,=öhøÝãÝSøY¢øÃ~ø3áµËw x(ëWRêzõñçü~Óüöà¿dï_~!ü=ø›ñgÂÚ÷Žuï…š·‡¼EàMZø•ñEþh¾*𞥪êÞñ¬¿-¼goðcYñÞ…¬ÞË¥øï^ð§âû8×O¶‡Z[]#H‚Ç\Û*Ä×ÅOÅSÂâ]4c/g>{Ó«ŠR­ÏS÷j*pž•*ÄFn5ê¶©Êš¥8¤¡(¹$Ü·òµ’²×kI·Ëº]ÿ–/ˆ¿·Wü[öwøgÿ:ñÄÏÚþÿ^ø¡ÿîð§ü—Äká þ~ÍVþñ7?k]Á“üvðOŠîtO„Z^µqá; gÄúͦwáÝSÃþ$ŽM#F»Óõý&ÛûKNÔþøõûbüuø­eÿ‡ø3ñ“ö‰½ðÂïø*'ÃþÃCöpþÎ? ¿eMXø;ûL·ÇiV^"øFÞ3¿ñ6»© câ׈¼Eðÿâའø[ è^!ðýÝÛU‘/OÏü?§ë_‰Ÿµïü:çö`տࣱè?³ttø&ï„¿d‹ïˆ:ž©ñ–_ê¿ ðÇ„ôko„¾:³°ð׆|âMkÄ~'ñŽ«¯ LxƒFÒ<)¦x&÷Oñ ç‹ü7àã°/JSÅg¸Ï«MÖŠ…®ây£OûSQIÓź³öXF›Nô*G§V…GUR§Ñs5ËN7VzrÇgº¶²^«Ù«6ÿ ~ÑW³éz_üþ ãûaü'¾×þüÔ/þ øËÅ_µ7ìëû4ëð‘“áÓ[ø£Á×ÿ |Q©XÝÿÂÔƒâˆW^šMwJÖôw[KKOÖ_ø)íwû_~È?´¯„6_´çˆ¾xKß°ÆßŸ þ=ø¯á?Ák]ý°¿m |A’×áïìks¦Éð¶/iñj>¹ÓŸá¿…žð§Åiš“Ûx¢ãU²¿Õo¿L>+ÿÁSa?>&ñ'„¾*üiÔü+«øZÃâF¡u1ø9ñÛ[ðþ¸Ÿîm­>1Yø Åþøe«øKân³ðŽk–|?ðç[ñV»ðò;biÚ4µ%‡sãïø(÷ìYð·Åº·ÿGf9?´üãþ Qàï ÁS>6Kñvïà®»û xþíñAý˜ßá7ÁošŒ¿l_‡ gý¢>ø×Ä(ø}©|N¾Ðl|YâoüRÿ„7ÎþØ´þÇÿ„¿ûþ/'Uÿ„ûOû [þÏã©„•OkR9즒jsue?bñëׄ¹¡‹‚£)a±(ÒQöpq¡J¬`á?dª2Ö)Òü-~U­ãï{ÑmÞïÞjýOæoöšÿ‚šþÔ>ý³?e¿Ùkàÿísâ¯xwÅ¿¿àœ¿³çŽ~0éß¿e½7¾~Úß µsûJ| ³¹øWñ5~#x¦_èÚ×Ä‹/Š~ñG„ÿgOx†ó fýž¾%éë>'Õþý†¿i￾ÿÁt¿ kúwí1ûPXÙÁ|üQãÏ„?¼ðwâ?ǯ‡¿þü%ñŸŽþ|±ñö©ðþ_ÚGàÆ©ñ?ÄÖÚw#üG‹âuæ“â_k_ £ð‚¼-ý‹üqý¶ÿfßÙŸá‡Â/Œ|a⿇~ øëã|4øc©ðsã]÷Ž5߈_|=«øŸÁ^Ô~hŸ5?Š^ñ^©¥h:ÂÝè>3ðo‡µ=W²“Þ"¶Ò|DñiRy€¿à­°Ä?ø/ÁžøÝ¬Gâ|e¹ýt[|ý üe§þÐV·: øãmoÇ ¼7¡ü6øÃy>•~4Ÿ†Ÿu/ xÏ[Ž6¢ßA,2IÈÃGë.u3„¦ä§ 2›æ§NXœ&"¦ªb¤ß2ÃFšrRSöœî/H½´ø/~¶Ýò´öw}¶×?šoˆÿðWßø(w…cÚcâÂÿÚL|Z¾ðwìûþÐ1øè> ü³ÿ†?ý¶þ/þ×? >|`ýІcðÖßÁz¨Òþx“Å7Aø« x¿âw„Ζ5ÿ›«Ý>æßúÿ‚þд?‰oø*Çìñ§âõçÆÏ ~È:×ìeªü&ñ†¿à‡^ ñe­Ÿí+ð;_ø—ã/ jIðËÂÞе-A×´¸m|!&¥¤ÞøšÓIgƒ^ñ/ˆ®ÏÛG ßð\OÙ{LýŒ4ŸÚ^ï]ðW>(øÃözý¥ÿhO†ß~ ]~Ñ=мoáïÙÏÄ>ð•ä÷¿|Yû*|0ñ§Âïë¾0ðT>»ø›ñ—àgÃß i^1¾Ô4Íüo§éVZνÇþпð_oÙ÷àGì;¥~ÒÖ ›âíqû1þÉ?´×ˆe ZñçØ| ¢~Ö7_Ãþñ×í£|ñ?ïêúvãm[^ðÕ§ŒtŸx“â6™áW¹Ñ<)ak«ÃueÇ”¨ÊgšÊ´)ÒiAÊ£„©û$xöyðŸí sãˆÿ g áÿÆ kÿÚÃCøOã?ø¢ú„w’üBñ%§‡¼káÍ3Hø]ð7Lý|!øO¨üGH®<=­Þk¾%ðÞ‹áoéÿô})µ-sÆ·wi4@êßðUø'÷‡üwñ'Qý¥<(|eû7Ú~Ö­âÍ7Ãþ=ÖôK¿€7×á0ñ¦“y¢xOQMsR´ø—%¯‚5oè¿oø‘¢xŽÿLÓuØÏªé«uÅì©Â‡³¡Žö|¸z4Ô¹¥YRsöµýœªÇ•Õæ…î—*‚JR‹V¨ÝÉ·´Ýí}íhÞÝþýç·Sÿ‚€ÿÁFnüoâŸé_¶gˆôë_ðsŠ¿à¾øj~þÌš„<û7x‰µ?ìÚêWcø­|EðÝiƒÂš¶³ã{‹ ÆÒݼqaã1*Õ¯ÿÁU?à¤Fª|-Ÿã¿ƒ¼;࿇_ðW?ø(ì=ñ+öìø°Ÿ>ZxOá·ìÉà‡¾%ýž4Šß-eß~x£â߈|eâ]ëÅ–ÿ²ý‡ˆ®|¤øOMµðUçˆ/¼o¦Koÿýx­4ß‹Z‡‹/|kðNçQÑ~|*øËñ/Äš³~Ñuߌ4ÏøgáÿÃßx‡âgˆ7ê?¼oûBü,Ó>7|(Ò|ðkãÏ‹­u¯…ŸÅ{à©ñ¶»¯h /ü;ðÿHÑ>(xcÄñ€ø‡«xRïÀ·ÚMå×-´ 2?·Z¶‹ù›²ýãw|ñºIÕvÕr;Z×qM7cDÿ»t¼¼–úz½{ù#ðã¯üÇþ µàÿÁ>¼ à|>ø…ñëþ eû-ø‹áGÀÏü?ø=¬øGá^‡ûVx;öšð›þÓrøöøðóâÖ•á]OöTñãxÄþñÏÃh~[øKÓ|Màÿ\x*Yu½s#áWü7þ ñÇö\ø§ûH6¥¨|ð•ûq~Àÿ°¿Æ/j ~jrþÄZŸÂí&_ø(gí3%¶©à­KEÔMÅŸhözrüT/ðO€ü5ứ|)£õ«ÿGž ÿ‚ŽþË¿¼c®|ý¾&øgâGÇ[Í âüß<3âý3âǯ„ÿüYðbYôè??hWá.½àˆÚ/ƒðgŠ­m¥Ö¡Ðîm_MŠÛÄÿàšµ/íðþ yûC| ¹×"øKû~Пð_?ø*ׇµÏ‰º‡|A¯üVý§t¯x]ðGìãã#ã_k×? ü¬x{C³Ö´oø!4ÿø÷Åúˆðž›â Ùhš”Ú—öåôöüO¯ÓúUåœ×H;¦êIÚZoªJ*Ú·dÜnú¾mofÝÃkÚÝ?­?ài¢Gå¿ü4Å5û _Úêqÿš‰ûZÿŸÂ¿N‘G°ëþ}ÿÆ¿2?à©ãýý¿k¹‡þj'ímšý=Eè;ž¿çÚ¦ƒ¼ë>žçÜ”¿¯¼m]Dz»‡õ÷’ ïù|ÿ?ÿ“;ñý—Øãÿ[+à~Jüÿÿ‚ŸøÃ¯ú®±¾?ñ2þçüýh«+Æoºi}Ú›ù”}§ðSþH×ÂOû&^ÿÔWJ¯M¯2ø)ÿ$ká'ý“/ê+¥W¦×Q@Q@Q@Q@Q@Q@Q@Q@Q@|…ÿùÿ“ýˆìпf¯ýS> ¯¯kä/ø'Ïü˜GìCÿf…û5ê™ð]}{EPÈ_´¯ü–oø'Ïýï?õ‚?mêúö¾Bý¥ä³Á>ìï|iÿ¬ûoW×´QEQEQEQEQEQEQEQEùgñ8göøøÌ=d/ØÿÿW7íÙ] ô®;â_üŸ¿Æû4/ØÿÿW7íÛþÙ’­~íÁÎÜ3–÷\·þâ7üiÿÛ¿úLG ïøóþ{Õ´^îÏéþ5.O°ÿ#üûU¤ÿ/óþZ÷¦ú}ÿ×õÐÄ•8‡ùýƾUý¥‡ü\/Ø´úþÓþ%ÿãöµÿ?}`‹Ðw=ϵ|éûF|<øã9þø—á>™à=sÄ_~1Þ|C½Ð>!øÏÄÑu­Sø!ñ£á=Å­¯‰¼7ðóâ}õ¦©i}ñ;NÖ!†_ Íiyk¦^ÛÉ{e4;pWIÆ–+V\±”¤©ÑÆP«RJR”š„%.XÅÉ¥eì‚׺ÚZ´ÒÕè¾g¤D¾ŸAýIÿ>µ£þ¼~¯?ç¥|6Ÿm%Æ?cßgþk÷Åþƒ¿­ZO¶šÿÍœøàcþKÿÅõ?òg'¯øÕÔâ܉íŠÄ>×Ë3M[õÁ//êÅB…E{¨Ýÿ~žÚ{Ïò>êAŸ_ä+ùáøÃÿ´øÝûAxËþ ýû0k7— ÿfÏÛó]ý–~<| ý¦§ƒ~0jþøƒðÃ]ð½ñŸáÇŽô-oâoƒ¾7j‘ë>!ð˜ø9᯴]ü øeà4Ö¬<-¬xPÅ¥ø^ý;mL`~ÆÞ8ÿ«€ø±ÿÐoVã¯íª?æÌ¼~Ÿ´ÅŽžƒþ0Ø×˜g\=Œ![Šp‡µ\±ÀfpöÄa«akRœ– OÙÔ£^wä”&§sŒÔ¢cN¬nÔcwmå ’’kÞÝ5Öê×VÔüáø™ÿo½Ó¿k­göëñGÄOˆµå•§Ä?Ú×Ųìðø¥ã¯†¿¶/Á7ö~¾ýŸôŸ?hÿ†_ô…_´­GÄÞ.ðFªXhÚx_UñG†´Ø¯üIiwªùìÑÿ×üWûÛøö±ñ¥‡í=ª~п~=i:§t üðσ¼+û&x&oƒ?³/ÀOŒRÐüñ³ÀZÁ»ŸhÞ?𾟮éZ³yãnÂÏ]¾³°QÔ¿a“ã×í°¿óeÞ<ÿÑÀüZç¿äͽZOÚöÙ_ù²¯6äà¾-~ófGÒ¼z¸Ž•IÔRÄ57ZS¦ð™›„ªâ!JZíÏ êªžÊ”)Ö¤aM9Jœ#9Ên­^Ö²é¯4/h¶Òø­k»½.ú¶’GÌ?à6? þ:þÃß­þ,ü/Ó5Ùâ'í£ñ']ðÁ¯Ù á/ìéðÛâ&¥û]ü(ð·ÂKM¾øK¬h¶ÞÒ~øÁÚvï_ã¯ÄÏÙZ[iž)ø†#°Ón,¿N>2~Ëÿ³Wí"|7ÿ û<| øõÿoöÀðü.„¾ø¤<)ÿ öWü$ðÿÂqáýwû ûsûDþØþËû/öŸö>—öß?û>ÓÉù±hoÛl?boŸøh/‹|ÿæ˜TéûDþÛ«Œ~Ä^8ÏüÜÅÏþ‚ÿ­ 3È©B¥*N¯³«8Nqž1¨§*tèÑŒŸµÃÎíB(ù¸©»ÍÊM¨TÞVºVV”•Ûèû·ÿ [ý­¿`O þÒþý‡|à¿h_<ûþÙ³GíSá/ ø_὆£áWÃ?³^Ÿâ]+Bø)áÝ Jñ‚ô¿‡šö—¯ZéºF·¦Úë6°Ñíììü¨ÛÉv¿!ÇÿXÎ?ã%1ÿÒðZ_ù#—þm¯þJ§ÿâñÿæ+¯®“öý·—ñƒþ 8ôp??ù2ÓVSö–ý·×§ì7à£Æÿ†…ø¹Ûþ쮸+ã²ZÓs—µr•“VÌ"’Œc¥ÒQK–1Z+i}ÝÊQ¨´VÿÀ¡þgåßÃø7ZøGð?Á ~þÚ‘i2‡ö ý«?àß¼}®þͱx‹Gø…ð7ö—øëñ'ãõ¶¡áŽz\¿|gàüH¾Ó.5y¼_ãkoèvžT6>¼žË¿àÝÍgÅ?þ3|ø}ûjÃà/ ~Òß²ÿü×àí{®~ͱøîûÅ-ÿ‚uøwÁ>øgñ'Àå>:xHøGño‡ülž%ø}q7‹g‡Tž+ûoËooq¦ê?¢žýµl_ˆZ%ö¿áߨgÂOa§øÇâ'g3þÐÿšA®|/øƒâ†ž'U6?±­ô>Bx—ÂZ²[™/Ùb]BÓO¿6ÝÂ~Óß·ÿ›ðaçŸøÈo‹Ý=?äʽy²©’8¨Æ5y99T³ ({?eh¾M9©IÆMk5&äÛ“n­WË{ï îŸçkvÑ#óëÅðo§ƒ¼Oâ|I¿øÑð߯߮ࢶíÙà­#ã¯ì§ ünøa¡þÚþøyáoüø¡ð/Ä?4Ë?ŠÃ‰ðÓÚ÷„~%Câÿ^é¾&²³Ö¬ü#cseþóâÏüá_Ɖ?àžšo‹>/épxö8ÓÔ>'¦½¨i>ñÿíÇñ7ÄRø·Å—þÒõÏü;øiq£kß³V‹>â+?|Zñ~±ðÿJK /«ÿà‹_²§Æ¯Ùö ð/?i}'Mðïí#ãþ8|sð·†|A¦êÞð§Ž>+üOñ/‰-¼;ðûBðµÌŸ ~øwOð{øUu/ü&´Ó~AãY½>Ÿýzü3ý¡¼Wûf~Ô“|Ñ|]ûYü-ðᅨןµoþ*üOÖµ\üøÅð¶ÓA²ðæ§û/|/±‡Î¾ø£kªÜê“ø£ýÓHž´ë¹®ãh?së:MÞ¦_“um}þñŠN+à?ø*ÿŒ8ñ!ì>8~ÆÿúÙF+ôó=>Ÿýð¯€ÿà¨C±¿‰ýWØÛ?ø™Ÿjj;ß²OòÔ²¾ ÉøIÿdËÀ_úŠéU鵿_?ä|$ÿ²eà/ýEtªôÚæ¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¯¿àŸ?òa±ýšìÕÿªgÁuõí|…ÿùÿ“ýˆìпf¯ýS>  ¯h¢Šù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^×È_´¯ü–oø'Ïýï?õ‚?mêúö€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€?i¾~Þÿ?ámü[øeð·þ/Ù öOÿ„þ7|+àíßìŒß¶ÿö¯ö7ü$Ú®™ý§ý™ý§¦ÿh}‹Ïûö…Ú|¯µÛù˜éûk~Æ dþÖß³&OýW¯…}?ð«ÿ_OB´0þ×–¤êTŒ¥íkԮ¬êÊ+É'ÞüóÃÆsrrjöÑ%Ñ%ú~>Zþz¯í¯ûŒøkÙŒzŸø_ ¿ùšÿ/¬§í³ûçŸÚçöatÿ‹ùð«ðÿ™®¿@Ꮏgú'_ùwxïÿšz?Ꮏgú'_ùwxïÿšzí|Œwÿ`Ãk×ÚÕùô#ê‘þy}Ëúïý-~OÛoö/ŸÚïö`Éÿªûð§§þ•emÏØ¸`Ã^þËãÔÿÂþøQÿÍgå_wÿÃ~Î?ôN¿òîñßÿ4ôÃ~Î?ôN¿òîñßÿ4õ“ã¬[VúŽÜJšþ×È>©ç—Ü¿®ÿÒ×á¤ý·ÿbÌóû_~Ë _ÿ ?ù›j_ønدþŽÿö]ÿÃÿðŸÿšÚûƒþëöqÿ¢uÿ—wŽÿù§£þëöqÿ¢uÿ—wŽÿù§¬ßb›×‡ôö•<¼½ÃXX§~fþKúïý#âUý¸b ÿ†Àý—zÿÆ@|'ÿæ·µZOÛö(ÈÏíƒû-=h„߇üÍÕö‡ü1×ìãÿDëÿ.ïÿóOGü1×ìãÿDëÿ.ïÿóOY>1Å;ÿ±Ð»ëí*_ä_°_Ìþä|pŸ·/ìL2Oí‡û,ç§üœÂ^ŸøWœU•ý¹¿bPÿ†Åý–zÿÆAü$ÿ濵}}ÿ uû8ÿÑ:ÿË»ÇüÓÑÿ uû8ÿÑ:ÿË»ÇüÓÖO‹1-[ê”Wý¿=¯ë`ö ùŸÜ’Óöèýˆ³Ïíû+=hO„Ÿü×þ?Ï­YOÛ«ö!ŸÛöUÏoøÈ_„ü×ÿŸÆ¾«ÿ†:ýœèåÝã¿þièÿ†:ýœèåÝã¿þië7ņõÂÑô矗üð{üÏîGËöëýˆIÿ“Èý•GýÜ7Â?þkêtý»?aî3ûe~Ê`_Úá?ùwþýzúƒþëöqÿ¢uÿ—wŽÿù§£þëöqÿ¢uÿ—wŽÿù§¬Ÿ×wÿg¥wמ×ù°_Ìþä~i~ɶìsá¿…Þ*°ñíeû4h:…Çí-ûhëÖö:×Ç…º]äúŠ¿l_ŽÞ'ðÆµ µ÷Š žM+Ä~ÕôŸè:Š#Yë©§jÚ|×}õµÄ¿P'íáûølßÙC'¯üdOÂçÿ ‡ùíX_±¿ì¥ð ]øGâûÝWÀ_jºƒöªý»4h¥ÿ„§ÆmÓ|;ûo~оÑ­¶[xŠÛìzF™ciç2î<´]K=Ô³M'Õ_ðÇ_³ý¯ü»¼wÿÍ=aò¬a j…;B1ù¥v¢’Wû¯ÿ Z¦—W½þÿéþµùí?o/ØgøÌïÙ<ëûE| çÿ/ά§íéû rOí¡û'z þÑÿù±ÿ<×½ÿÃ~Î?ôN¿òîñßÿ4ôÃ~Î?ôN¿òîñßÿ4õ/:ªÞ´iÿàRþ¿¥ÛSÙ®ïúþŸôµðøo_ØcþCöNÿÄ‹ø?ÿÍX_ÛÛöÈðÚ_²`×öŒø=ÿÍçø×·ÿÃ~Î?ôN¿òîñßÿ4ôÃ~Î?ôN¿òîñßÿ4õ“Íj;þêÿzAì×wýOúZøÊ~ß°§$þÚ²_ Ïíð{ÿ›óÍYOÛëöþOWöJÉëŸÚ;àïÿ6]»×®ÿÃ~Î?ôN¿òîñßÿ4ôÃ~Î?ôN¿òîñßÿ4õ“Ì&Õ½œWÍÿ_ÒìÍw×ôÿ¥¯–/í÷ûp?ᵿd¯í!ðsÿ›*²Ÿ·÷윟ÛcöGöÿŒø9ùÿÈç^•ÿ uû8ÿÑ:ÿË»ÇüÓÑÿ uû8ÿÑ:ÿË»ÇüÓÖrÆJ_b?{þ¿¥Øj 4ï{_×õ>_Ûÿö þOköFÉÿ«‘ø7ùÈçOðPØ4‘ŸÛköFÇøÉ/ƒüÙ×}ÿ uû8ÿÑ:ÿË»ÇüÓÑÿ uû8ÿÑ:ÿË»ÇüÓÖo&ÛåZ–qIÿý‚ó“ûmþȃ?ã$þ óiþsí_ÿÁB?l/ÙâŸìÁ¨øáíMû9|GñνñÃöDþÄðg€þ7ü3ñ‹5Ÿì¿Úïàf³©exwÃÞ'Ôu}Gû;GÓµ Zûì–s}“M±¼¾¸òímf•?AᎿgú'_ùwxïÿšz?Ꮏgú'_ùwxïÿšzÅ͵oëú¿õÜ=7à§ü‘¯„ŸöL¼ÿ¨®•^›Yš&¦øwFÒŸ´×þ §íÉÿÐé^wú{ýϹ«€téþ­sOÜ /ÿ X§k˪;ýÿ×™[—òGï×ékÓk_ðRŸÙOÃZ.¯â?Ýþч¼?¥êÞ½¯k_±íµ¥hº&‹¤ÚMªjú¾©}û<Ác¦ézm¼÷š…ýäðÚYÚA5Åıúýå_Šÿ¶¢ãö3ý®?ìØ¾=ÿ†«ÅdŸóï_¤?²–·¬ø‹à€µŸjúž»«ÞÂSö½WY¿ºÔõ+¯³øÓÄV¶ÿi¾½–{©ü‹X!¶‡Í•¼¨!ŠÛh£â¸—#£‘×ÃQ£^¥u^”ê9TŒbâã%%ž¾‡EŽªm¤¬í§¡ô=Q_4lQEQEQEQEQEQEQEQEQEQEQE|…ûÿÉñ§ýïüÿ[ßö•¯¯kä/؇þHÏ?ìïà ßúÞÿ´­}{@Q@Q@Q@Q@|ññ¿ö§ø/û;ë ð÷Å [ÆÐëÿtßk>Ðüðsã7Æ=cQÑüuáê·:gÁßxò÷FÓtkßxBÎkír 6Öâë^³·³–æQ:Eô=~^~Ù#?µïì¨?êÛÿm ý?ág~Âõêäy|3\Û—T©*PÅ×T‚RœMÞ*Z7§]«7Nœ¦•ÜUìúž´?ࣳè?iôý†ÿmóÿ¾ïKÿý˜OEý¤ýØ×íÁÿÐí^ƒ>¿çÿ¯øÕ¨—8÷äý;~×Ú¿WŸ…™toÿ ¸ÇÓø45}N¯Oþ}Çïgµø(¿ìÈzGûJ§ì3ûpŸý÷jü½¢Š(ä/ÚWþK7üçþÎ÷ÆŸúÁ¶õp?ðSKë­/öHÕu;Rñîñÿö*¾³ðö.±¯]Z~Ú߳垥OâWAÐ!ÔµIcKuÍsFÑ㺞'Ôõ]:ÈOy}ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ®Sþ 5ÿ&À¿ör_°ßþ·ìí]™}þ¿³q\ÃY«]?m W2”n·W‹WÝ5¡3øeþù3óñ~9|OøcOÚKÿ Ù“ÿ‰YV£øåñ@c±—í(qéâŸØû’~¿µhÏóé_F Éϧ?oóíW¢\cÛ“õ?áëí_Ô•(VW¾aŒvÕþï¿M°=o¯©áó/äß?þHùÎ?ŽŸF?ã ÿicŽ8ñOìyÔÿÝØz½Ç_ŠCñ…¿´ÁÇx«ö9êzõý¬‡ù=«èø—ö=:Ÿóú Љz{~¤þý~•çÔ£Y^øü[¶¯ÜÀîúi‚ëúô4…·äo·åýï—Þ|ÝÇŠcþl«öš8ãþF¯ØÛ¯~¿µ¯ùÍXO3ŸøbÚlöñUþƽÚØ“_K(Àü“^ ûJþÒ~ý•~ÛüJø¤x³XÑï6øjOøB›ãW€4/-¥j>%ñ‘ákIÿi?¦‰á¿ø¶ûHУÖ,|Oðçìùð—\ø¡eñËá÷‰~Øøjïà×Å)t‹/ øÆß¾'~Ð>1ðlÞ,ð¾£ñ?áoƒTøšÇÂÿœåÙNe:UqØü{•û(ºp§HûnIòঢœ¤¥iA]I'¡µ9Ô‚j0ŽºêßK+ë5×å}Ø!ûnøÉº~Â_µéÿ¹öÿèЧÛgƧûþ׿øR~ÿý5ù›ûLþØüûd|ý˜>x2ËWÔï¼mðƒVñTzŸ‹4]Gøáߎ³Oü{[Òü ¯êZŸ‚;Ño¾Ãeû+ZZé¾2‡â‡‡¼sâ/ˆ_­þxGMøÛð_ö€ñoìßñö{øKñãáN»ð«Æÿ-|{à}fëÂünøû/x/Çú³à¹ü1⫽WXñá§d4ÝTñY’öS•6ß³’sƒ§r¸`ÔgV{;»¤âã'¬kU{Æjýo®«ív×úgë°ýµ|pÝ?`ÿÚôÿÜËû ÿôhÔƒöÐñãtýƒkÓÿs7ì4?Ÿí¥_ ê_·V©iñ Æ?4þÍþÐ<%ªßøgIÐ/xâ—->]ü@ñ›ã¿‡Ÿ ôw[ðÿÂï‡þñÇŒ<ñwâOÄ‚<õr –œ”V')JJšŠäoÊiÛ’äu!ΛN:«s+íjv~¿ü—[höù³ÃöÊø€Ý?`ÏÚôçþ¦ØcúþÚ• ý±¾!žGìû^ÿáSû ÿôjWã×ÃÏø,_Š>$xDxÿñoÅÇðGµOÙ°ü ñoˆ´OÚៃ|wá¿ÚSö¦ø û6xTx¯âGÆ_ÙCáŸÂm+Ç·V?tOоðÇÁoˆ?´Wƒ|gá_ x¤ÂÐðý•­¾½qí>ÿ‚|OñV¡y k_³?‚ü1­ÿÂûOx›Dº¸ý¦ô¨¼«xöýªüû)~מ½ñоø2/ i^ñWtgà§Œ|G¥éúÅ«k-ZÛÆV?,à±ÖõYdù>œ˜œd¯hµÊÓN\·ºÂY{ÖI7vÚ·ÅžÖ§òÇ{|íæì~Ûâ+tý‚kÓŸúšaoëûjÔƒö½ø’Ý?`ÚôÿÜ×û _ÛZ¾ ¸ÿ‚¤|5?<;ûGhßu˃-ã?|;ñÍ׈þ$|ðO4ïi–^¾Ð_à×ü$>=‡à‡íOámr/Þ@Þ%øñ÷Åë«éKáo‡Äß¾·áß~–ø ź_Äx?Ǻ®½e¢xãÂÞñvg⯠ë¾ñ=ž•âM&ÓYÓí¼GáØi>&ð®½oi{ ZdžüG¥išö‡¨%Æ™«éöz…µÅ´y¼›/m¨VÄɤž³¦®¥k5z ë]m³M;=œjMÞê+ï½þÿëCеÇÄÖéûþ×§þæÏØL?Û^¤µ§ÅÏü0'í{ÿ…oì#ÿѱ^Õþ¼`?ÏéWdþqXÏ(ÂÆö©]Û¼éïÿ‚ºvò+ö_ùž?kŠMÓöý¯OýÍ¿°ˆþ¶ÍJ?j¿ŠÍÓöý¯Oÿ‘¿öéøþÛUï‘.qïúü¿—JЉz{þ€~ýn•Ë<¾„oiÕÓMe úÿË´ï²üÌù‹Rý³øOàÛÍrÃÀ?µ§Œübú;øÇÆš ®§q xW]½±²ž{ó§Ë´Û~߯Ìïø)¸Çì}â#ëñÃö9ü¿á²~ÿò¯¹> ÉøIÿdËÀ_úŠéUæâ)Æ•NX¶×*~õ¯{´öK·bâÛW}ÿÈôÚ+‰ñ—Ä¿‡?lµMKâüàM;CðOŒþ%ëWþ2ñV…á{-áÏÈ´™þ!øÿTºÖïì`Óüà85íoøªîHt/ Å­i2k—ö)¨Ù´Ýµ`PQEsöÞ,ð­çеŸÙø›Ã÷^6ðç‡ü3âÏø:ÛYÓ§ñV…á_j>,ÑüâmgÃÑ\¶¯¥øÅš¿€¼u¥øgY¾³ƒN×µø²ÇK¹º¹ð汘AEPEPEPÈ_±ü‘ŸÙÞÿÁA¿õ½ÿiZúö¾BýˆäŒøÓþÎ÷þ ÿ­ïûJ××´QEs>2ñ§ƒ¾x[]ñÏÄxgÀž ð¾Ÿ>¯âoxË^Òü1áoéVÀO]ñ·uc¤é}¸ Ï{¨]ÛÛD2J¹ÓQ^+àïÚOötø‰á_xïáÿÇß‚¾9ðGįÂðçÆ^ø©à_xWÇþ9û©©ÿÂà¯èºíö‘â¯ghzÞ¡ÿî…y¬}‹GÕ.þÇäi÷rC‰ñoö¸ý”~®ªÿ¿iÏÙëà¢hZ—„ômm¾-ühøoðát}cÇšgŠõ¯iZ«xÇĺ0Óõ/èþñÆ­á;³ ׈´Ïx®ÿGŠò×ú¼¶`BÑ^wð³âÿÂ_Žž °øðKâïŒ_uY®íô¿ü,ñ·†¾ ø7R¸°˜ÛßAaâ jz¾‰y5œàÁw½ôo01̨à­gÉñ×àŒ:wÅÝ^oŒ âÒgÿí/ø_:¤Ÿ¼"šwÁ/ì_ ÃãcþíëjâÛá·öO‚îmü_©Âe.‹ö ÜC¯ÝyZL±Ý°ªQ_=|.ý®?e/Ž:&©ðWöœýž¾/éž%ñÏÃj? ¾4|7øc¯üC²ðoˆ~"ÞxD»ðŸ‰uk}WÆvŸ¼%â¿Üø^ÆIõÈ<áŸøž[Ñ4]JúÛèZ+ò#þ â­{µ?왩x{áŸþ*ÞÏðöʱ—ÃÞ¿øq§kv²üEýˆ®$Öngø¡ñá¾€Úm¼¶°ØÍ¦¹u¬5Ö£föúTöI¨^Xþ»×æí‰ÿ'û*ÿÙ¶þÚú³ÿaZú.Œ¥Ä™:ŒåNON Q|³Ö*¤*A¿ñBKÈÇüš_ÝÙߺìÓüO•Ç/‰ä€?cOÚKðñGìÓÿ³n?Ž_?èÌiCŸOþÇÃõý«‡øt¯¢ã\þ<~ÿÏ·½hD¹üxü_óí_Ñ•(VWÿ… ^š/s/ÝÛþ zvõùxüËù#÷Ïÿ’>sã§Åú2ïÚXäçþÇ}?ÚÀ{þuz?Ž¿¿èË?iƒžN?xÿÆZËÁnþ>þÇéð'Äß¾økö{¶ý„þ[^jÞ'Ö4Ž–~Ì_¼_xºW‹ü=¦IàsðÛ\øq5°¶·³ñ9ñu”×¶·¿*³Šuy\+æ|²ÄÏ Îéen0©O W7>X7eCV\±RªšŠ•4çO›gºCH©o=Sq{^íyjõÓO¾ÿi-Kã÷ÆOøRÂÿ±§ÇëøV¿®¾!ë¿ÛÞ5ý­~×¢ÏðSã/ÃtµÒ³ÿjMSÏÕ¹ñD»h/>Áh4«mVqzo ´±¾ýA·‹Û§ì)û^ž3ÿ#ì?Óñý³ëñgöDø£ûPiŸ±÷ÂÏø)íûgMñ3á­û!ø›ö¡øÿð&×ö~øQý• ÙÏðúëâ¶™§|ñ§ÃÛ‡^5ð­çýðï‰tOŠW_î}3ƶ?±¿ìåû}üøw¥|~‡ZÒ¾'þÌÿ´oÇï|ÐïüaãȾ[…<7«xÿH×5ÛøSâ›uk›=?ÆR‘uyeóy¦[“cê,Ã1ÅæXz•pʬþ«®•%QTk †¯^.J1œãR¤ã(Âv¾°H.HF )Yü_i[Þ’ëkµ¢¾çôh?m¿žŸ°í{ÿ…ì:?Ÿí¡N¶ÏOOØGö½ÿ“öÿèѯçóâÿüûâßìý'í¸Ÿÿaÿ ÙÇÿòñì­í37ƒÿjçñ8‹Áµ÷ü"Ÿð«õ_…i¨~ΞxÓL>&”øËÁúØð/‡4µÒ.N•ñ]k›5“ìßø)¯ÿmχþÎïìÏcñz?€«âïŸÛų'Äÿ?jO øZ?Cÿ §QøSðËã/‡¼eá_xu¼k%ä¿-ôŸø—ÇøzÒÍ|'b—W2¯—>ȹ+NŽ#4¬è8*”â©Æ¢R¯SåËWJñ…Z5ãRIÚÆ£“\ºéµ]¹•5}µ¿E.’{¦­ÞçéÐýµ0øÉð»â|>$ÕôŸ|B×eý¥¯þè0i^'žÓök°ñP_Z{ߌ๟>|wñŸìÍã¯ØSÃVüûdÿÁ<c›Í#Aý­?¶ü/âoø(¯‹?´/YøªçörÑŠéß—á¶›£É¦Ü鿊GŠeÔn5¾‚úv©Å,§ _󌛊Q½*šÊœ}úZ”ß4iUpq›Sä—#’³wí*ÿ,Û¼–×Wì~ûÛ'âéûþ׿øT~Ãý”ÿøl_ˆôa_µïþ?°¿ÿF­~ øÇþ è< ð#âÿÄM{öiðn—ñgà/í!ûdþÍŸ>j´ÅÉÓ|IãØ£ÂþñŸÄcðWÄøâ‰ßŽ»áŸC­i:ž©ðÀß¼¥øÄz§Æo‰ ô³á{ßü—áÏø-Ç?þÙ:Ž·‰þ&Ãû<|]ý±ÿà¿>ü4ðéýŸ|«ü3ð7üköCø‰ñÃâf‰ñÄž)ý>1kÿ|+­ëšVuâïèþ ð7ÄX®l ²ø[ñÏán’º•®µ…\·%ƒŒc‰ÆÍÎQŠIÓN<ÔçQJJXx4­M¦•ä¤â¥«BW¯,WßÝ.’ó¿üú¡¶ÄcÓö ý¯ðªý…ú5jAû^|J=?`oÚôÿÜ×û ôkWàÀïø8dxÏá÷ì™ñŸãGì/Â_ƒßµçÁ/ø(Æ?kþ øûÅÏèƒþ ÙáßxËâöâ^|øcj–^#ðÇ‚®‚õ›OÍuâeÒu/ ØiP[øPúïöÿ‚°|Aý¬j…¿³ÿe-àŒ¿àœ^ÿ‚”xƺGí>,=ßÂïˆ_|7àøG[ðÐø3ðôhÞ%¿Ò¼B ¬Æ}{EPÈ_´¯ü–oø'Ïýï?õ‚?mêå?à£_òl ÿg%û ÿëp~ÎÕÕþÒ¿òY¿àŸ?öw¾4ÿÖý·«•ÿ‚Œÿɱ'ýœŸì5ÿ­Áû;Wf]ÿ# ý†aôü ŸÁ/ðËòg‹D½3õ?ÓüýkF%éžÜŸ¯oóíU¢^™úŸéþ~µ¡ôüÏôýo­TU–÷õ¢ßðô<®íܳãÜŸ¯oËú{Ö„KŒ{r~½¿/éïU¢^Ÿ™þƒÿ­õ«Ê0>¼×ŸR]ß›ýüC¡h’ìJƒ'>ŸÎ¯F€Œ0c@ ç¨#¡GåUâ^žÜŸ¯oËúWò9ðoãßígðSÅÚW‡×öñçí;ûd|Jðí»ã¯Ù[⯂jýª?àœß·ßÁÉu_k^ñ¿ìû£x¢Ë]ý˜¼wð·Â–±xÀZÁWᆥøƒll¼SâïBš—„çùüÇ1Ž T#:U'Ò—4á´eÏFœ!d›u*δcI>HJKÙûEZtiÕ¸Cžöz«ißÁuÝ¥­¬›_ÕÐøCð¦Oßü,“áÃÙ>ê‡X:§Ã‡ð_†ßÀz›x‹X¼ñ¾oü Úaðýá×ñ'ÄŠ"øD/>8è·…~2kÚ'ÀkÆ·ßü/âߊ^ñ‰t_xÏÃôø«OñOŠíõ­jñð_Œ¿hOø(WÁÿÚáO…~|mðdº×Š?eo„zO‹µoükð‡Åh^"ðŸÀ´Ý+ÿ |?⯈þø'ñŽÆO‰:ÿÄ_ÚB4¿†ðkæùeWN*Ö/R„(·FŸ$ÜñpÂBTeUÅ8Ó«>dá¢äº^Õ:‰7Ím$åï;«G™Ý.êß=ÛËø%Ÿì;eyý«'Âoë>$:·€|@|g⯴WŒ¼~|Ið§ÁŸ~|.ñ?ü,|XÖ¼kÿ GÃo~Ðÿ4oø—û{ûwÁë¨øN÷ÃÚ†¨ü.øYyàÎËÄ¿ðNØßÆ:„ø_ÆžðŠüEñ§ÅŸxâ_‡^ðWÄŸˆÞàåLj&øA¨xWâŽü;ªøÿGñˆ¬õ/Ìø(Gí£ñÄ¿ðLÏø'wǯø¯Ä?³†“ûq|iý€4oÚâGÃýZæÛ_øð'öšð忌~$k>ñ¨ŠÞÿòi÷:ƒí¼aÓu8aÖ´¹±¿½†Hûßß´w‡ÿà>Ô<û0þÒ> ý¥¾%|dý·eŸÙZûÀß´ïí yûNxSöñíºÝŒ¾&ÞÜx‚/ÚfÃáxzó^ÿ„CâÿÆÛËÍW[—LOøƒÂZ$úœUÄaäÝJ£î¬Ô\Üñ\õcìã4æù¬Ûi®W+(òÆn¥e«Wº²¿Ù²Õ­;}ÝíÑ ÿÁ<d/ Þ]ßi ïÔÉâ‡Þ+Ñì5ŠõÍ À—ß ¾6ø?öŽðfð›Ãšçµàß,~6|?ðWõ_† tßü:ñV§á"ÏÅž×4{DÓkÐìcoÙ– KHÕ'øCáÍnmóö€¿Ó,üWq­øËC†ÿö¢ý¡|ûVün¾ŸÃž,Õ5¯ß^x§ö„ø{௉ºEÕþ™u7Ãýcö6?_ÂZ—J—ðƒáßüûöÕøÏûAþÉ_²—<)û0øWÇßÿj¿ø*Wì‘ñ7㉾|Xñ_¿k°ÿxßÂ?>xWGøÙájÏÀ~9—ÅZ†¹á_øãÆ7RêZ5Ö‹¦xçH6ók-ðçìáÿ”ÿ‚€|}ø¡«þÕ—ž1ø?á¯x[þ)ûcþÜzìÃkðßÇ—? o>)üý¢~'ü·¹¿ÔGÆ«ê^+×~$|Ñü^<[yyseá¯úÞ½ð;Ãþ ²ñ¶³«~Ðú™i¿<7ÿñÁ­CâÆ‰à‹ÛÈôµ_x{Ã2·„¼iw¤Ëâ§øq­i:<úÕÝ‘s651´¹ã©9MÇt–³9]»»Úi7å+&Üd£¢‹Qô_›ÿ;ýÞ—þ¥ãüÓüÿ*³çñãðïþ}½ê :ôÿ?Ö®D½1ôÔôý~µY[åù¿ëóÌKÓßôÏ·½hD½=øA×üûUh—ÓØëþ}hÄ¿¯éýúÕçÕ•»i·«ÿ.ÞL7>ÿ‚œŒ~Çž">¿?c€>ƒöÊøþ û'àþ»¡Ø|.ø¢_k:U–µ¯ü2ð‰Ð´‹½FÒÛTÖ†—àÝ"ïS:NŸ4Éw©gZ²ÜßýŽ)¾ÇnË5Ç— ~ÿ‚²Ûx†ëöøká BÇIñe×ÅÙ>ÛÂú®©l׺n™â¿k¯Qhº†£f¡šîÆÏRkk‹»`¬ÓÛÅ$@ø?/~Ä~ ý¨ü.žÒ'Ëñ¦ßã¿ÁMö¶½ø³aÿŠÿ‚¿ü$øAâ?Ùß_ý¨ï´Ÿ þÒZ¯‚~øÇö}ѧð¿ÁboøWÆÞ3°Ñ¾4^ø\ñnƒ¦ˆ_| ð^Æ×T×þ1ü/ý™í¼/óoÅ¿†¿·„^ý¢ø‹ð§Æ~.¾øuð²ÃBžÓöø?¦hÞšÃÇÿüáï hÿ«µ—í“ãŸÙ«ãçì£ð’éü'7‡~5øLÍã?[ü7Ö3øÇÃ>ñ=íÁãã®k®»ãmcáõÝŸƒü{a¡üO‡Àú׊¿ü-ÿ‚ž~Ò¿þüøÁ¨[è? 9ê¿|.¼ý›?hóû5\x <ñbØê_Ú+âwíu/ìžf³uð\Ô—àì«í$|ycâüf||}ñ"ëèÏØSYÒ¾þÓŸµ/Å­wÂ?·§„>x·ö{ÿ‚[|øsãßÛÃ?|Wñ'ÇüOûHþÜ ×,|wðe—Œm¼lºF‘ðÖ_OãÏ¿²†¥ð—Å—|§ìóÿ}ñ§Œ|u}/&ð—ÅŸ‡¾øQûWj^?ðÁ‚~Òþ-xã?ìåã¿ø'®¥øF߯Ÿ¿o/Ûöoñ·„øçVøËâ¯|TÓL&ð÷ìëñëá·Àï~Ì^$ýŽüAâÏüPÖ>~×0ü4ñ†™à»ÚóàŸÄo‚ÿ¼W¬Ãu¥xƒB±¸²øÅá/áæ²úÿE; ÿà©ÿ´'ÅK/„Ô> Ò|៌Úgìoâ}Äþ4ÿ‚uk)¥xoÀ_·Î±??eŸˆ~4ƒÂŸðVkZ7„þ%ük²ÔþÙ5¦‘ªk¶¾<Òï¼K>„> ,§þ†4äÔ"ÓìcÕ®lïuHìí“R¼Ó¬fÓ4û½A!E¼¹±Ónu^ãO³žàI-µÆ­©Íi ¥¼ºì‘µÌ (¯Ÿ~#xoöªÔüK-×ÂOŒß³ï‚<lí â7ìÍñâ—‰c¿Da}s/‹<3ûZ|Òæ³¹«ZX§‚àšÉ$Ú…û0‘6>è_´^‘¨j²|lø©ðWâ—5œ)¢Yü-ø㯃چŸ¨,å®.u]KÅ¿´§Ç+}bÎ[|E ¦“¡MàÜ>£sû0öª+œñ}¯‹¯|5¬Úø \ð߆¼c=›'‡õßøWSñ¿†´ËòèRçYðžã‡Ú¦»f±‰¬l¼iáÙÙj¨ÑÉó/ü!·¿ý¯ì…ÿˆCñ›ÿ¦ @ìCÿ$gÆŸöw¿ðPoýoÚV¾½¯ËOÙ[õÍÿÂïÍðçã‡ìãá] ?j_ÛÊ Í;Æ¿²ÇÄßêÓëðþÝß´škz¶µ¡~Ø¿ lí4}Bótú^‡.ƒ{{£Û´»ñ¹*›Öû‡án…ûE醫'ÆÏŠŸ~ ésYš%ŸÂ߀^:ø=¨iú‚ÎZâçUÔ¼[ûJ|r·Ö,å·ÄPØÚi:ÐN Ãê71Ÿ³j¯ÿoŸ kÚÆû1øú?‡~$øÃðÛàGíYáŒ>ø?ÃoãxÇáÞ•ð·ãƒ¼?¬h~…f¿ñî¡ðsãO¾~ÐpøSB²Õ¼[¨£2ø'B×ügk h·ÿjø¾×Å×¾Öm|®xoÃ^1žÍ“Ãúï‹ü+©øßÃZeùt)s¬øOFñÃíS]³XÄŠÖ6^4ðìÎìŽ5Thäù—þ¿ÛßþŽWöBÿÄ!øÍÿÓ ÅÚöwø‰ûb~Ó¾øÕð_à¯Å¯…¿ !ü6ðØø­ñ#ö3ø7¤x«âß…|¯x£ÅŸ<-¤Áe>Ÿ«xë]ùÅß?h_ÛÀÿ¶?|ûpþγ|yø•û#þÑþ)~ÏŸ²ßˆhÿ-5oÿðp‡ÃŸxÿáå§ìéûUx{Pñ—Âoƒ?c/ÙçãfŸwðƒÆ¿ð¯$ñÿ‡/¯ÓÁ7ž"ðŽ¿§ÿ_šrjiö1ê×6wº¤vvÉ©^iÖ3iš}Þ ¢Þ\Øé·:ޝq§ÙÏp$–ÚÆãVÔæ´…ÒÞ]FöHÚæ_ øá¿Ú«Sñ,·_ >3~Ͼðq³´H4/ˆß³7ÄoŠ^%Žý…õ̾,ðÏíið{KšÎæB­ibž ‚k$ “jìÂDøö ÿ„ãàçÀ¿ÛkãŠ4Ú⦙¯üø•ñÛáψ¾)ü ¶øUû[þÒ> Ðg?‚ZDšç¾øCáGÁ˽+â>§ãŸøÓáWÃO Að7Έ|Kð÷Áß onüujKýWò/Æ?²çü?àïìÑûféß?foêÞ ý·¿àš?·OÃo‹2þËŸ>*þÓ9ñ÷퓯ø7ö•ý¥~x›Æ_öaøYoðæj>:|ðÚxsÆ?íõMjóöø1¥ø’hô‡¶úçôÙð·Bý¢ôCU“ãgÅO‚¿t¹¬áMÏáoÀ/|Ô4ýAg-qsªê^-ý¥>9[ërÛâ(lm4 h'áõ˜ÏÙ‡£ø¾×Å×¾Öm|®xoÃ^1žÍ“Ãúï‹ü+©øßÃZeùt)s¬øOFñÃíS]³XÄŠÖ6^4ðìÎìŽ5Thäüzñìû@øsöÏý?iO‹~:ðßlj^+ý¬|/¢üJÖþ~Îþ.ø'ð“áwÁ¯‚ÿðN¯ø+‡¼¯x‹ÂÚçÆoÚ/\°ÔuŸ‰ÿµ¾×¾$x·âdZf³ªx“ág€t½3K¹·Ñ-5_ÚŠù þ¿ÛßþŽWöBÿÄ!øÍÿÓ¯¬täÔ"ÓìcÕ®lïuHìí“R¼Ó¬fÓ4û½A!E¼¹±Ónu^ãO³žàI-µÆ­©Íi ¥¼ºì‘µÌ um{BÐFœÚ諫ê–z’umFÏNž·¨yŸ`Ñôãy4"÷T¾òeûŸmæÝÜùRy0¾ÆÇæÇíˆ3û_þÊ ѶþÚú³ÿaZõ_ÛÇâWü"þ!“Wø‘û6Û|×môŸËð¿âŸìañWö›ño‹|Ezì!Ñ´½7À´çÃéønÛâ7ì\ž'³ñ„Þ"øÁûBk‰k­]O¡\ø[ÃڿLjڅôë+›¯ô(® ðž…ô|"íĹ;ÿ¨Èÿé1ħøT}ùú{ýϱ­—Óè>ƒ¯×ÿ­U¢_O ÿóïZ/L}õ=?_­GÕ•¾_›þ¿3ÆŠ»Kïô,Ľ=øA×üû{ÕøÆôçôªñ/§ÐSÿ×úÕÀ: ¯>¬­ÛM½_ùvòfçŲÁ:c‹¯ øçÂ2|h´ˆßµ}çíÅâilþ!üT°×“ö«¿Ô´­Vëã'†2ƒN*jQ—-H'£tã Õ ­ªZ±½ªM7Í'»}·{i§à¾åÙŠ_²×ßø$>¹ûBë¾ ý>|JñÇľ*ý ¿ek¡«|&ý»>"~Í_ø—Iý•~ hÿµŽ…¥ðTøÏÂßµ£û#x#ÃÞñ§‡Óâ'Œì¾kÞ0Õ¾9x&ÓÁ¾/øù¦·‰¾/|=ð¸Ÿügá}?[ñ·'É9Ô†r­’aª5J¬áN†Pº«VWAÒ–«ö“¤¹ë{Ü®„cx¸&ÖÖM¥íd®ÒÞöi)^üËg¢þòÞçîçÅOø&×ìYñ¼þÕãâÁøIÿá¸áEÃQÅÄø¯¢ÿÂÏÿ†iþÎÿ…'ÿ"÷Ž´ŸøB¿á þÉÓÇü[ÏøD¿á#ò?â­þÞófó=ƒöý‘?gÿÚ™üyñ§Áz¦³¯|+ÕµÍoá§¼ñâWÂ?‰¿ñ>ŒþñAðÄß„0ðÄÏÅ:#ÄÚf™â[m3Ä:|vöÚÅìVÖëò)ãø#oíÝãï€ßðPø›öm‡Äÿ¶Ž?gOø"n‰û(üCÖ~+ü½ø•kñ‹ötø9ðcÁ߶ާáOˆ÷ðŠtKÂö­øÓZñ‡Çö¶÷Ÿðƒkž+Ó53Pú«ö‰ÿ‚NþÙ3|Kÿ‚‰xWöføs?ß٠â'íQÿËøÍðûà×ÃÏ| Ñ4¯Úá'Â_ƒþ2ÑlŸx7Àÿ.üOð¿ÃÞ4ñ7ÄGÁ~)×aý¤¼cð÷âÿˆ|'i/ŒmZÙX:–)·Vƒ®Ó”Üjc©TŠ©:Þþª)$½¢ºIym½û;m´]ì–Ÿ±_ÿàŽ_±—Šþø—Á >ø_à‰õ?ÙbÛö5ðïŠlô½cÇÞÐg¨¾3Ùü|Ô~ê? |Uâ‘àÍz|H‡PÖ%x?ã¿…ÿjÚÏÃÚW‚þ8~Ð_³_‹õÿÿ²oí›ðw_øUã¿[ÍðwÁ¾|6Ѽ1­i^ø ~οcø¡¥|9ñn©i7üEâÍ?H‡Eðï'àø#×üûÂÿ à¡t·†âømû(h±üGãM÷ÄXkŸ>êßµ'‰¿h¿èëøW[ÔüEð÷Äš÷‚-|?ðK\ÕhSNŒZ¦¥ì%5GNç³­Jó‚qš¥gR* |/÷‹Wkníu­ï{-½mÖÿÐGŒ?àÿðNŸÃãñ_ìë¡?~1þÑÿ|S©ÚüUøÝ¢ë×ß?kÏÙ|6ý¤õ‹Oh_´ÝwEо.x#MÓ´xCÔt߇Ëocg>—ám:úÖ ¨ü[àÿì5ÿ€ñoíñgàç¿…ÿÚ´ìOã¿ø'Äߊ~ÿ„×öŸµÿ…gãÙÃà/ˆ<-ÿøÖ¿µüGâÛoxÏþß×:íöo‡uOé"Þn¾6Øk¾/[+…üˆñgügö±ø¬xAtÙ>Çàÿ쉮Á_ÿàœÿ´6“ûj¿~jš_ìóû9|ø+ñ áïí{ñ/RƒÂÿµŸ†Ö?¼Uâm3Z½øðßÄþ*ñž¿mnú­Ï†XÔu;h:¯ŽðIÏÛ0ÿÁMhßÚ{áOÁ­?Ä?²&™ûJÁ*,ø“Ç ø´Þ%&ÔoW@øëáÿ‰~²'û?OÑìô§’ɼKþ éÿtÐÿaßÚ:ïö”¿øÁ§x÷Äšw쯤þÈ>ðƒ¼ñ?Á~ð·Â»ˆöŸno5ñ‡öý¦|Mâ+gKÑtÍ3LðŸ‰<ðÇÂ>ÓCðÃmÅ¢Šßö~%éïÉúvüÿ­hĽ3õ?ÓüýjªÑ ¥Fœ©ÛÙò.TšŒ`Ÿ,ZO–ŒbÚvŒ`–‘Vµt­wç~ºßï¾¾­÷&Q€?3Vâ\cÛ“õíùOzFO°äÿŸóÞ¯D½?3ýÿ[ë\µe½Ÿ’ý_ü@,ĸǷ'ëÛòþžõ¡ãíÏâÃúUx—§·'¯^ßOÈÖ„KÓÛ“õíùJóêË}|—ë÷ÿÒ»K¹ù…û\ ~ÞßðOÿRçí Ÿ¯ü.oØj¿[+ò_ö»ý½¿àã¿ü#Ÿ´)?_ø\ß°Õ~´W‰]Þ¬Úþïþ’Ö‰.Á_!Á>äÂ?bû4/Ù«ÿTÏ‚ëëÚù þ óÿ&ûÿÙ¡~Í_ú¦|YõíQ@!~Ò¿òY¿àŸ?öw¾4ÿÖý·«–ÿ‚‹óû1Æ?êå?aŸýnÙÚºŸÚWþK7üçþÎ÷ÆŸúÁ¶õrÿðQnfH‡ý\¯ì1ÿ­Ãû;Wf_¦?ÿê3 ÿ§ Lþ †_“<Ž%Î=ù?Nߟõö­—8÷äý;~×Ú«D½=ÿ@?þ·Jщss“× ÿ?¨¯ê’ï×Wè¶ü½tõmFOÓš‰}Ïÿ_ñ«Q.qïÉúvüÿ¯µyõeÝù¿ÓúòF…˜—§æ ÿë}kóoöÌÑ?cÏØgö~ý¢ÿm|¦~Ì~$Ó´½ÿâíû,ü ýžßöšÕ'ñOÄ? éú^™ªüEøcâß ø§Tñ߉µ}3ÃZ“üAÒõM= ×/5fÔ4=JÒÓÄzWé\KÓß“ô??ëí_”?ð[σŸ>4Á;~'èŸüâ>+ð‡>|Q¼ý´=1õ‹Ú+Âß~/ø7Ä&øSâ½7KH|iáÇÒmçñ×öÃoÃ~9ÔõïhZ^™ª]ØÞêž×|,ÖrŽVœ#R½=z´oJQª©K‘Ç•ª‰§m)5U¥j~óŠuŠ)»'$Ÿš¿[é÷éßCÊ¿d_ÿÁ5~ü4ýªÿàŸþ0ømÿ «áßì–¿<}ûZMûy\þÏ—ÞñÄ/ÛKÁÓ|iÒ¥øã$ñÏŠ~øËÇzTöIáŸ;Yé~ ÒüC¤iÂûÏøGOѵûoB³ÿ‚Xø—áÌ:‡-`|%ý˜´ý ãÄ>‹ìëªü:ýž´½rÊ÷Å>øËb·øMa«éÚf£â-â"C [êZ}î¯a¬K¬÷þc|Kÿ‚jþÖÿ¿hÿÚëö¶øñ?á—Ãÿ ~׿¿à™ß<¤øÒ×â·ÃVßgÙçÇŸ þ=xÄwº×ÁÁñöYø±ãâDšw†)ø á§Æý'öiÕÿe–ý­!ýµ~Ÿ‡´ÄO†ÿ <'ã߉·zˆ5¯ƒR[ø<[¬éw>,ñ‚|@º‘áíFwùiâqxt©fÒ(}bòS´§l©`)Så¼\gFT±÷R:Ukr»®]Ôa'w6šår[Ù¸'-ÿ½xú´´±ýCxfËþ }â‹~0ðŸ„í`sã¿í#ðîîç↼;ìõ©ü[ø÷ðŸâ'‡ÓY¿ŸÆ:6˜³øËâ—ÿøVî=Zî]jÛ]ð׉¼?p—Ó5î8•ºÏƒ:Wüö‡³Õµ€:ìKñÏOÐ>X~Ì:õ÷ÁëO?lôoÙŸV›U¶ÒÿgZçÁi­Á§| Ô®4-n „·€/&ѵX­ü?$š}ÚÃùGðóþÅñÀ_¶wˆ>*N~ ü@ø)©ÿÁ@%ý¼|)âO|rý²´o|'¸mÙ4ß…þý•¾xãáçìÃ{âêvžðGÆßë¾*–Ãál÷þñGÂøe4Lýÿ‚A~Åþ?ÿ‚Á>þ~Êßu‡šïÄï‡üR¸ñ—ˆ¾Þkz§„5©üiñ›â'ô)l5oø[Áž Ôe±ð¿Š´M.õõOصµýÕ•™»Óí­on]:˜‰ÕP¯ƒ¡Fšx™Jq‹nñN:Æ1RªêT©Ï8IAÚÎíS²ZI·îÛîÕü¬•žÌöƒß?à¿´w†¤ý˜¾ü`ý‹~<ø?Ãß-téÿg¯„>üQðÖ‡ðÂí øVÊÂ_„Þ ÕõÝ/MøqáÆ½ðLJ-m›@‡Ã:C]è:DIngÓíŸæ/Ù+ã—ü“ã•·íû|/ø=û5ü!´ðGíIñËö\ñÇìão‡ß³€-¾:øûàðüŸÕõx¾'|8c}fúw‰µ ÚkDè·­­xFþ͈¿ÜŸe†'“Ë|KK›¹dù\RjI{œËXÅ% Q匤äÒå´`šµßI·f½:|ÞíôíÕŸ¬´?Çïø'.ðÇöøMãß‚¿ÿh¿Øÿö¥øÑðÿöYýµ¿ø ö~øßû%ø#ℚmÆð;Á’xmüEui¥húŦ…â øWøeðÿÅþðZøKRÒüQ¨xÖ]k£zÁƒñÆ—ñ;Ãß¾h_t/†V4OˆZ7€|)¥øãHø7§k1ø‹OøK¥ø²ÇJƒ_ÓþXx‚(µÛ?Z_Åá[]f8õ84¨ïcYÇó3ñÿöøÍð·ötýŽeOü³µøÇñÓþ ðóö¹ñ—Ä_Ù¶óöºý¡4ØóÂñ׎¿iÛßÚSö­ñ'ÄmXøïá­×Ö—:ïÆ=ÀžøÍ­xÃÇš‡ƒ¿fíOYÓ¼W¨êåFäšÉNR”ÝHE8Æ“V_ œnáw-4’º¼yíÓKv¶«ý:[Æ2sø©ÿ?­_‰N×úõê´KŒ{qøŸOóÜVŒKÃÄõÿ>õ…Y[¾›ù·ýoê"ÌKéìõÿ>æ´"^˜úêz~¿Z­ôöà}O_óïíZ®??ÿçßڼ겵ü¿öþ½K‚»¿EùŸÁO†?c¿ú®±Çþ¶WÀ*ûWà§ü‘¯„ŸöL¼ÿ¨®•_ÁO‡üa׉ýWØÜæåüÿ?}§ðSþH×ÂOû&^ÿÔWJ¯.»¼ÿíÕ½¿ÔÔðßÚ/ö)øYûMüIø3ñKǺÿÄ #Ä¿²ÿá³ðŽ«áË QþÈý¬bÿÛÛþ+}gšýíÞÿ‰¿°¿ÂM ìÝCHÝàO|EÓ#ò¼A«øgÄþùGÂÿðGÙûÃ?ü1ðf_µ7‹4/†_>|ø¯øçâ/‚þÄZçŽþ(üQOگºn£¢ëðUOÙ~OŠþñÿ‰Éã„Ö¾ ðçìÖ¿SâŠ¿à›¿¯ÿaÝ_ö~?ãø¡à? xx|8šç/Dzÿíc¬Á84oøSÿ‚…|Tñ·í/ÿõþÔž(ý¥<ãÿŠ¿µ7ðÿ‹<ñ¯Á?Ήâápúýð_þ ÷û$ëŸ>\|øÅãO‰ ôo¿ðK…ß> xWâÃOh>1ø{ÿ£øùâÚ3öjñ-ŸŠü/๼9âßxÛÄ7ú_ÆMgBh´OxVk/éÿµhæ×'ûŸáÎ¥«xRX>üVø©á¿üYñW‰?h?ˆÞ Ñå¼ð¾“âGà¶Ÿñ¦æëÃÚ?…ôÂ×Z¾‡ðKÀ_¾ ü.ñ_‰ Ðµ lõkÿ ?ŒüUâ?ø¶x›ñÏÅÞ øõðëö²Ó~ü=OÛ5ü~ýˆ<û&.‘â_ÚÆ±ÂÿÁ=<1àßz'íb?h_뾿ð[Å¿vµJ·‰?h½{[ý¦µÏÿÃ?j¿ µ]J½nò~Oöð¿Ç«ÏÛSögñ—ÆO~Ûñ ðí‘áÚÓâOí oã½Gà-¿í‹ãŸŽÿðOmSÆÞýœµOê7Úg‡~ëIðçÄzÏÃ]#áŽðÅ ü; ß|'÷¼+ûBÉ¡€CtQEQE|…ûÿÉñ§ýïüÿ[ßö•¯¯kä/؇þHÏ?ìïà ßúÞÿ´­}{@Q@Q@Q@Q@~cþ×£?¶ì­ëÿ ×ûhãëÿ Cöúq_™_µÐÏí‡û+ÙµþÚ8úÿÂÑý…ù÷¯¢á7n#Êld_þK#Gðj‡õEx—§·êzÿŸjЉz{p>§¯ù÷öªÑ.1íú“üÿŸJщqaŽýO§ëù×ôMY[ÎÚ¿6ÿ­üÏ* K÷ü¿áË®ÓýϹ«1ŒœûàŸóÞ£éøÕ¸—§·êzÿŸjóêÊÝôßÍ¿ëRË1/L}õ=?_­hD¾ŸAýOÿ_ëU¢_O úž¿Oþ½hD¿Ð¯óîkΫ+_ËñoúüÀµÿ@>ÿϱ¯ËOø-·í%ñ§öCÿ‚aþÓ_´?ìñã?øW¿~ÿ˜ÿ„?ÅÿðŽøOÅŸÙð–~Ð <âø§üq¡x—Âú‡Ûü/â]oKÿ‰¦‰{ö_¶ý¶ËìÚµ¥Üª1¨>œêϽ|Kÿ ý‹—þ ûüfýâ;|#_‹ßð®Çü,%ð€ñáðÿü ?ü ñ?#‡Äþ ¯ö©ðPÐÏüTšoØF¤u/ôϱý‚ëÆÌUY`ñpù*òÃW… ªsöÓ¥%IÆnQP’›‹Sæ+÷®­sJIs'/…5{ëÕ_NºÍÏü'þ ¯ûzþÈ^;ÿ‚Ãü,ð‡í¨Þh²Ï„¿àšþýš>(ø¯á¯ÀmCÆ~øÉñƒÃ <[ñ5;JøG£ø/ÅšŸÇ‡úgí ã6·á)<3àýSÑÙü3ðÿôÙ´Í2ËöJ/ø,¦ÿ ÿ!ø ©ÙÜÿÁF?áÔããÈø‹ xÛþþ£¬?þ_ü À¡þÕðŽ7ăñKûL]cSm+7+ôŸŽà–ß±ßÇ}ãÃ~Ò_¼3ñKÇ_µÍ§ìï}ûYx—@×¾2ü<Ð>*xãöhðÌ^øiâox^×ãˆuoƒzo‡á}FßLÑ<ãA©]èZ„º/޼QãÙûTÔ=bø'¯ìvŸ´ý§Óச¿›ÆÇâqÔÿá)ñéðOü-Vðùð«|]ŠÏÁÁñxøy›J?‡€‡Ä/²–ð’ïfsóßVÍ)Õ©8ââéΪq§RsªéÒx¬e^O~#uBµ rP”dÝ/f«Frž÷…¾{t²×– ¿u'­÷ÚïOç—þ Áÿ×øËûBþ¬¾"[xƒâíû9ÿÁ?kßÚßãßÇÓ«|6øâ«ÍGÀ¾;øÏgð‹Bø ðóþëÇ_ ×~x:ê iZ×Ä ;Äú¦·¡8Ó¼þ¿Ï½W‰sÐåüºUµ +𬷷M¯_ëm &‰z{ò~ƒ§çý}«B%éžüŸ§oóïUâ\ãÜäõè?Ïê+B%Î=ù?Aþ>¾õçÕ–þZ/^¿×b^™úŸéþ~µ¡ôÏÔÿAÓôúÕh—8÷äý;~×Ú¯ Àϯ?‡oóï^uYogä¿WÿÐÒ wò_×õÔüºý¯äýÿàÿö.~пú¹¿aªýg¯É¯Úøcööÿ‚w{øsö†?ù™¿aœWë-yu]êJÞ_’4 ù þ óÿ&ûÿÙ¡~Í_ú¦|_^×È_ðOŸù0؇þÍ öjÿÕ3àºÌ¯h¢Šù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½\ÇüTgöe„zþÒß°ÀÿÍâýk§ý¥ä³Á>ìï|iÿ¬ûoW1ÿçöf€zþÒß°¿þ·ìë]x÷ìý…á¿ôô ŸÃ/ð¿ÉžaçñãðϵhĹǿ€ôÿ=…V‰}>ƒè:ýúÕ~1ŸÀ}ùý+ún¬¯Û]ü’þ·õ<„¬’ìJ£$ò]‰_ÐóÇn•Z5ÏãÇáßüû{Ö„KŸÇÀuÿ>ÕçT•þz¿$¶ÿ‡òj%Î=ÎO^ƒüþ¢´"\ãß“ôãýj´Kœ{þ€ŸåZ/OÐÃÿ­Ò¼ú²ßÏWè¶þ¼¼À°ƒ>¼þ¿Ï½Y‰z{ò~ƒ§çý}ª *ôKœg¿?€ÿë^}Iwë«ô[~^ºAYzëþ_×™f%éïÉéÓ·áÓó5¡ôüÏôçÞ«D¹Ç¿'è?ÇúÖ„Kœ{ò~¿?ëí^uIw~oô_ð= ,Ľ3õ?Ðtý>µiOÓŸð¨Ð`}yÿóïV¢^žüž;~?3^}Yw~oô_ä½³ôÏnO×·ùö­—§·'êz~_ÓÞ«D½?3ýÿ[ëZ/OÌÿAÿÖúןV]ß›ýù/Ch«%ç©f%Æ=¹?^ß—ô÷«H2séþúÿ…D£ëÍ[‰zg·'ëÛüûWŸV]ß›ý?¯4Qf%Æ=†ON§üþ‚´"^žß©?‡ÿ_¥V‰qnO×·åý=ëB%Æ=¹?^ß—ô÷¯:¤»ôÕú½¿?M@³ãß©?Ïùô«ª0ÿ9¨b\cÛ“õ?áëíVdçÓùןV[ùjý^ßן‘´U’ûÙðüqûx—Ûã‡ìlÔþÙŸsÿê÷¯³¾ ÉøIÿdËÀ_úŠéUñ·ü qûx—Ûã‡ìmø“ûf|ÿ?•}“ðSþH×ÂOû&^ÿÔWJ¯>nòùkø¿Ô£Ók—‡Àþ ·ñ¦£ñ&߃â&¯á}Àú·¡Ð4˜¼i©ø/ú¶»¯xŠRÑuËß èZ犧¯ù÷öªÑ/éú“þxïÒ´#\~©ÿë~}+Ï©+|µ~míÿæÀc}?·ú}Ôõúõêº œútúŸóü«B%ÇáÇâzÿŸzóêÊÞvÕù·ýoæod—ßêY‰}=€þ¿çÜÖ„KéôÔÿõþµZ%éíÀúž¿çßÚ´"^žÜ©ëþ}ý«Î«+_ËñooëÔe˜×?€þ§üûÕ˜×?úþàj :ô«‘/§°×üûšóêÊ×òüßô¿,Ä¿¯éýúÕ¡ô÷à}_óíïU¢^˜úêz~¿ZЉ}>ƒúŸþ¿Ö¼ú²·ËóåþféY%÷ú–#üПҭF¹üOè:ÿŸj„€}*ìKéôA×ëÿÖ¯>¬­ÛM½_ùvòc,Ĺǿ€ôÿ=…hD½=ÿ@?þ·J­ô÷à}_óíïZ/O~Ðuÿ>ÞõçU•¯å¢õåÛÈ7,Ä¿¯èùã·J¶£$Ïüþ• úð>ƒüþ•f5Î=ÿÿ?ʼú²µü´^¯ü¿Ct¬’ì~[þØ#··ü´úøsö‡ÿÕÍû úëõŠ¿(lAÛÛþ Ø}|9ûD~Cã7ì1Šý^® |La_!Á>äÂ?bû4/Ù«ÿTÏ‚ëëÚù þ óÿ&ûÿÙ¡~Í_ú¦|R×´QE|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯qø¿ðƒÀüª|1ø¥êzǃµOšÕÕ®‹â¿xYƒYð7‹´/øGVÒ|]à-wÃ>/Ðu=ÅþÐµË ý]Ó®’ëN‰W·y¡—ÿi_ù,ßðOŸû;ßë~ÛÕõí4Úi¦ÓM4Ó³MjškTÓÙù-­Á)´[ÝgV½Ñi¿ÚcAÒ/5;û­+D´?í_©ÿci·sMc¥i]~Ó)u¨gÚ¼VŸn¹DžïÉûDʲHÀgç¡ÿ£²ý¦¿ðýþÕ?ýuúõEtý{ÿA˜¯ü(­ÿÉ“ËåÜ¿®‹î?!‡ü‚1Óö´ý¦ÇÓãçíT?÷æéÃþ F£§ímûN§ÇßÚ¬ïÎWë½¾»Œÿ ¼Oþ«ÿɇ,–?rþº/¸üŠðJb:~×´øú|ý«þüí8Á*$?k¿Ú„}?hÚ´ïÎ×ë¾·‹ÿ ¬Gþ©ÿÉ,–?rþº/¸üÿ‡UJ:~ןµÿ»ý«¿ú'iÃþ [p:~×ßµ ú~еxÿßž¯× )}kÿAÿðuOþHv]—ÜÉÿ¯º?lÚ˜}?h_ÚÀïÏÓ‡üÊôtý°ÿjôý¡¿kÿ¿?_­”RúÆ#þÖÿÁ³é·Ú .ËîGä¯ü:Öÿþöªÿćý¬¿ú()ßðë}Dtý±ÿjÁÿwûYÿôPWëE½½ùýWÿO¦ÝB˲û‘ù3ÿ¹ÕOÛ'ö®÷q_µ§ÿE ;þy«Ÿ¶gí`?îã?k_þŠýd¢—¶«ÿ?jàré·P²ì¿¯øe÷“Ÿðëícþ7ö±ÿÄý­¿ú(ißðì htý³ÿk1ÿwû[ôQWë ½¥Oùù?ü ]6ê]—õÿ ¾ãò€Á0õÑÓöÒý­GÓöý®þý8Á1¼@:~ÚŸµ¸ú~Ò?µÈÿߣ¯ÕÚ)sÏù¥ÿ>›u .Ëúÿ†_qùIÿÉñéûk~×#þîOöºÿ褥ðL¯Ÿ¶Çít>Ÿ´§ít?÷é+õjŠW}ßÞÆ~Jj¿ðKñ v>*ý«ÿhßéZç†üHžñçÆÚ_Ç~ºÖ¼â/Åž½Õ|%âÿÚCZðηý‰â]H×,mõ*úÒ-KM³º04FGê/‚¼9ÿƒ|%á¶hÿÂ-áßÚgû'Û¿±4«M3íŸdóî¾Ëö¯²ùÿgûMÇ“¿ÊóæÛæ7ME (¢€ (¢€ (¢€ (¢€ (¢€>BýˆäŒøÓþÎ÷þ ÿ­ïûJ××µòìCÿ$gÆŸöw¿ðPoýoÚV¾½ Š( Š( Š( Š( ¾gý¡ÿe…Ÿ´Ðõ_·Žt_ø/Ã>>ðßÃïxâ¿Æ…ºÏ… ø‰ÿµÎ¾.OÂ_ˆ_äñV™s«øÁZ¼Ú‰nµ 6K¯ ÙRÕ¤¸’_¦(ª„çNJtå(N.ñœ$ã(¾êI¦Ÿš`~Bç-ÇOÚÇö™÷^¿jþ‰º_øtü?ôv_´×þ¿Ú§ÿ¢n¿^¨®¯cè3ÿ…zmöÉåòÇî_×E÷ãþ CéûZ~Óƒéñóöªûó”áÿ¤§íoûO§ÇïÚ¬ïÎWëµ¾¹Œÿ ¬Oþ«ÿɇ,–?rþº/¸ü‹ðJf?kŸÚ€}>?þÕƒÿ~vÿ©tý®ÿj!ÿwûVÿôN×땾·Šÿ œGþ©ÿÉ,–?rþº/¸üŽðJ¹‡OÚ÷ö£OÚ ö®ûóÔáÿ­¸?kÿÚ”}?h?Ú¼ïÏWë}¾µ‰ÿ Šÿø6§ÿ$;.ËîGäÿ‚X]ŽŸ¶íN>Ÿ´/í`?÷çéÃþ g|:~ØŸµHú~Ðÿµˆÿߟ¯Öº)}cÿ?ëàÙÿòAeÙ}Èü•ÿ‡Zêôx¿µWþ$GíeÿÑANðK}Htý±ÿjÁôý¢k1ÿ¿A_­Röõ¿çõ_ü?ó .ËîGäÈÿ‚]jƒ§í•ûW§íûZýúpÿ‚^êã§í™ûX§íûZýúýd¢—µ«ÿ?jàrÿ0²ì¿¯øe÷“Ÿðëýgþ;ö±ÿÄý­¿ú(©Ãþ ƒ­ŽŸ¶‡íf>Ÿ´ín?÷è«õ†Š^Ò§óÏÿ—ù…—eýÃ/¸ü ðLMxtý´¿kQôý¤?kÿ¿GNðLŽŸ¶¯ín>Ÿ´—ír?÷èëõvŠ\óþiàOüÇeÛúþ’?)?áÙ>#ÿ£×ý®ñ$ÿk¯þŠJQÿËñ éûl~×CéûJ~×cÿ~’¿V¨¥wÝýìÌ?ÿÁ6 ðgÅü\Öh_‹ßüEðúè þ¯Žþ4|Tû‹yâOø›ÄZ6‚>)|nñ͆¿á%¾ð7†?µ5'MŽîìm;ÏûL6«nß§”QH¾Bÿ‚|ÿÉ„~Ä?öh_³Wþ©Ÿ××µòüçþL#ö!ÿ³Býš¿õLø.€>½¢Š(ä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{_!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ÔÿhOŽìéð¯Vø­â x¿Æ–:w‰>xFÓ¾‹Ãø³]ñÅO‰^øSá-?Jÿ„ÓÅ> ð¬>wмk£}ºï\ñNce¦‹Ëǹf š£NQ„#)ÎrQ„"œ¥)IÚ1ŒRnR“i$“m»%pÛsÚ¨¯Î¡ÿÕ[§ìAû^ŸûŒþÅ_ýáÿÕÏöý¯ðuûÿôdW¬øwˆùp½rÌjÿÜ~Ú—üý§ÿÇüÏÑ:+óÀ~ßÚÛtý‡¿kÓÿq¿Ø ?Û&¤·Î¼Ý?aÏÚôçþ£¿±7õý²ª^Až­ò\Ùzå¸Åÿ¸CÚÒÿŸ´ÿð8ÿ™úE~|ÛÏÄMÓöý¯Nê=û_Û.¤·‰O#öý¯ðûÿôfT¼;[äù¢õËñkÿpÚSÿŸÿÀ£þgèù¯ã_ø(ôŸüâïˆ>1ýŠkÝÂðÆ¿ã/jÿÚŸ±Ž¡ý•á¿ éWzÞ»©}ƒKý°ïµKï°é–7W?cÓ¬¯/î|¯"ÎÖâáã‰ÿJ+ƒÅàåâð¸Œ,¦œ¡E ´%(§g(ª±‹’OFÒi= RŒ¾)z4ÿ ¢Š+œaEPEPEPEPEPEPEPEPEPEPEPÈ_±ü‘ŸÙÞÿÁA¿õ½ÿiZúö¾BýˆäŒøÓþÎ÷þ ÿ­ïûJ××´QEQEQEQEQE|¡ûD~ÖZ?ì÷âÿ†Þ?>/ü`ñgÄï üHñv‘¥|)o„VÿÙø[ª|4ѼK¨k×ß~-ü'Óbóu/‹¶Òí4‹Ífúë~£4ÖÖ¶ö^lºÑ£[V0ôj×­V\´èѧ:µjIíS‚”ç'Ú)¿!6’m´’Ý·d½[>¯¢¿:ÇüV=?bÚ÷ÿ_±Pþ¶E8Á@µƒÓöý¯ðwûÿôdצø{?[äyÂõË1«ÿpí©ÏÚøÌý¢¿ý‰?ú3)ÿðÝÞ&ÿ£ý¯ðûÿôfT¼:[äù¢õËñkÿpÚSÿŸÿÀ£þgèðÇ‚ÿn(|KñCágÂïþÌ_´wÂ{¯‹þ'×¼á/xöóöhÔü&¾$Ð>xûâÄúnª>þÒ|Weöß |5ñA±¼‹Â·VR†ÎÎöêÉo#œ}Ï\5ðøŒ-GKB¶ªJN•zS£QFZŸTŒd“[;Yô)5%x´×tÓ_z (¢±QEQEQEQEQEQEWÈ_ðOŸù0؇þÍ öjÿÕ3àºúö¾Bÿ‚|ÿÉ„~Ä?öh_³Wþ©ŸÐ×´QE|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ®Kþ 9Ïì¼ìä?aÏýmÿÙÞºßÚWþK7üçþÎ÷ÆŸúÁ¶õp?ðSIuˆ?dVoX麦¿Çÿت]LÖu[­GÔuˆÿmoÙñôËW\±Ñ¼G{£i·w¢ {íVÏÃÚõÖk$·–ú6©,)c?f\Ôs 챘V윕x7hÅ6ßd“od›& ¿Âÿ&xìkôà_óîjÌk“ŸÀSþ}ëæ‘âÚü`Ÿ³o§ügÄþ¿ø†µj?þØ#øû5œp3ûW|PŸû³3þMSÔÌ(¿±Œ×oøOÇì­ÿPÝ{y³Ãå}ãÿÃÿ’>œ‰zcè?©éúýkB%ôúñÿ>õó ~'ý°Æ1ð'öi=¹ý¬>(ŽOýÙÿ&¯GâÛŒ|ý™ûŸÚËâç¹ÿ“-?ä÷¯>®>‹û½ê²ÿ¹}öüK„ïîÙzéççÄú†%þ€};ÿŸcWÉ}? ù‚?þÙd|ý™xãŸÚÓâ˜üäÊùÍXOþÙDä|ý™9àgö¶ø¨?ù2ƒþs\qÔ]ýÌVºÿ¸ãv_÷úÔÓ•ÿwÿùùþ}™?íª¿ñ†µÁÿ«bøöÐ|*ñ_ø~žõúûɸü:ÿ¹»ÿS¿×åWÆöÍøÇðcâçÂ!ðwöcðïü-O†>ølÙÓö`×ÿ°®?µÏí‰ñ[Jû_ö®½ªkñáÿ 7©yßûKìßñû7›äyß»ó<¨ÿ5ã74Åa*à°˜šÐ¥B¤j?cR—,DÒµhÓoMoÖûxyÆ’”¢›•Ö©éeÚÿÕû3õ’Šüî_ÚKöÓlcö\ý—¹õýµ~+ÿôTƒöŽýµ Àý—eÞêõ¾,ôׯ<‡8[åø…×hÿò_×ÈèUi½¦¿¯øsô6Šü÷_Ú#öÖlcö^ý–ùõýµ¾,ôTËû@þÛ-Œ~Ëÿ²ÈÎzþÚÿ»}?`ƒPò\Õo®ºíþKúùÚCù‘úE|¿¿mÆÆ?fÙ_œõýµþ-öÿ»5(øéûoŸøfÙWŸ_Ûcâçÿ@5CÊs%¾²ë²ÿ?ëäÒ̽诃ã‡íÀØÇìÃû*sëûl|]ÿè©—ãOíÄÝ?f/ÙKןÛgâ÷ÿ@CËqÑß U|—ùNi#îÚ+á•øÅûr·OÙöPéžm¯‹ý?ñJ>.þÜägþ“öNçþ¯kãÿ@ CÀâÖô&¾KüüÚ=×õÿùögÜ4WÄKñcöénŸ³/ì›Ó<þÛ:â™~)~ÝÓöfý’úgŸÛoãÿKþ¡áq zR_wùù‡4{¯ëþóìϵè¯Ï¯þÓµ7Â?Ã>/ø½û=~ͺÃ}Câ§Á?†þ*ñ„¿lˆ¾$×¼1eñŸã>Ûø¦ÛAñ?ì_ð÷AÕm¼1¨xîÏ_Õlµxj)t:ü&© â ß}Ù^ÙjvVšŽwk¨iú…­½í…ý•ÄWvW¶Wq$ö·v—P<\ÚÜÁ$s[ÜC#Å4N’FìŒã(JÓN.׳íý!¦žÅª+Ïü]ñcágÃýcþñïÄ¿‡þ ñ‹üøDô/xËÞÖ&ü`øIðêìØ.|ßüQøuá÷xƒÆÞÓõ>ƒÂ~,ð¯|+áŸxÄÞñ§‚|iáýÅžñ„õ;Ä~ñg…|G§[kñ7†|C£ÜÞi÷‡õÝ"òÏTѵ.òëNÕ4ë«kë™í§ŠV‘Q@“ kúŠ´-ÅÖ´ŸxgÄšN¯øwÄZ£g¬hZö…¬YèéÖ‹«éÓ\iú¦“ªi÷÷Úv£cq=õœðÜÛM,£±¯ëú…t-kÅ(Ö´Ÿ øgÃzN£¯ø‹ÄZþ£g£èZ…£ÙͨêúÖµ«ê3[éú^“¥éö÷ÚŽ£}qœ\ÜÍ;¨µQ@Q@!~Ä?òF|iÿg{ÿÿÖ÷ý¥këÚù ö!ÿ’3ãOû;ßø(7þ·¿í+_^ÐEPEPEPEP_—¿¶HÏí}û*Ù·þÚþ¿azýB¯ÈŸø(÷Ä}?ö§ý“&ø_áOxÃ_o€¶TWšg~ kß ôx4sñö"{‹ëmsÃß þ*ÞÝjQÞ¦Ÿo•/‡¬ígµº¼¼}fÞ[,u£áÆŸäó’“ŒqmB©+rËá…8Êr~Q‹~F8…z5÷z´º®¯Cqןð«Q/OÌÿAÿÖú×ÌËâÚüŸù!Ÿ³o©ÿŒ¬øŸÓÿÒ®GâÛ·À¿Ù¬çžjïŠÃö2?ä×ôuLÂXbû¿øOÌ<¬¿Ý¼öÿ#Çå}ãÿÃÿ’>‰z~gúþ·Ö´"^™úŸéþ~µó~(ý±8ÇÀŸÙ¤÷çö°ø¢8÷ÿŒ/?äÕøüQûcqÿög=ùý¬þ) úÍ–óž•ÁSGùq}ßû;ÊË\?žŸð !«¼{|qòóôüO¨b^™íÉúöÿ>ÕiN}9ü{Ÿjù|Sûdÿ$öeçŸù;_Šý5f?þÙc§À_Ù“Ôçö¶ø¨>ƒþL þ_Zóêc©.+»ÿbÆyYkCîÿ¥ò¿îÿàQÿ??ϳ<·öêoÙ;þÎsPÏÔþÊ¿µø~•ûû_Ï7ÇO‡¶wÆÏøTØøWû1xgþÄë‰?´÷Å]gûsí ~(ü1þÅÿ“;Ò¿³6ÂÉþÛþÐÿ‰†ïìS¦ý…´~ßaú¿´¿í Ý?eÏÙ€qžmOŠßý½kòž)Ë3Ã6ž+ ƒÄÖ¢èQ‡;¥*~ôR\µT&¬Úû6}.‘ÙBp„9e(§Ìݯ}»_ú¿f~‹Q_ëûH~ÚmÓö]ý—¿ÛWâ¿ôý‚Í<~Ñ¿¶£tý—eÞ?êõ¾,ô×Í<‡8[åõ×Ê?ü—™º«Mí4~†Ñ_Ÿ ûC~ÚíÓö^ý–ÿÛ[âÏôý‚ML¿´í´Ý?eÿÙd}m‹_Óö5%ÍVøËåþKÌ~ÒÌÐ+àUøóûn·OÙƒöWÿÄ×ø·ÿÐ RÂôý·¿èØe_üM‹Ÿýµ)ÌVøJÊÞKËÏÌ=¤?™{Q_/ÆïÛº~Ì_²§þ&ÇÅßþ€J™~3þÜmÓöcý”zãŸÛgâ÷_ü@:‡–ã–øjªÞKËÏÌjq{IuÑ_ /ÆÛ™º~ÌŸ²\sûm|_ëÿˆRÂÜýºüÛ'ìÿ‰µñƒÿ ¡àqkz¼——Ÿ˜sGºþ¿áÿ>Ìû†Šø~+þÝMÓöeý“:ãŸÛoãÿ@L¿?nÆéû3~É]qÏí¹ñ‹ÿ¥ük7…Ä-éM|¿àù‡4{¯ëþóìϵ¨¯‰|ûK|^Óþ=øà‡í ð¯àÂÏøYß >'øïÀž'øûHøŸâö¦³ðÇÆ¼#yà«ÝÆÿ³—À¯#Sñü.½;PÐeѵo]\Â=ªÚK¤ÅæÁr>Ú¬eã%f·O§R“¾¨+ä/ø'Ïü˜GìCÿf…û5ê™ð]}{_!Á>äÂ?bû4/Ù«ÿTÏ‚éõíQ@!~Ò¿òY¿àŸ?öw¾4ÿÖý·«”ÿ‚ɰ/ýœ—ì7ÿ­Áû;WWûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ®Sþ 5ÿ&À¿ör_°ßþ·ìí]™wüŒ0?ö…ÿÓð&¿Ã/Éž$ƒ'>>¿çúUø—öýIÿ?Ê«D¸Ç·'êÃúV„KŒg·?‰ÿé_Õue½ºè½:ÿ[êx%¨—öïÔú~¿hD½=¸S×üûûUh—ôýIÿ•çÕ•ïç¢ô_çÛÌÚ*Êß7êL0Óñ¯ý¥¾1xëàgزmü+¥ÞØê·—^:ñe¯ƒ¼'ñ#âmÿ„ô›>ÓK¿²øQð£â·ä¿×t¹4ÿ^ikZ®ïè2séÀúŸóúÖ„KÃÄõÿ>õæb9§ Ætç$Ô*%85³å’q~qkTÞ©êRÑê¯åßîÔü®ñ7íÍñgAðGÃMBÇHøQwáÏøo[ÖüOûe]xãß²wÃZÓǾ/ðÍßu_ ø[HñOÄ[ü9¶Ð-4¯_|uñGì¿à Ýe®ÏÇ:ŠQøaáÏŸ¾2ÿÁIj„ß퇨ê–?³U—†>øCöÛÔÿg=Ccñ+Pý•þøóâ^™öoøµâøëâEŸˆ|%ŸÇ?Ù—Ä_?fÏü8Эüx|9ñÇ7?no¼GûÅôöà}O_óïí^Ueû8~ÏVñ]…Ÿì½áχ>ðoÅ;Ž?µ âÄ Ïâß…ŸµÄßÙ¶ÓÅúð/Å+ˆÿ³ì·ñu>j9øeûGê¿ ÿjO¥–³ªéþ&Ò4ÛO‡Z†½âÏÕÝ ö-ý޼1¢ øoöMýšx[ÄiáëßþØø|3ðÿÂöºo‡ô»³á‡ñ'¾#xãU>*¿ñN·}âoø‚í˜|-eá øw3Â?±ßì“à]Ïþý–ÿg?xOø• |fÓô/ |øgáíÇã… 7…þ,ZišG†,ì­¾%xm¢Œè>;‚ñFŽcS§ê–Åu¨âg8É×j ¬¥(9J7¥ ‘t£xjÜ£N.S“Óš¢å|ÎÖœRÛ¢×}z½{k§^èümøkÿMý·|yàŸ ü@¼øð³À^øõuû*ë¥ø‡ð¦ÎãDðßÇÏÛ—öaý—5Ÿëžø]ûn|føÏñ>M#Â?uCQø£­|ý˜l>|Hð}„|sðºÿ[ñ‡Û|)ÿý¦m¯$Ó>(kÿ²…ìõ?~ÚpøwÇwøÁ¢x~ßâWüóöùøOû|E¶×¼3kñ3ÇýƉûMj_´çø3á¿ _kž/ø[â;}:ÏT?nüAoáËÔýöaýšü9ªxË]ðÿìõð;BÖþ"øÇŸþ k:?Âoiš¯Žþ xŶ^>ðG޼e¨Yh]øŸÆ> ñÞ›§xמ'Öæ¾Öü;âÛ ?éÖzŬ‰ßè þxoW°ñ‡>x@×´¿øYƒKÖôOxJÕôÑñ§ÆúoÄÏŒfÃR±Óíï-?álüGÑ´ˆ>Ï4ðøßIÓ|WâŸí]zÆÚþ. QÄ«sbe'È“rw÷¹Ó”ÚŠ†ŽÉEëÒS´#!s/å[ÿ—¯]ûöÕŸ›:¿íùñæËáßßIû:Ûø;Ç#ñGÄZ~Ì^3Ò¾=ÞüWøÿ ø^ÃÀp|Qý¦ð·ÁMCâ߆<¡\ø¢óH×­¿iÙOáMÃj·ZTþ7Õ> xB=?Æ^-ýQð»ªx³Á^ñN¹àý{áî·âo x_Ö<â™ô+¯øSÖ4›MGPð‡ˆî|/«ø‡Ã7÷†®îfÑuyü;¯ëš Ú…”òiÆ¥§µ½ìýKÓßôÏ·½hD½=øA×üû{Ö2SM¹TsºWN1Vi$Þ›]«ÙZ+³w“Ò+KÚ×üº_ðÊÌKúþ€ž;t«j2@üÿÏéQ Àϯè?ÏéVc\ãßùóü«Ž¬¿_’_×àQf%Î=ù?Aþ?Ö´"\ãß“ôíùÿ_j­ô÷ýü?úÝ+B%éïü‡ùþUçÕ—ã«ô[~^ºðOüÜcö>ñ õøáûŸÃþ'à?Ÿë_qüÿ’5ð“þÉ—€¿õÒ«âø)ÈÇìyâ#ëñÃö8?‡ü6WÀ,Ÿzúà×ÅO‡øàÏÂAâÍ*çâUçÁŸø¼§É.§®é¾ƒÂš^ÝwÄ6štWGÃ=ìŠÖºN§â6Ò쵫ä“OÒ'¾¾ŽKuñ1nõS}`¿9AYzê~vÿÁGü'ñöóö¼ý†üuðGÃ?.­¼9áôðŸŽð.ÃMñ.™ã>i¿ô»?Ú®ÓÄÖPÿD?´gí[£~Ϛ߀|§ü!øÕñÿâÄâŒ<;ð§à.‹àm_Æ¿ð®þ'„WâwÄ[Öø‘ñ ᇅað߃¯¾ ü?Ñ'°ƒÅ^1ñˆüqáø+¾'Öµ4±O8ðÿüOàŒÂKàÛ?x­¾$xöUÓ¾Ǥèz$süPðWí}à¹~"|3øÇàë]WÄÚmÔŸ t_ø⦿ã­C]ƒCñ^gð;âÂéñú>‰yJ?4ütº’h_ F”ßðYÙ?a÷ÕÿjVéGö¾ÿ†¾[ÿ³ã~Í iýšöóökYÿá¨Mÿ†«U²×Ãð· ý[áÃ7៷o~üBø‘ûAøßöà°ý¤,l?ø%GÂÍ[Â? þ6~Ð>ø]/Áçü×á‡ü7\øuà„~'Ð<}ðý5‰ß¶>±â‹>‚{o¿<âï‹ |[ðËâ'‚ÛøKCøkâŸÙËâWŠ~&ø{ã/í3ðsöNð׌t?|?ý ƒ Úx›ìoÛËáí?ñÓÂ_ðSŸ„GHý±xçöfý±ì¿dO x[ZøÏáÝGÄÿµ\?¬oüUà¿ÜxC[Ò|{¯~ξ#ñ†q£þÊþ ñ̲|ø‘«k?´'ÃgCñ/ÂÝwöTе¯è¿ÃZ­ö»áÍ\Õ<5­ø3SÖtM+UÔ|âYü;uâ? _j6]ÝøkÄ>×üWá+oB¸šM/UŸÂþ)ñ'‡f¿µžM_Ö4ƵÔnvèù¦øyã?Š>'ý¬þ+xgàvµÿÖu/‡_·?ì% ü ‡ÇÚßí=¨þΞý‘5Ø‹þ Íã/ÚËÁ´ŒuK›âœß<_ñÆ·žý¬ì­¾5KûAxÏÞ'øb³|Z׿hëÉ?Z?à˜Þ ø‡à¿ØöI—ã6±ñÇZøéã/ÙÇà¾;KûDxÓâW‹þ(Ø|cÕþ |=´ø£ø‚Ï⎩¨k¾¿ÒµÝ2âÏ[ð]µ¦…§Š#ñ¹®ésxç_ñv·¬}¥£øSÂÞÔ|W«øÃZ…«xó_¶ñ_Ž5MGÓ´ÍGÆ~)³ð·†üiâ_ÞÙ[Asâ-~×Á^ ðƒíµ^[ÍF xSÃ~Šåt K´µß Š( ¿bù#>4ÿ³½ÿ‚ƒë{þÒµõí|…ûÿÉñ§ýïüÿ[ßö•¯¯h¯ø½ñßáŸÀ¸</ÄmSÄ·?|Aâ¿ ø DðŸ€>!|LñWŒ¼U࿃_>?ëÞðÏ„>x[Æ)Ö|@>üø•âF°ÑçÔ|Q¨èÞð½¶±ãoø_ÚװWäüƒö\øíûY~;ðìÿàoøO¼[§Ãeý³Iÿ„›ÁÞòákÿÁ'¿à¢ß³G€?Óükâi’oümøïð§Á_º½ì¿øJ¿á#Ö¿³¼%¡ø^ÑÀ?O¾|Rð'Æj^,øu®ÿÂEáý#âÅŸ…º†¡ý™¬igñßÀïŠ~2ø)ñKBû.»§é—²ÿÂ/ñ7áÿ‹¼3ý§´º>·ý‘ý³áÝCWðýþ™ªÞú5šOì5û\iÚ×Ä[Ï~ËðÅ‹oßðX¿‰ÐþÐZÇíka§þÐjþÛZ§ìÏû1øŠïá_Œ´¿ 4¯üRøÅð?ö†¿Ó¼3w£øwá.àUñöãþѾ!x;Â~ðƒþ ÑûIøGÄu-gö#ñ£û$j¾7ýŒþ"xÓöÖ¯?àŸŸŸö·ð…?oßüeðÄÿ >ø×Bý™nµÏø÷âgì‰ñ·R_Ž¿¼Bÿ4O„ÞÒüqñ‚÷Q´o†Ÿ À?¬J+ùìñ?ìYâ›{áæ¥â/ø&k|Vøf?gÝá÷ìëð~Õ~ ˆÁ>~-iß´Ç/x‡Ç’ü]ñ/Ž4¯|(?>øóà}ñ·ìomñ‡Æß³Ÿü) >üÒüMðÊÇÂÚί䟴üö™øÅyûTx/Â_´ügø¯ÿB·ñí߯üIðh_´×À_Ú¿àGí/ð÷öLý•õ›-ÄšßÇô‚ ø³û;ÚêzĆÚ7ÃO†öŸ²ìÞ$øe­x—TñýݬÀÓuq6¼©üFñWÂ; ~Öçâ7‚<àˆÞ*ð²Cx·º7‚~)k¿ü3à ~âw¶[ -|K®üø•§YÃmw=äøNùï­­a¸Óå¼üñ—ì£ûE~ÛŸ·ßÃŽÿ´‡ì/yðßön´×¿dü<ø¿ñSö{ø‘}©øoà¿ìÑÿ½Ð¼gsñ Â_ ~%xßÚ–‘mñŸöÅøሼ¥jþ>Ó|oàŸi:î»n¶1ü[ð_Ãoœ<-ÿ“ñ·…-¿oß KûF¶?¾ëŸ >xßá‡ÄÙËÀÞ2µøeð·þ ¥ûk|@ðÁ8|_¯êþ&ÕæÓüYû øËöƒÀ_üákßÙ÷âÂÏÙ¿Â?²íâ„þð_†ô4ú´¯ÌÛþNÿöUÿ³mý´?õgþµÎÿÁ=ô{ߨ×öQñ(ý¤>xOö?ðËütñ<ÞÐ"‹Á~ðþ“àWOð¶…áø÷áçÁ?‰ß?fÙšïĺ=üž#ðŸÁOˆ–ÿãÔˆ÷~ ñÇÄÏxSKÜý®®m¯ÿkoÙ6òÊâ »;ÏÙ›ö˺´»µš;‹k«k‰°¤°\ÛÏðf¹á¨µ½oö­ø³aãísàÃkÉuMNÂ]Äz?Âoøƒ[][JµÓí5­Nïâ·‹gŸ…²ézö•“ñ‹Qñ$z†í>ç‰ss“× ÿ?¨­—8÷äýøúûוŠU*AÆWFM¦æ §î§¬Zm;I]^ŒÖŽ2OzM'v®»mý|î¼ÍþÚô_‰ŸüqðïáÏÀ«‡>øá«x i{ÆÞOÚNãá¯Ãïø“@Õ¼ñOOðµ¿ìýðãáÄíáÛOˆ>(ñ ¿þ,üJðÏ›/Ýøÿà·Ã è?´l|gösÿ‚þÐÞ#ñ×ìÿðËã¯ü) 7ÆŸ~>xá§ÅZü0ø‹ðoÇ_´?þǶí¡DžñÆ?ŒþÕtíwÇ߳ǃ´_†_<ñ£â?„>*h:Äoÿ àOørÆúOÙù--ï-ç´¼‚«K¸e·º¶¹‰'·¹¶™)­ç†@ÑÍ Ñ»G,R+$ˆìŽ¥Ióþ›ûþÇzoÃßü#Ó¿dïÙ¦ÃáO|Ceâÿ|1²øðº×áï‹|U¦½¤ºw‰¼Mà¸<-†õïØÉaa%žµªi·Z•«ÙÚ47(ÖДðëÑÆ{XÎ8¾hÆrœ¡;ÁJ7Œ¡JJšqwåPu9W$š¥)ÎRt¥4ãmºÖÝÞ¿~ûÛT‘ø³¢ÿÁJ~4_üAý³>û=|Ñ>,jß´oÿ4Ž~ ÓdÑõÿŒúgÃOØ|XÖôy’Æ)t_â-¦‹Œ5-2X´Ý69l/u‰­^; $hŠÚÀ#ó+~Ο´­â‰}âß5ß…š—ÅÚ–ÇÁŸ ÛãWÀèþ>ø·áÖü+ðÒÇá…¾"ü=ñV›â†Ÿ´7Ч×üh¾°ð+xŸ‡¿à ßµ~‹¬~Ô´;xÛà×Ç/i?²ŸìmðÃàïǾ?ðf‡¥|^øçûrþÕŸ²lž5Ô¯~;ütø{gá gÃzíoöŠðŽâøqºo‚>êŸü'kû?x›ãoÆÙ_~Çß´€ƒöqñ¯‚ô/Ž¿5ÅÞ3ñ½ŸÇ_ x ljñ3Çß<¯|TñÏŽüyáßøD4¯ßø‹Å|M®x¶ò=#Áº&ƒ¦ê7ÞO‡´MM´°±µï4ïÙƒölÓ­ììôÿÙëàu…¥‡Âk¿€–V¶ ¼koeð+P¾·Õ/¾ ÚA‡Ò+o„×Ú­®£yðâOÜß[Aw6Œ÷Ç"ðU¡Œ“Œ¥‰iªmÉs4£ZPœU£qÆ›Ÿ2SçSå„ZŠŠesGùz®—Òë~·Ó§wݳñ—ÅðR¿Û«Áñ¤Þ/øaðÁ^4øû?ÿÁA¿h?‹šgŠôß xƒ]ñ_‡ÿb؃ƶ^Òü û>þÚ?´'†~øËÇÞý¦ø‡ÂÚŸÂï~Òúg€¾']x×Áz‡Ãx‚ÛCñž«ñ öðÖµ&™eú/£~ËŸ³?‡¼kðóAý¾hž³ð'ÄO…ÖžÒ>øMð}§Ã/‹×Z%÷ŇVÞ³ðü:,ø¡{០Þ|DðŒVIáÿÝxD¸ñ.Ÿ©Í¥X<áøSÃÔдMWÇí¿Š¼m©éFŸ¦j0ñ=§†<9à«Ox¦òÊÞ ŸkÖ¾ ð„¼%o«êÒ]êøc¾Ðc¸]+DÓ--y'Kß'ð5•(r¸ÚÐRn|­I¤Ú¿2jR‹Wò÷í®Þ¶ë¦¶ÒÝüüÓlÿŒgãÿƒ~Y| ›â™ãUøG'| àÛo[ügýŒ.> x7Â:~Ò¾$´²ñŸì·®ËáS¯Ýx·U‹Bý | 㻟›;_… ¾6Êx‹Xý:‰z{~¤þý~•Z%Æ=¹?^ß—ô÷­—öäýOøJÂ\Ñææ›ž®I4•¯²ºÕ·{¾ŠöŠŒRŠÕ+%¥¶¿­‹1®1íüÏùþUiN}8SþZ‰FŸÖ­Ä¿§êOùã¿Jâ«/ÃWæßõøŒ³ôöà}O_óïíZ/OnÔõÿ>þÕZ%éíú“øõúV„KŒ{qøŸOóÜWŸVVó¶¯Í¿ë0?1¿kQÛÛþ ãéÿçí?ø\ß°Þ~·×äŸíp1û{Á<ýKŸ´/çÿ ›öÍ~¶W…ˆþ4ÿíßý&&ñVI|…ÿùÿ“ýˆìпf¯ýS> ¯¯kä/ø'Ïü˜GìCÿf…û5ê™ð]b3ëÚ(¢€>Bý¥ä³Á>ìï|iÿ¬ûoW+ÿÿ“bOû9?Økÿ[ƒöv®«ö•ÿ’Íÿùÿ³½ñ§þ°Gí½\·ü_þMŽ?û9OØgÿ[‡öv®Ì»þFû Âÿéø?‚_á—äωzg·'ëÛüûV„KÓÛ“õ=?/éïU¢^Ÿ™þƒüûÖ„KÓ?SýOÓë_Ô•e½Ÿ’ý_üCÊ»ô×úþ»–b\cÛ“õíùOzü;ÿ‚¼Aa'íÿ¿ÿ…ùÉÿúã§ÅÙ¿kßøID'àä~(O†–Göi“ã¿ö˜>_…Ëãÿíá®·Žñà­„.¶FûlþåĽ3õ?Óüýkòïö²ÿ‚•\þÍ¿´Ä߀¾ø%Ä}CàÇü§ãŸüoâˆu‰ÒøÕ<ðžçÅ>ðç€<%agðçDzkÞ-ñ¯´-+U½Õîü-¥xCÂw÷þ+µêÚT Õþw:•ƒ”+×öl2Œý”«FR§^uNt¡ïT£WÙ:u£x©Q”Ô¥Û]0¿6ŠîϪ[«]7³Wº}ì|ñö§ø5ð/Á¿²/„?àŸŸµ/Ùoþ óñƒö†ý¨4/Ÿ¶@ð¿Š|YàOþ(ð'ÕñWüðq¿k/ xÏáV‡ð—ãÄc7†¼)©x×ßúF«¤ü%–ËU¿ÔæO”4¯ø(÷üëâV›syãŠúßì¹âßÿÁ~3ÿÁDµ¿h_þ _ͯ|røAûEüNðw‚<_©ÁñWá·Œu½@ø½ðÇÃ^ ÖüAá+[ˆ4½;J×®ÓÃVÖæ¶×l¿rüÿjý‘çøWû:xçâç‹5?„þ*ý >|ñ´^´ð'Ɖ~ð¯þ5üѾ9øwàlÿüð¶Oj_¯<«G¨xOáíÍÖ…ñ#ÇÚTšF«áß:ø‹H³¸õ}Kþ }ûè>øQã-göˆðæ›áOZŠx©Â¬ª¯cZ&Öôœ÷’æoV÷åÖëNÖJÉkæ+àŸíµûgøãþ ·ûUèßüiñ?ö¤øûÁ-¿h_ÙÿàOŒ´ÿ\ø{Äú/Æ‚~ñׯ¯ˆŸ~Øx7IñµàØòïÇþ?µÑ4ÿ^ µž¥ªKûD‹¯ìüsmõÿüöíðÇìÃñ'â/k߂ߴ¯Ä¿ ~×?²—ÃÿÙgÂß<1'Ƨýª5¯‰ú[ÜüQý“|iñSþ§ö@øOâ¼hrÂÀ´ñ‡ìËá+Ç¿ †¥àŠ~!Ó5û+Y5ïÜ}Sþ ¹ûøwÂÖþ,ñÆOhQÍñ»À?³”¾Ö¾þÐú_Å3ãOÅ_ êÞ5øgàà•÷ˆ>1xrxWCÕ5¿kzÿtß øŠÎÙN•¬ÝIskßYøóöðÇø¥¯èŸµ_ øÎM =Ãáçì×ûF|\øn|E¡^x‹N“Äß ~ü+ñŸÅ_ÛçØÍ³uã?èÞÖ¤±ðψ¦Ò¼G©iÚU×pmBQ¥ž9Æ1Ä9¸TmFXšøš¾Þ\¸»)©ÖåN§4$èÁ¸ßÚ)×i:VmÅj—E¯…n“vZûÏÈþ\?à™ß´_Å¿ŒÿðSŸØ“ã7ÇÏÛƒâ'Ä+OŽßðIj–Úg‹4o‚ÿ ~ø‹öš“ö¢»ðßÅŸÙ/Á>Ò~xnÊ}áZxwOñv­ÿÕÏü4ˆ.¾èZ߈þ ê?¤o‡²ý1ðömÿ…×ÿôÿ‚³xøxwöRÖáDøëþ iã·þÐ?²¯ü/ï‰z!“ön¶Ö¾Ùû9|Jÿ…ÃðßþÓÇGþÿ:OÿÂ+ñ?~¿iàmþõÿ„û;ĸŸ¿m_ÙÛàÇìÁ­~Øÿ|I㟅?<5ýÿ ˆ~$ü øïàoè'Yø…að·HþÚø-↚_Æí/ûSÆš¦™c¦ý»áÔ÷IÔtÿ[y¾»·×dð ÿà±ßðN­*Oï|rñ7…¥øyâ/xgâRxÇöwý§<~Þ|J/ü+½câ¯ü%?´ðÃÁ>=ÿ„D ø‰ããáßø êv«¢x–ø³æ– F8zxŒÂ•IC±×«UÆu”°•°êW©ˆœÔ%>zÑŸ4¢Õ9BÒ´æky6ÜbÖœº-š{%nÊÞgã·ìûÿ ý±þÛû+~Úÿj‹1ø»û]~ÒºWÀ™|Eá¿„ÿ²Ï„4„Éqñ¯ãïÂ]UMð?PðÂ_k—„¼qñâ·Äï…Ÿ­4ë[ o x;áæâ//‹ôÏ‘¾ÿÁWॴ¿ÃoKûZj_ µÿÁÿk_ø(µ€> ~ÍÚ…·ÄÚögý³?hÞÔõáñáŸGÑ'|BðW‡õ¯ þÚŸÿb¿øcá]‡íñ“Æ>/ñßÀÝ+B×|_oªø0þÍ>ñV—âO hšÚjÿ›Àz?Å‚Ó_N¾¶ý¡kWR|?ø½®éú†—rž(ýý”¿à¸þ7Kñ×PøÙðþ?ÙË¿þÿÁ5ø'áׂþé¾6ñð²Å6‹Ýþÿ‚•~Ä> Ó|ªØ|vÓ-í|tß´¶¹à߉ÔtgöUðâøÃö…ð¯ôx;IÕ¾øëáw…Œºþ¿à‰Ö>ñ¤ÚU®¡y¤è:Œ:f ÖÜßæ†g&œjB*nI©)Η;Œê©»Vv´ÓŒ¹T`âŸ3zÞΚÝ]ÙZÚJ×µ¾ÖzÝö?œßÙþ ¡ÿuñ¿Æ±û9üXðOößíð·öQý¬à zçÃ3Àž µÒ¾7ü+øƒû<|/ñìAð.´XkÚ.£àOŒÿµ?‡þ4½ðôÞñ>«¯xsAÐ5ÿx’î/Ûë~)ñ+þ ÿð§ìUûO|Tø[ûK‹·þ ýbÚÆ?áJ|²ÿ†9ý¸¾1~×ÿ ~|cýˆ‡‡ì~[ø'VþÊø_â_Ü⿇ücñGÂ_ÙCQÔ/ñ”wÚ'†|«]麊ZóŸðXOø'åÏÃ-/Mø×âÍcÄÿ¾"üoøAà_賟í=®üP¹ø©û7Øx_UøáðëÄŸ ôƒ7¿¼ão‡šgŒü3¨k>ñ÷…ü1«Ïe«Gu¦Z_Á ÛÁÇR–ßÚ2|ÊiIÍ^ÓŒiÓzUI´ù]Ú|Ò•áÉ7Ìôÿ·|¬—mZÛËäµV?œø*_ÆÛö#ÿ‚¯k_>øã]ø³ñá÷üÃS|uý üSᄯñ'áÏÀïø)>©¨üOøÃàŸ…ºGü7ð‡Æ>2ø£É¤øÂþÔ< š, ÉâßCâ«Ïjç^þÔü«i>!ðGƒuýÅmã½]𯇵}ÇÚK¿Œ´KH³½Ó¼Ví éº6†Íâ+9¡ÕÙ´mJҘݓ§i¶6fX¾Joø(—ìmÇ”ý›føÏmÅI>&CðI¼ñ'þ¿ü.ûCðA¾9Ÿéñ®_ •ÕcøDßGÄf·tAá“<‰eÿÁ=m»¿Û{ÁŸ´5þ¿ð®ßàÿÄ/Ùwö»øáûüUðž“ãɾ'xJãâ7ÀÉü<ºçˆ| ã›Ïü4ÕüCàÝnÓÄÚ]Ö‘y®ü?ð†°¬.¡»Ñ¢X¢š|}ØTªóª­µ ·Èá½ß4—3Ms9{ͫߢOT´µ’ù«$Ÿ¦žšýÿ q/ëÀöüþ•£ççºO×òªÑ/ôéßüûЉz{ð>ƒ¯ùö÷¬*ÊýµßÉ/ëPŠ»Kïô?9¿à¬Ú=ö¿û üGд½rûÖ·ñKöPÒ4ï銩xv÷Rý®¾YZkšrHV7¾Òn&ŽþÑ$e§·Œ9 “^û~Ìþø/Á? µ__µÃx;Þ|2ñLþ;üø¯ð³Ã>:ð.‹ñ«à®¯ðßCø‰ªøâ­¯ƒ£ø©à­vü/ñ߀[Fñö¥ðïÀ%Ô/4Ohž#ðÏŠ¼á¯|=×|¨éÍ^ «Á/>EñSàOÅŸ†>?ü Õf‚¾øðGÃ? î>j~ ðWïxO]ð?† m âßÀ¯ŠRxŸÄ:'„üYã@ñWŒo¼C¯øOIñÇ4ÏßøOñ׋íu¯Òú+?>ÿÁ~|2Ñ<àý#öý°õ‡Ÿþ$~Çßô_†×þhžÔ|eû ·ÀÍ/ö~¼ñ†—à_‚žºñÕ®ðÿöxøiðÇĶ~1¿Öâñ_†ô=#ű“â߃>üJðO¥ØÿÁ.¾é¿ ¢ø?cûKþ×|1ð†¡ð:ëàO޾ÏáOÙNý~?|4ý£¾h? |9sðjmÅÖ^ñ¿ÂèN±ûEéß¼c¢ü5Ò.~x{Äú7†õ½rÓQý8¢€>Bÿ†jøÍÿIý¯ð‹ý‚?úkë:Úk->ÆÎçQ¼Õî-,í­®5mE4øµ Nh!H¥Ôo£ÒltÍ.;ËÙ®nSMÓtý='•ÖÎÆÒÜGoÊ(ç߈ß~#xßIJë¾ý­?h/ƒÚ\–v–É࿇>ý•u? A5²2ͨEuñoöfø¥ãy~ÄIv“ø²ktQccedm…¿ üuðûPÕo<[ûJ|jøåo¨YÃmi¤üRпgM#OЦŠs+ê:TŸþ|Õ&¼¹ŒýšdÖõ-cOX@k{.3p}ªŠç<_¡ê~%ðÖ³¡hÞ1ñ'ÃíSS³kk/xB×Â7¾%ðìÌèÃPÑ­|{á_ø:{ÄUhÕ4ÿ³½ÿ‚ƒë{þÒµõísž/Ðõ?økYдoø“áö©©Ùµµ—eÿ†jøÍÿIý¯ð‹ý‚?úkëÚ(žm5–Ÿcgs¨Þj÷–vÖ×¶¢š|Z†§4¤Rê7Ñé6:f—åìˆ×7)¦éº~ž“Êëgcin#·Â~#|øãË®øgö´ý ¾irYÚ['‚þøoöUÔü5ÖÈË6¡×ſٛ◌Måû%ÚOâɬÑE”e‘¾‚¢€Ô5[ÏþÒŸ¾9[êpÛZi?´/ÙÓHÓô)¢œÊúŽ•'Á?€_µI¯.c?f™5½KXÓÖÞÆ ŒÜGñ~‡©ø—ÃZÎ…£xÇÄŸµMNÍ­¬¼iá _Þø—ó3£ CFµñï…|oàéïU£Tñ„õÛ’9{FéÑÑ@!Ã5|fÿ¤ƒ~׿øEþÁý5õŽm5–Ÿcgs¨Þj÷–vÖ×¶¢š|Z†§4¤Rê7Ñé6:f—åìˆ×7)¦éº~ž“Êëgcin#·Žåñßí+࿉Ëÿ<ñ×öµÓ’ú=3úWÁOÙÿÀÿ²'‰,õmRée·7©üyýž+k>'°øð#ö²×|)ðpŸËàß響c¬YxZãá§Âÿ‚^“Pñ­Î­m«x¶?|"ð7‡¡Õ4¸>Ëk­^ɨø›Yþ+óö½ý°¿eaÿV×ûhÿêÐý…} ÉI“ÿØd?ô™â?ƒSü?ª*D¿¯éýúÕ¡ô÷à}_óíïU¢_O þ§ÿ¯õ­—¦>ƒúžŸ¯Ö¿£*Ê÷óüþ¿3È‚»¿oÏúý 1/O~Ðuÿ>ÞõqFHçþªŠ1ŸÀ~f5Ïâqøw?çÒ¼ú²¿mwòKúßÔÔ³çü~Óüöø[ÿµÕ5ߎ6?²ïüÁ ø…ñ6ûö°ø¤¿>9øá&»ðß@ø‰¨þʳæãˆúvƒ«|VñÿÃèz¿Š¼Yqà}7CÔ2ñ¥ñ{Äzo†´›= ã–£ñÃâÆ®—þÒãÓažÎ_ý¥?à·?¶m‡ÁôñgÀ/ÚÃAÔu ÿÁ coÚGÇú§‡þ| ñ<:'íÃãÛ¿ötøñë\ÔÓRøqªéÖ~!‹Áž=ñ_‚>~Í_Nð7†<ñoöøðã¬ø›â^‘ûK|Lÿ‚üñGÆ/…š• x/áÝݿí:-?·¶>.×|U¬x3Y¶’-gTñ„ã’ÖÆïèÏø*?ì)¦¶¹m/Æù¯5_~Ó?cÝCÃZ¿#ñ”ÿ´'Á}'O×¾+ø'BðO‡þj~-ñ>•ðûCÕtýcÅ?|5¢êÿ t2ê+ëŸ%¶ùWÈ':SŒsé¦ÝZ2œå8Î)[',L$¥í©ÎrzÂrè{:^Í^Í~ít~©êº>Žßž·?™OÛëö‡ý°fý¯¸ø`~(Xêß mäñKñÓDøÁ«éþÔ¼-ö­Ÿü“öÌÐÿચOÀ}wãöãÏ„Þ4ý·­þxCá?†Ÿ |H4ßÙûÅ~kÿ I㟃þüý¬,tÍ?G–ßÇ> ý½>üløéû(ÇséúWµø~Ãe«~íøöçøñ3ö ñŸíïð/Çþè? þ4üTмkâ7âŸÃ¿ êšwÁñµŸ‹_Äv²|*ñ?Å¿ èZoˆ|â 3WÔtoƒ~4ñ:iö7¿„üãY$ÒtÍ_çOÁk?àŸC÷þ?øé§xcÄZ'€e¯|Zo ü1ý¦|[ð³á|µïï |Gø/}«ü^ÕÿgÏiV>ñ–—âí= Ç^>Ð~ýšmWGÒ¼}áï‡Þ1Ô[œõ)BœùÞhâ«ÔŽ&.U&Ôè)V:Q©,CJj”£ýç(ÒuiÚ%î­ìïm‹u7n[¦íøØüßÿ‚ùÁRiOØ?Æ–¾ý¼âø“Âÿ³÷ÿh[M7Yð/†øCãû?iß üñ¿„5ÏøûÁ¾?ñ§Äÿ\hþ,Ч‡á§ÂMGà·ðÏÃ#Xø¡â‰¾;ƒWм¢ù^‰ÿý½´ïЧÇ:‡í){¯x ÿÁÏ^$ÿ‚KÇðSPøGð.ÛÁãö[×çi4ȦñFðßLø›'Œ¼Amá_GãX/§-?dñ›OfšoïÆÿ,ý‰5Ò~Ïv¿þ#Çñžÿöqk‰>|W·ø`ß´.—¥¶³ðŽ×‹àdÿmì»ü0ƒâ4ž67%lCmA–Ôýùô÷äýOÏúûV5iNµjµa“ƒ”uNMÆ” Ûq|µlÜ®¯²ºi¡Ç¢pKK¶ÖëM®¿õ?ˆÿÙGöªøÕû\ÁQ?à‡ÿiŒð~Ñ–¿à³zÅÏÙŒx'Àþ ±®§áÿ„>&ð—…¾y~ðÆ‹ãÓ¯øÂÞñ+7Åož,!—R‹WM7X·ˆÿqQ/Löäý{Ÿj­ô÷äôéÛðéùš¼£ëÏùÿ>µÏ r¡©Õue9Fr›MI¸Ò¥K[ÎWrö|ÏT“’QI$[wéoøvÿRT9ôçñíþ}ªôKŒ{r~½¿/éïU¢^Ÿ™þƒÿ­õ­—¦~§úŸ­sÔ—wæÿEÿÐE˜—öäýOøzûVŒKŒ{ žOùýV‰zg·'ëÛüûV„KŒ{r~½¿/éï^uIwé«õ{~~š•wé©ù}û]Œ~ÞßðNñÿRçí ùÿÂæý†«õ¢¿&?kßù?ø'ý‹Ÿ´/þ®oØj¿YëÈ­üI|¿$lòüçþL#ö!ÿ³Býš¿õLø.¾½¯¿àŸ?òa±ýšìÕÿªgÁu˜^ÑEòí+ÿ%›þ óÿg{ãOý`Ûz¹ø(·ü›$_ör¿°Çþ·ìí]Gí+ÿ%›þ óÿg{ãOý`Ûz¹ø(¯?³,#×ö–ý†?õ¸¿gZëÀ¿à¿ì/ ÿ§ Lþ †_“<ª%Î=ù?Nߟõö­—§¿'éÛóþµZ%ÏãÏà?Çü+F%Î3ߟÀõ¯éê²µíÓEë×úÛCÇ‚²õ×ü¿¯2t^¿¿hŸØcãwįۻöžÕ|7¤\Cû6þßðLþÈß~1›ßøÛ]øñÑÆ7„¼PžñÏŽ|)㻄‡ÃÝmmåøwðFî_ø«âÜñx«Ç:ƒw{«üH²ýµQ“Äý*ìKœ{ò~¿?ëí^.? KB«•©ÕHrK•¶£(N-Ùµ”êT§7Y8M¨Ê.Ò.2qwVÚÚú¦Ÿªi5è9^"ÿ‚x“Hñì¿ñ?j_|T?±ÿ‚|øÃÁ_ døC¢_ëÞ!ºÿ‚||1¸ð–«ðÏà~µãÚÁ~ø5¤þÕÚ¥ž‘âoéž%½¿Ñtˆ–š^³âjš=©ŸOá¿g_ø!t¿þ~ß>ý¥ôÿüðwíº—ÁßÙ#á„þ#ð.üøûBønÇÁŸ~ |)øãàí/\¶ñF›á?Œ<#ñ/ÃZ‰¬¬5=&ËÄ6:4kK´×-4½kYÓmµk{XïàÓõmNÊ+„¶¿ºŽ_QQõçüÿŸZµôüÏôýo­JÂa¨F¤)RJ5,§JUT”okûYNëW÷®ÊÕ'«{m¢]¯²]—ÜÏÎÛkþ ¹ðÓö¦ÿ‚züNÿ‚z|›Àß²WïÂ|=7Ão„:¾ ððÏÆï ünÕ¿±~x_Zøk¡7ü%Z燵4Ô~ìh¸Õ¼GâKŸíÁqkòÿíWÿXÿ†žðVAÿ )ÿ?ü=~Â?óG?á%ÿ…bƒáÎ?äªhð³?áf`ú'ÿð†}¯þf¿#÷ß¹/OnO^½¿Ÿ‘­—§·'ëÛòþ•çbp˜Z͹ÒMºjŠ´§ì£KEE(J1ISÆbbšIÚ¢w¼)¸h¥%³ë~›Ý>¾q_w›?<-ÿJÖ¾|nOÚ[áÇí]‡þ7è?ðQ/Ûƒöåð&·­|‹Äþð÷„ÿo/‡~øiñKàŽ³àãñ{I—ÄúÆ¡x+Mºð§Å”ñ‡¾§$“^ü2º¶/i'‰ü:ÿƒwuŸ„üðÃá¯í©“ã8`Ú³þ ÙñkÇúïìÛˆ´ˆ¿iŽ¿~?[jð0øé¥ËðçÆžñ_Ä‹í2ãW›Åþ6¶ñf‡gäÅcákˈo,?¥ø—öäý{~_ÓÞ­ ÉϧùÿëþæTÀ`îÚ¤ÓwµªU_²çk÷š9*4“’´œb¢ß-ÐsË¿àŸ~ëÍýçóGmÿßø2óž>Ñ|WûIé¾4Õ5uÿ‚WkŸWÄß³®‘®|?Ð<{ÿÅý™5OÙžÊëâÃ=wâž±¤ükøgñç@ñˆõ|,¾Ô¼‡¦êÇÃRxŸÅK ÖfõŸŽßð@ üxý“þ~ÍÏñãÀßõß þÔ~5ý |×|má_ø›á®¥ x8üBø‰ã?o4o Åaã]wÇ66vz?ô#ãíÏâÃúVŒKÓÛùŸóü«†x<"RQ¥e4â×=MS¨ê¶ï-ùÛiïò¦¢’4„¤ÕÛê­¢è’Óî^Zw?? Á½^øk¤ÿÁG´‚ÿµÏÄ/ƒ:Gí­áŸ xàJ|?ðDÚ>£ûxNøÁ⟎-økàZ‰)wã/‡>9ñ¯Šo¬u/ i'áb[xFÿ_ÑžöÿQ×½§ô²üFÛöSý¥ÿgïÚËö–еë??µßü+ö§´øeáÙäü>ðÕý·íáðá7Àë…Þ¹o~/ohcø\šæ‘«5‡ˆ›ÅVµ¿†Îác£ÿnjÐÌkÃõ?ýoÏ¥YA“ŸNŸSþ•qO ‡N.4íÊ×'½;FÕ=®‹šÖçÖÖÚÑøb’¾g®»ï÷[ò? < ÿ?øyð÷öÒñíIáŸüºð×lOöØ×ôo~ž+þÒuÏ/ZÖ| àOÚãâyñoˆ>ü¸ø‡mcñKÐü ðÓBø—àïCs'>-ø^mWW¼½öïø%ìÙñóáV»ÿý ¿i‡÷ ~$~Øÿ¶ÿÄo‹^ø^|UàmF?þÏZ6¡xcà|^/ð§Áßx·àž‘ñ¦ûI‡\›â·Œ|®x—Ä¿¤O Üx÷Ç)½Ð4…Ò?[b\cØc¿Séúþu¡ôöà}O_óïí\S§´âœ}éI+¶¯%fõ½´m%¢¹ž›ÿ$¾KúêY‰}>ƒúŸþ¿Ö´#\ þúŸóïU¢^žÜ©ëþ}ýªèÀOƸêÊÿ?ÉŸùšAY_¿åý~‡çÿüøÆxÿÕpýÀÿÄËøŸóõ¯´þ ÉøIÿdËÀ_úŠéUñüqûø—Ûã‡ìlþ&gÀþ?âkìòF¾Ù2ðþ¢ºUyµ]å/ó,ôÚ(¢³¢Š(¢Š(¢Š(¢Š(ä/؇þHÏ?ìïà ßúÞÿ´­}{_*~ƺ“êß¼_u%¦›dÑ~Õ·vš!Ò¬-ôëWMöàý¡ôxîå·¶TŽMKPŽÁ/õ›öãTÕîoµ;¶{«É¾« Š( Š( Š( Š( ¿2¿k¡ŸÛöVÿ³jý´qõÿ…£û ë_¦µù›û[ŒþØŸ²¿ý›Oí¥ÿ«GöÖ¾‡„ÿä£Ê?ì.?úDÌqÁ©þÕ ‰z{p>§¯ù÷ö­—ôà}§ÿ^«D¸Ç·‰ôÿ=ÅhƸüêzÿŸzþ†«+ßÏEè¿Ï·›<È«$¾ÿRP:ô«‘/L}õ=?_­WŒdçðSþZþ%¾Ågâm#áÇMv(#øÁñwþóøSgñD×-à¼ø©ðKø{ªøûBøû6üHñm±¿Òué¾iº†«áý?Á~)ñwÃÚ|C½‡Â:Ô³_k`üæk™}GÙ%Gںʣw©ìÔaIÒ‹³äŸ4¥*°´Ÿ/<õP‘¤!Í}m²Ú÷»õG÷ú}õ?ý­hD¾žÀ_óìkø˜ð_ü§þ =ñ‡â_ÇføñkÄÚÆ‰ðƒöBÖ?n3ð«âÂo€šOµ”ýœl›ƒ´Á+ÿh_ eñÁ Ç¿ mïã¿ø)ßüÆš/Ã?Ú/À<;ð[ö-ý±ÿkÚÂ>.üO´øgðÂ_ÿg¯€>ƒÃß.üUñoųí+oàÍWö®ø§cãífêûâÂ?Šo žмàÍSÀšwŠî¼G£xrâ<XÊp¥‹qÑéJ›½?h©{TÕf½œjJ”g+ÞÚ”¤”¥ö2ïí»Þ×¶ÝRmw³ê`Œ þéþ•~þÚðNÚ—öªÿ‚•üOñ×|U¤üýœþ1ÿÁ¼Uûøëãv©áo üS{wÇÿ´_ˆu¿|2Ò~·ÅxßK×ï¾ëïâ=âWÙn¼#¡jÖ6¶—K®Ü½Ï‡çòدö¹ýº?hÛ×öRøñKöœÒl¼ ¨Á+4¯Û#â.ŸðO൅|ñ·âûixŸá™â´ßìíàïž ðÏÄÏ„©á‰|O¥iÞøymÙ¼Õþÿf階â«ßÔÚóá§ü+Æ?ô=OöLø­ÿ/è< ¦iúΓÿ ðGáGÚ|k¿âkKRÿ„wâOü?ö÷×/|íëÖÇZ´øÁá½&ãìfƆº=æâóâ1|Ç æèâ¥Jàù)¨ª“j Z{:’’‡-D¤Ô¡-ù]µzB.xÝ­ÝöÓºZߥ½O€µø ‹¦øçÄ><ø[ûP]ø7PÑ>1Á+þ3|ÓüUðm|y§|8×à–¿|Cð ÁWÄagñ_Áwto‰úˆn5ytk¯„×Þ¿¶‚[Íj&’FòÿÿÁ%iÏÙ‡â¿ÂïÚGàO‰uÿÚWãv™û|~Þ?¶–±ãOøàï„î|í¹ðŸá§Ã?ü0‡à_ÆßÚWáoü{§j–þÔom>)§íáWáΣo¤O‚ßl5]F=#îø(7ÆÚ_Iÿ‚ƒÿÁ(dÏÿ´/Š?g¿~×öéã&¹à¿|ñ·Šu(¾|ðWÄ>‹qñŸá¯Ä­3B»ÓµIµ›:ËL[[¸µ‰[Y±ÖÆÂÞÀÛOø*í÷âïØ£ö3ø¥ñG㾃ñVÇöþýƒÿà·òüRøcã?€?³ÍïÃm_ý„þ|W½øeâ½@ 7ø’÷ÆZ¯„’ç⟄>!]x»á/ŠtÝNûÃÐ|6Òô‡’Þo òÚ5*ÒXlE9Òª¥í)T\±¨ë`ëóAN¼¬ý¶>…åìoÉSJ4¢ã¢çi>ef¶k[ZK[-tƒëë»?gÿaÿø#ç‰þÐ×_²·íWð\ŸÁ_¾5ëÿ<ÿ C¯~Ð÷ë©ø›öuð§Å¿ ~ÎÿüQà]/ãÔÿkÖ¼IáGT½Ö¼?¤&‡ã›X¼-à½sEá/?àÞƒ{û3~Ûÿ³˜ý¯<£ûeü ÿ‚X|ÿ„È|ßÿ ßþŸðÿáßá&ÿ„{þRÂ_ÿ «þíA£nx[þÁÕ¾Ãý­ãϰý²óðCöúý²¾<|Uÿ‚h|tøñ—âÖ…ðká׆?àœ_ðEþγ߅¾|!ð/†j„ø·ið7Ç?>%xz=ÀZv¥á}àŸÄ2׺G…> _xáw„,4Ëo Íá%K«(SíOÛö¨øÑñßöÉøðããçÆ/øGüEð#þký‘~|"ý‚¼¡öjð¤7r|'ý§[Ä á˜~)kgâÿü$ºÛ ½{ÆZŸ‚õ/´0𮋦`žtëà%RXYÉQ¡NŒª¨JJpÄágœ’Š¿²§+M§R¢šœ)Æ*5 ´÷æWnïKÿ,•®»µÙ+Zîö?moÿàŒÞíïcûkøgöŽá¸OÚÃãÞ¿¢ü)ø'¦ü#øµã­6ÞÙæÕ>|Kø¯ð£â¾|Uø#âoHúωgø«û9øçã®»m5Þª|r¼³Ô/Þç÷J%Î3ߟÀõ¯óÌý‰?n¿ÛWötÿ‚vÿÁ:¾|øÃð¯öMø%©þÈÿ¶ÆMãÿÆàÓ<%ñöˆÐ?oš¯ÁÞ_Ù;ö¾×þ#jÚƒ¡Óý¬|ñCâÃ9ÿk/Œ?ðS/þÊ—vß ¾Ùxcá÷ÂÙKÁGXø?ðÓâÄOˆ? >"xvåî×á×Ä'Àvž×>*||ø…«ÜøÿþøÁS[Þ}mûÿÁA?à ¿µ'Œà¡n>%êž<Ùwþ ëÿòý¤~þȾøgð“OðGÄߎŸµ‡üNóãçˆ<ªø–‡×.t}gã}´Ÿ†4­'âe–¡iw©Z苨Íá¨@ºÊXês’ŠKÎÏáVåtÝX¿Šöäô\Îñ²wWýy4¿7é£?«X—§¿'éÛóþµ£ô÷äôéÛðéùšþ2?gÚ_ö‹ÿ‚„~Åÿ··ÃŸÛ?ö¡ý—>+|ñ'üÇÅ~![Oá|AñgìiûGA¢kzÝŽ£ñçáÏì'ð#áÃøO­h—ž5ö}Ö¾ üý«~øÓá.ã? xÓâ‡}kâÛOèÛþ +ã¿|Nÿ‚d~Á6ñö“â½/źÏìð:=bçÆÚ®Ÿ­ø‹Åí£øEÐìþ'\jš~­¬µÞŸñsOÓm>(h/«ÝÁâ¸ôi‘xÏHÐ<[·¡iÜoªµedãÍvÕô|¶i6—FµmìÒkQů¾ß…×õÓæ~ˆÄ½?3ýÿ[ëW”`{žOùÿ<æ ‰s~OÓ·çý}ªÚŒ‘ùšä«.ïÍþ‹ü—¡¬U—®¿×õÜü¸ý¯†?ooø'w©ðçí ó3~Ã5úË_“ß¶Çííÿíõ>ý¡ÏþfoØcúÃ^tÝäÛþ´E|…ÿùÿ“ýˆìпf¯ýS> ¯¯kä/ø'Ïü˜GìCÿf…û5ê™ð]@^ÑEòí+ÿ%›þ óÿg{ãOý`Ûz¹Ÿø( ÏìÍõý¥ÿaqÿ›Åû:×MûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯£¼{ð÷À?<'ªø âü!ñÀÚ÷Ø·<ãß h¾0ðž³ý—©Yë:gö¯‡|Ce¨èúöv±§iú­Û,æû&¥cg}oåÝZÁ*m‡«ì+ЯËÍìkS«Ë~^og8Ï—šÎ×µ¯gkÞÏa5t×t×Þ| çü~Óüö}}z}ùþUËkðG_Ø#ZÖu}gþ‚tŸímNÿSþÊÑ>ü°Ñ´Ï·ÝËuýŸ¤Xÿ±—ìZe—›ök O6O³ÚÇ>cìÜs?áÌ_°Gýáðgÿm~™/œ¿æKm4ÿ…ëßýÀäú§ý<ÿÉ?ûcÑc\þ? ÿëþ]+B%ÏãÏà?Çü+Ê¿áÌ_°Gýáðgÿmðæ/Ø#þ‰‡?ð€ø3ÿζ¹åâ—üÊ-¦Ÿí÷׿û}SþžäŸý±ìÑ.qïÉúñõ÷­—8÷äý;~×Ú¼'þÅûÑ!ðçþùÖÑÿbý‚?èøsÿƒ?üëkš\xå¶Woûž¿ÏýÍÕ?éçþIÿÛ@¨ÉÇâ~•v%éžüŸ§oóï_8ÿØ¿`ú$>ÿÂàÏÿ:Ú?áÌ_°GýáðgÿmsË¿æ[nßí›åª-aì’çÛû¿ý±ôüKÓ?Sý?ÏÖ´"^Ÿ™þƒÿ­õ¯”?áÌ_°Gýáðgÿmðæ/Ø#þ‰‡?ð€ø3ÿζ¹åÅ®_óm,¿Ú¯oü·ZÿÀ°þÿþKÿÛ^¨ÀúóVâ^žÜž½{~=?#_ÿØ¿`ú$>ÿÂàÏÿ:Ú?áÌ_°Gýáðgÿmsˉ\¿æ ÚY´ÞßùAkÿ=‡÷ÿò_þØûb%éíÉúžŸ—ô÷­—öäý{~_ÓÞ¾ÿ‡1~ÁôH|9ÿ„ÁŸþu´Ø¿`ú$>ÿÂàÏÿ:Úç–|åÿ0¶íûûÛ¿ü¹FŠ’WÛËþ ÷²Œ ~'ëV¢\c=¹üOøJüÿÿ‡1~ÁôH|9ÿ„ÁŸþu´Ø¿`ú$>ÿÂàÏÿ:ÚçžlåÜZêßžŸø)y‡³óü?àŸ¢1/éú“þxïÒ´"^žß©?‡ÿ_¥~oØ¿`ú$>ÿÂàÏÿ:Ú?áÌ_°GýáðgÿmsÏåÝZêßôÿÀ˜{??Ãþ úcãßÌÿŸåVdçÓõ?çõ¯ÌOøsìÿD‡ÃŸø@|ÿç[Gü9‹öÿ¢CáÏü > ÿó­®iâ9¯îÛ§ÅÿÚ­Í6= þ „¸ý¼KíñÃö6ü6gÀ ÿŸjû#à§ü‘¯„ŸöL¼ÿ¨®•_éŸðG_ØkDÔ´ígFøc¥iÆ‘}i©é:¶™à¿„–™©X\Gwc¨é×ÖŸ bº²¾²ºŠ+›K»ib¸¶¸Š9¡‘$EaúYáÙxKÃ>ð®-Ôú†t-#Ãö^¼R^Íe£iöúm¬·rA ´]I´opðÛÛÄò—há‰pnîànÑE€(¢Š(¢Š(¢Š(¢Šù ö!ÿ’3ãOû;ßø(7þ·¿í+_^×ÊŸ±®žšgÂ/ÛG©iºªÉûUþÝÚºÒ¥¸šÖ'Õ¿nÚU“M•îm­$–%ëéÌKÛÁ«ØßCis{k7“ýW@Q@Q@Q@Q@~gþÖƒ?¶7ì¯íû4þÚGÿ2ì'_¦á¿?fŸ€´ŽñÃáÃO‰ÿÙzgˆôÏ j¾;øàÏë> ÿ„®ÖÎ×ZÔ<}âí]ÿ„oS»þÍÒne»ÓâϻѴ™®ã¹þ϶Xý «ý™˜á1þËÛ}Vª«ì¹ýŸ´²k—Ÿ’§-ï¿$½œyá(Þ×V½¯moµ×æ|—ãðçñ?áþuFäšàáÌ_°Gýáðgÿmðæ/Ø#þ‰‡?ð€ø3ÿζ¿AŸ‰.Wÿ„k][þF7Óÿ™ÍõOúyÿ’öǤĸǷêOóþ}+Æ~~Ìß~x»ãWþx>k´7_âŸÅÛkøÒ÷ÂÞ'ø—u¶ú¿,¼ «ø‹Pð?„¼Yâx¤‹þM{Á~ðî¥ã‰4ý\ksè:,–¿ðæ/Ø#þ‰‡?ð€ø3ÿζøsìÿD‡ÃŸø@|ÿç[\•8þeË%‹•6Ý9§üþµ~%þ€}{ÿŸs_6ÿØ¿`ú$>ÿÂàÏÿ:Ú?áÌ_°Gýáðgÿmc>3æ¿ü&ÚÿõýÕ[þ¥ý^ßkÿ%ÿí¨â_O ÿóïZ/L}õ=?_­|™ÿbý‚?èøsÿƒ?üëhÿ‡1~ÁôH|9ÿ„ÁŸþuµÏ>,r¿û¯ÿQWÓÿ Ð{ïÿä¿ý±õøúUØ—úôïþ}|gÿbý‚?èøsÿƒ?üëhÿ‡1~ÁôH|9ÿ„ÁŸþuµÏ>$æ¿û¯ÿQÛþà°þ÷þKÿûr%éïÀú¿çÛÞ´"^žü ëþ}½ëá?øsìÿD‡ÃŸø@|ÿç[Gü9‹öÿ¢CáÏü > ÿó­®yç¼×ÿeµÿéýöÿ¸FžÏÏðÿ‚}ðHéøUØ—8÷ãðŸç°¯Ï¯øsìÿD‡ÃŸø@|ÿç[Gü9‹öÿ¢CáÏü > ÿó­®yæÜ×ÿgµÿéíöÿ¸aìüÿø'×_¾ü5ý¥¾øóà?Æ-'ZñÂÏŠøcǾÐ|o㿇wž#ðÄ÷6óê~»ñOÃoxGÅðh †Øèþ*Ñlµë]3ž½Õ¼%âk]WÃ泤_z€< á/†~ðoÃxMðŸ>øWÃÞðW…´h>ˤxgÂÒ,ô øJ¶…¾›£hÖZuŒ˜Ekm y;s_Ø¿`ú$>ÿÂàÏÿ:Ú?áÌ_°GýáðgÿmsOÌÛöI7¥ùÕìµJü‰Ù0äóºíoø'é¢ úÿ*µçüŸ ÿ_züÁÿ‡1~ÁôH|9ÿ„ÁŸþu´Ø¿`ú$>ÿÂàÏÿ:ÚÂuܯîÚï½ôí²ò4!ý±F?ooø'aî|9ûDÃþ7ì1Šý]¯Îÿ…ðKÏÙ/àŽ4OˆŸ <žñF‡¦ÞÃáß|5ðô×Ðéš¾®G¥jW^ø¤ê7:=Σ¤éó^X­ì+3ZÁ"¼sÁÑþˆV ÝÝõ¯¿àŸ?òa±ýšìÕÿªgÁuõí|…ÿùÿ“ýˆìпf¯ýS> ¤×´QE|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¯kä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@!~Ä?òF|iÿg{ÿÿÖ÷ý¥këÚù ö!ÿ’3ãOû;ßø(7þ·¿í+_^ÐEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP_!Á>äÂ?bû4/Ù«ÿTÏ‚ëëÚù þ óÿ&ûÿÙ¡~Í_ú¦|@^ÑEòí+ÿ%›þ óÿg{ãOý`Ûz»oÚŸã~±û;üÕ¾(x{ÁšoÄ~|ð‡á=gÅ×^ÑõcãÆo|Ó.u_ØøGÇ—º6›£^øò rúk?k×Wºl¶vöb[”ž.'ö•ÿ’Íÿùÿ³½ñ§þ°Gí½\—üsŸÙ{¿´‡ì9ÿ­¿û;×N”+ã0´j_’¶&…)ÙÙòT«JÏ£³v}¤í5ºM¯’<¡lÚù±ÙWönç×öÐøÿÐ-RÛö¿'öUý›yÿ«ÐøŸÿÐ+XŒ þð«1®?çüúWïsðç†#{RÆieþù=ÿð½zíÓÊúånñÿÀQ¶¿µ÷í‚ØÇì­û5Œç¯í£ñC·Óö52þÖß¶#c²¿ìÓϯí£ñGÿ N³¢^žü ëþ}½ëB%éïÀú¿çÛÞ¹'á÷Fö¥‹ÒËýî[ÿà?zõÛ£Ž.»i^>~ïBuý¬?lvÆ?eÙŸŸ_ÛKâ—ÿ@J?j¯Û$ŒÿÃ,þ̼úþÚÿú*H—?€ëþ}ªâŒ?Î+’| Ãñ½©âº/÷©oÿ€ýÿ¡§Öj÷ÝýMù[Î~'þÜÿµÂ†_~*øöPýž/|=ðÏÀž.ø…¯Yè¿¶oÄ›jïEð_‡õê–ÚE½ÿì9¦ØÜjw:lñiðÞj6’Ý<)q{k <ñþ¥hÚÞâ-6ÛYðþ¯¦kºEçöMWF¿µÔôÛ¯³Ï-­ÇÙ¯¬¥žÖ"ê ­¦ò¥o*xe…öɨükýµWþ0Ëö¸?õl_ÿ>x¯üút¯ÐߨëþMÇá×ýÍßúøž¿>âÜŸ“âp´°Q©U£9ÏÚTu”f¢¬ÚV[èuP©*‘“•®••ºMQEòFáEPEPEPEPEPEPEPEPEPEPEPÊŸ±®¤ú·Â/ÝIi¦Ù4_µ_íݦˆt« }:ÕÓEý¸?h};¹mí•#“RÔ#°Kýfý¸Õ5{›íNížêògoªëä/؇þHÏ?ìïà ßúÞÿ´­}{@Q@Q@Q@Q@|OûQ~Ô_þ |GøCðÃá‡ÂüP×þ(x'ã.ï+UT$á'7Ë%¬^‹TgZNç(ïu}G/íû_7OÙWönüm‰ßÓö4ñûaþØ ÓöVý›xÿ«ÑøŸÿÐ+XJ0æjÜKÓÛ“õ=?/éï_´ÏÞŽÔ±—û\únþÍúånñÿÀQ²¿µçí„Ý?eoÙ¯·_ÛG⇧ì(jeý­lVéû+þÍ?øš_¿úª„KŒ{r~½¿/éïZ.1íÉúŸðþ•É?ømmKå|TúZÿg¯êTqu›ÕÆËû¿*þÕß¶Ctý–?füM/Šý5'ü5Oí“ÿF³û2ÿâiüSÿè©â\c=¹üOøJ´ƒ'éüÏOóëŠäŸðúÚž+}?Úe²Ýü=Rþ³W¼~ïëúoÊÙºGí¯ñ›Ãÿ> x?ã¯Á < ñ‡Ç~ øz> x_ö§ñWn<+­i?¾)|[Óîu/øÃö^øC¤\iz´ î¼4fÿ„ÖÎî×R×4Ù ²Ôe·?¤õøûu.öNÿ³œÔ3õ?²§íCþ*ýý¯Ì¸—.Âåy¤ð˜E5F4hÍ*“s—4âÜŸ3IÙµ{[C®Œå8sJ×»Z+m`¢Š+çÍ‚Š( Š( Š( Š( Š( Š( ¾Bÿ‚|ÿÉ„~Ä?öh_³Wþ©Ÿ××µòüçþL#ö!ÿ³Býš¿õLø.€>½¢Š(ä/ÚWþK7üçþÎ÷ÆŸúÁ¶õp?ðSHµ‰ÿdV_iº^¿7Çÿئ-SÖt«­{GÓµ‰?moÙñ4ËíWC±Ö|9{¬é¶—­Åö•gâëQµŽ[;}gK–d¾ƒ¾ý¥ä³Á>ìï|iÿ¬ûoW)ÿÿ“`_û9/Øoÿ[ƒöv®Ì½_‚Núã0ËFâõ­ ¥¤ŸfšktÓ& ¿Âÿ&~~ ~×üñÏömôÿ“SøŸÿÑ•Vãð§íƒÆ>:~Í~ƒ?²Åõ<~Ùƒñ?Zú.1“ŸÀ}OùýjüKÓÛõ=Ï¿µQÔÁÑW÷ñšk®awooù‰ûûêx|Ï´ðò'ÎqøOöÄãf¡ØgöOø¢~§Û<~'ëW£ðŸíÆ>;þÍ€Ïì›ñHñÜÿÉéò;WÑÑ/éÀúÿOþ½hľŸAþ?çÞ¸*a(«ûø½;ãñÏWÿs;m™¬µív÷!·þ|Üžý²Èøñû3Ã?²WÅ3Çþ&¨ÿ"¬Gá/Û(ôøóû2 ñÏì“ñPñßþo\‘_JЧùþµàŸ´·ˆhß ü9ƒRý˜¼áOˆ>=éöš¾â{«Y.4IcªÉ«x‡Â~Õìó‰ß?k?‹Ÿ ¾"ü)ñí û;Xøwâw<]ðó^½Ñ?d߉VúÕž‰ã?êÕn´‹‹ÿÛ7R±ƒT·±Ô§–ÂkÝ;PµŠí!’âÊêðIí ­ÿmï…þ Ñ<áÿŽÿ²­Þ‘¡Kìw:ÏìƒñrçRjzµþ³pnf²ý¹4ëWÙu¨O>Uœ;`H•üÉÊÿ ø›â‡í¯kà†“xzËâÄß 5_ ëw?¾8Û~Ï>Öÿk/ øÊ?ø¾ËYðþ“û-ê:߃´/ xKÂúU¦‰gàø?Áÿµîµã]ÛÜKðóÄšsCñwÅ? ü\ý¶¿jß |gý¹>ø+ö½ñŸÅOü6ý¹|Aû<|ømð·à÷¤ðRü!ø ãü ´ñ?ÁÍ[áLJ?j†~7Ó|§xfãAøÇñSý¢?f/Úf覃àÃKñ_ᮟeòy¦%ÄJ•Lv^QQ§JU18‹¯kihßhÝróCš©îÂRlÚªº„ãÛÑt²ÖÑvùëmYý'ŒovÇü_Ùgþ¬çã1éÿwåRþÞäãþ—ì…ÿˆoñ›ÿ£Î¿ŸïÚ7öðø}ñÃöÔøÿ ?jÛöhøYñ3öañ.™ñ«À^ðÿŽü3ðïÅÿ¿coø.VñƒÇ¾ñe×…uÏëpYø#À¿ ôÝwž)>8ø9ðG^ð~½¬øÿ@µÁÿô+Œo‰ß·ßí7àëÙÓÆ(ý¬£ð„"þß>5ñMÂÛÛá·þ ë¾—ö/ñ?Áßx÷âÏìAûcÝéþ³ý¡¾üð—ƒ|ceãÿh>ø¡|Ô~| øíð¶ÿãN­§ø¤ø£â‡Ž¿f/ˆ¿ q­—dTçG/«Í*Ž’¾"´R”gÔmËÿ.Üìš“§;;'%Ju_Ú[_dý>Ï]<õZÒJk·»cþ/×ì„;ÿÉ™üfÿè÷jq¨þÞäÿ óöBÿ«1øÍÿÑñ_·ÁßÛÇöýñÿ‡®µ~П4xÇDZ_‡|{ðÓÁšÞ“ñ‹âìãïßðQ/ÙWöñŸÂxOØsà—…~ ¢|3ø—ñwÁºÏÃ¯ŽŸ¿hŒúƯ£é¾3øEãk¿x^ñäQü9ý¤¿k†ñ§…uÿÚ⿎íü[àïø(ÿ‡tðgìëðƒÇŸ4¿Á:?িcÿø·Kð¯ƒ<ámÄ×?´ƒþ&êS~ÐVz‡| á H¿}á¿^k|ú…Ö‡¤ÏpöòòܶNJ8IŤ¾9â­&Öµ]¤¯ªvèìÓMÔg=Ü“^I}÷·õê-Eý½ÛþnöBgþL·ã9úÍþóšœ[þÞäÉÂþÈCþì¯ã7ÿGí} çüŸ§oÏúûUµ#ó5ÏS.À«òÑ]—ï+?_ùyÿb¹åßð_ä|ê¶?·ÃÍÃ~ÈCŒŸøÂ¯Œçù¿ÃŸþ½X]+ö÷où¸ŸÙqŸù2Œß—ü¤WÒ/LýOôÿ?ZЉz~gúþ·Ö¸ª`ðªü´­Ñ{õ«Öoú°sË¿à¿ÈøOâÿÅ?Ûöxð¿‡~)|DøÍû-x»áý§Æ/ÙëÁ>:Ðü=û'üað^¾Þø»ñóá¯Â/jš‰çý³>#Á¥ëÑ|s}â1¦ð/‰áº½Ò ±“J™.‹'èΉ¬é¾"Ñ´h×?lÒ5Ý2ÃYÒ®ü™íþÕ¦êv±^ØÜýžê(. óígŠ_&æg‹vÉ¢ŽEdœðSuÇì}â#ÿUÃö9'ÿ'àù÷¯¸¾ ÉøIÿdËÀ_úŠéUåbaT´£Ê®Þ·j÷möîiÚ»îzmù%ÿ(Ô~ é?fWöÐÒ¼?­þÁzg‚ÿi]GâŽñ-&oú¯íemðrçömÓ>8EâÛ»†àýCÀ±þÓ6~OŒ—Ö_ äø»?Ãh®îíüfþ•2?àŸ>?ðŒÿࣿ³ÿ‹þ þÐß| ûxŸÁßðQ¿þÇ¿³V½uðÃGðĺVà¿ø%§Äÿü>ñ‚5‹Ú£ÿ Æ´wÅÏ|8м}¥Iᯂ³×Âýwབྷ‡?g{¿ŠV¹Ê?ªJ+øñý¹|«øGöäý©´û+I°·wÀOÏâ/øTVŸ‡@Ѧø½«hÉ‹ukï|"øuâ-.ÓâÖ«§xEu]ñ?ƒþßxsôö_ý¥¿hŸŽŸµÿÃ/ƒºíiâOˆ_³>íÑãoüwÑ~|Rý°>~Ͼ&ÿ‚T]xNêÿ^°øOoิ|Aý§¿i¯Ù¯Ä^2ø)áŸXxïÃþ ¿×´‡Ò|nžñ¿‡?zè¯ãëÁ?ðToÛÇ_<à¿þÖÖÞ%±øáaû:ë…ídž¿g}gÅ¿³ÿм[ÿIÿ‚fþÌ^9øk¨üð§ÀK~x“þí}ñÃ>:ø ñÃö†ý«¾8øW‹Ã÷úŸ>jº~⟈>×ûN~Öµoìýñ«öˆø]ðÿã¿Â?ƒž µý­µïxóö·øÝâO€ÿ³LjüeðÛþ …ÿ¤ñoïüCø£§~À?µÃoüVøã¨üOøÅâtÓ5ñŸŠ¼7ð³OøOðwâ/ÃÏ ø#áïÃFþ¦¨¯æÂÓöéý¨t_Ûöoøuñwö¯ðΈŸ´دFµýšþü/ð¥¬wž.ø›ðÓÃþ$øíy¯| ý£þü,ý·¼qð×\×/¼AqáŸÚgà§Ä»/~ÌÖBËCý¤¿gk}sáƈ®ÿ¤ú(¢ŠùSö5ÓÓLøEâûhõ-7UY?j¿Û»P7ZT·ZÄú·íÁûCê²i²½Íµ¤ƒRѤ½}#Y‰b{x5{èm.omc†ò~/þ mâÏx þ ·ÿñ×|MâøÛÁ±í_âÏxÇÂzΣáÏxOÅ^ø ãícÃÞ&ðψt{›=_Añ…«ÙÙêš6³¥ÞZê:^£km}csÌJ½ìCÿ$gÆŸöw¿ðPoýoÚV»ïÚÇÇÿ >~Ë´·Å/Žž ÿ…•ðKá¯ìÿñ“Çÿ¾Â9áÏÿÂ}ð³Áß¼Gâ/ˆ> ÿ„Gƺw„¼Uÿ W„´í_Bÿ„sÅ:…‡‡5Ï·ÿfkw¶ºeÕÔèü÷èðPÚ á¿Åø(OÄ}Ç BýþüEø°~|[ø‰ñŸÆºŸÄ¿ ~ÏðWOø-Àÿh³ß‡<]ñwS¾ðÅŸ‹þ økðÂ78²ŸÄß<ã3àOÁ/üÒþß|3ð¿Âo hßø*oí9ðgáÿÇŸŽZ—û5ë^Ò¼AÿEø=ðƒàå Ïñ—ÀàŸÿj/‰ºWÅž!³ø§g£øÃá?ÅMkö`•5ø_À 5ÿøSã‡ÀíBÛâˆn¼V殼5ñkþ ¡â¿)ø}âÏ„³Wߊñ¿üK¡xÃâNû(‹?ü`ñOíÿøsà{o†^5ðÇŒü_>³ñØþÐÿkŠ“xI¥Ò¾,x/Äÿ¼K7‹4o |uñgÇ/øGÐh¯ƒŸðNŸ‰>ýªo¾"jŸ³OÂÍkö’ø#ûCþÍiO ßü ð7Ævð‹ðÏÄšÇ #UøÉ­i—·¿Úü¤ë:ÿ‰,9ÁB>ü@øgáþÏ¿µ~|.¸ñ¯€b{-gH¹OþÐ7_þ!ø›Uðíþ-ÝØjSx[íÿŠß¿à”?f_Únü5û|WøG xöý¨þ4þÍÿ 4oÙ»ÇW¿¥ý¡¹ñOÇ¿Kð}u  üBø‡áïè6zO‹5_ˆ[Iñüš%‡ŒüA¢_¼WÙ¾øÝû#xÆÚÏÀ‰Ÿ±¶©ð+àŸÄxïöU‡ãOÅÏ‚ß~þË¿þþÎ?>7üFñÀ½FÆûÅÿðŸY|ðoÂí?öŽñŸ‚¤ø·ðƒÁÿ³ß‰<ßüCð×Äzÿ‡ü^o|FùëûrÿÁCÿiÍ?ölý±ý”ìþxûö~³Ðü[ðÏCð§íUðßö±ø™¦x[ÄúoÅßÚ·öwðOÅ/ø#Lý’u+m3]OÚ[ö|Òþ.迾 ø£Ãžðƹ­Ø|7ñ'é'Œ<ÿõ¶ñ¯íã/øWö3ƒâ.±ð)nj¿xÃCø#u_Ù›Åú^³¢,ÿ´¹­Z®»}ð+Å/„:ý®µ/|#¸Šox_ÆúÀÿŠ¿ ?h _…z¯Äÿˆº‡Á?|¨ü.ñ÷‡o´ÿh_|'ñ{V×~~Õ~(øMû0ø§âþ¿®|UŸÅ?¿n«ãkÿðO/…~¸Ñ> ë_±ÃZü<ð§ÃK½#ÆZ—Àÿx^ßá=…üyñ_Àßî,5¹´í&‡–~ ðGÄÿ‰^ð¬±/†íü/áxÇH°M+@ñ£iᲟ€~. /áwt‰ž3ý³¿k˜ü ãKŸ…ž(ø{ãm?Á_¼7ûjì?áfÉ ëvº—…¬oÿfïØóFM2;ø|I⫽W[ðcÅa‚µxõ?}Í_‘?ðP-?â>¥ûSþÉ|/ñ_‚<¯§ÀÛ*kÍOÇ¿õ߉<ú:üEýˆ’âÆÛCð÷ÄÏ…W¶º”·²ióêËâËX-m¯-F¸–úí;õÚ¿0lOù;ÿÙWþÍ·öÐÿÕŸû ×Ñp”ø“'ƒrJX¸¦á9Óš÷'ðΜ£8¿8É?3Cµû½R}WG¡òªø[ö¿'þKŸìÛÇ<þÊÏþþ]\Ÿ¶ýOÙ¬w9ý”~( ?ñ™£òú×Ñq.qïÉúvüÿ¯µhD½=ù?AÓóþ¾ÕýSG[TÅö_ð¡˜y]ÿ¼þ;}úøüÏ´ðò'ÎQøSöÄã¿f‘ߟÙ?â‰Ç·üž€ÿ9«ñøOöÇãþ/¿ìÎ;óû&|S<öÏüf˜ÿ#½}ôÏ~OÓ·ù÷­—§æ ÿ>õçÕÂÑéSÙ·ã¼®õÄùZÿæmÒ_ºü1ÿä¯CæÅð§í’ÿ‹óû2úóû%|SÿèÕ«1øKöËíñëödçŸÙ'â¡çðýµÇùô¢ŒŸaÉÿ?ç½|=ûUøãö±ð¿Št+…ÞÕôŸ€—>ŠëâÆ„¾Ñ~:ü~ð¯ˆTÔâ¾°ðçÁ¿ø£Ã6všM–‡¦ø»Ãž ý§üE«jºŽ©¢\ü Òôý*Þù˜ªthÂS¾6i4”iã1’–­]ë‰QŒRMÊs”cêÚ)6Ý½Õæã~Zí¶­üØßŠß³WíKñ“þïü$ÿ´_ÀøV¾;›â‡ýƒû(|D¶û^µ?Ãÿü7{][ûCöÅÔüý,hõ»¥†ÏìCUµÒç7¦Ò»ï±—Æÿ·»Íný‡ÿÆ|f<úÉø×çï~)~ÚšOÄO‹pø¶ÊëàÏ|7ᎺÀ?|;ø!yûEü2ñ›ø{áßõ/xŸãߊ¼/âMkã—u§Ga¥xÎãá…>ü%“Wñµ†Ÿð»Á_>3ÞëöÚ&©ðOìÍÿñöŸoð—Ä¿l-3Æÿ <#ûNü:øyûT|aדögñ7ÁO h~?ý?kÙY§íGð7ÀŸ >iþÖ?hŸ þÏt>ñoÄ?þxÂçAðoÄßø£ÃŸ<1k{òY† !Äb£øÿ«h¿uo‚0þ; ›à®–ÒþÔZ—Áø*GükáÏìÏû,j¶ú÷€ã“Ã7^7øs ø_áìžÓ,<ûBkÍ×ĘüUøcñlë?nøöœý <_û}ücý—¯lbºÇ‹Ó¼%oñSᵕ‡²îš‡&]S÷³„)òâêÉZ¤ªF›mãUœ•;´ÖŽqQsRŒ¥ªmo>—Ö)tMýw·Ë¾‡ìÂø“öøoù®¿²ÿ»6øÎyôñž‚¬.»û{·ü×Ùtÿ›4øÎyÿÄöøyãÛ[ö±ñ×ì»â_Ú"Ïã‡û'éoÇo‚?³Þ»Šô¯‡¾°ÒÓ>?íçûRüøÏûBC¢ü2øIñjÿSÐÿe߆—V*_íÛÜÿÍ|ý¿ñ ~3ô|×óŨþÛŸ¶õŸÁïø¿Oý­¾x¶Çáì‰ÿJý¨´Oü?¾;øcã”_±„°'‰>è"øÓâ?Ø×öoøqã;Y¼AñwãGïxÇöoø1áßÝø#Z:›âøÑá ø3îH¾5~Üš_‹~>ü3øwão|ø§ðö­ñ?ÂïYÉû:ü0¼øwñ á÷Š¿e¯Øßö’ŠÛã§‹¬ümû>xcá=ÏÃ={ö†ñ‚¾x‹Ãßtÿø‹ÀÖo}«|+ý¢¼iáMbânw‚É¥~L mà“öµ]ÜáÎ’_YrråRº¶é¨¶ÜyŽj«í¯¹ò>gêÝþÞíÿ5ûöBëÿañ›¯þ'ÕXWý½ÛþnöBãþL»ã7ÿGÝ|•¦üGý»ÿáø7CÐ>ÅâOêKðþOþ3ðÞŸá߃µ _Á¾ºø‘kû4þÓ2_øâ'Çh´‡»Öu+K¼ý™þ#hÞ)ñ¼ð¯ˆ¾/þÏËjþÐÿN¢^žÜ©ëþ}ýª^Y–Ë›— (ÙÙsT®®•µMÕiߪÞ.êI5b£9ÛY^þKôG,·ÃÍÁþÈC'þ0³ã?ÿGçOþ½L-¿orqÿ û!sÿVWñ›ÿ£ö¾‡qøqø÷ÿ>þÕf1“ŸÀ}Î?:åž_‚[PµÞŸ¼«·Î£×þ \òïø/ò>uKÛÝ¿æáÿd!“ù2ŸŒçêäÿÇOñ« ¤~ÞíøÈ¯ÙdÿÑ”|füÿå ¾’‰}>ƒüϽhD½1ôÔôý~µÇ<&mJ×þýMõŸõ¨sË¿à¿ÈøÏCøåñßágí5ð“àÇíñ3àOŒ|-ñ³á‡Åý{ÂWÿ >|OøU¯éß¾|@ý¼) èW—ZçíñëNÔôiÿõÉ®$›JðÒéW¾Óä“Z’ ¹­èE~H~Ö£··üÇÓþÏÚÃã7ì7þ~¹¯Öúò+Æ1«8ÅZ*ÖWn׊{¶ßâkÚMîòüçþL#ö!ÿ³Býš¿õLø.¾½¯¿àŸ?òa±ýšìÕÿªgÁuϯh¢Šù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½\§ükþM…ìä¿a¿ýnÙÚº¿ÚWþK7üçþÎ÷ÆŸúÁ¶õr¿ðQŸù6$ÿ³“ý†¿õ¸?gjìË¿äaÿ°Ì/þŸ3ø%þ~Lñˆ—öýIþÏ¥hD¸Ç·‰ôÿ=ÅV‰qŒöçñ?áý+F%ÇáÏâÃü+úž¬·òÕú½¿¯?#ÂJí.嘗ôà{“þZóÏ‹~ þÏÞ±ñ—ÇŒ? > øCRÖí¼1¦ø«âçÄ |6ðÞ¡â[Û KU´ðý–¹ã-_FÓ.µË½/FÖ5+m& ©/ç°Òµ+È­ÚÞÂêH½*%éíú“øõúWã‡ü{Â^9×|5ÿßñOƒ~üSø•¦|ÿ‚³~Çÿ~'Zü#ø[ñ ã‰<'ð§áå‡Å=KÆž9½ðoà x·Å÷:ƒo5¬w2éÚìó_ßéšUœ7:¦§§ÙÜø™–&x\-jôâ§:j-F\Î-Êq‹rQiµ''f´OU«:aåôÛCõBý¡~ø’ëáMŸ‡¾9|ׯ>;Úø¦ûàu¦ñ3ÁZ¥ÏÆ[/éë«xÖóáLZÜòüCµð~”Ë©x¦ã ¬Cáý=…æ¬ö–ÄH}Â%þ€}{ÿŸs_ÄÞ©ðþ KðÁ&ñÀ¿þÚ >ü}ø¿ÿ~Ñøwð³Ãÿ¼wàïüKý“,[öÓ~'øÀñÚËð÷â¡ñ‹HÕñ×í—¥x_ÆÞ±ñ·ü_Å3ÛþÙ¿´Oí%ዞ"ýš¼þ Óußü!øÛð§ö¶Ó|añRßŶ>3ðÂïˆþÒ¼%âÖÓu¿h:½ ñ&âO™†y^q®êà+)PT]•áûh`¹eTÑÓRÅJ¥J²äThÂnQ“§)KGI]ZkV×{Zú;uÒÉkvÕ´zR1/§ÐSÿ×ú×ëþøNãN´ñOÆ?…^ºÕ¾*è_t›o|BðŽq©ünñF™m­økàæŸ£«ÛI{ñWÄZ5힯¡ü=¶Y<]«é—v÷ö=ŬñJÿÍö_ðQ~Ä6|Qÿ[ñ·Â(¿cûS¤x£ögøwáO„ÿ´¦§ûd?ísáytýª|¤|MøÅðÃÞµý•äÓì<[í1ñ'Ä_5=ÛÇúÅÒé¯ÓAÒ¾ZÓb¿¿ ňìþ~ÖÚÕ÷Àø9káŸíë«Ù_h?´ÇÆDñ¯ì/á]CS²ð¿ÅÏ…ZŸˆÆ~øåñ:èx¿V»ø•?€5o~Ò0³Ò´‰þ26µ„¼4Î:ùÍkÇÙàªEhçíÛS‹t£VÜ´ã8ÊI9^Õ-Ë8¹&ÔR¦žóNûrÿ‰FúÙõVÓî?¯ öýš¼Iã[†¾ý¡¾ëßuOüIø_¦xFøµàSƺįƒ:N¯|_øycák~}vïÇ? t-cIÖ¾$øJÞÂMÀºN©§j>(Óô»;Ûi¥Î׿loÙÂíþø³ö©ý›ü/ñÚó]ð·…­> x‹ãÃâÝω|o‘sà¿Ûü8ÔüQmãõßÛëú þÒcÑ›Pñ:Ö“.“ovš•›Mü·~ÇŸ²Ïí-¥ÿÁMÿcÿÚ'\ýŸ>7xkáGÄÏø*'ü“ö ƒÄ>(øWãŸËà/ƒ¿´7ìíðƒÂ_õÿŒšf¯¡Új?5‰z߃õ› xsâu·…üA«ÝCµ¦%ÄÑÂOÿf_Ù§ãÕÏükþ ßñžO~Ò¾ëúïüsVð߇~|-‡á'íW§øöt¾Ó|má{|eøñT×´/ê–7ñMÇÀü=ñ7‡æñ~¯e¬øŠÛÄ ákÏñÇ1ÅUŒÕ”\±Ë ­;QxŒö֔馜ÿu~u;¥9ÚÕB1V¿Ùæ{oÍËmÛÐý²ðÇ¿øSǾ&hšEÄ>8ø²Þ_ë÷Úïˆ5™µ/è²h>Ñ´»MkUÔ4ï xoCµºÕ¯í|/á+= ïâ_x·Æ\Þ,ñŠ5­_Ô"^žü ëþ}½ëù)ýŸ|;ûøþÛû+x«Ç^ÿ‚‹|Gý­~8~ÒºW‚?i7Çß¿l$ø±ðŸáCükøûàíâW‰¼ ¤øƒOý£´‡žð¥ßÃÿøƒÃ¿³ÿˆþ xëâ¼~ Õ¼Qã뿆f±£ê#|ðŸü×ã'ÃmKãÞ­ÿMðÇ‹¼ÿý­|iá‹m+Ä¿µ¿Á›ÿ~ÞŸ ¿lÏÚEýôoéÞºð”Þ:ý 5oƒü:Ô´ßëñj~<ø¡àÈ<)©ëzwŠ|;¿ážiÊ©(àê'V‚Är¤Ô`ê¸MÆräKûTådÿxª¦ïg\—¿¼´|¾½;íÑ|»ŸÜôc?€úóúU¨—?‰ýù?¥~(Òà³Þðî¿iû;ê?¶Þ½ñãücözøßâû‹÷¿üM¡øwþ «þÐì><øSáü¿oüý ´ß€‡Ä„Ñ>ø|©XêvzN°þ }vÎN½9&ÿ‚•|&ý~8|wø?âÿø(Gí©|ý®cOŒ_ >|@ýœ¿j?‚^5ø©à½:kO þÒŸ¼᯿µ'íYûhübøKâ/ÆözŸ4¯VZg‚ü'âßxÅŸáñ?„¬4ûÿ rO1nÿìÕ’Œ%&ý×d”¥î­æÔcïF)¸ÊTã¯:ir_í+ée®îÛö½ôï®ÇõÝçÿ Ëùt­—§¿èáÿÖé_Æ—ì¡ÿÁ{´ocàÅSãLju߆²ícû_ü<øÕâû¯‰|ø‹ûE~Ó³Çà 3àßì¯ñÄ"¾ÿ…}¯/ì½ñÿÄÿâðïÃ}VÕ­4‹ 6ñ‘á=Bð–…¨é^)ñ(ÁcìUûOðô_ísûþÅâ@ñðý§?á£üÿþÿáwü4oü2(Ôÿâæ‚ƒàwü'ð–„ð£O†Í¿öGüK×X®˜÷ÊåõzêÊ¢åq\ÜЂ”’Zݧxïow™]4l£k$ÓÛUæ×ùßðèmiñOá€øÿ Gþ?€¿átÂÕÿ…Eÿ ‡‡¿áh¯ÿ„‡þøXÿð€hÿÂWÿü%ñLÿÂaý“ÿ÷ü$?ñ%þÑþÒÿF¯F‰s~OЯ½¿ðT¿Ùsþ ðçþ ­|BýôOÚ—^ð_ÿø##ñâ–¡ñóâÅï‹^³ÿ‚ƒjŸ~+þÎÿ?i›‡ñW‰´¯ÚƒÄÞ¾³ñ„ôë/_øßDøke©é~Ñôx[³éØ¿€.l5x6ûJ´ñVŸ¥ßxWÃ×zmŽâñ,>7±Ó®4‹9¬íøÄú¯ÇO|(»ømáíGÂ~þÝŸ¶ÇÂOÂ+'‡ôýFÛÂ~6ø;£~Ñ6> òŸí[é¾-øJÚ6wt­ÇÂoéð®¥sãb]ê/(¥ø¿ÔÝ+$»²56¶Öò]K¼K}:Ý^Ë 1Ç%åÊ[[Ù%ÅÓ¢«\N¶vv–‹4¥ä[k[x ‰-:Æ3O±Ómžò[}>ÎÚÆÞMGQÔ5}BHm!KxžûVÕ®ouMNñãZçQÔ¯.õ ÙËÜÞ\Ïq,’¿„üFý™¾üRñ,¾,ñ7‰?h-3TšÎÒÅí~þÖŸµWÁï l‘’‹Á >3x#ÁÐ^:±7z„w÷îKë›™XsŒú Šñ_…¿¼ ð{PÕu/ k¿µ{bθþ)~ÒŸ´_Ç->`œÜ#éZOÆÏŠŸt½ ñ¤;fÔtK=?P¹‡×2ÛôøWLñ¿†µŸ ë7^$²ÒõÛ6±½ºð‡Œ|]ðûİBîŽ_Fñ§€µÏ xÇ÷‘Bê×tËôBñ¥ÊÇ$ŠÀòü1ÁŸú?kßüX7íïÿÑ+_XéÖ0éš}Ž›l÷’ÛéövÖ6òj:Ž¡«êCi [Ä÷Ú¶­s{ªjwj×:Ž¥yw¨^Î^æòæ{‰d•À.Q_>üFý™¾üRñ,¾,ñ7‰?h-3TšÎÒÅí~þÖŸµWÁï l‘’‹Á >3x#ÁÐ^:±7z„w÷îKë›™Xl|-øà_ƒÚ†«©xK]øÕ«ÜkpØÝÇñKö”ý¢þ9iðÃæáJÒ~6|Tøƒ¥èW!Û6£¢Yéú…Ì8¶¸¹–Ü€µQ\ç‹ü+¦xßÃZÏ„õ›¯Yizí›XÞÝxCÆ>.ø}âX!wG/£xÓÀZ熼cáÛÀÈ¡uëºeú!xÒåc’Eo™áˆ~ ÿÐéû^ÿâÁ¿oþ‰Z?bù#>4ÿ³½ÿ‚ƒë{þÒµ½ûnüñWí)ûþ׳Ÿué>6øýû0|}ø)àíSÅ—Z…tß|TøSâÏø{Pñ5ö¥kÚ½Ÿ‡ìõ}vÎãYºÒô=gQ·Ó£¹šÇJÔnR+9³o?bŸ·~ÁûAxNݯ5ÝN]3áçí‡û^|3ÐîuŸø—Zñ—ŠuûÍáçÇ/ h—¾#ñG‹>xÿá­Æ»wâm<-ð³â—üoÆðLø+S›Ãÿ5©´CcûNx¡~#ëÐcÕ<;mð¶]CÆ7ºÄ¸ûcLø_û`ü@ýªüI®þÑÿÿdïþÌÖw?¼ð‡TÐhÿŠ—þ9ðÂÏøOQÑ%×µŸÙë\ýì|âߌ¿H·ð§uéÿi áçÃ=[ð¿Ãˆ¯|ïˆ|fýÓ¬aÓ4û6Ùï%·Óìí¬mäÔuCWÔ$†Ò·‰ïµmZæ÷TÔï8Õ®uJòïP½œ½ÍåÌ÷É+øOÄoÙ›áÏÅ/ËâÏx“ö‚Ó5I¬í,^×áÏíiûU|ðІÉ!x¼ð“ã7‚<ã«w¨A¡G~ád¾¹¹‘€ãG€?à?ô½_á¿ñã'ÃïëR|L³ø{ûQj?‹n®>,þßÀ)ÿf‡Ö-¢Eöïˆú…ßì{ðŠoŒz^³ýàëoøhÿÛø[ZÕε¡û‡Â/Ø/öžø{ñ/Úæ¯oû)ÝxCáGí‘ÿ"ý¶þê~¾øƒ¡|Kø—âÛÅ?µfŸðïàÆ]Vëáö£¦[x2ÇÀÿ´‡ü9øw¡ü.ñ‘ð›Oø›ãïÓÿ…¿¼ ð{PÕu/ k¿µ{bθþ)~ÒŸ´_Ç->`œÜ#éZOÆÏŠŸt½ ñ¤;fÔtK=?P¹‡×2ÛôøWLñ¿†µŸ ë7^$²ÒõÛ6±½ºð‡Œ|]ðûİBîŽ_Fñ§€µÏ xÇ÷‘Bê×tËôBñ¥ÊÇ$ŠÀŽßà™?~ ¯ì¡à­cZø3㿇Ÿ³ßí—yûVkÚýÕ·‰,6ø—û0þÆ_ðV¯Ùãâß‹|9ö¯‡šT^$ƒÅ¾;ý¼~k×¼W}áý^ðφ~#ê&±Ð5ÂÚ'Œ?K?áˆ~ ÿÐéû^ÿâÁ¿oþ‰ZúÇN±‡LÓìtÛg¼–ßO³¶±·“QÔu _P’HRÞ'¾Õµk›ÝSS¼xãV¹Ôu+˽Bör÷7—3ÜK$®ÆüIøƒeðÃÃø¿Tðß¼K£ØÞYìEà j>6×t6åÙ.³¥éxI5o é ×’ûE®‹u7R[þi~Ñ>>ðGÅÚgöCñ§Ã¿xÆÞÕÿf¿ÛU´ïøcU³Ö4«–ƒâ§ì1Ô wc,ÑÇyes¶—öSîì/bšÎö.¡–$ú¯öøVïÆ¾Ò¾8ø³â=ÿöNƒ§xs¿·íEû4|6±…|È¿·üC¦ü&ø©¢èn§E™µ{Ï |=ñ‹õ™ZÆŸ~æK»OÊh?bí+öUý³>øÆãÇ~/ñ×Äÿjâ=Öµã?‰^.ðÆŸyáˆ_±^¢ÚxBO‹Þ<øŸñˆìµ›ˆ5sÅŸ3ñ§ˆt?ø?Â:­âx³Äúµ†á¯ xk@°¸ÕuÏø‡]Õg´Òô]EÒí.µ-_VÔ®­¬4ë k›Ëˈmá’Eùï@ý¼¿aŸxKÆ~?ðïíŸû'ëþøo‡uñÆÚ/íðƒTð—€­üM­ZøsÃSøÓÄ–>0ŸFð´> ñõ–ƒ¢K®^ئ«­^[iv q}qÿð·‰|oÿüýº|à¿kž.ñ‹ÿcŸÚsÃ>ðŸ†4›ýľ'ñ&½ðSÆúVƒáßhZU½Þ©­kšÞ©wk¦é:N›ks¨j7÷Vövvó\Mmü^|_ý’¿k¿þÆß4Ï þÎßµïÆ}[Mÿ‚:ÿÁ8ÿg–Õ¾,þʾüeøsñ£á¿í•ð3Å~7ý¾øMø;ð‚ïâïÂOøWA×üo®üCoüWñlQx[J›Zøãâ *ÞYïþ[6Ìñ:œ”pêºúµjªÊm¹Â$¢¹[{Á_–Ѧܤõ§N3ZÊÞò_+Å_nÍïmºŸè8ƒ'éÏøU‰'¶²¶žòòxm-- –êîêæT‚ÚÚÞifžâyY"†bF’YdeŽ4Fw` ‘ü–êž"ÿ‚’h¿Óá%Æ•ÿ»ÖüÃáŠÒx«GÐhíkádðKŸÊ‘i^oŠÚTäýš-Aš_|+ƒÄxÁ1[¹ñ§‡¼>÷)wàþø ÿWý ~~Ò_ ?h¿~Ü:·Æ_þÍÿðPïülø#¦ü!ø÷àÿÙ·Å^5“Cñ|¿²þ‹áÚ_Æÿ¶‹ð>M^ñb|;‹À>ýŒÿgË]ÆÞ›Ç¿j^ðN»®x·VóêæómÆž¼§ïE]¨ÅJ.1qri¾{Êî0Œ§ìáRj2tù^ÊšI^I->ëoýY^Û_Oì/àW‚¾xá?ü9ðEì®~þjÚ‹µ_ˆxšÃÆWw>1ŸÆrü@×u¿ëž>¾ñ¶£¯^ø»Tñγâ=VñŽ¥­]øRÖµKÝN{éýš%Æ=¹?^ß—ô÷¯ã÷@ýº¼û-~ÆÞøqáø)Gƒ>|=ÿ‚CêÞ øcáo„>ý¯<3ñWßðX]ßLÐ?h:þÎËâ ³Ž‰9ŸKøÄh¿á‘´¿ Ú\è^h>é>Óì}‡öŒðZøX¾,[ÏøjûO„ŸðLóû?À%ø†?b³ñoo†üq¿jÏø@Xü þÊÿ„—þ?ìDøúVÓþ¥ÑÃ0oL Ü2Í9`’ÂÕ\°¤’¦½Åz.£ŒtVO“ÙÐVýìåN>ç6‡'÷—ÏÖß…ï.É7­êC_øóð3Â3éÖ~+øÏðŸÃz¿Åà6•k∾ÑnuOŽ~*ÒíµÏ |ÓàÔµ‹io~,xD½³Õô‡VË/Œ5.î×PÓô{‹Iâ™ýŽ%ÇáÏâÃü+øV—öøÛá]Zø+ið?öÜMjëþ©øOûFÉâ˜4ïÚÃÇÚ ‡ìCyaãk?þÓ/ÆÝe¼]à®X®»­Ïñc㿌.¾1%ý¯‚n¾?xšâ_ü>m?úDÿ‚BZ~Ó‚¿n/þÑÏñþëIø_ÿ+ý«¾~ÌZ·íuñ]ñ®»û&è“ø"ãàö¯ øÿâ¼—¾5ø§à–ÔäÂ?bû4/Ù«ÿTÏ‚ë >½¢Š(ä/ÚWþK7üçþÎ÷ÆŸúÁ¶õrßðQù68ÿìå?aŸýnÙÚºŸÚWþK7üçþÎ÷ÆŸúÁ¶õrÿðQoù6H¿ìåaýnÙÖ»2÷l~öÆaŸþV3ø%þ~LòX—§·'ëÛòþ•¡ãÜŸ¯oËú{Õh—¦~§úŸ§Ö´"^Ÿ™þƒüû×õIw~oô_ð= wò_×õÔ³ãÜŸ©ÿé__¶ü7ã¿ÁÚoãïÁ/„øSqáÙ¿þ Kñóþ âÿxïÃ^1ñu÷ˆ¼{àë¿xOáoÂ8¥ðÇÄXx'N¸×txšÿR·ñ6³âŸXêÞðý¯…//íüm£þÍ(À™¯Éÿ?ðNßüNý´þ7|UÒ|IáÍöbý´¿`¿þÆÿµçƒ|=â=KÁ'ñä~#Ó<ûDxVÔ|>ñǾ#üBÐþë|Ó_dzøjOøOf©k{âË-*ê_=›¼T¨ÓŽSSxŠ~ÒTÚRŒf¢ß^Okì•K[–“”âœ^ð忽µºú«ÿä·µúùØó¯ ÿÁiþx?áÇìF~:|+ø©sñkö¶øQû4êr|'ðï€að~½ñÏöƒø-áωº/Ãï†þøñŸKøÁ®xoZ×uÈ|¢|BÓ¼1â¿„žñ~£§xÇ?ôÏiþ!‡Míµ¯ø.‡ìAῇ_ >*j)ñm¼ñ7öañoíc©ê6^ðÅôß >xWÇZ_Â84oŠö6Þ9{­/Æþ,øßªÅðGÂz?„ãñ–™wñ*ÚóOÕ5ÝD·þÞ~Rÿ‚þ̺‰~|XøyâÏŽ:‡Ä€s~ÄÞ4ð…øÿÄþмK ÝE£|^»øg¢ÙY^«?Š4C®Û {ÜÙ³þ‘ðÖþß:wík¡ø7TŸöôñŽ£&¡ðËáŒ|Y®øö}øSÄmsã…þü&ñ׋|'àvKÛŒ~&Ö~(ßêPø Â^“Ķþ†ÛÁ‰c¤Jº‡ÎJ§Æ^ɬ;n5g5Ï:p‹£*•#){yÇÙÔ‚N¤=»œ\£Gßîto›u{uïeÒ׺~Vë§o¢Á|cOøSKÖ|!àÿ^=ñ~£ûSü ýÂ߇þÏ?üQmñ?ö’ð·|Wð{TÆžý¡#ð&Žº}Ã/ß?¶ÏìwðÓö÷ý—þ'~ÉŸuÏxoáÏÅoøBÿá"Ö¾êzã[3àoˆ^ø•¤bê>(ðÇŒt+´k¾Ó-5·xoQ3i3ßAmöKÉmïí~`øÿÿxý™ÿi!ÿ 9ñÇÇM+þUÿ ˜>:Â'âoXÿÂ(cƒ¢ÿ±ÿ…Ký±ðÇ]þÂþÝþ´ÿ„óþøNÿ´üÛøGÿáß“æã?´”¿ÙêS”aB 󌴮©bÜ¥%ov2¬°Q´ZQ§R½½å—'T÷óÑ^?§7ÎÇ™ü6ÿ‚Æ|6»ñ玾ë^ø§ñ›âýÿüCö¹ýŠ~|-ø=ð/Ãñmõ¯ì›á?øãânµu'?jŸxsÇ~øsáÚêzçÅ–ñ wÇk¨ÚÛx{öjð½å„–úŸÇ¿?àà?ø×þ Ñá‹ÚïÁøüqûux³ö"ý´¿l»Ÿüð5Áøð¯á÷ìãñwãÁÏüFø±m㯚_Ží¾kž/ð£ø“Oð7‹«áØþx¿ÂþÑôÈô­k@Ö¼¥ qu§üF‚÷eÂy—‡?à߯٠Á_<ðÀ?jÏÛxCö_ý ÿcMÇ^ñÏ„ñïÅoÙÇöø¡ã?Œ~2øqñ&ëTø'©ø^æÃAøƒã½wZð}ÿƒü)àÝRÑ ?Ä7¾%ÓÍå½ç•Uf·~ôP©)'~yaœ%y]ºªØzMÂ4å}±ÞºÞëuÙ>Ý6¿w~‡ËŸ³gü¶ºoí ã_ÛsIøiàÿ|:øUÿˆ¿øSað‹L—ÂÞ$ñ߯ø(Ÿì…/íã¯ê:ÿÆoŒvß t_x{ÄZ~»wám{Æ-øuá¿ü7еk8Öe°›Äï/ÿÁuÿa/|=ð'ÅmWÇzw‚|a­~ÖÕüCumðçVÐ>xÿö:øWÆ¿‰žñgм#ñ/ÄÞ×µo|4•üQð«_øSâ‰^ñý”JÖ1·:†ý¥Íøsþ+ûxoLñu¾›ã¯Ú-|E®ø'Ö¥àß·>Åã/ƒ^?ÿ‚i|¿³‡Å_…—VŸ ­tkOÉàû‹û¯h~6ð—ˆu­NþM+žÑåM/Uý¡ÿàŸ³·ímû?ü:ýŸ?hÿŒµoÆ-3À´]Çí-sñÇôo|Nñ·‹5-;ÄÚ>»àMoPÕ< uàÿ|ÖôO¥Ið§àß‚¾ø[C±Ó´Èü!má·Œšü)B JTå(óÉsYó9b%(©M;§ì\b­ºó"¢ ÝÒz[墾–wÖï}þE/‚_ð[OÙ#öƒð¶—⟄~øëã¿ý–—ö«¿Ñ´ÿ xMkÂ~›ãÕßìãgð÷ÆÑ§ÄÆÓ<)ñ_ˆºG‰.µkÝcS·øOà¯øOÄ_¾!|Sðƒì¢Õ®x߇ð^ïÙcâÿˆ¾xáÁ/Ú‹â‡ÄÏÚãWíSû>øcáï‚´¿ÙßT¿ð÷ÄߨóÁŸ¾!üZÓüCãIhû_„š¯‡î¼ñ+Ã~%ð_¾üFñ÷ƒ|I¥ 둮ج0}ªþ£ÿý…5[ø(n“üeÑô_ø)5׃¯~0hÞñ…t=7á­çƒ> j_m$øŸà[–º·ÄmI|Y¯é1ˆõÕ•¶—™iáÙõMQëgÿø"7ì¹û;|~øUûHxWâgíâ_ˆŸ ioÛCöªÑbñ‡ˆþ¿†õ¿‰·_‡ÿþ1Yx‹Gð—Á¯·ü":7‡¾èÚ¯ÃÝ÷~»ÐµûÝ^]gUñ&.Ÿ¢é¼“–5r©{%nU7ÏÄ¥(¦´n’rŠ÷¬æâÕ⛿wÏËÖÛ=ºö·ã¤íÿÍýƒaý²“ö'>(ñ Ÿäý¡áý“á-‡PøS/…Óö‡ž1ü=¯Å?øhŸìôñ!_MñQ~·ÁH~!àÙ>%.´’Û'³ÿÁ0ÿl/‹¿µŸ†ÿk¿üzÑ>é?dÛ›ãßì{­ê_ tx+Ã_ô…)á=GÂ?l>xËÆŸ|Gà‹?è¾*ŽêÓE»ñçŒ-iòÝiÞ ½‚c½ïÁ2~|4ý |cñËáÆ¿Ú—á§‡~#þкŸí]ñöqø}ñnÏÁß> þКöâOˆ^4Ó´ XüUñŠ–ÞÃWñoÃ-CâÌ¿×|[©iVz=Æ‘ñ«ö”ø[¥ŒÓO ÚÀßÚ/â§Á='Uw¸E¼×t¿‡ÖzÞ£ þæÛPÔ.m퉷Š#V¿h_~ýšþ|pý£¡hA{ ù.ldg’Ù˜á~Û¿ø³À¾Ôðý¯ƒ|uñÓám׊ôê> ñïƒWÄúïÃOøÀøWTÑ-?Cã÷À…ð÷‡<\ßþ/…!Õ4?Oøñz?ß~|k·ø{ñáU—Åi>5Ó<ñjÃÃWÄèúômÑ¢ñv•àïxãÃÚ^©}¥Égª6Óŕͅ½ôšªØjðßi¶‘£öý¯<û>§Â‡ fmoOøÉûRê¿?k/…Ÿo¯ÛÇÉãï‡úo‚|áß ü.Ôj¿Ÿ¿iÏŠÿÏŽµ¿‡~ Œ“j ¾øZøO¤j_ì~ÝGãxÿRý‘ø[©|SÕü ¡jüðÿáÿÄÛí?øI¼#ð·â_ˆþ0xHòµB û â/‹>üñˆ¾ßáø´­OSþÐø[áìb÷PЭ¶ì´ËojàEPEP_™µèÏí…û+ú¶¿ÛGÿV‡ì)_¦õù•û] þز°ÿ«jý´õhþ•ô<'§å¶.?úLŒqÁ©þÕľŸAô~¿ýjЉz{ð>ƒ¯ùö÷ªÑ/L}õ=?_­hD¾ŸAþ?çÞ¿¢jÊß/Íÿ—ùžT•ú¿È±ÀÏà>ƒüþ•üð_ÿÁZi¹"½ø=jÞ%²ø›ñ¦Ú|SÓt sž*ÕbÒ-¼Ÿà›? øø{Ävúý¿Ä‰õ«6ð¿ôF@>•øðCþ !w¤Zêÿ >2øÆ¾üÿ‚£éŸðR_Ø[Lø/âÇÑ®~YX^ëúÞ…û4øÛá¿‹~ê>ðÂkºÖ«uaeðÏÆw7~1>(Ô®-¥øg&‘äø—æsuœðÐÁJpW­Ï8I%í-MÓUJ|žß]W´Tãfå¡Ê¯Ín›«é}mç·Êæçˆ¿à»?²7‚¾"ø·á§‹¾~Ñ>Ö| cðçÅ^.½»ðçÂK‹ü,ø‘ñsCø%kñ_Çš~™ñ¢ÿÅ? ´/ x÷Å~³ñÿ€~)øsÁ?ü)§ø¯CÔo>É·cêþ0ÁqcO‚>6ø‡àiÿn5¿|yñ'ìã¦ê^ü ð‚þ"|Jøwà]Ç¿m<ñãÆï†^Ótσ6~&ð¯‡<{yñ+Ä¿gŸÆ¾+ðß…|eã-OV¶¾tÖÿà„ > x'Ⅎ•¶£ñOÄÿÿeÏŒ±Cøöªø×máo„¿¾ |kø¯â¯ú¿Œ4mOáŸìÇãŠ>6ø‹à_‹š¦/ƒ­|Mâ]>æëÃROöïˆZöƒ£j7?Iüÿ‚6ü'øwû=~Ⱦºø·ñcß´o쩯|Kø“¢þÖŸ u/ éõ?‹ì¯`ý¡µùâø›àÿŠÖô‰i¨ëMñŸ…¼G¨Ãá­ö‹©¥õƒÞMá:œBÜ©ÉaàãÔçPSQ½UÑ„Ÿ³§V§±œªR•¡S×äf•þçGïZöß}­jÒ¾ú¶Ó÷uGyð+þ û8~Ó?þ|ýŸ>ü{ø³©ü~ýœtïÚ§Hñ~ῇ?‚| ð”üpñìõâË¿ˆ·>3ø©áéúçÿ‰>Ö, ýª´ß‰?´~+x7ö<ö*MCãÅ>(·Š>ŒRüp»ñ·Œ¼Iâ½ûâˆ>(ßxÎâ{i5y¼o„¬|6Ðè:/‚´»[-=­>ý‰søþ€ŸåV–>T$«×…,G´\³§J‚‚Œn”$ݹ¥Ï¤§7’MîÜy9¯xÚÚ·{úé²Ómoò>ýª?o_þÊŸeÿÙúóàÇÇŸÿ?kÃñ©~xKà~•ð²ò[‰~xWþ3ñ´>"Ôþ*|ZøQ£h¢_ø’Ý&ëûBêÅ“MÕßW»Ñá·¶–ïáeÿ‚ø~Ìþ0ø7ᯉ øûDxLøãû4~Ø¿?fω_>ø Äþ ñÆ­ûü3ñ/¾7xN/xoöŒðÇ‹µ¿ü7_ßÅ}¡ë~"øSà^é—¾ð—ÆË ðçÄßé´¥ÿÄÏ x—Æ¿-Ònà®?àš´ÆŸ‰ž·ƒÖÚߌ—RoŒþøK¨ø{â΃ã±ñ—áèŸÃŸeÖ¼_à ¾):ßÙ ñp·ŒÃµñþ ûýþ"|2ñwÂo‹ÿµgÃßüPýŸÿdŸÙëã.•àü)´?4OØ“LðÖ…û?øóǯâo‚~)_øXžÐ|+¦èÚ…ç„ãð·ƒõ[F–y<¥…ý—°x§þ¹û4xŸã5·Å»OŠ?´„t‹ÛãÀðRæø+áo|8‹àåÏíià¨àSñÅÆ›®ü&×¼tÖE¼-ã­/Å?š|?ƒÅÕØŸÊ¨³KËšpIªp\²Žœ•+¹J7MûÔåEUr¼¥R-FеßîôýoÚ;üï¶–> ýœ¿àä_‚oìYû+|fý´ì4?ö…øïðÇãŸÆ¯xàKx Á~ð§Â¿„ÿ´WÅ/‚Z_Š4«Ú3ã÷†5Oj¾!áô±h¿ ¾ø³âÆëº7‹®<ðöïO°6vqüfÿ‚óþÀßtx‹ÆÚ¿Ä™¼=¡Þ~È“hZÎᯠKÄþÚµ¯ŠŸ ~(øÏUñ®‘«ßü?ð÷ƒ<-âýGâE潦ø{Ä—º­¶ŸáÝv[½ûg'áoüŸögøà/‚~øñïöÃø-âß‚Ÿþ-~ÎvŸþüMøsáÿ>;øñ›ã/Š~=x£á¯"øëâНZ³æR¼¤§ï;Å&ÒQ|²º¸¨6ÚMjÿ=4{i¦ýu)üKÿ‚ÐþÌ ~ü`øàÿþ9øÏáÏÁ?ÿ¾x«Äž±ø3§hz®­û7Yøj_‰^)ðо!|lð'‚üA¤Mªxxsáç‚¢ñ,¾-k>ñcü1øKâÍÃ×úºô?¿à±ÿ³Æÿˆ?¼-à|uÿ…sû6üø-ûM|lý£µß|:Ñ>øàßÇïÙ¿jφþ(Ôïâ—ü-›¹5†ñ˧êN™ð¢÷UÓ0øSâ·ˆ|+ªhšÂÙ?öl¸ý•üg¥øÀú•Ž­ão†7—rüK½¹Ôo­uOÜKá[i»4tå”ñŽqæöq—5šzû6ä—[*­A½[‚RM7e^íºÿM|¶¿õcäm'þ ¹ðgö¥øû^ê_°¬ÖñþÓ³Çìâ¿ÛGÀ^øÑ øâdžþ%|2ð:I©ßØÜh_³gí«ë>ñ?ˆ>Åkà[¿|FñwÃ_Œÿ |CãÏx§Åÿ5}> ¼+¬þÅ~Ç¿ÏíAû(~Ìÿ´”š^Ÿ¡\ü{øð‹ã¡áý/P:µ‡‡5_ˆþÐ|[ªøjßQuŽK±áÝKVºÑ^Yâ†äË`âæ.‘'À-ÿØøû>þÊþ~Ç¿´ÿíã/Šÿì®ÿ²ìçþÓ´n«áƒ_³ƒ……+¯øHø%ð]mãñ­W]kÃ=ñG€<{ñ‡Ä‰à ü5»ø±áo Ý\ëzOÝ?°ßìË£þÆß²ìßû/hëáù›à§Â/x/Äzφ4q é1ñýž‘ÏįˆƒLËK§ñ+â߉ü¯\^K>£¨k¾$ÔµNæëP»º¸—šµ×´w÷vIG›™[DÚnÜ×k§-֪õ´ïëÑo·_–öÛ_«â\c=¹üOøJÑqø3þ•V‰z{r~§§åý=êòŒ ~'ë\µe{Û®‹Ó¯õ¾¥Ánþ_×õÐü¹ý¯äýÿàÿö.~пú¹¿aªýg¯É¿Úücööÿ‚wÿðŽ~ÐÄýás~Ã5úÉ^]Gyɯ/É|…ÿùÿ“ýˆìпf¯ýS> ¯¯kä/ø'Ïü˜GìCÿf…û5ê™ð]@^ÑEòí+ÿ%›þ óÿg{ãOý`Ûz¹ø(¯?³,?örß°Çþ·ìë]?í+ÿ%›þ óÿg{ãOý`Ûz¸oø)vµ£xoöN¿ñˆµm3@ðþƒûB~ÄÚÖ»®ëWöºV¢èÚ_í±û=_jš¶­©ßK–›¦i¶0Oyy<6¶v°Ëqq,pÆî:°-,n ¶’X¬;m»$•h6Ûz$–­½‰ŸÃ/ð¿Éœ$KÓß“ô??ëíZ/L÷äý;Ÿzùz?ÛGö8ïûYþÌã'œüxøZ8¿äjúþuy?m_ØØù;_Ù—'þ«ÏÂÏþjÿÎkúF¦a‚ó…×WlE/—ÛÿƒúùJI.W÷?/Îëï>žQ“ôæ¯D½3õ?Ðtý>µòܶ¯ìißö¶ý™y9øõð¬qéÿ#_ÿ_Ÿj½í±û gö¹ý˜†y9øùð« è?älÿ9ö¯>¦?ÿæ/ ®®Õé|¾ßáè²þY}Ïúê¾óêh—§æ ÿ>õ£ôÏnO×·ùö¯•£ý¶ÿbñŒþ׳îsñ÷áGà?älý=Í_öÞý‹3û_~Ë£¹ÏÇÿ…€çÅŸ˜ú×LnÿÌVõv­Oä—¿øz,¿–_sþº¯¼úFüÍ[‰qnO×·åý=ëåeý¸?b¼óû_þ˘ëÏíðŸÿšÚ¹íÅû göÁý–Ç~h„Ãéÿ3oÓõ® ˜Ì3ÿ˜š«µjzöû_‡¡´c$¶zë³ÿ#êØ—öäýOøzûVŒKŒ{ žOùý|ŸíËûŒgöÄý–G~h?„½{ÌÝþqW£ýº?b1ŒþØß²¸ïÏí ðsÿ…oéï^}L^þÑ}]ªCVö·½çé©V}ŸÜϬ£\~©ÿë~}*Ê œútúŸóü«åýºÿb1ÿ ‘û*úŸøÈo„}ð¯«1þÝŸ°øÆl¯ÙLcžh„]Oýν}«‚¦&‡üþ¤ú¿ÞCVöëÖþš…Ÿg÷3ëH—öãñ>Ÿç¸­—§·êzÿŸzù"?Û¿öÏí™û(ŒsÏíð‡©ÿ¹Ã·øUèÿoØduý³¿dþ?êâ¾rð°íþÁSKþ~Ó}~8êÞÝ«›F-%£×]»Ûü×ôÏ®c\~~=ÿÏ¿µYŒdçðSþZù$~Þ¿°ÀÃh~ÉÞÿñ‘úÿácV£ý½¿aa×öÒý“8ÿh߃ýOýÎ?Zà©ZŸüüƒ¶¿uon¿˜ìû?ëþ}ç×q/éÀúÿOþ½hľŸAþ?çÞ¾Aöùý„ÇüÞ§ì•ÇLþÑßG'©çÆ_ç5z?Ûïöózÿ²G þÒ'©çÆýn}«‚¥X4]¿¼µnÖëøù…Ÿgýï¼û%é þ§§ëõ«€téþ­|ƒíýûŽ¿¶ÏìÇOøÉƒ}{ŸùÿÎ}ªÂÁ@?`ÌäþÛ_²(ì3ûI|ÏÆ¯Ö¼ú•F¼Ó»==Í⬒ûýO;ÿ‚  ~Æþ%?õ\?c`>ƒöÌøÿÖü«ìß‚ŸòF¾Ù2ðþ¢ºU~UÿÁAÿl/Ùâ—ìèxáíKû9|FñλñÃöDƒ<ñ»áŸ‹üY¬ÿeþ×_5Lé^ðÿ‰õ cQþÏÑôíCU¾ûœßdÓloo®<»[iåOÕO‚ŸòF¾Ù2ðþ¢ºUpM¦ôè†zmQREPEPEPEP;GOÔá{6úÏP·ŠóQÓ¤¸±¹†îõ #P¹Òuk–Ýä/4ÍRÊóMÔm™„ÖZ…¥ÍÊEq±¥Êù ö!ÿ’3ãOû;ßø(7þ·¿í+_^ÐEPEPEPEP_™¿µ¸Ïí‹û+ÿÙ´þÚ8úÿÂÑý„Çõ¯Ó*ü—ý¼þ'|5øQûVþÉÞ"ø§ñÀß |?yû=þÙš-ž»ãÿè Ñ®õ›Ÿˆÿ°ýõ¾“m©ø‹PÓ¬§Ôç±ÓµÈl"®¥µ°½¸HšYÝ=îœ)ñUR¤£G)NJ1K–Z¹;$¯Õ™WW¥5«º¶›îŽî%ôú©ëôÿëÖ„kôà_óîkåèÿm/ØÜuý¬ÿfaÿEãáoSÔÿÈÕþsVÇí«ûü5¯ìËÿ‡çágÿ5uûÝLÃÿA˜Woúˆ¥«v·Ûü|Ï5F]#/¹ùšûϧã\œþúŸóïZ/L}õ=?_­|µí­ûù»ÙŽ~=ü+ž§Ÿÿõ¹ö«ÑþÛ?±ˆÿ›¹ý˜xàgãç‘Éêyñgýzóêcð}1xgoúIÝ¿û_Ïpå—òËî×U÷ŸSľŸAþ?çÞ´b_×ôþ¿ýjùZ?Ûsö.ówŸ²øì3ñûá@úŸù?_­^ößýŠÇüÝ÷ìº1ÀÏÇÿ„ÿ‰ÿ‘³üó\1¸N˜œ;¶ß¾¦îßý¿éø‡,¿–_sþº¯¼úœ Óð«‘/O~Ðuÿ>Þõò¢~ܱVrkÿÙpvý >ÇþFßóÍ]öãýŠüÞì´:ŸÚ á0üÿâ­úWLf¦"ƒ¶ß¾§«vþ÷¡º‹I+?=çÕÑ.?×üûVŒKœ{ñøOóØWÉñþÜß±(ëûb~Ë çöƒøKÀü|]þqW£ýº?b!×öÇý•‡nh_„|ÇÅÿçõÁS‡é^‹¶Ö««·÷‡gÙýÌúÅ}z}?Ïô«1®ä?Ïò¯“Çí×ûð?á²?eP:ÉÃ|#àá_Vãý»aá×öÊý”Æxÿ“‡øB0ýÎççÔÄÑÖÕ©;h­R¿Ÿáä²}ÜÏ­¢\ãÜäõè?Ïê+B%Î=ù?Aþ?Ö¾Höïý†ûþÙ¿²ˆÏý\O€?îpãÿÕW£ý¼¿ažÿ¶ìž3ÿWð€`ûœ8ÿõWLE.•ié¢÷âõ?øyºM$­øz~w_yõ úóøvÿ>õf%éïÉúŸŸõö¯’íëû äømÙ8û8¿ƒãü,jÜ·¿ì+ßöÓý“O9ý£~où¾¿pT­ mRÚ/yuùýÝ6ù–}Ÿõÿ¾óëÈ—¦{ò~¿Ï½hĽ?3ýù÷¯cý¾a.ÿ¶¯ì”3×?´wÁÑÀíÿ#—ùϵ^öüý„;þÚÿ²@Ï'?´‡ÁÁǧüŽý~}«‚¥HëiDz´—_Ÿãè4›éýM`D½3õ?Ðtý>µiOÓŸð¯“öÿýƒ@ÿ“Ùý‘²êä~ ÿógV#ÿ‚€þÁcý¶ÿdQÜçö’ø5øùÿOs\fµ³O¢³ï×¹ú%enÇÎ?¶Çííÿìõÿ„sö‡?ù™¿aŠýa¯ÄOŽ´ÀOŽ¿··ìÿ CãwÂŒ‡ÂÞøè|Qÿ «âWƒ>!ÿÂ9ý¹ñ›ö(þÄþÞÿ„GZÕÿ±ÿµÿ±õoì¿í³ÿh/Qû'ö+Ÿ+öî¸dîØÂ¾Bÿ‚|ÿÉ„~Ä?öh_³Wþ©Ÿ××µòüçþL#ö!ÿ³Býš¿õLø.¤¯h¢Šù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^×È_´¯ü–oø'Ïýï?õ‚?mêúö€>xÖÿe/€^"Öuk>ûf¯®êwúΫwÿ O-þÕ©jwrÞß\ýž×ÄPZÁçÝO,¾M´0ÁíÅj¨3?Ꮏgú'_ùwxïÿšzújŠù—þëöqÿ¢uÿ—wŽÿù§£þëöqÿ¢uÿ—wŽÿù§¯¦¨ ™Ꮏgú'_ùwxïÿšz?Ꮏgú'_ùwxïÿšzújŠù—þëöqÿ¢uÿ—wŽÿù§£þëöqÿ¢uÿ—wŽÿù§¯¦¨ ™Ꮏgú'_ùwxïÿšz?Ꮏgú'_ùwxïÿšzújŠù—þëöqÿ¢uÿ—wŽÿù§£þëöqÿ¢uÿ—wŽÿù§¯¦¨ ™Ꮏgú'_ùwxïÿšz?Ꮏgú'_ùwxïÿšzújŠù—þëöqÿ¢uÿ—wŽÿù§£þëöqÿ¢uÿ—wŽÿù§¯¦¨ ™Ꮏgú'_ùwxïÿšz?Ꮏgú'_ùwxïÿšzújŠù—þëöqÿ¢uÿ—wŽÿù§£þëöqÿ¢uÿ—wŽÿù§¯¦¨ ™Ꮏgú'_ùwxïÿšzúDÑ´ßèÚG‡ôko±é™a£iVžt÷eÓtËX¬¬m¾Ñu,÷Sù°Es4ÓË·|ÒÉ#3:(¢Š(¢Š(¢Š(¢Š(¢Š(œð¯„<5à2ëFðžg¡iw¾$ñ‹î¬¬UÒüKñÅÚç|i¬¸wv7ž"ñ‰ußj #ßêw/G,kw_×ô/ èZ׊ðφôG_ñˆµýFÏGд G³›QÕõ­kWÔf·Óô½'KÓíî/µFúâ ;8&¹¹š("w_+øñKPøÃà]wź–•g£Üi¿iO…±ÚXÍ4ðͧü ý¢þ*|ÒuW{€$[ÍwKø}g­ê0¯îmµ BæÞØ›x¢5ó§ü;á&»ñßþ ­ûyü$ðž‹ãoxÏÆß²GÇÝ7Àžøw¯x¿Ãþ/ñ_íþxRð'…4ɼ «hÚþ¹oâØèº³àwººðïÄmPÔ¼ã=Äžñ6½áíPïZ+ðRð߯]/à÷ü;â—%ÿ‚‡jºD?eo‡?²ï†u?~×öß4Ù7IJŸü>ãâŽ<#ðÛâU§‰>6øŸÅZˆÇ_ã­¤xRóöÀ½ñ‡>?xá÷>~Òþ1ñƽ/ÉŸ§ýªŽ›ªhµ ¿ðS‡ýŽ<;ûBþÑzwƒõ?‚^ÿ‚Šø_öŒÔæÖ?fïØCÅÿ³ôËñâïíý«|ÿ„ßŶÌZŸÄ?ˆ:Ή§ü[³ðo…¾6èŸ |9áÛÏ„¾þ£4oxWÄz‹4x›Ãúî¯à/[xOÇZ^¬éÚ¦£à¿^xWÃ>:³ðÏ‹,lng¹ðçˆ.¼ãOxÆÛFÖ"³Ôgð¯‹<3â­›H×´»Ë®‚¿œxcöðð¯Ä_ø(6©ðóÂ?´™®üDðÏ'iÚçŽÿhßÛ7öŽý¨.ü®|k?³>‰¬êúˆ~~Í>ø¯gðçþ£WÖ¼i®Üê^þ” ñ¤Þ"·ðwŒ<-âÉü!«éþñdñ“¯Má}{Vð‡…þ éZ'ˆ£ÒîîŸEÕõ?xãÁ~7ÓôÝIm¯/|!âÿ ø–Ú4mI½»êkòGþ ñðËBýˆõÏÚ öv×#ø‡cá¿þÖßþ~Í>&ñwƒü[w/Åíà¯üŸöÑOŠÇ‰tý]é'Oý™~-éz¿/ï4ßÍñÁïø5£ãÙ´ï ^~·PYšÞ¦ø‹FÕü?¬Û}³H×tËýU´ó§·ûV›©ÚËe}mö‹Y`ºƒÏµžX¼ëi¡ž-Ûá–9\iÑ@2ÿÃ~Î?ôN¿òîñßÿ4ôÃ~Î?ôN¿òîñßÿ4õôÕó/ü1×ìãÿDëÿ.ïÿóOGü1×ìãÿDëÿ.ïÿóO_MQ@2ÿÃ~Î?ôN¿òîñßÿ4ôÃ~Î?ôN¿òîñßÿ4õôÕó/ü1×ìãÿDëÿ.ïÿóOGü1×ìãÿDëÿ.ïÿóO_MQ@2ÿÃ~Î?ôN¿òîñßÿ4ôÃ~Î?ôN¿òîñßÿ4õôÕó/ü1×ìãÿDëÿ.ïÿóOGü1×ìãÿDëÿ.ïÿóO_MQ@2ÿÃ~Î?ôN¿òîñßÿ4ôÃ~Î?ôN¿òîñßÿ4õôÕó/ü1×ìãÿDëÿ.ïÿóOGü1×ìãÿDëÿ.ïÿóO_MQ@2ÿÃ~Î?ôN¿òîñßÿ4ôÃ~Î?ôN¿òîñßÿ4õôÕó/ü1×ìãÿDëÿ.ïÿóOGü1×ìãÿDëÿ.ïÿóO_MQ@ø‡Ä/á=;ömý£>'ÿeiöÿ¼ñ¶lñWà Kw}ÿ̺±µ¹³³¼´[Ùݺ0˜j¸ÜVAEÖÅâ(ᨩ>XºµêF•5)}˜¹Í]ôWb””c)=¢œŸ¢W÷­øÐ‰ûP7_ÛÇö‘éÎ>~Ä}?äÏù*ÃûO1ÿ“ñý¤°?êžþÃÿýµö’ð㉣½,–ÿ˜¸uÿ·No®Qï/üŸ²TWãºY~Ó×öòý¥:s‡ß°÷_Où3ÃïV“Ký¦[¯íçûKvéðûöê}?ã«)x}ÄqÞžKÌTzÿÛ£Xº-Ù9kýÓõúŠü‹Mö—n¿·§í1ø|?ý‡:ûÆÿž*Àðÿí+Çüg¯í5Ÿo‡ÿ°ß_oøÃŠÊ\ ÄÞžKÌLzÿÛ£úÍ.òû¿¯é?+þ¶Q_‰þ!~Ñ¿<=á/Š—ß¶OÇïhº/ÇÙ¯Bñ_ƒøÃûU|fø·à¹^dUT¼ŸÁÞ7ЧÔ-/a%ÍŒŒò[3/Ûwã_Š¿f¿Ø¿ö»ý£< §øVñ·ÀÙƒãïÆ¿i~,µÔo¼+©x«á_ŸxëÃÚ‰¬t}WAÕï|pýœüu¨xƒIðOÇïƒÿþ xÇTðÖcâ­7¿<­xÄ:‡†oµ+^Ò,üAg¤k·—5Ö©¡ë:u¾£´×ÚV£l’ÙÌù_áoø+<šGÅ_ÚŽËâÃÏxÇàWìéáK¯ø¯â7ÃO„vþÔ>xs·ïüËöHøÉñâúx‡ãß‹´Gág¿~ÆuKmGÀRÞ|Xñý„Ÿþ4Y|ð×…åÖ~|÷};þ ³ðçâWí'à­SáßÇx'öQÔ>0i?¾;jžð.¡ðÓ—¿ü ¬|Gñýψü/á_‰>#ý ~xUü-áírëÀ~-|øyð¿âÅŦ›kð³Æ¾2“Åþÿ„ŸŸÔàŸ³þ£¯ü\Õ×âçí7¦è߬üa៎>Ò>#xWNðņ_?kÚóöÂñ÷ÂohÿÃÜx;ÅŸÿm‰Þ½Õ´ëí;âF—ð—Fðïü5ãí ,Kâ»ßƒ—lÿà·ÿ ~| Ñ~'~×? ¼KðÇ>6ý§mï‚>x§â¿ìuàWÄÞ ýÿhü&ñ7àñÄÏÚçÃÿeÔ<o†<ñÃ0|Y>4ñ7Åí?Æ—?ü ñá+ø3Ç~!wŠ?àŠúÅŠo~<þÓ?´oÅ߇??gï…ÿ ¼9ã|CðF¡ñÎËÄ6¿à©²Ä?Sÿô¯†ÚW€ôŸ€ðPë-C᜺„þ#Öþ2x_WñÿÅQâ;û¿\üRú¾çþ ±à»F:Ÿ€hßÚ{á/ŒôÿŒ?µ‡Å?üBø{¬ü‹Å¾ð÷í¹ñÇãí!ð7ßð–ü ñg‡5?„^7øÁ§Û|P±“Ç^ñÅøºÛK—Áß4='ÞÒ´0¸~|Kð_Ɔ?~1|8ÖÄ?>,øÂü¯Çoshšç‚üwáý;Å>ÖÖò(.í“SÐõ[Õ·º‚˜Dâ9âŽUdå`ø[ÃÖžðLJ<)a}¯jv>ÐtYj^*ñµâïêš.Ÿo¦Û_xÅ~$¾ÔüEâ}zîd¸ÖäÂ?bû4/Ù«ÿTÏ‚èëÚ(¢€>Bý¥ä³Á>ìï|iÿ¬ûoW×µòí+ÿ%›þ óÿg{ãOý`Ûz¾½ Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( ¿:¿à Ã:¯ìAÿg{¬þŸ±Wí‘_¢µùÿ2ð¦—ã‹OØÃÂúÍ׉,´ÝSö½¿ûMÏ„:óš¿ì—ð·Œø«ö˜õ?ñš¶8útøî9ÿë×J¸î¸|'wþÛ[^ËýÃðôùiÇ{˷¼¿¼}AãÜŸ©ÿéVdçÓù×Ì+û$ü,gÅ_´Ö{ÿÆjþÙ#ÿ{ÍYöGøVqŸþÓ|rã5ÿl¡ÉéÓãÐéý+‚­\oZ]5íuwéÿ0?ð6ù_»Þ_ø ÿä½§§3ûy®?fû¯o޲Oæk_‚çò¯Ø?‚ŸòF¾Ù2ðþ¢ºU~Kë±ÀŸé§FñMïíâMßi£éZÿíû_ëkjz¯c¯è:±Ô>:ÜZ›í]Ó4ÝoH¼òÆ›«éÖ:•œÞZ[Íy§~ËþÓl¬ôí?â'íga§éö¶öV6_·oí·ieegkAkgikoûAǵ­´¤6ðAE Q¤q¢¢ªÏø“‡³,çONx1§…… ×ÄJNQ­^§2qÁ¥f«%né4kBœ\Z“¼¯t—T—óy¯5âŸüGñÁ_î¼{ðÓN>#Ô¼ªé~.ñ?‚ Ó—QÕ|{ðóI•ϼ/သ Í®x";r?¶<_¢hž¼’3[¿ž/Ìëφ4ω~ø?¨|gý¯`ø‹ãŸüCø“áoÿÃr~Þòjx/áN¿ðÃÂþ>Öµ øñ&…eýƒ®üeømcý¨êvz¶©ÿ ÚtkFÏG×®4¿@OÙ“ÁDóñ3ö½ÇýŸ·íÇÉÿćÿ|0Ò.¼?ðÖûöŠøy Þë—ˆotOþÙÿ¶G„t‹ÏkR¬úƹu¦øãΟeq¬j³ªÍ©jrÂ÷·ÒªÉu<®U믃_´ïˆøWñ—ö´ƒâŒ¼㯈ðÓ~Þ·¼ÕüðÓ[øuáÏk¶óÇû@µŒvÞÖ¾,ü;Óî⸺†îi|Qdöv÷0Á%¦SáLÂʾt¿‹_y4’× µm¤—VÒZ•íáÚ_rÿ3õÚŠüÇöUðuøû^žƒþOëöçäÿâEòjÈý”~p?ábþפôÿ“ûýº9?ø‘uŒ¸kÞ®KmR·_û€Þ¥÷/ó?Kè¯Í”ý“~¿?kÓÛþOïöé?ø‘Ÿç5q?d†Ç¯Ä/Úôôò¿·XÉÿÄÿësXË!ÆF÷©†ÒÛN¯_û‚5V/e/š_æ~Q_žIû!|2n¿kÓÎüg÷íÙ×Ôãöÿ<ñVìð¼àÂû^ÿâþÝßýÕŒ²ŒLoz”4í*î+v‡ùŸ ´WÀ‰û|-n¾>ý¯Oaÿý»ÿÇíþy«‰ûü)n¾<ý¯y8ñ°ÛÇñ?òrç±–_Z7¼éiÚSÿäv‡ùŸwÑ_ß´&‹£~ÆÞ6ý’¾(øÅ´wˆ—[ý¢¼AàOxCÇ¿¶'íIñ#Â~.ð®©û)þÓšôZ^«á¯‹þ$øAÿ³¼_០øªÆùü/>¥i©xrÏì7–~d²Øã©MÓ›„šm[X¶ÖªýRùèRi« ¢Š*QEQEQEQEQEQEQEQEQEQEòüçþL#ö!ÿ³Býš¿õLø.¾½¯¿àŸ?òa±ýšìÕÿªgÁtõíQ@!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^ÐEPEPEPEPEPEPEPEPEPEP_ŸðP?ù þÃÿöwº×þ±Oí‘_¢uùÛÿÖ?añÿW{­ëþÙìp÷üò?ûeŸú›Dηðªÿ×¹ÿé,à"\ãß“ôãýkB%Î=ù?NߟõöªÑ/OÐÃÿ­Ò´"^žÿÈŸå_Óµeøêýß—®‡†µiw,Ľ=ù?AÓóþ¾Õ£ô÷äôéÛðéùš­çïÏà?Çú×ÇÿnO‡ÿ>1ü=ý4¯†¿¾>~Ð?¼⿉ú/ºG€¯|O§|3ðdk¬øïÄz¯Åˆ¿ <¢èrjQÍ¢èOâÆ×1Ÿ²ø/Áßµd¶ß³Ä«~+¦±ºð•Ï€¾-Ýx_ÄÅ·€Eý™gmz/íç±Õtiõ=SÒõ+ΣĵÏìŸàŸßü0ñ§í=û#Ük|Gà/ÁðJeë-> þÏ7?´ éwšÏìÛwàË ø'Ä·^ Ô4;¯ø þ:þÏž)ñ¥Ïˆ–ÒÛÃ?¾ øtãi_˜_ ÿ`?ÚWá§Ä»O~Èè‹áo~Οíõ¯|PøÄÿŽ~1ø/ÿøûSx§áΛñ?À?tí/ö–msÀ? üIeð÷ö†ý·>| øåáÏj:W‡µÏÜxsâ?ÄKŸ¯?¿à¦?±íá¿Ú/ÆßhO†ƒÀ_²×ÄËŸ†ü}âx?Ã> Ó¥ŽÊÂm3ÇÚˆ5=~;+„~+ÔçÖ|9à?‰—¯§xWÇú׃¼aÿeö¹¤é)ªÝ{v§ûeþǾðß|eâÚ»ökÐü!ñCMñvµð×ÅZÇÇO…úg†þ"hÿía¾ñæ«àmr÷Åiž-Ó|eqoyâëíëPµðݬñ\k2ÙE*;x°ùn!F´q1åå¯?iN­ )*­Pœ”ªFvŠœT ¢ãJQŒb­ªRœt¶¾îéô³JÉ­z»êúŸŒçìµûLüIý¨~,þÑ?àžRüLø]ñ'Yý¬g€^$øÿð.=nmâ7ìóÿ{ø_ðnû[[?]x UñTÞ(ý“~*êž"Ðî¼E6‰ð§Uð.©ãßxËÆ^-ð×À›ÏŠ{Þýƒÿi?~Ö?³7ÄÏ|›ã¿ô/ ~Æ>øÏûPx«Çÿ õoX¯Á†>Ð~+üAð'Ä¿ü8ý°~ ø›Wñn•®]Þ|ðGƒþ6~Íÿ´2_ɨ|Z´ð¥ÿÅ_‰^ý4ð‡ü§ö+øƒûXø[ö1ø}ñïáïþ5xÇàÕïÇ x¿Âž%ðŽ¥á¨L»Ó¼7§x«J×nl5¯ˆºÿ„/o¾(iÐVׇÂoë5K}/Ârhº–±WÃÿðP=ƶ/ÇOØÃá¯ì×ûH|KñìË®üÒ~<üNð쿳~‡ð“á݇íàËOøÅüDý£<ñ;Å›á¦ÕîüQoà_…þ)ñ5ŒþÕìô¿ëW^^ójað~ÓëS«9ã.Ü=…G,Tiº’¦Õ*íSŒ§(É?g~WN2š{EÊËD­½Õ£¢OW§M·}ô?84Ù;žþÞß ì´‹ŸŠ>\xöý–4?‡_|?à½BëáÅ·Æíö‚øÑáÏ xÅÚN±ðúþOGwð—öV×¾|A~|@׿cÍgáÅ}[Bð¯ŠüW«iøSÅÿ³·‹þ0øCöz‹öPøÑâ|;øÁñ;Â?Ö ~ÑÞÿ„/ö/ñ7޼'áoãˆ7Ûh?¸úí¿ûø‹Àž'ø¥áÿÚûö]×~xèßðšüEÑþ?ü(Ôü áñö£¦xy|Qâë/ÏáÿwRÑõ}?F¶¡huKí+R´²ÜXÝG¼MûxþÃ>O IãÛ;öOðt~1ðn›ñÂâ¯Ú+ᇓŵ^÷Ãú?Žü6Ú¿Œl×\ðn«¯i𖉦ø£K7Zö¯§^é¶×ÒÞZÏ |•0Ø6£9WMÆ1÷£:I8Æ·´§Ë))ÉrÙR„”ï.U%+Èw–º~{kå®ök©øÑû>ÿÁ0|Tu¯‚z'ÿgoŠvãý©|9ãoŒßÿhO~À²xa´#öý½~x¿ÆÖ_b… ¾êÚŽ~*|pøEàŸø—ÄZ—þ1ünÓWMÔþ'ü>ð'„ü¨M¬z/ìûÿêý¢4x{ÂúÿÃox7ÄÞ0ý¿à™úÄïüBøãK»~Üß üû[øKö±øÕá1û:üOð¯Ägãx‹à/¼ká/‹_4øâÓNðׯMKMð ðÙý˜ð—íKû1xîãÇÖ^ý£~øÎóáF˜u¿Švžø½ðûÄW? ta·GWñü?ˆo%ðv˜-`šàßø‰tëO"fó|¸Ý„Þ ýª¿e߈^ð¿Œ<ûI|ñÏ„¼kãÛ…^ ñGƒþ0ü;ñ7‡|]ñJòÂ÷U´ømáoEñö™¯øúçKÓuJßÁúUÕ߈f°Óï/"ÓšÞÖyá°‘QµG;:7:mÏš1¦âÚŠmFÖVøe'­ØsK^›^×ïu÷ùî|OâOØãÖ½7Â]?Mý§µoø¿áß-tþÚVÓø½?k?'ü'¾5ñE¯Ãeð·‡5ßü¿øuàKÚÛø"óö‰Ñj‹{ãwªIâ·Žûâ—‰ÿ[¢_ÓîOùýkÄ<5ûEþÏ^,¼ð¾Ÿá_Žÿ¼MãoxëÀþ ±ðÿÅk7ž/ñ·Âû=CQø™àÿ ÛiºåÌÚÿŠ>iúN©}ã¯é)w«xFÏM¿º×í4ø,î>jËöÕý/<ñâe§ímû2]ü7øA«išÅŸˆ6ß¾Ïà†ö·«ÛxFÑ~"x®/6ƒà­[V×ï,ô=/Nñ-þ™{«ÝZé¶°Ë{

Rt©ÝÆKT›¼ï~XÝnÝÚ‹MËY4Ó›{šE;.öíýz~]©#\~~=ÿÏ¿µYŒdçð_óüëó¥?à©?±Ë~Òw³Š|MÐZÇNýŽu_Û‡Pý¤ÆŸ [öV±ø7¡üw»ýu»KÏŒ#â“mâÝâ5õ¾©úx[O²³ºŠïŰë¶óh‰ú¢êš^»¥é𿇩Xk:.±§Ùêº>±¤Þ[ê:^­¦j6ñÞXjZf¡g$Ö—öÖ“Eugyk4¶÷6óE<½ÿϹ­—Óè?©ÿëýj´KÓÛõ=Ï¿µhD½=¸S×üûûWYZþ_‹{^ ~`ÁQWýOý]ÅÀøˆßµ¦Où÷¯×Jüÿ‚¤.<9û {~×3©ÿ†Eý­ ÿ>õúá^&'ø¯Ñ~FñVI}þ¡EV (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ ù þ óÿ&ûÿÙ¡~Í_ú¦|_^×È_ðOŸù0؇þÍ öjÿÕ3àºúöŠ( ¿i_ù,ßðOŸû;ßë~ÛÕõí|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¯h¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¯ÎÿÛügZý‡‡¯í{­ÿë~Ù5ú!_ž?·èηû_Ú÷[ÿÖ'ý²k×áÿùdŸö7Ëõ6‰oáUÿ¯sÿÒYÅD¿¯ØóúVŒKœ{œvè=?_Ê«D¿Ð§óìkB%éïÀú¿çÛÞ¿¥êÊýµßÉ/ëSÆ‚Öý¿?øbÌKœ{þ€/åÒ¿2hÙö€‡öïø}ÿý—¯þøÇÚWì·â¿Ù+âÂ_Ž*ñ—Ãß kþÔ<|ÿü#âÇÞøwñcVÒµ¿øêSq¬è³xH|K¢C—oâÍ#ß'éäc?€úóúWò ÿý£?h¹|ÿ|oð/í ñËáî›û5kŸ°ìïû7è^øÁñ#á6½ðšîÿâOÁ»ŸÚ+Äö? ´?xjïK‡â—ˆ®ííü ñ»ZðÜ—Ÿ~Ë⋆~0¾øo¯ø†ÛUù|ûG†„ëS©QÆuq4ý”ã ”¥€Âb1΢rRVåÃ:IJ„§Všä”šé¦œ¤Òi^ÉÝ]>iF6ÝuwÝ=[¡¿µWü_öÙý¤¼MÆ=wÆß²_‹>1üWÿ‚x|cý€þ8ø{R´øµàoƒ? GÅÏ\ø½>=þÏZUæ™ñŸÅÇŽ<+§]Åá»Åú—„?á0þÄÓua⯠Apš•ó÷ÅŸø!Oí â?ƒ¿ðS…¾ øŸðgÄz·í]ð›þ Kð·ö|ø‡ñ/[ñ½§Šìö ðÃo üXñÆÍ3Έ®|1©|Eÿ„ µM|yãÑ­ÝÍkÿ ,ºÂðé|jÿ‚É~×?ðØ>*ý–¿e~ϵ;ÿþÝÿ ¼ šçÃO‹& ãìGðºßãßëïŒ~Ôþ*ø‡Æž6>ñ6™£ü(øUáO‡Þ8ñ¦ƒiáŠô_ëš­î_Šà½_ükáÙþ.~Ì?¾øÏà_ˆ¼oû þËþ ñ·¤j:†µ¦þÖ?µ_„'ø¥ñ2{TÖ>2|"øy{àÏ€Þ:/Ã=cÂ~"ñwÂh.þ/ø¯A“ZøÙᯠ.¡qkòØš™Yâ=£ÅTŸ´­Jnõ*9Íà+Q®éÉ9{FðØ®yÍ·ÉR­rM¸-ª”mËmÙ[ß‹]¾ÒKÑ7ªÔõˆðFßÚ_Å ý¨>é^?øLºŽ?à®·?ðUß„z­·ÅŸ_5oMâ ?û{örø·7ÿYø¯á›¥ÛÚ½ÿ‡>5ü&ø‰ãOAâK}_Ò¼'á-SCµ½>‰ðSþÿñCáÿí)û|rÔ4ٿ¾ýŸ>:ÁD~8üwøwᯉÿµÆ+í[Yý®¾xáçÃ˯xóö—Ô>$øŸâïtýcÁ^$ø³ã­zëàO‡õ=bi$þÒ¿à þ'øŸQ¼Ñ~?i=§ìyðÃáïÄß k¶ýœ?l/hŸ ¼u«Câ{Ÿ> øÓã_ÄxCĺ4;|Aq§êšÏëGíãŸÛOÁgá·ü2ïøOÿ´ÿá0ÿ…‡ÿcáGÅß쟱Â-ÿ—ü”ÿø)/üÛþï·ý¯Äßò??µ¾Çÿ/ø@³tÿøMb2Ú±©ˆ£CQáªa”¢éþùN50øúTá¥RÔ•J^Ñ6¢éÝ79'0N¥â›ŠM7åkr7ó¶î¶Çäÿüßþ ûWþÀŸ¿dOŠ0ñ_ìùñ/†ÿ°ßÆ/Ø»ãf“áÿüHÓ%Ð`ñ—íÁñ'ö¶ðÄ_…—zŸÁ§_ˆÌ4oèþ×¼âØ~®•}þ«¦øŸ\¶µ¶³¿û“áüÃ6ðSÛËöçøûð×önø·¥|p×cþÊ:—ˆ¼§ø÷âßÀïþο[Á~9ñ½ç<¶ß õÝ_Æ:g„uß êß ü]«jðxkIÔõ‹UÑ´ËHê~Þ¶WíSû.| ÿ‚xßxÿ ¬þ>~Öÿ¶?ì—û$üL·øËà-n_ øSøõàÿËã-Z×Á ¿hOâë^ñŸ‡í #KøóñÃzvœº¦‘cãˆtïÏð‡?௟¶ÕŸŠ|'ÄŸ þÊþ ðUüãþÁñÓÀÿ~-ø?Å>*Õg¹Ô Ÿí àÍC^øßã'À~›gý”—? õ½3âUεru"ñζˆòIåØOa…tërá§N¼#8©Æú²ÃÇšîíÆ•G)&Ÿ-Mbî£O~W•֪ݛÖúvÕXõ_‚ßðIïÚûà·üŸö-ýŒ<ñCàW†~?~Ïß´£üTø½ã? j¾0Óô|,×>.ük×|iàÿ†?G´ø±ðŸâ¿ð£â­¯… øŸáé*ÒáƒÄÞÓõ‹O øŽóX—å?ÙwþûY|øi­øCÇÞ;ý˜¼A­\ÿÁ?lø'>‡{¥x£âV«g¥~Ð?´íkñëã—€üo ƵðkL»±øc£xâ¶“ ø—ĶvÍãm;\_ÒôoëZ*[júÍŸ ?ிµŸÅ_ø'¦™ðÓÀ·_þ x^ÿ‚HÁDm_|fñwÄ?Úân5‡?´wÇŸÙçÁÿgŸ‹_ÿhï|aо0h ØøÿLñw~&|YŸCº“Aмáï x{MÐìô¾3ö¢ÿ‚¥~Ú¾'ÿ‚[|qø{à?‹püÔe?Ø þ½ã|oŸÆ4ÿÛãÇÄßÚóß~ ø§ÇŸ ¾1hß¼=>›§é–驸kâ{jñˆ.üøg£[ÿÁ ÿg¯ø&æ¿­ø.ûÆ·Zßí9ð_öŠømñã]ñaÒ<;k«|ñÜ~¾ð¾µâ{Ý^ÇâýŸˆõ#yð÷lÓõ?ÿÁÿiÍoöcý¥—áœß³ŸÀ/Û?âÇÏØóãïÁoiÿ´ŸíËûFéš'Že WH]/Æþ<þÔ‡Äoøƒ]j>9Ó<¦øà7ƒ,ü= Çàÿø»Tø‡a¥Úx‹Cù#áü#öÉøAñö‰ø ðãLñíñCãçü;þ •ðWᇉ~1k0|MðçÁ„_³OÃß„>9Ð~x+Ãÿÿi¯ÙkÁš}…÷ü$—Pü?ð]ÇíðÓÃÓ4ÿ\øcBñOˆäÓ<#­}ñŸþ £û`|,øuû-ÜZþο—â_ Oâ­Çwž ÖuçÕ.|ocm›7+X (Ó‚›UÚQnÒŒœihɲåqµâ¬¯ÞZ¾–½úl×éøÞ×:Oø*güÿã7íáûe_þÒÿ ¾%ü5øc¢x öð/Á¯zÿˆ¼[¤FÿµÂßÚÆçã÷‚ÿánxGøqâ?ø—ön¹ðåÜÖZ½“ßëzÕ¯­´-nÛÀ¿ðŽØ_?ô©áSâ9|9áù|ck¢Xø¹ôM&OXøfþÿVð埉$°µË_ꚦ™£jz–‰o©›¨t›ýGGÒ¯ï,ÞâïM±¸’KX¿›Øþ ³ûVÁB~%ü5ð‡Äÿ€~øYû+þØwíà¿ ]迾ü ý¦~EðRëÄ^Ýá&пm[ö ø«â¸í´É4߈zÿÃßÙ‹ökñGÀ?ëþñ¯‡uŸx8YøøýÅÿøÙñcãoü_á[ü|ø‰â/‹ÿ~|Bøíð/â—ÅkPºñ^™ñ[øeñÆz_‡üGàߊ O ükðºøhÿð´ü)­x›HÖ|E¤ø‡FÔõû¿h(·´Ê5)9¹ÓRýês“i¤Üy´Z[ƪqkK6œl‡~­ieÓKúzj~ËĽ=¿Rþ¿JЉqoÔŸóüª´KŒ{r~§ü?¥hĸǰÉéÔÿŸÐV5e¿–¯Õíýyùߦ¿åýy—?ðT± þÇãþ®æýd_ÚÓú×ëe~JÁS?ä\ýý]Ìÿ¯ì‹ûZÿ…~µ×“[øÑQYQEQEQEQEQEQEQEQEQEQEWÈ_ðOŸù0؇þÍ öjÿÕ3àºúö¾Bÿ‚|ÿÉ„~Ä?öh_³Wþ©ŸÐ×´QE|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¯kä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@~y~ß|ë¿°àÿ«½×?õ‰ÿlªý ¯ÏOÛãöÿ³½×õ‰¿lªõ²ùä¿ö6Ë¿õ2‰_áUÿ¯sÿÒYËD¾ŸAýOÿ_ëZ/L}õ=?_­V‰z{p>§¯ù÷ö­×‡sþ}kúB¬¯óü—ùÿ™åEY%÷ú’Ð¥|PßðO/ÙËTøûûM|vñ^…?Ä;/ÛÂü5ûC| ø›¡ü:øðâ«ð=6×á/üã/ë:å·‹|¦iÃOÓ­ìü]‚îííOÂÞ,Ó4èÿmÆ2søÆ´"_O þ§ÿ¯õ¯+J~OkN3䛩e~Y{9Ò“ZýªUjSšwŒ©Ôœ$œdÓ¤Ú½¯§âŸæ“õW>bñÏìoð+Åšö§ñ'ÁÞð?ÁOÚ÷S¾ÖôŸÚŸáwÁ¿Ùþçö†ð¦·®Xéš‹õ¿xÛâ—Â?‰šhÖëz¿ˆ|IãÝ'ã“á߈²üUñg‹5íßø³â]¦«¡Çáßk:®£©(t M'N²Ó4+FÒôí/IÓ¬í¾Á‰zcè?©éúýkB%ôöúÿŸc^mJhêûjn3‹|ªÊ5]OwáN¯$Y[šjŒ›PŠG3µ¯¦ÿvÚï§E²èxç†fŸÙËÁó|*»ð—À‚~¹ø‹àø!qáß…^Ñ'ø7Ä ¥øö/…Siº ´Ÿ#ñ¾šâøü"Ú:ø–ÅE¦´/mÀ޽Ö%ÏâAþOéPÐ¥]‰z{ð>ƒ¯ùö÷®I¨E5Æ VmE(«F1„n’[F1Š}#–‰%¬µÞïòþ›3Žñ§Âφÿáÿ…•ðïÀ¿¿á^øëÃÿ<ÿ Ï„t ñ+Â_kÿ„Wâ'ƒÿ·ôýCþŸxkûBÿþÿè¿b×ôo¶Ýgjßi›+ì·û3œÆ;| çã§ü5ü’?qûKù¸Ÿù¸øééñsþGîŸñPWµÄ½=øA×üû{Ö„KŸÇÀuÿ>ÕçUP“nPƒoVÜbÝ–×m~>E]÷þ¿¤™î¿aߨ³Uðg‚¾ꟲ컨ü=øokã ‡~¿øð¢óÁž°ø‡=õÏìüá{ I¢xV×Ç7:¦§qã } ÆÂO©_K­%ì—— %¯þß±_Ä;Oé¾>ý?eÏiÞðð«ÂÖ/ýŸþø–ÏÃ_ ¼'qewáo†ÞµÖ¼%{à ]éºuÎàý9-¼=£\XYO§iÖÒZÀÑý<ƒ>½>ƒüÿ*³çñýÿ_òé^mXRwN;4®¹#ðÅ%ðì’IvI-‡wÝýçÏú×ì}û&x¯ÂÞ0ð7ŠeßÙÛÄž ø‰ñ/WøÓñÁúÿÁ?†ºÇ…¼sñ‡ÄÜkßO .‹­|F7:6p|o©XÜø›ÎÒ´Ù´üËV‹Wáì«û0|Öt?|ýœ>ü$×¼1à-Ká_‡5¿†?þø WðÿÂýgÇÿµ†ú&¥á_éWšW€µo‰Z¶©ñ Sð}„Öþ¿ñÆ¥¨x²ëN—_¼¸Ô$÷Tõÿ?ýÆ­D¹Ç¿'éÛóþ¾ÕçTPº|°MuQI¥Ñ-4WÖÝì÷(ð oÙKà6¯üRø‡ð³áoÃo¿¾.øwÄ:Š¿h¿„Ÿ ¾è_d¼×íÜ/‰®|c­xÄV¾+×tQlüC§Û|EѼk᛽kL±“Ä>×l}>â¯ìSû ü$ýƒf?…?²gÀ¿øIÛáw ;^²ðåÏ5ˆ5ÿê>)ñwˆõiFOÓŸóþ}k‚¬»¿7ú/ò^†ÑVKÏSòÛþ œ1áŸØÿþÎê|ýOì‰ûZÿ‡é_¬õù9ÿQðÇìÿgw1?ì‰û[‡ó¯Ö:ófï&ß—äŠ (¢¤Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( ¾Bÿ‚|ÿÉ„~Ä?öh_³Wþ©Ÿ××µòüçþL#ö!ÿ³Býš¿õLø.€>½¢Š(ä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{_!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚ(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š+á/Û§Á¼Siû2ø›ágÂß|_»øQûG]ø÷Å~ð~½ðÓÃþ#_ ê_³Wíð¸jºtÿü{ðÛ—Ÿbñ_Ä Çwcÿ <:‘±¹º½³³»[)Ñ~í¢·Ãb*a18|U•\5zXŠNKš*¥‘©(õ\ÑW]V‚iI4öi§èôgóàŸ¶õÂã?²‡í0p?è%û+õ?÷sãÞ¬Û’`ÿ†Oý¦¸ÿ¨ì­ÿÑA_Ð=õrãÎ –õ0¿øMþK¯_Vaõj]¥÷ÿ_Ó~VþÓöèuÆdÿÚsÿAÙW¯sÿ'CþsV“öï+ŒþÉß´÷þ‚²§^çþN‹üæ¿}¨¬¥ÆÙì·©†ÿÂxÿò]zú°úµ.Òûÿ¯é¿+~ §íì«×öLý§øâÿöSüÿäé?ÎjÚ~ß±/üÚ_íEÀÀÿOý”¿ùº^¿ýzýᢲ—gRÞ¦ÿGüúõõaõj]¥÷ÿ_Ó~Vü#_ø( äþɵ·úwì£ÿÑKþy«Iÿ¶_ù´¯Ú—¦úwì¡úÿÆSŠýÒ¢²—fÒÞt:˘ôù—ìaÙýçáºÁC­þm'ö§èÿMý“ÿú*>•i?ࢶ+×öHýª}8¼ý“¸íUþ}köúŠÊ\I™ËyÑÖ×ýÊéóòc?¼üIðQ­?ÿ ûUcþ¿?dßþŠº?à£újõý‘ÿjÏN.ÿdÎãûVŸZý¯¢²–}˜KyRÖßòétù‡±‡ŸÞ~-§ü›J^¿²/í]ø]þÉ}=¿ã+Ïi?ग़Bõý‘?kÂëöJéíÿ]þx¯Ùª+)gÙo*zÛþ]®Ÿ2¹#ç÷ÿ_Ó~VülðS-øÄOÚÇ×_²OOoøÊú°ŸðS}zþȵŸQÒçöHè?îì~ÅÑYK2ÅKw mö@äŸßýMù[ñù?à§Ú õý¿k^¼âçöHééÿ'd=êÚÁQ<<½dÚ߯8¸ý‘ºzÉÙòkõÚŠÊXÊÒÝÇ[}•Ð|‘þ¾_×Íù[òIà©~Qÿ&û\äÿÓÙÿ¢Ò¦Oø*†×¯ìû]uÉÄÿ²'åÏíj?ɯÖz+)Vœ·k[tìQüôþÖµ‹~ÕGözð׆ÿg¯Ž¿ øsñÚóâ‰üOñ>óö@‡@öøóðæ;8¾üyøâ[½^ïÄŸü:–öéáÑd–cP»ºÔ-…²$ÿеVMßVEPEPEPEPEPEPEPEPEPEPEP_!Á>äÂ?bû4/Ù«ÿTÏ‚ëëÚù þ óÿ&ûÿÙ¡~Í_ú¦|@^ÑEòí+ÿ%›þ óÿg{ãOý`Ûz¾½¯„ÿmˆ^øWã¿Ø;Ç¿üqá‡ÐkßnxÏǾ%Ñ|á=ûSöý´´m3ûWÄ^!½Ó´};ûGXÔtý*Çí—ý¯R¾³±·ó.® ‰úÿøx7ìÿG½û!âJüÿæÒ€>½¢¾Bÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ëÚ+ä/øx7ìÿG½û!âJüÿæÒøx7ìÿG½û!âJüÿæÒ€>½¢¾Bÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ëÚ+ä/øx7ìÿG½û!âJüÿæÒøx7ìÿG½û!âJüÿæÒ€>½¢¾Bÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ëÚ+ä/øx7ìÿG½û!âJüÿæÒøx7ìÿG½û!âJüÿæÒ€>½¢¾Bÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ëÚ+ä/øx7ìÿG½û!âJüÿæÒøx7ìÿG½û!âJüÿæÒ€>½¢¾Bÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ëÚ+ä/øx7ìÿG½û!âJüÿæÒøx7ìÿG½û!âJüÿæÒ€>½¢¾Bÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ëÚ+ä/øx7ìÿG½û!âJüÿæÒøx7ìÿG½û!âJüÿæÒ€>½¢¾Bÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ëÚ+ä/øx7ìÿG½û!âJüÿæÒøx7ìÿG½û!âJüÿæÒ€>½¢¾Bÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ëÚ+ä/øx7ìÿG½û!âJüÿæÒøx7ìÿG½û!âJüÿæÒ€>½¢¾Bÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ëÚ+ä/øx7ìÿG½û!âJüÿæÒøx7ìÿG½û!âJüÿæÒ€>½¢¾Bÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ëÚ+ä/øx7ìÿG½û!âJüÿæÒøx7ìÿG½û!âJüÿæÒ€>½¢¾Bÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ëÚ+ä/øx7ìÿG½û!âJüÿæÒøx7ìÿG½û!âJüÿæÒ€>½¢¾Bÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ëÚ+ä/øx7ìÿG½û!âJüÿæÒøx7ìÿG½û!âJüÿæÒ€>½¢¾Bÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ëÚ+ä/øx7ìÿG½û!âJüÿæÒøx7ìÿG½û!âJüÿæÒ€>½¢¾Bÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ëÚ+ä/øx7ìÿG½û!âJüÿæÒøx7ìÿG½û!âJüÿæÒ€>½¢¾Bÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ëÚ+ä/øx7ìÿG½û!âJüÿæÒøx7ìÿG½û!âJüÿæÒ€>½¢¾Bÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ëÚ+ä/øx7ìÿG½û!âJüÿæÒøx7ìÿG½û!âJüÿæÒ€>½¢¾Bÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ÿ‡ƒ~Áô{ß²þ$¯ÁŸþm(ëÚù þ óÿ&ûÿÙ¡~Í_ú¦|Gü<öÿ£Þý¿ñ%~ ÿóiGüçþL#ö!ÿ³Býš¿õLø.€>½¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(ÿÙbuteo-syncfw-0.11.10/doc/src/Buteo_FW_-_Plugin_Manager.jpg000066400000000000000000001554451477124122200232130ustar00rootroot00000000000000ÿØÿàJFIFHHÿÛCÿÛCÿÀ;„"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?þà¾:|t‹àŒ_ ­­¾|Gø»â‹¿åøaàŸü0—ᥦ¿}¯Ú|4øñgP¼¼Ô>,üGøWàÝ;GÓ¼ð¯ÅwW^+KÙ¯SO°°Óï'¼?8ÿ†•øÍÿHùý¯ð´ý‚?ú7¨ý¥ä³Á>ìï|iÿ¬ûoWçïíáÿwø‰û!þÕ?¿f‡¿²F‹ñÑ~ÿÁ3|aÿ;øã}gö‹ÿ…Bmþ|8ø­â_‡ž<ð^‰áŸøR_Æ­â[ #Ãâ?Ý7ˆ,¡×õ+ø<9ue¡ÚE?‰”ô þWã7ý#çö½ÿÂÓöÿèÞ£þWã7ý#çö½ÿÂÓöÿèÞ¯ÊÁÀ_ 4±Ã/ |´ñ_€loþÅ>Ó¼AÅôÿ…ÇðÒëöñøg¦üKø)â¿|ðçÃOxÃ’ÛU]óJøñ÷á·Æ=vïCñgŠ|ðÆ? 4k?k?"þØð[¿ÚçâOüÛö¯ÿ‚ƒ~É¿uÏٓᾤøZý’?k‹ßˆ~/Ýø«ì·ÿ†ÿeoˆ>ø…û>øïÁo©|8ñî­á½3ÄÞ!ÒídðÏÅχ«áZÜEñ3Gøƒ§Å¡Ú€B?ðÒ¿¿é?µïþŸ°GÿFõðÒ¿¿é?µïþŸ°GÿFõ~hø7þ á²·ÄÚÛÀß<¤|<ƒöÆÔcÏ‚šÆ·ÃŸxóÅ:FŸ};ê~h¾!üYøwâÏê¶VÚ7ߟ ¾~Ñ|Mÿ ‡5ÝsÁ>ðÓxŸWð¯Í~ÿƒ‘“âçÁ?|QøûßøËÄ·ðNOÚkþ _ñ“Àþ;ý ì>Øü5ø1û2üeñ÷ìý¯èñ}ÁÿÿÂÏñ_ˆþ'|6ñ Ó`ŸÃžŽÏÁ‹câ)Ò}^ëþXÀ?p¿á¥~3Ò>kßü-?`þê?á¥~3Ò>kßü-?`þêü`Ñÿàâ=KÇ$ðw‡þÿÁ=¿hŽ×ZWì÷û |xý¢ô‚:ÇŒ^5ø_ÿ Çð§Âÿt/ ü:±øOû,øçÀ<†¿þÀ5O…þ6êÿ¶ï…> þÌþ>øûðÞçã…Ïìóð;â×Ãï|1øùû*þÏ·^ ñ7‹~ü øMðçÄ? þ$êµg‡|Eá3¥üÖ>4Øë|qáh¿üYãO|?Ò<'á'üÇö‰ø·ûO6‡qðá—þ |ýž¿oo~ÐÚ=ׯÏxsÄçÄ/Ù³Â?°_Åï x“á½ïí-û(þÌ—6>‡ÀÿµßÃé:ÏÆÿñ,?¾*üA×õ-'Áÿþk´êßü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_›Zÿü+â¿ÄÙ þ ¯¬h_¬>~П±ì/âïÚCáÿŠü7©üCø“àˉügð¯ö›¿øSâ]"OÚ#ö^ýωµŸxïötñ(ñ-Ãoˆo¦‡OÓt¿ˆ5•¥û|TøÑ{ñ›âíÄ5sá~…ã/…ž…á jzG†~øoVšÃ_²¼ÔaÒ|I©xªbt ý­MfÛ¯üø‹ð×á–‰àïøsÁ_~$ü_Ó5ÛÏx³QžÛ_±Ô4ï‹¿<©ËðØøBÒ|)ã/ i:“àñ6ã]RWÁ¶Pxo_ѼCm¡|6¾økN-(ɸÚIµiFRVm>h¦åu¢š‹jÒW‹LI¦ÚÖñ²wŒ’Õ_FÒRßWÒz;4Ñô×ü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_>ýª¿ntø‹ÄÿøÓö×o„wßðUoŠß²µÏ‰üUàø&¥ûÚ|"µÿ‚—|Cý¼ àIø{…ÿ"±ñ=Χø?á/‡üsâ¸n,lþ9]Åã_ÜøƒáCMy©úÿíÿSý¡åø ÿÎñ·ìÍð3á­·ÿoφŸ²·í«Ù|Pøá«hÂï|Gý­?à™u߃öú¦û:üK¸ñ½ÇÄ ¿o?áî§ñ#þ¯Þ|7ðž•â‹>𯌼_¤ø{᮫#?Mÿá¥~3Ò>kßü-?`þê?á¥~3Ò>kßü-?`þêø;Pÿ‚Ééº/įÚ-WöUøñuû8~ÏQþ×ö~"ý ü7ð¿ö–Ôt¨µŸØ“Aø¥¨üaºñˆµïه¿²¦›àÍWÄ|uðëáæ³áÛ â'Œu߈2xWÂþ øuá-CYÕ#ðïÜ¿³Wí%ñGâ‡ÄOŠÿ¾>|Ð~|iøWào‚ÿo<9à¿‹ããƒu_…ÿ/¾,h~ÔbñÄŸ~ÝZxÏNñWÀߊ>ñÏ„¿á»Ò´[Ý OÔ¼7ãx_Óµ†¹ÿ +ñ›þ‘óû^ÿáiûôoQÿ +ñ›þ‘óû^ÿáiûôoWã—ìIû@þܺÇì»û þÚß~7~Úéá¯YþÃzŸÆ~Ò>ÿ‚Uéÿ³¯Æ ~Øþð‰¾ ü —öCøvŸ´WÃßü2ñÇÄOüLÓ¦øñ↚îðúK;ø{_¿‹Æpø_Ò¬¿mÚ¯þŸ‰W:׼?M¸ýºlüãOø&n¥‚ü ý¡ðÏÀŸ¿jö_Ó¥]:OKkã¹ü!ðŸâ÷ì«ûY_ˆVž0ºTø©ñ3Ú”wþÚÅ êü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ^»¬þп¼9ñ—³Ÿˆ~8üпh?ø~çÅžø¬üKð^—ñ—Æž³ƒÄ×Wž&ðŸÃ íjxÃö¶Þ ñÅγ£èwštxOÄÓKr±è:£Zþ9k´ŸíK¬~ßÿ´?Ão†ß¿j¯\ü+ý»ÿe/ƒþýô?Ù3ÃZçìcaû,øÏömý‰¾)þÑ^.ø™ûXÛþË2_øC⧆4¯Œ¾"økÃگ텥xšã[Ó>xn×áeÿ†ÿ‚ƒÁ8fmNøàþÎþ Òü ®xûÀ?·&—ãˆ~Ñõj'" ðÆO |Yñ-Ïþ~ŽÿÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕó—ìMÿø¡ûPj¿³4¿f]à^ûa~Å7?¶ÇÀKßWã%üžðÅçìõaãß |JÒdøIðÂ/k–ûO|+Õü y j¾<´ñw‡.uëEðçÄš3xRçóã—üëöÍø}ûÁC5 Å~ÿ†ŽðÇÛûRý“üsqà?Ï¡x?öwý—>.þÞ¶3hþ#ðÜ–ƒDñoŠ< à?ø'¿Ä½þ]ZÒky5Š¿Š­õZù£ñ0î?ü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_›¿´wü£QýøÃãGVñ_ÿc¿Ù×öƒý¢5xëTпg/þÏ 4ˆ:Gì×ãÿèÖzOí+ñ/㟄µi-añ÷ìûáí[@ø‰¡x×þ"ÁK>3ü$Ö>(|5ø•û*øÂÿ|!ìíâxh~Ôºÿ€ü*ýŽÇŒçý™fOŸ´Ÿí;à¯ün×>_ø&Ãá/ÆŸÚ¿ö~Ó¼-ðí|mð<_ã‹ÿüAýŒ>:kZœ<ð?Åøq¡iÚ–·à»‰ºÆŸðŠ}ˆßðZM_Áº¿ík©xsö3øË㯄Ÿ³7…?mél¾+[xKöšðÿ„|Gñ öðgÅÝkâãoŠÇ숿²Ï>øÃÇ<_ð£Á~=ð_í9ñ›Æ­ã{ß éZïÁÝ#RÔ5=/Eýÿ†•øÍÿHùý¯ð´ý‚?ú7«ñÇí£ã¿‡-—ˆ|gû~׺6¨ø¿áï?á&ý†µ;ÅŸ<}ᯆҾϥ~ÚW×QÿoxçÆÐþÝ, ¦éÚ?ÚzÍæ£Ú_j¿MéüJñßÂJÇÆ¾|FñN‡âÍ*=CáŠ(øaâßé>2[¿ Ý[|Jð¦œ5þjxSâ—Žþ8ÿÁ'ÿà_þ)k¿ð”|MøÀ¿ðF?Š_|Mý™£èŸð‘xïâí!ûx³ÅÚïö7‡tý#ÃúGö¿ˆ5}CPþÌд­3G°ûGÙtÍ>ÊÊ(-¢úÿþWã7ý#çö½ÿÂÓöÿèÞ£þWã7ý#çö½ÿÂÓöÿèÞ¯¯h ¿á¥~3Ò>kßü-?`þê?á¥~3Ò>kßü-?`þêúöŠù þWã7ý#çö½ÿÂÓöÿèÞ£þWã7ý#çö½ÿÂÓöÿèÞ¯¯h ¿á¥~3Ò>kßü-?`þê?á¥~3Ò>kßü-?`þêúöŠù þWã7ý#çö½ÿÂÓöÿèÞ£þWã7ý#çö½ÿÂÓöÿèÞ¯¯h ¿á¥~3Ò>kßü-?`þê?á¥~3Ò>kßü-?`þêúöŠù þWã7ý#çö½ÿÂÓöÿèÞ£þWã7ý#çö½ÿÂÓöÿèÞ¯¯h ´Ú³Ä?ð±~|<ø‡û(~Ñß¿árø¿[ðƒ¼cãÝgöX×<'ÿ f‡ð¿â'ÅÙ4­V?„_´ßÅØý»Â ¼Zö7Éá+7ûJÞÎÆúòËí°Í_^×È_´¯ü–oø'Ïýï?õ‚?mêúö€ (¢€>Bý¥ä³Á>ìï|iÿ¬ûoWÈ´·üãà‡í}û}ßþØß´‰õŸü7Õ¿`ôý‰uÙËH»ø‹ðå5ˆÇÇOã-׎õ‹Ÿ >,x7_Õ¼9¬iέðïÄõ \ø_ÄÚ>¡wqâWS°™ü>~ý´l¼}¨øïö³øaâ_x?Ç3~×¾&þÃñ|­|GðžåþÃ_¶”ºŸö¯ƒ<=ñ á^±¬ý¯GMBÆÇì~=гµ+«=VãûRÖÆ}Qëÿá ý½ÿèåd/üBŒßý0jñÿÿÁ$?àþ5øÓáÚXý›t{?оñ×ì×ñ3ÃZÇ„¼wñ_À>µñ×ì}£ê~ýš*ø—·¿?i/‡¿¶‹®l~$ü^Ðdöø[¦G£x/ãgƒ¿áñþ–¿ ðGÇïøßâ7Å gÂßôÚ—àÏÄOê~ ñ?5Z/~ÒÞ!ý¥>øYÑü;¯xKà{}jë^ýfÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨ÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨ç'þÓÿû_|ÑügðUþ êþ þÎÿXÕüqñÃú_ÅïþËzW‡´ÿƒ#ö‚øqàøgáWÇy¼wá/YÐmþ,ø#Æ:~‰¨ÁöE¥•µ½µ¼_¡~øãÂω>)h_ð”|2øÁðÿÆ_ ~"øgûOXÑ?á"ð'Äj^ñv…ý³áÝCHñ‘ý¯áý_PÓÿ´ô-WLÖ,>Ñö­3P²½Š ˜¼ þ¿ÛßþŽWöBÿÄ!øÍÿÓ£þ¿ÛßþŽWöBÿÄ!øÍÿÓ SJÿ‚n~Èg…>(øRïÀž?ñ‹üeÿ„|@ñ÷ÅÚ+ö“ø½ñ¿QO…>$±ñ¯Â¨4OÚâŸÅÏ|wðM·ÂïiÖž;ømkàˆÞ¶ðŽÖoøN-#ÅWz´øšüÛöðÜúÍÕ‡ÁínòãÅ+ñ¾/OâO9ð&…ñkì‘üX†_Mèßð…þÞÿôr¿²þ!Æoþ˜5ð…þÞÿôr¿²þ!Æoþ˜5x׋¿à—ÿ³ÉýŸÿmOƒß›Æ?2x?áη¯]ünø³â_ø¶O†_ð¼|i.“g/‹ômCRÐIð\þ ´ðï†ü%oáß\ý©&Öü?û,XÜ|Kñ/…uèž*ý™fñÿŒ4= ïÀ^ ¾Öôߎ _Å^%Ñü5âxâûÁ~º¿†ÿTÓô-oÇž0»ðæðéú—‹¼A5œÚÍÝŸøBÿoú9_Ù ÿ‡ã7ÿLøBÿoú9_Ù ÿ‡ã7ÿL€?•ÃàsÅך§Ž>#XxcÇ_|kேº¯Æ/|TOø%guù¾%é~5ý˜ü#⨾Þ\jzÍ–›eðCÃí|5£|N}Á¦h#éÃSÔoošúCë÷’ÜÝZ—ýaÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨ÿ„/ö÷ÿ£•ý¿ñ~3ôÁ«ITæMrSÜ]ã5ËK'}¥ñIuŸ½¡*6·½'dÖ®÷»½ßv¶O¢Ðç|1ÿêý”¼%ñO‰ºW†~+^j‡ãŒÿh _x£ö¡ý©|oð+GøÓãÿˆ#ø¯â‰>ýšüiñŸ_ýž<1â‰þ'x·Ä4«ÿü/Òÿáñ†«qâ &ª,71y¥§üþ ÷§›‡Ó¾ x§NœxI<áûÍ;ö†ý¦,5‡>²øÅð—öðß‚¾ ê6Ÿ¡¾øá‡ß¾ü0øƒð£Ã®<£|!×|=tß ì¼#iâoZëOÅû¯ÛßáOÂ_Š?áþÈZ÷ü+o‡^6ñ÷öü1Æm/ûkþï j~"þÉþÓÿ†úÔ³¿´³¾Çöÿìûÿ±ùßhû×—ä?Ë#â÷üºk?ðM¯ü6ÿ´ÿ?Oø»Õèåù&gšÂ¥LÛ”Ô*KÛáérÎK™+W­M»­o×ÌkbðøwÖ©Èä®—$åu{^ðŒ’×½»ì}Ð?`¯Ù5þ'xÇâÝçÂtÕüMãÆñõLjô Æß¼Eð’M_â·‡'ðÅ?hßußê?|-㟉¾¼Ôü;ñǾøw£xÓÇ>·â?Åö«mâ-r=Cµýžÿe²å‰lþ ø_^Ògñ„šü$ºÿ>%|QøÁã=VÃÂZcè¾ ðìÞ;øÅã?øÒ/ø#H–çMð?íµè|à»ÝBÛÂú“¡z·âÇüôÖ¿à›ÃÚƒÿžåJ¿à°­Ó[ÿ‚k~?jéñl×sá. [àcÿ…¸þj1Y– íYÿàªßü¬ýÔ?d¯ÙëUý”¢ýˆ5/‡6÷ß²ì?´¿Ù¾/…·^!ñtÑ'ÁÁ¶ŸôŸ¿ˆ¼ý?–Z}¯ŠŠ¿á2ŠêÚi|Cý¸ƒR®ƒÆ³‡Àïk_ðÞ«¤þk/Ä¿ø,;t×?àšƒŒóðçö¢ÿçµÖ£´øãÿ9Ð,ðŽ…ý‘âŽ?4ߊ_µí=bÿþé >|Óõß²êz…í–‰ö†_þxgû3öÚF/ü#¿Û3éòøƒW×u]OÀ¿á ý½ÿèåd/üBŒßý0j?á ý½ÿèåd/üBŒßý0jðŽÃÊtÏø%ì¢ê? 5'àŽ§§'Á¹?f›ï‡Ú§Æpø;Kñ'ì{gà-'öpñþ­àDø <㊟ ü3ðÇÀ¾´øµã=Ä;ê_þ0ë ¼tºU¥Â¯‡ÿ¼ñÃà÷…<o£þÔkÿ -ÄŸ¿kÚ/V:W>8þÎ_ |UàO†_ /~üðô­ñðrúÓâgŠm®>×ÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨ÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨¾øqû.| øIÿ þ÷¿áÿ†_ýŸõŸÙsà_üTÞ1ÕáøâøR¿Úþÿ‰ßˆu/øI¾×ÿ ïðwþ*oÂAãøCÿÑ|Cü$)þÛóÏþÀŸ²'‹¼1âø“àÖ›«h^0ðí‘àMz+øéu ü+ÿø…wñOöºÒì5Ø|Q½¡Ÿ‹¾:¿¾Ö%¿Ðµ=7RðE½Ý·ðÞóÁþšM)¥ÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨ÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨Ë|yÿ§ý„>&ëþ/ñ޾ ëÞ!¸ñµÿÇWUÑ®~9þÐ–Þ Òµ?Ú‹á¿ÅO…´^¥à_‡–m<ðÊÿã_ƒ¾5üLÿ…“yðãÃ~¸ñ_‹|FŸµY.>"è^ñV‘ì?¿b?Ù³ãï‹õˆ|âI¾!êÂ6/ˆ>ø¹ñ“á't>Íñåþ\x3ÆŸ > xÄÞÕt[_Úãk~ Õìï|iÿ¬ûoW×´QEQEQEQEQEQEó·í}ÿ&™ûPÿÙ»|lÿÕkâjð¸×Ó·ëþ{§í}ÿ&™ûPÿÙ»|lÿÕkâjø¦=ö‹íñOà¯ø°^:<Ÿû¹AþM~£ÀM,?T›ÅSµïÿ>•öOcçóãQÒÿ»úYî1¯OoÔÿž*¿öôäûŸóú ðÈü?ûFŽŸ~ ñëðÇgŸüIaÓü*ì~ý¤;|Vø"1Ï?³÷?‡í2?ȯ°¨ÓûqWÑi=ºí?ò<Øè­gç¶ÿyî±§oÄÿ‡ù÷5ñ—í¼1ែ^§ãˆÏþOX¯eÃß´—ýã¹Ïì÷ãÓôþ2l~_ZóŸŠ_>8|`±ð¶Ÿâ_Œÿ l!ð—Š¿á1ÓŸCøâëifÔ‡…üQá!ó_þÑš’Kcý›âÝF*Ý-®>Ý ”Ÿjò"žÚçÊÇSöØLeT§Ï[ Š¡M?h“l=Jq»TÝ—4•ÞÉjmEòÔ§6¥hU§7n]£R}uÑ7nöî~¿Q_'‰¿kFÇü]ÙØgþ­ÃâWO_ù:ÿóÅN¾ ý­þjŸìì9Çü›wÄ£ÿ¿aÚ¿4|5˜Gz˜U¿ü¼©ÓÒ‰ï¬}´jà1ÿ䵨¯ŒWYý¬ÈÏü-_ÙØݶüJÿ豩ÓRý­þj¿ìì?îÚþ%žOoù;!øÖo‡ñ‹zØ_ü·ÿ()c)½£Sî‡ÿ&}E|~/?k2@ÿ…µû;sÿVÕñ+ÿ¢Ò¦þÖdgþçìì3ÿVÑñ+ÿ¢Ö³y&%o_ ÿWÿæq¬TŠ>èòg×TWÉ‹ÿ hßóW¿gaÓþmŸâQäöÿ“¶©Ä?µ™8ÿ…Áû;â3|Jÿ踬ÞSUo‰Â¯ž'ÿ™ŠúÄçÝOü§ÿË«(¯––Ëö´lÅâý†êÙ>%=äîEjþÍ_n~-økÅZw‰õoê?¾üBø—àOÇá? x‡Âþ_øD¾+|Bð7†5[ 7ÄÏŠ$¶ÿ„‹@ðe¾±ucoâïÿgÞÜ\ÛËyù0¯=|èRöÞÚXª¦Õ?mÌ¥R5%ÕJ4Õš¥=Sm;i©p¬§%YŸÊK›–ÍEÅ?†RÙÉoçØúBŠ(®#P¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(ä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{_!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚ(¢Šù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^×È_´¯ü–oø'Ïýï?õ‚?mêúö€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€>vý¯¿äÓ?jû7oŸú­|M^7ôÇn¹ÿ?çŠöOÚûþM3ö¡ÿ³vøÙÿª×ÄÕäq®?¹=Ͻ~—ÀîØrïŠþ™Ï 5W­Eö¦þþfÿðGŸÙ/â/Æ_Ù7þ ñCösý“×à—~þÔþ:ø±ûEÁ@N§ðÂOñ»ö}ðÇÆŽšGŒ¾¬ø‰«~Ñ?¿á/ÓæÐ>5¿Å¿‡ðÿ…Ç‚6è7·¾¹°Õ®~¦ý•?k²ü4wü+m ÀÚàý§¿àî‹ß±ÿÿá8Ó5ýLhÿ >5ŸkžþÁñ7†¿³üs`|5`,ñ^»ãÅAãwľ(Ô>ßâëz§üM5»ÓiöÑeeöm:ÚÒÒÔ?àš_±F³à¿ˆÞ¿ø,O‡>*~Õš¯íÃãiñâÖ›âý«õ«½6ú÷ãO„Ùi¤éVš–£ß\2ªøjEB´#ˆ¥B1«.nX:±ÃP Ý.J÷%ìš•JöÒŒ”¦æÒŠÅâaRu9á' NñI^\®¤¥ï^OUÌ­ùSÑY=.'ÿ‚À~ÑðëãBéžø&~)KÿêÖÿà?õKÍÇmðû@ð„ÚχbðÏÅoŒ:-·Ä/øH<]âO®¹ƒüKà-#UÕ'ÐŽŸ¥hÖð^E}óOÄ/ÛÏâ—í/áOÙÞ_ÚàwìÛâï| ÿƒ¦þ~Â^‹ÂÏñçEðǃáø_¦ß6ƒñ§Ã—7Æê^$ø¯áýW_ñè÷>5‹UøC¬i÷6ïâO‚ÓÅÁúùñgþ “ð{߳狾 ~˳¿À«ÍâGíYmûY|Sømñ“â‡í àÝÅ.£°›XñÃ?Œÿõ¯üDýœ¾$Úk^ð&¥áOø¾ ð‡ô½\ðÖŸðòÍ|W>µ¦yçì=ÿ~økð‡àî¡áoÚgÂÞñŠ&ÿ‚–x»þ ðóÁÿ E·“…\>"¦B½XÔöܪ-ÊMF)§4Û‚qM%Ê’kvìÛn¡:q©Np‹-ï{oeg½ž»·m´º?‰ŸØ«ã߈¿mø.?ü[öÖÖ®5{‹ü?øßû+cW•ŒÐøãöqÿ‚\x+âgÆÛhíåD¸Œiÿjo‰D·ª—Óé’\4VצúÎÛöïölÿ‚Å|kø»ûtþÆÿ$Ѿ|Jýš?m»ÏÛ@ðÆ/†Ÿ þ(xÃZ/‹¿dÏ_ø«Å6? ¾.|Dø±«_~ÔžÓu-2ëÀúïÄ‘û6~οÖä‡PðlZüVšÞ•¤~Ÿü1ÿ‚cþÃüoðSâ7Ã_ñìïãßÚ'âoÁÝSOñÏÄùað‡ŽjýÓÿõ£¤^øÚëDññ¾‡ac¥[éž)Óµ½Á¶VvøOðÂ[Ä#áüköøñgá7ÆÏ…_.<#ñ àFµñ^ø3soñsã–§áO…÷´Í_Iø³gà†Ú߼Oᯇ|;âË]\˜øOMðŒÑ5m_Q×|9£èúÝíÆ¡' 6.’þ-;ÔÄ*ÕZ”Ó’qÃ*‰.K4Õ:ñQ}%NJI©[¥Î›KÝ–”ù"šZ|m_]-xê»5koùGÿêÿ‚Ê~Ø_´¯Š?à‘oñãÀ³]§ƒà¨ÿ‚€Y›o„Þø¡áï|:ñOìM©xžòÛÄ1ßx¿â_t»¯xßGÑâðýǃäÓ®õ-;T±ŸÅñxáí5¸¼ z7ì×ÿøÙñöíý>¾ðâ_ìÍûo^~Ø~øñáŸÂ¿Š>ðÖ‹âÿÙ+Á÷þ+ñMÂß‹Ÿ~-j×ÿµ/ƒtÝKLºð6»ñ(~Í_³w‡5 qâÔ<¿¦¹¥hÿ¨ÿà˜ÿ°ÿÀ¡û"…¿á?°ü/Ïøe?ø¹?u¿øUðÔÚ_ð¼¿äcñö¯ÿ Çü'Úúü”øKÿáûOüQÿðù0yXß?à’¿ðOÿ¾üoøSðãÂ?þëuÿ‚÷6ÿ~9ê~ø]{ñ÷KÕôŸ‹V^øk­üLÔþøsþ+µñ¹;xKMðŒÑ5m_R×¼7£húÝ寡'4¡‹Š¦¥Z2´ äù¥v”0ê¢^âMJQ®ÕÒvœÓMF¢é¶Ú•šZu¼¹^ý‚m=m%g»ü¤ýŒÿà³µÿí3ûAÁ+#×~þÍÞýš?à§~+ÿ‚ÉáÍ'KÑ~'OñßᯃbÝ+Æ–ÞÒ5ív÷âð=ÇŠ5Í{ÂÖŸ‰5~Ôž9±¾ý™þ7?Æí'Â~"ÒcƒsøgþïÄ{k_xš)|Uø«£7|W¬ÉÒ.¥y©?êoÂø&¿ìMð3ã-¿ÇÏ…ŸôÏ |KÓ.~"ßxNëþˆÚ烾_|^¹‚ï⦡ðwágˆüa¬|+ø/¨|GšÚ?øM¯þx/Á·~'…¦µÖf»¶¸¸Š^íݹæô’æ¿ÚŠ+µîè¹£UrÚÞ󳲌_#vKV¬­²w–úöåw×mV­<Ál_Úþ û |5ý²¿i$ø ¢Íñ¢_'ƒ¼ðCÀ?|%…¡øqñsâ‡ÂýzãĺÇ>-üL¿ˆÁúMþ“g¥Ùø|hK¤×—ºéÖ-m¼?/=ý±ÿìá>%êõø÷_@þ˲÷ÀÏØÏàgÿfÏÙ¯Àÿð­þ ü8ÿ„˜x/Áð’øÃÆØ£Æ0ñ|Gÿ|AâŸj?Ú>,ñF½«ÄÛ]¾û'Û¾Ãaö]6ÚÎÎßçÿØþGÿÛ ÕÂüKÿÕíñî²­ìúŠNòX¬3îý†57ói•{xÙY{*¶_öýôžŠ(¯$é (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€>Bý¥ä³Á>ìï|iÿ¬ûoW×µòí+ÿ%›þ óÿg{ãOý`Ûz¾½ Š( ¿i_ù,ßðOŸû;ßë~ÛÕõí|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¯h¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(çoÚûþM3ö¡ÿ³vøÙÿª×ÄÕåQ¯OoÔÿž*õ_ÚûþM3ö¡ÿ³vøÙÿª×ÄÕæq.?>¤õÿ>þÕú?»`q«¾*?úf<<×ø´¿ëÛÿÒ™4kŒ{r~¿çùV„kЩúúwÿëàÔ.?çÿÖÿ ¿zýO×°ü?ÏZúš“ìü—êÿ­v<ø-oý_G·õѓƽúŸ§§oþ±&¯"ð=Oòíþ5 Iëõ?ПJ»äçðúzŸóî+Ϋ?»o—Wóü½ àµþ»§ë§ùkfX‰:c¿éÜÿŸOz½äA€>½¿ÏÒ¡p:{§ÿ_üõ«Ñ.1ù}IëþÿJóªJÿ?Á-¿#¢ ^–_‡oëÉ®¥ˆ×‡óïíW"LòÈôü—5 /AØuÿ>殨À÷?çç¾k‚¤¯=§_ëÌÙ+´»’¢äçÓ§×üÿJÐp3éÀþ§üûÕxS§ùç¹íÓüô«è¼ØŸóø×V{ëÿ ¿ÏüѲVVìMôÏÔÿAÓôúÕè×8õ=ýùÿ †5èÔý=;õ‰5z5Ïãúþ¿åÒ¼ê’îüßËoësX.¿/ËúûÉã^˜êxOò+óóöãâí’=?ho‰Ÿú½¾=×è\kŸÇì_óí_ž¿°Ol¿û8‰¿ú½þ=×-Y__¿ÖðÓ÷8Û#Hÿõî¯þ•Gîòï¯d~’ÑEæEPEPEPEPEPEPEPÈ_´¯ü–oø'Ïýï?õ‚?mêúö¾Bý¥ä³Á>ìï|iÿ¬ûoW×´QEòí+ÿ%›þ óÿg{ãOý`Ûz¾½¯¿i_ù,ßðOŸû;ßë~ÛÕõíQEQEQEQEQEQE|íû_ɦ~Ô?önß?õZøš¼ê5éíúŸóÏå_^ë>“â'TÐ5ý/N×4-sN¾Ñõ­X±¶ÔôcIÔí¥²Ô´½SM½Š{=CNÔ,çšÒúÆî­®í¦– â’)ž1ÿ‚h~Éž0ñ&£â?øWÚO…¿´~ÇÿxOᮉỲXZØÄ»Kÿ„ëì¿jû/Û/?~þuýÅÕÇËçl_¨È³êV½´kTujªŠTÜ,—$`ÓS”uÓ§#ÏÆ`牜%Æ*1åjW»Õ½,Ÿs½qnO×üÿ*Ðzõ?_Nÿý|ð¿øuWì™ÿBö£ÿ‚Ÿ†¿üîëÄ_ðLÏØ·Â:±âŸ@žðׇ´ÛÍg^×õ»…š^£i:tu©jzç€!´²±³·ŽIînn%ŽbFwuPMzòâÜ¿æ»iEÛ¿ü½G2Ëj¥nzù6¿ù)ôÚ/AÜõÿ¿à*ükŸÀSþ}ëåøuWì™ÿBö£ÿ‚Ÿ†¿üîèÿ‡U~ÉŸô/j?ø)økÿÎî¹åŸ`¥s¯ÿNémÛýá,%T·§ëÍ/þWåý3븗¹úŸ§aøÿ¥\rAõõü?ÏJüÎý›àŸŸ²ßÇÙçàgÆ]_Á2èZÇÅO„þ ë:§ü;¹Óô]gÅÞÒuÝ_HÓîn~‹‹›3R¾º²³¹˜y·ÐE3üÎkÚ¿áÕ_²gý Úþ ~ÿó»®yæø)_LR¿ý9¤ÿ÷eoú—=Xßøoþß—ÿ+>×p3ø§ÿ\ÿžjìiëõ=:ú}?úþµðßü:«öLÿ¡{QÿÁOÃ_þwu•¬Á$?cÝvÞÞÛPðî¹åÚê:v«nö1øI¹Ž÷K»ŠöÕ…æ•àk+³KŠòͦ6z•”—v£Õ…ÕÍ´¼ÓÇàå{K¯GB—çõ§ùkäW±«ÿNÿð9ò³ô5íëÉöçõ5~5éïúóÛé_ŸðK/ÙÒ ®®ô[»[[h¤žâæãMøe ðD†Ifši>¬qE+<’Hʈ€³57ü:«öLÿ¡{QÿÁOÃ_þwuÏ,F_ò÷¯¿û-—üÆrU_fŸþ —ÿ*?D#_×ôÿ?Ê¿;¿`®>!~ÙƒÓöˆø›ÿ«ßãÝ/ü:«öLÿ¡{QÿÁOÃ_þwuõÀ?Ù§áÏìßc¯iŸ±›âì¿?OÔ…öTÚÍÄ_Ù–šƒ¡[Û}¦ã]¾ž÷|syÓ•|·34ØU«‡ú¼éRiÊ¥z5©F£Ò†"-'EfÛu••’J/W¢*1Ÿ:””Q”}Ù96äéôp´†º»¶}EW °QEQEQEQEQEQEQEòí+ÿ%›þ óÿg{ãOý`Ûz¾½¯¿i_ù,ßðOŸû;ßë~ÛÕõíQE|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¯kä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{@Q@Q@Q@Q@Q@Q@W–|`øËà_þoxæþíRëP´Ð|1á­Æ}sÆ~=ñv¨Z=Áð½–íKÅ-×nçi6‘¥Æ£¨Oa¤Xê ©×Ç—¿üuñËâ]׉ÿhs¦Û| ð‹šãáÀ-Q¯‡üS¨øoTè_þ7Þ‹h!ñ^²olíõÿ‡ IàÿŸ°xƒÄQøŸÇÖÚMׂþøy­xÇÄ^ Ðußx6‡¾,Õme¾Ôüˆí|Y/†£žîáôÍ3Q׬,¬tËÍr$ØŸŤ íO×_QÓtmsÄZ]¥¦¿©v”Q_%|Jý¢/þ~ÖŸþ k3x?EøMãÙ+öÍý¢> xÇÄêËTðÍÿìÕñ/ö(ð®‡4:äºÍ¯‡ôŸŸ ~ÑŸuO[G½º{Â÷–·e¦ëVºí}öîý—üEðÃÇŸáñω´?|4ŸÃx¶|ø×ðÛÇpjš?Éô?„ÿ¾øc⯌l~*kWÖšÂ=GÁÞ ×¬>,k³ áÍωõU{EúöŠø×Lý¿e­_á6¹ñ–ËÆ~8>ðßÄèþ k~»ø û@éßì~1Ma¤k6ÿ ÛöhÔ~ZþÑ“üD»ðö½£x®ËÁ¶ß &ñ _Úéóx^Oíaó—Â/ÛçÆŸ>~Î^+ðEŸÃkÅ´—üö¦ýœüwe¤øœøXþÎ_³í%ûTiZ¿Ä™4ÙüOµ‰uÙ£ök†(üCw¨Áá˜~7ø÷ÃWoáXôMNÃáüÀjþÊ|Kðƒöhøð«Æ1YÁâo‡? üà­b ´¿³ŽëÃz=®“² ØÕíV±ƒp¨¢VÜáWvÐUñö¡û|~Éš_Âßüi¾ø°°ü3ñ×ìñOöëð·‰Àÿ':—ì±ðODøWâ/ФÑmü/ˆ4Ùü'¤ükøg=Ç€5m&Çân¡?ˆäÓôj:ŽƒâKMž#ÁF¾j¿u wöp×­¼Wã? ~ÓßðOƒÞ9ð×Ä¿†_>ÜÙ|7ý´loƒ"ø‘áÍÇš'ÃMkÅžñ_€|MÓ>ë>'ð­ Ô<]àüfÕ|eðoÇŸ<`./|gðëÁ>=ñŽ|!k§ê÷$ðþ—«=— ð»þ û%|bñ‰|7àŠ7¿iðÏÃã#k>2øiñgᇂÓVÑu­#R·’ÓPÒõ].þ)ìµ >úÖYmîìîá–Þâ )ctb§äÏ xãìÝâ] Ã_­õ_Œÿ³f¹­iÚ5¿ƒ5¯E'Å_ÙâJæ ®¼)âêÐEñ'à®\\]øOÄzÄ_¾éqÝê¾:ТѼ áÞ'ödý¹|7ûT~ÓߴÆ’ï„ÿÿgoÙ'â~‘©øáŸÅ_…Ž~=üDý²4iþ%ð×Å+Â:Ü~›Àÿ~øÃá­ä~Òc×t__ø¾ÃÄ^.ð¯Š<)>™ú@Ïø²ïÄÖ×ï|¢i>$ñm¦“}qáÏëºôþÑõfw{ 7Rñ®‰âKÒòáR µ´U­Uü߱ʊó/ƒü+ñŠßZÓ`Óõ¿|Iðd–¶>øâ M3âÃíBðMö3«iö—wú~¯áÍcì×3xWÇ~ÔuÏxÂÒÞ{¯ xƒR[[ĶöÚ(¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€>Bý¥ä³Á>ìï|iÿ¬ûoW×µòí+ÿ%›þ óÿg{ãOý`Ûz¾½ Š( ¿i_ù,ßðOŸû;ßë~ÛÕõí|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¡¾#|FðÂê:ñÖ¡{¦øoM½Ð4Û›7@ñŠu5ø‡Jð§‡ì,¹u‹Uñ]ýÝæ«¨ù^ð^â^Yhs}^5mð3ÁŸð¹õ:ÔúߋҴ>x3Ç ð¤±|Kø…{ñ+Çþ!Õn|KâÍsìk£øcOÕoílm[Ã<0²\Â-à ÚÂÞÇÃúUÕö«­]mº×¼O­ëž(Öu^÷{â7Äoü'ðŽ¡ã¯jºo†ôÛÝM¹¹Ótø§Q“QñOˆt¯ x~ÂÃÃþÒµ¿ê·º¯ˆu½+KµµÒô«ÉÚ{ÈÙ£XVYcºtçVp¥J©R¤ãN:qsIͨ„S”§)5Æ)¹6’M±6¢œ¤ÔcܤÚI$®ÛoD’Õ·¢GoE|¬?lïg§ü.“ôý–¿j#ÿ¼rœ?l¿Ç¢|k?OÙcö£?ûÇ+ÐþÅÎùNgÿ„¯þTaõ¬/ýáÿðu?þKÍ}çŠþÙ_±N¿ûYxòçÌñf“á?‡^*ÿ‚wÁEbŸj1>¡qã}'_ý´µŸÙþoxkHM‘|m?OÙ_ö¤?ûÆêž§ûlüÑtÝCYÖn~0é:>‘cw©êº¶§û0~Ó¶n™¦éöò]ßê:…õ×Áè­l¬l­a–æîîæX­í­â’i¤Hј/ìlßþ…Y—þâ¿ùPþµ†ÿ Šø:Ÿÿ$~q|ÿ‚{~Ö? t?|IÔ5Ï…)øï£ÁFí?n†Þ ñ‡íñûâ„|AáÍ[þ ïà¿ØOÅ? þ)~Ñ¿>ëß_Ót[ÿ‰úß…üi§ü+×¼=ú7Éô‡žðÍËü5ð/ºþÅŸ±?Ç_ƒ¿¼/ã¯ÚÄ 5é|Ÿ·¶½á;/…—~.—J>>ý¼ÿoµµÖѼUáÍ+ûßÃ? í> x?ò cĦ›­xƒã†¡»“ÃÑYxŸÇ_«4Wšn.Ÿ?`?Û+ã·ì[£ü7ÕuÙ³EðŸ…à’¿ðRÿø&¯Áý^|N½ñOŠ>"~ÑÞ)øáüMø£`ÿ ­ô¯øOOoÙçTÒ<_á? ÝüGÖ¼&ÖvÞ‘­ü@oÝxwáßêgíûüSø¿ñçâÅ ëÿì<=ãaÿfM–¹ªxŠ×X¶ðNßø)7ÆÛãPÔ-´ÿ jv0ü2ø£h_ ~ͨ]ÿmxî×SÓã âï‹õ_Yø;À6wð¼’kwoñ߆à—¶7ÄßüK½ý­~:xsÅz_įø'gí³ûø¯â&ñ»ã7ÄŸxƒÅÿµVµû6Ì¿|ðOÅŸ¾üý|9>™ð£Å÷:—Áï†2êÓøoQ‡Áºn¡ñCâÞ”údŸ ? º(óïö`ø+ûQhŸ´çÇÿÚGö–‡àuñ_öqý~xwÂßüSãïA¦j³§?l?x»\×5?|<ø|e²ñ{~ÐþÖ|7 …ƒO !Õü ¨&«ƒtïˆA(¢€ òïüðŽü_à?ˆ:Εqiã߆º—Û|#ãMQ¾ÐBý¥ä³Á>ìï|iÿ¬ûoW×µòí+ÿ%›þ óÿg{ãOý`Ûz¾½ Š( ¿i_ù,ßðOŸû;ßë~ÛÕ©ûg ü ǯƟÙlæÑ|¬¿ÚWþK7üçþÎ÷ÆŸúÁ¶õOûoÝOcû=_ÞÚé·ºÕÍŸÅÏÙ’êßGÓ$Ó¢Ôµiíÿi¿„C¦éò놓¤G{}"-­¤š¦©¦éÉ<±µö¡gl%¸ÓÉ?äs”ØÏÿ©TŒ1_îØúñ[ÿMÈ󨓦>ƒØw?çúÖ„IœcOaÜÿOÿ]xl|uÛöjøÖsŽš÷ìæ8öÏÇáþ~•~?Š~;ÿ£gøÛøkß³€Àôý ùúWîµ[wÖ+ÖqV_[þ‡ÈÁ-Ýü´~ßÓóG¹Ä™Çä=€ïý?ýuãÿ´ºÿÆ6~ЇÓàwÅœ}€µþ§ÿ®¢â¯?èÙ>7þÿìÜ0=h1þ~•Ã|_ñ?ň?¾)øEýš~1Ûk>8øqãi7:Ÿ‰?g84ËmKľÔô[ µ­~>^]CcÕìR^KmewqºÈÐÚÜH«ñ6ÔâÜ ’”[n¤’iÿ7ßóÓSGªi'{?³/Ñy~ÊZÞ³â/€^Ö|A«êzî¯yÿ OÚõ]fþëSÔ®¾ÏãOZÛý¦úöYî§ò-`†Ú6Vò †(Slq¢¡ëó«àÆ?ü.øMáOëÿ²÷ÇkÍ_BþÝ7w:6¿û3Üi²iø—XÖmþÍ5÷í§]>Ë]FæóláÛp²¢y‘ªJþÈ?iŸíÏýF¿e±üÿizüiäyªß %ëV‚ÿܧÔ}oÿ?Wþ?þDúÆŠùL~Ó>)nŸ²íxÏü†ÿe®Ÿí1O´¯‹[§ìŸûDŸûŽ~Ë×ö™©y6d·Ã¥ë[ÿËGõªißþÜŸÿ"}S\Ä/‰^øeeá‹ßËz£Æ?ðOÃ_ÛiÖo}y}âëöšÛ£![+G¹›VÖo Ù¦èZn§©H®–Œã#öñÆ?dïÚ'žŸñ=ý•ûÿÝÍWÁ·Ž¿m¯‰º¯ìóuû3~É¿,î¾xëÆ^<×5=ý˜4Øü?®Ü|;×¾øOVÑtX~6øºÇÄZ†•ñ ÇŽ”5»Ãþ+°ð§ˆõ=ŶšlÞ¿Îy^: šT®—»Z„ß¼ÔW» ’“IµÌíh«ÊME6©b(·e=|ã5²¾î)z.¯E«?]¬›âMÿÆÓ´ _Å´Ô-®5Ÿé¾):¨ðåÞ¯aqa´4MUôÿ´¤mqœ“"˜Z'}ñkl·/z-à’A¬—bÅÌ–ÐI4°Û¼á|ׂn'–(YÌqÉ<ΊG-ùCû"x‹öø7¢k/Žc_hZ^­,šíü~ øð[â—ÄÏüAÕ._üAø­ñÃÇ_´G€¥ñö¿®""Î#øYáå²Ùmc¥M§x{MÒôO²Çíã£ÓöIý¢ð¡ý”ÿú'i<³·§Izâ°«ó¬X¥Þ_ø.§ÿ }5_+~ÙÃ?±ëñ§ö[ù´_+Ô>|Yƒâ´6VðO>ë_¼fžñ7†|tþ ›W¶Õ§ðWƒ<iqowàøï×zu燌¦•J9îUN¢åœs<¾ëš-{Øš2‹R‹qjQjQ’m4ÓNÄb%a1‹ºt+kgÒOF¯tÓMZ÷<ê$é öÏùþµ¡gãÓØw?Óÿ×^Åvýš¾5œã¦½û9Ž=³ñøŸ¥_⟎ÿèÙþ6þ÷ìà0=hþ~•ûVÝõŠõœU—ßÖÿ¡òpKw-ß·ôüÑîq&qù`;ÿOÿ]xÿí.¿ñŸ´!ôøñgAà-Ÿéÿ먣ø«ãÏú6Oÿ†¿û7 AŸÚ Ÿ¥pßüOñCâÂ/Š~ÑfŸŒvÚÏŽ>xãÂMΧâOÙÎ 2ÛRñ/†u=ÂmFk_——PØÅu{—’ÛY]ÜGn²46·*ÄüMµ8·($¥Û©dšÍ÷üôÔÑêšIÞÏìËôGÞ_²–·¬ø‹à€µŸjúž»«ÞÂSö½WY¿ºÔõ+¯³øÓÄV¶ÿi¾½–{©ü‹X!¶‡Í•¼¨!ŠÛh£Ø¼Iã xAü<ž&Ölô_øJüK§x;Ãò_3Ç£â}^É´.4^jfÆ{m=n¤/oÚÛL¶y5ë+[ƒþücñßÂï„Þð¿û/|v¼Õô/íÓws£kÿ³=Æ›'öŸ‰ufßìÓ_~Ñ:uÓìµÔ`Žo6η *'™¤¯¿ñkâŒß~ø«á¯‰?eÚZ×Nñ>žÛêúV¿û,Ûkž×tû»}_Âþ0ðåã~Ò¯ýâø—OÒ|UáL#>›¯èúuò){uñ§‘æ«|$—­Z ÿrŸQõ¼?üý_ø ÿùÙþ þÐz?Ä xZóÅ2èþñŸŒ¾$þÐ?ü;á[K››©µwøñOâ€õ›Û%tyŒé~‹TÕ¯¦ò´ë[íNÖÆ;;PÒàºú>¿?`Ë/Ú;àÅŸŠ¼cûL~ο<_ñjçÄß´¯Ið÷Wý›SÂ>øoñ#ãwŒþ6kóé°k_´„¶^$øã/Ũx¹c2B:M«ÛjÓø+Áž?´¸·»ðŒüwá˽:óÞ<ÐgŠX5湎å¯-.ìí¥¶ùýf¹*ÒÊHòÎ6º¼d­(©E©E¸ÉJ-J2‹i¦šm3HÉM)EÝ;ÙÙ­žŽÍ4ÓM5{Ÿþ×ã7ß²ðÿ«‰Ô¿õ™i ãâLãßôý~߇­l~ÛÚ¥ö‹mû4êzo†µ¿ÞÛ~Ñ7~O‡|;?‡-uGÎý›¿h«y~Ç?‹5ÿ ø}>É ²_\hk¶;­m§K_´ÞµµÇ‚GñKÇ\Æ5|k9ô×g>¶~?ž+õî ðƒI]¼bwiiί»[þWì|Öiþù%Þú>ÞG¹D™Çä=€êÏõ­—8ü‡Ó¹ÿ>õáqüSñßoÙ£ãiúkß³Aõý ÿ_Š»Å_vý™~7óÀÿ‰ÿìÝÓñý ÇùïÍ{Õ9ŸX«ÿÓÈh—ý½÷üôíȬ’ZéäÿÈ÷h“¦>ƒúŸóï^GÿôÖµ›íOöœÑ¯u}NóHо8üIþÃÒ®¯î®4ÝûOã—Ç;­Kû*Æi^×OþѺD¹¾û$Pý®áVk2E )Çñ[Ç£§ìÅñÄö×ÿfÏÇþn~(Âbq™t)aàªÔXê5\#R•Õ8áñqrÖiYJpO]äz}HR¯)Ô|±t§¸Ëâs¤í·dþæ~½Q_&ڃħ‘û)þÑ?ø:ý—?ú%êAûMø¡º~Ê?´Iíÿ!¿ÙkúþÓð$ÍøV½jÐ_û”ö¾·‡ÿŸŸù,ÿùêê+åQûKx°ôý”?hŸü~Ë?ý5 ý¤|^z~Éß´Oþeþ‰ª—“æ+|:^µðëÿrë4ÿà?ùêj+åÅý£|dÝ?dßÚ$ÿÜ{öVÏö›§Ú'Æ­ÓöKý¢OýÇÿe^ÿ_Ún¥å8õ½/\Fî`úÅ'´¤ÿîOþ@ú‚ŠùŒ~О9<Ù'ö‰ÿƒöSÿ蜩ÇÿŸ²Gíÿ…ì¤?Ÿí;RòÜbÞ—®+¿÷8þ±K¼¿ð]Oþ@úbŠóŸ…l>,ø*߯š~â/ «ø‡Ç¿ðç‹€Bý¥ä³Á>ìï|iÿ¬ûoW×µòí+ÿ%›þ óÿg{ãOý`Ûz¾½¨QEòí+ÿ%›þ óÿg{ãOý`Ûzµ¿l¿ù!Ëÿe¯öYÿÖ£ø9Y?´¯ü–oø'Ïýï?õ‚?mê×ý²ä‡Çÿe³öXÿÖ£ø7^žJíœe/¶g€ùuHÃþíˆÿ¯¿ôÜ=‰=}‰þƒüýUøÉðSâ—ícÿøûxëj[öNð7ì¡ûWx÷ö5øGá |ø=ñKNñ_~ h^¼ø“ñ ã¬ü%«ø«Å>×9ü1ñ÷í3â¯ØŸö¶Ðf} ö·ñçü-ÿŠñŸìã§|½ðoƽGGÓ4|Qø¨ê?>øwš×l4]"÷ÅZOÅ|jðÜÚýŠjZn‘¦ÙÉ&”]Çûi:. ¬éFsuéЫìjÍ:RT¹fêÑ÷c6¯k\%ª‹>n—"RO•I¨¨JqçŠ|ËšéFzµ×•õ]OËïÚcþ ûRüÕÿà»úGÿj|7ð÷ì7¤ÿÁ'!ø?ñ à×ì»ðGâ~¹ðÇ\ý£¼=áFø×©x?àÿÅøSNñ¾™ñGÅšŒ¶ï£|]øÛ⛟†žÖ.¯#ö–øÅá xcQøAû4?í-¯'‡¾ Gñ{UÕ|m¢xÍŒï'²¹¼àoǣ隅¦£¯>—lò¼?$~ÐðDmSöƒðÏüDÖÿkJ-gþ U¡Á>ô½[ÆÏÁ TÖ>꟰͇…moüIªéþñÿ€ü7ãÝKãE߆¦Õ/¬¼?¢ü'Ñ<uªÉ•¥ëvZ÷ö“ÿ‚)|=øûû\|Yý©ôï|Ó/h[OöŸtŸ_±‡ÀÚÇÅ:S|²²ðÕ•÷ìï⿎ÑëÞø+¨øóÀ:}Ÿ‚þ ¾»ð£âÅŽ±ok§ë–Zv™¯i%þ™óóþÕ§íœ!)sT“Š«Z5¦ñ™­H(ÆU¥ËFyzjð“¢ý’¨¥F*—\~®ÔnÒ÷cðÁ«¿e†‹ÕA?zQ¬ú®d¥oyózOÇ¿ø-ìÇû7|Ný­~ü@øgûGÞYþÄ:ÿì½¥~ÑŸãþI÷ü!Ÿkÿ™³ìÿ¿ûçà¯ìLß?nÏÛCöÌо&.«¤~Û>ýl¼}ð¯Uð[ G¾9ý›¼¨ü:ðŸ‰<'ñ×ÅÐ[Â/­x3U»Ä Õ|}ª?‰×ZÓüoe¦Ç7‡n9å<ÂU×;µ^mòû+Ê6>1OW+(G=-;ÔžíJ0Ò*Ї»¬Ü×›ãj‹m^É]º«¶‘ÛFÿ¿lø(÷Ço‡?µ×ü—áÜÿðQÆý›u?Øãá·ì¯âØ'ömOþȺ»|ø•ñ'öd?¼aðÈxkâ?Á/|{ø»ÿ /Ä› hÅ~xëIÕ| >$›ËËÈôeÐí,>¹Òÿय़´_ÃoÛrÊÚV {ÂüÿëÛÁL>?þÍðW‚ŸÄþý¢ü+ñm⥿†õŸC¤øÍõíÂv:÷€tkß4Ï6¡mouªCgªùúô¦_³ßìH~þÛŸðP?Û üKÿ„§þ¯þKþ-Çü!ŸØŸð«OìÅð“YøXü%ÿð•êÿð›ÿÂoý¯ýºâ—ð‡ü#_gþËÿ‰ÿý£–þÐ?ðKýöŠý¯þ7þÓ^)ø»¨é>øíÿ¯ø—ÿÁñ'à ÁpkišÄÿ‰ú×Ä câî—ñçÅÛhÙiúÝLJ¬|qà) ûLQk“x¡£Ý£6tñkšj¬Ü¥ˆªáMÎ\±¥õŠÒ§&ÝIG•Ó•4¡ÂдeãîôEÓÑ4’äÚV|Ü´Ô–‰;ÝI¶ïwªf§€¿à­¿²ß|oàÿ]iŸümâÏø%ï‡ÿà­÷4ñ§‡ü'kàï~ËZî¯i¥M¦x²ëAñψuËoŠz]6«®øFÐ5Ï Ã¥Ú]ɧxçS¼Xl§ùUÿ‚ÚüøÓðÛö‰ðwà ~Ö߳ĭþ Iñ“þ Yðïâ׎~|ñ:GÁ /K»ÑüñCÀ^ Ö~4Ühü]5î§¡üAðoÿ‚1jŸæñ7įŒ¼WûRøwÃ_ðE¯ÿÁ&W࿞øYãü%еÖ´Kï‡úÿˆ>5jÚ,_µïé àøtO_¦—«|E×ï¼[7¼á)tïøwàŸÙþ ÓñËãWíñ—Àš‡‹><§ìñüñ©Á'ü+û@|{ý‹ü}û+kñ7Œ¼wq¥xGÁúÁ‰š‡‡µï‹³ü-øk¥Gâoxç@ñ…ç„#øSö®ý¤>(ÍÿŒøMÿDøƒñ;ÁŸ¾ øAüYð:çMÓôˆlø×®üDðŸÁ_‹þ*øAáK_üZðÏÂïø‡áï¥k{/üEѼ'«ê>ðäé=«Ákâ/ÛišEÌ©snÑÅxî³Ä@‘ÿà’?·w‰kïxMñíýâKã÷…|¯Aû_Á?ÿh_ÙŸá?ÃøWâm­­¼·ÿ³þ½àŸ|$ñ†‰àOøœÏ¡ö†Öõ Üi¶ž.?|QwçÃýx‡IÔu¿kú.â=SÁÚ¶­¢êºf—âí×B¾Öü-©j3ÚØøG²ñF‘â ÞjšÔ±jz}¯ˆ´oB¸»µ†_HÔ´÷¸³›ó#áÏügÇ×µÿì÷ûfþÓÿ´G‚>:|Xý—¾üHðÃo|=ý™´?ÙëÆ>:—âw‡Gƒuo~ÐÞ'Ѿ&øïLø©hÞŸUµð¦…á ü'ð7†5mwZÖt_ ض¡=›~—X§‰ÂÔ¢êJœd–"—´tèòJ­6ê7 ôæêÓŠ”£ RÄR©éISræ<Nš§R2åRiòK—š\Ê.ѳ„£i=R„¢ß2½¬v?³wü{öfý¦þ,xáw‚4/‹z‡Æïø^Mû5üXñ·†<3¦|+ý¦“ömñðçÆvø=©hþ5ñ‹]|t%½™~!x3ÀjúMµÞ¡¡&§ ¼»|óödÿ‚ÔþÏ´Üÿ²Uöð?ö¤øaàÛkÄß¼ðâßÅ¿ üÒ~ø‡ÇŸ ?á$Ox'W¼ðwÇx“A×õ‹¿ k:gƒÖ÷ÃÙ¾(Õ­¥Ó4ÝEï-¯á³â¿a_ø"·ÃïØgãükàüÖ|ð–ïã%ßÃ[DýŒ¾i_´µçü-ë­_ȰøµûejÉâß‹>1Ó¾èÞ$ñ†<Ÿl~ ß]øgRƒÂþ9ÔÛÃÕ‡¿àŠZ~ÿø=ÿ׋ö‘Õ¬¾!þÏ¿$øÁðOö¸ðçÃSáÿ|9øÁeñëÆŸü1ãï|;_ˆ÷ïk¨hV>8Ö¼*Eñ+‹Ý6öÿW´Ô4‹»¨-¬<‡W7pŒªS„gÕ©R Ñj¤âðnÏÚ5SÆÆœÚ³JµmÇš¯JŽ7ÊäÔœT_½uí/;r­U©9Fîúòö¯Åÿ˜ý›õy´í+áߟÚCâeûKüuý˜~ ø#áß„¾Üø‹ö„ñ7ìÑáÕñ?ÆïˆîeShÆ…“W»çÑókîË“å&©·f¬Ü“vI®kþ ]ûTüWý”>þÃz¿Ã?Ú þƒAø×ÿ*ý™ÿgŽÿ?²þ _Â#û=|GðÿÅk¯‰ú¿ö§ÇŸ|Cøoàÿì /Xx§þ=oÃ“ÚøûÜjRI ¾±a{ùañçþ ûi|:ý„?à´?~þÔmñÓÀß±ÿÅOØãÃß±?íøÿ ¾êÇâu·Åÿˆ_ 4oþ àï†?ìëñ9~Mâ;Ï/<5ðÒ+ÿøIŽ­lâö îÇú ý¹¿b3ûjÿÃÅÌÿ…iÿ —ûwþÏ_¶Ïü‰Ÿð™ÂÀÿ…ÿ v>È×á_øEá*ÿ„¯ŸÿÅIý…öù5µ£Þÿ‚™~Ä_ðñ؃ãgìiÿ 7þïü.?øVäüGÿ„3þü#Ÿð¯~.xâ§ü‰ÿð–xû_û_þìù4¿°jj¦ý‹û:ï‡ Lç‰:“ŠörFr\òt' ?x¡NÕ%©8)óÆ-MGšûRpŒ`¥üIɵv—4[¿»w¢j×Ù´Ó²<+ö…ÿ‚Ê~Ì?³?ü<]¼yàO:°ÿ‚dÃ"ÿÂùÿ„GÃoÿá-¶ÑÂÿøT_Û?4íïìíë?øO¿á2ÿ„û/˹ÿ„{þ‘yÔÿlïø-ì³ûx—öŒðßþ~Òá’|-ð?Ä´>»ð?À> ñ7‡>ß~ÑšìZGÁïø—SñįýÄÞ.°‘¼Ojíü#0è‹oiqâHüS«h~Õ>ý¹?à†º·íwâOø(¦¡àïÚêçெ¿à¤ÞýaøÅá›ß:wÄëÅß±ž§¡jº¿/ÅÊ<'®øsJ{}‘iúv¹ÿ dz?ˆ­üwÿý†­àoþ|ÿÁi?aÿŠŸ¼Ö¬>%xKâõŸÃßjþ9ø{e¨x~îûÁ:´rø—‡[ÔvI'%M®Vä¹±.6\Ûò¬2|É7yò§#jp¦ùZ½ô¾úiM=×~}W•ÝÓˆ¿ðUþÈ?à´=øññ'ãßÅï‚ß°Oü;§þ,'„gï¾ÿ…9ÿ Iá ?~)ÂßÑüWñÿþŠüK£øÏÇÿð¹¬þªeχ~ÿÂi¦ÍæËúGû-~Ý? ÿjŸ‹?´ÿÀÏ ø3âÏߊÿ²?ˆ>i?|ñsÇuQ¤|iðmÇÄ„¾3пáñ_‹m¦ð#is«Øéúäú´3 éþ1ð†µOôüâý­¿à‡ßðÕ?ð÷ ?ißøA?áêßðÀ@Å–ÿ„£þ9ý†¿áÿª³áïøZ?ð´?áÿªwÿOÛ?ænû?ïÿA?gØsþ?íËÿ ý´?ágÿÂWÿ çÿ —†ßð…aÿª?²ç káOüŽð–ëðœÿÂsý±ý½ÿ"·ƒ¿áû8Òÿâ¡ó´bâœë);¯sšmÝó;9Öq³m´­ìl–‰I¤’MFÒ‹ÑoeÝl¡~–þ}^í-uÕ¿†?à ÿµÀÿ¨w‡_„Ÿ³ ~˜WæÀQø(Oír?êáÏýT³ ~—WŸ˜½7ÿN0Ÿú‰@ÞŽ”ÿíú¿úvgÇ?µÐÎ¥û.ú¸OÿYö’¯€ÿà¢ÿhÿ‚_±íñWöGðü,¯ÚÁž‹Tøuá%Ñn&Õ<#àë¿ø¿IðÔ1]Iâ OB´ÑÅÿÛ>Ç?ßßµ¸Î©û-ú¸Sôý˜ÿi*ñŸ‹¾ñÏ~øÃ >,ëßü{®i^G…¾-xkÂþñ®±àZ+›{«}N |HÐ KOkSü2ø…ñCÅözÃoýáø,÷à ~Î_³Ä?~4Kñ‹âÿìQcûr|\ø]ðkþñk|ýž4‹ ?þÑ"kÏ‹¢ÒÏথ¨j_ÛžÓ4ÿø«ãÞ¹µ³ñ?ýÆÚwˆü3¤ëéðIHüiñ+ö·øÝûD|eð‡‹~0þÖÿ±w‹a¿øƒàÀ;_Ù×à üuûx¯Çúç…µ_Šï¼yñ¯W½›Hvñλâ‹{M#Ãú_†ì<9k£[ÁooæÞ&ÿ‚(ø‹[øsðkÃÚíaeሾÿ‚pêðJoŒÞ?³øš¶ñ{ö?»—OŽÖÓþÕ¾2\Kð³âå–Ÿ§à|@oxóCŸWÔµM@ø + týL%ý±N –.MSœ\eRæ’®š«jØŠ³IÒ³öY(AIFÓqƒ•õYKWö”¶’²äø/E8Æ[ËÙÆR{Ýj}Åü—àÞ·ûXüýþü=øËñ“Æ¿f~ØÞøðçJøpÿ´¯ÙßÇÿ$øu¦øÿÄ:ÿŒ¾'ø7ÅÛiׂ×Ä:†àÍoPŸÃºŽœ4 }sÄWKáåÆðüOà'Œk‡_±†±àŒß þ-ücÒ~!k_ ábXü)´ÆÃ?Üx·Ä?Û~ð¿Å¿ürø+¨ÜøRÎûÄ…ûEü$ø;¯kZv£mm¦jØÝiÑ?àïükŸm…ÿµ_€þ!ÝÚøáüOÁ_ðMÏ|¿ð·Ûo­¼'à‹ZgÄüC½ø›ÿ 4?k»‡GÒlü'qáoø@âûDÊþ ÿ„™7ÿcŽeOø!¾±û4~Ð?±_Åù?jí;â…¿bŸþÚþ2ðχogk=â—Źlïkº6µ©üjøáÆ ^ûÇu*PRƒSIÜc‡Õ]«ArÝI·>Y=l­e%»èÓnú]}!ðGþ Aû/ü~øïû4üø{ðëö‹žÛö»ñ?í7áßÙëãF«àOi¿~"Ù~Éú^¿ñ7ÅÚ?‰[âDÞ&—ÃWWÞÖü?áÅ_·‰dÕí­gñ'‡<5 k&·©z7ìÕÿjý˜iÿ‹þxAø½áý?ã€øìf_‹~8ð·†4¿…´úþÍ^"ø×'Á½OFñ¿ˆ|^ãÁwb[éWâ'‚þI«é×ZŽ‚šœVÒ…ü@ý‰¿bŠ>ý½ÿà•žøSáŸÚž?Ùƒö¸ÿ‚—x«WðÇíû)x‹àþÍší=áíZß %øË}â/†ÿµ/Ž5߈>6Õ —ÅŸ¼oâoÛx/Ã6£kidN³©ê¿¨?°ü+áçì%ñ—ÀÞ7ð/Œ¾ ë_þÞ|h»øehŸ±gÀm'öš¼nµ³éÿlýY<_ñsÆzwÃ}ľ$ð¿‚Sá½Á;ëÏ êpx[ÇzŒ¼%§Zøz¼¨bs ¼®Q‹J²Wh$“¥„””W7¿Îx¸§{H¸ÁI^§.‡NŠÑ7wÆ÷m¾z‹]4m(6¶iɦ®¤fÿÁTÿkψ?³÷í½ÿ®ø&lïøaÿÙ¿ö—?·ý£~-ù?³™öSðkáÿü _øOj_…| à¿?ÇšÈðÆ•l|H|]ýŠ<íf_\éßxkþ _ûtÝþÍß±/Œæø°Ú®Ÿñ;þøgûxö‚o„ßô7ý³?` o]ñ¥¦ñ!¼5uà±áO Ÿ‰ãL—L.ø[ x.úá!uá›í)îõ .ÿt~=þÄð¼ÿmïø'×í“ÿ 7þøa/øjïø·ð…ÿmÿÂÔ?´÷Â=ágüÿð–iðƒÿÂý5ßùü_ÿ /Ú?²ÿâAäÿhËoöéýˆá¶?áOü,ßøVðÉ?·—ìñûnÈ—ÿ Ÿü,ðþü|1ÿ‘³ÂŸðŠÂYÿ _üŽñRa}ƒþE=cí?èüõéâñU&—<}”Ý¥K ›^ÿ,œjéË9JO™ÆV–±”iÅÅh½æÒÑóIëhÝ»ZúµåtxZÁefüP²à­ƒþÑÿ"¿Ãïù9þÞ:ÿ’£ÿ$þ¦ù(_õKªŸŒà¶²Çƒ>8é¿ øqûHøÎKöõøÿ×_Œ^ðƒ.þ ÙþÕþ8ŠÂ][ÁWþ)Öþ%è:彿ÃSªiñxòsáWÔæºoé>7‡FצҾ}Öÿà†šµ÷Åh¼QáïÚêçCøSüOÂ?ðY4øW}ð#N×5{Ÿ‹6Æ9> ü5¿øŒŸt‹™ÁJþ0ü1øû%øÂß <„ìuøÕûBh_¶µ–¡âOƒ| ãøü5£j>é~ ðçĽ1çL#Eÿﺽÿ„ñ¦9ôéî}óÒ®ÄÏÔÿAýOäj×$ p8úŸóÉýzÕä^ƒó?Ïÿ­øWŸV{ùþ Ÿã©²Wi}þ„±.NÎ;Ÿè+Eç¼ß—z†$ÇQÇùÀü:ŸÈÕè×'>¿ ÿëöü=kÍ­SËò_«ÿ‡FûD1ôÔÿŸzЉ:cè?©þ¯¥At÷à{_óýkB$éŽÿ õÿ?Ö¼Ú³ßÉýíï÷kþz›F6^oðòþ¿CóÛà@Çü/öº¶áÏýT³}ûdsðF/û-¿²¿þµ'ÁºùïàXÇü;ö»?â]á¿ýT?²ý} ûc ü€züoý•‡þm/ÁºÓ ¯e?ö’é82j¹b?ëÞ+óªq±'Laî{ŸóÓŸJщ:cè>½ÏùéÏ¥Agö¿ôÿõÖ„Iœvôö¿çú×ê•e¿—âßÏþçÎÅ]ùuþ¶×ò¹õ¡ôê~ž¿úÄšõªÏ{ú¿Ñ^G4•ûÿ_ו‰¢\þ?Ëÿ¯þ¯F¹#òÔþ¡E=;ŸóúV„IÓüñùu?á^uiïýkm>å÷›Á~Ÿ£üÿ+ìÉ£\°ÿ>çüóWâNŸ—ãÜýOÿUAäñôçØV„kŸÀ×ÿ¯ë^mIî÷¶Þmÿ_…Θ++ýÞŸ×å¦ä¨½a×üûÕä^îzýúÕ IŽOù=¿/çW"\œÿœw?Óÿ×\eø~-ÿ—^»›A]ߢü˧ùöÏ'ñ>ߥ^EÉúp>¿çúTH¸Ç'·òÿ?áWcLcòä÷ÿ?Ò¼Ú³ìýæïø~(×rx×AúŸóý*üIŒ{r~½‡áþzÔ§ãÔÿŸéZ§OoÔÿŸé^uYùúy%»þµ×ÈÞ*É/é³óÛöãÇÿ¶Oýœ/Ä¿ý^ßëôž¿6`ž> þÙCÓö†ø™ÿ«ßãÝ~“W?\v7þÂñ?úzf”ƒGþ½SÛol|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¯kä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{\†¡EPÈ_´¯ü–oø'Ïýï?õ‚?mêÛý±9ø)n=~8~Ê¿úÔ¿«ö•ÿ’Íÿùÿ³½ñ§þ°Gí½^åñwᆙñ‡À·¾ÕußxbÞç\ðW‰-|Aá7ЗÄNµàøwÇþ½°'Ð|O KåkþÓ Õ¶« jv—VFæÙàU–>̺´0Ù†UµN†3 Z£I¶¡J¼'6’Õµ¶’Õ½ «ÁÔ£Zø§J¤#}å•ßMYá1§áëì=?Ïô­ÓðõöŸçúWÎZ×ì¯ûggWÿ„oö©oøG¿µ/ÿ°¿¶ô„çYþÆû\¿ÙÚÿaýš¾Ãý§ö#íÿcÿDû_öÜì¬ñû.~݃§íW§à›áyþ³M~>%Êe¶"~W¡[çöãGˆI.EçïÃüÑõdkÐcþƒüóúV„IÓéíÓüOzù~Ì?·xéûVißø%ø]ÿÐÑRÙŸöó?jí;ÿ ?úëŽyîY-±.Þtkì¶ÿ—Oçækuÿ.ÿòhy_íyyyë©öI’=;{ÿOAZ®õ8éÛüý+ãû5þÞëÓö°Ó‡ýÀþöúþÌÕ ýœ?o¡Èý¬´ïüü*ÿèe®YæÙ|¿æ*:ïûœGËþ\š¬=d¿†ÿð*üŸË¹öÌk´gòþ§új½zŽ¿Ï°þ§ò5ùÁð³á×í±ñƒá—ß‹~ ý®"ŸÁß¼ áˆÞ›Qð¯Â» ù¼5ãéþ&Ðe¾±“öfw²¼“KÔí^êÑÝÞÞbð³1BO~¿³×üz~Öºpÿ¹á9þ³rÏ‚–غzïzX¥è¿ÝÙ¤iU_òêWÿ?þLûÁ ì:ŸóëÓÿÕW£OèO°ì?Ïô¯ìýÿ?km;ÿ ï„§ùþÌU øÿ?k;ÿ ß„‡ùþÌ5Ç:øYmŒ£ó§‹ÝîôáRúÒ•¿ÅKÿ– ñ'OרvùéZ'Oרvùé_ƒàGü,tý®´ïü&þçû0Tƒàoü üZøµñEø‡®üCÑ^ o[ìíõk­ZÞÏáþƒ¥“¥è?üá»=:ÏÃ~±³&ÎÅnd¹Qqp.e¹žtú—âïà 3ão| ªë¾"ðŽιà¯ZøƒÂo¡/ˆ4kÀ7ðïü;{`ßö?ôOµùßgýÎÊϲçíØ:~Õzwþ ¾Ÿçû4×Ûωr™mˆŸ•èVùý†yQÀbK‘yûðÿ4}YôÁ? ÿ<þ•¡tú{tÿÞ¾G³íÞ:~Õšwþ ~ô4Tƒögý¼ÇOÚ»NÿÁÂÏþ†zãž{–KlK·û-¿åÓùùšÇ]Ë¿üšWû^^^zê}‡dNÞÿÓÐV„k€=Núvÿ?JøÀ~Í·ºôý¬4áÿp?…}¾¿³5H?gÛèr?k-;ÿ? ¿úk–y¶_/ùŠŽ»þçòÿ—&«Y/á¿ü Ÿÿ'òî}³íü¿©þƒÚ¯Dž£¯óì?©ü~Z|9ðüÆ?h\þÕzd ðƒÇ>ðý„ƒÂÿ à7zGоøÇq\¼¿ðÎ7-zˬxƒ\´ûI¶Ó•VÑlÖÞf´{ËŸk_Ùëþ ½?k]8Ü¿ðœÿ?ÙŽ¹'˜`¥öªjûÞ–'¦ÛPz~;_[£HÒ«ùw&ûóSû¾3ï^ƒ°êϯOÿU^?¡>ðÿ?Ò¾³÷ütý­´ïü'¾Ÿçû1Tƒàü tý®4ïü'~çû0×$ëáe¶2Ξ/w»Ó Z…KëJVÿ/þX~ƒÄ?_aØ~?ç¥hD?_aØ~?ç¥~vðP±ÓöºÓ¿ð›øDŸìÁR¿ðPñÓö¼Ó¿ðšøBŸì¿\“öÛ‡]¯ gÏþaªçOøSûéòÏëg£büÿ‚ˆ~ןöð×þªÙúO_þÍ_³WÆ…ÿ> üZøµñEø‡®üCÑ^ o[ìíõk­ZÞÏáþƒ¥“¥è?üá»=:ÏÃ~±³&ÎÅnd¹Qqp.e¹žtû¸ñs…Jׄ”ãXz|ÉI):8zT¤â¥Ë•Ê Çš1“›Šz-)ÅÆjÍÊr¶š)NRIÙµ{5{7¯W¹òí]ÿ!¯ÙcþÎ'VÿÖ`ý¥*¬IŒg·'ëÿÖÿ=kÙ~2üÓþ2Xx6ÚçÆ^3ð§à?ÂuáßxþçÕ ÕßÁÞ0ð-ŽſŽüãŸÝi×^ñƹ°Ë¡›”º6wV·–ïnDŸÙWöç?j8wÿGÃ/þ†ªû=Ëð9m,."¬áV*ÊIR©4Ôæå8Å­Ÿ}úh3ƒ¯WêB)Ũ¤ù¢ÒIèÚûþgÓ‘'¯Ôý{Ãüõ«ñ'¯ÔÿAþ?|®?e¯Û¨týª´ï_ù|0?Ïöi©ì¿ûwŸµfÿ‚_…ÿý ß>#Ê¥¶"_:õ}6§²þ¶2XBû å8i·÷½mçcë(“''¿òÿëÿõëB5àqÉýøwÿõWÈ#ödý¼GOÚ·N÷ø[ÿÐÏRÙ§öõÚ¿NÿÁÂÏþ†jãžw–ËlO¥èâ:îßÂ×VýÞÖÚPòþ÷o^ÇÙ1'Nßáëøÿžµy$qÀÆ>½‡ùüz×Ńöný¾OÚÇN÷øUÿÐË^9¯ü>ÿ‚èþü#_Ú¯Mk/ˆ_ þ8xåî†>»Ãð»Äÿ4{8Û~δÏiñGTi [ ¯´Ë»›«!kåÞòÏ5À=±1{$½–#«Õ¿Üì·}l´ÕØ×ØVÿŸoÿ§ÿÉŸ¨j½üOõÿ?J¿tãßðì;õÿ=+áaû;~ßçíi§àƒáGÿC%H?g¿ø(éû[ißøOü&ÿèc®Yã°RÛOçK×w¦ÑRª’^Ê^~õ=ÿð3ï8×'?€þ§üûոצ>ƒëëþ{æ¾³ÿüz~Öúpãò/|%éøþÌTñðþ ¸Çís§ Ô»ð“ÿ¡†¹'[ +ÛE_½<^ßøM×üËŒ*'wJ^^õ?þX~…D¸÷ÇRÏùÅ_‰1oÔÿžŸ…~u?ðPÅÆ?k­8cþ¥¿„_ý  øÿ\cö¼Ó¸éÿ×Âüÿeúåš¡+Û‡Wï fßøIש¢çëFø//úyê·ëä;ö ãâí–=?h‰¿ú½þ=×é-|iû#~Îþ>ø ÿnþ x§Gñ†¯ñÄCÅ—šÞŸ>ûëýVñ'Ž5øWâ§?h_ø{Oñž¯û5ü`Ñ~ xêãYµÓ­ôí[ÅZ÷À/ÿ´ež¡á9¬u]FæûÃñø'ã÷ƒ´»›­bÏAÔSÅZo‰¬bÒ¦Ò,ô½sYâbý"ûÃÿ±ç쟠ê–siúž‰û4ü Ò5 ˆ%¶¸±¾Ó~xZÎîÎ{i£Šky­®!’`–(å‰Ñ£’4e*>–¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Šñ/|-Ôü!ñãÇÄyµKâôŸ ¯l4¸c¸[í/QðO„fðž«-óÈ¿g’=FÚßG{?³9(¶Ó •X©m¯ðOÇï„~'|bø3àßXk?þëþðÏÅÏ -bÃSð†¯â¯‡Þø§áô‘õ=:ÊÏZ°Ô¼ ñ'ÁšÄZχnµ};VmãP‹Ä>¹¥i¾Ã@Q@Q@Q@Q@Q@ùûO~Ðß´OÃß·¯‰| ã½kTðì_ÿÁøSûYx'ösÓ|à+ý+âÿÆŸÜÿÁKtÙô¿x®ëÂ:—ÅÑ5Aû>ü-kx/Å^»“ÄÐä±Ô-4­CÇ:޼ƒÂ_µGí%á/†¶“ñ»ÇŸ¾ CàÿdɾüIøóáïø'ƯںËÅ´÷Å|5›á߇~þÁÿü[ðš-7Ä·öð¯ìÍãÒþÙãωzÆ¥ã=7âǃþk±ê`ºÕÉø{\ð7Žx³ÂzÇ„üc‹«øçÀËâ_j?ˆSI×¼'âÛÏ|JðxÖtÙ¯ÃWð׎ü¨xKÇ>1^èÞ-ðæƒ¯Ú[k: ö–¿‚Ÿ¿kßÚsâw†5Ï‚:ÆÿŒ>Õ.?à«·°Øý£~%xöFö’økðÂØÃÿ¶ xáw…Çÿ1ð?Çu_ØßÄÿþ]ü9ðŸüwöSð~—¯üQý‹¼d|+â¯~É2øïAñW†´x·¿þ i>Ôµ_‡¾‹à/‹t¿X5ÇüBðŒ_<ãϨ\é0xÛÁþ&ðŒÚ­’G%晉4[ÝMBÒ9u%Í’^µÌ /îÞXÑ_å&€>&ÿ‚SCâ;ø'ÏìÏоßüÕbðŸˆ/jþ&¹Û¡ø³þ_éšOŒ/õøÃ¼/ýñ3Âof·‡à§†<_¤|"Ót½KðM–aú\烼5kàÏø[Áö2¼ö^ðæ‡á«9¤D‰æµÐ´Ë].ÞWŽ<¤o$6¨ìˆv!%W€+£ Š( Š( Š( Š+À>1þÐ:wÁÿx+Âgá×Ä_ˆÞ ñ΋ãOéÚ€OÃȳôßx'MÖï5k¯ˆ¾Ù&û߈†ÂßN¹Ô®§Ýy$°[ÅmæI¾ _^l-)V¯S›’œ-Í.HJ¤­v—»JO]“"¥HRƒ©RJ¯'²»Q_{iz³ßëó¯ã•–µâïø)ìMà ¾ üTð߀mÿe_Û—ã6·àŸ|Nñ¿Ãÿ xëÇ? >?Á4,þ]|BÒü­èÐøÏJðÔ_0þÅ?þ"Cyñ+Q·×-ü{mi¨üVø‡Æj>ø£ão Øßè_SÃ>»øÑð¦öÓG†çà_ÆmsÃöž+ø·ðN_‡ß|aqªx«ÄºÆ­}ö»izcêqkM§X¶±Æ—¬Ö–çS‡L»¸¶»ºÓ¢¿1›¨ìnn¬¬în-U·šâÒÚi#i ‰’õyæÁEPEPEPEPE~q|tÓõþÓÞ)Ð/#Ö$¼ò þÊß³~øÏá‚?fÿ€¾ øQñ÷TÔ~!|1ð¯Áÿ‡¾ø{ãÍC[Š5«ÿø/HðퟆüQ{«Áoo©u­é·ÓêA wrJ‘ _‘£øK¥·_ˆ´Iì?ã+iÑõ<|]ÿ<õ5v?„3c>>ý¢yÿ«®ý§ø¼|^ÃÖµ—׎ù…»PžËþß'ûVóæ_øÿ#êkÙ7öWÒ¾x—গû4~ÏúoÁ¯ÞiŒ>X|øsgðËÅz†¡xSÂú÷‰|oáÈü+®Þhžð'ü;¤]jšUÔún…àß i6o ‡‡t‹{?øÉûþÍÿoþxwÅ?gŸüøOã‹Þ4Ö~ ø»àÏ€üGðó^×>-Yëz¾·­ZøRðõ߃âñ§ñ7Y›â?ˆu»$ê:¿‰î5~æêmjþêòmÿØ7âö­ñ—öbøe¯ëñê2kº…<ákZÖ5ëŸêÞ,Õ´¿‡Þ½Ô¼[ªjW¶Ð^6£¯^j“]ß%ÜÚË\™gŸS¼–wqõ¯âÿ hׄü;¬ë6z~·ã­KRÒ<#¦Ü3‹{SÒ4OÅ‚ª2¼Ö~ѵMV`í¶S°%€Sñu©º5ªÑmIÒ©:m­p“‹júÙµtz‘—4c$¬¥+vº½«++=6ÎÓNÓ­-¬4û h,¬ll ŠÖÎÊÎÖ$‚ÖÒÒÖŽ kkh#H`‚H¡‰8ÑQ@h¢³((¢Š(¢Š(¢Šù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^×È_´¯ü–oø'Ïýï?õ‚?mêúö€ (¢€>Bý¥ä³Á>ìï|iÿ¬ûoW×µòí+ÿ%›þ óÿg{ãOý`Ûz¾½ †ÿn xwÅö¿³W‡|Y h¾'ðþ£ûDÝhh~!Òìu­û쟳í}köÍ3R‚æÊçì×¶Ö×–þtäÝ[ÁqÙb×À"ýš¿gS×àÁcõøYàcÇþºŸóÞ¾ý·¼IáÏÛþÍ>"ñn¿¢x_Ãúwíwý¡®ø‹U°Ñ4kµþÍß´U§Û5=N{k+oµ^ÜÛYÛùó§uq¼{¦–4oö’ýœ†3ñÿà˜úüUð(ç×þCÝùï_±pl¤²J<ßï—îßùÖÖêô¿–çÌf‰ ¯Â¯ž{ùþ¯jЋöcýœ3û?|?÷J<r ÿëúÔ~Òß³xÆh‚#·?¼>§üþ~/ÚcölÏí ð<}~,x?|rúþµîÕ•nŠ¯ËŸ~‹ÑiýXäJ ËÝíÐì_¬Úx7ãÿí7ðÂð‡…~é¾)±ø§é¾ÐâÐþÁ¬Þ|ýœ´»»k+=2[]×Lº7W:•Ä0héw6«q=ä·¯çI~†xûƺÃoøÓâ/‰ä¹‹Ã^ðŸˆükâlíÚîò=ÂÚ=湫Ikh…^êå,,. ueiå Xüyýšÿh?€zí•ûIø£\øßðƒFðλa¥&‡â-WâWƒ4í Yxþþ϶R&•«ÝëPéúƒG{¢ën¶wºÒu;v[ ¤Šø)‡í'ðSLøñ{â·ÂoÚáŽ5=CözøÉðCâGà ücð&µªøÃÁ?¼¯ižñW‡|;iâw{Ï|(ø©XkJÖq ™þx‹â…°‡RÕG‡`·üƒ?Ââå›cª,6"Qu#'5F£‹^Ê|Ê6²³»¿sépU)¬5ºME«sÆÿÒ×?j´­FÛXÓ4í^È»Yê¶z£H†9 µõ¼wP#$”s¨Y %[*O~¿4eoÚWàxKWø¯ñCö—ø¢øûã%Æ‘¯ÿªüqøv‡á_ýL]+á‡Ã/ìÆñK[išÞáæ“Ä?#„J_âw‹‰¢¾vÿ†¾ý“?èègoü=Ÿ ù¦£þûöLÿ£¡ý¿ðö|5ÿ暨ãèÿ„õùöÔ¿çí?ü?æ}E|íÿ }û&ÑÐþÎßø{>ÿóM]Ÿ¾;üø¡«\h >2ü)ø‡®ÙéÓkz/¾!øGźµ®“osie>©q¦èÆ¡yæ¡ai5ô°­´w7¶<«-Ì(ó<&.sžE^R‘ŒWw'’ólj­6ÒU ÛÙ)Å·è“=ZŠ(®r¾ý¤h¯€cþ¨·í/ÿ©Çì¯ý+îšüóý®.¼iiñëö~“Àº…üG«Ÿ„_´š\Øø·Åú·‚´Ø´Óã?Ùy¦ºƒUÑüãë©ïRåláO}Ú `žæåµ(d´ŠÖóéxCN!Ëý1Rÿ˜ WW¢83?÷Þ´ôý3f$éú{çñÿ=kJ$ÿëûÛñÿ=+ÃcÖÿhßú%_Ï®~?øïŸAÇìÒzUøµ¿ÚDc >Ÿû¸/ ŸüFcÓÿ¯ë_¯Umëxù78|Þ²ôôíkŸ7%Öï}ùëtãÓÓ¯aôÿ8×¼iäŸ>>п°†·ãoøÇû7̳f³ý§öoøM³¾ÙåéþwöoÛ>ÉÛ>Ëiò\K‚ÅcòøPÃB*ÇF³ƒ¯‡§jp¡Š„¥ÍR¬#ñÕ‚µÜŸ5Òj-¯CVÎuŒ])Å>I½\é4­·´e®Ú}ÿRk´¤7þ|;ðÝòXèò|BøÑð«âÖâ 1,u'Å^ øUkñ[Á÷pÍ<¿èÚ>·á$oiŒNöú׆õ›+¦He‚ê/¢¾üDðïÅŸxoâG„¡/„üabú¿†oµ+ tÙu[«ˆôZZÎ|ïì_éñ[ëþº•b’ÿAÔ´Û点0ÇøÙûTþÎß´í!ñƒá?оZh’^x㟇ô¯ÚÇ×’|iøcsáoxQ<(uUý—ìf𦬾øñÂ2xšÆE¼1ã­QQ“RÐ<-q§~„YüVý¤ì­m¬l~|³²²·†ÒÒÒÛö–ñìÖ¶¶ñ¬6öÖðEû%¬PÁH‘Ej±Çª"…‚|?›&ÓÃÓVvwÆ`’Ù=ÄÙ«5v›IÝ7tÒö~»†Óß–ºÿ ¯ÿ!ýo±ö-òBüYý§ß§À¿€Ã>¿´çÄ/éû%žêeø¥ûQ7OŸ?ñ'~!ÿô%T<2[Ñ¢ºëÀÿóHþ·Cù§ÿ‚«ò¿?ϳ>±¢¾S?jF8~âNüDÿèI©ÇÄ_Úœò>~ÏüÿÕÏ|EÿèG¨yF=o mñ¸¿÷2WÖh¾³ÿÁ5¿ùYõ-ó_Ã?Ž>&×¾(ø¯áÅO |;ø}ã=#ÁÞñ·†m<'ñgPñòxÇJñž§ñCL»·²ƒÄ þê±jo†7—ºœZ~™­ÛgêöW2ÝÚe¾”®*ô*᪺5”cR1„šŒéÔ-Jq«§JS„”¡8ËÝ“µìí$ÒÖH©E·ÚWN.ñn/I$ÕškUøQX”~}|J]ßµ¯Ä?û7oÙçÿVWíOÏàmD?ÏÏÔÿž y¯Ço†~"þÖž4ÿ„ÿÀ ñÈÑÿgo€Ù?ð˜øWBñ7ö_ö‡Ä¯ÚíÿÙßÛV¿aûwØl¾×öo+í?dµó·ýž-ŸþÓÿ¿doÙ‹ãWì½û<ØV_ŽŸk³ñ«þ„> ü'ý–íD¿ð ¼)áïxÿþ_|pø•ð[Âú ÂÞ þÔÒ1­Þidj¶CÈÔ_H´ÕjÉëҡ峫7¬=i/z­ENœ"¢ù¥*•'F)6Ü’ê•ÄÂSÆWåI¿i6“vÒ1æ”›jÉE&ÛnÊ×?U¢Oéùv×ùV”Iëø÷Éì?ÿÔWá¶•ûpÿÁ4õ/‡<~?dï±Ýü]ÿ‚xoþ ™ªü<Ô¿gÏ‚–¾ þÕzýö±cuῌZt¾"þÀÓ|?áì„»ñ³àoxúá,õ}"]KÖÞkȬ¿Xâý—ÿf³ŒþÏ_×á7€Ôó ~\ûzVÿYÃWMÓ¬ê$¡)rF.ÜêñO÷ŠÚ'£Õ[[4ÑΤZæ‡/«kf¯²ÿ.÷-Á*¿äÓ<=ÿa'ÿU¯Ãºùûö»øÏãïÙÓö ý›´?é^$ø£oâÏÚÇÞ<ý™a†‰¯u¯øÏöYø÷à Kà±­[ZM§èÿ¼Ià=SBñ²`±Óüñ.cÚw­gRCÇû.~ͯìïð3ÿ <p?õ?ýZñ¾ý—>=ÉñbkŸØ*„òüøùñ à­”Ÿ¿e¿‡ž“â5Ç€`Ó­[ãOÁ7{=a¼_ðCÇ6¾ ¸¶øñ#¤Éâ[{=zì«8íäY¾Ãt%^¥Oí)©W­Rª‡Ô“IJns\˯"“³q;I{·m{±ÓPŒ}‚jŒ[ö¯t’Z{.¯ÍÛÏKþ¥|(ð׋¿ñh~ÃþEïóÖ¿?øÓDøSâ]{I·ñ·‚ŸLñ_‹ü 㯊¾<²ðýÝž±­øàƒ~-ø§@ŠòÖÃYÒlu»ˆ´¦á«ai|y•U}—Ô"Ûv»I,snÑNNÊÉ&Ý’mmeImB?:Ϻ_óçÏæìèŠøÃ_³_ì¥â]Dñ%ìËðnÖÃ_Ò4ÝnÊÓÄ?üOëŠÊY.?óYú`©ëÿ—Û[wþeGR_òæ þâËÿ”ÿZjÑ_Œ~ ñ—ì/ñ#öÑøÕûü?ýˆ¼5âÿ~Ízà gö„øŸað;örÓ~|%»øÓàkâ¿k—~$ñF‰ñ3ĺï<;¢M5§ü+Ÿ†6Ñt{‹»(¼M¬èqµÔÖŸB~ÊúžàOÚsö©øàŸx/Áÿ ô¯èÿtÍ#ÂÞƒÃñiÚÕ÷Á/Ù«M½¶°°Òd³Ðm4»·¼»Õ.a·Ñ£»¸Õî®/e½s<±· |+U£Š«VTRnÂÆ”Zu©Ñkž8ªºÆU6äkÝjéšÂ´Üã SŒT¯fª95î¹-8î—~©Øý¢Š+Ë:¿i_ù,ßðOŸû;ßë~ÛÕõí|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¯h¢Š(ä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{_!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚøïö¸ÔÿeÀ;þÑ:§þ³í%_Ÿ¿ðQÿÛ3þíû|fý¯ÿá[ÂÝÿ…D>żÿ„ÃþøH¾+øá‡ü¿ð‹xÓû'û(ø×ûsþEOíÿٿٿèmþдýý­uoÙhú8WÿY‹ö“¯É?ø-ŸìÕñ«ö»ÿ‚a~Ó³Çìïà¿øX_¾!˜ÿ„CÁÿð‘xOÂÛ´ÂkàxƒÇï†|-§ÿgø[ÃZÞ©ÿMnËí_bû—Úu›;IÿOÈ*V§Âõe†æxˆÃ0žB ¤Ýd¦éòÓjJrçQ僌”›QqiØð1‘ƒÌ"§nW*1Ý—#åæ»º²³wwVÞúŠ¿à¨Ÿ¾ øëö½ø-ûCþÌÞð—ÆÿÙ—þ ÿãïø(„´o…?´&­ñ[áÆ„ßÄš.© ÂÄñ/À/„>(ø{â“ãm/ I¥ð¯_…,îî©©]xgWø»©økþ:†—po_H¸2iöÿ8|{ÿ‚q|,ð÷ì­ÿ°ý›~ø¯Åÿ´×í5ûünøcã/Š¿¾(üfø¡ã›ýgá7Œü=ðïáë|]ý¦>(x×Xð÷…äñ.±c:uÏ‹ô/YÝÉ©«t±[ÛoÉÁ7¿o-Ãÿµ?þÏ–þ?Ö¼[ÿËÉÿ¤ñ†í¾.|#ðå×ÂßÚb×E´Öî­üI¨ëÞ/·Òµýþg¿ðŸ‡u¿Ýø£A¼ñ„kâ kÂÞ¼ŸÇ6;×Äæ8iµ8ËI5ìéó¨óVj7:œçY4©Ár¾f›Nn!N…EtÕ=b¯)ZöŒyœä”y›jòz«]l¿¥ïþÔÿ²ÿ|5àxßöø àï|Lð¥ç>ø³Å_>x{Ã?|§xnê4ðF½«øŠÏKñ_„ì<#smâ«Ïh7WúE·‡'ƒ\šñ4ÉRé¼~/ÛÂ?ðÓCáoÚþÿÃ8ØOþ‹þœþÖ_¸ð—ü,øGMáøý©ÿ ßü(‘àCÿ þËû@|ÏüR?ÚŸÛ?¾¯æ[öÀý“¾3|ø'áxßÀ? þ%êŸàØ-cöGøÙðGZøÅðu¯à‰?ðhG†¿`ïê×^0ø}áë­Sö±ñŽ‹eâü%¹ÐÖn¢‹Nñ•um¨i77v“Å+üÿûA~ÙÚïÿÚ;áGìkðá.•ñÏöø¥ðÛÆ?®|9âÿ‰w_~ü1ø#àgMðµ÷ÄO‰Ÿt¯‡?üM§Aâ/jö> ð^á_…Þ/¼Ö5áxš¬žÓ­“PŸóþ ×û~Ò?³¿íÃûüWñ‡ÁË|*øuÿÿüýŽþ)ëZgо]Áaûbx_ã_†ø?➟á[¯‹ZÇüGøi7Æ?ü2ø_â9-¼_m¨øÆš'‰>$x6úÓC¼²Ôôâ´¸Ó­ùêb+Ί“„©OÚÆ2´’¦Ü\çÊ-½+|²µ¥$®´Ö0„e£R¼n®ÒMÛfÖÉú®‹KÜûŸµ^ ø(ÿ¿n?xö ¸Ó¼a¨x/W·øÇû@|Ô¾ÝÝÇu$^×¼!ñnÓÄZF‰ªxÆÖ±Éuá›hÿ¾!Ÿ²ßÁ­ü?Ñ^ZëÕøÛðN_…_ ¾(jþ/ñTžýŸtÿx'ZšÖ³à;øºÛÃú&½4¾ñç‰u‰.ôËÿfŸø$ÏíYðoÅðn_†þ)xOÃÿ|+ûøGþ (ßµw‹äñOƒµo|3Ö?h?‡pê?|1a£ø³X°ñ/Ží|/â¹ôÿè·þð߈l´<e­]BmûyÕq5nãNQ\Š3œeÔå$¡›U*7â¨ÉÉ(É[hB6æm&ï¢z$”“»ÝÅkw~emÿKjø+Â?‚¿ÿ`ÿÙà–¡û?|vøŸû}^x’ûá¾µâ/Ú—Ã? ¾øwÀºFšïák9ð·~6ë£|oñl7¿ ¿gÝ?DðUÖñkâV•¬øCBñ z®™s ýKñïÆ$ðoíeûÂ9¨ÿgÂS¤ü[ðv½þ‡awöï ë¿eßí=7ý>Öëì¿jû,é–g¿ƒgú=Ô[Ÿwó÷ÿëÿ‚c~Ý?äÿƒqdø·ðMü4ß°\ðWxjö‰Ÿuãð¦?ÚnAð 0|;ñX>9OC©éQF¿ ¿á1Že_,¿xÿjeÇíeû ØGâ?çÿ +öcÿëW*N¢Ä9Ýsa±.1µ¹o€œ¤®Õݤåof¬­ª.QI×TªBî÷»öÉ/M,ôè׫ý<¢Š+À;B¾ý¡ÆïÚ;à ÿª)ûK‘ÿ…×ì©_pWÄ´ÏíðÕý¦3ôÿ„ïöS¯¢áGlÿö´qŸúŠ8³÷:¾´¿ôõ3ðßþ ¥ñ7Å ~Á=¿°~!ürø{áˆ_ðUoÙ;áÆ/øg|jðŸÄ¿|ñfñwþÃÝ/þçP°øÅâoøItí>°øKÀ?mñf³¯Ùh£Âº}ÏŠ-´MŸ˜µ÷ÄÏÛ?à¿ü—þ Sñ7Àÿm¿„ÿ³‡~(þÆðïÿ|~ñ‡í àÚ£Ãöz¿Å_ƒ¾ý¥HñOÆ­KMý§ô†šÇ‰5TѼ¿u }JûÂúŽ¿§x/5˜eþ™ÿjOØóá§íyÿ ßÿ '[ñΉÿ ÁûV|ý°<ÿ>¥ é¿Úÿ¾ ÂCÿ®…ãíÿ ø—íþÔ?á%¿ÿ„ƒLÑá×î¼›Oìïi^\ßhÐý¶¿c¯†Ÿ·Çì¿ñ;öLøÃ®xëÃ~,Âÿ µð×Sðþãk3à_ˆ^ø•¤ RñG†ý“¯>>xþ ÷áÙ_Pý ¼M{ñ»MøL4ïþ×Zå•·Ã/hZ=ÇÃoÞkz%¿‡/mõMsÄÖRÉyi¯_ézø^M6mWÅ:7¶þÕßðE¯Ù‹ö¿ñíaâüIý£¾Åûkè?tŸÚÿ ¼uୠÞ/Õÿf]OMºøEã±ø§á§Œï"×´ #Oo O¦ÜßÞø"ÿL¼}boÂo¦x{Åš/æ_üÏöÖþ)Aûhø›GðW‰¿gÏþÓP~Åþø—ûXxŸö¹ý›¾þÄZ¾‡ð“âOSJøûXø7â–µàŒ^ñÿÃØ5o|.øiῇ#âgƒ¼s-ïÃívëì~56ZNƒåã«f”¡‰š¨¬GK’ç+sf§§²’Š…9eð›«îÚ•OyT›œº)G)SVék¹$¨'wÌž­Vk•ßU¥•—¹üoÿ‚›ÿÃülÿ‚õül> øíñ´þÆðëoø´ž?ý©¶|¹ÿ†ˆð–“á!ÿ ÿà?øRz§ü3_‘ÿ gü$ß¿âkñoþ‹´›-gþ(/øöõ_ö@ý¹uÚKöý¶¿eÏü—à×Åا_ø‰ ·ø‰eñ#FñW„ÿh߇ßþjÉ©Úx[ƒDñM¦ƒ§]YxÛ¶ðøGÐõ¥Ú¼Yb´+ó³ãìÍÿzý°|ÿRø—¨ÿÁC<ðßöØÿ†¶_ƵŸì½uà€ÃöoÕô­öwþÅñ\ú‰´…Ç⎽á„Ñu/øZ—þ+ÿ„ÛWKý7ÀߨWª ·ý:ýžþ~Ì^ý·à Ÿ>|cÿ„×öøÿÿ £ÿ WðþÃ_ðª¾ë^øÿ‡4‹?|7ÿ…“à;ÍGÄçþ6­¬ÿÂaöOíŸghðOlüŠxŸjŸ¶§*¥Y8ÅÒmÂu³ ÆÍG™Ë—êªÊ_bªwjMê£î>hÆ*íIYÆ4~]ý¥î¾Ô{£âmQñŸíÑÿFÿ‚ƒ~Í~.||ø_ð?öøSû%hüðãOÄßÙîûÆ_jx¿â‡ˆþ1x§Æß»á/ƒKᯊ/¯£ÔôÍÂVÞø{o­Úxmu/øÝ5JçS½ûãì ௉ÿuÚ;áׯŸŸ²¯ÇOü5´øAñ#âOìç«ü.³Ô¾'ü;Òo.ïü7£ø×@øÉð›ã/‚¥Ö¼#u¨j?ðˆüBÐ<1 |Lðõôú^›ã8t”·°ƒÃþ*ÿÁ?goŒZ'€¬¼Cñ›ö¸´ño†g~É?~.ÿÂð1øÇûE~Í?|Dñ/ˆ¤¼Ôÿ†á ñG†ãÔ.´ŸxƒÃzvZeŸUZ*£ñ%Rrö®I¹S•NjtÒ’i8S冱²P|¿5,š¿Â’\ºèì“zwi·ßªÑ ñÛþ s7€¾ _øsàOì¸~:x*ø$ÿÁb,|{âÐü ’ãà¬*¾³Ô|}áy>|@¹±×‚,^ѵ;=SW“Qñn§¤ø6ÿÃú.‹&©ãí#Ä>~Õm?ø-¿ì¤Yßx»Â_±¿Šÿàß ¿à¡þøoö£øÃðÖi.¾'üJðÛh¿¾+øᇅ|7៊þ8Ó|I©Ú|½ýŸ¾ ø¿Å¿gøb/¾1¿.|[u/ÁIJ¾!ÿÁ$eψŸ¼wãøõ/ŠOÁ0|Qÿ•_x]ð‡üû-ø›\¾Ö—QðeŽ»àok–_<:·£Hðøƒ]𕾑gg£àmNí&½Ÿ·ýž¿à˜ß¿g_¿hx·âî«ã_€¿ðNŸ†ßðLŸi~+×¼}ámGà/Âïh¾8ðÿ‹.ÞjÚ¥¾³¯izÞàÛ:K˜l¼§Ü´WqyµyIsÍ8ó©Yr­Oùy¬ÞÉ·²µÍ—.É{ÍY=w²ùi¯NþF¾¨1ÿ7ð0ÿ«|ð†?ð3ö©þ}kôâ¿2õ‘ø)ÏGý[߃ÏÓý3öª¯ÓJáÌÝñm÷Ã`_þXáͨ+S²é:¿úvaEWžl| ñsûZ|Göý¿glû©_µWòÿë×âçücàƯŒðSßø#.¿ðÂóã—Ãï |>?ðQøXŸ´?ÁøSÅŸð¤ÿá,ýŸ<eá?øIu¿‰ß ~/|ð¯ü,GKÔ< £ÂÃð…é×>ÛªÚxDÛx¦ÛOÕ4ÏÚŸ®ïÚÓâO·ìíû:þþWíUÿê®®$õü{`vèkõÌ(â2,¶Œ¥(¥OZñmIË^–!$ÓN*Nš‹”Z”nÚwHùªópÅ×’I¾j±³Wþ$eM»líÍ{=¬÷?œÏÛCþ áÀ‡?ðM_‡ß³G‚¾7|gº_ø/?ì³û_þÒ?ïôÍKâoÄ=kRׯ¾#_|_ý¡þ2êž ð¾›¡øC³-áû_ø©<;á_ø~Î-„°i–K€µ¯ÙÏöÐ>)Cð®oÙoöŽÔnÏü½á/ÛÖçÆúÁÏk -?eoÛAø¬þ?Ó4»ëÃQBš”þ,Õ´ë‹Ý+áÀÒÚÛâN¡á+ýcÃ6zçö…túþ½†}¿ÏZÒ‰03þIõþƒük›–Ñ”œ¡7J>âPŒb⡨´¯«rqO™Ý¶å»wZÓ¯4’iMêÛmݶӿ¢¶ÖÛÐþÿi߃ÿ>þÛ? 6xÿþ†ý˜ücð×ö†Ö´/7¿~Æ:톜ÿ³÷Ãøî{¸¼)â]OÂ0èþ!ŠÃšM®©ª|:/Œ-µYü<þ"þ!õmwö=ý¢üM ë>Õÿfÿ³i> ÿƒÊ5Ú#T†×áoÄÍ>â_ÙOV³šÂûöˆ·Ô4ýÖþÇà{ÛO*Eñ¦ÆæßÁVÒšÛÅÍH¿¦¿ðT?…¿ ¿dO~Ãþü'øo/ÆÏÚSþ Áð7á-¥çÇ-sö‘ø©ðáoÄÚkMø«‹?i ~Êžý¤>|_ŽVzÝ¢k’xÎËMÑ5íb}OÄ“Ük6ºþ³ˆôÿ¸¿h/ßµìáŸÙÓâ·ÇÏØ»Yñ—íÿ ø û6êZö¥û9~Óÿ |1â…4ýn;߆_ ¼#àŸ‹¿µF«eûKjÚ¯‡5Ï|Gø©ão |¶°–M3Ç6šÎi«ø‹Á©‡„jb#RM(ÍI¸¸É~õ+{IOÙ¹N]m¯ñI·c®3“TùV­[[«rèì—2ItÖýsÉÿà‰?¾$|ðWüOῌþxëá7€t?ø+?í¢ÿ³Wƒ¼]áx/Öÿ³eý×Ãëï‡:ÁÝ'^°Ó¬®> ê7søŽãÁº§„b—Áú„í«Ë£ÜÏ(¼jòÏÙľ;ÿ‚`üGÿ‚›øSâ÷ìáû[üi³ý ¿nß‹¶ïìóâ/Ù—özø‘ûAZ|Zð¯Çïx†QëþÒ/<ð¯ÆŸ µÏ]èö¿/ðæ­k=޽á?舴‹…TÔ4Ë2"é:NcJªR¤¦éórórËšr§uh¯²Õ¬Òû7‹§ÍÍ+Æê\©öû-knÛõ×¾§ò¡ÿýžmŒÿµoíSãß þÏŸ´\ß´Æ þ ƒ¬Á%~,ZøKT×´Ø®çÁž2Ó5ßÛCÿþ)øVñ/ÂßÙó_xŽëâë~-}âF—pšO‡5Ø=´RyŸüëö/ý¢|qñCþ7øÕðçöeý .ýgðæ8ñ§Š¾èëºÂ%«_ÿmüP±Ñµ¯ˆjñe­Ê}÷ð cþ û\ú‡xsÿU'ìÃüëïèÓðõö‡ãþGð'Àqø(_ít=4ïê£ý˜+švXllSºöJ^®Xì4ŸÉsYyz•ïÒo~k|•¯ÒïÏÐý,¢Š+Å:Ï¿i_ù,ßðOŸû;ßë~ÛÕõí|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¯h¢Š(ä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{_!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚùö°Öe‘ÿWªÿë0þÒÊ©D?OêOùþ•¡ûVŒë²Àÿ«‰Õ¿õ˜?iJŽ$þ™ÈŸð¯ÔxbVÉh¯ú{ˆ¿§µ—ç·ü9óØõ|\×÷aÒÿe_4Iøä?úÿýZÒ‰=}¿úÃü/J†$éßúŸþ·ùÖ„Iœ£ùŸþ·lW¥Z¥¯÷ÿÀôKWòÙ™B;[eÛÕ]:µv¬~rÁRþü$ÔÿboÚ»ã׊þ |ø‹ñoöuý”?hÿŠ?|iñkà¯ÂÏ‹÷ÿ üwà„¾-ñ…õïZ|Pð‹´¨_Nñ.‹¥jÒi÷V¡5œPê–¶Í$/ùóû-~ÚZ7Šà~=ý¢þ õ­üpý¤ô_ø%§Šh߉ÿü#eÿäø™â?Ù‡âð3Ãþ9ñ¯Ä¯ þÎÿ >|ñíö·ðWÇ·š7Ão¿âøs}«[xâMÔzÍþ›â='öóö ø!ÿ +û1þѳ—ü$ßð…ÿ>.ü2þÅÿ„þ?øZ¿¼CàSâøG¿µ´/íïì/íïí_ìoíÍûPZ}‡û[Ný²ö"ÏüÿÁ9?áfc?°ý‰ÿárÂÓ?³éøÿ 7þçü%}ÿäiÿ„3þžŸñ#ÿ„¯ñ7¯žÄÆ£ÄJ¤=Ø<<“´¥j¼Ï—™B¤/.]¥5$“²hí§ËÈ£-m5ºNÑÒÿeéÝ+_ñ_˜ÿ³gügâ•ßí­ðÃß?hWIJM×ü£ð»þ 7ñ]ñ¯Â?†5Í{âœÿt+~Ð^-Ѽoâ½GÀz¶©ðѵ½{Ⴞ"ø“ᇅîç»´ðݶ±=–¹súIûÁM~þÚ3ü%à |u«üð'íQð÷Âÿ<1áj_fo‰z½æà> ñçÄ 9¼ªk6k§Ic⛯ xÓLžûM:¯„ì⽊Sò×€?à‹~Ñ|qàÝkÇïümà}#þáïø"÷Œ¼¤ü;Oê¾-𦟫ÙÝx‹ãž›âÙ(>"¶ñ§Àÿbüð÷À \ü*ý‡þ~Î~<Õü1¡ëZ~­{㎼<þ5øÓñ«â§Šcð÷…-¼[©Iãïü4ñþ‹u…øæuñ-·™Í‹‹Šš¼›Ÿ4ÔÚV,SsrKFÕ›w»’iÜèµ9^Û¥$š^}>ÿÁöý‰†>ŸçüüúV„i’1Ðp>¾¿‡Rj(ÓÿÈ{ÿ^™5¡tú~œ~§ü÷¬+Tß_Ÿëú/†•ì‘4IÓôþ§üÿ…~~~Õk·ö²ý†=õ‰ú²¿f/ç_¡Ñ'O§éØgßüô¯ÏÚÁvþÖ_°¿©Ô~$çÿWìÃÇášâŒï*«¾ÿ¨uì¿_øsV¬¢—J”¯ÿƒa©úcEWˆu…|Qñôgö’ø ÿdGö˜?ü'Ÿ²ž+ízø³ãÀÏí'ðÕý¦ø¯?e*úvÏ0o´1ÿ,q'aþçWÖ—þž¦^‰:ž{Ÿ ÿ<ŠÒŠ>ŸáùŸÇ·ø‚$éÛü=>§ü÷­(“¹§ä?Çòô¯ÒjÔß_ŸæýOòg‡ÛN®×ü¿­mwК4é×ôçßž;×á¯ü¸˜ÿ‚#þÚÇ?áœ3ëÏímðŸSú×î¬1ç’9ÿ8ÔþµÊüHøSðÇãW‚u¯†_þøâ×ÃÿfÿÂGðÿâo„ ü2ý§ÿb¿„¿ íÿ±¬¯4¹ü)öÿ |Jóu{ÙµûaàÝ{Â>Ôµ/ˆ¾>~Õÿb_Úoþ²ý§~év:¯Å_†>ÿ‚>7ƒÆ§¥oOÑï¼eð0øïÅ·zA £ô¿ßøÉ­o’M.Q íÕ¢›Mû\OýcüIøSðÃãWõ¿†_~xâßÃfü=ø›àÿx÷Àþ þÆÕôÿi[🊴ý[AÕ²µý+KÖôß·ØOö _M°Ô­|«Ë;i¢e§Àï‚öÞ ø«âÛ„? ñ_Ç}?ÃúOÇCà G⌺_„ô ¯ x[Lø­¬¦’º—Ä=?Ã>¾½ðׇ¬¼_s¬[hÚåÖ§Em§ÜKnþ6*IÔçUÚj1QnÜÊj–2Ÿ:QQŠkë0z(ÿÏ[3²ŒR-õÙlÓ•'k¶Þ¼Ž÷êû™?ðJŸÚö¯ø«ã_ÚÿáGí1®Âc£üñÀÝKጼuã¿ØoUý£ï<=ñká¥Ç‰x·EÕ¢}r÷LÖæÓÿg"L Ÿé×üó¯$ø9ðà—ìýᙼðàç¿‚>ºÔî5»Ÿ ü"ø{á†Þ¸ÖnㆽZ}ÁºF‹¥Í©\EooÅü–­u:A I3,H«ì‘¦HÇn×ÿ­×õ®Få j2››\×”Ÿ4­)7g&“vºŠm&Ò×[²ôè’ÛemmfíÒû“Dž¿|žÃðïýEhÄ>¿¯ ÿ=ꓦ?~¹'üúñÒ´¢NŸOÓ¹úŸ¥yõjoý&û|¿­M`¬¯Õþ_×è~xëÃðS¿ú·¯þ?鿵_5úa_šž#à§¾õoü?Ó¿jÎ+ô®¸s|B}ðØÿ–Xsj?Ãÿ·êÿéÙ…Q\F§Â>6]ßµ§Ä¿oÙÛötãÔŸ‰_µf+±‰:œžçðÿ=+“ñ‚çö´ø™ÿfíû9þø•ûVdÿŸ¥vñ'à?ÿÿ×ìkõ¼ªVÊrõù…¥ò\ªÿ{þ¶>o¿Úk¿úy+_ðIâN˜ü;ýMhD™#Ó·Ó¹ÿ?­Cvüý€ôÿ=}«J$Àúcóì?¯ó¥Z¦úÿ—ô¿=Ðàºé¯ÞžŸðÿsV¹ù{ÿIý‚üOû~xcö*ðF“ÂmSÁŸ¿à¡³í=ñ¿Â?#Ô/|1ñàOà ?â‘ñ+áõ†màÿé^,×¼Y¥xÊ-2מ.´Ò<®éòj–Zþ¿alË×%û_Á-¼3㯠þž ýŒ>þÍ?³oƒ?fÏø*WìÃûyüUð…¼§üðÇŠ¼/ðwOñ>“ã‰|7 |)ø}y¥kŸµ½&óÂÚf‘'ˆí4-?SÓô+{-_Æ:e¶™§Fß°§çýúÓïZ¦H‡êÏ&¼Zô¨ÍÔ”£ïUQR–œÊ4ík;]'mRÑp”¢¢»]¥ëß¾¿å­æoÅßðD_þ1øûKxRëâgÈ>%ÿ‚íx÷þ ïð.×@ø‰ñ—Á:»àNMÓÃ_ ¾$üSð†¼/ñ[àÏŽoôi|HÚ‡Ž>Câ½Wáö½‡5ïøRÔ­ÅÞúåÿØý5¿ØóàßÄ x¯Ã? |'ãOŒ´/Å/ÚÆZO‰_´·Æ(ëßÛB±mCÅÿjßøÛâ—ʼn:¦™á­2÷Ç߯‘ð³ÃÞ+ñ ×zµ‡ÂÏj7¾§¯~€Æ™ ~~çÿ¯ü«J$À¿o¯Cþ¼ÙS¥J\ñO™G—V›³mï½Ýõ³µº3¡JN:õ×NÖI~WîMt‰÷>ŸjЉ:qéÛ¿aþ­Ctúþg×è?úõ£tÿÍ¿ÏôÃV¥úüöÓ«ÿ.˵‹„nïÑ~dÑ'â™ÿëõýkóïàPÇü7ö»õðßþª/Ù‚¿CâNŸçŽçê~Ÿ¡¯Ï?ÃðQÚðtÿ‰w†¸ÿºAû/×—5jÿ¨hêfß«4ûtŸ÷åÿ¦ª_Ò?I袊òΓä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{_!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚ(¢Šù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^×È_´¯ü–oø'Ïýï?õ‚?mêúö€>FýªFuïÙXÕÅjÿŸü2ÿí+Ö¤‰:ž{Ÿ§oÿU}㟇>(i6úÄ¿ø3â…g¨Ã¬Zh¾9ð¾‡âÝ&×V·¶»²ƒT·ÓuûBÎ F =BþÒè¡[˜í¯nàIV+™‘þÿ‡U~ÉŸô/j?ø)økÿÎî¾·*â6O V…yΫ.j~ÍÅóÍÉ|S‹M^Í~'›ˆÁNµiTŒ “QV—5Õ’]ZÛú±ôiŒc¿Oñüzý+Fúqõúõÿ—Jù»þUû&н¨ÿà§á¯ÿ;º?áÕ_²gý Úþ ~ÿó»®™ñ6òç¿íÊ/ÿs-Þ·"8 «íSóÖJÿù'õ¿{ýCdƒÓ·§ãÛÛ¥iFsÉÿóÞ¾Lÿ‡U~ÉŸô/j?ø)økÿÎîøuWì™ÿBö£ÿ‚Ÿ†¿üîëšYî_c»þê—ÿ4t4XJ©Zôÿð)kÿ’a"cwëíÿÖç­hÃ8ÿ>ŸÔþ¹¯‹?áÕ_²gý Úþ ~ÿó»£þUû&н¨ÿà§á¯ÿ;ºæžkƒŸ\Rÿ¸·÷4¿à*Rµ©ÿàrÿågÜQ'søÿAø÷ÿÒ‰:~é×ÓüÿZø/þUû&н¨ÿà§á¯ÿ;º?áÕ_²gý Úþ ~ÿó»®Iâðrÿ—˜¥ßýšËþbúUcöi·ÿ_%ÿÊÐX“§ùçüùï_ž_µªãö²ý…ì#ñ+ôø•û0dþucþUû&н¨ÿà§á¯ÿ;ºìþÁ:go…þ0Ð|qàÛM{H×|=ªézµ¬¶vþ°K§ÒuK-^ ù4¯i÷“éÓÞiö­umÜA²I©©‡·ÂÁÎp©ˆœÝôãáéS‹u¨Ô£w5ЍÒ?5”%~^]/u\•”£¹¡+©ÉµË8ËDé¤ïËmÕ®Þ»xQEæ_þÔVÞ5ðïÄO†¿´O†¾+ø‡àÿü%øý¤xÕ¼«|;±Ôü<|C®| ñ>™=üuàoí+4χ>(2 mVþí­¡{öÈýµEv`1µrì],eSJJªŒj©ºmU£RŒ¹•9Óž©'YÆÒI»«§•jQ¯NT¤ä£'ÜZR\²ŒÕ®¤·Š½ÓÒþ§ãr~Üþ\gàÇ3ßðO¯où­ùïV—öíð¢ã?>:à³ðK“ÿ‡·üô¯Øz+Ü—c¥¾/éö1?îtäYu%µJß}/þUýj~A§íëáÿšñãö>~'þKu[öüðrõøñëðàâyøáÔþŸ¥~¸ÑX¾$ÅK|ÿÆuÿ¹ÂÖšÚ­oü¥åÿN¼É„ÿ‚ø,Ÿ¿8éˆþóñià¡> gàgÇî:b/¿üü‡NÕú»Ec,ú´·Áàv¶ØÏþlþ¾â–+jµò—ÿ*?+Sþ #àeòB¿hÂ?ðú~>õe?à¢þ^¿¿h/N!øÓÿ¨ä×êMŒ³iK|ïÆúÐgõ©_V_óö¯þRÿåGæ ÁG¼½~þП„zzø¾ß[Oø)7Ãåëðö†>¸·øøÍxúgEc,te¾ ÿcWþîuêZ¢×ü½©÷Rý) ¾.Ú|xý¿|ñ+Gð_Œü¡'­/ÀÐÚx强µî¯áË/Ú_Ôî-àð_Œ|ifšrYøÓFН/í.f¹h¶žT 4¿°”Q\µëŸþ8ýïáÿúñ˜éû_||OˆŸGòøñ_¦RþÔÌÿècŽÿ¼Gÿ,«ÐÿŸð\?ù%÷š#þ ûãaÓöÀøü>Ÿ¾9ýï4áûxätý°ÿhôøñÏÿŸÕ~–QKûK1ÿ üoþWÿåƒö?çÍ/üò?5Gìã±ÓöÅý ‡ÓâOÇAÿ½êœ?`oŸ¶Gí>Ÿþ;ýïuúQE/í {ߌÿšßü˜{?óæ—þ ‡ùy#ód~ÁGOÛ+ö†O‰ŸÇþ÷ºõ¿ÙßöF€Þ>ñOÄ ¿Š>"ø«øÃGŸOÖNœ]ãN}Ô"žÖÝ+í§ QEÎYòí+ÿ%›þ óÿg{ãOý`Ûz¾½¯¿i_ù,ßðOŸû;ßë~ÛÕõíQE|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¯kå¯ÚgáïÅßjŸ³‡þ h¿,üGøWàÝ;GÓ¼ð¯ÅwW^+KÙ¯SO°°Óï'¼?8ÿ†•øÍÿHùý¯ð´ý‚?ú7¨ý¥ä³Á>ìï|iÿ¬ûoWçïíáÿwø‰û!þÕ?¿f‡¿²F‹ñÑ~ÿÁ3|aÿ;øã}gö‹ÿ…Bmþ|8ø­â_‡ž<ð^‰áŸøR_Æ­â[ #Ãâ?Ý7ˆ,¡×õ+ø<9ue¡ÚE?‰”ô þWã7ý#çö½ÿÂÓöÿèÞ£þWã7ý#çö½ÿÂÓöÿèÞ¯ÊÁÀ_ 4±Ã/ |´ñ_€loþÅ>Ó¼AÅôÿ…ÇðÒëöñøg¦üKø)â¿|ðçÃOxÃ’ÛU]óJøñ÷á·Æ=vïCñgŠ|ðÆ? 4k?k?"þØð[¿ÚçâOüÛö¯ÿ‚ƒ~É¿uÏٓᾤøZý’?k‹ßˆ~/Ýø«ì·ÿ†ÿeoˆ>ø…û>øïÁo©|8ñî­á½3ÄÞ!ÒídðÏÅχ«áZÜEñ3Gøƒ§Å¡Ú€B?ðÒ¿¿é?µïþŸ°GÿFõðÒ¿¿é?µïþŸ°GÿFõ~hø7þ á²·ÄÚÛÀß<¤|<ƒöÆÔcÏ‚šÆ·ÃŸxóÅ:FŸ};ê~h¾!üYøwâÏê¶VÚ7ߟ ¾~Ñ|Mÿ ‡5ÝsÁ>ðÓxŸWð¯Í~ÿƒ‘“âçÁ?|QøûßøËÄ·ðNOÚkþ _ñ“Àþ;ý ì>Øü5ø1û2üeñ÷ìý¯èñ}ÁÿÿÂÏñ_ˆþ'|6ñ Ó`ŸÃžŽÏÁ‹câ)Ò}^ëþXÀ?p¿á¥~3Ò>kßü-?`þê?á¥~3Ò>kßü-?`þêü`Ñÿàâ=KÇ$ðw‡þÿÁ=¿hŽ×ZWì÷û |xý¢ô‚:ÇŒ^5ø_ÿ Çð§Âÿt/ ü:±øOû,øçÀ<†¿þÀ5O…þ6êÿ¶ï…> þÌþ>øûðÞçã…Ïìóð;â×Ãï|1øùû*þÏ·^ ñ7‹~ü øMðçÄ? þ$êµg‡|Eá3¥üÖ>4Øë|qáh¿üYãO|?Ò<'á'üÇö‰ø·ûO6‡qðá—þ |ýž¿oo~ÐÚ=ׯÏxsÄçÄ/Ù³Â?°_Åï x“á½ïí-û(þÌ—6>‡ÀÿµßÃé:ÏÆÿñ,?¾*üA×õ-'Áÿþk´êßü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_›Zÿü+â¿ÄÙ þ ¯¬h_¬>~П±ì/âïÚCáÿŠü7©üCø“àˉügð¯ö›¿øSâ]"OÚ#ö^ýωµŸxïötñ(ñ-Ãoˆo¦‡OÓt¿ˆ5•¥û|TøÑ{ñ›âíÄ5sá~…ã/…ž…á jzG†~øoVšÃ_²¼ÔaÒ|I©xªbt ý­MfÛ¯üø‹ð×á–‰àïøsÁ_~$ü_Ó5ÛÏx³QžÛ_±Ô4ï‹¿<©ËðØøBÒ|)ã/ i:“àñ6ã]RWÁ¶Pxo_ѼCm¡|6¾økN-(ɸÚIµiFRVm>h¦åu¢š‹jÒW‹LI¦ÚÖñ²wŒ’Õ_FÒRßWÒz;4Ñô×ü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_>ýª¿ntø‹ÄÿøÓö×o„wßðUoŠß²µÏ‰üUàø&¥ûÚ|"µÿ‚—|Cý¼ àIø{…ÿ"±ñ=Χø?á/‡üsâ¸n,lþ9]Åã_ÜøƒáCMy©úÿíÿSý¡åø ÿÎñ·ìÍð3á­·ÿoφŸ²·í«Ù|Pøá«hÂï|Gý­?à™u߃öú¦û:üK¸ñ½ÇÄ ¿o?áî§ñ#þ¯Þ|7ðž•â‹>𯌼_¤ø{᮫#?Mÿá¥~3Ò>kßü-?`þê?á¥~3Ò>kßü-?`þêø;Pÿ‚Ééº/įÚ-WöUøñuû8~ÏQþ×ö~"ý ü7ð¿ö–Ôt¨µŸØ“Aø¥¨üaºñˆµïه¿²¦›àÍWÄ|uðëáæ³áÛ â'Œu߈2xWÂþ øuá-CYÕ#ðïÜ¿³Wí%ñGâ‡ÄOŠÿ¾>|Ð~|iøWào‚ÿo<9à¿‹ããƒu_…ÿ/¾,h~ÔbñÄŸ~ÝZxÏNñWÀߊ>ñÏ„¿á»Ò´[Ý OÔ¼7ãx_Óµ†¹ÿ +ñ›þ‘óû^ÿáiûôoQÿ +ñ›þ‘óû^ÿáiûôoWçÏÁ/Œ·¼ká{ÚCÇŸ—ö…ño†¾+Gmû/|RøQû5èÿ±÷Ç_‹øWãOhÿ ÿcOÚ‹àG…¯>!ü:Žk Üø·ÃÓ~Óž+øµñ?Yø-áOßëŸ4ŸiÆ©à¿CðU gâF¯ðvÇö_ýžôÏŒZ_ÇŠ¿¾ x;Äž5øÏ/Â-#Oø‘ñCöøçûzx³MñH·øOñûLÓ~|&øð’ÛÅr鶚߈/5o‹^%Óì<0¾!øY†¾ }‡ÿ +ñ›þ‘óû^ÿáiûôoQÿ +ñ›þ‘óû^ÿáiûôoWÈÿ ¿à¤?¾9ÝÉáo‚Ÿ±†ãO‰¿ 4ëµ/€µOÚW@ðü z‡ƒÿjÏÚcö>¹ð—ÀOêÿ äðÿÇø—âì}ñ÷WðlÿnfÏ \x'Að®©ã/xZñ|Ó¾7ð¯ÇÛ×Ä~ý±i)~0~Üš?„ÿfo?·ßŠ|7k©ø/þ c}ûüVøOûþÛ~Áû>x7Gð¯Ãþ ‰u/¿ ¯ü+/‰üj|uuã¿ ø“Ä6_/-¯¼oâÐØøi_ŒßôŸÚ÷ÿ OØ#ÿ£zøi_ŒßôŸÚ÷ÿ OØ#ÿ£z¿;¼uû~übø_ñöý¤5_h~'ý‰õþÔ ?eÿ Ùhþ“ûSöƒý¾Ùxê]SNñV—bž%ñ,Ÿ¼kà_Ûáå–yâ OEŠ/Ùûálþ ±Mcâvµþ˜X|oøwû?xcöhøSûVþÓ?ôOÚ➃ᇾ—â?޾ü/ñWíñKÓüá¿?ÃÍwá[oøƒ^ñ·‰´Vo |9ðûÁ§ê>1ðþ‘¦èÖQêš=” ðÒ¿¿é?µïþŸ°GÿFõðÒ¿¿é?µïþŸ°GÿFõyWÆKö„øÓûWø»öpøOûAø§öZð×Â?Ù«ágÇümà‡ ;ø×ñã·t _§Æ¯üBðô~Z|›Uø¢øCJð׎üC7Å/çüHðT6jÚ¿ /íõñ7ž<_ ø‹àσ|mðÃá_ÆÙ/öHý£ÿhüRŸA¸ÓÿiïÚ¿Ã?ï¼7Áïš‚¼A7~ íþÓŸ`×üa®|dðωt ?ÜI¤xKÆöþ ñ&¡ÑÿðÒ¿¿é?µïþŸ°GÿFõðÒ¿¿é?µïþŸ°GÿFõ~{|ÿ‚¾|Jø | ñ‹?cÝ3Á~ø½àÏø&oÅMvóEý£­¼i®x á_ü?â¥ðö}ñö-ßÁŸÚø§Äz_í¡ëZŒ|'g­é±éŸím~)oÿÂcz> AwÀßðWjß>|Pø…û"'‚u/ÚãöeøûA~É^ Ð>?i~8¹ø‡¬|fø¯û)þÏÐü9ø®OðÃÂ:¦üfý´>G¥x¾Ú/ˆvzׯj;Ö4xÇÞ!øQ¥€}ùÿ +ñ›þ‘óû^ÿáiûôoQÿ +ñ›þ‘óû^ÿáiûôoWÇV_ðQ¯Ú_Ä¿"ý–|ûü7ÖÿjYþÓëñ_ÂúÏío¨xwàß„5oÙãß°·Ä þ?‹‘þÌú׊|yáÏ‹?ÿoo…šž‰¬]üðf»áO[Oá_øFÛM:ß‹|/Áü#ÿ‚ÎÛüzø§ðSÃ_?do~2ø]ñ+Jý”[Æÿ´Ÿ†ÿ´‰µ…š÷ísðSà÷Çß G«j¿ ÿf?ˆ²å·„¾ü:øñðß]ø×∿µÏŸx;K¸×µÀ~+Ò¬tK¿€~€ÿÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕøýñþ çûlxàÏì_ã‡?²Á¯…þ%ý·<=û|ø??‹¿k=WÄú|Ÿ¾&~×ÿ°ÏÀ¿‹~ø‚º_ì“«7ƒ¼KâKŸÛcà×…|1®èŒÎŸð¿Ç?þ0ȺÅ?„šÀ¯ˆŸ´_üqãφ?gÛk$üñ/Œõ‚_¼9.Ÿ£›m3Ä?_B°øñ8k§ÉâXîôÏŠÚðRéš”&¨¿´ øƒ^Óî§ð~{¦sßðÒ¿¿é?µïþŸ°GÿFõðÒ¿¿é?µïþŸ°GÿFõ~y|Rÿ‚øÛöv×ô¯‹¾?²ø‹ñ#áçÇ?‡¿´ßÅ/Ùãá/‚t½÷JºðžûMÿÁ6ÿc¿ÙrÁt?| øƒû@xãUøáâŸÚV?ÚµáMwY×¼!áŒþ1ðmßÁŒÚýÂ}/ᇤø{þ µ}¤ü%Ó>/|}ý—þ$üðÜZ—í%á?^x»Eøéà˜®ümð'à$´¿†¢ø[á¯Ú;ömý›~0øûÀ_>hôý/Ç^$øGðÎãLø›ð·Vð•á_¦©iâ>Äÿ†•øÍÿHùý¯ð´ý‚?ú7¨ÿ†•øÍÿHùý¯ð´ý‚?ú7«ÉÿcÏÛÏUý¬#ÙÞh·ÃüLø(_í…ð‹^ø“âü)пfØWã_ßk:ÃÛ=;á_о,øûöäøeñ+Oðž½áøwÆÚ߇ü]mû8x ÅW6¿|UñQѼUyâaá}WAð¶¡aá}$—ÿmüGÑo|CàÏØ;ö½Öt};Åÿ¼yyÿ 7ì5§y>,øWãïü0ñî•ö}WöÒ±º“ûÇ>ñ‡öè }7TþÎþÓѯ5îÇPºëÿá¥~3Ò>kßü-?`þêâÿf/ˆ>ø]û7ü@ñ§/5 -ËöÍý¹t‡}#þ#ñf«sªø³þ 5ñÿÂ>Ó4Ï øGI×|I¬jlj5Ý'I²²Òt›Û™.ob&%‰d‘=‹þ§áoý?h?üDoÚÃÿœ­ÝJR÷c'(ÆOE)G•É&ôn*prKUÍüJòåìåì›IÙÝ'kÞͧgäûü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ]ü5GÂïúÿhOüD_ÚÃÿœ­ðÕ ¿è_ý¡?ñkþrµ<Ñ{J/惞Íü ™ÈÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕ×ÿÃT|.ÿ¡ö„ÿÄEý¬?ùÊÒÿÃSü/=<=ûBŸû´OÚÇÿœ­Vûj/iç‡þ¿ÏÍ}çÿ +ñ›þ‘óû^ÿáiûôoQÿ +ñ›þ‘óû^ÿáiûôoWÑ?¾ øWâ…mBÿ†•øÍÿHùý¯ð´ý‚?ú7¨ÿ†•øÍÿHùý¯ð´ý‚?ú7«ëÚ(ä/øi_ŒßôŸÚ÷ÿ OØ#ÿ£zøi_ŒßôŸÚ÷ÿ OØ#ÿ£z¾½¢€>Bÿ†•øÍÿHùý¯ð´ý‚?ú7¨ÿ†•øÍÿHùý¯ð´ý‚?ú7«ëÚ(ä/øi_ŒßôŸÚ÷ÿ OØ#ÿ£zøi_ŒßôŸÚ÷ÿ OØ#ÿ£z¾½¢€>Bÿ†•øÍÿHùý¯ð´ý‚?ú7¨ÿ†•øÍÿHùý¯ð´ý‚?ú7«é¿x³Ãþð—ŠBÿ†•øÍÿHùý¯ð´ý‚?ú7¨ÿ†•øÍÿHùý¯ð´ý‚?ú7«ëÚ(ä/øi_ŒßôŸÚ÷ÿ OØ#ÿ£zøi_ŒßôŸÚ÷ÿ OØ#ÿ£z¾½¢€>Bÿ†•øÍÿHùý¯ð´ý‚?ú7¨ÿ†•øÍÿHùý¯ð´ý‚?ú7«ëÚ(ä/øi_ŒßôŸÚ÷ÿ OØ#ÿ£zøi_ŒßôŸÚ÷ÿ OØ#ÿ£z¾½¢€>Bÿ†•øÍÿHùý¯ð´ý‚?ú7¨ÿ†•øÍÿHùý¯ð´ý‚?ú7«Ýþ$üVð_Âk ÿÆw!UñWˆ‡„ü9aá_xçâ&½¬ø€èÿŠ^ÂÃÃ_<9âŸOäxwÂÞ Õî®—Kû¥–—q%ÕÌ?»y·ü5OÂßú~Ðøˆßµ‡ÿ9Z$Ô9yß/W(¨¹$ön*qm-R”[ÒJíJ2ºŒ£&¬ÚM;'{7g¥ìíÞÌù þWã7ý#çö½ÿÂÓöÿèÞ£þWã7ý#çö½ÿÂÓöÿèÞ¯¯h¤Qòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíç~'è¾|,øÏáK=cOð¿Å߇øŸá»ÛÙZkö:¼3¥ø¯G³×-tÝCVÓ­µ‹m;V¶‡S·°Õu;(oRxíu È;™=¾Bÿ‚|ÿÉ„~Ä?öh_³Wþ©Ÿ××´òí+ÿ%›þ óÿg{ãOý`Ûz¾@ý¥¿à?kïÛîÿöÆý ¼O¬øÏá¾­û§ìK«þÎZEßÄ_‡)¬F>ëן|3¬_ø_ÃQëþÔí$ð´øgV·Ô´++->ߊÖ?à‰ðKÍwÂ< ©~Êz#xOöÒ´¯üRÑm>$|hÒí¤ðžñ±?hû/|=“Kø‘gsðSÀ2|uCñGRðÁiþx7Zñl·:†³¡ßý®ê9¾•ÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨ÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨€ñŸüïöø{ñQñWÀ´½½ø©ûI|=ý°|]scñ'âöƒ$´ïÂÝ2=Á<ÿÿôµøiã«=.!c¬ê Ç„ÓÅöï:x {í7_ÍÚCþ åøãÏx áìµãŸ~Ë¿|û6|gý—ôjþøÅñYð·‚>?x÷Æÿ¾(k>ø—£þÔ¿~"xŸSñ‰üy¨êÑx#ö–ñí)ð'ÃúÎáÝ{Â_|=«ÛëWZ÷ë7ü!·¿ý¯ì…ÿˆCñ›ÿ¦ Gü!·¿ý¯ì…ÿˆCñ›ÿ¦ @8é?ðFŸø'Úøsàžã?‚¯ñSø/ð_öwøúƯ㈞Òþ/x?ö[Ò¼=§ü´Ãx·Ã? ¾;ÍàÛ¿ iz΃oñgÁ1ÓôMF¿²-,­­í­âý ø³ð·ÀŸ~|Kø)ñKBÿ„£á—Ƈþ2ø[ñÃ?ÚzƉÿ > xsR🋴/íŸêGˆ4íêú†Ÿý§¡jºf±aöµiš…•ìP\Åà_ð…þÞÿôr¿²þ!Æoþ˜5ð…þÞÿôr¿²þ!Æoþ˜5ršWüsö@Ó<)ñG—~ñÿŒ_ã/ü Câ¾(þÑ_´ŸÅïúŠ|)ñ%~A¢~Ðßþ.xÃã¿‚m¾xãN´ñßÃk_üFðå·€¼v³x×Âqi(º»Õ§ÄÐ?à–߰׆çÖn¬>kw—)_ñxê|køùã~'ZþÒ¾ü øñ§|\üPÖÛâæ“ñOáÿÁ„ÃÅÚGijâ­3QñÏ4/‹_dâÄ2øÚoFÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨ÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨Ƽ]ÿ¿ýžOìÿûj|øDÞ1øyãoÛoö^×ÿfˆÿüñ3ã_í#ãY<>|ñ“Áÿu½zïãwÅŸø‡Å²|2ÿ…ããIt›9|_£j–€ÚO‚çñ§‡|7á+úçíI6·áÿÙbÆãâ_‰|+¨øDñWìË7üa¡èW~ðMö·¦üsøJþ*ñ.á¯x»ÇÞ ð­Õü7ú¦Ÿ¡k~<ñ…߇4‡‡OÔ¼]â ¬æÖnìÿÂû{ÿÑÊþÈ_ø„?¿ú`ÔÂû{ÿÑÊþÈ_ø„?¿ú`Ôü®kž.¼Õk0ºÕì|ñ³YÐ> øwÅž/ýŸ¼IñHø!áدľÓcñýžµÝwÂÓÚéÞ8Oiöž£{yâ9ô×Ò_¼–æêÔ¿ëü!·¿ý¯ì…ÿˆCñ›ÿ¦ Gü!·¿ý¯ì…ÿˆCñ›ÿ¦ ZJ§2k’œnâïÙ®XrY;í/ŠK¬ýí Qµ½é;&µw½Ýîûµ²}‡;áø'Wì¥á/ˆÒ|MÒ¼3ñZóT?|gû@Zø+ŵíKãZ?ÆŸü@ñÅüIðçì×ãOŒúÿìñáOñ;ž#ø¥_øwá~—ÿÏŒ5[øa4}Qa¹‹Í-?à¿ðO½<Ü>ð[Å:tãÂIàÞiß´7í1a¨|9ð%—Æ/„¿´†üðgQ´øÅ ÷Àïü>øÍð7á‡Ä…ø=qà}á»áë¦ø_eáOx²×\ößøBÿoú9_Ù ÿ‡ã7ÿLøBÿoú9_Ù ÿ‡ã7ÿL³(hý‚¿d×øã‹wŸ ÓWñ7Ç×#Ð5ÿ|FñÂI5ŠÞŸÁÿüU£|×|_¨ü ð·Ž~&øVóSðïÄ_ø[áÞãOhú߈tÿkÚ­·ˆµÈõ×ö{ý”~þË–>%³ø3á}{IŸÆhð’ëþ4ø•ñGãŒõ[ i¢ø7óxïãŒü{ãH¼à"[7Àþ¶×¡ð‚ìou o èzLZ…êÜqŸð…þÞÿôr¿²þ!Æoþ˜5ð…þÞÿôr¿²þ!Æoþ˜5OðÃöýš¾xß@ñï€ü%ãKMGÁ]Û|7ðLj¾7üuñçƒÐ^è—Þš?¿ü{ñ+Äß~l𞩪ø:Ѿø ÁMaàÝWUð‹[xoS¾Òî9/Ù»öø[ð/á¿Áÿ x†ÛDñÇþ|~øÍûVÙxïú³ðËBÿ††øûcñÃ@ø‰âÝÀŒ¼E›á;~п>øÀ>,ñ'ôÿxO XǨêzÿ„4Ytßð…þÞÿôr¿²þ!Æoþ˜5ð…þÞÿôr¿²þ!Æoþ˜5s(ÿ‚nþÆž1– u¿„Ú–ÏøH>.xƒ]³Ñ~,|hð¾™ñ~;|iñíñWÂ_´ |DÑô¿ ¼Uñ—â¼p~|Z³ñ§ÂóÄÚ®™áÏiÁÒ‡ÒÞø5ðÓáÏ‚¼Eðë·Ӽâß|cñÿˆô »ý_\µÕ¼Wû@|Lñ·Æ‹Ú̺þ¡ª]ýŸÆ?>"øÏ_›HŠâ=HZmÃúv“áë3I²ñøBÿoú9_Ù ÿ‡ã7ÿLøBÿoú9_Ù ÿ‡ã7ÿL€4á‡ÿeOøg„²ƒ|ðüŸ³÷ÀKàæ±ð£áÔÚ—‰f´ð–­ð Å>ñ¯ÂÍN-vmmüU«ßè~%ðÆ•¨kzþ»ªÏãxßXÓüy'‰´ÏøŠÏUú²¾Bÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨ÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨³øïû)üý¤fðåçÅmÅw:—…­5½'KÖüñcâïÁŸÜxkÄí¦¿Š|â|ñ×€5Ï|9ñTš>‘/Š~x¯PÖ¼âI´­6mo÷òØZ¼\•÷ì)û*_ü\ð¿ÆÖøS ¼þºÑ´íÆ?|;ð¾]cánŽþø[âànâÍ7à—‹¼oðÃB0èß ¼wâχºßŒ¼¦iúE„µÍÛEÒ#²‡þ¿ÛßþŽWöBÿÄ!øÍÿÓ£þ¿ÛßþŽWöBÿÄ!øÍÿÓ è°WìáxWÂzŸ°øÁ?ÿbŸ…¾Óÿá:ø“sý™àOø'oÅ=Wã_ìu¡}ªóÆ7·¿ð§þ&ëzŸ‰¿´õ ›½câÚ±¾)j6ðü6úT_=~Ì_ðHÿÙözýš¼-û>ë^ _Šwþο g߈Þ8Öõï‰)â«O‡G†fÔüGðÛÂz‡ÄoÚ~ÎÆ<5oñ«TÒ~êÞ’?‹Ïâ%泬xÿKµñ}}ÿ_íïÿG+û!âüfÿéƒQÿ_íïÿG+û!âüfÿéƒPçÂØËörøâ? xÏá¿uKxOBøÇáû/ø›â?ňÞ8Öí~?øà÷Šþ/j^>ñŸÄx³ÄÿüUã-gàÁã?¾$j¾+ñ–“¥øJðö¯i^›QÒï|óÂðMÏØËÀ:çÂM{Áÿ 5 þøWàß„<áû_ŠÿßÀOcû<øbÃÁŸõÏ|4¹ø‡?ÃÏ‹Ÿ>økIÑôÏ|RøµáüKðÈÑt[½/Ŷ÷Ú>™ukÓÿÂû{ÿÑÊþÈ_ø„?¿ú`ÔÂû{ÿÑÊþÈ_ø„?¿ú`Ôâoø'ìƒâ߆ÿ>êÿ µh|û<|Ñgß‚PxâÇÆoøá·Âß xÛösø“á3Ã>;ðŸÄ-Ç–¾&ð×d¯ÙãÅžø“/‰fø— ë l/tŸÙÉ­x£ûséï‰ üñƒáÿŒ~üFÐãñ'|{áýKÃ(Ñ^÷RÓ$½Ò5[w¶¸z¾‹y¦ëš§o½n´{AÔ´Í{BÔà´ÕôMKOÕl­/ ùëþ¿ÛßþŽWöBÿÄ!øÍÿÓ£þ¿ÛßþŽWöBÿÄ!øÍÿÓ »â¿ìû9üoþǾé>#‹Ã¿ <{ð7ö°j~"ðõ‡¾|IñGÁ¯x£BÐl<1¬è¶Z.£‹¿g¿ƒ>#ðŸ‹tÈ-|eð÷Xð•}ðÿÄžãS{þ)¿`OÙ6çá$u¯…·>/ð|aðŸí¨ˆ~*|Eñ·‰þ3x#Ä~ñ?‡>!øëâ·Ž¼o⊿|AmwáOè÷ÓxãÆž ƒ]ðN•oðë^·ÔüeðÌ“ÿÂû{ÿÑÊþÈ_ø„?¿ú`ÔÂû{ÿÑÊþÈ_ø„?¿ú`Ôë¿ ~|ø?âߌÞ:ømàm3ž,ý¡<{iñ;ãµeq©Ü\xÓÆö>ð÷íu›ˆµ ëË]&8ü=á-Iðý¾“¢M¬K®x¢m6Oø£ÄÚÆ¯óþ±ám?örøÿûS~Û¿~,h3ø'âÁÿÙ_àoþè µ½;ÄþÓ>x›ö†ñ™¤É¬Çã¿ß|lø—ñkâgí?âm?ÂZ'ƒ¼ðþ{M>ÇÁÞ ±ð÷‹¼BujÝü!·¿ý¯ì…ÿˆCñ›ÿ¦ Oð/ìññPø§eñƒö˜ø©à¿>$ð]”V¿ü5à_„ºçÂ/†¿ õË}FÓÅ>6²ðŸˆþ0üi¿ñįi×°èQxÓVñ"Ïáo E¨èž°Ñ ñGŠåÖ€Ÿs«xJ×ã÷üßHøå¤xK]ŸI½Ô´™|Cá}⟠x…ô­FÿL:ÞýŸ{ugäÏ'ß(2séÏãÛüûW†þØ·:oÁ‹=FÏHÔ3üHÎá‘ÿhSú™e.¿í9þx©?átüHÿ£Fý¡ð¥ý”ú'+Ô£’âôýîWÿ‡¼›G§lE¿m̽¼?–·þ×ÿå~Ÿfx‡ì#­ë7Þ-ý¬ôkÝ_S¼Ò4/ÚâÏö&•uuq¦èßÚ~<Ýj_ÙV3Jöºö…Ò%Í÷Ù"‡ísªÍqæH¡‡è¥~[~Ìüsø/âº×ŠeoŒ—ö¿>)ø¿Ç@ñìÇuqg¤ëÿ¾&øÆÎßYMGö‡Ò£·ÔbÓ:üC<Ùö‰ÿÂöOÿ蟯è_­åÍû¹¶J×KgyKôÿ˜Î§‘e'JºjOýž¾%ùv}#E|áÿ Ïâ/ý'íÿ…'ìÿÑAGü/?ˆ¿ôhŸ´OþŸ²wÿERÄ`žÙžNý3œ©ÿîá\Ïþ}ÖÿÁ¿ùYô}ó‡ü/?ˆ¿ôhŸ´OþŸ²wÿE8|qøŒy²íÿ…/ìÿÑAVªa^Ù†Tý3|±ÿîØs?ù÷[ÿVÿågÑ´WÎ_ð¼~#ÿÑ¡þÑ?øRþÉ¿ýÂñøÿF‡ûDÿáKû&ÿôPU/böÆåÓ5Ë_þí %_ü[ÿ•ŸFÑ_:ÿ#öBý¢ð¥ý“ú(hÿ…Ýñ#þ ö‰ÿ—öMÿè¡«P‹Û—¿LÏ/–(9×òÕÿÁ5¿ùèº+Á¼ñÆûÄ_ô߆ž'ø1ñ[áf»®x3ÅÞ9Юüs{ðwRÒu'Àúç4[ÛÏðÇâçÄkË]FÖóâ7†e†VÃN¶»¶šííîä–Ñá>óJtåO–îS<'N¥:ÔçiCš)Jtä”á(¾Y;J2‹³M2R½¯£³N2‹NÉÙÆI5£OUªiìŠ(¨(ùÛö¾ÿ“Lý¨ìݾ6êµñ5zâ œútúÿõ«Èÿkïù4ÏÚ‡þÍÛãgþ«_W8>3üGðÉ´'õ2~Ê_ýuùŸŠ ØÜ' {)á!ì±Y÷7Ö±ø ùéd6ö]Äáýª\ÏÙsò{¼ü¼ñæéÁTŒ*׿Sw§BÜ”êTÚXýœemô½¯­¯go¡(¯žÿát|Gÿ£Hý¡?ð¤ý”¿ú&éGƉà~É´'þ¿²—ÿDÝ~aC"Å«~÷*ÿÃæIÿÏ›µ¥ßs¶xˆl•o?özÿü¯ÎÿF} ƒ'>>¿ýjøSþ {­ë:×ì™àïí_SÕ¿²WÃÚ&•ý§uý™£X|5øö#OûT²ý‹L²óeû%…·—koæIäÄ›Û>Ö>4|HðÈß´/õ2þÊ?ý•òïìUÇ/ÙÃà~•ðÓDz·Æ]W]±»±ž[¿ x¿öc¾ÒY-¼#á]Aú¿í¡Þ7šܨÁ¶’ÙØ¬¯,0þ·À4©e˜<êž7“ᧈÄe£™ÞOz‘¡K6U\yqÎþÍץͶµúÛÏÅOž¥uä£ Éµ‡¯£”¨8§û¾ª/îgê=óxøëñ œÙö‰ÿ“öOÿ蟥ÿ…çñþö‰ÿ“öNÿè ¯»Xœ Û5ÉŸ¦u•?ýÜ9îÖôë/û_ÿ•ŸGÑ_8Âóø‹ÿF‰ûDÿáIû'ôPRŽIÀýÿhŸü)?dïþŠ µW öÌr—é›åoòŇ3ÿŸu¿ðEoþV}E|åÿ Çâ?ýíÿ…/ì›ÿÑAGü/ˆÿôh´Oþ¿²oÿEZt^ØÜ±úf¹kü±Bç¶ðª¿îoþV}E|æ>7üH=?d/Ú'ÿ _Ù7ÿ¢‚º_†ÿåñ÷‹|càmcá‡Ä_…Þ)ð_‡|âËÝ/Ç× ï¿´´ê^<Ñô+ý&÷á—ĈÚsmÔ~ø–Þþ×R»Ó/mü»9£¶ž µ•/ÙÞ3”+a*ªqRš¡Áלbçs:tkÔŸ/<áÔlœ•ìM6•¦›v\ÔêE6“v¼¢•ì›ß¡ìôQEfYóWÇ¿ùÿdoû8Ÿë(~ÓÕìê2~œÿŸóë_;þÔºÖ¥áïþʺƑá/øëP³ý¢u³øW·>³×µO´~Ë¿´µ¬¿`¸ñ¿‰üáxþÃòê7_Ú~$Ó·ÙYÜGeöÍA­,.¬'ƈàɤ~МÿÔÉû)ÿ_Ún¿ñC-¯ŒÎrj´ª`aðý(5‰ÌòÜFãgrº¥ŒÅЫ({êÕ#NMJ*nP’f¤a骺÷÷)U¨¿AoI'¦Íßgk4} E|÷ÿ £â?ýGí ÿ…'ì¥ÿÑ7Gü.ˆÿôi´'þŸ²—ÿDÝ| ,“­ûì«ÿÙîݳ²Óþ Û,D~íkì¿Ùñÿ௙ô%Hƒ©üùÿ=ëçø]?èÒ?hOü)?e?þ‰º˜|høðÈÿ´/õ2þÊ?ýµéRÉqz~÷+è¿äy’ì½3»yyûx-oü'¯ÿÊüÿ>Ì_ÚçþMGöÿ³zøÓÿªßĵ½û)kzψ¾x Yñ¯©ëº½çü%?kÕu›û­ORºû?!|,øƒà}ïWñWìµo¤ÚêÞ,ð–¯ éפö_´ýä:t7—ðÉ{-¥…íÌvË+Áis*¤/?ÀO|\øYð›ÂžñìñÚóWп·~×s£x«öY¸ÓdþÓñ.³¬ÛýškßÚKOº}–º„1ÍæÙöu•SÌRWýo‚ãC”Ö£‹Ìrzg˜Vª©Ï;Ê99a°Œ’X×£”$—œYÈ—5^hӮ׳‚ºÃ×ÝJ£kø}_y÷óxøëñ ð?dOÚ'ÿ OÙ?ÿ¢~—þŸÄ_ú4OÚ'ÿ OÙ;ÿ¢‚¾½bp/l×&~™ÖTÿ÷pÂíoN²ÿ¸ÿùYô}ó‡ü/?ˆ¿ôhŸ´OþŸ²wÿEð¼þ"ÿÑ¢~Ñ?øR~ÉßýJ¶í™eÓ8Êßþí‡3ÿŸu¿ðEoþV}E|â>9|F=?d?Ú'ÿ OÙ;ÿ¢‚—þÄú4?Ú'ÿ _Ù7ÿ¢‚­OöÇåoÓ6Ëþí‡7÷+àŠßü¬ú6ŠùËþÄú4?Ú'ÿ _Ù7ÿ¢‚”|pøŽNì…ûDÿáKû&ÿôPU%IíŒË_¦i—?ýÚ:þZ¿ø&·ÿ }E|éÿ »âGýíÿ…/ì›ÿÑCGü.ï‰ôh_´Oþ¿²oÿE Z§¶'/~™–^ÿ,Hs¯å«ÿ‚kòÑtWÊ^/ý¦¼Sà/ ø£Ç>,ý”h+ÂÞ ðî·âÏêŸÛ²Õ÷önáÝ6çXÖoþŦþÓšçØôë;›²ØZ]^Üy~M­´ó¼q7Õ´N”¡ÍÊ”á9N1ô+Çš qr¡R¢Œ¢ªAµ&¤šVf¤Ú\ɤ›R„àìî“´ãÓqz®ÁEVE!Á>äÂ?bû4/Ù«ÿTÏ‚ëëÚù þ óÿ&ûÿÙ¡~Í_ú¦|_^ÐÈ_´¯ü–oø'Ïýï?õ‚?mêúö¾Bý¥ä³Á>ìï|iÿ¬ûoW×´QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEóíQÿ$»ÃÿöpŸ²/þµ‡ÁZè>1iß5„ô„þ"o|SÕ>øßNøiâİÑ5Wð¿Ä ï êvÞ ñé~&±Ôü7©6‰â)tÝIl$§…¼iñÃ>$i|¿‡6º¾¥ðïO™tí)õ»÷·³ð§þ ðð߃f/Ø?öOñÏíQâïü7ý”?j‰_¶Æ}KÂþñ¯Á¿~Ó?5ùüY7Âéÿ<ñâÛÇßu„‡Ä¶ïÿ 6ƒâx£Ä·z-¥þ›¬x*ìÁ%Ÿ,W ÉÅQ¥AB–*2I`$§_Rtó:mÒï)biÏ)„¥*nXjÊ1q©Í]«=ÛÖ6ø¶ir'×FŸ´¶¾ò»¼´>9øÿ:ÿ‚|ÿ‚"ÁK¼EãïŽ:dðSø&÷íQ¢þΞ2øÙ¦ü2øU¿‹4O~Óÿ-|ñKMøc}àðÆ/ ü@ø-ñ?QðdžZçÀ·RXh­®ÜÙÁâ´mfOÑ_ØöÁøŸñCþ EñÓöoøqûa·üsö$ð—ì‘àO‹‰ûS6‘û:êKð»ö•Õþ&Üø^Ùõ>*~Ê? >|ñØñÄ“âSZ¯†nüCá³h4‹E^Úý'òÿÚkþ ëðçÆ=þ à¿„ßµ§Ä…_?à ÿ eÏ üHѾ,Ûü\ý®¼g¤|Yý–þ5ø[â?†~/\üZøÝûI\üBñ¾«ðÿóü(‹ÀÚÞ±xrÚúË\Ò¼RúN‰gà©?£ŠÏŠÊÞqÂáèÔ­Š­ˆ«;a©áÞÛa²¹Ú+ØÍ¨RÅSÇCK‰*p”¹”áQFN1Ÿ2æm(¨­Ûæ´§æ·-Ü£wäÑøQñ§þ !ñàüÃö]ñοñ{]ý”u/ø%5¯ÇË…_¿e‰´—Šcý¡o?k½Wáä?oíÿgo‚¿þ:iZ|7Ñ®ü9u6©ugðšÇQ›JKØ-üa®è’jaÿÌÿ‚¬|OÕàŽß²í‰û]iÿhÿ‹ÿSöŒºñ÷| ¡~Î<=g¤|-ý¢~)x"ÇXñ§>$øßöbý˜>  i>Ðt ûÆžñ—Ä ìõÿ øgÅך75-7ô¯þo?ðTøyOü-ù°Oøa¿øRßð…ÕÄÂûÿ…¡ÿ þßû•?á ÿ„þ£¿ð–ÿÌ¿6¾ÿÁõ¯ƒß³¯ü÷à×…¿kÍ3Vñgü7Çÿ´ß´øçöl²ñ—‹Ó~Òþ#ñîµsªxƒà>¥ñ™týâ'Â(¯ñ7LýŸ¾x+áæ•©ü_žÛàe·‰.¾×ãðïŒô­3Áþ/»ñŸ‹ì4]sÂñëú—ƒµë ZãÃV¾„hå Ú•J’å^W%YsÃÚf*‹šöJÉÆk›Šçµj¶‹”geûΫµöþåúÿŽÝ4^Wøcþ ÅÿBýº~=ø6è|Wøåÿ _ü7Ïü>þÏþ-ŸÁíþ¿ü2Ïü'?ð¢?ä[ø£ÿ ÿ'ö>ÿ$×þïøIþÍÿü$>tþoí/ìÉÿƒý•ÿj¯ŒþxÃÿ<;¦üxOì·ñ‡Çžð®•ðö©OÙăÃ_ன¢xïÄ~2qà{±-ôËñ#Á¥Öt{[½KÃñê°ÛJåŸØÿþkÿ ©ÿ{oøjøO?áÔðð?ù¢Ÿð‹ÿÂüÿ†ëÿ„˜Ñ[ñü*ÏøU¿ð‘ÕFÿ„ßìó(}£÷?°üÃá¿ìñ«À^:ð¾ kþÞ|n¼ø]fŸ°ÿìý¤þÔ·«ñžïYû>Ÿñ‹öÝÕãñÆi¿ ô?ø›Âžá–Ÿð2þ÷š¤ñö¥ã_é¶¾¯R¢ËªÊ¬©µ*þÉS¥*qnX¼uH]*jÍQ–0æ*¤Ý&á(.Dœâ®õÚ÷w ëßšþzê´tÿÁ:¿à¡ÿॿ´ßÚ[à÷Á¿ þø’Kø|â_ŽZ?ÂÝ_Í¡ø·Æ>ñJxkKø{ñgân¢‰á_x2òÏWºñ ¾ƒgpÚžšº ε4ô:'ãÏìaÿ|ø¥uwÿ ý¢¿lÿøhÍ'Ÿà¦þ'ÿ‚o~ʲ…üû"ê1øâ=æ»ám#á÷ÁO ÜøÄ:§Ä~ÕúÊê'⾿ã?ÃöZ¸Ñ¼[a}ð»Åþ,mÄW¾ ýƒÿ‚]þÃ_ðíŸØWàoìWÿ Gþ?ü)øYŸñr¿á ÿ…uÿ 'ü,_Œ?~,È›ÿ wŽÿ±ÿ±ÿá;þÀÿ‘«UþÐþÊþÕÿAûwömŸÃmÿ=Óá—Çï ÅûHÝÛüKñßü;ÅðY¯€_£øGšWÀ¿ŽºÇ†õ/xÅ^¹ø‘:|eð—†í4oH×µüQðÎçÅvž"2ÚiþºÒášç®‚ª˜ˆ¤'R*„œdåì•Gf›W‹”rm)+µž=toµß›Óþ•ÏbñWüËö\ðÇÀ„ÿ­~~Ѿ+Ô¾)~ÝöðM{¯žðwæøãð»öÁºŸÅÖ—Ÿ ~'èzÿÅ}ÁtÚ5ï„ZÓPÔü)ñÆ7Ä>¼ÑdÕôÍBæúÇõ¯Ã½ïˆ<5áí{QðÞ¹àÝC[Ðô^ÿÂ'}_øR÷R°·½ºð߈eð®¹â I®hSÌú^¬þñ/ˆt¿µ¸m\Õ´óoqøp?à‡úOü+/ÙûÃOûG]Kñ/À?ðXÏ Áf¾?|J„&•ñßã•–µâmSÆ~ð·€íþ#Û§Á¯ ø’ÓXÑ4oßÉ≷^±ðÚI{aâ˽Nyíÿxëº0¤¹}“oY]»ü)«hì¬õé~®Û¿_/É_ñ¹ùÁû\ÿÉÙþŸöø—ÿ«+ö`¯ÒzüØý®äìÿaOûüKÿÕ•û0Wé=}DU°yrÿ¨Z¿ŽaŽg,]êVôñ~iQPYó·í}ÿ&™ûPÿÙ»|lÿÕkâjöñïÚûþM3ö¡ÿ³vøÙÿª×ÄÕì5ùŠqæÂpÏ–+?üid†šù#¯íSÿéÞ޼؟ëÐ*Tî•1FO°ëþ5~]B–ÚÁÛ]ÞöZhv…:ßü‹öîøYðþ ‡ÿ ñíKâŸøþ ñÿ¦ø©û&Ã+jÿ fû?…+ý´/ŽŸ>[xnËÄÞø)áÿŽö?4 †mÇ×ÿµM6x¼-¦§‰¼+â+»RóPþÁ«ùó¶ÿ‚x»X³ý©~ |Cý­¼1âŸØ×ö¼ÿ‚“xßþ =ñ‹à®‹û6jþø¯â-sÅ|ñNø ¨|r»ý£¼Oá9þYj¿¼ uùí>i¾3ñ Æ“w¦ø‹Â±ê6öz_Öä¯KÚýqRqs´ªPöÒÖæÄÓ¤ý•OeR¥+ÅNôìÜ_´®³©Í£Õ”µNÉI¥ÊÞªé>–~cÁÿn¿ø)7ÆŸðROŠ¿³í û_üDÿ‚bü×~|'¸ýƒ?h­7ötø=ñwöyøÑñ'WÐæ¾ø·sûIxÃâ¿Ãßjzm‡ƒüaycáK/xÆÿ­í¼9¤j:ÿŒ~$øEõ¯ êš§?ûbÿÁLlÙ«þ cÿýýcý |®~Ç+ý‘¿gˆ_µ&Ÿ¢ü5øu¢øÇÞ,øóûG|@ý“í¾1h^)Õ¬üwñáç…fø‰ãƒ^&Ò´-3ã5熴mG†ßTÖuÛ;ÏjïêÏíÝû~Ð?¶–‹ñ¿á,Ÿµ×4Ù‡öøuaà|ø¹ûxãf¡ð¦ö=>ëJÖ>!~Οtˆ?õïüAÕ`¹þÙÓuï‹ÚgÇÄðGŒ ¶ñ€­<6–¶ºdþÓÿðoÏ€¿h WSƒÃ´^½ðãÀö_ðIo„?ðK_‡^Ô>Âuâo¯ÀÚGÀŸ´WÃ×þ7_ˆÞëZ6§ðëÚ-÷‚ Ð<;=ýÜ3øŽßǺt®š\E‚žYˆöÑ£öiP­áý¢R:PX…7†…Uˆ^üåi^«R•H)F/ s¶Ú»÷®®íçmÚåéÑÛMw? |7ÿºÿ‚›ø£ö ÿ‚òþÑoûAŤxötÖ?àœ?b™¡ø7ð8Eðoà÷í«ñÛTÖm´4Óõ/…ŽÆ¯ð#Ä^ ð÷Ûþ+ZøÛZ°6‡WÓõ+}~æïW¹þ¾?aÂX>(ŸøhÏÛóãù°ÿ„'þO—ö-ÿ†Aÿ„H]ÿÂ]ÿ$»þ0öÿ…‡ý½öoø­±ÿ ?þ/ìoÿÈ•ÿ 7üU¿”~<ÿƒut/|ÿ‚Œüð¿íP|á¯ÛÃá/üÛá‡îÛàRëð;Dÿ‚fx?À>Òî^Í>0è©ñ&O‹v^‚g·[Ÿ5û ŸÆŸeó.w~xsö¥ðÿü%ðÒßþ|Zû_ö'ü!_ð£?f¯ˆ¿³Çü#þGö¿ü$ð”Â}ûX~Óÿð—ÿjùÚö'öOü ÿðÿfêÿoÿ„—ûnÏþÿC<ZoêÐ¥*‘—,(ªmEápq»Ãê•zx—%”õs›çUW22ÖW²]ï¯4¿½ÙÆÛô]~¥'´•* sëÓéÿ×§Bžßןã»ßNÄ7wwÔp«ó‹Å?ò”Ù»ø;ÿKÿjÚý¯ÎÿÊP< ÿfïàïý/ý«kërHrýuÿÔWÿ¹ÜüwûŒ+˯úøÿôÕCôšŠ(¯P“æ¿C??daëûDø‹ÿYCöž¯l¯øóÿ%öEÿ³‰ñþ²‡í?^×_‰ø¬¯žd¾\;EýÙÞyþg¡—|ûÿÝz_’ßðYÿÚƒö˜ýÿcý+ã'ì×á¿êQØ|uøO£þÐÞ6øià=â‡Äï‚ß²Þ¡}ª\|bø¿ðßáÿ‹m5?x—Æ~³°Òt*?é·ƒôqâøžÍ4]ööÓõ¦¼ö‰øyñ«âO€-´o€?´5çìÓñKñW‡¼Icã¥ø]àoŒ~×4ÍæI5_øëÀ~6K)õ?ø²ÚSo¬Ïàoü5ø…e%½Ç†þ h[oa¿ø|±Ò¥‹ÃT­ 5)B¬%Riìe«ªŠ:Óå¶ö£W[^”ÕâúªêšWM-Õ¯wm®×æ¾[Ÿ•ÿ²÷ü§öð—ì[ûK~Þ¾1ÿ‚“EûpþÉ? ŸB×ôŸ_| ðÃÚCàä—vÖ5ÇÂO‹zWÆ>ñ‡ücãkÃÖ¿à—àÁìÓV‚ýCÆF¡iâ›/EñçüÓödøWû4þØ´WÅ„ÿ´·ÃýköñÁO~Ò³_ˆ¼ðÀ~Ñ^ Ú#Uð‡Áí^ #LøÁ©|)Õô?éÿôŸY^Ù|\i`ÑtßCymoâ 4hW=?ü‹á~ÁJþügø§¥^xóþ ­|$ñÆ~Ïÿ4ÿ€? >êßæ²Ö~^|/ø){ãÏŠÒXÝ[xÎÖ|FÔ|GñÄ:ÇÄÝwTÕ.õmOO»¼{Êø_þ Qÿ¦ø“ð7þÇÿ'øuðoÁß þ8|mý¢u?Ù]#áoì-ÿôøSû1X¾ð‹ö‰ø?xÖ:Âÿ€Úo޾.|KÕRÊo|DñN¯ñâoÄKO ‹ÏMàáÇ„×WÓ.¾Ç ‡È±X¨AW©)VÌ0t¢œ>¯„œ²èTJTpÔ(/gÏ…J’£‡•WJ†"*œ¥R•^vêÅ_•YBO~g̹­¼›ÖÑi&íwtköûágüÇàÏÅ»?ÚÊÏ@ø_ûEé?c_xß>kÿ -aøÑsŽMñGÁ¯hÒ¼O¬XYø?⇇µ8õ½7[ñÞ»à…ð&a¯ëmþhÞñ þ™òŽ£ÿùýt_ÙïöÞý 5¯|u¶Oø'׈¾øsö‰ø[¡Ã=üFñݤߴO‹´ü4Ô<âÏ…?´/Žÿgÿé÷z¾©v5åÒ~7KÃ'Þ"Ó5½:ÓÄ60h·™<ÿ‚'ÏñgàçüÂ_?kjŸ¿à¨|Óþ!|xðÃ]7Àð†ø3öiðÿ‡ü+ðgÂÃáæ—ãk«yú‹¥ücº¶ñ/„4ߊ>(ñŸ§èÒî­ì-|âüëâ?ŠŸà©|Wû^øG×à¦ZWì¥ÜêŸ ÿd+/‡~[þšŽ>™¦ø3áVŸñÿP¶Öü9ã]+D²Ðô­x«Ã3øÖ(îo5_^yòÜtáhäNOÚן/µÁ4£MÔ\²õŒq’¥ïR´óMN¬• -)J|²«¥’Úwøw÷¹/®úBömjö±ú'ûMÁT<û!ü$Óþ1ümý™¿jO øxx'Yø‡ã=?S‡ökðµÿÃÏ išÖ¿¥XÛêZÏÿi_øâ¼GgáÛ¿i?~xËâ÷Æ»-ûÃòx£á߇u_hZMÿÏZOüG׿o¿ ü²ø7®é߱ƫÿ®“þ ywûXë:§ƒ-˜|#Õn4=gBø—'ƒÄ¼U¢|Ò4®ø _°ºð†§ñÊÿã þ™-¿Â­à–ƒ{ñ{Ä'üOþ ­~ÜßõŸŽ>ý¥4ß„ú‡Š?a/Ž?°~µá|²ø÷ xwÂoõ GWø³ðŠ ÿŠ£ø_ñ}¡Ô$ðŸ‰|[mo¯^øŸáê ZÍá«”·×m¹ß ÿÁü+¥x§à¾­®üx¾ñ†ü/ÿd_ø"×Ço Ÿ†ÃJŸâ×ÁXt«ƒâ‡ÃÝrßâ Ãüñä¾"‚öÿPÓõ½;ãqáûØü?ftíNÓþ©ûpÐÊ•¹ÊõçN¬eCöR•œ9t䔡f©ÉÉÁ©¯kÇœÎNnNÝÖºkoÆÏ¯]íÐú§ö/ÿ‚¡üý¶¼j>xCÀ¿>øóXøà/Ú»á×…¾:x_ÁþÔþ.~Ëÿu‹ßø/ãÇÃÓàüC³›Á:¶µfºt–+ºð§ôÉïôêøFÊèe?š·ÏíÏ㟅¿ðW†ý•¼ÿ,oø'wì±ü¡¿iÍÄK¡þÇû¼IûLÇûMøËáΛ¥.«ûLüø§­xÐêÿ´ûÝ@|$ð¡£ø›ÄGÁið´º}ÈñúØ_ðMø$~‡ÿîñKxŽ×Æßü_ý‰ð+ÿ³ß„n~þÂÿ¿fïëе½;W½ñ×ÇþümøÝñcÅ‘øwÂV¾/ÔäøàŸ†>!¿ðô^/Ô¾Oã©×ÄößK/ì5Ÿø*Xÿ‚•ÂÐéûÿà ÿ–ÿ„'þ®'þçü-øXßð–ÿÜ©ÿOü Ÿõÿ„»þ`ÕÝIàhâjº_¼¡ì%ì¹á{Ô´yyUjSåw»Rœ$“nÍ«1{î*ú;«ëÓ®Ízh~9~Ì?ðQïø(Ä¿‰?ðmÖ‰ñÒàü(ÔÿoÁI5oÚïá‰øqáMüMö|ø>¾#ýŸ-:n¥gyáØ¬t›oÑOÙÇþ Qû-~Óÿðí¿ø@|ñÿHÿ‡£Ãaÿ€ÿ„ÃÂ߬?áÿ†%þÜÿ…«ÿ ‡û⧈?áÿ„ƒþûÏøWßð…ÂÁþÕóm¿á#ÿ„S|¾O¿~Ðß°×ü/¯Û«þ ÙûjÂÑÿ„Sþþãþ-¯ü!?Û¿ð¶?᩾hŸ ÿärÿ„»Gÿ„þ_ìíÿù|eÿ ?Ú²¿âžò´¥üæý’à„z·ì©ñ_þ ·â›?Û çÇ ÿà™!ý»¦ø/ð¦ûà&›¡k!ðí¹¥x–;í'Å_í>*_Ï/‹¼¯x³P¾¿ñl^“Bñ~‡¥xkBÒüàFÓ^ñ/‰½(}J¬#&•žÎ£P„f¢ªNy„áMµÎâç‚‚”•%Êä¹ZŒK™iºmjû%ºþõÿá þÁpe?Úãÿì¿ðáÇÃ_ÚVâ×öÉñWíOá¿ÙË㎯ðÿÁZgÀŸ‰V²“âC⟌t_7ÄÙ¼S/…î¯ü+®øwÃ(¾ oɬZÚ\x›Ã^ðöµ ëºŸÌ?°¿üo­û(ÿÁ1-~&꿵¿íÅñëþ kûq¿Â_ˆZ/ìÓû:|ñÇŽõÙǾ3¹ñ‰ãÿ„øûuðÃá•ëø}4oøPðÇŒ|Ká½j×A_|R×~C}¯Þi¿~ð×ÅoÁAÿà’žøGáÚÊ/ÙWö¸ÿ‚¡ø·Yð§í1û#x“öw“öaÐ?j¯jöþø]'ÆËíCÄŸ ¿këÿ¼uªÁ/‹¾xïÅ>¶ð?…tMNÒÒÄkTÕ¿N¿dø!Ÿü2—ü9çþ2‹þßøtïü<þhŸü"ßð¿?á»?á&ÿª¹â/øUŸð«?á"ÿªÿ ÇØÿæPûGî=?e„§î¦í(ó$îåî¬b‡4¹o+ayâš„œ¹—4TeýïëþÝ¿ê~­~Å¿µçÂÛÓöaøMû[|7¿ŒzF±ªxjéh>*Ó§ðçŠußø“Eñ•i¨jö6º®…â¿ ëš5áÓµmWK¸šÁ®´½OPÓæ¶¼›ê*øþ sû ÿö?aO¿±Wü-ø]ð¥ÿáfÅËÿ„'þ×ü$Ÿð±~0üAø±ÿ"oü%Þ;þÇþÇÿ„ïûþF­WûCû+ûWýíßÙ¶V±ŒyåÈÛ‡<œ·q¿¹{¤îÕž©;éb–Êûõ>wý¯äÓjû7ú­|MUc¯ù7‡_÷7êwâz·û^ þÉ¿µý›·ÆÃù|5ñ7ÿªª~Ç_òn?¿înÿÔïÄõô˜elºŠíŒÅÿéŒÎKÞµGÿNééUO¦¨¢ŠeŸ!Á>äÂ?bû4/Ù«ÿTÏ‚ëëÚù þ óÿ&ûÿÙ¡~Í_ú¦|_^ÐÈ_´¯ü–oø'Ïýï?õ‚?mêúö¾Bý¥ä³Á>ìï|iÿ¬ûoW×´QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEóíQÿ$»ÃÿöpŸ²/þµ‡ÁZù¯þ ·ÿ(¸ÿ‚“Ù‚þØ¿úÏkéOÚ£þIw‡ÿìá?d_ýk‚µÔ|Wø_à_ÿ ~%|ø¡¡ÿÂOðÏâ÷€ýŸþ%øóÄž&M'DZjÿábþÐZ'€þ#Þ¶´,|A éÞ!´þÎ^§özÿ‚ç~Û_¾ÿÁ¼iaðàG~$ÿÁD>Á_!Ô5mVþÉÐ4>ÃûO\Õu=bÿìÿjÔõ ÛÙ§¹“áÁÿà›>ñÃßøcö_Ðm5Ÿ…2þÐ_ð®ôíCÇŸµÿxSKý©ü7¬xKãÏ„t߆þ!ñö«ðïþï4Oøš'øksáiüá½CÅ>)×ÿ…àûßü+ÿŠsYñ£á? Â-á=FóIÿŠ/AðçöçöïÿkêQÅxž…|nW[ S †ÂÕ§ûÊõè9”cJµg”FN<•_,%OŒ„£i»Ô äç'9Óq…E%)I=v¾©{Në{Ê/~mü#ÁOþ üqýŸÿà¤?ðqÇíàxu/üÔÿf¯Ù¿ö.ý¦| ¦E$³Yéß¶Ïü‹WðoÀÿŒì]ÖÆÖßáwÇoø#Ãz¥ä±ËÿÿÅ]oOÓÄk¨ÞJŸ¬¾ ÿ‚~ѳ_ì{û þÏ_³W‚¾ Ýêÿ³ÿü­ð£þ ñ‹Ä_4¯ëºg¾üø]ðëÁüøe<_à¹7ð¿Š.,¡â]/VÔnnî®=xæX*Ô°”«a§/«ÓÃÓ“åƒSö9} ;”¡ícÏ(Ö…^Y9+Ñ•6Ò”9 %Í&¥»“Ýõ›zi¦iÝ>íŸÏWíSûu~ÒŸÿi?÷MâÏjß±įø5ãü2ßö2ñ÷þ Y躾ñOÃwZEׂ>#xÏáßÇxª÷â}LjᲶԾ.xro G |»ñ'Â?xÁÿµýKöˆŸà¿ÚÜüTø«ðë_ø›ðÛöcø‡û?ü ÿƒ8¿á®~þÊ>6ø_ñ“Å? ~[†–ÞO xÅšßí3{ñ7¿¢ñÍ­¼´–‰âË/Švÿ³Å¼? ’?ŠQ¯í.?´‹ŸðOØ÷ã—‹eñÏÄOƒ6W^(¹ý™>#þÆWú„üañáœZ¿ì³ñ[L¼Ò¼eð?]Ó>ø»ÂN½àGŠþæÿÂúv³c}/Ãï´^*øsá[[jÐò‰ÿºý…¹'àoüØü:ìÅÌøÃÿ&-ßàwü”Ãþgü–¿ñp+ÐÂãðÔáMS¥R›5¡i-9n¤§*’ŠÖ³ŠªîÓ•·NoVž·W¿—–š­¶ûÙùGû7~Ü¿µŠ|=ÿÙý‚¿a‡_²ÇÂëðC·ÝåÏÇ]+ãÇŒþ iÞ:GÃßg?‡í¢|SoŠ^²ƒÄWÒG©|RñÏ‹¾6ø›Ã^ ´Ó¯õ |Gñ+\Ë«dÿÁM?à´ÿ´·ì?wñÇQð…gì{áoÙSÄÿµoÁo xã'Äø<iûEjþ Ðã_~Õº¯ÄÙ÷Áÿuk~$Öl¾ x/IýŸ?hoøßÂÚ6‡ñ+Æ?ÃÝÄÚΉàOÖ¿‰?ðJÿØ;âÏÃÿ‚ÿ |mð2[Ÿ~Ïÿmÿfï†ø{â§ÆŸø‚Çö}ƒÃz7„¦ø'âßxâ7†ümñCáf«áÿé6>"ð'Å/øÏÃ~'{i/¼E§jšåíÝÇ ñ·þÇÿÒý¡®|m?ÅÙ–Êæ×âG¾ü8ñîàŠ_þx;žø…¯Á[ wÀÿ¾%øÁ×÷ß ´½2ÏÃþñÚñ‡ü-ž°Õ¡ðÝÅΕ7U ˜'R3)¸{ÎQ䌥&ë)IÊnªrç§Í­Nvq“Wne͵ö·WÛM-ÑúÝo®‡óWÿFý§¿i\ÿÁÎý¢u¿ƒÿµGìÍû*éßðI­/á§ì»ñÁ_| ðëM¶øññ[áçü!.Ÿâƒ_´×ÃÿèÚ…´íOQ¹ø¹«Ùx™µoŽ_ô¿ø®Â÷á/ÂÉð ^ýTý»à´_?f/Ž_·Šü:ø]ð›[ýžà—?ðÀCö›Ñ|e§øâ÷ã_Çý»7‰®¿k¿|"ð7í)“ñ?ã€ôߌZÀOè~4ø9uâíáçÄ/ h÷*ø{­xwI·ðï,ìlüsoᘮ¼/ˆ¦ð^§©è_Æø'Oì_ñóã^‰ûBü[ø£x»â¶‹ÿGÚ5‰|$ý—þø‹ö´¸øðBÇãÏÂÚ/ÃoûL|¾´“Oð_Å€¿¶7„õŸüñ/Æø‰cºÑ?g?üð…=„Öþ“ö‚ƒÇòÜè–¨ÿÿà—?°§Ç¯øl?ø[á+ÿ†úÿ†}ÿ†´ÿ‹™ñ‡Bÿ…±ÿ ±ý™ÿ þE¯ˆ:?ü Ÿð‚cé¿òM?á ÿ„ŸìßñYÂCçOæß—þ “û\üyö“àe±øžŸ­ÿhŸ%<}ñR/…²~ÐÖš,ž¶øÿ?À(¼sÀ[ŸŽVúL²ÛÅñzãᬟWkÕñ¿?i®ú.’TÝùR½–êâùšÖj¤¯kÚKTïi›k®÷üß—k-þóý®†?k/ØLúê??/øY_²ÿùúWé5~n~׿òv°Ÿý„~%ÿêÊý—ëô޽é+arõÿPµ?õ?sÃâ­ÿ_þš¤QEdh|íû_ɦ~Ô?önß?õZøš½†¼{ö¾ÿ“Lý¨ìݾ6êµñ5{" œútúÿõ«óocÍ„áÏ,V}øÒÈù\èÁÿ¿ý{¡ÿ¥b¨À÷ïþ}©ÔQ_šÐ§·õø~—è@*uü鈽Ïáõõü?ÏJ’½Šöþ¯ÿÒý”åÑtßÏþóô (©{ŸÃëëøž•ìЧ¶Ÿðáß~ˆê01ùÒÑE{)í§üøwߢ2œº.›ùÿÃ~~ƒ”dûwÿ>õ55F¹ëþêö(SÛOø?ðï¿D@WçŠå(ÿ³wðwþ—þÕµú=_œ~*ÿ‚ x Õ»x8Ÿ¯ÛÿjêúŒª±Ç;Ìõ7¯ÏòÛCš´½ú)t¨ïÿ‚ªþ_Ÿ¡úIEWPÏ›><ÿÉAý‘ìâ|Gÿ¬¡ûO×ã—üñ®üKðÏüOö¸×~xÖ×ÁBÞïà†—ãÉF“­]ëþ$ø{âÏ? ¼­xKºöã §ƒ5_RñŠúþ·«é~<Ò5ŸZxËÀxFÞ÷Æ–5ðwìoÇŸù(?²/ýœOˆÿõ”?iúÍý¨?eÿ¶oÀ¿~Í´§ÿád|ø‘ÿÏü&ž ÿ„›ÆþÙÿ„?Æñï‡?â£ðˆ<-âÍ;û;ÅžÐuoø”ëÖ?kûØo¾Õ¦Ý^YÜ~Kâ"ž‹8[Z.t°¹VZŒ'9Ò£ÄÅJ‘„*8ÁÍÁICšQ\Í>hÙ5Û‚\Ô1i;7ZI;ÛW† –«Ìþy~:ÁQ~+Á<õÚ§á/Áo?²¶•û5Á"<-ÿõÒjxá'‹¾ß|jñíÙâk×5ÙÁ¾ø“©ø3à7†ü)qâY¼U…üuÆýCWÖ¯uûÅs]¤þ&½ý$ý¿lÚã·üoþ 'û"ÈŸ¼=û5~ÂþÉ/ÉàˆWß~ ËûNþΚ¯Ä£o/OÅËh‰áŸi“L/á}ë]øR8ü8l¿¶/[ÅúÔÿàß±ŸÇ¯:'íñgàvâ7ü!>~¯'‰|u£ø{Æ'ᦷ/ˆþŠŸ<=â'áÇÆ3ðû^žM[ÀçâÇ„üf|%|R}û=£g­ü9ý—¾|*øçûD~Ò¾ð?öƿڿþ'ü/ßÂMã SþßøQ^½ð¿ø§5Ÿj>ð·ü"ÞÔo4Ÿø¢ômù¿oñö¾¥W‘üƒÆe•0ÒPÁÍc*`ýœ«Ô„úåJ™c«Y·VNN^Ç5¨«r*xêXxÓTèB¤wQþ%ËÍ{mîÚV[yÃM½Öïï4~EþÓš&‹ûOÁr~þÆÿ´—‡4¯ˆ²G„?à›?¿jO |ñÕžµðâ·í+qûCø{á=ì¾<ð³×…¾'Ýü5øW+ëÞÑ|M§kxJÿ]ºñ>™kgxò]?ÉŸ¶ßìÝeû.ÿÁT¿àßï„_°þ‹áo‡—RkßðW­oÀZ'ůü\ø£ð³À:‹¿eÿ‡Rø’m3ïÄ#â/ÀžÓäÕu¿ üø{âχ^ ]BÐ|=/mu«íf×úý ?dŸ€´íÇuO‹ÞÕï|Wð»PÖ5?†<ñâgÁ_‹ßî¼G§®“âTð/ÆO‚¾1ø}ñSÂV(Ó#‡OñN• xÃOÒüKemim®ÙêÚ[$^#ð·þ qû|ñìýãÿ†m<3ãoÙÅ|wðoÅKñ⾯®é>9ý¨¼/¦x+ã×.ø·â7…ôm'HÔüIñ}üw¬éÐéÖsh7ºMÕ¼s¯§‚ÌhÒ… ËK‹ÂK FXZ•ká1Ô#ˆ“u⛕LM9TR¡)$ê¨ÎVŒfœ¾ÚÎ2æ»æI86–ù]µ¶ß/Ê¿„ŸðYÏŽ_´ìÿãøåàÍ'àÏjÛÊçöðÿ†¾iÿþ*þÑ~$ñ׌ÿgOø³Âž)°ø;ðkGøÅðÆ×Á/§øGSñϾ&|Yý§üáÏ‚Ú èVñ|B›ÄRjš ðþ OûbþÕ~ÿ‚¯ÂŸ‡?³O€<]ÿKÐ?ࣖ¦ø üQñ‡>ø§ö)Ó5;?x‹ÀÞø‘á Iü?­k:±â-ÁÞ"»Öu-gJ›Nðv›ã Þ}£Ç5úÕ¥ÿÁ#àžúÂ?Ù¿àv…ðãAøwû"kŸ¼Aû9Ûh>;hÞ-øeyñ“T×õ‹ZwÄÍ3âu¯ÄÝgßî¼O­ÁãOø£Åúç…Â*`øhødßø¹Ÿ5ÏøUðÔßÚð¾?ädø¬Âuÿ ×öÆ¢?âåÂcÿÇÚ?âÿ„{ɃÊô£ˆÉíUÒÂUæ•l]J*téµ uiæÂÓ“U›—³\ºn2U9%B»„æœc8’¨­y-TS³zµÉÌö¶¶šéu%u½¿#>ÿÁk¿iÿÚ‡àgü’†? >øSö„ÿ‚ êß¶gü%Wž;_ˆWÿ¼ ¤~ÃVž<“Ç–Z މâk?éš§Åk¯ é±ø?PÕuÏ[x íF]WJñÓXD—^Eðãþ ÓûTüJýŒÿà’>;‡áÏÂ= ö“ÿ‚•øŸöÚ‹Äÿ„ÿgÚ§öŒøcð׿±ß‹¼¥N¾ý™¾xÃÅŸ´‡ïYÖ'Öþ~Ò^ÿ…ÓñxwVÖ8|ñÇìÅñ{ÇžÔâ7Àψ¾ñG„|Sà_øsĺç„ux¥ðït­ÅZv‡â ¼Wàñ­éÑ^Ýx?]ЯZkÔ¹KëŸåßàŸí«ñ·þ !ÿ+ÿƒs¿l/‰>øYào†ÿ|aÿ¸—à?ƒ¼ž.Ox[ÀþøA®|&—DøÁqâWUÑõϸø¦kéâO '‡4«ã¯ßéMá $èPêßõáðCà‡ÂoÙÃáOþü ððËá7ÃtxÃ6Ïm¤hzoÚn/§™å¸¼¼½Ô5ËÝ[XÕµ+«Í[[Ö/¯õ^ú÷S¾»»›ä‡_ðIÿø'÷ÂOŽ¿ÿi?‡³å¯„þ1|$ñ‡Æß|1ñ›ñâùÐü ¯þÑz6¡¡|bO |=ºøqðãJð÷‹lµjæ/ZøI<áo]×¼KáAñ¹«j·šá+a©K/e%íUxáì¯ìéU¡‰¢©µ*žíÝjMÊõ$£NQ\ÎWŒŸ.»Zý.Ó‹¾‹Éé¢Ôü¢ÿ‚xÿÁcÿm_Û÷Ç åðÏìíƒðCö¬Ðkë_†Ÿ,¿c¯Û÷À±Ç‹>êž(Ó~ j_´Çë¾ýž¿iÝâïü"ºÅ¶«á߀¿~ê>ñÆœ¿ u¯è-Õ 6_~ÅðXÏÛ—ÂßðO?ø$í¿Œ¼g ürý¤?à¤ÿà ½§ÆŸ~Ì´‡íª|:økû3øãÇz–¯áÿüý•¼Uyñcã_‹õ²iÞÔ¾èŸ ¼ð«áš^“ªü;ŸDøY­øçÄ_Ó/Â/ø&—ìAð#ã=¿Çÿ…?4Ï üLÒî¾#_xJëþ/‰çƒ~ßü`¹‚ïâ¶¡ðoáO‰‡þ —û_|Að¿üOáN‡û8èÿ²‡í…ÿ-¸ý¥¡ñ†ƒûY|3ø³}áŸÙÛþÀºçŠþ$ÿkü‡ÄŸ~&ëW_dÓtËÿ„¶š·|)åxS\³×u vÜÅ-Ïç×í•ûGÁE4ÏÛ ö¿øûUøÇöj×þøOþ ©ý¯¿j_‹²Ã㎱û'ü<¶Ô?eŒÑë ñàÀ¾7ðññÆâ [Äß‹aÔ<ŸB¶¾š(f½ƒI—_m>¹a†K˜í–i"œ ÷ØëþMÇá×ýÍßúøž¹Œ¿ | ð7þ ññ[à§ÂÝ þ†_ÿco|-øuá¯í=c[ÿ„wÀŸþ jžðއý³â-CVñ¯ý“áý'O°þÓ×u]OX¿û?Úõ=BööYîeëÿc¯ù7‡_÷7êwâz÷)$°m·Öñ–ÿÁ8õî—þ•XújŠ(¨4>Bÿ‚|ÿÉ„~Ä?öh_³Wþ©Ÿ××µòüçþL#ö!ÿ³Býš¿õLø.¾½ ¿i_ù,ßðOŸû;ßë~ÛÕõí|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¯h¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(çÚ£þIw‡ÿìá?d_ýk‚µìÕƒñ[á½ÅÏàËÿx‹ÂªÞ!ð7Š´ÿøLèÄ6¿ðïÇ>ø‡á«ûñNâŸÏäx‹ÂÚ[]ZêþÕ,®ì¾Ók%¿ï„‘üW­üý¼¶uøG?k¦ÿ„{ûNÿûûoß¶±¾×/ö_ö¿Øf°ÿiý‡ÈûØÿÑ>ÕæýŸ÷;+ä8¯…±\GýŸ,6;.¼ÅƤ1²ÇFSUþªá*o Åūњ—<¡$Ò²iÜÒoc)·N¤Ôãœ=žŽ.¥Óç©ö•¬š>øQîy?çüóšu~z‰ÿ‚†ÑÝißøN|"ÿè`£þOü3þŽëNÿÂsáÿC|Í? ó(o›ä_)æû¿\tÓÏÈßë±ÿŸÿò‡ÿ/?Béè2séüÿÏô¯ÏøQ?ðPÏú;­;ÿ Ï„_ý £à_ü4p?k½;ÿ ¿„_ý õÝO€1ðß6É~Rͺm¾R¶ÜOšiP¯¯ýxÿåÇè•ùÝÿ 3þ ÿGw§á7ð‹ÿ¡~øQŸðPïú;½;ÿ ¿„_ý õÛO‚qpµóL¡úK5Û¯üÊúþZ_óâ·ßGÿ—¢#’­X•ø û^x¿þ aðXižø_ûQh¾$ñô+àŸø»í¾ø;q¡øGÀ(ø§áo…šëó?ìã§>ûÇÞ+ñ ÖŸáÖfrþðwĽvÜ ,3ý=ð“ß·‡ÆO‡^ø‘áŸÚÝlôïÙL×:>±áOƒ¶ºÿ†5í.öëEñOƒ¼McìÉ:iÞ*ðg‰´í[ÂÞ'ÓÒ7_Ò5$†ç¶Ÿ â"ìó,®É_™<ÊÍ»ÞËû6ú%­ÒNê×w±õ¥ÿ>kåþ]ý[Ðý]£­~tÂŽÿ‚ˆÑÞißøM|!ÿè_¥ÿà¢ä~׺wþ_úë¶Ÿ Õ†ù–[¥¶þÑé¶ùr2öíÿËš¿}þZ~Ž€¥-~q‘ÿ‚‰ÑÞéßøM| ÿè^£þüKþŽ÷NÿÂkáÿBõwSÈÜ-|Ãçe˜y_þ`õøÝÿÏš¿}þZ~ŽÔ¨;úð>ŸçùWæïü)ø(—ýîÿ„×Âþ…êwü)?ø(˜ÿ›¾Ó¿ð™ø?ÿн]´òÈC|~ úG£Óeõ%²"Udßðª[¦´¿ùh~׿òv°Ÿý„~&êÊý—ëôŽ¿1ôßÙ?ö¦×¾.|&ø—ñ㎃ñ.?…ºÐŸJ´¼ƒÃz3éúN©âokþ(x'à·—PÔuð6‹¨Öoæ¶€ÀÉ ´WÓŠí®à¡†¥ ‘«ì(:rœE)b1½ßi siF¬Sn÷”’ºI¹¦ç'ió$Úm%G^W%¼^Íéo@¢Š+œÐùÛö¾ÿ“Lý¨ìݾ6êµñ5{Hô¬ÏˆþÒ~(|<ñçÃM~ãQ³Ð¾!ø3ÅÖ®ôy­­õk]'źö©\isÞÚjpj0YêKc5Ý…í´w+Ïis¼/ñwŒ~þÛ_ð’j?ð¯ÿk­[þô?ìøL|9ð7þOøðµûöö'ìÁý™ÿ!?¶ýìßòáö_;ý#ͯ›âޝÄX|®žÂË_2©Uc^2*¤q”òØÓöO ƒÅÝÅàê*œþÎÊPåç¼¹n_cR¤9ÍNâ¹=žŽªÝùç ùÕ­}í¥þä¥$Züóÿ…ÿ ÿ£ºÓ¿ðœøEÿÐÁJ>ÁCGOÚïNÿÂoáÿC|Í/±ðß6É>RÍ¿\¡y#¡ãWJïçì?ùyú$=(¯ÎïøQŸðPïú;½;ÿ ¿„_ý ôÂŒÿ‚‡ÑÝéßøMü"ÿè_®ú\‹†ù¦Oòy§ë•/$eõ¯ús[ï£ÿËÑ 2@õ©ÀÀÇ¥~QüXð×íÛðoáߊ~$øŸö¹K/Ã6Ë•¤xSàýæ½âMoQ¼¶Ò<3á X?ìÉÔ¼UãjO…¼/¥ ¢mSľ›`’#\+—¿d/ÿÁM~3Mâ|Tý§´o üB·Õ~*j¾ŽËÂߣÑtñ¿Œþë|fþ9ðω¼ ž ŸV¶Õ§ðWŒþÝÛÜZxÿÁž<ðÝæyá¿kÐM ú\ÇrÖ—v—vÒÛ|ÿ†ÿ‚…ÑÝißøN|#ÿè`¯ˆâîÆq.;/Æa1ùf\²´ñÒÌ#UÔ†a˜âù °¹~.›¤éâéÅ7R3çŒÓ¦¢£)mCì#V•IóÕö‰ÃÙZÞÊ”,ùêAßš è­kjîÒýëVÀÇ¥~y€ÿðP°r?k­;ÿ Ï„ý ¿ð¢࡟ôwZwþŸ¿ú+ç!á¦ió|…ÿÜLãü3[šýv?óâ¿þPÿåçè]ùéÿ 'þ ÿGu§á9ð‹ÿ¡‚øQ?ðPÏú;­;ÿ Ï„_ý ÙOÃÌÆ6¾m’=–“Íöëÿ2Ž¿Ó®Çþ|WÿÊü¼ý Q’?3SWçhøÿ ?k½;ÿ ¿„_ý ¿ð£?à¡ßôwzwþ¿ú벟c£¾k“wÒY¯ÉÈ¥m¹ŦÿZÝ?ƒÿËÑ+ñÿãª~Þ|=¢ÜKûR·Œ|wã¯Úx+ágÃOøsà¹ñ?ÄßÅ-ÂiÖlý™í¬ôíCÒí¯üOã_êwú7„|!¤júö§8ŽÒ+{Ÿýˆ¼[ÿ(ý£<#­~ÕúFŸã?JðÄm%í|ð{FÓ|Qð{ãF7Š~øßD±³Î¹8Ò/Ο⯠$·wbê[ÿßÍ2?š—õÃ1pi<Ï)µÚy›Ié½òÄîîídÕ“MÇDçëKþ|ÖÿÊ?ü»ú¿©ûꃩüùÿ=jJüè¿à¡ãû^ißøM|!ÿè_£þwüCþŽóNÿÂkáÿBývÃ…+Æ×Ìò¿—ö›Ñt×-Bx­¨Ö¿­þ\~‹Ò¨ÉóúWç?ü(ïø(‡ýæÿ„×Âþ…úQð?þ :~׺wþ_úë²9R;æ9kÿÃŽÿønþ·3öïþ|ÕûèÿòÓôzŠüâÿ…#ÿÿ£½Ó¿ðšøAÿнGü)ø(—ýîÿ„×Âþ…êì§’8Úù†ì´Y†Ýæ¯ôÃÛ¿ùóWï£ÿËOÑÚzséüÿý_ο7ÿáHÿÁD¿èïtïü&¾ô/R‚_ðQ1Óö¾Ó¿ð™øAÿн]”òÈÇ|~ ¾‘Çü—ûŠÛr%VMÿ¥ºkKÿ–ŸMþ×ßòiŸµý›·ÆÏýV¾&¬ïØëþMÇá×ýÍßúøž¾Tñ‡ìÏûzx÷Â^(ð7‹?jý;Uð·<;­øOĺ_öÂËí/øLºÑõ›·i¿³=ž£göÍ:òæßíVv·¶þgksé«÷/ÀO‡ÚÏÂÏ„Þðˆ.tËÍ_BþÝû]Î5ÕÆ›'öŸ‰ufßìÓ^Ùi÷O²×P†9¼Û8vβªy‘ªJýÓ:XZ!Ziƾ&¬Ý(ÖQŠ©O ÝÖ¥E¶Ýü)¤’»ÖÄG™Îrp”„"¹œ[n.£ ¥§¼·±ìQEsšŸ!Á>äÂ?bû4/Ù«ÿTÏ‚ëëÚù þ óÿ&ûÿÙ¡~Í_ú¦|_^ÐÈ_´¯ü–oø'Ïýï?õ‚?mêúö¾Bý¥ä³Á>ìï|iÿ¬ûoW×´QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEó‡Ç?ÙóHø›ðû⾇áXô xçâ•Ç€µ[Å÷–÷Woy¨ü7Ö´ S£Q)#Ü *- CcaiåZ[\^ßß%±¼ÔuÞðÇ„ÄRxgE²ÑO‹hÿ ü-ák?Å£ø›Æ~ ø“û@ü@ð-®mfÒãÇÅ?ˆ^<Öl¬™Ý&M¥øÞ-/V±›ÍÓ®¯´Ë[èíüí?Kž×èúç<%âÿ xïBµñ?„5›=@½¹Õ,íuKwµžçDÕ¯´-VgHØ½Ž¯¦ßXN €'µ”)eˆGEPEPEPEPEPEPEPEPš|Xð-ÏŽü ã=+ÃÓèÚÄ GÀ<ð¯¹Ño|=oªék¾›Ï]éW­áÛ“ m*xžÝ<ÕÕ]£ô?øóÂ~Ö¼áÏêé§ë¼O}àßiÿd¿»›^ñ›àß|@¿±ŒØÚÜÅc§„<âm^}GT’ÇLA§%‰¼þÒÔ4Ë;κ€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€>Bÿ‚|ÿÉ„~Ä?öh_³Wþ©Ÿ××µòüçþL#ö!ÿ³Býš¿õLø.¾½ ¿i_ù,ßðOŸû;ßë~ÛÕõí|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¯h¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Šüîð?í}ñãÇÞ ðŽtÙ÷á%¶“ã? h+Òíõ/ÚKÆPê0iÞ#Ò­5‹(/áµý—ï-¢½ŠÚò$ºŽÞîê]a¹ž0²·§—dùŽmí¾¡‡Uþ¯ìýµëaèò{_iìÿV—77²ŸÁÍn_z×𯊡‡äöÓäçæå÷g+òòó|•­Íí¾›;~ˆÑ_ ¯íûF7O€Ÿñ%üuÿЫSÚöŽnŸ~ úÉÌxïÿ¡R½Â\@·ÀÇÿ püÔ`³Ú³ÿÁU¿ùYöõñ8øýûH·O€ß}?äæ|yÿЧ^Íû?ühƒãg‚nuÛËxwÅúмyáø/AñŠxÐxoRðGÄOø9åÔåм+ª5ˆÿá¸×tiuO èwX]ª-´ŸgyŸ‡’æyuׯaÕ*R©1’ÄaªÞ¤¡9Æ<´kTš¼iÍó8¨®[7v“Ú–/^n¦å%&œ*GÝM&ï8Eo%¥ï®ÖL÷*(¢¼£ (¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢ŠñÏÚ'F#ýŸ¾:xy¢óƽðsâvŒ`s0Õ<­Ø˜¾eeýàŸg̬¼ò¤dU€Ññ'À‚ž"2‰Î¿ð“á¾´g‘1Õ<£_A,ä‰ ûò]‰ÎK1äúÄ‘Ç4rE,i,R£G,R*¼rFêUã‘tu%YXe$A"¢´´´Óí-l,-mì¬l­á´²²´†;kKKKhÖ{[[xU!···…(a‰8£EŽ5UPŠ(¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ ù ö Ë~Éÿ nO?Ú+ã}_Íïr5Ÿˆþ0ÕEá=K^‹ÁvÄ€ÄÎKÄõíSÓôí?I³ƒNÒìlôÝ>Õ v¶:}´6vvÑ–g)­ºG(]™ŠÆŠ»™›$ÐÊ(¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€><ñz·ŒnO‚úß7Lø/ðâßÅMJoû?‹¾(x£Á? þÝ«g·ü"¾øãhr7η$DÊ‘\+ý‡EQEQEQEQEQEQEòüçþL#ö!ÿ³Býš¿õLø.¾½¯¿àŸ?òa±ýšìÕÿªgÁuõí~vÁBüs«|/µý—~%èúuæ»ðïã?Çè¶šÄ77MÖ­á/ø&×íí¯é¶ú¤Wz}äút÷š|1_Cies%³J]ÛJÉ2{˜Ó¿k2qÿ “övçþ­âWÿEÕ|³ÿSÿ’eðkþÇOÚƒÿ]}ÿ¯Ñ´ýxOóü«à¼@â,ã‡pÙò|M,±ØŒæ8¹TÀeø×R8Jy;Ã¥õü.+Ù{7ŠÄ “Ôýç7$9wÂЧ^¥eUI¨B‹ŠJ”ìç*ÊMû9Æ÷PŽ÷µ´µÝü7û/ö³ÿ¢Ëû;â1|Jÿ躣û/ö³ÿ¢Ëû;â1|Jÿ躯u¢¿?¥â'Kâ̰¯¿ü!ä ÝöÊþ^lë– —ÃRïoöŒGÿ-<+û/ö³?óYgoüF/‰_ýuðóã´¿~#øñ“ƵÿÝ|<øyãχ—žøgãOiÿÂCªüf±ñn•«[ë_>+ØŸìK†z^¥a}&³ }£ûZòÍ,ïf‚'H ïù~zkÿò“ÏÿÙ½x?ÿKjºû®âŒó:Çâp¹–*…zÀT­Ã-Êð²Xbp‘Œ•\&…ehÔœ\yùd¤ù“ÙrWÃÒ¤©Ê iºœ®õjÍ4éÔvqœåÒ{][Cô¾Š(¯ÐÌŠ( Š( Š( Š( Š( Š( ¿&¿f•Ïìíðù¢ß qõ>вóÒ¿Ykñö{Ðþ=MðàŒº7ÄŸ„VL¿þI¥Xê|e«j6Zkø7Ek+[ýR×ö‚Ñmu+Û{SWz…¾¤Á{:Is›c«kèþ»G8Õ+¼¿Wú޾Éÿ^g…|X_Jÿ‹ }›tü‡×¹ü?ÏJЉ:qì3ëÜþç¥xl~ý£¸ÇÅ_‚C¯ìõãÓǯüœàë×ßñ®oà¯Â_Úàu÷Ä-CÂ_¾ê|Kñ^­ã uèògÖ”WÊkíhHð¸?gaëÿÍñ+üÛŠæì>2üMøuñ¿á·Ã_Þ:øS®è_|!ãë ]øá| «ÛøëÂÞ.ø? hš5ÌúÅï‹Vwzv½gñV–Y®,4(tûÍåÕLW/ ÆymHFrUðóp„ê8A×Rq§9Ûž„#uÉÙÉ^ÖWvN–"-¥É5v£wÉk·e{M½Ú[~ÚQEy¦á_ ~Ü>ðï‹í?f¯ø¯@Ñ|O j?´M×Ûô?év:Ö}öOÙ¿öоµûf™©AsesökÛkkË>òn­à¸l±Fë÷-|9ûoø“þ¶ýš¼EâÍEð¿‡ôÿÚ&ïíú[G±û_ìÝûEXÚý³SÔ§¶²¶ûMí͵¿:y×W[ǺYcF÷xfë>ËÝbU½y&rcÿÝ+ÿƒÿnGÏiû6~ÎÝþü8þø“øèGÞ®Çû4þΜgàÁCŽN~øþò?ç5´—ìè1Ÿ¿99ø©à^¿ø>íý=êä´§ìãÆ~?üäçâ¯áÿ!áþs_·Uu5·?ee/&ÿ¯Cäâ¢Þ¶²×§•—åòDñþÌß³‰Ægÿ‚\óÿ$«ÀœüŸÿY«©û2~Íçþm÷à‰'¦~x§þ?¥Aí/û7÷ý ¾ŒúüWðÀ÷ïþq?iŸÙ·¿íð Õ5mt¿?µ]Næ;+f{—·Ó´»+ÝJþeˆÇiaisypÑÛÁ,‰÷·Â/ÚÇöYÓ>ü0ÓµÚ[àŸ¨iÿ<ea{ñ“áÕ¥í•í§†´È.­.ígñsÛ][OÜ[ÍK ¨ñÈŠêT~{Æ|UjØ м´ë¦ãJ¤ùo*m&Ô]»¯™íe“§UNpÜœ¢ºK»ô¿©öe|÷û*|Fñ7ÅŸ€¼yãIlçñv¯c¬Ùxšm>Ò;95Ïx›ZðƦÐYFÌ–«öÍPЦ' Œ‘°1¬_ð×ß²gýìíÿ‡³á¯ÿ4Õâ¿³çÆ¿ÙoàÇà ?‡ºíoû5ëRiþ1ø­®Ú_Y|eøymhþ9ø­ã_èqŠãÅ'Útmĺv{ c÷–ÀF£ã~£ÿ 9ˆj:.¦j°ÚÞÀf°ÔËP´šçOº¹µ3"Næ¿\þx‚÷žð7е­`ÔZ^Ù^ÚxkL‚êÒîÖÇ=µÕ´ñÉ Å¼Ñ¤°Êˆ®¥GçœaC^¶†"«:é¸Ò©>[Ê›IµnézžÖW:p…TçÝÁÙÊ+¤¼×“ùŸfWÏ¿²‡Äoü_ýš>|UñŒ¶sø›â7Âÿø×XšÂÑ,,äºñ&k«o‚Ê6t´CÔdÛ«°‰· fÛ“ü5÷ì™ÿGCû;áìøkÿÍ5x¯ìÛñ³ö[øû<ü ø5«þÖß³V»¬|+øGðïáö³®iß¾ÛiúÖ³á é:¯«éö×>&7Ö:ž¥cu{gm1ómí§ŠùׯýGÿ@x¯ü'«ÿȧ¶¥ÿ?iÿàqÿ3ï:+çoøkïÙ3þއövÿÃÙð×ÿšj?᯿dÏú:ÙÛÿgÃ_þi¨úŽ7þ€ñ_øOWÿmKþ~ÓÿÀãþgÑ4WÎßð×ß²gýìíÿ‡³á¯ÿ4ÔÃ_~ÉŸôt?³·þφ¿üÓQõoýâ¿ðž¯ÿ Ú—üý§ÿÇüÏ¢h¯¿á¯¿dÏú:ÙÛÿgÃ_þi¨ÿ†¾ý“?èègoü=Ÿ ù¦£ê8ßúÅá=_þ@=µ/ùûOÿùŸDÑ\ï„üaá/ø~ÃÅžñG‡|iámWí_Ù~%ðž·¦xÃú—ØontÛß°k:=Õæyö=FÎîÂëì÷2}žöÖæÖm“Á,kÑW4£(ÊQ”\eã(É5(É;8É;4ÓM4ÕÓÑ–ši4ÓM]5ªiìÓê™Æxçâ?ÃÏ…úM¾¿ñ/Çž øy¡^j0èöš×Ž|Q¡øKIºÕ®-®ï`Òíõ-~ûO³ŸQžÏO¿»†Æ)šæKk+¹Ò&ŠÚgO)ÿ†¾ý“?èègoü=Ÿ ù¦¯>ý¯Æo¿eáëûDê_úÌ¿´…qÑ.qù}ëþÿZû|‹…0Ù¶[ u\]zS•Z´Ý:p¦â•9(§y+Ýß^Ç•ŒÌja«:Q§ %»ÉÊþò¿Cܿ᯿dÏú:ÙÛÿgÃ_þi¨ÿ†¾ý“?èègoü=Ÿ ù¦¯‰sËè_ðçúÖŒIŸlþƒÿ¯Ûÿ¯^”ø#ÿ¶â¿»Eí¯oÐÁfµšOÙSWó—ùž£ÿ }û&ÑÐþÎßø{>ÿóM^ñ£ëOˆt/_Ð5M;\еÍ:ÇXÑu­úÛSÒu'S¶Š÷MÕ4½JÊYìõ ;P³ž»ëI¦¶»¶š)à–H¤G?#D™Ç¿è?úý¿Zãàš1ñ'Œ?dχßð‘ê?Ú?ð‹i>ðvƒþ‡aiö è |ý—§ ZÚý«ì¿jŸý2óíóïÿHº›jmùÌ÷"ÃåT(Õ£^µWR³¥%QBÉrJW\±‹ºq¶·½ÎÜ2x™Î2„cËeË{î–·o¹÷åW=âíçÄÞñ?‡,õ{­ïÄÖ´K]zÆ4š÷D¹ÕtÛ›5{8¤dŽ[­6[…¼·ŽGTyaEvU$˜=ÂÿdøÃâGìóà/‰¾6Õn5}Sâ„Þ1ø›£Ïq¥±²ðÄ/øŸÆ? t(#±†Ïà õŸ x~Êå–[«ëM6ëë«ËÛ›‹¹¾•®OÀ^ Ò>xÁ¼?“ xÂ~ðn‡ US‘áÏDÓc*Ÿ*ì²±v¯Ê1ÀÖPEPEPEPEPEPEPÈ_ðOŸù0؇þÍ öjÿÕ3àºúö¾Bÿ‚|ÿÉ„~Ä?öh_³Wþ©Ÿ××´ùsÿSÿ’eðkþÇOÚƒÿ]}ÿ¯¦¿áKüGÿ£·ý¡?ðšý”¿ú+æ_ø*Ÿü“/ƒ_ö:~Ôúëïø(%~„kúîám Zñ7ˆu]#@ðî“¨ëºæ­{'•g¥èúEœÚ†§¨ÝÊAÚÙY[Ïsq&È¢vÁÅ~qâf.¾ Ãj<%GSž¦±9~ïY/³XÌ6!Ó¿3æö\œö?7$yzpPŒêâ9œÕ©Ðø*T§¼±¿³”o¶—½µµ®Ï4mkÁ¾"øÃâ¿ÙãÃÿðR_ë¿ü  Ûx«ÇtoþÄŸÆøbòÜÚxÅ ì¿gÙükáÝêÛÅþ¸¶Ö5}ÏOžxvX®=oMkŸ^¾$ü5¿í ÏýK_²—ÿC% ¿²oÇ-Àÿ´ÿìsÿŽ×¼9ñûÃßÿmø)íáÚRñoìÕûJøWá•ûþØK¤üý˜t©¿hÏ|-Ò¿gŸøsযðÛÁ~3ðõ‚þ)kÚõÓøâ{[«mU¼9ªØøúý™?kÏÚö‚ýªio|Hýº>þÊ øÿTñ¿ìà/Øãâ'Âw^ý¢>x3À^¾ð°ð׊ |HÚãö…ÿÂköQÿèc¯;¸ý.n¾'ØüeŸöšý¡_âN™áë? Yx“û?ök_#@°ÉiaýŽŸ³¢è2yOã7Ú¦Ò体ûOܸ²Óŧâìuÿý«>$þÉ?ðP_‰?µ÷Ã[Š2øSÿ$ñׇ¿à“^'øið{Mñ&§â_Ù?\ø“oà/ü ñ†õo~Ñ¿ðxMø¡ê_߯Ãã°×´Úý‹Løhɤë×>ð;þ ƒûvÝ|&ý¥mµ/ö|C­ÿÁ½ÿdðWÅ3áwÁí._Ùkö©ñ'†ü{Ÿû=iš7‚/é0ñ¬Òx7Sµñ&¥¯é—×–iéàž}ƒ«]ákåXz´eõjÓÃex “uýJJ¤rº|ôáVš”Ú”¨Ôq±•Z‘qŒºtd£Ì«I?z*UªÊÚ+;{WfÓõ³w²nÿÓßü*Šþnóö…ÿÂcöNÿèa©áNüNÿ£½ý¡¿ð˜ý“¿ú+ùrÒà¬?¶^¹6µo¢þÑzN¯qáÏø4ŽOø)¿™àO‚—òèðPX¬týR_‹¼v¾•ôÝZ-7UÓ/äøGx!øT–ZË|9Ý@òy¶·ûuþßþ>ø=ñ«àÿÄÚçÆš“übÿƒW4ßø+›ãŸü9ø+ðÓÇß >?Co‘âx]ð¿Ã…šÇÂß4H§µñÅåÄW>1Ð5m{]×~ø—àž¥màÙ|#ïRÇq>žÓ3Ã$œSÿd·fãy[ê dùšm6•£vìò•,>–OüWª_ôó§çsúÖÿ…;ñ;þŽ÷ö†ÿÂcöNÿè`¯Ö®]¯»•FžŸöþ5oåOùü¾z¥ÿ~)k'ãÇÿÙïÆ>5ñ¯ÄŸü6ñn›ªøsÆ,Ñ~és ª|%øâѯåøwá¿éWZ¯Œ+x>û⧃ÿeoé^ øwûêž+ø]o­IãéÚ/> é·¾%ñ.¿¡Ùè÷ú¹ðù¢dWšUÞ£ý,~Ì¿¶íû\üý¢o>xcögÐÿfÙköìø‹û|JðĘþ+é_¼A§ü!ð¿‡î|}ñŸÀÿ¼=¯øÜÉâiø3á&»ðžÆÓÄ^·ÔgÔþ2xjílšççËÚkþIñwþ y}sŽôSþ ¿û)øçá'Ã9ÏAý«<)­|7øàÍoÀÚ×»m-t= ö”Õµ»mcÄ>¿>0ÐWY}mn|E/ˆõíFÎÏÆ¿fûçáÏìoÿÿø÷㆟·ß€~Øx¯Uø§?ÃßÚ¯áÿ‹ÿ·¾,è^ñ'ŠüCàCð÷ãõ×À g_Ñþ[üa>×-RÃâ'‰¾ÚüWÑã¹]ê–¥»¤?#†§Z£K…¯“§Œ¬¨Öqu'R>εH´«9R•j~Ñ|0­9URön:úsp—ÅJ¤9S§8¦’‹N1ÕÃÞQ—+ÝÅr¿zìø×öVÿ‚þÖÿ?d¿ÁMüAðÿöt± ·ÂÿÛ+âœ_ôcñSÃ_µw‚4?Ù—^ø£x.Ò__]xÓáÅ}gâ<Ÿ Ábj“ðë㜿þü»ø—'ü>óþ /û;_|9Ð~#Øøþ¿ÐüTÚÀ¯Zgˆ~ êÚ׌++M[ľ<ñWÃïGñµ‘¾1/Ã_„,ñV·ð“áIø¦¨`ø„ß <á7ñ}¤×vZó_Z^]C6¿Á?ø&ÿìSðCøá¿…ßt]/DøÅàOøU^?°ñ/‰¼{ñ5/„‹i¬XÃð‡G—âWм]qàï„v–¾ Ö¢Óþx.oøNÃØxvÚF ¸Îž:ÔÔ«ÆñŒ”ä§)sÍÂ*UöQINw’§{Q½á)+F7FîÐ{§²Ok©;´¬¹š÷’Õ-[üŠþ !ûN|Á| ð•ÿƒiÏüDÿÅ3ñ#öøA8øA ÿÇü”?ð´0kʵ¯ø+gííã?„?<u/ìåðÃâo‹?àÞûø,7Â/‰¿>|AÖ$øi¬=´z_Œ~_øÆŸï´ßëZ½ºê:÷Ã?ÊÖÚGÂ{PÐàñ—„þ=é^Ôí¼iû'àŸø$Gü×áúø¬xWàÍ”¾9ý’6x_Pø¯uáÿÚAøEq •—Ã-/ã–›ñÏÀº•¡ø_Âðé~д}&Çõ-$Ãÿ®¹cíi©*µÓK—ÞrqJêWn1º~ꎉÙ{ÍÊò•¾]9U·¾‰.–Û®÷û––JX“×ê~žŸùé_þÔ£µ—ì7êuˆßú²¿f%Ô¿'ø[6™â¨þ ÃðÎ+é> Ká&ÕÓÁ‰grþ#m5mæ)ñŸí™ÿdý¿fïØ§öŒý°>øÃà·í›'ìÝsðÆÓÅÿ ¾þО¹{{ω<ð®ÒÓÄ~+ðmÅðmÍ“øªïZ‚ OÂ÷2ê­ ÝiQ­¡š]BËðöyÿ‚MþÜú7‡?à”ÿ³ÇÅÿÙ{O¾ø]ûxþ 3ð‡ãö‡økáïøûHýµ~|LÒþ_èWžÕ¼Sñ+¾ ñëxËFðŒ(Òü ∾ ՟Ě߉>hZNƒ ê+ò¯ÿÁ$à§ÿìeûbþÏžø­ë¾ñì«û0|øáÚ7DZwŒ¿jM Å? ?kσÿõ¯ƒ? ?k†úÏ…ßÅŸ²Ãß…¾ ñ·á½7ã|ÿ/î|E‡„> øJòÞîÛUð«æ9Œ¨Ï—V¸¸Ñ«)B¿Ô¨ÖO–påqŽ&¥J1øÕISåP~ó;!B2½XÉ)ÆéÊ)8:®ÅÝ7¤×»ËÌ›{'ý¦ü6øÇðƒãñ;ü%ø«ðÛâŠx'Äw¾ñ›ü:ñφÁ,~|ø£ñŸÇ^9ý¬õÿ…ž0ñÿ…þ/ü ¿øå ¯Â?²ÏÄ^x—DÓ¼?â?Ç£ø£â§†¡×õX'[é¾ ðæ·u¯x2 øŠM+Eø§ÃÞ]j5ŠóÃÃVÓ`{¸ÿ,¿kÿø&ÏÆ¿‰¿µÇü·Çº§üp~ÓW¶o¿ÙÂ_°_íÞ%ýŽ¡_€ß~þË3ü5ñWÄw×>*|pð‡ÇO„Iáÿ‰÷^ÕdºøsàmcZñz|8a-…Ι‡çÔ²Åâ1N•9B…O¬b)ÍFmœqQ§;:U…IÓ£8¾]Tã~YsNùÚm5É +µñIÓrZJ7q‹”^·´[jêÇôCmûpþËú€ÿ>3üføgû4é´GÃï|Bø}á¯Ú;â7>ø¶êxgCñ:xnM#ƾ"ÒÇŠ|?oâ ?Oñ“¦K}.™ª7Ù¥bZ6Iñ/íQû/ø#Çž øUã?ÚCà'„>'üDƒI¹ðÃ|`øy xóÇ6úôÂßC¸ð„5oZx‡ÄÐk3‘“.‹§^¦£)Y´ÎB×óÇûx~ßðRïøÁ_<;á~Ðþ½ÿ‚>ë³Äïü%ø¯ðƒàÿÄ¿Š_·>‰á›ÍÚ×íñ3⟉|ñCâìÕy&­âOè|;¯ÞhºÏÄÏk“|RÒãðÆ¿«Þ×ÉÞ+ÿ‚IÿÁ@|5gð#YøðUÓ>0ø£öMÿ‚f|2ý¡´ßŠ¿e?Úöø£ã_Ù›á‡Ão‡Þ8Ò?kÙÇâîŸk⟠ëíü1ª¿Ão~ÍšŸí$ž*Ôá‡Ä~—áw‰µ_Ä|5qx˜·‡œ”T.ù&›n7•­xJîÉZiFïâqŠžÑ§•æ–ýVŠê×½šÒúY·eçoëÎÚö“âôŸ³ä_¾Iñò,ës|âo‚Ÿâü:*Ø®¦uy~.¶|i˜4×M@ß¾Š->ÂéveòH~7ý´ট ?dŸ~Ì? <<žø×ñ?ãÏíÕû0þÆ^<øq¢üfðîãÏ´Ì,ºÐ¾-ø»Â:'ŒüA=¦kᘮtŸ k:wƒañu¾©¶~/Ó#·V¹üvð_ü§ö°Ñ?à 0ñ_<)ñ§Æÿ õ_ø+Ö£ÿøñwÀŸ¿d†¼#à=KAX¬4OÙx“àGÅÛ“Ç>;ðÞŠ/¾j?<1¯øö}ñ¿‚õ[MF×ÇõÝ*çĺ§ñ þ iûqCûMéO¤þÍ:'į[ÿÁÇß ÿà¨ûT¯Åƒ:~¯¦~Èþ!µÑ$ñ'û?x£ÅZoÄùbø;>ƒ¿‰|2––íªK§xIþøwÇK©\øg’¶"»Œ¿u(IÔq¿,¤ÔSMifšzûÿÛ\Ö‚KÞNé>ÛÛKùvß®Çö‹ÀÏ_¯ÿZ®Æ¸þCüϽAäçðÔÿŸz½þCùÿž~¸¬ªË{ôÕù··õÐQWi}þ‡€þÖkÿ§ûMû~Ï úÿ¸ñ/ÓüŒW ~Í~1ñ'þ x/ÅÞ.Ôµ¼C«ÂGý¡¨}ŽÂÃí`ñf½¦Z¢i–¶V1yV6VÐ~âÚ?3Ëó$ß3É#ðßµ¢ÿÆ(þÓžß³×Æ‚~¿ð­üKËú{Ö—ìuÿ&ãðëþæïýNüO\8×|&¿Öq·ùÒÀ¿‰Sü¿ôª§ÓTQEyFçåìпñŽŸ½OÁ_…Ÿ€ÿ„Bÿõûñ_ŸÞ ÿ‚˜üPøßñö˜ðìµû*xsã®…û$~Ðw³ÇŽÚ‡Á i ýcA¾Ót¿xûáïÀïøÓÀº·Ã=Q»Õ-tOxçö‚øo'Ž$ðψ ðÍÕõšÙKúû3.gO€>ÿ>Ãþ]?çÛÞ¿ÿoïø'oÆïÚö‡Öþ2~Í?±—‰ÿeÛ_AøÉàFøOÿø-ûNü0±øGâï„öz߇Óľ-ý¥>\ë^ø¹âÃàˆ5 ÷á÷†þ |B}NúßDÒµŒ^(ðL7z=Çî9½lU(Âx_i'²ö”¨Ós«V.Òœå‡ÄÒ§8ÊÍ{hBk‘Ö§}~K mZ¥•ãJNÑ‹ºø’œ$ÓW¿+rW¿+éýß~Ó³^‹áŸˆ4Õÿh_ÚWƒ¾øäü/ø©âÍOâÏ€¬|5ðÏâXÔ4­%¾üA×®µø´¿xäjºî‰¦ øŽïMׯ¡¬évGO…¤s;Fý«e­oƱ|6Ñ?iO€:ÇÄYüyã…xKøÇðïPñ¬ÿþÇe/þÅá[OÍ®ÉãÏéiÒøÃ `Þ ðÔz…“ëZ}’Ý@dþUjÏÙëÇ:ÿü·Ã_±O…M•ÿìÝûd|Qý–¿à¥ÿ¼?gr“àOû&øgâφ|YáíwM‰Ý¬ôŒÞ0ðŸ…o¼Ks{“SñÏ„¢Ò¯-Ä7ɵZÿÁ/ÿl+mr[|°‡ÄW_ðtô?ðP‹­z/|Mbãö ‚kûˆ~'Í©/Åäš\7¦¥< ÚcñA&¿»•~‡¸‘›ÆžgŒJ±†ž410õ}¤¯:r»’åNÑ)Ò”–’”Ó¿#GRÃR´ªÙΚNËIY[}Ô”Ô»+>§ôÙ'í ðËâݯÀ ¿Ž_­~;ÞØ®©eðRçâg‚ ø·y¦½ƒê‹¨Úü7—ZOÜX295»‹Fx „mx$6èÒ–¿mßÛ÷Lý¾$þÉŸ4øGÅÿ¿lŸøëÁŸ áj|X> è§ádžôßøŽûÇÿ“À¯ô;ÝFm{Ã~ð?‡to‡Þ$Ö|iân×GÓ †a¹þ!ý›?eÚà?í=ûFøkâ'ìOðëö¡ðOÇ?ø)ߌ¿n_~×?>!ü¶Ð~øÆ>Ðl<5ÿ߆\èwÞø}¤xsÀ>ð•Ö•âkë»?Œþ´Ñ_ýÿ8ýžtÚká>|Bý‚,¿nï„ÚŸŠu)ü{¤ø{ãþ|rø=*h²Eá߈__â&¥ð÷Ã!Ö¥»šïGñ|{øEwe£Ìdž¥ Ã•lF*¦´£aZ5-ºU[²©Ë­)óóÓ½ªÒ…Jqçæå~ÎH¸Â©ß4SmJ+Wô’µ›^ã”dôW³ºúoá—Ç¿Z|ñOÅ_Ûᧇ?bÙ¼ âi~.>:øãðËÆ? WÃZEí¥¦“ñ3Dø¹¦Þè|>ña½„hÿð±ü7ðËÇVWQÍiâh¬ö_üÅñ§þ ¯ðá7íûü4ÑüCð‹Çß¿n-+ö¼×nk 3ã߃l¾ü$Ðÿdß…z_ÄOXŸXµÓµÏxËJñ-Ρ?†¯õ> x:ÓÁÓØË{q6·!}2Ê/ Á3?o}þ ßð#ሟşµ¿ÙŸþ ¯ð×öÍø]û0üRø¿à_üLÔb„ž.GðoìãOŠ×z¼_µïˆvšl·^0U›Åq|)µÕÃÚv»§i:vŸ—Ðúßì]ñ¿ãoíñÿ©ý¤¯ÿàžŸ foƒÿ>6ÁL~&þÐÞ Ð¼yðÅ7ÚV©ñ·àÿ´?‚?þ/è¾ ·ðþ‰â/ÿ¾ øJ cÄöŸ .><êš>‹ªøâ¾§µo¬|º˜¬T¡©Ô„ŸÕ]êSsœ½¥zj»›…8ÒJ4ÜÔÓ9¨þñ(5§D)Ó»÷¢íí>e¤…”›–²µµ”tQ»Øý½ñ7í;û4øáw‡¾7øãö‡øàß‚Þ,:`ð¯ÅÿüZð‡~ø”ëQMq£ÿÂ?ãý_Ä~ÖN¯½ÄÚ`Óµk“ Iiæ¤nW°ñ7Æï‚Þðïƒ|_ã_‹ß üáˆz§‡ô?‡þ(ñOü)áÿxë[ñe³^xWFðv·«jÖšg‰õ_Z#]xOÑn¯®õ›eiôènbRÃù'øGÿ§ÿ‚„xö"ÿ‚*[k^ø‡¤üNýƒ<_ûvßþxûö<ñÅïìŸÚSÅßí~x¯áö«ûH¯ÄÿØçÅw~ðÖ£§[Lñ¥Æ§w§øSǵ·…dѼs§Ås¢ý]¤ÿÁ1hÏ„ßÿàˆ—~øñðçíyáÿÿ²Å?Ú3áGÅ]ZÛCý¬~ø§ÃžÔ¼Iñ{Zøkð3áwÄ=#à>¡ªÛéÚµ¿…þÿk辿“Gø}¢üG›DþÚñ#Å×’÷°Ò…©Ñ–±ž’¨¨:‹m]?iQJì×±z>gɯ³‚²SOY-Ú<Ê=~×*³»^òí¯êÇü›þ _ðÃöíý•¿dÿž*‡À_³·Ä¯Úüüuÿ…Sû;xƒã‡¼Sãÿ€øãøëþëê:'Ãíâ_öá oø³þ¿øCt}n¬fÊÝ5{Û~/ÿ‚˜|/¶ýª¿à›?> Ãà/Úá¯ücþxoöŠøcñ“ÃÞ"ð?‚Çì‰ðæ×ƺÏö~Ñ<[ |N>$×ÛUð6¥öx[þ½cDÔëûnò"ÛðÓö'ÿ‚hþßß³WÂßø7çÆZïìïo­|Dÿ‚|øƒþ {¡þп"øÁð~ËWðŗ훩üDÓ~øÄx¾ê¾ ×<'£Ûê:½ã˜ü¬xÃÆºV‰«Ñ<â?Ù^hô¿ðNŸø&íÇð$ÿÁ¹â¯ÁøE¿áƒ¿áï_ðÕßñr¾ëŸðª¿á¨‡ÿáEȹãýcþŸøN¶4ïù&ÿð˜Â3öŸø¬áògò¼÷ˆÄ8ÓŒ¡%;Qr›ƒM·õWRéÇ•?ÞWRI'fôMi·$5šµåe)ۭݹan÷êP~ý¡ÿgï‰?|mð›áßÇOƒž>ø©ðѤOˆß üñ7Á^)øà†ítùSƾ Ðõ»ÿøU¢¿t²‘uÝ6ÀÇxÉlÀLÁ Ÿÿi_ÙÃö²ÕµÙãö€ø%ñëNðúiÒë×ÿþ+xâ•–ŠšÅÆ³g¤¾­uà}{\ƒMMRïþ µÓžñá[ÛX‚ØË.™z°4ðJOø%íiû/~Ðÿ²\ß´/„þ5êïûÝ~ÜQŽéñóöCÒ?gŸ韴§ˆµ›í=¼#ð¿áÿÀmoöÉøÏ¨øøêÚ‹<]iûO|aø{gðÆÞMgáö§âOßÅðñT?à‚¿²oÆ?Ø£þ aû3þÏ´OÛ?…ß|qñªïâO…mõßøªâÞçÅ´ÅøR{ïü>×|Oá}rk¿ë¾¹ŠKR{ Y¡ÒoÒúÆæÂÛ•Ö©+sAÂê]ýÛ*zmm\¤¯ÕFêÚ¥JNéÞÖ¶Úïæö²vé{??«¿aø“ZÖiÏêzÚ|=àŸÚã7ü#:Øì!þÌÿ„“ö„øí©ë_évö±_^ý¶ú(çÿ‰…ÍßÙ¶ùVžD%£? Uù¯ûÈýûdú¸O‰ú½¾=×éEpcÿß±¿ö‰ÿÓÓ6¡ü ?õêŸþ‘áÏÛ^±ðÅ·ìÓ®jPkW6V?´Mߟ‡|5â?ëöŸÙ»öгì~ð–•­øƒQÛ-ÄoqýŸ¦\ý’Õg¾»ò,­®n"ðXþ=øÌã_áû5~ч}>ŸëÚ¾—ý®†u/ÙpÕÄêúÌ´•|SûrþÖý†?dŸŽ?µwŒ´[¯èß¼%±oá›+¸ôéüMâMs[Ò|!௠iÉÒéPxƒÆ~!Ð4{­Wìwí¥Ú^ͨ&Ÿ~öËg?ê<'ZZ¯–*¸º“ôŒ!')I¥7ËÉ蟒ïàf0”ñܱWr4—vÒIn–­Û_¼ôØþ?xþ€?yôýš?hãí…'ü:UèÿhÿÐã>Ÿ³/í"p=±ðœÿ‡Jøâ§Åÿø)—ìíû:|vý¢¾([þÄ~0Ñ~þÇ¿¾9ÝøcÂÞøãào|/ø½ðãá>¿ãÿxâïˆ?t?ÚÀ“ëºBøwÆ>$³ñìÑ­}eÖ4 !åš=:çöÿ‚ž~Ï¿µÁ‡:·‹¾(x/Ã?í?c„ßµíá3á¿|>ðOƒ¼5âi—ÿ.ñ x‡N¾CêK0Ã:±£5RYAέ(SrWWå¼Z“¶¶MÚÍ;J2Šçö59\“Œ¢›‚”­¶özo»Ñ½žªÿhÇûAø ø§þ8óéû1~Ò‡Û ùÅyÏüÿã‡> þÏ:7¾'xK㟆|Si{§Mq¥Ù£ö‹Ö¼¸àð?ƒ4y[íÞøY«iÏ·QÒu |%Û3}ŸÎP`– då4ø)?ìk¬ŒqMñ3Å^Ô¾| ׿i¿‰þø•ð/öøQã7ö|ðÌŒúÇÆ x'âoÂß x»â7€-Æ•}oˆ¾è¾-±ÔoÖ 7M’óQ½±µ¸ÝøÿýŠ¿h}[UÑ>|rÒõ}GGøáïÚrò?øKâËigÿ@’éÿt½CâW„|#§kž °¸vÒ_gˆÅO™¾[c0’æå³’.Y%½›åºm[~åŠÅý¨SVµÿuSK÷n­–½ô}Ï«‡íoð€ôÓ><Ÿ§ì›ûUýãïøk_„g¦•ñïÿ3ö«ÿç/_…ß¶Çüßâìýÿ'²ý#ý¢ÿaÙàëþÀÿµœ_?lø»Å-âoŠGã׋>Çð{@¸Ó?jÏÙÃGFÖ¼/£/‹4»]:מ/_øF¼Pöš&»kqh_VþÊßðRk~Ãß²‡í/ûZ|>ñoÁ‹ß´w€µ¯Ãð3áo¿|Dð‡ÂÿþÑ>ÄÞ5Ðþø›KÔt=Ç>.ø{£øãÚXüÖ¼}ñsÂ>×>|.Ó¾+]Koû¯‰~5ð”^1[»)t¿†öÒI±ž (ŽÒÅ=¿æ7 ö•ãÿ0]wôÚå*¸‹+û4í·²Ÿÿ->®ÿ†­øTzh´ ÿ»Hý«¿ùËSáý«>½þauÆ-µÿxkÂzmÿŠ¿f¯ÚG ñŒ5ý7ÂÞ°¿ñ/Š~èþÑÿ¶%ø«ÀözÅð4qøóYð5ï„´mħo÷âg‡|]ªÚxrOÚhzÇ…ôëñqú'ûvÝÞé³?‰u-6îëOÔtÿˆ¿³Íõý”òÚ^ØÞÚþÑ_ gµ»´º·x綺¶k{ˆdIa•XÝ]AC –Ô­1Ž3šS§o­P’´—%ÚX%{4ýÛ§¶ºÜn­u6éY)?áÍ_•^ßÅÒë­™÷•矯ou?„ÿ 5FîëPÔ5‡ž ½¿¿½¸–îöööïÃZd÷WwwS¼“ÜÝ\Ï$“\\M#Ë4®òHììXúxG`QEQEQEòüçþL#ö!ÿ³Býš¿õLø.¾½¯¿àŸ?òa±ýšìÕÿªgÁuõí~\ÿÁTÿä™|ÿ±Óö ÿ×_ÁA+ôÄžð;¯øCÅú‹â¯ ø«EÕ|7â ø“K±×<;âOë–3ézޯ蚤Zn±¢ëmÕΪézµÅŽ¡cs=¥Ü[Í$mù÷ÿSÿ’eðkþÇOÚƒÿ]}ÿ¯ÑêüÏÄôÞ†šºqÅçÍ5£OÙd YôwJÏ¥®tàªâzxý+xöµû:þÏþ'øI¥~Ïþ#øðwÄô=?ÃN‹ðO[øeà­Wá&¥x"ïN¾ðf›¥|7¿Ñ.<§éþ¾Òt›Ï ÙZhÐÛhzfŸq¥Gi-•³Ä\~Í¿³µßÆ;Ú&ëàÁkŸÚÏNM"Óã¥ÇÂÏMñŽ×IŽÅô¸ô»‰ÒhMãX4äÓ$“NK(µµ¶[ÑbîÑŸgQîÎ)Õù¥ UWJ­EÍÏÌ”åï{N_k{=}£ŒTïñ(ÇšöVï²ì¿á¶ûðŸìÛû:ø âoŒ>5øàÁ_|eø†—øÿâß„þxÿ|qÜð]]'Œ<{£èV~*ñ2\ÜÛ[\\.µªÞ¬ÓÛÁ,¤Š6Wü4ýœ?gŸƒ6^5Ó>|ø1ð£Nø—©\k´ÿ†¿ ¼à[/ê÷ÜÛÝê¾6´ð¾‡¥Aâ­JêÞòî‹ív;û©áº¹ŠIY'•[ÙªTõþ_çúW¯EÕ–Ž¥I&£'94ãNÜ‹Wª‹²‚Ú=.ÑWIyiÕŸ2xwö$ýŒ|!­má?Ùöað½¶½ðŸÄÿuË|øU¢Á­| ñ¶¹}âüÕ¢Ó|)lšÂx—SÔ¼Câ‡W‹7„5ýsP¾Õµ]îþîââN×Aý›¿go kš7‰ü1ðà·‡3×|/â;Ý]&Yt¹4»J[Ó¤’Å 6ÎѤwü¿Æ¤¯^‹¨Ún¤ÛÕ_žWJOšJ÷Ù½ZÙÉÝêK²Wi~öþ»4xóö.ý޾*jþ ×þ'þÉß³GÄ}wá¶“¤èµ¿|ø[âý_À:‚Q´=ÁZ—ˆ|+¨Þx[IÑŒQ'NÐæ°³ÓŒhlá„¢ã¤øÍû.~Ìÿ´¶eûD~Îßþ=YxRiî|/iñ£á€>)ZønâéaK™ô7ƒ…¥™ƒWøâˆéâV¿t?oŽoxgÂiiio c™cê| äþç¥}ïË–žkþ,¿Núc¬xÙ¶øoJßÝbL{ÿSÿÖÿ Љ1ø~­þz~õ IÓAýOóý}+B$é þ§ùþ¾•õÕg¿^þo·ËúZtceæÿ/ëô¹þ[>xÊØÀtý*Y¾~Ó±Gíû{øÿ[hÖú?`-3þ Ÿà4§Sò]mxƒYý–/n/ݾѧÜxoBXD)TýÄý¥?j¯|2ý†?`ÿƒžø§ñCà¿¥ÁþþÓŸ¾+ßþ ü;Ôþ#ü5ø)áë-/á§ÁßþÏÚïÃ}_ãwí#®ÊºF±­éüwãƒ>ioŒWá>¬.¼iˆ¿¶øÓ$c ýOùäÿ…hF˜çðç¹þŸþªùå^›§ S»¥ \Î’ø!Èå§µºŒ§“¥xJ£j^ê¿§õ—6›§öœ­ÍÕèµåZ¥h»­mªÔþ*>*~Ñ´ÿÅÍ~ËħíKûHx>koø4CKý¿otß…Ÿ¼sð¿BÕÿjí\ŸÄ¶Ÿ.ôOêú&™iâ‹m¬ÛVÕ4[m*ÿ\Ð4øü«Ý^øûWðΣ¿ûq~Þ?5ÙoöI³Ôþ6üYøGñ{Ç_ðCMöã±øïwñ÷ã?ÁOøÃãÆ•ð¿LÕõ|$ðGìç¬ü0¿øÙûMxÇ_Ôm¼KâãO¼YðKá¯Ã;Nñ„?u(î¼gˆ´x“§ÿ_þ·ÿ_Ö¯¢gvëïÿ×?ç¥aS ;M}fW¨¬Ü¢Þ‰§wzšlífšm´îµµ8«{‰[[^ݼ´ÛUòwGñKû^~Øÿµ£ð—öjñÏÅoÚ{öƒø!àŸÁ·~#ý¨þø÷à׌<]ðÒŒ¿ðR­OÀ¾Õõí/ÄŸmà‹Ä×Úw€uH¼]‰{ooaà 7W×>"è—^½Ñ“Äš>í7ûQþÙñ|8ÿ‚kÛëßµоü'ñÏü£áÆ? þÐß?j_Ú/à‘âïø(«áoM¨xÅþ/ø#ðwãçÄ/Úïâׇü0t/i?²—Šô¡øÚÞ!ñ\‹ª]xžâÓK׿ª_ÚÇöÒ¿k‹OèZïí#ûQü$ðWÄ¿„:ÏÀŸ‹ þøßÀ1|;øŸðËÄO®®»¤ë~ø©ð»â®›á?ëo‰µ¿êß~¯ÃωZ·†î,ôOÅ÷šf‡áë}'믆<ð‡áÏ€>ü=Ñ¢ðï€>ø'¿üáø%¸ž ÁÞ Ð¬<5áîåšêh´ÝL²²Žk™æ¸‘ M,’3;pÔ¡QÊw«%ZM¶Û‹ïmcîÙ-nÉË–ïXÊ<©ò«ô]¼öï{oßDì=Ÿ ÜUø× ôäýÏé\%mújý_õø›EYzÿ_æíѶOôÏÔÿAÓôú×å·üö•ý£ÿdïÙ'KøÁû:xsÆŒv?¾i¼gðãÀÚÄω_ÿfKûÝNãâïÅ¿‡~ñU¦¥áøÃövV•¥Çã 'Uð–ºýLj|Khš6yyiú ‹ÀÏùý?Ƽoã÷ÃߌŸ¼m£üøÿyû8üCÓ|QáÿØøà|2ðGÅíZÓt‹‰$Õ| ãøÍ,æÔüâ«iLÄÞ ñwßYÉmiqáßè›o!¾òqŽs£V0”ã7£(8©©5§/4 ®¼å/iGFºi$¥$šºv•ímÉ7ºÞÏe£Z’?ÿà©?>þͺíY®þÜ^*ÿ‚‚~ÌÞ9ý¡þ |ðÇ‹ôŸ¾|ký•ï~*ʾ¸ÿ†¬Õ~ xnûÂV~'¹Ñî×XѾü?ñv…¥j¶«cá߉öZ•®¹cîÿà´ß²ïÂÿxÏÀ7?h¿KàŸÛ³à‡ü²çÄÞðOuÿ ëÿ´wÇx“Æ–šw…–o‰Ú‰u}áÔ>—Ãÿ$ÿ„nÛÅ^'Õ4]?Â>ñ•µÔ÷öŸþÕðLüýŠàªú‡í|[ñïãßü¯MÐV㟲ìwñÇÀ:Æ„>ñµû;Zx/à'Ã=S㮫ðóKoˆºKø§â'Å¿‰¿×ÃÚç5˜/|[ã]WÖ4ß·¯ìMÿqø«¯~À?ðOýö†ø‹©ü/ý¦þÁJüÿtý¤ç×¼+oãÿ|Køµ¤ø‡ÆZ¤ÿ |Uyoã/¯‡¼Y¯ø7[ðv“âµÇŠß@×ü?¨Ä|-«,ËŸ*¸è¸ÑQ4i:’nN¥¥ígW)Ôœá(UŒ]I8rΚ“ŒUûTi[šîÎImm-Ý”R|­J ¨­]íw§è–‘ÿ„ý˜5€^*øÝ?„>8i÷„ÿl«ïø'Ìÿ³æ¥á?Iñß[ý²mŸâ,Ÿð·ü)áë=3[ÒuÍBü3»ñM¯ˆ ¶–>¸Óc’ëõ«öyøñ£áçnôŸŸ¥ý£>"j¾-ñ7‰¯Dù`þ÷»½ö²ÛEu{­Óëª=æ%î~§éØ~?ãéWÑx¹ëõÿëT1/sõ?^Ãðþyõ«q®yüùý?:ç«=ÿ÷Y_‹,±ãùñÿ>õv5Æ=¹?_óüª×òÏüóõÅ^zõ?_Nÿý|óªK¿M_¯õùšÁY^Ûþ_Ö¿q൲ÿÆ(~Ó¾¿ðÏ?IÿÃoâ^?Ïrjçìuÿ&ãðëþæïýNüOPþÖëÿŸûO{þÏà>x—çÞ¦ýŽ¿äÜ~ÜÝÿ©ß‰ë—ï„Ã?úŠÆé¬èiOø•?ÁOÿJª}5EWœn~XþÌ«ÿåðÔüøUù  ÿúýø¯{};ð>ÿúÿJð¯Ù‘sû8þÏç×à—Â>Ÿð‚h9ÿ>Þõ§Ðÿ_ë_¾W•§?)JÞnÿ§ùŸ—¢<«À?³çÀ_…þ1ñ·ÄO†Ÿ~|<øñ3RÔµˆÞ9ð7ÃoxKÆ?5kT}oYÕ¼oâ}EÓõ¯êz¶µ$š¾§®ß_Ý_ê’Iu,·lÓl‰qøqõ'¯ù÷öªñ¯§n×üÿ:¿ôöýOùçò¯2|°VŒTRmÚ)E9KV촻ݽ÷:"›Þí»o{¥¢Jïþ^ȱãðýOÿ[óéWbNçëøvþµ@‹Ð~'üñô­—¦~§úŸ§Ö¼ú³ß¾«ÎïwòüÍ ºõíëk¼×TN‹ÀÏùýõ«ñ'Oä?ÿ‘â«Ä¹9õþC¯ùÿ¾‹Ó¯O§ùþ•çÔ–íz/^ýàÙV^¿×ãøèL‹’8àtúöuW Ÿó5 KŸOæzÿŸð«‘&NOåÿ×ãùןVVùh½_ù~†ÐWwíùÿ_¡b$éÇÐtüÿúõz1“ŸÀ}Oùýj\äÿœ~§5v4Æ?!îOóý+Ϋ.‹Ñ~¬Õ+ìOãè?ŸùçÛŠ¿ãÜŸ¯oËú{Ô§OoÔÿŸéWãOÓ“õ¯:¤··]§õóÔÞ*É/é³óßöÿ‘ÿöÈÿ³…ø—ÿ«ÛãÝ~“׿ÏìÇÄÛ+þÎâgþ¯uúM\xýqØßû ÄÿééšPþ úõOÏì#ã¿ÚÜgTý–Çý\N©ú~Ì´•|YûtþÉýº¿dŽ?²Œ5Ë¿ é?¼)•oâ‹+$Ô§ðljt wHñ‚¼FÚ\—k«[è^2ð^é?nÓßT³´ŸOMCO{…¼ƒíoÚÐgVý–‡ý\N«ú~Ì_´dDž¿Sýøþ>•úW Bx~4j%*ug‹§8;ÚP©9Fqv³³‹iÙ£ÃÇÉÇåiES’}SŠM?“Ôü‘ø±ðwþ ‘ûDüø×û9|Jÿ†ðw„þ&~Ç?¾ë>.ð׌¾>xëÆ¿þ1|DøGâ/‡þñª¥÷Ã†Þøà%ñ³ˆüm Å¥~Ò:ì:jÜhÞÔfŸÉÕƒüÿ‚X~Ôß þ,ü/ø¢|`øYà-OÀð@oÿÁ.tßx7Pñfµâ¿þÕ~ñFŸâ+OŒ¾Ðõèš>³ðÇA¼³]gGÔõmcIñv¥ªÛCmàki滋÷Ú$õúŸè?Çñ­“×ê ÿÇÖ»«`¨Îq©9Ö©8YBr©ï%š³Š­mlÛ|͹7#UšM%®Ê6Õ¥®·½üü­¥’þW>ÿÁ?l/k_´‰ük­þËzg‰þ3Á~2Á:5Ÿø{â×Lj^*ø§ûS|E×aÔåý¤~2ø·âÁý;\¾Ó¼pñ6¡âíFÖmw^ð}²é ðÏ…üC¡ørËS¸÷kø%Ä߇÷xçãGÄoi_|ÿÞÇÿ§ø¹¨|.µø›ñâVŸñ?òÿmx÷âOÃ߇ú_ÃX5x×Âöº½Ç„íì%‹âw‰¼B¶:5¯Ãd¸¿GÑìIëõ?ПZüQÿ‚ÌøƒöÁð §ì¥ñ?àŸÃÏÚã/ì©àˆÞ;Õ¿nO„²7Ä|)ý¥|_à{¿ Yéÿ õ_x¿á–³á¯Š'Ã> ñÞ ñ/<;àOè×þ${oiúΡ¤øhë>!Ðü\N …¥)Æ& Óµäþ)¥)Ê0IòE¾zœªþÎ2i6’:©Õ©R\­Å_«VÙ/…·»I%}9œz3ñãöøâOÚƒö¿ñ‡À§ø½ð/â—¯ Á¸wücXý¡ÿd½cÄ?~x;VñÅ|á 'Æï Ígñâ÷ÁZF¹ñ7į4í÷¶“Zxjò9§Ò.uÍGö/þ aÿ¾ø©û|IÐ~!üfÓ> ßøáÇìwàOØçŸ|!û@~Û¾%xçÂ~ñF™â ïî´ïÚÇzwÁÙûáÄžѵ_þÏŸ~ëZw‚þÇ?¶ÿ‚¤\~ß¼!û+|Vð6¹ÿÜ›öÖ~üøÓñCá5ü>$Ô¿h}kã>¡ã ˆ<û2ü|ÑüSàØôq¢ø~o Ý^xzÿ[›QÖ–{Í*×M´“Yù‹Ã¿ðG_ÚáGì…ÿ»ý•üñágƇ²«ñ¯Tý«~|XñÅO‡_?i‹Ï‹ßð”xÂrj7ßü)âx»ÂŸ³ÿÄêz·„þøÇGÒü%ñ;G·°±ñ…΀ÐB¶þ×ð·þ YiñûàWüwÆ_?fù0ÁðûÁ^‹öS‹ÅÒ|zÕüiñŸKøkãÛïì¼&Éá&Ò~Þêž-þÖӾѥxvOµEoû%ðGÆ>!|,ðGŒ¾*|×>üF×tXî¼cðwÄ^.ð7u_ë±Ï=­æŽþ4øm­ø‡Á~&²f€_é:Æ©+^i–êšf…«CCÓ³”põ¥)FS—2çm)r¥UÑ­dÜR÷ÔiIÓ›w—/+’)s¦®’KKiwÊ¥¥ï§¼®­­Ýî‘ü øwþ èý¬¼)û~Æ t=cöT°ý«?fƒÿ´ÃÍ'öžøsñçö³ø ñoá'Œ¾"þÑÿ¾4|4ñÃï‹^ðN«¦|_øaám/â[7‰>|Bø àiüZ|A'„~;h:N¿¨}§é/ŒßðB_ÿ>5üañ§Š>"ü"ý§|=ûZü-ý„|#ûIjÿþ5~Úß4ëˆ_²„"ø3Ã_þ*ü>‹à×ÅIb×<'«Oa&¹e¯ÿQ±®è?¯æjôI|~¤ÿ‡øWJ¢¹RvJ &Ó^ä9.ÓVo‘¸¶Ók£Z[Ny7¾ºúêï§Ï¢ùÜþa¿à¥¿ðE¿Ú÷öñ'ü@~ÍŸ?fëOÁOüûMâ×øã®üOÐ7ðŽ—³uâ[É.µkÙMá„ð|š~¾|eáïÜ/Ûõqû,xÄúxóàO¿ü47¾? ÿœ×ØÑ¯OnO¹ÿ?ʾ@ÿ‚€.?eo{xóà}Éý¡þŸÊ³ÃòLJq¿½ˆ¢¾^ÙIýòœŸ}|Š©ü)§ÒÿÒ-ù$}CðSþH×ÂOû&^ÿÔWJ¯M¯2ø)ÿ$ká'ý“/ê+¥W¦×„u…Q@Q@Q@!Á>äÂ?bû4/Ù«ÿTÏ‚ëëÚù þ óÿ&ûÿÙ¡~Í_ú¦|_^ÐåÏüOþI—Á¯û?jýu÷ü¿Hdûµù¿ÿSÿ’eðkþÇOÚƒÿ]}ÿ¯¨ö·ý”@Çü4ïìõïÿ§á¿ÿ4µð~ à1¸ì'ýOŠÅ{,Vwí>­‡­_ÙûJY''?²„ù9ù&¡ÍngZü®ÚájS…ZÜó„/N…¹åÞÓ¯{]«ÚêöCQ_=Ã\þÊ?ôsŸ³×þŸ†ÿüÒÑÿ sû(ÿÑÎ~Ï_øz~ÿóK_ŸÑȳ…kå9šõÀb´ûét]ú³»ë?çýüþKÍ}çÐê2qøŸ¥M_;¯ísû(ÿ';û<äòâôü6ÿæ—üœÓ¿á®¿eú9ïÙçÿOÃoþikÖ£’fê×Êó·üÀâ{+oK¢×Õèc,EÿJËoÞCÿ’>†¥'óÇü5×ì¡ÿG=û<ÿáéømÿÍ-HŸµ×ìžOí?û©…Q\¦¡EP_™¿²úÆ6þÏ~ÿþþþ='ñëÿë¯Ó*ø[âüöLñþ³m¬ÿ®ð·‚~ͦC¦e|?ðOÃ_ è×MÝí×ö…Íü ×¾n§/Û~Í5ßš¾e­¥”>Xò7?Ñd9ͦ8ÈÖ¥V¯Ö^ÅÓä÷}‚Ä'Ì¥(ü^ÙZÏì»îŽfX—IÆQ³ö—R¾¼î ZÉíɯ©ÛD1ßô¿çúÖ„kŽƒ¯{õúþ¹¯ŸÿáÕ_²gý Úþ ~ÿó»£þUû&н¨ÿà§á¯ÿ;ºögÅX9ËŒRùQ~¯ø«s™eõS¿=7ó—ÿ"}tÇùõ?çééZ¦HÇnŸçÛ­|Áÿªý“?è^ÔðSð×ÿÝðê¯Ù3þ…íGÿ? ùÝ×4ø‹;þï¿î'¢éüsE‚ªžôÝ¿½/Ï‘ŸWƸþü~§ü}jôI“þOÿ[ÿ¯_!ê¿dÏúµüü5ÿçwGü:«öLÿ¡{QÿÁOÃ_þwuÏ<ï+û¸¥úuKnßï?Ì¿ªÕïOÿ—ÿ+>ʉ3ϯק§Ôÿ.• ‹Ðw=Oùôéÿ믉áÕ_²gý Úþ ~ÿó»£þUû&н¨ÿà§á¯ÿ;ºæžeƒŸ\Rÿ¸_§üÅ-¿OcWµ?ü_ü¬û¦$éÛ? ÿ?үęÇoOaëþ­|ÿªý“?è^ÔðSð×ÿÝQÔÿà“?²±¦êF£áJãOÕ,nôëè›ðê5ô[]D&ƒáôSÄd‚WA$2G,{·FèáXrσ’mTÅ^ÎÉáé[›Íým´›µÝ·³Ø¸Óª´å§¾¯ž_—³ýOÑh“8íéì=Ïõ¯ÏÚØcö²ý…}õ‰Xú‰_³~Vü7ÿ‚CøᎱã¯i¿|=ñÄ|ñkÙxƒÀ^&ðÂOüIñ¯€üA%νðÛâ¿Á‹Þ(ðHðÆ±ª^h%¼?ㆿn4»þ'xGÆ©áÿ‰~ð¤~Ò/¿b¾ÿÁ:ÿg†^-ð÷޼câ _Ð5='X¶6ÐxÃΓJÔìuˆtíJMÁVwØI{ajo-íïÑeòU¢]"™yib)ÇÚN|ên•jTáÆP~ÚJ.S¨êE×Jѧ>krû»–á-åkšm¶šåœgd’|×å¶²VÝ_cï*(¢¸‚¾?ý¬ÆuÙhzþÑ:¯þ³í'_`WÍŸ´—È-~j? -|¨ë¿ ¾+K㛽+Ç>(×<¤êšMߊ¿§··×tü@¼·Ômï> XjPÃ/‡žÚæÚÂ•¡/êä•©aó\jÓ:Tê·9ÍÚ1NœÒr}ÚWó9ñq”ðõcå'd·~òzúwà}?Ïò«Ñ®?¹=Ͻ|t¿ÿk5?òfšþ+o‰_ýU:üfý¬—ñ†:‰ÇýNÿºúÿɸý2yÖTöÇá]¿éôUßÞx ˆëB§¯+òíó_?#ìÈ×öäýÏò«ñ/LýOô?O­|V¿?k%Çüa~¢yÉÿŠçâW?ù­æ¬/Ç?ÚÍGü™n£ÿ…×įþ†Úãžk—=±ØWÕþþš»ùÈÙaëßZU¤ùlƹ9õàSþ}êükŽþOξ_?µ’ÿÍ•ê'ŒÈùñ(ï¶w©×ãÿíf¸ÿŒ)Ôxÿ©÷âWÿC]qO0Á=±˜Wm¿Ú)jß{ͪ5U¿uQmö$ÿMOº"\uíÉúŸðþ•y€;ž¿_þµ|"Ÿ´7íf¸ÿŒ&ÔNäø”9ÿÄj>ÕâÚÃöšð_†|GãþÆW:g‡¼) êþ$×u+¿ˆßmítíBÓî5MRúæâÙ²8 ·´±µžy¦šHâŠ8Ýäu@XrO…{bð¾í45ÿʆª?çÝ_9ÿò'èœkŸÀ}?úçüóW"^çê~‡ãþ>•øÿû:ÁJ>4þÒÞ¾×¼ û\Ǭøzñt¿x.о4¾ñƒµ7–î+{}^-'öt½„[_µ…ïömüry2Øê6 }_HÖ4í?èÄý¦?k%ÿ› ÔO9ÿ’‡ñ(}?æÙ»W«ÑšMb0Ö’½ž&„]¬šMJ¢i÷M&žŽÎ汌£{Â¥ï¯îæöóQiú®çè/w=~¿ýj¹úvà}Ïó¯Ïuý§¿k0sÿ =¨Ÿû¨Ÿ¿újeý©?k%Çü`æ¢qÿUâW__ù6C\sq–Õð¯¯ûæ]öþ.Ÿ–¾E$ï¬*[¯îêò'è„kÐzr~¿çô«ñ¯@~§ééÛÿ¬I¯Îuýª¿k%ÿ›ÔO9?ñqþ% ÿæ±?úõ:þÖµšÿÍ‹ê'þêOÄ¡ÿ¾Âk’täö«…}_ûf _Ëøëú±²’Óݨ—ýz«§þH}/û\/übwí>{ŸÙãã_ä>x›ÿ×ùQûɸü:ÿ¹»ÿS¿×ÅŸ><þÖ>|Pø_ÿ W¨è?ð²~ø×À?ÛŸðž|JÕ?±á0ðÖ§áïí_ìÏøfÍ;ûGû;ûGíŸ`þаû_“ö¶Zùžz}Íû)hšÏ‡~x Fñ‘©èZ½Ÿü%?kÒµ› ­3RµûGúŸ‚? ?/ø@ôÿ_¿ﱯ§~Ó¿ÿ_é_ØÚ~ֳDŽ>ü%ÿ†uð·ÄÿøC~x7Ãÿð–|?ø•ñ+SѯáÓÃ[®WþÁ¿³5;ŸìOí)´Ÿ´_}ŠÖþÈ}ºçÌßR¯ÆÚÉæÌuÆüW¸ÿÍp5û\ï*”¤ãµv×ïb¯w¾­žçÌC ˆV½›+û­ío'ÿ sìè×Óè?Çÿ¯õ«ñ®9ôýOsþ}}«àŸ~Ðß¶6¦í#öño/ð@º/‡¾"xŽÆü¤Ä‰/ãÅ¿ü3¤­µ°®Ô^ñƒ³Yܶå_|ÿ%ý£ükñ¶ßàf‘ûêw%µ·ñŒÞ+žÇãF»­ÃàËÛè'ÄV´ºìÿ©ÛO¬éZ‹¼¥kº^“¨]xfóÅZx…t×Ô¬bºó«gji}vƒri.Yó®i;''e¯w)Ú1Wrj:›Ç ]Þô¦’WwVzk¢vmµ¥–½µ²?cbNçê þyü«A üOùý+áÄø÷ûY®?ã µƒŸù>%ñµL¿´íd¿óe‰ÿ¹ûâPãÓþM®¹jcðnü¸Ì/eþÑEzý¿ø;ÆN´ê/ûr^½åÕŸvD?χ^ÿ×ڮƹ?ËëÿÖëíÅ|¿´Gíf¹ÿŒ%ÔNêø•ÿÐÓS§íûY/üÙ¢p?è üJüOü›A®9â°ÎöÅa]¶ÿi ·ë­OêÆ¼“ÿŸu?ð\ÿùïe^ƒóþµ~$éùþ‡§ÿ[>•ùþ¿´Ÿíd½bDÿÝBø”?÷ÙÏãVGí7ûYøaýGÿ'įþ†jã©V“øqWѵa–û¿â£TšIrTóýÝMÿðô5ÉÏà?©ÿ>õz5ôíÀúÿŸç_žKûPþÖKÿ6;¨œ ø¸¿¿ú:ÿõêuý©ÿk%Çü`Ö¢qÿUâW__ù63\U•í[ ¯ýFat_ø;¯o_ÇGw šmûªŸü‰ú+ãðýOÿ[óéWâ\cÛ“õíùOzüã_Ú»ö²\Æ j'ÿÉHø”2ñªuý­k%Çü`¶¢yÉÿ‹•ñ+ŸüÖ\u(ÎWµL/o÷ÜÓÿ­ÍT—UQÜ*¿ü‡ôíæOûÉBý²ÿìáþ&ÿê÷ø÷_¤µù×ûøWâ6‰ª~О"øà=cÀ:—Ä?ˆšŸ Òu MtXÛÿÂiñ â·eÒtÍ_]ð÷…î5ßì+ØØ^ߦ§ù“l™¬í’â?lüFñž©à/ Kâ#áÏŽþ(ÜÃyim'…þ¯ƒäñ/Ù®]–mN(|AÑôµ_í9tÍCFøƒðãâ„~%|ø­¡4Åq¦Ø|Nø_âãá­F&Ôü+>‡u{qs¡íEû2Oíð$wçâïÃñôò0þ?¡«ÉûRþÌCþÑß½Oü]ÿ‡ßüÑ~­yuèÕšqt*´ìÚP¨µVq³I4ÓI¦¶vjÍ”SVœ{nº¤ï=ÖÎÖÐü;ÿ‚ÿÁ+5™?àœŸÿb/ØÃàÅÿ?hÚoÚ‹_ý¤¼cñWà§…'ð—ísñCÀ~+ñ¯Ç^"×¼MðãYm{Æþ_øCÓ¾üñ¥–q¦O£x!eѵ GôàGü‡à¿Â{ø%Ž·{â_¯Äø%·Â_Š>øs„ïô ?áÿÄ|}øG¢|2øÑâhú¯„õê-©Þiú‹|0žñƒÛO×5k©õ˜õ«FM:/´cýª?eñŒþÒsÿƒáï^ÃþF.ß—çW#ýª¿eÞÿ´—À1ž¹øÅððp;ÈÄ:ÿ¯&y|£7Qa«7ÉJ {)rÆ4¦ªAFÐM·RÓnNMµRV:}²åQö‘ݶù•Û’Q}vkD–‰;u>ø{ÿ\ýšþþÎß±çÀo…ß?hïx‡öñOÆ~Í_´~‡â…×ü?ÇÝoÆÚ·Å­öó\ø?¬|"ñ‡¼egã­Gú–•«| ¸Oì-7CI³c.±uúuðCáFŸðGág‚>éž2ø¡ñÏÁ:2i+ãŸ?ßþ®jüµ‡ì°üœ·ìÿéÏÆ_‡#óÿŠŒuêk’X:Ðþ²´cj5-eÆ*Ü­iÆ7ݨÆ;$jªÂÚÔ‹mÝÞQÝêú÷oæÛê} älõíþ~•~5Ǿ?RÏ×¥|ííeû*ù¹ŸÙ÷_ŒßIïÿ#'óþ•z?ÚÓöSós³ØÇ¯Æ‡'ןú¸® ¸l[½°Ø›=¿qSE¥þÏSHNîêCM½øï÷ùýö>‹‰1oÔÿžŸ…|{ÿ\~Êž3>ž<ýŸÿh…Yü«Ñcý­ÿeŒþÓ¿³Ï¿~}yñ/ã_-þÛ_´_ìõã¿ÙÇÄžð?ǃ>3ñN©ãÏÙ^ð§Åx‹Ä‘±øùðËR¾66‘®^j7ŸcÓ¬ï/î¾Ïm'Ùì­nn¥ÙÈœôpظâ°Ò–¬E):5RŒcV ¹>T’êÛvJ÷vÔ¹Ô¦éÔ´àÛ„’JQníie}[¾ŸzÕ}|ÿ’5ð“þÉ—€¿õÒ«Ók̾ ÉøIÿdËÀ_úŠéUéµà¡EPEPEPÈ_ðOŸù0؇þÍ öjÿÕ3àºúö¾Bÿ‚|ÿÉ„~Ä?öh_³Wþ©Ÿ××´òí+ÿ%›þ óÿg{ãOý`Ûz½+Æ?³_ÁOø“Qñw‹¼ý­â[ìÚ‡ü$~,°ûGØ,-tËOôM3^²±‹Ê±²¶ƒ÷Ñùž_™&ùžIÍi_ù,ßðOŸû;ßë~ÛÕõí|Ëÿ uû8ÿÑ:ÿË»ÇüÓÑÿ uû8ÿÑ:ÿË»ÇüÓ×ÓTPÌ¿ðÇ_³ý¯ü»¼wÿÍ=ðÇ_³ý¯ü»¼wÿÍ=}5E|Ëÿ uû8ÿÑ:ÿË»ÇüÓÑÿ uû8ÿÑ:ÿË»ÇüÓ×ÓTPÌ¿ðÇ_³ý¯ü»¼wÿÍ=ðÇ_³ý¯ü»¼wÿÍ=}5E|Ëÿ uû8ÿÑ:ÿË»ÇüÓÑÿ uû8ÿÑ:ÿË»ÇüÓ×ÓTPÌ¿ðÇ_³ý¯ü»¼wÿÍ=ðÇ_³ý¯ü»¼wÿÍ=}5ExÿÃÿ€Ÿ ¾k7> ð…?°µ{Í2mæïûwĺŸ™¦Ü]Ù^Ímö}gYÔ-S}ÖŸg/+:ù;UŽIQý‚Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(ãøh_ˆ_5‹Íö¶ðç‡ôOͩɆ?i¿‡VÚ²|k ‰È°³øÇ¡j×ÚÖ»ðX͆Äëúö¹â…z¤â9OÄ-UÔ­<+Ù·V×ÖÖ÷¶W]ÙÝÁ եݬÑÜ[][\F²Áqo/Éð»Äú6« Ž›ñ#àïÇøü?ñÏÄ~ñ^‘i,ëªèþ ø÷㯉ƒÃ6zµ½­ï‡u½*ûWðôöÚg‹5Û}cîúBÊ ‚@,v¨$Í´¶§j³`sµIè -QEQEQEQEVŠ›n÷Wú–§¨]ɵ¥¼RM<óH‘ÆŠI5òßÃíkâçí ãO|Q’O|ýž<1w&«ðÿÁwv’è?þ=\ÏaugiãO‰Ú}ýºê¿þÃçö—ƒ~ˇ¼Oy‡ˆþ"Â;con€>„øàiþ$ø+XðL^4ñŸ€"×M…¾£â?‡Ú®‡âÕÑ£Ôm.5­#J×§°¿ºÐÄZT7š ν¡gx£FµÔfÔ¼/­èzý¶Ÿ«ZxÏÁßÙ›Ã>)ø×ƾÓ¼3¡xM~xá'ÂøcL}:ÃÀ^ðÍοâß´ê@ŽçÄž>ø‰âÍKUñ.ª†kfÇþŸU»ºÔíndRQ@Q@Q@Q@WÊ,ý±~iúÖ§à¿…Ö^.ý£þ%iWa}à?€z4>6:&¦»ƒiþ7øƒq}£ü"øgsˆÄöß¾ ø^ùRXÞ ;’ÊŒõ}yÿÄoŠÿ ¾hCÄ¿üàÿ‡ºËök}OÆ!Òô {ëÖÊÓ´Ïí›y5MRå™"³ÒôäºÔo'’+{Ki§–8Û·³šk‹;[‹›Itû‰í š{ ä·š{)¥‰[I¥´šâÒYmš$µž{wt- ÒFUÛÈtŸÙßàžñ7XøÏoðã÷Ÿu¹åšãâ.¿ ÇŠøÃá½çˆm¡µ³¸ö‰ øëGÐ ¯¯kä/ø'Ïü˜GìCÿf…û5ê™ð]}{@!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^ÐEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPücøðÇã¾…a¢üFðñ¾¸Ðo޳àßhú†¡á¯|=ñÇåÃ⇞:Ðntÿx+ÄP(ÿiè”·v¦];Q[Ý2æêÊ~ŸáÇ…µÿx/D𷉼â?Š:ÆÕ¬Þ:ñ}—†¬ËÅþ9(ÖVÖ~ðdzwƒ4ðªnm­ï-æµ»·†êÖâ7†âÚæ(ç·ž)WŠheVŽXÝIWGVV‚55PEPEPE|»ãïÚ“CÐèÖZïÅ߈žðŽ­tú~ƒ½©Å±âV4Y‰àÿÁçøƒÆû££C øcLÕµ™Ã/‘c!ažáÇÇ|VñU ðçÀ/ˆþøHÖ·sÍñSâð¶ø]©êî¶ó6Ÿ„>êðÞ|W‘nnþËäŸ<9ð ÚÙKs{c¯-¼6WŸAM¦é×7¶Z•Å…•Æ£¦-Òi·óZÁ-îž—Éw«etñ´ö‹yQGt°I¸HãIƒª(hÄ^Ð|] k~ñV¥øÃ>%Òu Ä>Öìmµ=[ÑukIluM'UÓ¯#šÒÿNÔ,§šÖòÒæ) ¸·–H¥FG`[áÏ xsÁú&›áŸ h'…¼7£Û%ž‘áÿiV:&‰¥YÇŸ.×MÒ´È-l,m“'d¶ñD¹;Pf¶è Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( ¿àŸ?òa±ýšìÕÿªgÁuõí|…ÿùÿ“ýˆìпf¯ýS> ¯¯hä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{_!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚ(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š+Œøã'áÃÏ|K×íõÍ á烣=žŸ4V0ÝßÙ[IrÑ$÷vÑ3Ì|íû_ɦ~Ô?önß?õZøš·ÂÓ\N”ïÉR½*r³³åHÆV}›³èEI8Óœ–ñ„¤½T[GŸÚúýº~Ëß´Içòý™zþ?´H?kIº~ËŸ´Içòý˜úþ?´•q'L}õ?çÞ´"NŸþ§¶ýuúÌø;"ŽÔq‹¥x‡C¿û_ìßiöíZ±ƒRÓ/>Ë}ûE[^Û}¦Êæ ¾Ïymouÿ.â¥Ww‡íu©Ÿ²çíÿƒOÙÿ¢J¾_ýš—?³¿ÀOû"ÿ ?|¡/ð¯x‰:{~­þ¥~ÃSƒr(6•,F—¿ûDûé·Sæ#šc%oz¤ßîÖ‡X?k]Xôý–¿hŸüþÌ_ýu ý«õ“ÓöYý¢ðmû0ôJW?töü2Ïô­Ó§·á“þ¥rO…2HÿËšÚoþÑWåöVcŠ{J?øÈúáÄý3ãl¼w¤è^"ðŽιã_ Ýxʼn¡'ˆ4kÀ7ñ€àNnçÃú”2Åäý™—ó\Â<6aŽÃÒMR¡ŒÄѦ›mªt«ÎM½[QŠ»z·©îЛ©FŒåñN•9ÊÚ+Ê ½:jÏx¢Š+ŒÔ(¢Š+æ_ŒŸîü/âKOƒ_|9oñGöˆñ–š¦›àö»žËÂ?ü=t×0[üJøÝâ{8.Ï‚¼—6—0é1ÁsãˆZ¥Æ…à]Q{]sTÐ>š¯*øSð[á×Á{ZxD’Î÷Æ^&ÕÍBÆÒ Bç[ÒÚöÎûèÊøÎ?‹µ/ÅÃ$_~X|ð¼“"CñCö¥{…×.­NÐÚ‡†¿gk âËè˜Áñ3â'Á­^ÙâŸB¼†Ý}…cä66qjPÞêZÛÇ}yojÖ6÷w‰ -ÍÔMsxÖpÜLX­Zîé­ÑÖs9O5­Q@Q@eñƒâŽ•ðkÀZ‡õÄ^&µ³Ö¼áë}Âi¡?ˆ5]cǾ5ð÷€|?gaÿ 6½á?3^ñ6›ö«S^Ómml…ÍËNÍÅ'Úúýº~Ëß´IÏýDÿfOëûH×Aûgsð+¿e¯ýj/ƒ•åÑ&ÑÏOÃÞ¾û…ø,ÍrÚØ¬d*ʬ1µhE´©ÇÙÆ†¤W*ëÍVm¾ÖVÐñó m|5xÂ“Š‹¥»Å7w9§«òŠ;Qû\êmÓö\ý¢Nê)û1ÿ_ÚJ¤µž®ØÇìµûDóÓþ&¿³þ´rÑ&}¿ ÿëÿ…hD™öþƒÿ¯þîO„²HÞÔki¦¸Š»ýý?MÎE˜âí¬ãðGü…ñ7í¥ÿ†¼CâÿþÌ¿´Náß èz·‰5íCíß³]ߨ4]ÂãTÕ/~ÉcûFÝ_]}–ÆÖyþÏemqw?—åÛA4Α·Û5ù‹ûK¯ücgíê‡|Y?@<¯ö÷?¥}Aûɸü:ÿ¹»ÿS¿×Èq6U‚Ëàá8{ybãSš¤ª_ج+…¹›µY§môìz9~&®!ÖUZ|ŠŸ-¢—Åí/{oð£éª(¢¾Pô‚Š( Š( Š( Š( Š( Š( Šü_ýÿlßÛ3Åÿ‚q|Hý¨ìß㯆ÿðS/…Þ Õü ?À…Ÿ> xŸàÆ/þÊ>6ý±-|ãm?â7íñòÏâ_† >"è0|CðíÏÃ-kAñ—‡|;çÃýgHñÕÆ¡àpøÛÿ“öuý¦¿h‡¿¾øËÄß¾þÊŸ²Çí⯈? <'oªê <9ñgâ·íàŒŸþ/êþ!ñÖ¤Éð³áχ?g߇¾ ¶Ð< kß#Óï>%øªËÂþ6𿇵™¼úaE|âOø(ŸÀÿ ü:ðŸÅ _Ã_ßÃ^.ý¢¿lŸÙžÊßEðe—‰¼IkãØwBý²¼CñsY økÄ:¶¹¬xo_°ý‡~-GðÒ×ÂV^#ñÇŠoüAðçN¼ð^‡y®ë‘xcªýŒm? ~Û¸øƒàß…>h'Jð§ˆt;߉1ü(×¼?ã/ øÒÛT¸ÑµüIøñcãÂÝÚ>iâïY|D?þ^.ˆ^ð·ü$^}\ìª+óáŸíÃñÆ_·G‹¾ êÞðUŸìÁ®x‡â×Àÿ€ÿì`ׯ~.ý¥?f¿ |7ñÆÿ ë:…Æ·?‡/4 øäÂ?bû4/Ù«ÿTÏ‚ëëÚù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^×È_´¯ü–oø'Ïýï?õ‚?mêúö€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ ùÛö¾ÿ“Lý¨ìݾ6êµñ5}_;~×ßòiŸµý›·ÆÏýV¾&®¬ûîþ°ÿúzu¿…Wþ½ÏÿIgÅqë´gøWðS‘Æ~?xè`û¶³ùý*ôzßíÛáOÁ.GÚÇ|üFƒþq^Ízsôì?Ãô­“Ô{Ÿ§aøþ¥~óR¢þX¾×rÕõõó>B1inîý?ÈñxõÏÚG·Â‚Žÿ´8øŒ§üâ¯G®þÒ|cá7ÀîGÚÇ£ÿˆõÿ üÿ‚ü{øÕðkþ ÿcðÿà /Žü/ñþ'ÿ övø-ãß øHümýžü }áâ]â‡ÅO„|Uÿ ÛQÕ5èÿð±øà/¼o¯[Z|;ðÖ±áKÝcÆ:gW5¡ EZ£oc9ÂUªáîapø—+ÅÙûµÔ\rN.VQ’g\0ÓpE;©%%u;º²¦–«¼.ÞŠÍ+Þçí¿ì§¥~Òÿ³7Âm;ápøoð3Æßa¸´¸þÝ?ü}á¿7ì¾ðï‡vfÙ«_òüÏì¶nþÐ}¿kû>Öò<ù¾—i–Æ>ü çþ®oÇÿÓöN5ø¤ÿÁv/¼Kû>|5ø¥àïÙ.]câwà«z/üw\ø[­üe×< h_õ½PÕí¾!é~6ñWÀ]?Æ·¾#þûsáÏ|øeã+)µmJ KЯ´±Ô>jÿ‚×ÁO>=YþË_ðQÏÙÿà_‚üið‡âŸìuðçö Ö?hÚ7áí «xWYø1ñGöœø¯ðçź/>Ýé¾ðŽüüiψ?ŸüšgJü%ý¥ÿཞý”uoÚÁÿgmMþ'þÌÿµÏÂÿW>Ñ>&¾¡©xËàÅ/…"øë¥þÔ¶rGðÏíz^—¢ü#ðÄ_ø‡ÀQézÕŽ—yá«-çâLo¯EycïmÿwÓ5MöÍñïƒþü9ÿ…Aû-~ÕZì‹á_¿?i-'á§Ã‰¿mü)i«üPÔµ‹Ø¾øÃÅÑ|âýKEømáM'á…h¯ˆ*þÔ'øðŸú¹ÏˆyÿÖJ©GÄÿÚŒŒÿÂøøþÓ¿?úkð{Iÿ‚óßøŸöyøgñ[ÁŸ²,šÇÅÁYtOø$Ž·ð§\øÑ®øÃúÅ­sEÔ5‹oˆÚWüYðNñ½÷… ÿÂ=l|7㯂Ÿ é>)ý­4_„:ˆ4ŸüOñ¥ÇÂ{ÍÁ·‘kwž´•Å…rÎ,c̰tytÕÔÌmï'$¯õ”®ã7³ŠM»tÑ“ßà§Â¿À  äþ?ç­6??hoˆ³Ïíµñ×ÂðSoˆŸðPÙ§À¿?iû=[öý¸?gŒ/°ý’¼ðY®t„ø}ðWâÂuƒÅ!צ¶Ón­~%kŸþ|Dñ­s®kzô~ðÞ‘¤øÚÿ÷œË0†’S¤¥•IJP¥NVm{J—q‡;\sä§ÎùeRši¿ÃЕDí&¤ ¥Ê’rkEhÇy[â’W’W´[V?¦õÏÚDc >Ã?´?ÿ&ÌúÜÕèõÏÚLc ¾Ã?´'ÿ&ÆúÜ×ä÷Åø,ú|'ñ7í3ðŽóön:§íðWößýœ?c…ÿ¿áp}€|p·ý«láñÁ‰ëâcð¶òãÀñxƒÁZw¼Gsá(|/ãu²—±i‹â»•ÖPÓ(hð[-Zïâ‹xk]ý“¥ÑþGÿ€ñWüñþ'Xüs²Ö5©~)Û3'€þ$Ù|<—áv’Óx{_kk¹|k¡]ø¶Æ_ÚÜho kß.¯õk/ù•s| “<\”œöx¦”ÕIÒ’“JÑq9))5Ëk»FQoxák;6­Í½=U£$õÝ5$Õ¯}–§éçìç ~Òß5?‹ˆøuð3ÅŸð´|s¯xÌB~<øûBþÃþÛñ·Žüaý›æÙ³YþÓû/ü&¿Ùßlòôÿ;û3íŸd‡í¿e´úŒ|jý¦O#àOÀ®êæüÿП_šÿÿà ¿h¿|ð#öL¼ø‹û;üý­|Uû|eøæ¿üá_x;â‡Ãÿ éz¿Ä_Ùüñf¤YxŸá‚5½wCðÖ©¬èŸn>%ê“ß¾£áo„Þ µ±½H}Sö»ý¸o?f¯±ïìãàOƒwßþ6~Ù+ø»§x«ã­/ᾕ¦x7àÓñ7âljnüÿÄœøƒÿЙù×óGÿÌÿ‚ü\øSÿbÿ‚üWø™ªÚ~Ðß>8kß´¯‡[ÆŸ´Çí#ã]#ľ3Öü%ûHü`Ó|9àÝ3PÑ>þÓŸ´Æ/ˆú·‡ììôøOáÿÁoXiÞðü~(ñ/‚,,<1k¯} yÿ|*¸ý•àŸŸ´‡¾x;Ã,ÿ‚ƒ_üv²ðG…hßÚCÂ?³¿Áÿ†°þÍ7Úö‰ñsQøûEk^ ñ^‰¦ZŸé:g‡>«x"Ÿj¾(Ðl¥ƒÃ··-fžW±Èœ!7¥MÎ:î­˜7Tö\ªëi>j°‡»ww¢íÒ¥‹rkÛI¥)FêuqæïOKòÉëÙù»Ãâ·í@FáF|üiψ_ý Uä_5_Û£âW‡,|#àMàg 7UÔÒ/ø§Ãÿ´Ä-Câǃš VÿBøs¯\~Ëvúw€|M«;Gn|ws£øžûÃöj—ÃúUŸˆeÓø»öJ𥶣âÏø¦îÛáÏâñ‡qâØ¼E jþ'Ò–£¢è>ŸÄÚ„|oªj>’‡ìKÿźý®þÏüúþÓßúëòWö@ÿ‚Òx_ö¦øÕû4ø2O€º§Ãÿƒ?·Qý­ì?ñ‘¾$Yx«\ø·ÿ eâ+â¿ü,¯…°x7Cƒ#QÓ´ýWÄ^ ò|oñûbÃOkmWûúx nöÿ‚æëµ÷Š?àœzoŒd)~ x_þ [áÿÛoƒ¾'°ø÷cñ:óAñwì_©ëÇâ‘â/…sá]{úñ`Õ-uŸøJµ¼;sàeдí;ƺ÷4ÿ²Õ¹pT6ß¼Ì5¿%µúÒ²~Ò 7e%4âõ/÷úþözo¥?úwýÙ}Ìý¤ðçÇXüYð—ÂÿŒ¾øWðóþïx×Ä~×|/ñ·Yñ¯ö¦³à¿ü2ðìž—LñWÁÏ…8Ôõ¯øYv·š0Òõ bîãûP·m5wÅ8›â.£û\x—Åúǃ¾xá'ÂïØ‹HWãÄÍCVø®klÒì®nŸÁü'?…-ÐéwwWšpÕüsñ?GRÒÌÑø3[Ñ®a¹›æ?Úcö²ý†ýõˆßø•û1ãüúWéõy™…:0F”hÆ¥yB©(óªõé¶YÔš¼iÆëš×M¤®oAÍÆjrrq“j)Ù³åŒVŽO¡äß~øá¾‹ªZø·âÿÄO"×uc­jž'ø€¾±û%ÃÙÚÚ/ÂÞð?†<)áß xjµY,ô›]>æäM,÷ZŽ©©ßÜ\ÞÍë4Q\ÁEPEPEPÉ_¶ûê1~Ïwòhö–Wú´fGÒìu-B}'N¼ÔSö›ø@ÖVš†«k¦ë7Zm•ÅÈŠ½BÛGÕ§³ä¹‡M¾’5µ—å˜õ¿Ú3þ‰_ÁNÄçã÷އáÿ&Ôqÿ믭?l¿ù!Éÿe¯öXÿÖ£ø9^{õÏaøžµú·IG&¯¢mæx‹^ú²àuÑ£ç³Xß tT!Ûþ~Uïÿ xÊk´ýŸ‚^§?´ŽÇáÇìÐÎjòkŸ´ý‚§?´áÇìÊÎkÚ"Ooþ¹ì?óÖ¿|xøûð£öZÿ‚·ÁBtÚö—Ö>0~Äßð[ÿ‹? ~ø;^ý¢~5x›àÖµû<Ù~Ð?ü ÿ³ÍÿÀ]Ç:§Áˆ|to‹Þ(]ÿIð ŸŒ<6ÐéÞñ—g¥iöö¾Æa˜Ã£'AN.êÉ©M8ÓÃÓUjÉ.gÍ.Dù#îÝ«6¯s’•U´¦×½­²œ¹b¶Ñ_w­»3ú”ø—¤~ÒŸþüCøv>|Ñ¿á<ð7‹|uƒñçǺ‡öWü%¡¡Kû<~Í–_nûÛ¾Õö?·Yý«Ê0ý®ßÌó“Ôþ ø£ö—øOðã߇šÿü#ÿÚÿñ7?´W´¯µÿjëÚ¦·ÿö]Ô¼#ûKìßñû7›äyß»ó<¨ÿ2?àào‰ß> ÿÁ!¿k‰¾"xëá?Äo „ÿ„wâÃOxƒÀž6гûN|Ð5ì_x_PÒõÝ+ûSAÕu=Rû ý¿Û´FûNºólï.!“óSÅÿ~4|+ø«ÿcø]á_~ÚŸ²î‰àOø"GÆß ¿gÚ«ö‘ñ—í ñÆ?‹:<tèkƒì?hOÚ‡Nø{á} À¿Ø>øûˆ®<},>#O é3øp\Ûx9Å\"µ<>3BÃÓö´åí±4”+Ú©/ÝÖ„[Q³U&ž±öP“sK· ÔàåJ³‡<¹d¹iÊþÍÃù Þž×K-¯Ì×»oêÔ|hý¦IÇü(Ÿ_ø“~?ãëÙ8ÔËñ‡öœnŸ¾3Ïí7ñÿ¡7­7 þ;üu¾øãÿ¦é7Ÿþ,Þi?´GìaûEx¯öƒÓ.~"øÂãNøçâ]þ åðgÆš'ˆþ1ÙK¬½·ÄíwHñ¦­¨ø·KÕük·¨XxŸR¾×­n"Õ.î.äñØÇöŒøðÛö¸øUû7ÁT>#ÿÁC?foÛÇ\ý£¼_qà?ŠÚÆOø‹öý¸ôm{ź¹ðWÁŸ|5ñ7øà _è:¾ƒám+JðÃïxþ COÒä‹þñþºþƒÃ• ™I%•Â)Î0r–' œèa«F<ßZ²œ–!(ÆN*nœí+òÅõ©âŸüÄIû®V:7²œ Ý½žÉÁ¶õ²kG«?«…ø·ûO·O¿ÚsâÿBg~Õ(ø©ûPœÅŒø Ïý\çÄ?×þ1+ó¯Íø-Gíñöý€~"iÿµ½WAý£¿hÿü:ý’?gßj·Ú‰cø»ûBø¦ÏÁ67ž×´©"Õô?xwÂ3ø¿ÅžÖtwM[KÕü?i}§Íku wp~ |xý¥¿jïÙßþÿ~ý™|UûQ|zÔÿkïø&ÿíMð»áׇÿi)þ)xçNý ¼Oðã—í5ð?Çþ#jß­üEÿ Õÿ>øËÆ^’Í|Cu ¯ƒì–څ怋g8Š9M Î-¦ù(Ê«j¾6ÜÊ3œi+âþ9Bœåä•ÞK„±Iûyk%‚•íx§/á윒߮›iý‰‰ÿµÿ…ð Ÿ_Úwâ'ÿBML¿?jféð;à§?´÷ÄOéû$ëùœñ¯‰ÿi/·ü³Ç³?‰?à þ ý‚üÿxøßâx·öÃÖÿlÏüHð¿íãáß üDÖ4ŸþÎçí¯ukûOh:ƒðÒÎ-K\ÖtÛ«-Mø‹kc{£OÐh³'ïOüKÅ>(ñ×ü›öñÇüI¯øËƾ3ýŠex»ÅÞ*Ö5ø£Å^(ñÀÏjþ ñ'‰5ý^æóV×5ýwV¼»Õ5cS»ºÔ5=Bêâööæ{™å‘¹ýž\äâ²ê ¤Ýý¾9ÅÚ\«•ýmsEêÔ¬¯ª.õ’»­>šrQM]'·²ûüÿ¿j†éð?öãþ®{â/ÿB=tŸ¾4j?u¯‰¾ ñ¾‰à¿|Høaã<9©øO¿gñôZ–{ðÿáÇl¼Yas«x+áÖ¼ºc'Ä[MèÜxR;K}_L¸ŽJìOOA‰1ø~­þz~õðoÀ1ø(?íp?êáßýTŸ³ râia*xZT§Nœ' ¦)ÊïBœ®ªâ*A§’Þ7»MZÅS•_ij’”\œZq¦—Á9-c½WSôŠ(¯ ì (¢€ (¢€?/¾Á)~ ü'Ðþx;â‡ÅŸŽ?¶/¦ý~ü+ýªàýŸüCð»áG½Ká¬ß ø—û@üX×~kŸ 4‡ÇK³Ó¾,x·ö’ø†~&x‡ÃW:î´yçÑüâÙøÛâÚ|Gý9¢€?2gÿ‚Zü*¸øŸ¥|@ÿ…ÿûZØøc¿¿iOÚ á÷Á]â·‡¼%ðÇá¿Å/Úëá÷ÇÏü|ñ/ƒ5?ü>о1Zêž(Õi/‰¿ü/­jÿuŸ|#ø…¨‰¾ ëü©x£ÁÞ"ö/Ùöð7ìªübñ'‡¾&|Vø©âÏŽ:·ƒµ?xâl´"WÀZ%îá¸tÿ |øEðGáàÕE†£tø¿Á_¼á¿ÿ üáŠð×?€¾韵gÄOiÚûk5¿Š_¬<m­x¼üx·Ô|YáÏŒö/oc§k¾ñlj,<3iáˆ|5{áïdÒÿbŸ…šGìû:~ÇVÚÿÄøeû2Ãÿ®Ϫøq¼w«ÿà üIø3ñKá'ü%Úœ~‹Ã÷ÿð‘xƒàw„ìþ"ÿcxc@þ×Ñõ[øgþÛ½7PÒ>¿¢€ (¢€ (¢€>Bÿ‚|ÿÉ„~Ä?öh_³Wþ©Ÿ××µòüçþL#ö!ÿ³Býš¿õLø.¾½ ¿i_ù,ßðOŸû;ßë~ÛÕõí|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¯h¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¯¿kïù4ÏÚ‡þÍÛãgþ«_WÑ5ó·í}ÿ&™ûPÿÙ»|lÿÕkâjêÀÿ¾àÿì+ÿ§ g[øUëÜÿô–yLiÛ®9>ç°ÿ>žõ¡vëŽO¹ì?ϧ½Cc}=ϯáÐõ«B$Æ8ú{Ÿ_àÿëWíµ'ÛÑzu?¿_#äÖ¿Õ–>,~Ç >3þÔ²íeâsÇV?bßø_ßð«t]SÐ-|¯ŸÚ7áî™ðׯãÇún£áS]Õ²´-.Þï¿ðŽøÂ¿bÕžiõíË7Kþ×ÿà_ðO{Ÿ þÕ_¥ýª|Káoü4ÿ‚‚¿üóã7‹ôÿ¿-¾"þÆÿ´G‹´4ñ÷‡|a®Xë>Ô|-ðÛá4^ ðæ±â/ é¿<â[}C ¯k÷~!Öm,íµ=3öÚ$Æ;ú{Ÿ_óý+ø÷ý¢ô_ŽZ—íGÿdÝ|)ø‹ðŸÁž Ò¿cÙ–ãâöƒñ ࿌>&ø›Ç>?ðMߊҮ‘ðßžøõð“JøU¯.•oâk&ñ‰üñ“OmCWе1áe·ð桤ø§ç39Q£Ë?«Æ¬±ª©ê”¯ I9¦åÌქoÎ1¿~JO—Ú8¨B6ê­í ­kKKÔ”’µ¯¾—?a>Á ¿d-Sá§Ã­GÁ¿~:|Tð}ÿüŸÂ?ðY üOO‰? ¼`Ÿþ:Ýè|ú¯‹t…顸§àGŒ4¹-|@EUñV¶5í=;âbÙÜÂgûbÁÿfÛSÄ_´wˆ¼mñö‹ø\¿µ¯„>ø_öƒÐ~ øëÁžðçÄ›ÿÙ×Ä–º÷ÂëÚw‹>xæK¯xJÂÝü'n’]?…gðüÑÝ\x^OiZ‰ôÆ~Þ¾þÁ?±/ÂÙŸã7Œ|ñká—üsà÷í¿s£é~ýžô‡ºw„~üð߆×Ä?~ ü}ø{ñ§Tø–Þ&ñ5žàïüøà‡>:¾ÖWÔüOñJѼI¡ÝxK±ø©ÿ#ý½üo¯Ùj¿hX> èëÿÃiðVmWÃ~øGðÅ6sþÓÚv¹>¹yu¥_|Gð_‹õ};Úì aá OÃ÷ú†µ¢[ø?ûQ´m/KñµÞŸãm'Æ©‰À¸{9a›¼iÊPŠ‹‹s§(Ú2u÷cRp“m6§$ÓÕQ§W™5?´Òm´ôq{r½ÚMn•º$Ø¿Úoþ _û|Qý¦x÷Àÿ>xŸöŸÕo ñþµ«ø ö°´·‡Áׂ>!|3ºÕ'\ŸøBóJý >7|[ð.—à cà6•©xÄI©øbÖÇLñeö«á;o‡ºæâ¨¼Câ]MXx—GµAÔ9/Ùc⟉~~Ó³¯Æïøz?xÃàçü]ðâ·…<)4WSÅâ|=øáïè^– KÙ£ÖµM"ÓMx¬Ý.¤K’–ì³#Š®# êÉÆ„m=jTi)IÕq”Úåmý˜;»>e¦‰7´a%y»«r®ží’Ý[í?$´ò_¹Þÿ‚%þÌ~ðLJ<6ÿioÉáïø*vÿzoxŸÆ õkÿµ“§ÛØx‹P¶øIco}ð‹U6æÿWÐíìl¼wu¨Oq4_­ÑÖ÷¿‰ðLŸ…¿hˆ´Ÿ…¾5þÔß³÷Œ¾8YüÓÿh½öqø½oð¯Fý m>ܳ|0OøŽÃ·ß|/6…£Ë?„nõƒ~j~ ðmÝÿ†|A{©éš…ü7?„ÿ³ü£þ 7¯þ̵oĈŸ-upÿðDïÁ@þxûǺÏüêÛâ¿Ã¯Ú3IðO‰u y~~ϳÇÄ?øßYý”în®tëßø·ö™øMgâ»Ox^ðŲÖïSÂ÷Ðgü ÄŸ¾"þÁŸ²¯Æ£àªP£ 2×=¦”mkÅ_Þoi5îÝ8ɦí¡vœV²»ë¯tŸn–[êš¾š|"ò08=Ïùÿ<ÕøÓ§·ê}¿Ï§­AtÇÐ{žçüÿJЉ:c·ê}Ïô®z³óùùu?ëaÂ7wè¿3óËÄwÖ?ðSÞ^\Aiiiû:xVêîêæXà¶¶·‚çö¬–{‹‰åeŠ ‰Ie‘Ö8ãVwePHýø{ñ+À¿ü>|[ðãÄ–0ð«j7ú]Ÿ‰tqq>«Ï¦Ê »ºðö°ðG§x“G†ß_Ð.5-öX§ŽËQ¸{yÖ?ÂßÛßöqø‹ûAÁA¾iþñåþ˜4€žÔu†w'áüøŠ,üKûKê‘Ûê×þ|mðtz¢iò%Ä¢©ê6Ó^é÷ÖvÚæ‘qwgsmo«iɧ˨i“O Å£c­c©ér^YHësl𖛍iïNÿ†jøÍÿIý¯ð‹ý‚?úk„ÔúöŠç1ñ'ÄSL³[kßx¾×Â6^%ñÊîÇPÖm|á_x: ÇVXÙ5| ·Óìæ¶»Ò~è_³¦¯§ë³K8•5VOŸ¾0êÞ[F>Í hš–§´$µÅŒ÷¸ÕE|ûðçàÏÄox–-wÄßµ§íñ‡KŽÎîÙüñò®™á©æ¹EXu n¾~Ìß |b/,-C`îì/¬oc ‹îڴ׺}õ¶£y¤\]ÙÜÛ[êÚriòêdÓÂñE¨ØÇ«Xêz\—–R:ÜÛ&¥¦ê{Ï-åݹ’Þ@ËïÙ•ã¾{üøTOÓþMüþ>Õò§Æ¿ø&/ÃÚ"óÇúůÚöµñ‡À?Šß|/ñSâ/ì§â?Š^ñoÀÏëþ×ôOéz…ß¾ø›ão>Ÿx{HÖäøWðÏã/‚þAyjNð½…›½«}aû/DÉû6~Ïhò<ìŸþ«Í(ŒI3/€´dC1r7¸Š(â HHÑ0£è“Ô{Ÿ¯aø~?­~댅*üÑ«(óI(Éî¥xÍK–V”d›Œ¢Ü¡%e$Ñò4å(r¸;?wT“³MY«ìÓ³OF¬Ó?ßÁ7þ(~Ñð]_‡·Ç‰¾ kÿÿg¯Ùóáìv:¥ïŽ|sð«Vºý£¾9|;¹øá/…_<%à†^:ø©èþÑü/ãM Ä:·ñ5üãH-¼5e¤_x#Ã÷Óê·nGÿwý™Î3㎟ò•Ñÿ†?ñSxÚ\tð/ü“/ù!õ,ÿÈÿÿU7ŠýV‰=~§ëéøžµ¡zýO×Óðÿ=kÅx,4=«qö޵j•å*œ·ö•ÚäŒ"•¡+^ë™ÞNMõûZ‘'ËËÁ(ÞÖ‹ÒüÍ·«êû-I~|øSþ ¹ð»À_üUñÇáÇÆ_Úsáž…ñãÍÇíAñöyøuñZÓÁŸ~!|{Ô<;oá½sâ´í ¶|Ciâˆ,´íOÅÿ o~*·Áïëz^™¨ø‹áæ§%œ(=_öýˆ~þÕ~2ýŸ>%ø§Å>|Pý™<]âïü)ø•ð{Äúg…þà >ü]ý§<ªÿÁ>5¿Ú Uøñ{Eñ¯ÃDø¬ºoíC©øžûãW†<]ywð†ïÀú–â‹?]èv·Ú<7âÏi–dþñ6›­Çw¬Þîü?ÿ‚-~Í gïØëàwÂÿŠ¿´‡€¼Gûøã?‰?f¯Ú3Añ_à ¾=ø:OÚ\ñ†±ñsG¿ºÖ¾jÿ¼C xÆÓÆwÞÔ4Ý_á ®ƒ§hÉ ±êÖ·:½çë¼IÓð'ú ?ÇÚ¯D™9ÿ8î~½«Í Ú”U £¥Õ£IE-tiP£i|KÙÓ´´GO<ÿ™·½û½o^i]mï>ìüÿ²ÿ‚o|â§ì ñ›Vø¡ûFøÏÇ¿ðNíö‚Ò¾k¿~,Íñ;[ø—?í-à+O‡ßµÏŽþ/øƒ¢x—Çž6Ö Óm?´|*ÚŠü§xzþEӡӧ𥎑á­;Égø#'ìÁû3Ÿø'1ð'Žþ=jÇþ ÿ wÿ þï|=¿ÿ„¸þÚ?Ûð´GÅïìo…Úö÷ööõáðü!Ÿð€ÿeyvßð‘ÂT_;õ$`téõÿëVŒi€à=ýOÔô®°¤ù#Ítúî½—*ß§±¤ÿíÈõ¹¬\’Õ½ù´ÿÉ¥÷³òÿömÿ‚E~˳Æü_ð«ñkZ²ø*>9ٛᥩjï H×—×wK‰=ÛáÏ„#iÿ ´ÏÏ Ë«C§Kkð“áÏÂß ;¦;G‡ÂpêŽÆúúõÂ:ðÝ埾x·â †•gá/ŽþÜi÷“\Ýê¿ t‚z½þ» °ˆ“OÕcøÙð{ã— ³´Âú&›£_´Ä­Åôöø·qá€ßü5â]]Öm_Úkâ—¦^-Íï‚ü_á_ØÚËÃ^"…QÔéÚÍ×€¿d¯xÆ 7,²3øÅš 4 |ˆdGújŠ+äíCötøÁ{}ymû{~Ö:E½ÝåÍ;•§ø;ö–ÃL†yžX´û5_Ø¿SÕ$³²G[kgÔµ-Býá‰òúîàÉq Ö5OQ¿·Ò´ûíNì\›M:ÎêþèYÙ^êWfÞθ˜ZéÚu½Þ¡rc¼‹+ [›Û©vÁko4òGr|!âxj- Äß¼wñ‡TŽòîåüiñOøe¦x–xn]Z:[_„Ÿ~ø8YØ1Ú<>‡Ptv7××®×›øÇàÏx»@†o ümø¿ðQ¼<5 _Q¾ø9ᯃÞ*×üGi “·öLúOÅσa»1˜Úm>ÓÂú.›­ÞÞº[ ‹Å’;BàŸ´GÄoüWýš4ß|6ñnƒã_ j??eø-µ¯j0j‹ygûT|·Ô4Û¿%Œº~¯¥Ý,–Z¾¨Gmªi7ñMc©YÚÞC,È“§·ëÜ÷é×ÿÕ_˜¿?dß÷þ/ÐjŸühøëàoÃñwöi²×~|KÒÿf=/â7í×íðJе/zìéð'áG†t ? ‰!Ô¼'iâ¯üVñõ¤M«Oð¶ñu]ãõ$éǰϧsøÿžµúÊÙ=uf¯˜×÷´·û® ë{Ý+^é+5fÚix§ûÌ?ëÄ?ôå_—ãÑÞÄÑ'On×¹ïÓ¯ÿª¿'4oø#Oìýkâ_Š2kŸ?i|øÍûhø—öùø‡û2øŸXø'ÿ OÄ´W‰h^ñí!¡x Âß¿hƒ¾ øaáOÚKÄ~øsã߆¼=áÛï‹Zç€?ì£ã|„iû:^j𯹮ü$ðï†ÿgÿ„|¤øsÅÞ¹{«xŽé¼7w¯ê7ËŸÛ0¬eô 5À{ñüÏZЉ:Ÿ©ÿn+˯Jƒ¨ê:Psv¾šJÎn<Ñ¿,¤Z’RqrNri«³¦Ÿ//3åÝw_ ÒoT½Ø¦“·»g²?5goø%Á_Ùûâ7À_ˆò|Wý¡>4Ïû&|3ñoÂÙ/ÂßüWðÿYð¯ìÝàéú^â=#áêøá‡Ãÿk·w~Ñ4A¯|Vñ'ÄXø7LÓôRÞÖÆ×ʹkÿ·økªxóàÿ‰þ(~ÑŸµïÇÏ~Ï߇í)ðKàŸÇŠÞø•ào‡¿ìN¸þñl^<Õ~GûHxÔ|>“ÄZ«x Døñßdž–h­í4Æ´¶··‹ô¶4Éè8__é5£àgðÔÿŸzó§F„W*§Tïk¿z^êJZûÉ(ArÊé(E%hE.ˆ¹-o«Vé³×NÛ»µgw+Þ÷>,ý­?`/€?¶ÿ‹?g-köбÕüuàÏÙ»Çþ$ø—¤üÕ­<®üø£â­wÂWžÓæøËà¿ø3Ä“x¾ÃÁö:ާám7MÖ<;i§¨Ý¾½½`é§Çñ¿Æoø _ìñcMý§¼1á-/Æß³7ÃßÚçá?Á…_þ~ËV?~ü1føñŸCøÙàˆþðz|×4ýâ[ë:øW]ÖfþÐе?êz¼+á«o]Çâ‹Úˆ“Ÿò}?çҮę9?äõÿúõÁ^)99SŒ¥'ï6µmÇ‘«­Räºi4ÚµÛ¾Ôܺ6’Ù_Mïé¾¾¶<¯ã×Á_ þÑßþ6~Ï>7¿×ô¯|yøGñ#àÇ‹õO Ýiö>(Ó|-ñGÁÚׂ,ñ ׯÖÕuëÆ¾Ô­´OÚ—öŸð§‡gDGŽËÁÞøÃ¢øGJ²eEMÒ´;-8åÏÙs$…½áoÁŸ|°ÕtÏÁâ¨-5«Èo¯ÇŠ~#|Føp×6ð›x­ïÄoø®÷M„DH{]6âÒÖWýô°¼À=x§QêtW ñែ>-xf_üAÑ?á ðä÷–—òéßÚZ¾•ºîÅÚKY~×¢_é·Ëå;1òÖèDùĈà^§~Ã?²Ž—cªÚüÑdÔ´ËËkû ûýgÅšµÝ¥Õœésk$3êºý䈰OM[¼‘ -å’͸ëòŸŠß|SðßOÒ|A£|,ñWÅ-ìÑøÎ\i×¾6ðÎ ˆ4ßÉcsã»KYŸÚÚ'†µ9/ê­«øàì³gâßë7Ÿ>,üIøá?‰Þ5K¶Ž7¹ðÞ­éC@ñ‹5¿g‡Xñåö©áýC Þ¡àÿÀÒhÔÏ|Iýª´ï‹7Z'¿g?ˆ¶ßb¼Ð§ñÄoè0À|Sá YuKßé¾]_²kÿøúûTÒ|D¾$´ñ^gâoÛhRY&Ç\Ô ¿Ñ¹oø/ÆúlÑj^øÍñ§K×¢>b^ëÿ¼[ñF¹— |­KÂuoøf[™ŠÝA¥éú-ÚÄXiÚ†™:Ásgû| ý„5Û?|$ð†Ÿàï†~>Ñ,ü?ñÅqXiöFhšÎ±«øOÆÞ0%Ž“áÏx{Z‡Å~(Ðæ:‡áŸx6î/ézf‘¢izš¥¯Òß~ü4ðÌ^.ñŸ‹´=DžßídÆö+«¿’+_ éÖm>¡â+Û•eû-–o{upYLq•Ë'R*xŠî´Útæšr›¦©S´\%M©.E-ÝDÓsæ‹iÇ‘|Ž&­ŽÆKZQ–¢äç«* AÂ.•JU²Œ×½,D%J²©I:j”=ãàWÅÇø£ ë–:õ½†“ñáö¿qàïˆúž×ÎÓZ·‚ ý7[Ò#¼f½ñ~ƒ{¦x›@k—ž[{=Déw7Wúmä‡Ükò«öÕ¼Sã¯ß¾(ßÙ\éz_­Î§}¥Jb?Ùn'ðîƒà*ñíÙífÕtßxU Ôd‚I"þÐkö…ÞQÛõV½,;›£MÔææqÞK–n7|ŽqÓ–£‡+œl­7%ekG•ia(:üΣ†õ#ËRPæ~ÊuceÉZt¹%Z±åªçXÚÈ¢Š+c¬(¢Š(¢Šù þ óÿ&ûÿÙ¡~Í_ú¦|_^×È_ðOŸù0؇þÍ öjÿÕ3àºúö€>Bý¥ä³Á>ìï|iÿ¬ûoWÑ?~ øWáw…oô£(ÊIjã8ÍEµ¢“„’zòËg2mFMZê-«««¤ítšm_uuêƒþ§áoý?h?üDoÚÃÿœ­/ü5GÂïúÿhOüD_ÚÃÿœ­{ŒŸaÉÿ?ç½M_ŽCÅz“µò¯LÉü÷À}ßw¯¡õ ÿÏøÿà—ÿËÿ†¨ø]ÿBÿí ÿˆ‹ûXó•£þ£áwý ÿ´'þ"/íaÿÎV½šŠë‡‰Ò¿átÛ2ï²××ð"X)§eZ/¿î_ÿ.íú5ÿ Oð¼ôð÷í îÑ?kþrµÛ|:øÕà/ŠZ·‰4 ¿Œ­uÏ iÞÕõíÇ þ'ü.Õ­tŸÜø’Ëú¥¾ñ;ÁÞ¼Õ4íRóÂ%´Š÷J†öÚ+î ‰a”FÚ¨ÀúóþÏ­| ªkzÍü«ÃZ5–¯©Ùéïìõàí½*ÖþêßMÖ³5OÚ¶ëMþÕ±†TµÔ?³îîl~×ßdšk.F,~§‡8²9ö.¾åÏ éá'ˆ…EŒU“”+á©8Ê J鯴µSMI'ªºxU¡:Ju#%)ò´©¸¿‚Rº~ÒVÖ=SÑÛ}OÑÊ(¢¾¼Ì(¢Š(¢Š(¢Š(¢Š(¬ýGVÒ´tµ—VÔôý.;íBÇI²“Q½¶²KÍWT¸KM7Lµ{™"[CQ»’;[(‹ÜÝÜHÛÇ$Œªt(¯¿kïù4ÏÚ‡þÍÛãgþ«_WoñKã„>éÚeï‰í¼e«^k·W6ðï€>xãâOŠuíBÖØÝËia x@×õ‘!®5=J+ ÁÏÔõ;+T’tøÃö€ø£ñÿâì÷ûDO'ìì~ü!oÙÛãôšÎ½ñsâ&‰mñjú4øOãÑ[à >ÙxûC´µ¾ÔþÅ=äÞ3ø—á­nÃJ3Ç7…WS³Ûu`ßpö‡ÿÓÐ3­ü*¿õîúK=V$éùê{gÿ×Z'OÈSþzóPÄ=ÿEÿ?Ò´"Nžÿ¢ÿŸé_±Õž÷Òÿ‚ÿƒý-O—‚ë÷_‡ßº±4IÓòÔÿž¼×›Mð àeö¡ñ‹V¼ø/ðžóTý¢4-/¿´§uðëÁ÷‡ÇO èž¾ðVáߌw’èÏsñ7AÒ<ªj~Ó4É­éöÔo´Kx´«»‹I=J5àzœcØžµ¡tÿÀÚ¿ü ¡êÞ¼Ó<#á [Á0ÅàíKÃ^µÓô[ï Ň®l¥ÒQ,Ç_áOÙ÷à7üMáÏx+à—Â?xËÂôÙÿÂ~-ð¿Ãoè&ð¿ÀmP·Õ´/‚~×´ÓUÑ>h𭥦¥¤|6Óní|¦ê¶÷–z,70Ç"úÄIŸóžçéڮęçéÿÖzà©Ê›j1½Ö¶WmY]»_ÝJËð{A7­Ý—Ÿ_ëúÜð/þÈŸ²—€t‰þð/ìÇû=ø/ì5M/ãG‡<'ð_ῇ4/‹º^»e©iºÖ›ñ?HÑü5g§øþÃYÓµ^ÃV³ñ]¾­o¨Ùjº•­äsA}r’ûŸ„<%áø_Ã^ð?†ôø/ÁºáOøCš>áï øSÂÞÓ­´Þðþ‘og¤èZ…¤ÙÚiz>¥ÚZéÚfkoeem ´1D»H½â¯ÿZ¯F=ÿAíþ}=+Í«(­’JÖÑ[KÝíÝ¿Ÿ©®­Û{“Æ1ôÔÿ?×®jüIÓAýOóý}j“?è¿ç§áïZ&ÑÏOÃÞ¼ê³ßñò]ÏúÜÞ*Ö_çúk÷¾"ÿ‚žøÕ¼x?ÿK¿jÁüëô²¿5¼L1ÿ>ð'ý›¿ƒ¿/·~Õ¢¿Jk›ïˆOþ¡p?ú…‡.ÁÿoÕÿÓ³ (¢¸ÍBŠ( Š( Ì?Ù…sû7þÏŸöD~~$ø@þ_á^ýtïÔÿŸé^û0/ücoìö}~ü'Çãà='üû×Ð'L}õ?çÞ¿qÄOßšþô—¢MÝüÿ§¡òTÖ‰ù/ÉÁûìõDÑ'NøýOùþ•~4è:×ÜÿŸÒ¢p?AýÏÖ¯Ä?úý{Ÿ ÿ=+Ê­=ÿ/É~¯ï:`ºýÏîÿ‚¾ýšDñ¦9ôþýoð÷«ñ'§¯^ÃðïïPÆ™#êÏ'ü+B5ÀÏù'¹ÿ?Ò¼ê³ÞúÛñð?¥¡Ó¥þí¶ý:þ¤¨½â¯ÿZ¯"à'·òب¢Nçê~½‡áޮę9ÿ8î~½«Ï«=õÿ‚ú/—ô´6‚»¿EùÿZÿÓÃOóõ=ù=?úÕ~5ÉéÓ€=ÿúßç¥B‹À©ý?ýB¯D1ôÔÿ?×ּʳß^úþoçÐ×rxÓ§·ÜúÿŸéWâNžß©?áÛðô¨bN˜úêϽhD1ôÔÿŸzójÏ~—_rÿƒýln•’Kú}ÏοÚÄcö²ý…¿ì#ñ'óÿ…•û0çùþUúa_šŸµ¨Çíeû úhüJðø•û0Ÿ®kô®¹±ŽðÁ>øYêf,Ò’³«Ó÷‹ÿMR (¢¸BŠ( Š( Š( —?ln~ @=~7þÊßúÔ¿똉3íýÿ_ü+¨ý±9ø)oÿeÃöUÿÖ¥ø5X1§¯ÔÿAþ}ñ_¥pŒ­“Õ_õ2Ä7ÿ„¸>óÁÌ•ñPÿ¯ü*U¿õÞÝÉ¢OÃ? ÿëÿ…hDŸ†Aÿ×ÿ †4õúŸè?Ͼ*ükÐw<Ÿóþy¯j¬÷üD¿¯ÔæŠÕ.Ÿånÿðu²êMdNÞÿÓÐV„kÀõ8ǰÿ=j“ÿ¯×§aþ}ý*üI““þGÿ_ÿ¯^eYö~ž¯wýi·s¢½¾Wù[¾úzô¹}>ƒùô¨!NŸçŸ×ÿ×­^ƒ°ÿ?­yµf¾_§WóÙ™ºVVþ™,Iž}y?OOÇüô«ñ§OÐ{ŸOJ†4õúŸðÿ>õ¡zýOôãøûW™V{ýïô_ÖúÂ=_Ëüÿ¯Ôš4éïúoóéé_Ÿ?à¡ßµßýƒ¼5ÿª‡ö_Å~ˆÄž¿Sýøþ>Õùã𠯯hä/ÚWþK7üçþÎ÷ÆŸúÁ¶õo~Ø·:oÁ‹=FÏHÔ¿¾#ÿ&‘ûBsÏüŒŸ²ŸNßóstïø]ÿèÒ?hOü)?e/þ‰ºÜý¤~3é³ìïñïö†×4»oEø ð_â—Æ}cF³˜[Ýêú_Âïk¾8Ô4»YÌSˆ./í49­ ˜Á0ŽYUÌRØßœŸ°†ûzþÐ_¿cÛOÇ_·0ÅñÿÀ>øÿñWöb—örøA«|²øeñ‡Âž8ðßÃ_„"Ðl|ûAøÆ^Ð|C¡ÚËñâ'ƺMö¹§ê7_n´ÛÈ,m¿™°³ÂÔÃO,›$¡F*•¸…ºµ]9ÕötãK5¬ùš‚NU)ÅΞ­ÇÚ”f¾±]»_Há´WJîôWW²»vvGßÿðº>#ÿѤ~ПøR~Ê_ýt«ñŸâA?òhÿ´!îâ¥ý”ºâN øßöoÿ‚½~Ê¿µƯ|ð•ñoE³øÖ~;Ù‡ã<)áí'àïíWÿ Ëâ/øF~9ÂŒÖô¿kž-¾½K‹»¯øOüðÿûgH²½Õ|7ý³im$ƒÆÿf¯ø.ÏìÓûS¯ìÉÀ_µÍ¶£ûfü?ý¨|aû.é¾1ðgÁ 7ÆO~È­¯?ŃþÕî¾<Íá+â ±Ñ!Ô|=¬ø·]ðïÁû¨õ½?MÖ>*èZþâ­ÞÅ:W;— eñöQS¨åW:Š‚qÄI¹sg6^Î85Íqq’þÐÏ¥6œëñ#CË&µË5®ªÒ1™¾øwö8ÿ‚îéß?c?Ù ãïÅÙwâðøùûmüTý¥<û=þÍ_³êü7ñ߉>+xkö}ñ/üUãŸê^6ø¹à½./ |<ð>‡ñVø¥ª|)ñN©ñAñ5ç„>Ëà}gÀº®½õ¶³ÿ–ýŠü?ð‡ö@ý¤u½oÇWì·ûf[xÆ/þÓZ‡ô}?àÿÃø;ÃZÿˆ¤øñÂú÷Äöþ-ðWŒüCqáøSÃ6šO„¶M{W«•d4¡fš¶qR^ýL]'&¨f•”#N¦ ¼g*®‹QÕÝÛaát£^»oÊ‚éÖŠÝI[æHßð¼þ"ÿÑ¢~Ñ?øR~ÉßýÂóø‹ÿF‰ûDÿáIû'ôPWñMãßø/Ïíç­|ÿ‚cüZøUñ2Þ ?áìÛáßÚƒþ Ëøcð®öóâ'‚4ßÛ“á×ìQâM+Q±Ô¼ ssð×\ÕüOáÿˆž »Ò¾Cà-JÇÃþ.Ô|G¦Gg¡ø{D¹Ò?g¾þÞŸµ7Ç Cþ }â |YGð«ÿÁJþÁ5`ë |5¹¶øe¯øzãÁ ý >+øzú_M}ñ2/éþ)ñ‡Ç[¦ø‘s㯠†Z†—á›GÑZïDºîyæmN<Õr̉.iÁ¤óvã8W… ÂŽ©ªëuì=öÔ­½«WéÒ‡UùóòõÓ¹ûx>9üE<Ùö‰ÿ“öNÿè ¥ÿ…ãñþö‰ÿ—öMÿè ¯ä“ö‰ÿ‚ÙþÖß ?`?ø-׈tŸŠÿíj/Ù“þ ·ñ§àì¥ñCFý‹õ¿|øUû<ø/öŠýž|£|=ñgÆ}7öt×e ?Yø;^ø“£ÛÃñ߯3üYÕ§ñ'†e3ßëž"øs=ÿÖ¿ðY¿ø)—íËû>þ×6ÞýŠ|[kcðoöý¾þÚðP/ ÁàOxÊëâçÃ߈?´§Ã‡–ŸâÖ¼_ám{Zð&½¦ü%³øñ:MSÁÚ¿ƒµ <y¯_ vMkLðÚØtÓÎs J–[“GžSŒdÞmû¡6îó†oN6®NhË•.f:1I¿m_DžÔ:¶¿çÏK6ü»ìDßð¼~#ÿÑ¡þÑ?øRþÉ¿ýŽÏöBý¢ð¥ý“ú(+óŸÃ¶'ÅŸÿÁs´ÙcÃ?-u¯Ù ÅðF­/öÄð÷ƒl|9àù,µo‹:ßíí'âeŸ‡Wâ–·ÿ n­ô«/ƒÚ)SV>Ùþѯ¸x þëRÓïo´8ükãøÃ⛩øvãJÕ.$ýŸµ-óT¥©èúåG<Ä^*YvYïSUtþÒVM6¢ï˜i--Úí+ër=‹×÷ÕtimG­¿é×™ÝÂîø‘ÿF…ûDÿáKû&ÿôPÑÿ »âGýíÿ…/ì›ÿÑC_AÑ]ó™O|»òy‚Ñnÿä`ý˜ýƒÿŸÕ~ê?ü¨ü ø¡ûFþÑ(ý³uvø¯ð3Røà?z&ƒ¯üÕþ-ø+ÇŸ¼à›¿xx|ñ—ƒ¿eH¼yáMOÆÚÚøožðd¾>øçð·Bøgá½?_M.óYñ7Œõ›Íö7ö¸¾Õ¾éÞ*»øä?h+Îþ$Òüwa ø?Ã^M>{k[Ñü¤xJÂ)-|7kuaus^(×¼eâ˜u;ÝN CÄ÷ÑYiúwÈÞ)ÿ” xþÍßÁßú_ûV×é5zsŠ^ͤ—´¡†¬Ò½“¯‡¥YÆ7mò§Q¨Ý·d®Û»".éõ´ç÷Pœ¢žš]¨ÝÚÊû%°W;ã øǾñG¼Yaý«áoxw[ðŸ‰t¿µ^ØÿixÄzeÖ¬Ø}»M¹³ÔlþÙ§^\ÛýªÂîÖößÌó­n`#•z**c)FQ”dã(µ(Ê-©FIÝJ-Y¦šM4m&ši4Õšz¦žé®©Ÿ~1ý€Zñ&¥©øGöˆøåàŸ\ýû?Ãð¶>=x“û3ɰµ·»ÿ‰Ö§ñÆ+ëß¶ßEs¨~þ5û7ÚþÉa‚6<ßü;»Å§íiñÐÜûñ³ÿŸµ~œQ^‡ö¾mÿC<Çÿ q?ü·É}Æ?VÃÐ=üOÿ‘ò_qù‘ÿññXéû[|u÷?ünÿçïNðOOŽŸµÇÇqôøñ¼ïw¯ÓZ*µsG¾eÿÂÌGÿ,Õ°ÿóâþ ‡ÿ"~fø'·‹ÇOÚëãÈú|Bøà?÷¼S‡üçÆC§í{ñì}>!üqÿçñ_¦4RþÔÌÿècŽÿ¼Gÿ,«ÐÿŸð\?ù%÷šðï¿Ñßü}ÿËñÇÿŸÍ8Á?¼n:~Ø?ÇÓâ7Ç1ÿ½æ¿Kh¥ý§™=ó oþ×ÿåƒö?çÍ/üò?5?á€x[^ñ¿Ž?m_‹žð‡…ôËcÄ>#×~%üeÓt#L´MóÝÞÞ\|yH¡‰AbòÊÑüÒFú½_(iÿüSñâRüMý£µ-Äv~ñUåÿÀÿƒ~žþûá—M½š/üLñQÕ´ý.çâÆK»EP²Õu}.ßß ÀÓ< ¤n[êž;ñ¢ó|Ùïšf/׉ÿå¦VÃ-°ôý§ÿÈù/¸ù«Lý€|A«iÚ~­aû\|{k-NÆÓP³k|x°¸k[Ø#¹·iìo¾8[_YÌa• ¶—–Ö÷vîZˆ"™5¾?àž~-?kŽãéñãpÿÞï_¦´Tÿjæ|Ëÿ…˜þX?«áÿçÅüþDüÌðOoŽŸµ×Ç‘ôø…ñ¼ïx¯ øåûüXðÇÁ=µŸí6£ñ+ãÇ‚|{señãƒ.›á›-3Äߟþ;ýïtáû|A?l¯Ú}>&|wûÞëê¯Ù÷Æß*|9ÕÓÂ^*×t­>ò‡ v6ך_ÄÏ…ú•Èe—Ã~,Ó® ¾¿ðÕÄók>ñ Õ¼«Ë|ÚM¦·«ûå/¯ã¿è7ÿ…5ºmöÇìhÿϪø.?åäɯÁ5þ']xãÀ_¼3ûj|T·ñÿÃ+Ã{á3ñ"ÃÅ¿<*ɬhZåî™­øOâŽNæ=7U¿ðÖ†Úœþ ñƒuû˜ôÛ8ζ#¶Š5ûßྷñþúÛÄ7Çÿ|>Ð5ÏϧC£x×áo‹5oÁ_¬¯#»{RÇÃ$Ól<]ðÿPÓ^ xµ? k7ž(³‰ïa:?Œ¼CwRÛ{uÏR¥J²æ«Ru%ksTœ§+]»^M»]·kîßrÔchÅEvI%÷ ¢¼Ã⯀|CãýÆ×Â_üÔ­´O +YXÝÞ,_~xëÄsø×À7ú‘´zcx'ÄŸ´K«ë»eÔõ_ $¢$ŸKQEQEQEÄüDøwá/о¾ð?Ž,oµê7ÞÕ'ƒKñˆ¼+©Ã©øWÄZW‹<;¨éÞ"ðž«¡ø‹I¾Ò|E¡éZ­¥Þ•ªÙÜ%Å”jÒ4-,OðvµÿöÕ/u^÷Fý§~9h:Eæ©w¥hƒâWÇ=OûM¸»–k+ûJëã¢]jÙÖ¯§Û®Qn.üŸ´L«$Œé=×CŽÂÁÓÃcqxznn£§CZ”ÚŒ\Ü)Î1sq„“Wj1MÚ*ÙÎ*š¥*s’VRœ#&’wJí7dÛvÚîçæ8ÿ‚wø tý­>:§¾6ýîÔïøwŠÇOÚÛã°ÿ¹ÿãwÿ?zý6¢µþÖÍèg˜án'ÿ–õl?üø£ÿ‚¡ÿÈŸ™ðïOŽŸµÏÇÿuãÿ?zQÿöñ€éû]üyOˆ_þ÷Šý3¢—ö®gÿCþb?ù`}_ÿ>(ÿà¨ò'æwü;çÆC§í{ñìÝDøãÿÏâÿûñ¨éû_ü}ÿËñËÿŸÍ~—ÑKûO2{æïü+¯ÿËõzóæ—þ ‡ÿ#äÍ!ÿþñ¸éûa|OˆÿÇþ÷špý€|r:~Ø´ú|Høè?÷½Wé]¿´³¾?ÿ…Uÿù`{ óæ—þ ‡ùšÃöñàéûc~Ðcéñ'ã¨ÿÞõN°7ÇOÛ#ö…O‰‡þ÷ºý'¢§ûC÷Æâÿð¦·ÿ&Æüú¥ÿ‚ãþ^Kî?6‡ìñtý²¿hqôø›ñÜï{¯Zýÿdgø ãïü@»ø£â/ˆÚ¿Œ4yôýnóŃ̶¿|ÿð‰ÚÛjwþ)ñ_ŽäÂ?bû4/Ù«ÿTÏ‚ëëÚù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½]íQÿ$»ÃÿöpŸ²/þµ‡ÁZä?i_ù,ßðOŸû;ßë~ÛÕ×þÕòK¼?ÿg û"ÿëX|©’¼d»Å¯À™üÿ ¿&w>?ð/…>(øÆß üw£Ûø‡Àÿ|#âOøË@ºiR×\ð§‹´kßø‹G¹x$Šu·ÔôBòÊf†XåXçcˆà0üÔýœ¿a_Ú÷ölÑÿgO‚^ÿ‚‚Äß²7ìÓu§èÞør?e_Ü||ø‹ð‹ÃVÒØø'à¿ÅŸÚżcàû¯ xgIM3ÃËâ?…?³ÇÂ/ˆº‘il¾+²Ôš]]ÿV(¯åŒ&"½S¡J£S•:´h×4a:qœcZEN¢I%RŸ,ÕÓRN1kݨ“iõWÕ6´ºvvÝ6–ý¼ÙøuûÁ<-û)üiý™üc'ÇÍSâÁŸØH~ÖÃöø7'Ã[ k¿‡íŸâ;oâáø—ñNë²|gm?O¿Õ<;às‚>b麄“êÃÄ7ñCrœ·ì‡ÿ8ÿ†Tÿ‡>ÿÆPÂyÿ¢ÿ‡Íÿ„_þßü7Wü$ßõVüEÿ ·þoü$_õQ¿á7ûüÊ?hýÇï’§ðÿ?çµI^ü3lʯ?>%ËÚÆQ©zT=îzXúU”Õ¥8fx÷9+J¥LLëJR¬¡8ãìᦛm«èâ×^œ‘·d’ÛCùµÓàÝ¿hß³WìWð¾5ü1ø‘¨Á>¾*~Ô~%ýµÚöIðÿÇ„¾"øOûUxƒUñ7Œ~~ѵÿ‹šf…ñsĺ¯©Á©xgâW‡#ý“g{ψ^,øÝð_Á_³ŸÂ€º7í5ñÅZGŒ?áÖÆ‘û?ÿ±øaðWÂ&øcá–ám+Bø³Š|G§^øH¾ñG‰>ëgZ“I´ú›þ yû ÿö¿aoß±gü-ø\ÿð¦?áfÅÊÿ„'þ×ü$Ÿð±~0ü@ø±ÿ"oü%¾:þÇþÇÿ„ëûþF­SûCû+ûWýíßÙÖ~”WjÅbkÓö5*)SsŒÜ#N””jbjÝrB.1SÆbd¢š‚uZå´`£•î•´¶íéh®¯]#|¼Ùüá|ÿƒt¾| ðÿü³Ã7ßåñŽ•ÿ3мwàÿA7Âìˆÿd¯ø«Å_ë^ñ>›¢§¾¥…׊|W§Kiág^•ÚãÄözw…ÿM4ÿÙ“ã%œµÄ$ý¡|?¢þÔ?´'Àχ|!ñ¯Â`ƒÂŸµ_…ß ü[¦øGÅøC㿉?­ü[¡èŸ´Å‹¿4¯ø»Æ÷–Ù~2Óþxƒ[ñÐõx›íd9ôþçúTµèÂ¥Z9¸·¢Ö5¼”’v‚ºM^ß+YØ”’ÛúéúÿÃ<~Ýô‘Où°øgù4ƒßò}ô‘Où ÿæ£ÿÉÿ¨Ýzƒ_µ‡„>)ü4ñgÅÛ?þÃ/ þÈ ø5ñá/ü3¯Ão‡ÿð´k ÄZn¥âïÛ?þÏê—> ðOü&Þ¶Ô<5ÿ ë¡[\|6ðßöŸöΙªK{emõõèÒ»é‚ N»E5wþh™;+õé«þºWwüáñOü¥ÀŸönþÿÒÿÚ¶¿I«óoÅcðTú·o~oý««ô’¾š¾ôìÿ¨Xs–—Âÿëåoý;0¢Š+@¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¯>3~Ð6Ÿµ'á—€<>ß¿hXÏ}àï„úN©m§µ–“ ž?ø›¯È—P|8øW¦]DÐj-Ôìî¯ukÔ>ðF…âïOiáû¢#’9T´R$ŠHËFÊê$†FŠXÉR@x¥GŠE'rHŒŒ)ˆð—ÃOxWñ¿ˆ<-áë]7Ä èæ£‘tht¯†ÖšÈŠåæ—ûs]×nD.–4õ (¢€ (¢€ áü5ñ'Á.ñ?Žü kÐ^x³áž§¥i^8ðìÖ×Úv¯ Ï¯höÚî…w-ާkg=æ‹®i—&mÄZr]è¬Öz­•†¥q}£ê¶ö}Å|Ëñçà׉|Eªè~ ßižý¡~Ø]ÙøvëV–æ×Â|pò^jß¾(µ’Mq/ƒuûÂ/ôÃk{¬|4ñ’YxÃB¶¿´>%ðÏŠ@>𢲴íCSÐômKVÑn¼5ªê:V}©øvúïM¿¾Ð5 »8n/4[ËíîÿH½ºÒ®d’ÆâïJ¾½Ónfæ±»¹µx¦}Z(¢ŠÃñ6“}¯øw]ÐôÏk^Ôu}'PÓl|YáÈ´)üAừÛYmà×4X|O¢øÃ’êšd’-åŠkš³¥5Ì1‹í2öÜÉnÿ'AãÚ§à‘†Û⯂l?i_@þC|Rø¥ü[Ò­D7Þ8øª_Üi¾&Xcò×TÖþxÊÿXÔ¯L“hÿ´»ò-~Í¢€Nå ‚2ÃdgAÁä)h¯’¼I¯~Õÿ ¼Eâ n? øcö—øM{«êµ–‰àÙ4ÿ†Ÿ<¤]ÝMs¥é$Ô—á‡Æ+-Ø‹{+Ë¿ü"ñ3ZE ¼¶ž0Öï.€>µ¢¸¯‡ž<Ѿ&xKLñ–ƒaâ­+OÔÞúìÏx7ÅñF{¥êZV¥c«øWÆ:N‹¯iך•Õ¸yì¥ôq¥þ—u}¦ÜÚ^ÏÚÐEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPÈ_ðOŸù0؇þÍ öjÿÕ3àºúö¾Bÿ‚|ÿÉ„~Ä?öh_³Wþ©Ÿ××´òí+ÿ%›þ óÿg{ãOý`ÛzºÿÚ£þIw‡ÿìá?d_ýk‚µÈ~Ò¿òY¿àŸ?öw¾4ÿÖý·«¯ýª?ä—xþÎöEÿÖ°ø+J[?Gù?‚_á—äÏf r@õ¢¤AÎ}8_óüëùbŒ6ûÿH«þ'¶ÝÛd€`cÒŠ(¯RŒ6û¿Vþý„"§ðçüõ¨êp0ô¯VŒvÓ³é»Ù[2&ì­ÜZ(¢½J0ÛþE¿Þ̃­N•séüÿý_Î¥¯VŒ6ùoÝÛòì›ìQNA“ôçü+Ô£ ¾[öVüÌ»o¹*Œ ~'ëKEêцßw}^ïä¿=N}9ü{ŸjeL£ÜòÏùç5êQ†Úv%²þºMÝÛ±ù½âÏùJ€ÿìݼÿ¥ÿµu~‘׿ç‹?å(>ÿ³vðwþ—þÕÕúG_C‰V%ÿP˜ýBØRø_ý|­ÿ§fQEsšQ@Q@Q@Q@Q@Q@Q@W!ãïø/áoƒµÿˆ ø/ð›D[þ#ül‰í-üwã¥Õ4ëmFO|ˆ=Ûxz!ex–~3øÇ«ÛÅ{áÛ†¹ð÷Ã&ëÅkwãOzÿˆ·¿üa㻟øÓáÅž·w©K è?¬-ô_Þxj+ÙaÐ|Iªxv;«CÂçÄÚrA¬ÛxkÄqéÞ*Ñmná²ñ&¤jñÝiÖþ›@€|àï…ÞÐ|àØø_´k-EÓ–O"Þ9n&¼»¸šyäšîÿQÔµ ›½OWÕµ ‹­OXÕ¯/u]RîïQ¼¹¹—°¢Š(¢Š(¢Š‚êêÞÆÖæöîU‚ÖÎÞk«™ß;!··¥šWÀ'lq£;`€p q ~!è¿>|;ø­á«]VËß| á?ˆ>´×m ²Öí´?h6"Ò`Õì­nïíìõ8ì5u¾µ†öî;{‘,+q0Míæ?µÕÏ‹m¿e¯ÚxHÕuï_|ø‰£x/IÑlu KR»ñf½á}ODðè·²Ò¡¸Ô&X5BÎâäÚÄdŠÖ¦/FÒ§³ø?Ã:w‚¼%áé ³Ið—‡tO éjQ#+§h:e®•d¥# m¶´ˆlB.0 (€:*(¢€ (¢€ (¢€2ügÔc¦|/ø½§Yhמ+kH%¹¼“á—t}G[økñf [kk›ë¨~xÃÄΓ§B×^$Ñ´& NÐEPEPEPEPEPEPEPEPEPEPEPEPEPÈ_ðOŸù0؇þÍ öjÿÕ3àºúö¾Bÿ‚|ÿÉ„~Ä?öh_³Wþ©Ÿ××´òí+ÿ%›þ óÿg{ãOý`Ûz½ßâ·Ã{‹ ŸÁ—þ ñ…U¼Càoiþ#ð™Ðˆ4máߎ|9ñÃWö âÅ>ŸÈñ…´¶ºµÕü?ªY]Ù}¦ÖKß #ðÚWþK7üçþÎ÷ÆŸúÁ¶õ}{N-ÆJI&âÓJQŒâÚw´£$ã%Þ2N-hÓM¡4šiìÓOV´z=UšõZŸœºßÀoÛÇûgWÿ„söºoøG¿´ïÿ°¶ü9ðgûgûírÿeÿkýƒö`ûöŸØ|·ýýí~oÙÿs²³‡Àø(`à~×ZwþŸÿú+ô®ŠäYvT¶É2éäëÿtBÒÿŸµÿð¢¿ÿ,?5?áDÿÁC?èî´ïü'>ô0Qÿ 'þ ÿGu§á9ð‹ÿ¡‚¿JèªX,µm“äkÓ#Êþé¥ÿ?kÿáEþX~j_ðPÁÏü5Þÿ„ßÂ/þ† wü(Ïø(wýÞÿ„ßÂ/þ…úý)¢­a°+l«&^™.T¿÷LVoz•ŸýǯÿËÍoøQŸðPïú;½;ÿ ¿„_ý ôÂŒÿ‚‡ÑÝéßøMü"ÿè_¯Òš*•"Û-ʦO•¯ýÔWÿ?+àúßü°ü×à¡ãû^ißøM|!ÿè_¥ÿ…ÿÿ£¼Ó¿ðšøCÿп_¤ôU¨áÖØ ­ze9bÿÝ@åþýoü[ÿ–›ð£¿à¢ôwšwþ_úéGÁø(ˆéû^éßøM| ÿè_¯Òj*Ó¦¶Áå«Ó+Ë—å…"þj¿ø:·ÿ&~m‘ÿ‚‰ÑÞéßøM| ÿè^£þüKþŽ÷NÿÂkáÿBõ~’ÑTª¥¶½2ܽ~X`ä_ÍWÿVÿäÏͯøR?ðQ/ú;Ý;ÿ ¯„ý ÔïøRðQ?ú;í;ÿ Ÿƒÿý ÕúGEZÄImG½2ü ü°ÂöQïSÿUÿäÏο„¿²ïí¢~ÐÚÇ_Œÿt?‰º–™áÑá;‹Ç]MÔ­ü?§é¿‹a¦éžøUðÿD—ÊÖþ j×——Z–öH%·.–¶Ö§ôRŠ+:µgZnsiɨGÝ„)ÅFœ#NŒ)Æ0ŠŒ#¥¥dTb¢¹c{]½[ní¶Ûm¶Ûm½XQE™AEPEPEPEPEPEPEó߯?ŽãÀ:Æ‹ðÓÀ¹ø¥ñ߯Vêøm§^6ËLÑi¬¥ø…ñGÅÚúÛáßÂÝ2þ-o|Iyg¨júÝô3xÀ~ñ‡Š¼½ À>„¯ñOìïá?ˆ<=ñcâ.«­øæÓÀVúU×Âÿ†zä–ðÛÀ4±žúk¿‰Öž¶¶†?üCž9ì­tOx¶MnoÁa,žÃ÷ºÆµwÓüðoÄøj÷þ—Ä›¯‰¾<ñ&µsâ_êéVÞðŽƒsyicg„¾øf¹¹Ð| ¡[XC—³«kþ"Ô®ŸP×|C®j:¾«w"úÅQEQEQEQEW˵ýzûö±ý™þèúÞ¯§é^ý ~0øÏNÓµË=?Z±ðµ¯ÃŸ†>Ò¼Ekm4Vú­”šÇÆ+­{LÓõ¹·]SÂÑê°À·šMµÍ¿ÔôQEQEQEQEá¿>2Ïð7Oðçuß Üêß ÓVm?â·ôÝD}»á&x ‡Iñþ¯áƱwÖ>éÚ‹´_u«-N ßè²Çâë­/RðÖâKýÛ-®mï-à»´ž«K¨b¹µº¶•'·¹·Xg‚h™£š£e’)cfI•ÑŠJÏPMku W6×1Iżñ¤ÐOÈÑË ÑH­±K2I«#£2²•$WŒ|ø/ið+Hñƒ<1âMJ÷á›ëój¿ ¼ ©[Äð|%Ðï ŠKïøcVµÝÏlµ¶_x?B¾‹ Ò¯WÂ:4ãÂúVƒ¦i@ÙEPEPEPEPrC¦&–(äh$ó¡i\Ã.Nj͈°&9<©dza¶Hé®ÀÐÖ¤Öaѵi|;i¦j ‹L¿“B°Öµ­F½ÖRÖVÓ-5mZÇK×/t½2æôA þ£g¢ëVV¯-;—¨MZM§E|‡¢þ×~ðþ©§xGö’ðWˆf?j7ɤé×¾;¸µÕþ x¯T‘ÊZÁà_ºDIà ùufغ‡ø[â]cHøíàïüð”:»Ùøkã.¹¦»ðW_Ó%š8l/õßxrkûo…7R´‚;»_‹¶^Óà´}{]VóGÒZn§¦ë:}ޝ£êZ¶•©ÚÁ}¦êzmÜÚ~¡cuÍkyc{k$¶×v·0ºKÄÉ Ñ:ɲ°$íQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@!Á>äÂ?bû4/Ù«ÿTÏ‚ëëÚù þ óÿ&ûÿÙ¡~Í_ú¦|_^ÐÈ_´¯ü–oø'Ïýï?õ‚?mêúö¾Bý¥ä³Á>ìï|iÿ¬ûoW×´QEQEQEQEQEQEQEQEQEQExÇíâÏx öyøó㟠ßÿex§Á>(ø³ÃZ§Ùl¯¿³|AáÏëšÆöJÚóN¼û£gmqö[ûK«+/ɺ¶ž’&Ò•9V«NŒZR«Râåu)ÉE94›I6¯dݶObe%ÊNöŒ\·²WvÛSÙè¯Íd[ý«¾4óÏý—zâ9ŸóŠ´–ÿ›ý«þ5óÏý—:â9óŠûp&mñYf×þ./ÿ˜¿«3ÌY¾è©â?ð_ü·ú³ò¿èýùÔ–?ý¬>6óÏý–úâ8ŸòjÒi_[ý¬~7óÏý–ºâ7Ÿòk)pVgñyn×þ&/ÿ˜¿«ý«Cþ}×ÿÀiòÓô.Šùö8øÙ?ÇÏÙçá·Žu9µ;ϯ„¼¥xëTÔôíKþÚñÈð?…õë:}އ!Ó Ó5GY’âÒ8-4¤‹tŤØÁ1WÔòuiÊZ”dÓ•*“§'¸¹BN-Å´›M§k¤íº[Œd¥É^ÒŠ’¾öjêûêQEfPQEQEx/ÆÏ|Zø–þðW‚ü}Â߇:¨ÕŸâ¿Œ|5qsÆ­2%²KðwÃK™´»Âð’yú—ü$Ÿ¤»¸ñ?†´ë¬|¤ÚëúÝ¿<è^ø•àøƒÇ>ðŸˆmuýcᶯeáïǦÃy=Žâ+ëÕÃ×ȶ5Æ¿g§Ëmu¬è–:…Ö© E}¦rÓOmOO=¨Ž1#J#A+¤q¼¡THñÄÒ4Q³¹’6šVI*,…@.Äñÿ¾x#áOƒôO|9ðÆ‘àÿxrÔÚi‹j–¶vÊò=ÅÍÄ„nšóQÔ.åžÿUÕ/e¹ÔµmJæëRÔîîïî®.$ì袊(¯)ñÏÇÿ õk}â_Æ_…?5ÛÍ:bÓEñÏÄ?øKVºÒn.nì Õ-ôÝXÓï'Ó§¼Óïí!¾Š¶’æÊî•¥¶™Õ«ó×âxÏíiñ Ûövýž?øYµ6+ÚÈ2ºyÆ`°ujÎŒµ9é¨ÊW¦••¥¥õg.3,5V1R|ѤÚZß]= ᯿dÏú:ÙÛÿgÃ_þi¨ÿ†¾ý“?èègoü=Ÿ ù¦¯‰?~§ÿ­þ¡cßúŸþ·øWÙÏp1ÿ˜ìSíîRWïöáÏ.9µiËšiw¼¿ÌõßøkïÙ3þއövÿÃÙð×ÿšjìü ñßàÅ Zã@øiñ—áOÄ=vÏN›X»Ñ| ñÂ>-Õ­t›{›K)õK7@Ö5 È4è/5 I¯¥…m£¹½´åYnaGùþ$Ç¿õ?ýoð¯œüwãx7ö²ý”ÿáÔ³¿á)Ò¾&x;^ÿC°»û‡5¿‰_³Oö¦þŸkuö_´ýšßý2Ïì×ðùè÷Pî}Þfc˜L‰§ŠÄNtiN¤c8Óå“‚½¥h¦“ÛGszJµiÓtà”ä¢ÚæºMÚú¶Ö*(ª7º¦™§K§C¨j6j÷ãKÒb½»·µ—TÔÍ¥ÝøÓ´èç’7½¿6}ýè´¶ÜK+»‘“m3§Âž¹Å†~ÿ…¬ßÚMNOÿ½O†pÄ÷Q×ÿð’Iâ›™-¬¾Ì&S¿ÔþÊ··Fí¢š×NÓáÈð4²zygÂŸŠº_Å›?ßèúeöŸgàŠ>I=ì–ò&±ª|8×%ðÖ½©Øwp¶#]´Ô´ÔŽb· >p³"0Ú=N€ (¢€ (¢€ (¢€ (¢€ +òOá‹?ho|*øeãcö¨ø·m«xÓá÷ƒ±ª ÃZM¦‹¥KWÔ¿g[CU¿û”lÔoçžòúãͺºšIåwl_f‰ÙârëZüÞ×kÞܶúŸ5í­ùymÖúý«‡µý}öå§_âÚß;ù§Wå·Ä¿|dø1áÝâEÿí!ñwź>‡ñ+àݧ‰<1«x?öqk-wÂ^&ø½à x·K'Aø#á-b;«Ï 뚬:|ú‰t[‹mA­g[øV&'ô³Âþ ²ño†|;â­:+¨4ÿhZGˆ,!½H£½†ËYÓíõ+X®ã‚k˜#ºŽ ˜Òá!¸¸‰%±Í*#x™¦QˆÊgFЏz®´g(¼<ªÉ.F“Rö´h»ûÊܪI®§V J“„gV“SQO[Úܲ—n¶fíQ^QÒQEQEQE2X£ž9!š4š‘â–)QdŽXäR’G$n º:’®Œ ²’ ‘\w>øáv‹7†¾x3Ã>ðäú¶©®É xGEÓü?¢c[¹kÝ_P‹JÒà¶±·¹Ô¯îï^#ûEÌ’O idwnÒŠùÃãÏÇÍ+àÄqÛx»Dø—áŸëþÕQ¾>økÀéñÀ? uæI­l$ñO¬ø«Ãöð;Ûê‘x—\ð[|5‰#6¾#ñ^’ÍäIñ…¿n_ˆ_døi࿆>,øž>‡Â߯~.ëcBÖ¾,x#Y»ð«ðƒLð~ÜxKâ· ºøgñgJø‹âÿ ëSë~"{esàmHÛøÃÂ^/µ²ý ø¥ðã\ø‘a¥iÚ?ÆŠ¢±¼šçQ¸øZÿ-¯üE°ˆ£Óµ]CÇ_³í)TmBJJóMÙ7x¸©Ål囊m®<|åO Vp“„£ìí(î¯V ß4ù[è›gÑËñ;ö³lÅÃý†êÞ~%qÿ›OS/ÄÚÌŒÿÂÅý‡ýÛÇįþŠŠž1ß¹àŸ¯ò«¨¼Aþúõú¤ø#ŽÙfMýÇ×e¿õsç–7Ú_X©¯™E~!~Ömÿ5övdÿÆ;üJ?ûõ52ø÷ö´où©?³·'þ1×âWÿEUj"ð=Oòíþ5v5ÇáÀÿ?ç­rK#ÉÖÙvïþ­¿¯Ô×ëxŸùÿSÿf*øßö³cÿ%/övÿÄtø•ÿÑWX—íñáGŒ>ÏñŸÇ?5†¾7ñÆ­àÏ\xOà·Äo xƒGÙð»âGtmJÃP—ãoÄÈæóuÿiZEÕ›xNë̲ÕngVMn'_Ez{ò~Ÿçõ5ñwíÂ?â™ø$ê¹ÿ˜SãQ¯7”åtðXùÓÀá¡*xdéÊ4⥔ðÕe Å­SŒÒi®ÈÖŽ'*´S­Q§Z’’mÙÆU!Ÿ“M¯“?f¨¢Šü¨ú0¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(ä/ø'Ïü˜GìCÿf…û5ê™ð]}{_!Á>äÂ?bû4/Ù«ÿTÏ‚ëëÚù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^×È_´¯ü–oø'Ïýï?õ‚?mêúö€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ ùÛö¾ÿ“Lý¨ìݾ6êµñ5}_;~×ßòiŸµý›·ÆÏýV¾&®¬ûîþ°ÿúzu¿…Wþ½ÏÿIgˆDž¿Sýù÷ô­“רŸè?ÇÿÕ^ìÿàS×]øÖ}qûJ~у'ÓŠƒüýjô³ç]{ão¾?iÚ;“ø|VûåYÃ[Ê^~âò²øÿ­5Üøè'½—–¿ŽßÒõ=Î$õö'úñÿõV„IëìOôãÿê¯ özðë¯|o÷Çí5ûHòŠâ¼gö‘¾ý•ÿd‚ž4ý¢¿h¯Š~|øwÿçü&>0¿ldø³Að7‡ñáÿøïľ)Ô>ßâŸhz_üJôKß²}´^ß}›N¶»¼·ó«Õ£Îu*8Bs©)F1Œcvå'Q(Æ)6Ûi$®ÚÔÚœ¤’Ûi+7{Ýh½ÖÛÖÉ-nÑôü«þM3ÃßöÒõZü;¯Ò:ü&ý—üqûüOñ?Œ>üñ×í'à|6Ðô/ø¯à‹|_û}~Ëž:Ðü/¯$ZN…âØ¾üeÔ¾øž÷Ãkai¤Áâ]#Ãך4OŸ¦Kg}²Ÿü4HÇ´IõÿŒ²ý©9öããëÚ¿;¯Ã‘ÄV¯ˆ§˜S”*Ö«5ÉG.j’“4kr·ÚVz4Öúä1΄'Ek+^ÑJöp½žÑßcôŠüxøññöYýš5ÿ„Þø¿ñ“ö¥Óüoñ×\ñ‡~xÁ??oŒ>!êžÑ“Ä^.ÿ„káÿÁßxóÆWºg„´bÖk~"xKᆟãǸX.¼áûùî8Æ´N{Ÿøk_Ú£ú|eü+Íþ!þÁ¿³‡ÅÍKÁÚÏÄÝ;ãµo‡÷º–£àCĵ/íK{wá]CX‚ÖÛS½Ñ&ŒÁì.ïmì­`žâ³¢‡ X6u25· W<®¬¥C’-]s7%ZmZ7ir;»'Ê›’qÅ6ÒtìŸU;¿-9]õÑk®ÇÔ_¼wñwℾ ñïŒ< Âß…ÚÕ¶Ž>x+Ŷÿîôõ[«CÆ¿톦úO‚Gˆã¹°]á“i·^+ðÝ…‰¾ñ®¯¦ø‡V½ðG…~ƒ¯ÎoþÀÿ³/ÃMcUñÃß üIðWˆµÛ8´ýsÄ^ý¢ÿhý_Öl ŸíQYêÚÕŘu=FÒ+Ÿôˆ­ï.¦…'ýèA'Í^ºŸ³ŸXÿÈÝûDã§üÏíYÉ?÷YÿÏ‹ÊéE{øª‰ÛU4d¾Mâbß•ÒóH§ˆ—Jq·F¿fÿ3ëÊ+óßáߊ?áR~ØÚ¯À2÷â/‰|1ñ àïÃYIã¾$ÿÂ%¯iÚ¿í&»¤¯Ä½{Æš„ð”iþðÞ}k¦êú5œØ–W’[ÞN¬èEyøº ^TTÝD¡Fjn<ªÔiÖIÇši4ª(»I««¦mJ~Ò vå»’µï¬g(½l¯~[삊(®c@¯Ì_~=мûZxËûjÃÆ·ßÚ³·ÀO²ÿÂð×â7Ä?+ì_¿iï8ê?ð€xWÄßÙþ×Ù?µ¾Åý¡²ëìiû çÙÿN«óûâ0Ïíiñ×þÛöxü?âå~Õý+ëx-Û;‹ÿ¨\FÎßÉÖÎßqææºá4ÿŸüÙåü|ð(ÇüH~6tÿŒiý£O=Ï OóÒ¯GñûÀcñ øÝíÿÏûH©ãáIéÿêé^Ït÷à};žÝzÿúëòßöÝý«¿io…¿¶Ÿü‡ö?ýõžÿ†Øÿ†¿ÿ„¿âÆ…^>ø½ÿhý›þøKâ†ÿß…<ñÓà?Úá#þÐÖô=dêž$ŸìÿiÒµKŸÙ—zv³úv/J…?i8ÔjU(ÒJ3•zÔèSK™$¹ªTŠnRQI¶Ý“<tå'ÊœSQ”›iÚЋœž½#mvHû¾?ÚÀCñ øáíÿÉûI©ãá9éÿêé_<|Kø‰aâÚödñÎàŸŽWžø{{â©|_ªÙÃö‚ƒû"=KÇ5‹&û×Ã(uCÎÓ¼#âœiv—­ö“(Ž{»®|söcÿ‚¡ü?ñìÃñWãíoá_ƒ~5ýœ?k‰ß±Æ›?iþ3ñ‡üEñÿῌì¼)c§|ðž‘§x§âW‹¤ø‰o­øsWðÇ‚t/Å>+·ŸR¾Ò#—\H›X¹úá÷ü;ö8ø™âo^ ðWÆ[mOÅ´ž£ñoAø=£]x#âN…u¯x«à=·Û>/ø_ÿ„‡ÁÚT_þ#x ×7çÉ2xGÇk³Zøzä+íññ5òünt'UÆ8ª*<¾Þ•:Êeì•¡:rq”¦ýœS„“¨ùRoGÓNjSUStåtù%(·¤õRJÊ)½Òïd}Ò?kïƒ'¥—ÇCôý”jƒÿ¼f¾ký­~2|?ø¹ðO]°ø}cñ¶‹ÞÕ4‹Ÿ5 ¯ÙWö¦·‚Óâ÷à Rx.Îòíþ ¢Zè¾*¼°›Àþ+wcÞñGˆ,æI ¹‘ã Ïø)?€¼yûoþ˳ìýñ£àN¹á/|\ýµ~||ðß>þÓ)ñ[]ø¯û,ü"ðÇ/¼#û8xïFð$åŸáÆ©â+{߯Äo®…©xzîÓDøcªêþ6µÔôË¡¼ ÿ)ý‡~!|LÔ~x[ãöƒqâÛ oŠ—–š†¯á¯x[áß‹mþ2|6øÃ⯠h¿þ*·Â”f¸ø…Ã_x®_YAw{®¥•Ôðü‹É2;ÉGŒ|³öwxÌ¥7 s´ÁûÍ*±Mn›ÛkúK‹²nõ6”ªh®ÕßïnµOËMÏý~ jvú>ƒñgö˜ð¿íðïÄN«ñs\øqð&Ù»öžÔ#ð޽ñÇâ7‹>%üWø—ñëÃ_ 5ø“â‹|Cã [CðŠ^j€~ÁQ-·‹|_ã,¿Q‡ímð„ôÒþ=§ì›ûUŸýâõò¯ìÃûx~ʶ£¯i³ÏÅeñ®¯áÏh3»Ñõo|Gøs«j^ñUÅý§†~#xOKøŸá_øçᧈnô»ûMâ?‚íõÿê—vÏoe¯Í9Hßæ?ŠðQ]_àŸüëâ7ì»ñ5<áŸÙ?á'üWÆðQoˆ?¿áñ¾³ñ?Ãú¿€¿h-Cáß‹w‡õVÏUð•ðßIÔ5ïøFtO†ú—Ž/üCtÝfò)aÐdç–S”Bœfêc'5R¬7$¦÷kýŽJ1º•ýér­Û¶ºG‰“zRVוөÍm4þ*wÖÛkºÐýHµ§Â3ÓIøø~Ÿ²_íX÷‹ÒÿÃXü%ÿ ?ÇßüD¯Ú·ÿœ½~^üEÿ‚ÌþÆ–³ÏíñwàW5?Œþ7ø)ûOûfx{ᵟÂ_ŽúF§ãï…ÚîŠçáïŒô˜oþ[jZÃk¿Ýi^øãÝÏRÐ~8ñïÆ;ß[x;ÅshÜ—ì×ÿqøM¢þÇŸ²ßÇïÛËãW€¼ñö§øMǽÁß ÿf_Ú[Á1ü?øf4¯ÂO©ø¿ÁÚΣñ·Æ¶Ÿ |âMJîÎOÚ¯Å3xà_ÄO ^øwÆÞ–jº^»­óO“Åò©âåîós}s Ëne‹êVÝÛ×M4½Æ®%î©¥·ð§ví}½¯m~hýqµƒÓDøþ~Ÿ²Gí\÷‹U[öÃø+ éZž»®Û|qÑt=O½ÕõgVý•?jm;JÒt­6Ú[ÍGSÔõσPÙØiöpÍu{{u4VÖ¶ÐË<òÇnÃÒ´}KL×t½7ZÑ5 cFÖl,õ]'WÒ¯-õ 3TÒõ xîì5:þÒI­/¬/m&ŠæÎòÖim®­åŽhdxäV>3ûX¯üb§í2êß>3þ|8ñ'óÇò¥O/ËgZ.\mªÕ„.±T.”åݰù¶»„«×Q“N“j-ÛÙÏ[&ÿçéõíóÇ쥭ë>"øà-gľ§®ê÷Ÿð”ý¯UÖoîµ=Jëìþ4ñ­¿Úo¯ežê"Öm¡óeo*b…6Ç(ú¾pî (¢€?&¿f¥ÏìíðÿÕøZÔøBÿ?{¼kÓÛõ?çŸÊ¾2ýžþx7Vø ðKUºÖ~.Ås©|"ømq™û@üzÑtèî/|¢ÜÍŸ£èßl4&É$•–×LÒìlôë<»[[{h¢…>RoÛþ ©kâ¯xVÿã×í3¦'ïzÇì½ãßjÚü‡Hø'àïÚ@×!ðæ«ð¿Ä¿´¥kið/Fñ¶¯ueoø‰…ݾ£¥jVw·f©§Þ\ÿBâ±8z/÷ÕáGÉCÚJœ9­g.^yÇ›}mªM?OŠ£NsÖ”ÔRo•7m­{'çøw±ûþœŸsþ–*ükЩÿóèká‡×ÿ²×Äï??g|RøÝ­üaý˜?áVŽ~¿k½3þsñ¯Âwž8øeÿ­ã­?Âþ%ÿ„—Âú}Þ©Ÿë~ þÆò~Åâì­FH­ßãýž|zëÿ=Oüdßí$>ƒŠãü渥RŒÓ”*¹.iÁ8F2JTäáR7U4”'BkxÎ.2I¦–­M4œlìšM´ìÒqkÝÙ¦š}SMhÎ?öÌ\~Ïzÿ¯ü'Ÿ ÿÃïðÓüýkôoà§ü‘¯„ŸöL¼ÿ¨®•_“µ^Ÿû~Ì¿üqñÏö­ñßÅß |ðǃnüm®ë_¿koˆ:v™s¬øóÃð]ÌžðŸ¼[â Nàx÷YðºXM¦ø~þ]&ýíµ™šÊÛNŸPµúNø á :ÊÏNÓü]û@iú}…µ½•……—íUûOZYXØÚDZÚZZÛü^Ž kkx#Hmà†4†‘#(ù<÷+†kV‚§‹9aá.hJ’”ÜkIrK–5SQn•D¤ÒRq’‹n,ô°˜‡†Œ¹©¹)µi)4¯ª»‡E%¢ÙZûŸ¢4Wä_í+âÙ³ö>ø%ãoÚ3öøÛûDü;ø5ðïþ±ãÿÃGþÚ>-þÇÿ„»Åº|=ÿ÷>"xŸÅZ‡ö‡Š|O¡éñ+Ðï¾Ëöï·^ý›N¶¼»·ùûÄ?¶ÏüƒÂw_±¥–½û[|z·»ÿ‚‚ ŸÙÚÓã·í÷«MñXxÛTð^‰á“pš?Š/›áÏö–±ñÂÚaÿ…ª<öë½FÛRrøwÄk¤üÍN¥J\µ3*Q’Q“R¥µK’jX„Ôg?r-«J^ê×C¾8ÙI]P“Nëâ{¥v´‡Dîû-^šŸ¾´WÃüð™ûÞ4ý¢OýݧíOÔöããù>Õi~ø<àÂgûDç¹ÿ†¶ýª?ù¬‚¥äŽøÇ¯ýC/þh+ëoþ}/üÿùYöíñbþϾ #'Æ?´O=?ã-ÿj®ŸøyjԳǂ›ñ‡íîíÿj±ÉíÇÆnßáXË&¥ñsùacúâQK&¯ì£ó¨ÿùYöMò þΞ$øK¿hŸsÿ qûVóç«Iû7ø ±ŸþÑ<óÿ'sûVð?ç¥a,»ñU{é…‡ÿ5”«Í»*qÿÁÿ•ŸZQ_þÍ>9ŸCøýûIþΩ7ŒõíÁÞ+Òü]áÇ?!økà_„Ÿ?k /‚šïÄ¿ø?Âv¶Þ ¾Öt?]xSWñ7‰´ÍÀ:ÏÄ «¯ƒþñ~¯£ø»ÄZ‰´MPðÞ¯úŽ#0•:øˆÏØÂŽ”*Õ•NX΢œ*ÔnŸ7,‚¢þ+©ÉT÷©ªmËçéPRŒæs©'¥{&œ½k¶ß2ÛáÑÚNZ}àŸ Õm>xóÿ 5ñ¼w8Ð?fïëû>×ò—ñ[þ …ÿøKû.Ámìo~1}‹âïì«Á:ïþüBÖ,fߊþ;ð}§íWãO CñÁþ6Öþ|øyû?xóSÒ4v¿Ðåm7àø_ êú¦»£[ø“Åwz“âù~ׇþ gñsà߃¿à²—?¿hgø‘ðÏöÕdmáí;ð×à·Ã¿ëz¶›ûUx¾!ñÃx+CÔ|?ð×[½ðo‰5Û/ ü8ñ/ŽuŸ§„Œ4Oü`¹ø‘¤x{PÓuß5çØiI®J°÷*MÊ¥*PöRÇÆqçÍìÜKm¯f½Å)§;-þ§Qj¥ïE+JnüÊ‹‹VZÿ/yûÖM+¿ÚïˆöÖ<­|KøÃûpxßá?ß ÿgÂEãÿ‰Wß²g| cV°Ð4í¯x§à^—¡i_ںhºwÛ¯àûv­¨ØiÖÞmåݼ2r_´Â߇º€¿ioÛ+Çø_ðß\Ó|Ks㿉ZÿìÁðÏÁ>Öõˆï>iº×‹u?‚þѬ¿µeñ¼ÞÓmµGì—Ú¶»akmº´šiOåöìý¬¿joŒŸðNÿø/—ìÑûKx¿þh¿dý[þ ‹ðÿRÖ5_‚þ+ñægû@|aøqã/ø;Æþ<ýžþüøKâíOÃ:‡†ìí¦o |:±_ëë¾—Äž4‡JµñçÒ?·oíÉûGسþ ³ðÏÄ¿~~Ó~ý|eÿæÔ¾üjñwÀ?€>>øwâí3ö›ñGÁ?øÛÀ׿ ¼Càüñ¶ðÏQºuðu߉ôø¯C]sEÕõOßø§FÐüE_<§(׋ /ªÕ—-\=å8i½ Ô¤þ¾¤RWŒùÜ*8óE=á„kÙ¾{KÚBî3–‘Wtå [âN¬_“WŠi3ú`ñWÇßxþÏü'_ðSåðWü(?ø@ÿázÿÂYâÿØcßð¥GÅ?³°ÿ…µý±ð2ÏþÇü,¶ZÂÿ ö7ü&j¶ÿ„{ûGÏ‹¾/…~2økO޾ÿñIþÊÿý uükÿÁDþ"kÿ ¼kÿ[|@ð¶ŸàMW^ðÿü8¿ìþü2øÑà‰ÿµtŸh—_Û_ >1xGÇþÊÞ ø ÿHñ¯ì+àOØÿâÿ‚÷ZíðçÁÞÐo|.<9âêž øñÇÏŽwšî¡ãO‡šÇ…þ x‡Â6šW…¯,í> øÎÉï¤ÓðuðnVŽ uQÑkêïA^NîšN)5ªošþꓽ¯’¢K÷µ_º¥m[Tím¯wÛÖú=Y~j7< ¡üNø5ûü@ø·ðÛÄßÚ_ðüBøc7ìeãßøƒûWÔ,¶3û\ü~çþ¥_ÙK§þ#)ü>¢¿ŽÏø ×íûHüøqÿøañ^-{övýº4_ø+‹âOƒ×¾ðEµ—€çý™¼uñâw…ü[áÿ[è£â5÷‹¼CãGĶþ"]SÅsø,øNçBÒô¿iÚÞ—¨x›ZúÛö9ÿ‚¬þÓßÿà ¿ðN­ EøÉ¯üLý‘o»ŸÛÛDOø“áOÙëÁš¬÷²„|I©Yxÿá_Â_†‹ã/À/‡÷Þ:ð¦­á¯ h´?íñëÇ4ðí¦¯y«Aá«í&ÓUÖø–/(Ro ƒŒçìÔ’À`mReàü¿†»®©7®¾Êqæ^Ò«µÚ~Ú®Êêßö‹'¾ïúf_†_ÈÉý®hêVý”zâ2TÉð»â«c?µ×íÏýJß²‡OüF3Ú¿–¿ø%Çü·öÿø·âø!6¯ñóö‹‹âÿ‡ÿफ़ÿ‚¡è<%ðàׂ¬ô{ߨßRñŽ«ðËÆÕ¾ø#ÂºÕ·Š¯WJ_ø‚¯-ü{ákM%ÁÑø¶=gÆä°Gü+öéý ?k/ø#Àø“ûLh3xWöúñ—üSQý d=7áoÁ­/Qø'¥þÌZgÄÝ'ànê0i‘éÞ9ñM¶³Í溒yÚ|ÖÚIž×ͯçÿþ 5ÿ6ÿ‚†þÕ¿e/|d¹°ÿ…3ûZ_þÜ:g~üAñ?ì+ðæÏàö©ð ÄÚ÷ü+=;öOøáOŠÖß·/Å™¼1máÑà¯Ú*? uK Þx—BñÖˆÑxxüU'íçíV1ûY~Ã'×Qø‘ùÂÊý˜¿ÏÒ³hWdèa£‡Å5lœÓŽ¥XJ3§F2‹MFIÆI¦­}Ó§GÏ6ùé§ûÚ’ZÔŠi§&ÕÕ­m|ÏÓj(¢¼#¬ù þ óÿ&ûÿÙ¡~Í_ú¦|_^×È_ðOŸù0؇þÍ öjÿÕ3àºúö€>Bý¥ä³Á>ìï|iÿ¬ûoW×µòí+ÿ%›þ óÿg{ãOý`Ûz¾½ Š( Š( Š( Š( Š( Š( Š( Š( Š( ¾vý¯¿äÓ?jû7oŸú­|M_D×Îßµ÷üšgíCÿfíñ³ÿU¯‰««þûƒÿ°¬?þžoáUÿ¯sÿÒYäq&1ùr{ÿOÿUhD˜Çä=Éïý?ýU IÓØ{çüõçÖ´"N˜ÃØw?ç¯>µûYïÖÏ]µoú×禇ɥÓEøD˜Æ9ô÷=ÏôÿõWáïü¢¸ÿ‚(~ÚGþÍÏñ?ðÖ_2Ïô¯ÕÚwã?ü3oìÏûDþÑCÃ?ð™ÿƒøñsãHðwöÏü#¿ð•…žñŽO†ÿá!þÊ×°¿·°¿²ÿ¶±5ìϵ}·û+Qò~É7óéñþ^Ð>ÿÁ9¾þÝ¿±î¥¯x×ãÅÏ|5ñìý'Ç#¤ÇðëEðUµ¾¤þ9Ÿâü)JM~ÇWÒXM{T´¤á*‘ýÚ’œµ²²mvá©Tæ§RæµHiÌ£y/zÚê´Z»4›ôGÕµ/ì…ñ£àï€ÿà¥_ðR¿Š¿´Å‡Œ¿jm?þ IûBþÏ¿ 5Ÿ€ ¼Kû2øSà÷¼á|iµÖ¼5m}ñÃã‡n¾#7ÄÛké¾8ˆöwž–Ô[è–vˆ¶gñ_ˆþ!|Døsû&Á 5‹ßµíEðóöDý©>Éñwþ ûUÞ~Òí¾!éõÿØ—ÁÞ3øáû¿³øªûÇ |ã¿‹-ª,^ðÆ·áÿ x‡Ä0ÚÞÛ\j~ ÕŸYþ•øŸà CSÒ~|.—Æ£hÞ ñÿƒ®ü-ªüfžÏDÒî¾;økP¶ðÆè|qðÿOÓ<#§ðßðNŠ_¾5xçþ 7ø›ñ“â7>-|Hñ1ÿ‚íÿÂGñâo‹üAãÏø€èº?ŽŽ_R“m«Þ¼šø_?Bž–T÷RJݽ®‰Y轪VNÞêî­ü·ÁÿhïÛ“âí‰ðÛã·ímÅÞ~Ü6Ÿ·Wì³ão¿µÅŸˆºLžñf¯'Á«¸?f‡ý›_örý´Ï†º…§…t_ËñãDÓ?ho‡þ+’óB›Tñ¥¤ž‡û"wþ_ÔÿAï^1â¯Úà€~#ø3àߎ¾9üðWÅ߈«ü?øYâ߉ž ðçĬ×ZBþ ð>±­ÙxŸÄë-ݵŬGDÒï„—M“,N«-Ÿí'û8Üüc¾ý­¾?ü¸ý t§³TøÅ_MñM}CÂrø÷OŽÿáŠkÍãk7¾ð-¼þ4´[6¹ðœ2øŠúø> ZKo«Ï¯ûßüñ?âgÁoø$/íqñ/àïÄO|'øá¯øP‡Ãß>x»_ð'´íŸÚsྫÅ^Ô4½wKþÔе]SEÔ¾ÃÛ´FÿN¹ólï. “Ã|!û\ü~Ö¿à·þ=мgð×öšø)ðÇÁ?ðE¯ü`·ý•>,|[øº‹<}á¯ÚæÚ΋:>‘ðÃö’ø¡û:è~%×¼?-×Ã?Ç~=ñׄ<ø·â'Â/Œ¿~_êz¯à‹‰ž3ý›>%¦·qâ{[=ø×ᯇ>5ü,-&µýã]SðÞ¡£·£| ÿ‚Öi¾5Õõ[oŽ_³v©ðKG‡þ 5áßø+扪ø{⭇ŻGöž%·ñ‡†5M.?øh¾1Ñõhî_©m­ÁâÏ  gZ‹ÀšåÕׄtþ',¹5ìªIs'eQG“ÙáèJ ò¥J8Z:6œœt|³’–©Wz4´³û/W)I4®õN¤¬õJöz¤ÔßðKø%ïÅ_ØÓâNñã6™ðNÿÄ?c¯~Çø‹áÚöÙøõñ+ÇðŠtÏOu§~О;Ó¾ ~Ïß n$ðæªøö{øCðZÓ|âOÄsxCâ†àûûßn~ÙßðLïŽÿ´wí‹ûXþÐ~ñgÂM+Áÿà‡ÿà™þÓ~Ü_-ð¥¿Ãoh÷úw€ý¶ùjÁ¿ið '޾ªŸø6„ÿÁʯ‰þ!?µcãÒÂì ÎÄø¤ÿÅÃ%˜ÿ®$œÏñþÉûW7ÃÏ|&ø‡û=Ý|G—þ…eÿ[ý£¬~#kßl¼ á¿ ê:…×^øñðCRðïÃÝ[Xñ§ˆ´mz×Ä‘i^ñ¶ðæÓÄÚ9Ð'¿ñ†n㻳î¯ÿÁQõoŒ_·ìëû%~Ïÿ´øã×üóáOüuþ9xÓã-Ïë¯ üø—ñ^/‡¦[|1±øIãÉø'CðM†±¨Ã³Ç þ©k¢E}yM*Gq<вÈ1Îý¬—þ1Oö›=ÿážþ4ìÃqþ}}«ß£O^ÜŸ¯§ùþµá_µ¢Æ(þÓ™íû=|h'ëÿ ßĸçßÖžIbð©h–'§dªÃ•~_ç¹-~î·þõùÿ[š_±×ü›Ã¯û›¿õ;ñ=}5_2þÇ_òn?¿înÿÔïÄõôÕ|ÑÞQE~T~ÌëŸÙÓà ÇüÑO…xúŸè?çñÍ3ß²ìCñöüð/üïöxñ'Ç? |<ý“¼[ÿÓý¬µ/Œ~Ѿ jšïÆÏÏðóâ?Á¯ˆ‡IðçÆû¿Œºw…<á­nïÃÞ ·–Ö?~%ñ-…Ϊ]ÅâÛ‹xhÚ_ôÏû2¯üc—ÀÿTOáV>§ÀºŸë^÷ãðýOÿ[óé_ºfJxº´ÝVÝ:X„é'4ªÂ´=œ£)Bp|–¿4”f¤ã%kßä¨Tt`Ô~'ÈÔ¬Ÿ+ƒæM&ž·ÙèÕ·?Œ_Û³Å?ü ñþ©ñ—ìñ⯉žø³ám'þ³®è>.ø?âøWâ‡ô}7Á~ºñÞ§£x‡Áךˆ´»+Gâ–ñ-ÅäÂ+ýµ¢ÇL’ñÔ~:þÚ¿´÷‹®àåO~Çþ(ü@²øWðÏþ —­~Ìkáïø»^ðïÃoø÷áUË|}øðÃ×÷i¾‹[ðl/øŒxÆÿXÒõýÏÇMs}¥kÞ(ÐÚÒë⇣²ÕüE¬ø¹u6Öµ/uÿ‚–þÑ´çï‰ßðp÷Å~Ô´‚/?bM{þÛâoÙÓÁ¾øÏã­#á¯õoŒº_4o‰ €mµá«¿øÆÃU×_Å^ ¸°ø»VÖ_Ä>-ðþ»¯é>Ô´ê+ö^ý‹|û2x×ãïÅ‹O|OøÅñ·öŸñ|Eñ»ã7Åû¯øÄ|0ð„~øq¡'áGÃß…?t-Áž=‡| ¥Lí{qª]j7$‘}Ÿäƒøê*ó¥—Ö&ªb])IÖ´`¤Õ5)æn›MVvpXú/•JJ/ *“\³†ê´9—,’ä½ÚW²Ã_ìë­«Ù]I»-Ÿá_ü¶¸ÿ‚$~Ú§þÍÄüKo€Ù?çÞ¿™mkῌô¿ÛGöTð‹´™tï Á:¿à¹_°§ì ð:îK55Ïøÿö­ý·ÿiӪ¹ÿ‰“ð·Uýœ|>¶n·—LÐ4i²ïÛµôL@ÇÓüÿŸÖ¯D?Ï'ü+ n ë…_Ú¸~î>NNdãJuj+¾u¼ç -=×N÷|Ú]œQåNÒrNû6¡­ÙI]êÓvI­‹Ÿÿ·ŸÆXÿà¤_ ­>ümø¯ð§WðÇü/à§ì7ñ à§>=|d×ügñ_à?ޝ¦^ñ›þΖ:¿†?e†Ÿ³>¹¦j6^ø®Áð‡ÄŸþ!®˜|s«|_“_ѽŸ ~ÑŸµƒñPüWƒö§ý¥/ïâ/Ï~Àþ Ö¾5xóZø_mû(xÝ›û{áøyªk~»ð¬E§Aá'P±»Ó~.œÓü8³ðî³â+½cûdEè?3üÿúß…^EÀúÿ.Ãüú×L,ù¥'ˆ“曚J+u­•K^É]Ù]ÚM7vlª-" ¶³»ßmýÞ×ô»±üVþÐ_·¿Ætÿ‚“|)´øqñ»âÇÂ_Âÿð]‚°ÇÄ/‚~=øùñ—Ä4ø±ðÇwÓ&½ã7ýœ¬5 ~Éÿ ¿f]sKÔ¬|1ð7]·øAâOŽK>:Õþ0I¯èž)Ÿ_ÀøÕûVþÓoíÅá¿øóöªý§¾üp›þfýŸg{?ÙÏÿ¾$x?á&©ÿÙÔl4 ~]j>Ñf³ðyðÇÅK{›ùu5šêoŒbçÄ×^'ÑüVÞŠãÃ_ÛôIøóúúþÛÞ¿=Â÷MÁ7~)Ö¯¼ðÒ÷ãuïÂÏ›ˆt¿ ø+NðõŽoÁZŒÕÚªåyóêœR][JO[iÌ•ÝâÜ›ZÆIYZÖVóü¶ßM=t×ùùð/í%ûukðV?xCÅŸ´u·Âÿè¿ðVÝKá‡gïˆÿj}Vëâoü‰|%¦…§|<ý‚þ~Í_~ Ûx_Äž kŸ‰úoíÏão‰¾Òô/ˆz-÷…<¯x;J½°ñ«úÿPÕ<}ñWâwü³â¯ÅŒÿ~%k?à®ÿ·gìÏðßÃ>øÝñCÅß ¾üðoˆ>x“Ã>ðOÂí{Å7Þðâh7Z¥ÆŸ ^麾ðûOáßO¥hÚž±e¨þóÆ¿¯'ØŸÔâ¯F¹üxÀuÿ>ÕË(¸»¹¹k)jº>îömk­—¢4‹º¿*Z%÷[U¦Ûú]ŸŸ¿¿å!_µÏýƒ¼9ÿªö`¯ÒÚüÔøÿ) ý®¿ìá¿ýT_³~•×&=ß~ø|ÿË:”‡ÿoÕÿÓ³ øsö‹ý£>{|ý¥Ïþ__²­}Ç_þÑ#?´oÀAÿTSö—ÏÓþ¯ÙV½nÓˆ0˜ÏýAŹ’¾ ²ó£ÿ§é˜z¾…¢ø›FÔü;â=#Kñ®Ø]iZÖ‡­éöš®«éwнµö›ªi—ñOe¨X^[I$VwpKosѼlTø‡‚¿coÙá×…üoà‡ß²¯ìßàOüK°þÊøàÿüøcá |@Ó6ʿپ7ðþ‰á{'Åvn'c×­/íñ<£ËÄ»è”Oà?ÏùéWc^ƒÓ“õôÿ=…~³V0›R”!)ZJ2”c'ÉZI6®“Z4­u¹óɸ«&ÒÑÙ;j¶nÝ|ÏËïÛ+þ Gû=~Ò_±¯íû%üðÁÏØöëöŽ´ø%aâÿ‰¿ þø"ÞîâÃàGÄxëÁ6^"ðׄo>Éã MKðÍ÷„|/m©øžÞéúýäÚJ}&Ó/~óÒÿg€?‚|qð×Iøð{Køqñ7S×µ¯‰ôï†^ ²ðOÄgÅLâ_Æþ¶Ñ"мY©ø¢Œë×úõ…ýÖ°Ñ¡Ôe¸(¸õ¨Ç~çþ~¿Ê®¢ò=ùÿë×,>%V4¡:P¢ÚZ{8J¬Ô~ûõê¹r¤æåï¶”mªœä”\›Š“’¿óZ »ï{BWºJö¶·ù›Nýˆ?bý3Â^<ðû!þËú~*YøÃâ‚ì¾ü)´ð—Ä‹…Ó¥ÏÃ+xr ¦ã O‡W1E?-¼Cg¨Ãá¢I|>š{¢°ðOÚÛ]ý…ÿe‡ÿ þü\ý•¼âÏ…Ÿ·§í¯ð‡öq»øiá?ÿõ߇þ,ý >9ë7Þ"ð×Ä¿~ ñ4Þðæ½¦Zxƒáí¦¹â_K¦øÃƶúÆ› jv¯{gö_¤¼Sü»~*Án>x÷ãÿ†?à™žðW€¾,xçJÐÿ௿±‹¾)]ü‹â5§‰þ|"Ótÿ‹gþ&ßø×áTúŒ¾è^¶Öm$ºø¯¦kÞ›ÀÚ…æ—«YxŸDÕSN¼O7jXz’¥J›¨¢’^Î6q”ýè´¢ýÙsͽ-¬ÙÓI¹ÔŠ”¥Ë{·Ì÷KG¾úE'ÓNÇÓ ñì3ûS~ÔðQŸÙ#Qý–< â_ˆÿ ¿áÿá²µ¿‰þë ý¢á;øwuñ3öyþÚÔ§ŸÄ:ïÅÏøTš‡’ÓMÿ…©á½7þ-^ <öË8…ü™ûb~ÆVÿ´Ã¯‹šÂë?~-|}ø[âÙëâ×ÇïþͺoÆOˆÚÏìóâßø‹G×þhº‡ÄO„Úťܞ­§kþºñf¿ãŸèwšeÑ»øi­]j6Ú†“ø â_ÙÓö‹ýšþ#ÁÇšÏÀÙcãßíeãÁ3¥ýœtŒÞ+ý§¾'ÇûH7…¾êqü\±Ò>/x×ÇŸÅOÚ;Gøiq­jËã_…úwÄßÛëú>™À^Î/ k°øIþø™û$þÓ³~ÄÿðpG„߳¿íI¯ü7ø—wÿ¤ñWì»àk_أƳ¥‡Ä~ßÇž×?hcàGì»àχ¾Ñü9o¡ßh–÷~7Ð|#ᛯø@ÑtMCâåíß‹"Ö5‹Ÿ¶2J…\/<äñ²—³r§îÑ«Ž•(©*jRç {HûÜÕTåêÅOªõ‹T’T’¿½ñFŠnÎM+9·m­TýÝ?³Ï؇öFøsû þË?ÿe…WŽ£àß„›H‡\Ö#´‡Xñ6¹«êú—‰ü[â­Z+¡´†ÿÄÞ*Öõjkku0Ù}¹,¡’H­ÑÛògö„ý¦?àŽÿ³GÄïø(§ÆßÁ<| «|fÿ‚eøóöFø§ñãâç„¿d¿Ùvÿâw‹>'~ÙÞ!Ñ<[ð»â?Â/ë:öƒâ­{Çšеë?xûÅþ2Õ¼â /Ä\k^Ô|S©$W3|%ûDþÈ?¶…¥ÿÁÌÿd/…¼ቾÿ‚xø“öa´Ðô/hþø»u/Ã-GöÐ?|A¨@ºĈ:Ñ´Ïxsâ¥×…ugÄþ$ñƱk¤xšæj–ÑÉñÿíû#|ZñOìõÿØ~Ͱÿícà†ßtŸø"ùý”¾k³ïÆøëAøa¯øZãâ.›ð×Áº–ªøX¶øv¶÷·þ#ðŽŠ·7ÿ ôk¡x£CðiÒeÐt¾LF&q„aO  ©F´`¥R1öñ¦©§¢ž”îìùjSŠRæºÚ5w'7.gÚ÷[rt›æÕîªJÞi½-cû^²ßìÊt¡áÿøg_?Ø_ð¸?á¡?±áQü?þÉÿ…ùöÏíñÃû;þï±ÿÂàþÐÿNÿ…—äÿÂiöÏô¯í¿?÷•¹'ìãû=]|^·ý¡n¾ü¹øûi§¦“iñÂãá‚&ø¿k¥GbúZi–ßäÐÛÆié¦Ë.œ¶QëKl¶2= ˆ[»F”Ÿø(÷ì™ûbüGÿ‚•~Ô^5OÇ[Z~Â3ÿÁ>>7|ý|iûVüBøCsðëRÓî>3i? >.AûO~οdºøŒ5oã\ßu‰t¯‹? 5»ø<9{aªiv–>#ý<ýŽ¿eËØÿà¸?ðXÿÚ_âoÀ]nÖÎâ?ø'´_³Æÿü;Õ­4=Wgì•âüj›à¯Žõ­*-ZºÓdŠÃÁnü©^\èÆåü7®Mgý£=•Âuܧìþ®¢•~NfÕ­Yª(·•$â“Uiµ5pP¾¼÷÷yš³ëɥﮒ³ìâÓGëƒeOÙƒÀ?ð¨O€ÿg€¾ ÿ†~ÿ„ÿþ'ü"?þøoþˆø­öŸøZGáö7‡¬¿á[ÂÉûmçü'ßð†ÿbÿÂcö«ŸøH¿´~Ñ.úÞý?düBÓ¾.ø'öTý›¼ñ_H×üaâ­'âw…~ü0ð÷Ä=/Å´§Ð¼âM;ƺO…í&Ò™ôýjæöÑŒ'è¨×Óè:ôÿð5mG@>••N_åŠÙì½Õ’·m"•û+tV«¾ìðŸþÊ_²÷ÃÿøTðþÍß<gßøOÿáAÿÂ#ð{áç†ÿáG‹iÿ…§ÿ ƒûö_ð­?áeý¶óþü!ŸØ¿ð™ »ŸøH¿´|ùwþvü!ÿ‚J^øcöÎøûbü\øÏð¯Çþ'ýšGí«|>…²7?f¯|NøƒûLøkIð/>+~Õ>6ð޵_|hñü~ÒΓe¨øCá'Á½8êwsëréE™¬dý”Aßðçü÷«ˆ½âÏ?Jóê¨>–Õr¥t½Þ[]'gnXÚ÷µ´ÐÒ»wÓo[ßüßÞxçƒf¿Ù×Àü[ñ£Àþ x'ãÄž?|YðÂÏxoâ_bº¸†îæ?xïFЬ¼SâD¸º¶·ºuVñf¸‚ ¤ $Q²ü¿ûW ~ְǧöÄù|Jý˜«ô=SøóþzWçŸí`1ûY~Âãþ¢?sõÿ…•û0ÖtÚ窒VX\nÝþ§ZåJþïý|¥ÿ§aÿý0¢Š+Ç:¿àŸ?òa±ýšìÕÿªgÁuõí|…ÿùÿ“ýˆìпf¯ýS> ¯¯hä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{_!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚ(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š+çoÚûþM3ö¡ÿ³vøÙÿª×ÄÕôM|íû_ɦ~Ô?önß?õZøšº°?ï¸?û ÃÿéèÖþ_ú÷?ý%žmt÷ýëþqZ'Oqùóý=*“רŸè?ÇÿÕZ'¯ÔÿAþ?µ~ÏV}½êÿ«ôî|´·íÿ¯ëÓ£>Vý»þxÛãGì5ûfüøi¢ÂIñâÇìŸûDü5øáÏí-#FþÞñ¯Ž¾xÃÂþÑ?µõûý+BÒ¿µuÝVÂÇûGZÕ4Ý"ÇÏ:•ýœS\Gütü[ÿ‚ÁBõïÙïãO€ü;ðVÛZŽØ ö"Ò> ü._Š¿lÓLý±µÿ‚px?öÌ‚ßUÔþ#Yè67ú7ƒÿb]FöÿÄZ†©ƒüPšÀ·ð^¿â+¹lì­¼ø“×ê ÿÇگęäÿ‘éøÿ.•óÙŽ w­*ŠÔåMrJ1\­M6Ô¡&ÝçÍôæŒ+q³î¡ZtÕ¢£ñ){É÷‹ÒÍtVz^Î×´™üœÁd?`ïø)?í_­ÿÁ@<ð‡à޽ñáwů þÈ÷ì®>ü]ýŸþx/PñßÃïx)þ=xƒö©Óhú·ŽaýßáÛþбiZ‡‰|s¤Ý|9à á@o“Dzø-|^t›Aáqâ=¶{¿­h×·sÉöÿ?δ"Nœc×éØ~?ç¥yµð4jJ¬§R«uyœ¯(>U(bà£röŒq•9S»\´’Ò-K¢Y%F)E$¬š½&›³ëì£}îÞ·ºþK?à³°_ü³ö±×?à žøAð?^ø…ð³âç†ÿcÍwöTþ/þÏ¿ü¨øóáïˆüÿüAûViž,ñ§€¾!üfø«c¤øSNÑ> Üx®}oᯄ¾éVi—>7ðÿ‡m_䝸/WÀøz×þ ñBÏÂ?~:øGö‚Óà•:6ã=+âwÃm[âì‹ãÏ…~/ðg†o¾ê .5;ÿ‹:OŒ¾5i¾8Ðñ®»âCZ-ZfµýËF»Ž/ê ÷¯ñgìµû2üBø›áÏ>=ý~øßãƒßM“Â_|_ðáÿ‰~&x^M"cq¤?‡|y­x~÷Å:#éwϧ>›ªÛ5”ÄËlbsº¸1XXÉUqœùê¶äå$ÔSXˆÙ% ´–&i)=" “”©ÔwJÊÑ]/®°z»îùõ¾h8·ŸüŸö²øÛûrþÚ¾9Ò¼)ñ«âÀ¿ÛRÛöß|ø÷û"ü°øAª~ÌZLÒ|]ñïí ðãßÇßéÞñ&–>0øïöEðf­yã RëWðwÄ}6öÓWµ¾ðïêoì{û'|bøeÿcÿ‚ÂþÔŸ¾Zhÿ ?i»ø'ý¯ìíñMwÁZ½ïŒ-¾þΚï¾1[ÚhÚF¹©xËÁðèž.}K»‹ÅÚ/†“Äf+ký u­6ÐÞCúñàgüç¹þ‚®Äž¿Sýù÷õ®J”á¹§+ºŽ¥›MsIUºZ_•:ÓvoGm–Žù›V²ÙGEÛ–ß?u/BxÓðõöŸçúUø“ðÏè?úÿáPÆž¿Sýù÷ÅhFž¿Sýù÷ÅqÔŸoEú¿ëÈÒ1²ó{ÿ‘ù㯌ÁO< ÿfóàò>Ÿmýªëôº¿5ð ïÿ ñàãÿ“ßµe~•×&?\Bÿ°lþ¡aÍhüöý_ý;0¢Š+ˆÔ+àߌþÖŸ}¿goÙ×ó?¿j¼ŸZûʾñªçö´ø—íû;~ÎxÿÕûVsøõëéøIÛ6o¶·þ•Lóó/÷oû‰ÊGOcßúŸþ·øV„IÃõoóÓð÷¨bN˜úêŸëéZ'L}õ?Ïõô¯Ñ*Ï~½üßo—ô´ø‡á/‰zGö&¥âŸ xËB¶ïƒt»MKíÞÔ|í"{ø-~Çy-½ý¯ñWþ Ñðãoí%ñ3öœø…ñ Sñ?ÅÏØ;ÆðN¿x×_Ñ´ÿ‡zÀOx÷RøâKëk[_ 'Œì~!]jµî‘¿oãeÑ­´98|45T]X}ÙdŒt©ÿ<Ÿð­ÓþÜ÷?ÓÿÕ^>"ªJRœ#)5äúªn¤¡~–‹«Qǧ¼ÎªnQI&Ö­¥ç%üõå]¶ò¹øÉàø!Çìãà³ãC}ñçö²ñüž1ÿ‚mø§þ Wž:ñ—Áë¯øC?e_Ü@úuŸ‚áðßÀ¿ ÛXxßÁ–VÐi^×5X5­KuŸQñW†¼QâíGZ¼÷?Á$eÏxÛÁÞ3¹Ôþ)xÚßÂ_ðLÿÁ%.<ãOxRëÁÞ.ý—t-ZÓU—Sñe®ƒàë—?µä´:V½â _Ðü'6—wuàm6ñὃôÞ$éÇ?×ÿ­ÿ×õ«è™ÀºûÿõÏùé^\èaãðҊ꯭•ïwvïw®¿~‡J”·rwjÏÓN6ù>§â÷Ãÿø!oìÇàvñÓ¿ÆïÚÏÆMãø&ÿà– øãâÃme|û,x¿TûnŸiàˆí~é‘h¾2ð&›Ÿ‡<¨\&£áuÒ솥âŸx§ÅšŽ»â][Öµø$7ÀÉ´ßÙáþüný©þüBý?cß~Áš/ÆÏ‚?¼á?‹¾>ý˜<9§h¶žñ爵?†>"Òlµí_Úxº×Ç <=ðÛÇ^ñ}Åηà¿xfD°ŠÇõU îzÿŸj¿ tÿ<~œŸþ½pT¥F+Jj+¦÷I4ï{Þí¯^—6‹—ÄÛò½¾ÿëüŒ¼)û|#ð—í—áßÛNñOÅ­Câ׆¿b½'ö±ÑüOãh¼]áKï„z7Å[‹ÖÞ*ñ¥â}Uø¡â‹W^%¶K][ÆÞ!ø•©E­é²\\jÚ5çˆnn5ù>á‰:{~¤ÿ‡oÃÒ¢rsøêϽ_‰:cè?©ÿ>õÃQ¨ÞÊ×nM.­ÿÀ·ùhZNM/é$Mtöý[üÿJðŸÚÙã?ißoÙçãOâá[ø—ù…}tü‡õ?ç§5àÿµºãöNý§§ìññ«øm¼M“þ}ë<ÿÛp‹þ¢¨|Û«Êÿ¡uîê×¹íþýy-zýŽ¿äÜ~ÜÝÿ©ß‰ëéªù—ö:ÿ“qøuÿswþ§~'¯¦«Ã:Š( Ë_Ù‘sû9~ÏÿöD¾þgÀšùöâ½ò5éíÏÔÿŸåŠð¯Ùã?gãÿTGáN>§ÀšŸë^ûãðýOÿ[óé_¼×Ÿ¿;iyI/$›¿õçä||uKKè¿Bx× ?Sõôïÿ×Á«ñ¯@~§ééÛÿ¬I¨"\~Ÿ¯aø~\{Õø“×ê ÿƼê“íè¿Wýy l¿%»ü>_%¡2/Ôÿ.ßãWâN˜ïÀúw?çÓޫƹ9ü¿©þŸ˜­\ úð·ÿ_üõ¯6¬ï¿Ñ/ó7ŠÑ]¿ :^é'©4k’=°×·ùúV„kÃÇ¿ù÷öªñ'ù÷=/~ßJ¼‹ÐvϹ¯>¤¯~ïð_ðÌèJËúþ½{îMg“õü;~ËÞ®"äçÓ§×üÿJ‰F¹ÿ8ÿ=óWaNŸçžç·NŸþªà«-üô^‹wýwò5‚ëò,F¸ôàSþ}êìKÓ?SýOÓëP¢äAþ@ÿ>õz5èÔý=;õ‰5æÕ÷vïäº/ëõ5Jí.äñ®qïÉúŸçWã^žü`?Î}øªñ®Ðõÿ.•~5ÏãÀö¯ùö¯>¬»õÕútþ·ÓÌÝ-—¢×õ?<~ŒÁCk°?èá¿ýT?²ý~”׿ÇÀî?à¡ÿµàÿ¨w†¿õPþËõúO\¸ík§ÿPØ/ýC ]ƒþß«ÿ§fðÿíÏíðzüý¦?õ;ý•+î ø‹ö„ÿ“ø íðKö˜ÿÔïöT¯_…?äÿ7ÿP1G>cþçWÖ—þž¦"/#Ð>ßãWx§üÿõê× =ù?áý>¦® ïøóþ{×êõ%»è¶þ½ç¾dñŽý‡üý?\z^OÓÓü÷5 /AéÉÿ?^*â/~ç§ù÷ÿ â›é7ý] ¢¬½/ø.ïæNƒ©üùÿ=jÚ¯@:ÿ_óúTQ®1è?Ÿùæ­F3Ïà?Ïùï\5%Ÿä¿¯ÌÚ*ý-·éÝv»íªìX}>ƒüÿžõqW¢óê­EþCùÿž~¸«Q®õü?À×InöooëÓñ6ŠÕ_Ö¶vرúwà}?Ïò«j:ôÿëÿZŠ5ïéÀÿ?OçVcçðçü÷®:§mY±b1ŽþÏz±äç×þ?çÞ£ AVâ^žÜ©ëþ}ý«Š¤ŸÍþ_Õ`¬¯ßòþ¿BÄkÃýñ5j1ß¹àŸóÒ¢QÐóïVã\}Àúÿž¿Zà«+÷×oEþ}¼Ù~¤È¼Øsþ~§úÕÈ×?üÿž•/ÔÿŸåÏçWcûùú:á©-ßE¢þ½x«$¾ÿRt^G þ}¿Æ¿ ¯¯kä/ø'Ïü˜GìCÿf…û5ê™ð]}{@!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^ÐEPEPEPEPEPEPEPEPEP^ûShú·ˆf/Ú7@Ð4½G\×uÏ€ÿ´}EÑìnu=[XÕµ?‡Þ!²Ót½/M²Š{ÍCQÔ/'†ÒÆÆÒ®nîfŠ"’Y¼QZQ¨èÖ¥Y%'J¤*$ôMÂJI;kfÕ™24eì¥öºjçån«ûYüðÞ¥q£x†çâ~…«Ùy&óJÖgßÚ LÔ­MżWVÿh±½ø_Ôu´ð\ÃæÄžm¼ÑL›£‘X×OÛOöx\gÄ^<ãŸù!y?ølëôƒYø_ðÏÄZ•γâ‡~×u{Ï'íz®³á-SÔ®¾ÏV¶ÿi¾½Ó纟ȵ‚h|Ù[Ê‚¡M±ÆŠ3?áJ|ÿ¢IðËÿ/ ÿòª¾º\_V[àaÛLD¿ùSÜóVY­íŸþ¿ù#óù?mÙÐ`øóÔÿʼnø÷Ïþc:¹í»û8.3â_çþ,GÇο‡Ã.ß穯½áJ|ÿ¢IðËÿ/ ÿòªøRŸ¿è’|2ÿ ¿üª¬%Äò–ø%òÄ÷…¬´ö¿ù'ÿoýz ÇûpþÍ£–ñ?ýЧè?䘟©üª-[öþý•¼=¤êzî¹ãé:6§Þjº¾©ð3ãåµ–¦éöò]ÞÞÞ\IðÄG µ­´RM4®Á4gb&¾ïÿ…)ðkþ‰'Ã/ü ¼+ÿʪó?Œ¿²wÁ¯‹ß ¼}ð½¼ào Gãß j~¸ñ‰à/ ÿké6š¼ÖòçO?`·)tmdš(¤FÑ<Å`Ê+ q•ï„—þ­ú_ýŸeÛCEƒjËÚ­?éßÿn|_ìÌ£þF¯gþÈ/íÀôÿ’_Vãý»ÿf5Æ|YãÁÿtö‚?SÇÂßÀ~µöOü)Oƒ_ôI>áá_þUQÿ Sà×ýO†_øAxWÿ•UŒ³šrß SåŠë…{°Ò[Tþ ü°ù o_Ù||[ãÀOø°´ÿ:Ú¶Ÿ·Ï츸ϋüyÇ?ò@hNOáð³üñ_Y”ø5ÿD“á—þ^ÿåUð¥> Ñ$øeÿ„…ùUXK1¡-ðÕ—¦.z²¨M;ûHéÞ›ÿå‡Ëþßß²Âõñ8çþHí ×ÿ _oóÞ®Gÿý•W¯ŒüyÇ?òoÿ´G_ü5]¿Ïzúoþ§Á¯ú$Ÿ ¿ð‚ð¯ÿ*¨ÿ…)ðkþ‰'Ã/ü ¼+ÿʪÆX¬,·Ãâ>XºüÅש^Χüü‡þ —ÿ-?7|ñcÁÿà¢~ ñÇÛ½{Tðµ¿ÁŸøQõMoÁ>8ð?™â "?ÚGXÔì-l|uáß j7¿bÓ¼I¡ÜMugi=’ÿhE¸3¬ÑGúß\.ð¿áŸ‡u+mgÃÿü ¡jö~wÙ5]ÂZ™©Zý¢ mn>Í}e§ÁuŸk<ÖÓyR¯›ÒÂû£‘Ô÷Uˉ¬«Õu#û:4ãINJ4hÓ£ÉB ¶©¦Ú„Uݬi¸G•´ÝäÛK•^RrÑ]Ù+ÛvQE`XWÅ~ |mºñ·‰¾2|ñG¦ñ«ðëáÏ€gøyã?j÷ö¯ü!>4ø…­E«i¾-¶ø¿ðæÇEÍÅ ZKË CKÕ>Ñýƒj–—‘M|Ñ/Ú”WV‰ÀVöøJŠ•^IC™Ó¥UrÊÜË’´*C[-yn­£FuiS­J‘æÓµåVÚŧò½ŸSòa|?ÿ#^žøéÿ"«ÿôOÔË¢ÿÁI—§ƒ~zÈ©'ÿE~°Ñ^›â\åýpXþf0úŽþ}ËÿÖòÿ§ž_ÕÙùFº_ü¡zx3àGãáI?ù:µ0²ÿ‚•®1à¿€ÜÔ§7ÿE~«QY¾ Í^øŠOmðXŸ÷,?©áר—þ ­ÿˡþ \½<ðŽ|%?ÿE'Z­¨·ü-;P}À³­Î®–Wm¥[êž¿³ÓgÔ– Œ:…å¯í7}ukc%×”—W6Ö7“ÁI,6—*Âÿ«T<ó2•Ó­EÝ[ý˶Ýa®½V½w+ê”?–_ø6¯—÷ü¿>ìü¸Žoø) U/àÙü9UÞ«á;¦@Ø‚±ý©²ƒª’%TœU{ÿ5<û?ÿá#uÿÑQ_¤^ñG‡|oáŸxÏÂÖ›âO x³DÒ¼KáŸè÷Q_i:î®XÁ©èúÆ™{<7z~¥§ÜÛÞYÜÄÍÖóG"¬ nÔ<ã÷©Aíÿ0XŸ÷,?«Qí?üWÿ“?1WRÿ‚›¯OþϽ1Ï„.úâTÔˬÿÁN—§¿g¿AŸÞqÿ›U_¦”Vo3ŽÞúàp_üÎ5‡¤¶SÿÁµù3óEuÿø)âôð/ìóè3àûÞ?ók+’ø‹¦ÁJ~&ü<ñçÃ]{ÁŸ-4/ˆ~ ñGµ«½#Â’Á«Zé-Ðï´ JãKž÷ö¢Ô,àÔa³Ô&’Êk» ëhîV'žÒæ ð¿êÝ£˜â£(Î?VR„”£%Á^2‹M5þϺi0öÚk÷–i¦½µ]SViûý­Ï ýšüâO|ð_„|]§dø‡Iÿ„ûCOûe…ÿÙþßâÍ{S´ÿKÓ.¯leólom§ýÅÌž_™åɲd’4÷:(®`¢Š(ñcáOÇï‡ß>|ðÄý;âÏüeá¿„_ôc@ñìýñîÊöÚûH𾟢ê"ÿ LÖ°êºn¡dš…„×:}ÌÖ“[©Ñ W §íû=®3âqÏüÏŽüŸÃá¯ùÅ~›xƒáç€<[{£â¯x?ÄÚ„©e ÿˆ<3¢ë7°ÙG,ÓÇiÖ£esáá_þUWÚÏŒëM¶ð4ÓwÚ¼´»¾—¤Ï&9T#kV–ŠÚÁòHüòOÛKöxψ|yêâÅ|yëéÿ$ÐÕ¥ýµÿgP9ñzÿʼnøõÿÎÏ·ø×èü)Oƒ_ôI>áá_þUQÿ Sà×ýO†_øAxWÿ•UŒ¸²rß–!ÿòž½M\“¿¶øÿn|í¹û8޾%ñàíÿ$#ãáÀ÷LOùÅZ_ÛƒönŸxðc§üX¿Óá‰é_vÿ”ø5ÿD“á—þ^ÿåUð¥> Ñ$øeÿ„…ùUXK‰9¯|ý±?ýîRÀÛþ^­?éßÿn|<Ÿ·7ìÔ½|QãÎ8ña¾?~òL?Ï5_Gÿ‚€~ÊšÕ´·ºOŽ|c©ZC©kD×_>>\C©áí^û@Öô÷xþ2­Þ•­iº†— !í¯¬îm¥ ,L£îŸøRŸ¿è’|2ÿ ¿üª¯•ÿcÿ‚? ¡øiãë]{áÖâÚ£öÑ{­xA¡êµ·Æ_Cxš÷I¾•ý—¨Ú¦€Ñfº 鱨–´H]±–}=p’I«;bUúZÏêú-î¬ï}×[ú£ÿŸ«ÿ¿þLæöîý™AÉñ_§üXOÚóãáu]OÛÏöaQÿ#g=üX?Ú §áð·½}…ÿ Sà×ýO†_øAxWÿ•T”ø5ÿD“á—þ^ÿåUc,Þ”¯|%M{b£ÓþåYW’ÿ—‘ÿÁoÿ–#Gû{~ËÃñw=OüX/Ú“éÿ$··åùÕ´ý¾ÿe±×Æ<ëÿö…ÿçWþOµ}]ÿ Sà×ýO†_øAxWÿ•T”ø5ÿD“á—þ^ÿåUc,Ã-ðյ틇ÿ2 QšÚ¤?ð[ÿå§Ë‰ÿý•Ç_øðgù ´7ðøWVÓþ û*/_øðvÿ“ý¢8øj«éøRŸ¿è’|2ÿ ¿üª£þ§Á¯ú$Ÿ ¿ð‚ð¯ÿ*«bp²ß ˆ×¶.—ÿ1©Ô_nø*_ü·ò?>e/øwâgí¹ûMüBð|š½×„|Y£i—^ÔuŸ ø›Â7Z•®‘ðûöuðÆ¡s‰âýB×a‚{BÕôõšëL‚;‰,e–Ù¦·1ÌÿªuÇøáç€<%{.£á_x?Ã:„ö¯e5ÿ‡ü3¢è׳YI,3Éi-Öem<–²Omo3Û¼†'–Þ Å/a\µêªÕÔ\%*q‹—3Q£J£Í%)IÆ É¨Å97h¥d´„y#fîï)7kk)9;+¶•Þ—mÛv¾ýª¬|o¡üAøgñsAøm⟈^ðºW[Â:·Ã»KÃßðë_|M¦_Ïcãïø#ûJÆM3á׉̫¡MªßÃ=µ´/b>Ù7Ü4Vùv:®[Œ¥¡ S«EUQe7Mª´jQ—2§:sÒ$ãË8ÚI7utâ½×¥*SrQ—-ÜZR\²ŒÕ¹”–ñIÝ=/³ÔüX_Ûcã9ø'ñ¿?÷Ezáèÿ øQ¿ýÿwð;¯þ ?à Þ \gàgÇÞüòø×¹ÿ’åþs_¬tVo?®ï|]ôÆw¿ýêpÿŸµ¿ò—ÿ*þ®ü­ùD¿ðP¯“ð3ã÷·î¾ÿóò«)ÿð:ã?þ?ð;Eð3¯~¿þµú©Efóª’ß‚ÚßóÿÍ…ýY/ù{Wÿ)ò£òÅ࢞'àWííûŸüý*uÿ‚Œø `_´¿î~óÿ™Ô}>•úEdóY=^ ÷ãù°&Ÿµ«§ýzÿåGæÿÁG¼OÀŸÚÛ÷¿ùûžjÂÿÁHþŒgàGí ×| þ¿ýkôÞŠÉãÓß„ÿÀ±¿üÙý]—ì_üý©÷Rÿå_ÕýÍÿ‚”ü>ÏÀÚ?õïð#§þóÅx/Œ¿h=/öý§ÿdícÿ>"x+Møâ kLÕ®|}ÿ ú/·_xßâÀû½" &|AñµÄ¾T Öd¿’þ->wY¤2\<î°þÔÑQõÔ¹Ü0˜jrœ*Ó猱nQZr¥;)â§ òNV挒nöÑYû'¥êTi8¾WììÜ\d¯jiï³]{…Q\F§È_ðOŸù0؇þÍ öjÿÕ3àºúö¾Bÿ‚|ÿÉ„~Ä?öh_³Wþ©Ÿ××´òí+ÿ%›þ óÿg{ãOý`Ûz¾½¯¿i_ù,ßðOŸû;ßë~ÛÕõíQEQEQEQEQEQEQEQEQEQEQEQEÃ|Bø‹á†:.•¯ø²âêßOÖ¼sðßáÖžlìæ¾žoüUñÿ†þøFÜÁ.–³x›ÅZRêˆtí;ízÁZÉ]Í|•û]Ù^êú?ÀÊÎêð굯ìë{uö[yn>Ëeàÿ[ü@¸¼¹òãAkðЉn%O*2é™!vI£úÖŠ( Š( Š( Š( Š( Š( Š( Š( Š( `£äþÉŸôѵ kR»Öu‹ómi0›Ý[X¿¾Õu;¢ž}þ¥{w}u$·W3Jú4QEQEQEQEQEQEQEQE|Ëû2ø¿Ä¾,ƒãÌ^&Ö/5©<'ûM|`ð†‰5ë#=†´ÍCM¼Ñtx"³Ò-õ#ah§{ù#»—bÓTQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE|…ÿùÿ“ýˆìпf¯ýS> ¯¯kä/ø'Ïü˜GìCÿf…û5ê™ð]}{@!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^ÐEPEPEPEPEPEPEPEPEPEPEPEPEùŸãÿÛ:ÇKñoÁßj2øÁšG„á¨í~7|+·0jž'—Ç t¿øcCø²$ž"Ô¼MâoxSQø\lñÔ0ð¥¤ÄñxŸKGý0¢²t ÝOQд]C[ÑÛÃÚÍþ“¦Þêú_[ê¡êwVpÏ£¶§h©k¨¶™u$¶FúÙÞìÁöˆUc‘@Ö Š( Š( Š( Š( Š( Š( Š( Š( ø5ñ+Ä~7ñçíCá&œ©ðg㾕ðûÃ/ck%¬—^ÖÿgÏln¯¼Ë‰ÍÕÜz¿ÄýwK’õÞ³<”ZÙÝýö¼?á×ÂÝ[Á?h_O¨é·'Æ_|>ñN™§Z‹‘§j>øcῇZĺ§› [´—ÖþÑÚÑ­f˜I–)îQEQEQEQEQEQEQEW–|1ø[aðÆçâuņ«y©‰¿üAñJò+¨a‚="ÿÄ:O‡´»*ÄBO™gÐôM(Ks{rÒpO©× ࿈Þñî£ñJðü·’^|0ñÜÿ|V—vj°x– xSÆO›;7Úì·ã=d»P¨òÍ4As fî袊(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Šù þ óÿ&ûÿÙ¡~Í_ú¦|_^×È_ðOŸù0؇þÍ öjÿÕ3àºúö€>Bý¥ä³Á>ìï|iÿ¬ûoW×µòí+ÿ%›þ óÿg{ãOý`Ûz¾½ Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( 9ø­ñ7AøGà­GÆšüWw©o=†™¤hšoÚlj|E¬Ýŧh~Ñ⹚ûS¿ž(üÙæŠÒÂÑnµMB{m:ÆòæÆßüøñWö´ðíƒy'Ã_ ë>Òï> ¾—ã?xÅ^"ôSá_xÓÄPx¯Ãê>7ð”ðüVZõ§€’).¼!à Ù¬®À~X¿F¿l­2y|ð¿Äî…ôühðçˆ|Vä*ÓDÖ¼)ã‡vúÖæUKÄ~9Ð5{ë—ÌZ}Œú•ÁŠÞÎY¢Ãð÷‡¿ÕþïÓçñ<~%|Úó­W?iûÙI.f“æ„|¼Ð“iÊÖùÌml]\­׫B• tœ!J\’«í¡+â%$¹œT”èÓù#:5$ã)¸ò{gÂŒ±üH“[ðæ½ ·ƒ>"øN:ëÄ^mEu‹ ô­`ÝǤx£ÂºçÙ4××|1©Üiú”wW^“©Øjzuö«iVRÇo%ß·Wäߊ>!&›ûl|Ñü*ͨxWI¹ðWÄIìdÝÄ?|Eálü1©‡ïgðÞ‘áùüTö³[íý*ð,RJqúÉ]XiÎTÚœ¹Ü'*j¥’öŠNM$’”eÍN|©EÎqI4—©—U­V„½´½¬©V«F5íûxÓi9¸ÁF tæç‡«ÉÆUhÎQŒ#%”QEtáEPEPEPEPEPEPEP>ñ5ͰŽâÆ2¨%úú€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€<ÿ⯀->)|;ño€nõ­gÃGÄšL¶¶(ðåÛXøƒÂšÝ´‘_ø{ÅzÈ!#Ö<1®ÚiÚö˜·+5”·º|ßÛ]YI=¼¿‰¿±—ÆÝGãí5ñ×áßÅ?ø;À? ÿj=7âWÄ];Oñ߇ôÛOŽ´_ÙÓàÂïéŸ,"פÖ5ï…Zgм ã‹wÉÝËs'Á;+Ï9›Åš<¢_¶¯ªkÇ¿ƒqO-¿†|{iãÿøÚ+y¥·“ÄÃÉ<¦[øBêX¤F}XÖ~!iº–·d>]VÇAm&ìÉ¥_jVw~w¥|4𾡣/‡/¼1 Þøy¢X…u£éóèæ 2[w²(À ÀàÓ<qUj”©BÙ8ÆNnW”åTåŠÃNkßnO™ü½âb3^VÜ£(Þü³§9SšNÊéN2JVWVvW²ô°˜…Š¡ ÜŽ›r«Npo›’­ ³¡V*V\ñUiÍB|±s¤áÜQEV§HQEQEQEQEQEQEQEQEQEQEQEQEQEòüçþL#ö!ÿ³Býš¿õLø.¾½¯¿àŸ?òa±ýšìÕÿªgÁuõí|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¯kä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@òçíûdüý˜¼Ià?|U½ø©?‹>%xkâ/<%á¿„Ÿ³ŸíûDësx7á-÷ÃÝ/â?Œ5Ý7özøSñFïÁÞð–¥ñ[á݆§âOE h¿lñ^›om{q/ÚðoŒ ñφô?xCÄÚ5Ê^hþ"𿉴»]kÃúî•wcºÓu}&öÓP±¹C²{[ˆ¥_•…kêZnŸ¬é×úF¯ci©éZ¥Ö©éº…´7–†Ÿ{Û^XÞÚ\$]Z][Ë$óFñM ˆÈă>"þÈŸ!†};à?ÇïøÂ7€#x[Ôí.­4h€Uû7…¾ Ïá|IÐô´-4›mt 4›}kgäYÛ}Gñƒã·Âï€ú‚µ‰þ!¼ÑĈžøMðÿIÑ|-âÿø£ÆŸ |+øÿ øgÄÞø›áoø§FñøSñ«á¯ˆuÿGƒQð¾£¯Üø;ÅÚ?¼9âh¹T£J«Nq¼¢šRNQ’OxóEÆN²æƒn2²ºvG5|&á*ÔÔ¥ÔgNœÔenh9Ó”$éÎËž”›§;.h»+|éû5þÅúwÁ­f?x¯V³ñŠ-î}.ßOkùôë GRóÿ´õ»ÍOUÙªxƒZ½ûDäßjBë,÷s »Ù!¸¶û®¹ÿø¯Ãž𧉼qã ^ÏÃþðg‡µ¯ø§_Ô\ŧèžðî›s«ëz½ôŠ®ÑÙéºeÕí˪1H!v Ä`ü™«ÁB?e]/àŸÀÿÚׯ?<_ð×öñkø à øcû=~ÑŸþ"xëÆvÞøãSÃ6¿>|'ñÆ'Xðç…~|LÕ|]eâ‡úD¾_k¶ž)=ý§Ù[HÆ1ŠŒb£¤£¤£´I%d’Z$´Fð„)Â4éÂ0„"£B*0„b­Æ1IF)h’I%¢GÚtWÎÿjï€_´n£¨é5x+Å>Õ"´ñ瀼GàSLñ׆¼9qw¤&£ôE2‚Š( Š( Š( ø›ñ ü<ñ_ƒ¼¥ü1ø‹ñCÅ5ð÷¼Uc¦x†–_Ùº€5/iíþ­{ñ3â?í=wj|7oak¦Ýêw·eä²[Á£Jü°øõñœÙö‰ÿ‹öPÿ螨 Z\[Ýü?ñŸ<7y§^xoÇšð;×1ܵݥݥ´¶ß?¬Wæ§ÀŸùHgíwÿ`ï ÿê¢ý˜+ô®¿W¡Uâ0¸S„)Ë–å¸ÙÓ§ÏìáS€ÃâªÂŸ´JŠœjU’¦§R¤ÔT§6œŸºçM·ËR¬v»P©8&ì’»QW²J÷²[fÒôËBËW¸Ó¬'ÕtË{ëM7SšÒÞ]COµÔÚ͵+k+×®m-õÓìú%Ž;¶±³7 !¶„¥êøóâ7í/Ÿâ¯h>¸K;½ö©ðŸÀÏZ‰t™-µ;DñÇ|]}á-WJG@Ò¼S®ÜøZðÏŠ-ZïOÔ´ÆÕ4wkšÖŸ¥{çÂOŠžøÙð÷Ãß< .¡sàï RãÃz†£c&œúΓ§ëZŽiâ %fyt/ǧ sÃz†Bê¾Ô4ÍQ4¼X×AžEPEPEPEPEPEPEPÏ´ÂÝkÇ6ñoƒ-í.ü}ðÛT¿Ôô]*þ÷û:ÏÅZ¹dºwŠü>¢Ë$:lºµ¼:v«£ßÜDÖ°x—ÃÚêC¥K¨J¿ë?µwßÇw¥ë^ ø±oñOv·ºøsð÷ZÑõ[[ÀË~(Õ#·øvÚt‡d©ªi^0Õ­.lä[½8ê(è³~§×3âx7Ŧ⯠xgÄÆ×?e> Ðt­dÛd’|ƒ¨Ú\ù9$“åíÉ$õ®Yá¯RUiÏÙÊvöžâÚJ*q»\µcÝ©ÂÑé¶®üÊùw´¯­¶wP£ =(Ò§~Xó;ÉóJRœœêNrûS©9Jsz^RnÊö (¢¬Ô(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Šù þ óÿ&ûÿÙ¡~Í_ú¦|_^×È_ðOŸù0؇þÍ öjÿÕ3àºúö€>Bý¥ä³Á>ìï|iÿ¬ûoW×µòí+ÿ%›þ óÿg{ãOý`Ûz¾½ Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( ͯڣöZøÏñÛöÉý˜<ðÿã/Æ_ÙßáÿÃÏÙköÛø}ãÿ‹Ÿ'ýŸ$ñ‹x‹âÿÅ?Ø3Zð/Ã;;ãÏ¿ ÆZ'‰&›Å~ðN—â? jŸ´ˆÄ/ nZi>,øºãþ ¨ü;øUû|ø[à÷À½ϯüoý<3ðKºñ‡´ïüdý‚¿gÏÙŸþ ‡àoŒ?³E‡Ä9µ‰uèŸôßÙkãÀMrËÆ—þ·ñ¥m xŸÆ{¼†ð¡ñíçàÿ†_´l? g¯xÇBð? ÝxÁ÷{™}û8ëß´ø·oàyþø¢OY|,Öüeyñ†ãÆCEøyáûmjçÃ?/ìûxEñáOÃígâä?…|3ÿñí¹mûSþÊ^øIð•>øwãïìKûtx ã7ƒ¼ð'ö§ñí• _KûKxçÃÚ¿‹.õÍ+âl~ ¶ý­5_x.?Üø7ÄÚÃ_èŠüÙý’cÍKö\ý©¾>jš >(×>ø¿ö[ý–ü9¥|Rñ¿ˆ¼-¬xÏâgÇ{OÚ£þ eûAþÒzÿ‹,ü?‰-§ˆõŸ~Ôþñöµ¨Zx+ž¾Ô~!\iž´ŽßÃú¶‹áÿÒj( Š( Š( Š( Œþ8Þü@°ý¥¾MðßÃñ^¸ÿi¨®´ïøë[ø}¥C¥7¿ežößYÐ~üL¼¹Ô#¼K"Ó$Ðm-¦¶¹»»}^Þ[(lï÷lj?jÀÿ…/û=ñÿW7ñÿ¡"µ|oÿ'[ðCÛö{ý¨?õcþÈ£ú×ä×ügÆ_|;áoø%ǃü ñKâ÷½'ãŸü[ö$øñbïà·Å߉üSã?ƒŸtï‹Ú_Žþ_øçá/мã[]ÄVÐZKu—â àÔtí+X²ž×VÒtÛÛ_ÇøÛêøŽ)À`§–åõçW,¢Þ+<ÓÚ(G¨âéá3<%’ƒQJœ[ºæ›Õ®Ì,d©Vš«R)Vøb¨Ûøt–ó¥9u×_D~¨ÿÂIûVÿÑýžÿñ&þ#ÿô$Qÿ 'í[ÿD_ö{ÿÄ›øÿБ_ƒ ÿ‚ÞÿÁ85¿ø.ׄ¾4ø“âÿÇÏ€ðLÏþÈž;ø Þ?ñ÷ˆ~#ü[¹ÓmO…º¯£ü ¼øµñRñ'Œ|I¡xkâ¶¡§i:'Šü}®x¿Å:âk™5MCY¶Òôûëü;ÿ ø/Pð/ÄŸê>x‹EýšiïÙkà¿í㯵†þ>|ø9ðcöµÓ¥o~ÓøÏá…Z^‹ñƒÃ~ñLRø⇃F‘à+ÿ x®ÏTÓâ×õgÓä|ü2úÏß¡ÃyUzìΣˆÎ ¦ñx|&„\*gŠJ£§ŽÂFp7-jÐ¥JRö“mÙâk&¹®š¡ö[Rچ˖Zö»²?nÓÄŸµi9ÿ…/û=qëûN|HÿèG5'ü$¿µwýoÙëÿsâGÿB5~ ~Ø¿ðTqñ+ö„øAð¿Á øûáσŸà¹ß°_ì9Çµ€ø5gñ¿ãwŒ¼/ñÄþ|Mð\?¼uÄÙëá š'…þ1üŸÆ~›âöµ{£‹øßÃÍ.§ú+ðcþ 3ñcö‘ý¡~0x#öýï~%~Íß³ßí‡âߨ‡ãgǵø÷ào |@ðgŇ^Òµ‰^5²ø âÿhÖ^*ø7à=w_Ð|/«kzÆKŠµÆ¢ú—„þø†ÒÂù!ìXwJ”*Ï Ê`¥IÖŸ>+3„iÓýÛ‚”çžÛÚTU"•jÜÍÃÙ¹ÂJ2“m¯¬VzÙ{´]Úµö¡²êö×}­ê>øWûWx'ö†ø»ñçþÙëSÿ…«m¦ÛÿÂ)ÿ ñ"Ëûû?Â? ü+¿ûwþ~ïûSÎÿ…oöý¿Øúw—ý³ö]Ïýö‹ï¥á3ý«¿èˆþÏ_ø”?ú+ñ›öÿƒ‚¾þÛÿ´×€~ð?ƒ|;£þÓðÑíûnN4ðOÍ9:œñn«ræfV¥ÊæªÖ’s¼¿†¯*“R“Ö’ÝÏšÊÊÎÊÊÖïÿn_Ù'öµý­t½ïÀÚ7À¿_­îü1¢ø—Ç/íñ#Y¹ñ/ÃÅø¥t)¬Çì­¤­¿Š|3ª‹íSáÇ‹å–öoM¯xËK‚Æ]3Çö~æð¥Çí#à øoÁ~ýŸÿg ÃÐt xsG³ý§¾$Gi¥hZŸo¥imªÃ!ü–ö:}¥½´+“¶8”d×ãì{ÿãø…û[/ì¿ìK¢ü6½ÿ‚”ü?ý¸¯ÿf©õßÚ‰õÝ|Yý‰ÅxƒÁÿïôoÙìk^øiã+=OŽß⎕á¯x¯BÖg×ìcø5â/DÒuÿü[ÿ‘ÿ‚”þоý†¿a?øÇÂ>7ý±¿oŸø*ŸÆ?Û¾é¿ÿl¿Š–_üIàOÙGÄ?xæ<±…7‚ÄF¬¥6¢é{±QU$³ö0vµZ½]íOû¶VöZß™ZË«Õèê7þÚ¿þˆ—ìóÿ‰Añ'ÿ¡”x¿ö°'à—ìóÿ‰Añ'ÿ¡¿!´ø.|jƒþ /ÿ ©û2[üQ½ÿ‚­ø{ö°Ô|-Å/ò|´ø/®þÈ^Óµ/ˆú‰o4_ƒÿäñe¹Ö¡ñއ­¤ÙéL#ð½¶§c¦êßÛñXiþm«ÿÁ||}¦x§ÄÖZwì%'ˆ<£ÁcüoÿfÑ|GaûMh¶ž ñwÆÛnÃ_i^Õ¾XXiþñeÍŒÉã­7]ñ~™ÃËmOÃ׈þ'Mw¯Yøg¢žuœÉ¥,¿.NÒmJUã%ÉV¥ ^/0ºµZU!f““‹qMjÆóú¯N”ú¤ÿçÏf™û‰ÿ oíaÿDKöxÿÄ¡ø“ÿÐ…Gü%¿µ‡ý/Ùãÿ‡âOÿB~bxþ |Wû-~ÑøRžøÁqð¶ÒÇY†ÓÅ^7ºñ1Øû^¿ÿÐM/àŸÁÏé?²®£¯ücñWüÛJÿ‚9ühø%Æ}2ÆÏáGíמ,´ñˆ¼ñ6ëáÙÓ>,xfÅ4O]øm5/|.ÄVþ+OímSÂ2è÷QÏ×Ëݾ£‚jé^øµ«J[}y蓵öoKßE>ËþŸUèö¥åÿN¿ õô·ëÏü%?µ—ý?Ùßÿ‡âWÿBsš§Ç?Šß|UðÇOø×ðó࿼ñ#ÆzŸŒ¼?ûAx“IJøwV´ømñâ%…Æ£¥ø³àÃ=&M;Q_\h>wü%v÷0ê:¶žÐÚ^2*üËð÷üOÅÿ¶ÿìÛñCádž¼¯~Í¿´RÿÁLuÿø%G‹ôßüT¿ñdúоø—Eñwí%ñáÅ'Ã? üO"è_²¾ñk₼Bžð‡ˆ´Ÿxi ǧÇg¼¿]ÿÁK¿äž|ÿ³ˆ·ÿÕñæ½-â+:5pxX^Ž.W„±|ñtðµjSœ[ÅÎÕ"¤”£(»4ÓNÄTƒ„T•Zߤ¬Õ+5*M;SM^-õM_Gµ¿J(¢Š¢ÂŠ( ¿>|Aÿð‡†~;|@ø?©~Ïÿ´Að?ÂßÚ#àì©ã¿ÚnÚ€óü Ð>9þÐþ ø âï…^ŸJWöˆž×^¸ý¦~ ø:oÚ|ŸÂZŒ|Z¶7zäzV¨ëVß Õù±àø'w&ý«?jÚsã!ñŒuˆÿµ?Âï<%¦üxý "øSáË_…ÿ²gì«ðoþ"øû8AâýövÖ¾0xwâ¿ÁOx¯Ã9Ô¾xãÄúf…ÿ ÛS¶ñ­–¯áý3BðxÑÿl?€_´À/†_´g…|yáÿ ø'âg‡ÿgýI4¿ø£Áz?мâ¯ÚwÁ ¼uðgá'Ä›;ÄúÖ‘áŒ2Ò>:|"·Ðü5ËýGÄZÄ_ÃáY5ûoxbóW¯«þÚŸ³¶­yáo ürø1ñǺWÄo ü1×þx/ãŸÀÃ㟠øƒÄ>ü Ö¿·t_üK𸱛À6øáðÖÓÅÞ2øúïQñg…| àoøÓâ·Žþ|>ñ‡ãŸÀßø&í×ðKàŸÂÙ›Nñ왬|2_Œ¿ðGÿÚ ã7ŽïÔ/'ñ-×Ã/ü!ðÜz”_|/óWÁÙ öŽøÍñ‡ö[øa ø þøgûü ø?ð~/޾5ýŸÿjßÙë^øË¤|ÿ‚ ÿÁ2ÿjÈ  ü&Ð>&ü,ðׯ=gVñ6¿ûA\Ù|t†ßÀ@Ò¯Â_Úƒöhøû©jú7ÀŸÚ#àgÆ­_Ãú†|Q¯i_ ~-øâ>¥¢xgÆ–gPðwˆµ{xƒYºÓt/X+_xgW¼Š?^³Ss¥Ü]B מø¿öÎøIàŸÚÛáŸìk«ØøÎ_‰<"|S¦xšÇHÒæøoáëíCKø¥âxÅž —]·Õ´Ï|KðÏÀŸŽ¾&ð“gáÝNÇTÑ~xêmGUÒ.môk]kåïØëö ñ¯ìØÿðM)5I>C'ì}ÿÇøûüa“Á«Fþ,ø¹ãmcöñ ^'ðcÜø;Bm{À²kß³‡Æ]oUÖ|P|3âfÕüo¡jIák«Ïxªm Ë~*Á3>?üEñÇÆßÚ2Çö¸ñ§…~8x‹öðgí ðŸàÖ“aðVoÙ.?f‹»?þÌžñï|Aû2xö£Ðto|=ððƒã…·Ã_Š^‡¥ë¿>,YøoÃÞ1Ò/u9¼tè^1ÿ‚¼ü&øuáÏŠß¾ ~ÍÿµG„¾ü9×ÿmÿxgãîû:ê¾øÛãïø'þ—ûDëŸ< ð¿EðÏí®üLÒµûíöTøé«|<Ÿã?Ãÿ„^ñU—‚åyõÝMGLŠëõš¿|KÿƒÓµÿÙ_öàð¥Þ¡ý»ûS~Ñ1ÿÁXGÁßøËöƒý¤|Yû=ü+»ý½~%~ÔšŸÃ}cÿ|G­k_ þ ø’ÛáÆïø_ãŠ~üµñÅý×Ĥ²ÕøkÀÞðׂü1>”’[øf_·êÃPî4¿ø'g€5_€ß´ŸìûñËãÇíKûVxoö©ðœÞø•âÚ3âž“â_EðÒø0ø'K²øYáOx/ÀŸþÞi̼þ¿Ï½|­,n5èñ5« «UîUŠ¢”©Ù/d£>*4ùaËB‚µ¨ÓåÛ–=–ÖõZïßw¿wÝßò+Â?ðEoÙ+Àÿ³gì]û/øwÄ¿m|û~Ù~ý»|⩼Sà‹¿ˆ¿><ü>ñ'ŽþÇ>/ðõ÷Á¯ßµU‡ÁÏÞ|[Ô>~É×ß-­?f/†Ÿ,øa~Gwñ;N¸ñ„+¨ÿÂç×ô|Eø£ð®óQð“Ëðõt¿ø£MðiúL_˜ãcO M)T¯”W4äëZ´«Q®ªòÒq‹7ˆ«9Tš•ZJµTç53μ9ZJÍ馉ZÝìùV›;#ú0ýœà‹?²×ìÃÿÝÿ„Ç¿µøußü6ü(øKüSðîÿþÿømŸíÏøZ¿ð¸±~øþøGÿá ¼ÿ…}ÿ_ü+ïì¯*Ûþ?øJöKçyWìíÿœýƒ|Aû~Ä^ý’j‹> ðgìuâOž$ý‘m¯_~ xç⦕'ÆÏ|J±øó£ét†~%øâÍ ^Ô|S⟇Þ#°O†·M¦Gá› qgâÍëV“ówá‡ü3þ !ð‹þ ào¿µ÷í+?ˆ~jÿ·7…ÿc|LøðïöAý ÿfOŠ:¾¡àë}'Nø'ñÛFø]'ÃïÚƒöLý¯üO­ÚÝøÿľ%Ö¼aªøÁú…äÖ û/Ù|0ÓÄ×^ ÿÝý²¿lo| ÿƒoþ|0øÑàÿÙ×Ãß·U¯üFÇã×ü)Ù‹öXðg‡®Wöxñ/üEðÃVð?Ã]àæ•ðËáÞ»á{ÄÔu‹wðg„ô]Å>-¿—Ä_¼=ñnu};Vö#…Ì9%VxÈNíVçSœ©Ê¡šc*s~ãß•¾´§šæÄNœ×¼ù2¼4´|¼÷„W^ö³òºØýÙñì/ÿÖýе߸&/Ä_üjÑ?eo ÿÁ8lÿh†ÿ²¦‡ñã_Ã/‡¿þ!kµ€ ðWÄÈ> _üO°MoǾ6Ôa†ûÆZ\~ñ_…/ÅÚ¶­ªjV:Æ™4:M¶ÿü9_öYó?|ÿ”½ÿÃéÿäiøwÿ'KÿBü’¿ù õ+ÉEÿª©_ÏìËûAþПðPÚkþ ?ý«~:|lñ\þ+éßðV‹Úx7Â_´_ ]jß³v•âß êÞ"²Ñ/~êïg©þÐ ðÞàŒ­ìšf…á»õO€À߈:ŸŽ/þÞÿ‚nÁS?mOß´—üÎëâ¯ôÏxWþ *?à¦Mñ_öa·ðÃýþÀëûxúïEøSý™â èZÅ;ããKko ø·þn½âc¨ëºýç†ÿ±Z(í&íú®2”SúÏ5HB·¶nr~ô18éNn—½‡ÄÕs›JsŸ.’pö‡4wåÓK+t´7Ößj*Ë¢ísôç[ÿ‚(þËš¿€þ'ø2Ûâ?íáýWâüïYÿ‚¸è´O|7_|"ý¯5[½2êÖÿášê¿ µ_IðÿAM>k=Â_|ñäÛjº‹júÞ­vºmÖ¢Á¿eÝá^µðÖ/‹´ö¥¬êðRË_ø+‰ñZñ§Ã[⧃¿kûA¡¤:þ‹quðyü­x=¢ÑðÏÄøÖ[Óârâ÷U¸½:EΑû7OA“ŸNßçÚª•zú^¤·æóÒÉtÛE¦ÍeÛúþ’ûɹ?಴Ÿ ¾>|7>+øßÏÇ?ø(‡Š?à¨ÚoÄ{x2Ûâ?ÀÚïÄZ¿†õ?Åÿ58>ÇáýDðe׆ãƒÂÚ/Ä |F‘¬5}rÇÄú‡‰mïbKiü³öV ¿gÿ†ÿð”ümkŸðPï ÁQ5ˆòø¯Á—þ;þ×^Ö|M®j^0øç©Ü|<—ÃúÆã[ÏÍŠ´_‡þøpéa¤hZ…¯ü1ibñ\~²Ñ^)Õv¼åºöõ’oNÉ%ÚÞ‚²íýiþKî?9?g/ø&'ÀOÙ—âϾ/ø+Åß¼G­|Aý­?k/ÛSQð÷Ž5ïê^Ñþ8þØ:7€¼1ãíOÃöZ€<7«Zé ð§‚µ/ ü3´¿Öõ-KHÑ~#üD‡Äú¿Œ.u-ëÃqÁK¿äž|ÿ³ˆ·ÿÕñæ¿H+ó‹þ ^1ðçà7¿ílóüzÅ{ÙZovïþÍõ²Áb,¾÷÷u9«½]*QÿÓ´ÿ+[ä~’ÑEÖPQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEòüçþL#ö!ÿ³Býš¿õLø.¾½¯¿àŸ?òa±ýšìÕÿªgÁuõí|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¯kä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@3øßþN·àý›ßíAÿ«#öD¯p¯ñ¿üoÁû7¿ÚƒÿVGì‰^á_ˆøçøgÿR¬;û±XÔ—Þw`ßîª.õÿðU+ H=1^Óüÿ*}|…m÷|÷o䎠¢Š:שF|š¶¾ˆ.ø»ðàí áTð?Çïƒ_ ~8ø&-JÛX‡Áß>xGâ_…¢ÕìÒhí5Xü=ãM[ÒRµŽâxí¯–Ð]@“L‘J‹#†üÓøýÿ•ðßí-ûb~Åß~#øïᾇû=Á=üI?ŒÿeÏÙƒá_ìùgàK½ijidµOüUŸâoˆômÂ~ñÇÃÅžð€>ü)Ót»m;Ãúľ"›MþÞ»ý…”W·„­^ƒN•IGÝ©k>UV<•\9“P” Üyà”ù[JK¯<­&Û]Sû¶¿ùlxd¿³ìÓ?ÆH¿h¹¿g“~Ðp[‹H>;Kð—À2|d†Õl”¶Ñ|OŸGnº[6š!MpF, ³ örcªžý”?e¿‡ßð§¿áýšþø#þçþü(øD>|;ð×ü(ßø[_iÿ…©ÿ {ûÖ_ð¬ÿáf}¶óþü!ØŸð™ý®çþ?í/>]þýOAÎ}?Ÿùþ•èQu,—<ì’IsI®T¥Ç{[–rVþYJ;6]‘à¾ý“ÿe¯‡£àïü ?³_ÀÃ<ÂÁÿ…ÿ‡Áχ~ÿ…ÿ kí_ðµáOcxrËþŸü,ß¶ÞÂÁÿ„+ûþ?µÜÿÂGý¥çË¿¡ðwìùðáçÄ?ü]ðÁ„>ø±ñ?â_Äÿ|5ðg†~!üC+p—j|sã]E²ñ'‹vÝGÈþßÔõ \"L?xªÃר¯R“›ÞRw\¯ÞnéËžW¾éɹ‰·k³9µ¢Vîþë/Ãô ™FךFN?ô©«Ò£´óÿ%ú™…Q^¥m÷;}íÿ›²o°ª2@üÿÏé_œðSù'¿ìâm¿õCüz¯Òg×ùŸé_›ÿðSù'¿ìâm¿õCüz¯,«·ÿPØ×ÿ–X„—êrUøWý|¥ÿ§`~‘ÑE© QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEòüçþL#ö!ÿ³Býš¿õLø.¾½¯¿àŸ?òa±ýšìÕÿªgÁuõí|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¯kä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@)ñƒàW„þ0Çe¨êZ—Œ¼3ã?xoÆ^ðg|ñ âG€µoGãUÐgÕà|<ñŸƒ[Äšsê¾ð®­.‰¯\ÝéóÜè6ܼÒ?È_ðÀ¾>ÿ£Çý¡?ðå|uÿç÷_¥Sm¾TÛj+–7wå3Ÿ,o²æ”¥eeÌÛݶK„mÂ-½[qWoEví®‰/’ìÍø`oÿÑä~пørþ;óû£þÇÿôy´/þ¿ŽßüþëôžŠ.ÖÍ¡{8$?ðþ^Kî?6?áüÿG‘ûBÿáËøíÿÏîø`ÿÑäþпørþ;óû¯Òz)óÍm)/ûyÿ˜{8$?ðþ^Kî?6á‚> Ñå~Ðßøs>;ÿóû£þ#âýWí ÿ‡3ã¿ÿ?ºý&¢Ÿ´¨¶©?ü _æΟüû‡þò?6á‚> Ñå~Ðßøs>;ÿóû¥ÿ† ø‚:~Ù_´8ÿº™ñßÿŸÝ~’ÑOÛVÿŸµ?ð9˜{:óîø ÈüÚÿ† ø…ÿG—ûCÿáÍøïÿÏîø`Ÿˆ_ôy´?þߎÿüþëô–Н¬W[W¬¿î$ÿù öTÿçÜ?ðÿ‘ù·ÿ ñtý²ÿhÿu7ã¿ÿ?Ê?á‚þ!ÿÑæ~Ñøs~<óü¯ÒJ)ýk¶ÄW_÷§ÿ$/eKþ}ÓÿÀ#þ^Kî?6ÿá‚þ!ÿÑæ~Ñøs~<óü£þ/âýgíÿ‡7ãÇÿ?Êý$¢ŸÖñklN#ÿTÿäƒÙRÿŸtÿðÿ‘ù¹ÿ ñþ7ö‰ÿÃñãÿŸåejðN½w^—I—Äÿ´÷ÅoG¡j2kEŽuߊ>9ÒtýZM+SÐΩo¢ø»ã6µ¤&¢šFµ«iðß#s¶£wÆ'|þœQMã1mJ/‰q”e '^«R„ÓŒã%ÍgE¸Ê.êI´ÓL=•$ÓT馚iòFé§tÖš4Òiôz…Q\ÆEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPÈ_ðOŸù0؇þÍ öjÿÕ3àºúö¾Bÿ‚|ÿÉ„~Ä?öh_³Wþ©Ÿ××´òíY¤|Eÿ„‡öPø‡ðóáG‹þ2ÿšý£µŸøÇÁÞÖþè~,ÿ„O\ý–?i¿„QêºTŸ~"|.ð…÷Ø|_ñGÂI}bþ-·Ô¿³n//¬lï~Å44ÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíòü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_^Ñ@!ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕõíóìuà/|+ý‘¿e†=Ò¿°|sðãöqø!à/ènÓuOìox?á—†<=â-+ûOF¼Ôt}Gû;XÓ¯,þÝ¥jÚmß“ö‹Ë«Y"þŽ¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢ŠÿÙbuteo-syncfw-0.11.10/doc/src/Buteo_SyncFW_-_Sync_Scheduler.jpg000066400000000000000000000727001477124122200240620ustar00rootroot00000000000000ÿØÿàJFIFHHÿÛCÿÛCÿÀë;"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?þà¾:|t‹àŒ_ ­­¾|Gø»â‹¿åøaàŸü0—ᥦ¿}¯Ú|4øñgP¼¼Ô>,üGøWàÝ;GÓ¼ð¯ÅwW^+KÙ¯SO°°Óï'¼?8ÿ†•øÍÿHùý¯ð´ý‚?ú7¨ý¥ä³Á>ìï|iÿ¬ûoWçïíáÿwø‰û!þÕ?¿f‡¿²F‹ñÑ~ÿÁ3|aÿ;øã}gö‹ÿ…Bmþ|8ø­â_‡ž<ð^‰áŸøR_Æ­â[ #Ãâ?Ý7ˆ,¡×õ+ø<9ue¡ÚE?‰”ô þWã7ý#çö½ÿÂÓöÿèÞ£þWã7ý#çö½ÿÂÓöÿèÞ¯ÊÁÀ_ 4±Ã/ |´ñ_€loþÅ>Ó¼AÅôÿ…ÇðÒëöñøg¦üKø)â¿|ðçÃOxÃ’ÛU]óJøñ÷á·Æ=vïCñgŠ|ðÆ? 4k?k?"þØð[¿ÚçâOüÛö¯ÿ‚ƒ~É¿uÏٓᾤøZý’?k‹ßˆ~/Ýø«ì·ÿ†ÿeoˆ>ø…û>øïÁo©|8ñî­á½3ÄÞ!ÒídðÏÅχ«áZÜEñ3Gøƒ§Å¡Ú€B?ðÒ¿¿é?µïþŸ°GÿFõðÒ¿¿é?µïþŸ°GÿFõ~hø7þ á²·ÄÚÛÀß<¤|<ƒöÆÔcÏ‚šÆ·ÃŸxóÅ:FŸ};ê~h¾!üYøwâÏê¶VÚ7ߟ ¾~Ñ|Mÿ ‡5ÝsÁ>ðÓxŸWð¯Í~ÿƒ‘“âçÁ?|QøûßøËÄ·ðNOÚkþ _ñ“Àþ;ý ì>Øü5ø1û2üeñ÷ìý¯èñ}ÁÿÿÂÏñ_ˆþ'|6ñ Ó`ŸÃžŽÏÁ‹câ)Ò}^ëþXÀ?p¿á¥~3Ò>kßü-?`þê?á¥~3Ò>kßü-?`þêü`Ñÿàâ=KÇ$ðw‡þÿÁ=¿hŽ×ZWì÷û |xý¢ô‚:ÇŒ^5ø_ÿ Çð§Âÿt/ ü:±øOû,øçÀ<†¿þÀ5O…þ6êÿ¶ï…> þÌþ>øûðÞçã…Ïìóð;â×Ãï|1øùû*þÏ·^ ñ7‹~ü øMðçÄ? þ$êµg‡|Eá3¥üÖ>4Øë|qáh¿üYãO|?Ò<'á'üÇö‰ø·ûO6‡qðá—þ |ýž¿oo~ÐÚ=ׯÏxsÄçÄ/Ù³Â?°_Åï x“á½ïí-û(þÌ—6>‡ÀÿµßÃé:ÏÆÿñ,?¾*üA×õ-'Áÿþk´êßü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_›Zÿü+â¿ÄÙ þ ¯¬h_¬>~П±ì/âïÚCáÿŠü7©üCø“àˉügð¯ö›¿øSâ]"OÚ#ö^ýωµŸxïötñ(ñ-Ãoˆo¦‡OÓt¿ˆ5•¥û|TøÑ{ñ›âíÄ5sá~…ã/…ž…á jzG†~øoVšÃ_²¼ÔaÒ|I©xªbt ý­MfÛ¯üø‹ð×á–‰àïøsÁ_~$ü_Ó5ÛÏx³QžÛ_±Ô4ï‹¿<©ËðØøBÒ|)ã/ i:“àñ6ã]RWÁ¶Pxo_ѼCm¡|6¾økN-(ɸÚIµiFRVm>h¦åu¢š‹jÒW‹LI¦ÚÖñ²wŒ’Õ_FÒRßWÒz;4Ñô×ü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_>ýª¿ntø‹ÄÿøÓö×o„wßðUoŠß²µÏ‰üUàø&¥ûÚ|"µÿ‚—|Cý¼ àIø{…ÿ"±ñ=Χø?á/‡üsâ¸n,lþ9]Åã_ÜøƒáCMy©úÿíÿSý¡åø ÿÎñ·ìÍð3á­·ÿoφŸ²·í«Ù|Pøá«hÂï|Gý­?à™u߃öú¦û:üK¸ñ½ÇÄ ¿o?áî§ñ#þ¯Þ|7ðž•â‹>𯌼_¤ø{᮫#?Mÿá¥~3Ò>kßü-?`þê?á¥~3Ò>kßü-?`þêø;Pÿ‚Ééº/įÚ-WöUøñuû8~ÏQþ×ö~"ý ü7ð¿ö–Ôt¨µŸØ“Aø¥¨üaºñˆµïه¿²¦›àÍWÄ|uðëáæ³áÛ â'Œu߈2xWÂþ øuá-CYÕ#ðïÜ¿³Wí%ñGâ‡ÄOŠÿ¾>|Ð~|iøWào‚ÿo<9à¿‹ããƒu_…ÿ/¾,h~ÔbñÄŸ~ÝZxÏNñWÀߊ>ñÏ„¿á»Ò´[Ý OÔ¼7ãx_Óµ†¹ÿ +ñ›þ‘óû^ÿáiûôoQÿ +ñ›þ‘óû^ÿáiûôoWçÏÁ/Œ·¼ká{ÚCÇŸ—ö…ño†¾+Gmû/|RøQû5èÿ±÷Ç_‹øWãOhÿ ÿcOÚ‹àG…¯>!ü:Žk Üø·ÃÓ~Óž+øµñ?Yø-áOßëŸ4ŸiÆ©à¿CðU gâF¯ðvÇö_ýžôÏŒZ_ÇŠ¿¾ x;Äž5øÏ/Â-#Oø‘ñCöøçûzx³MñH·øOñûLÓ~|&øð’ÛÅr鶚߈/5o‹^%Óì<0¾!øY†¾ }‡ÿ +ñ›þ‘óû^ÿáiûôoQÿ +ñ›þ‘óû^ÿáiûôoWÈÿ ¿à¤?¾9ÝÉáo‚Ÿ±†ãO‰¿ 4ëµ/€µOÚW@ðü z‡ƒÿjÏÚcö>¹ð—ÀOêÿ äðÿÇø—âì}ñ÷WðlÿnfÏ \x'Að®©ã/xZñ|Ó¾7ð¯ÇÛ×Ä~ý±i)~0~Üš?„ÿfo?·ßŠ|7k©ø/þ c}ûüVøOûþÛ~Áû>x7Gð¯Ãþ ‰u/¿ ¯ü+/‰üj|uuã¿ ø“Ä6_/-¯¼oâÐØøi_ŒßôŸÚ÷ÿ OØ#ÿ£zøi_ŒßôŸÚ÷ÿ OØ#ÿ£z¿;¼uû~übø_ñöý¤5_h~'ý‰õþÔ ?eÿ Ùhþ“ûSöƒý¾Ùxê]SNñV—bž%ñ,Ÿ¼kà_Ûáå–yâ OEŠ/Ùûálþ ±Mcâvµþ˜X|oøwû?xcöhøSûVþÓ?ôOÚ➃ᇾ—â?޾ü/ñWíñKÓüá¿?ÃÍwá[oøƒ^ñ·‰´Vo |9ðûÁ§ê>1ðþ‘¦èÖQêš=” ðÒ¿¿é?µïþŸ°GÿFõðÒ¿¿é?µïþŸ°GÿFõ|áûjëß¶‡ñ‹J»ðOŠ?j/‡ÿ³Â;P‹ÆŸ±§Â_Ù“ãáøÓ‰|bþ,±øßðããǃ>%üHÖþEà¸>Oà?öjð׌µvˆö^-ñ/‡¼¯&§{¿ðPx[Ä¿ ¾ ¦ŸáŒßþ+ÍûOð—âu‰ü!à/Úáí ðÓÆ_üoûMxS@º³ñ¥ßÃÿø7ÿiOX|0›ÄÞ?’ïOðgíPø™¦OñNÇSÐÀ>ÿ†•øÍÿHùý¯ð´ý‚?ú7¨ÿ†•øÍÿHùý¯ð´ý‚?ú7«óÏÀŸðW߉ÚÃÙûâGÄߨßOðTŸ¶/ì±ð¿ö‹ý”<3áÚBÇâ¡ã?|QøÏû þÏz'ÂïŠ÷ú§Á߇šoÂG¿øûrþÏóè~5Ò§ø™§\øQñw‰|S¤x+Ä~oê?£v~3ý¯²ø“ðçÆÚ‹ô‹DBøƒé~1ý©ÿ࡚oí7û?ü,Ó¿e߆ºŒüYû:þÞ~-ñ7Á+ÏÚs@‚¾:Õ¾üEÿ‚{Úü/øÁáÏÚ:ÙïSø»§x~×Høçñ7áŇ…5o€~ÖuxžæãÆ> ohø£jö7ü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_Ÿßÿà³¶ÿ¾)üð×ÂÙãߌ¾|JÒ¿eñ¿Ä-'á¿íâmGáf½û\üø=ñ÷ÂQêÚ¯ÃÙâì¹má/†?¾<|7×~5øƒâ/ísð§ÄžÒî5ígFðŠô«ïļ+ÁU>;^|_ýŠ~"]üÓü+û2~Ö¿SÆŸ <1oñSÃÞ"Öþ#øWö‡ý¾ÿà“²ÇÀOŒž>¸“á=—ˆþüAøcð÷öÊñ×Äoà—‡õox/Äz_Œ´ êß.|ecw7ÂÐÓ¿øi_ŒßôŸÚ÷ÿ OØ#ÿ£zøi_ŒßôŸÚ÷ÿ OØ#ÿ£z¿8ÿi/ø*ïˆþþ×?|!7‚!ÿ‚›x·áU÷ƒ~hß5]nÀ]øÊßÂ>9ñAð‚|?íŸ?à¤þ;üOøðþÏÄŸ $øƒÃž ø½ûOüZø)ñáµ¶‡ð÷Rð¯Ä¿ Ïû ~Ö¿<5¨k:¶¿à_â'‡|Aà?ˆŸ³ç„¯¼3uðóÇ^ Óµ ;\ñf—ã+Æ6×Zð÷Eà¿ù?ÚWþÍ ö!ÿÕÍÿ þWã7ý#çö½ÿÂÓöÿèÞ­‡¿´Î©âß‹º/Áþξø£Äÿ<ñ?Â×ÿõ/ÙßZÐhëÚ(¢€>Bý¥ä³Á>ìï|iÿ¬ûoWÈ´·üãà‡í}û}ßþØß´‰õŸü7Õ¿`ôý‰uÙËH»ø‹ðå5ˆÇÇOã-׎õ‹Ÿ >,x7_Õ¼9¬iέðïÄõ \ø_ÄÚ>¡wqâWS°™ü>~ý´l¼}¨øïö³øaâ_x?Ç3~×¾&þÃñ|­|GðžåþÃ_¶”ºŸö¯ƒ<=ñ á^±¬ý¯GMBÆÇì~=гµ+«=VãûRÖÆ}Qëÿá ý½ÿèåd/üBŒßý0jñÿÿÁ$?àþ5øÓáÚXý›t{?оñ×ì×ñ3ÃZÇ„¼wñ_À>µñ×ì}£ê~ýš*ø—·¿?i/‡¿¶‹®l~$ü^Ðdöø[¦G£x/ãgƒ¿áñþ–¿ ðGÇïøßâ7Å gÂßôÚ—àÏÄOê~ ñ?5Z/~ÒÞ!ý¥>øYÑü;¯xKà{}jë^ýfÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨ÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨ç'þÓÿû_|ÑügðUþ êþ þÎÿXÕüqñÃú_ÅïþËzW‡´ÿƒ#ö‚øqàøgáWÇy¼wá/YÐmþ,ø#Æ:~‰¨ÁöE¥•µ½µ¼_¡~øãÂω>)h_ð”|2øÁðÿÆ_ ~"øgûOXÑ?á"ð'Äj^ñv…ý³áÝCHñ‘ý¯áý_PÓÿ´ô-WLÖ,>Ñö­3P²½Š ˜¼ þ¿ÛßþŽWöBÿÄ!øÍÿÓ£þ¿ÛßþŽWöBÿÄ!øÍÿÓ SJÿ‚n~Èg…>(øRïÀž?ñ‹üeÿ„|@ñ÷ÅÚ+ö“ø½ñ¿QO…>$±ñ¯Â¨4OÚâŸÅÏ|wðM·ÂïiÖž;ømkàˆÞ¶ðŽÖoøN-#ÅWz´øšüÛöðÜúÍÕ‡ÁínòãÅ+ñ¾/OâO9ð&…ñkì‘üX†_Mèßð…þÞÿôr¿²þ!Æoþ˜5ð…þÞÿôr¿²þ!Æoþ˜5x׋¿à—ÿ³ÉýŸÿmOƒß›Æ?2x?áη¯]ünø³â_ø¶O†_ð¼|i.“g/‹ômCRÐIð\þ ´ðï†ü%oáß\ý©&Öü?û,XÜ|Kñ/…uèž*ý™fñÿŒ4= ïÀ^ ¾Öôߎ _Å^%Ñü5âxâûÁ~º¿†ÿTÓô-oÇž0»ðæðéú—‹¼A5œÚÍÝŸøBÿoú9_Ù ÿ‡ã7ÿLøBÿoú9_Ù ÿ‡ã7ÿL€?•ÃàsÅך§Ž>#XxcÇ_|kேº¯Æ/|TOø%guù¾%é~5ý˜ü#⨾Þ\jzÍ–›eðCÃí|5£|N}Á¦h#éÃSÔoošúCë÷’ÜÝZ—ýaÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨ÿ„/ö÷ÿ£•ý¿ñ~3ôÁ«ITæMrSÜ]ã5ËK'}¥ñIuŸ½¡*6·½'dÖ®÷»½ßv¶O¢Ðç|1ÿêý”¼%ñO‰ºW†~+^j‡ãŒÿh _x£ö¡ý©|oð+GøÓãÿˆ#ø¯â‰>ýšüiñŸ_ýž<1â‰þ'x·Ä4«ÿü/Òÿáñ†«qâ &ª,71y¥§üþ ÷§›‡Ó¾ x§NœxI<áûÍ;ö†ý¦,5‡>²øÅð—öðß‚¾ ê6Ÿ¡¾øá‡ß¾ü0øƒð£Ã®<£|!×|=tß ì¼#iâoZëžÛÿ_íïÿG+û!âüfÿéƒQÿ_íïÿG+û!âüfÿéƒVe °Wìšÿ¼cñnóá:jþ&ñãxúãÄz¿ãoˆÞ"øI&¯ñ[Óø?⟊´o€ºï‹õ¾ñÏÄß Þj~ø‹ãß |;Ѽiã[ñŸâ{U¶ñ¹¡ÚþϲÀ¿ÙrÇĶ|/¯i3øÂMþ]ÆŸ¾(ü`ñž«aá-1ô_øvoübñŸ|iƒ|¤Ks¦øÀöÚô>ð]î¡má}I‹P½[Ž3þ¿ÛßþŽWöBÿÄ!øÍÿÓ£þ¿ÛßþŽWöBÿÄ!øÍÿÓ  þ~óWÁïè=ð„¼ii¨ø2 »o†þñÆÿ޾<øOðz ÝûÂóGð7à~%x›àÏÀÍžÕ5_Z7Âx)¬<ªê¾±ko êwÚ]Ç%û7~Â_ ~ü7ø?á¯Ûhž8ñÿÂoß¿jÛ/øwCÖ~h_ðÐßl~8h<[ xOñ—ˆ¡Ó|'oàoÚâGÃ_xÅž$ñþŸàïÉá‹õO_ð†âK.›þ¿ÛßþŽWöBÿÄ!øÍÿÓ£þ¿ÛßþŽWöBÿÄ!øÍÿÓ cÅðMߨӯ2Á.·ð›RÙÿ ÅÏk¶z/ÅÓ> ¯Ço>1ý¢>*øKâö‘ሚ>—ñ£áwо2üAñ·ŽÁï‹V~4øQ¢^x›UÓ<9àÍ#B¸:Pú[Àÿ¾|9ðWˆ¾xCÂÖúw‚¼[ã/Œ~?ñw«ë–º·Šÿh‰ž6øÃñ{Q¹—_Ô5K¿³øÇâ'Ä_ëói\G¢éëM¢xNÒ|=c¦i6^!ÿ_íïÿG+û!âüfÿéƒQÿ_íïÿG+û!âüfÿéƒPü0ÿì©ÿ ïðöPoƒ~“ö~ø©|Ö>|:›Rñ,ÖžÕ¾x§Ãþ5øY©Å®Í­¿Šµ{ýľÒµ bï_×uYüoë$ñ6™â?Yê¿VWÈ_ð…þÞÿôr¿²þ!Æoþ˜5ð…þÞÿôr¿²þ!Æoþ˜5t¿dOßüOkã_Ø|IÑü_ƒ¦øRûÄÿ>>ü~ýžuïxGEÔµ½cEðwµO€?¾Þ|Fðv‰ªøŸÅ†‹á/Íâ?é7^)ñDÚv™lþ$ÖÍÿ-aûøMý¨>þÐZm·ƒôOþϳ'ˆ?fïŸ ¼7à+} | aãOøJûÅËx–ß^–Æ÷FÓ<ðãÀÞøaàý7Áºøw¦^üT—þÚ|I‹IðœŸð…þÞÿôr¿²þ!Æoþ˜5ð…þÞÿôr¿²þ!Æoþ˜52ûöý’u?‡ß ~ê?-/¼ðCöqÕdÏ…Zß‹¾ \ÂðW¼ø©Oá; R_¶³u­Øjß³WÀíoÃß5=Fû⇅uï‡úv»á龡®^êçÃÿÙ[à?Ã_…^:ø+¡x"}{áïÅ7ñD߬>(x¿Ç?üAñføzÓÂ*¹ø¯ãߌ~%ñß¾&Ýë^Ó´ÏÝÝøëÄúýÏü":Náx¥AÒtÝ>׆ÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨ÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨Ëþ éû)C¤øïF×<ãÿˆöß<-?‚u›¿_´/íñß]м#q®h^(“Ãß üMñŸâÇ>ðî‰ñg@‡âŒ~(üYø£ãívËã–©ðKVø¡yãÏüQñÏŒ¼Sñ/Ä>$—öpø§Áâψz·Š.|@øKá­'GÓ<ñKâ×…üoñ/Ã#EÑnô¿ÛßhúeÕ¯#/ü{þ ý.¡âmTþÏ–±êž*ðoŠ>\j6ß>0Z_xwÀþ)ø§ð×ãð¯Ã›»_ˆ0ÏðŸDðoÆO„~ø§ð†Çá|ž‡ào´í_ÅWÀz׋ücy¯ú¯ü!·¿ý¯ì…ÿˆCñ›ÿ¦ Gü!·¿ý¯ì…ÿˆCñ›ÿ¦ @o Á,a ÚøÒÚÇà…ÍôŸ-~'CãSÄß>6xÏÄ^&Ô~2Aû3'ÄŸê¾(ñÄ}sÄsüDñ.¡û~Î>(‰£U_ˆz_Žþ¯Ä=Äúo޼YãŸø›Øþ~Æ_³§ÀßøsÇü­ÛxïÃ:ÅïÛx÷Åß~+|Mñæ¹gñã[ø1âŠ×¾<ñ¯ÄßøÃÅüIâ›ïÙã൲x¿â.­âŸèÃÝÂþÖ´o µþ‘y‰ÿ_íïÿG+û!âüfÿéƒQÿ_íïÿG+û!âüfÿéƒP×ÇO~ ø¥ñ—ö>ø©ÿ Ã~ðìŸñkâgÇ?øOPð-î±âˆ^"ñ'ìÏñ³öqðµ¶Ÿñþ¢ü9ðÏ„´?><ñ_ŠaÔ>øúûÅ×ö³ÓµÚiZÌÞ"ã¿g}b‹ÿ¾:~Ó~´ÔáŽ~üø1ðÓÄš•ºÙÃñ*‚ž1ý£ü[≞…¦k»Ÿ‡:íßÇK-Áºõí­Šø­|+ªx§AMCÁÚdžuÍZ‡ˆg?Ú+ãú†?hÿÚáŒþ C©¶¥ã¿…?g/üŸâ½µµ¤ãLð޼aâÚ“ãD—άÖz§Š2øùûEü,Ó´OøãÇõo[üBøSáïZhÿþ$h:´~ñÇÄÿxÄŽ—«?íãµÔt}'Äwz½Žï ë‹=ÍŒVͧN&Àôˆ“ãéî}ƒÿ­_4þÙKÙó_>ž<øøŸø^ÿ r§ÿª¼üNM”Æ•W¿ Ÿ$ùZ¦®­ª{§Ûªé±´1X—(þú¥¹•ï/5÷Ÿ«&³¦ø‹FÒ½¢Š(ä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{_!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚ(¢Š(¢Š(¢Š+áïÛsI¿× ýš4½3ĺׄ/®¿h›¿#Ä^ƒÃ—:ÆäþÍß´]Ä¿cƒÅšŠ<>ÿk†,n?´4+í¶·3½§Ù¯VÚòßîøïö·Õ?e±ëûDêŸúÌ´•{|6ížeϵ{ýÔæÎLv¸JÿàÿÛ‘ód aE%m~÷¯žçÃ^ ø¥ðgÁßôï‚^.ÿ‚–YxSã6­6m¤ü#ñ'Ä?Ø«CøŸ©Ï«¢I¥C§øTø=kâ«ÙµHåôè­´™d¾I­–PêO°xÃömÕþ"xz øÃöŠøã«èwú§=€Ó?g» ÷¾×´ßh³ý«Lø ez¿b×4}:ûËK•ŠçìÆÚî;‹9§·—ùý¥[Æ¿ðOˆ¿¶¯Ç?cÏø*ü[ã‡í¹¥xÇöÂýš|cqàþѳ¿í#ñÆðè𮓦ާŠtë^ñ žOü3ñ&Ÿ¨x—Ã:~•Ÿƒ<ˆ¾$Ý~”üEý«¾>øËöùý¸þ\þÛ¿`ƒ¿°æƒûjþ Õ>(ü=ø5âo†í¥¯øïQø©©üUÔü!âã JÊÏàŸ„aøIñgáEÞã=bÂúóS×õUƒÃڧͼݹU§ZŒTÕUN4£J‹¼%Lá'V¤£B¥9ÓÃT|ê¤}õ*S§¤¥Ü°©ZP“k—›™Ê[§M5ËÎ2RœW-ž›šQz~®|(Ôî|_£k^ø9ûxxçÆúGÁÿj?|UiàYÿcÿÿ¼ñ×ÃëM:Ë]øeãô¯€:½Ö㯠ZÜéqxƒÂþ#žßÄúWÚíW´ŠK¸ž_WO |ecÿ'iñ×ÓþE?Ù_¯þ#Yàw¯ãÇöjý¡¿hÏÙcâ߯Ÿü,ø©ŸðÓãïü¹ñ›ö1ø—ðf÷ÀÞ ÕtoxcãÄ–ƒÅþ2Ô!þÕzÍõÄ?¼)àox[Å_ ÁÕ?³oì»ð›ö^Â~ÒõáÏÀûH—Ãßm|GVŸõ¨<|Þ9Ô®|H½áeð¬Þ‚Ò[=séà©¶=ž¿iáKŸÚN¶ñE÷üo/üºÏÓxàÔZí×üþÞm6Þãá…¾’þóhðÜë:D|[ŽÝ¾(G>§aüD^@²rK‚RŸü'àÔc.X?ìü äծ佞‰¿…;I«©(½ cJ­•êÕmêïZµ–«o{]õÒÛÙ´IÞÕo¾*²ý¿¼ñ|9ñçˆ~üBÿ„_ØÏÅÇÀŸü#ö?øJþxËþÿÙãQÿ„cǾþаÿ„‹Âߨ|C¢ýºÏûKN¶ûLý|;ø¶p?á®>>ç¿üR¿²Ÿãÿ6Í_ÇÿÃÿÚßãÇì‡û<ÁF5OÙïû[Nñ—Ç¿ø:çö‚ý˜µ¿øoHøe­x·áï‚~*ê>—Ä~(ð]·ÆýGGøoã;±áË?xSQøÏªé tÝsÅ–7ž-Ôô½>Ôm~¦ñ¿íýÿ¶ý—¿d'QøÉ£ü+øÃãßø/Ã_ø'‰þ*ønçö"øåñų/Œãñ³ÂŸ<3ðcVøõû;ü+ý¢tß;HÒ>&xÂoáícÂþ(ð?›g§i~ñ$2k\¯†¶¸,"’3åËðn7r²WtV¶³ÖËuÍwbÕ9ßøµZèz·øSÕsl»®¿3údO†Ÿ“û\üõãÂß²~Ÿól¦¬'ÂïŠÍÿ7uû@óéáoÙC§sÿ&Éþôÿ iއá¿hz¿‰5ojÚ>‰¤éz¯Œ5û]Ç]ñ^£§Ø[Ú_x—Z²ð¦‹á¿ YêÚíÌ2jz¯†¼; è÷—SE£èº^œ–ö0u1§OÐ{ŸOJÎ¥X-°ø(ÿ܆ éyþáë}ÎÀ“nÜõuÿ§Õù3Àÿd¯ŒZÄ}âƒ|M®ø“ÅÞ6øAñGâ§‚uÿx‡Ið–“'ˆt­'ã/Å ø&áaðfŸ h²j¾ðž™i­Ëiá_[M¨D÷ZLg–júÚ¿5¿`Où?loû8?‰?ú½~=WéMxXõ㱪Œ ±x…B1„cZj1Œ"”c«(Æ)F)$’I”th¶Ûn•6Ûm¶ÜÛní·»mݽXQEÈjQEQEQEQEQEò?äýÿf¯û4/Û{ÿW7üæ¾½¯¼iÿ'ïû5Ù¡~Ûßú¹¿àŸ4õíQ@!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^ÐEPEPEP_þÖƒ:·ì´ýN«ÿ¬ÅûIרUòíb3¬~Ë#þ®'UÿÖaý¤ëÚáí3¬½ÿÓé骇.7ýÖ·øWþ•$õçéØ~?‡éZ'¨÷?NÃñü?J†4íןsØŸOzÐ?_séþ­~µV{ßç§Ü—õóÜù¸«¿×Òß×ÜxV©û(þ˾"ø­añßÄ?³oÀ=w㆗sc{¦üdÖ~|<Ôþ+i×z\I™ucñ÷óøºÎçN…+íõˆå´‰;wQ@ê¼eû:~Ïÿ|øñ3áÓE'Ã7øeà¯øëÀáÔ`oø»^Ñ/õÿ ¼:7Ñ6‰¨X´w„Ü¡þò½n4ü}}ϧùþµ~4è?þOÖ¼ºÊ¤½œ=é)Ir+J{¦ôÕ«^ï]·:#Íug%ee«Ñh¬»WÓSÄ"ý–fRrgO?ò]¿á¨ÿä‘xÚgþŽ+þEÿù.Ç·Åßù(ÿá!®…¿gÙòãâí¿íqð#àÔÿ­¬F™mñ¶o† —âí¶˜¶O¦®Ä™43ã(lWN’Ki´¶âÊG´ù c>¿c¨ã¿×°ü?ÏZ½w?Sôì?õæÔPéïxû«uö¶Ý]Ùî¯êtÂíîþWWÚÿ-[íØð­söOý—<[ãèþ+x¯öløâoŠ1ø¿Àߣø“⃿õ¯Gã߆ÝÛ|4ñºxÇRðíψ—Åÿmﯠð7‰WQׄ¡¼º‹@½ÓÒâe{:—ì‘û+xƒÆÒüK×?foÙ÷Zø‹?üñ^oêß~ê>5›â—Ãxo­þ|K—ÅWž›]“Çþ·Ôõ(<ã¿ox^-Bú-Q±K¹ÖO|Eè;ž¾ßþ¯óÖ¯"à ½>ŸýzóêrkhÇWuî­eÖOMõz½uõ6Mè“}–¯oòÓð<\~Ìß³Œžøðùÿgÿ‚Oà~2ñÄoŒÞo…^o|\ø…âétùüU㿉Þ: Ѽ}ã/ϤéSx‡Å*²Õµ½j]3O“R¾¹{;c›Ùoög±ðoÃO‡–³·À»O‡ÿüs¡üOø;àko„žƒÁß þ%øfãQ¼ðßÄO†ž‹Ãé¢xÇ>»Ö5kž±Òµý&çTÔg°Ô-å½¹y}¶úŸ©íÀéÿ×­^F§¹ÿ?çšó«8tŒv¶ËáNöùËT¶~¦êé%w§Ÿ^ä±'¯ÔÿAþ?µhFž¿SþçÞ¡:{~§ÛüúzÕø“§ëî{Ãüõ¯:¬÷¿«ýõ婬µï·§õùyŸŸß°/üß¶?ýœ'įý^¿ëô¢¿6?`~<ûdÿÙÂüKÿÕíñî¿IëÎÇÿ¿cì/ÿ§¦mCø4ëÕ?ý!Q\†¡EPEPEPEPEP_!xÓþOßöjÿ³Bý·¿õsÁ>këÚù ÆŸò~ÿ³Wýší½ÿ«›þ ó@^ÑEòí+ÿ%›þ óÿg{ãOý`Ûz¾½¯¿i_ù,ßðOŸû;ßë~ÛÕõíQEQEQEòí\3­~Ëþ®'VÿÖ`ý¥+ëÚø§öÏñ7†¼ÿ³'‰<_âžÓ¿h›ïíÄšµ†‡¢Ø}³öjý£l->Ûªj—¶6¿j¾º¶²¶óçÏ»¸‚Ú-ÓM7³Ã÷þÙÀY]ûYY-ÛöU4ùœ¸ß÷Zßá_úTM“ãéî}ƒÿ­Z&1ßÓÜúÿŸé^ í7û5 gö†ø; üZðâOüOÇÿ_ŸZ½í=û3Œgöˆø; ü[ðâOüTÿ¯Ï­~«V5uýÝG×à–­üº__Sç â—Å»u^^~w~½¬{ÜiŽ}:{Ÿ_Ãüô«ñ'Cþ}?ç^íCû2‚?ã"¾€8ø»ðüsêâ üsëWãý©?f%ÿ›øè3ñ{áö}ÏüŒ?çšójÓ¬ôTª»ÿÓ¹uݽ:ôò7„ ’÷¢¯¿¼¼¾z~wò=ö4è:×ÜÿŸÒ´cLséüÿúßáï_=[þÕŸ²Û™?ÚSà ´y3*|cøvƼ¸ä1ÊÄdÆâ)c“c€Þ\ˆøÃ):)ûTþË£þGà¯Æ‡œÿåÅùןR•w{Q¬ïu¥9»%£[=z?;”· ñgíq¯h¦®hZßLj¾‹­i¶Úž“¬i:—ÆïŽ÷ºn©¥êVRÍg¨iÚ…œÐÝÙ^ÚM-µÝ´ÑO²E"9ý5¯?šÇcSM5‹Ä&ž5ZwM=SO{›Qþ +j½•;à ¶ŸpQEÈjQEQEQEQEQEò?äýÿf¯û4/Û{ÿW7üæ¾½¯¼iÿ'ïû5Ù¡~Ûßú¹¿àŸ4õíQ@!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^ÐEPEPEPEPÌ¿ðÇ_³ý¯ü»¼wÿÍ=ðÇ_³ý¯ü»¼wÿÍ=}5E|Ëÿ uû8ÿÑ:ÿË»ÇüÓ×!ñöVø%áøÇÄ~ø}ãïh^×5o øMñçŠtËÿk¶mÍΓáË]GXñ•Ž—a6±} {u µ¯žg•ЦÖû&±´Ÿx_—ZƒB×4f ë2øwÄ0éZ•ž¡.…â {+ J}XKI¦m7WƒOÕ4Ëé´ÛÁ äVš…•ñ]BîðÏÀ/ø'¯Á†_ |; xÛÃÞ-ø‘~.¼QñKÅÖž#ñµ…¯‰þ$xžáõêv±xŠ&¶ÐáÕ.dÒ|/e8’çM𮛢isO;Ùy¯ì¿ðÇ_³ý¯ü»¼wÿÍ=}5E|Ëÿ uû8ÿÑ:ÿË»ÇüÓÑÿ uû8ÿÑ:ÿË»ÇüÓ×ÓTPÌ¿ðÇ_³ý¯ü»¼wÿÍ=rÞýœd/ê+Ò¼5áÝ#[Õ< ¯ÿÂ+ã-;Nñßn/¼1â/ì½;[M[´_ùú}ìº>¯¥êEp‰ö>þÒî$#Ÿ°ëäoŽÿ |máßÙ~Ó?´ïí‹>Ò,ôˆŸ ’ê?Ký¢¾é׳_Ià»é®f·Óì~&ø9ougà·Œ/äŽßNÖõ WÁºõÌñž¯y¥€oÿÃ~Î?ôN¿òîñßÿ4ôÃ~Î?ôN¿òîñßÿ4õô}…ÓÞØÙ^Égw§½Ý¥½ÓØ_¬)}b÷¤­gz–ÓÜۭݱs ÊÁsq ÌŽ"žT #[ 2øsðsáÇÂoíŸøWþþÀþßþÏþ×ÿ‰¾½ªý¯û+íß`ÿÞ©©yGö•ïü{y>oûï3Ë‹ËôÚ( Š( Š( Š( Š( Š( Š( ¾Bñ§üŸ¿ìÕÿf…ûoêæÿ‚|××µò?äýÿf¯û4/Û{ÿW7üæ€>½¢Š(ä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{_!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚ(¢Š(¢Š(¢Š(¢Š(¢Šñ/ÚâÜß~øƒÆZNŠü\×4ÿ†ë¥\~Í?·¼_¨øêý¬¯~ |V»½}OX¼øqf-e‚ïáÇÅ+‹ËOZðbÜXÅàÁ{®øufÒ|a©éš(Õ”QEQEQEQEQEQEQEò?äýÿf¯û4/Û{ÿW7üæ¾½¯¼iÿ'ïû5Ù¡~Ûßú¹¿àŸ4õíQ@!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½^·ñë⎣ðsáž¡ã­ÃV^/Õ¢ñ?à é~Ô¼C?…4ëÍGâGįü8²šÿÄ6¾ñeΛe¦Üø²-RêKê³Ë”–ÐÛ &YcÖ…¸šôpôcÏ[VPæŒyêÕš§N<Óq„y§$¹¥(Å^òi]“9F”äíFR“³vŒSmÙ&Ý’z$Ûè®{ð²þÒ?´Kcþ sëûKøçú~ÊÆ¥_Ú/öŒnŸ> uÇ?´¿Ž¿úkß|#Ä+|_÷9€ÿæ¯3ûKÿ?Ÿþ ­åÿNüÿ«3îJ+âûBþÑÍÓà/Á?Où9ÿô*TÃã÷í"z|ø#øþÓ<ÿèS¨|+Ÿ-ðQ_÷;€ÿæ¯1ÿhàÿçëÿÁU¿ùYöÅñ§íKñÃþ6øk¡ü^økð›À>ø‡â­WÂãM'ãî¹âáÝNÇáߎ¼c>¡¦xŸàgÃ.KKþy´!!ñEµÄ7ú­‹Ãmy‡„ý¹^V7/ÅåÕaGIR©:j´"ªÑª79ÓRæ£R¤W¿Nk•ÉIrÝ«4ßE*ÔëÅΔ¹¢¥ÊÛŒ£iYJÖœbö’wµµÜ(¢Šã5 ðOÚSâÞ¥ðsáV«®xSN´×þ'x£QÒ~|ðã²[ø³âïŽn†‰à}&÷Ët4=FfñŒïá9Ðü ¡x›Ä3µÒn$O{®C]ð„|Mâ_x¿^ÑaÔüCðâÿZÕ|q=æßj¾!Ðo|/«j–v)rš|šÇ‡u=SE‹Pº´¸¼²ÓumZÖÂkhµKõ¸ÊøIàK¿†_ üàKž"ñî«áéÚfµã_Üê^"ñ~º‰uÏê“ÝOpaŸZÕ¥¼ÔSM¶tÓ4xn"Òt‹k=*ÊÎÒD¢Š(¢Š(¢Š(¢Š(¢¿;¼û_|xñ÷ƒ<#ãö}øIm¤øÏÊô»}Kö’ñ”:Œwˆô«MbÊ ømeûËh¯b¶¼‰.£·»º'WXngŒ,­éåÙ>c›{o¨aÕ«û?mzØz<ž×Ú{?ãÕ¥ÍÍì§ðs[—Þµã|+â¨aù=´ù9ù¹}ÙÊü¼¼ßeksG{o¦ÎßZøûã/†þxÓá§…|_c¬ézOÅ-NÿÃÄ ³ÿ„Mñàm;þoø‹Rûh½Ñuÿˆ)sªCà«‹Í=4-_XÑ[ÂÿÚðø§]ð¶‹®Ið;⾟ñ¿á_„¾'iÚMˆ­õ(5/jSÁs¨økÄ~Öõ/ ø¯Ã7÷À[Ï{áß躾‹w4*‘Éqc#ª ;òÓöŠÐ¿jŽ–ž*EÓ< á_hxsÅüQûBx›â—ÀÏXÛ ²j¿µÏÙgJñ†õ;iÒÞþ| ø‘ð§Äñê¶–šÀשmowß± ý¶?f_„ZßÃO‰¾ø ñŸU¹ø¡ãïé~1¶øåñ#Â×’ØxûR‡Å:Äzõ•÷ìëâg¾ñ¥ãGÅþ(ÖµˆuáÔ/|Dò}’9–ifî— gñ”bðÊö¶/ã¢MÞKãôæjïEv™ŠÌpm6«h­ÿ.êõì;¿;mÔý¢¾'¿iéðà§üœÏ?úëÙ¿gÿ|lðMλycáïø¿CñW1O ê^ø‰ãG<ºœº…uF±ñü"î.©á}âk µE¶“ìï3ñã²\Ï.£øÌ:¥JU#F2XŒ5[Ô”'8Ç–j“W9¾gËfîÒzÒÅáëÍ”ܤ¢äÓ…Hû©¤Ýç­ä´½õÚÉžåEW”tQ@Q@Q@Q@|…ãOù?Ù«þÍ öÞÿÕÍÿù¯¯kä/ÉûþÍ_öh_¶÷þ®oø'Í}{EPÈ_´¯ü–oø'Ïýï?õ‚?mêÔý³¹ø_?²×þµÁÊËý¥ä³Á>ìï|iÿ¬ûoTÿ¶üzŒß³ÕüZ=Ý•†­/ÅÏÙ’=.ûRÓçÕ´ë=Eÿi¿„ cw¨ivº–s©Y[ܘ¦ºÓíõ&{ÈKxu+$[¨½<—þG9OýŒðú•HÃþíˆÿ¯¿ôÜ9}>ƒüúÿZ¿úvà}Ïó¯Ãÿ´_ø§ðSÐÅ‚ñÑãÿP~µv?þÑçÅ_‚|p3û?øìç=æåÅ~ëU§~:è¾-¿ðºþ'ÈA;ÞÎËÓ}<üþóÜã^žß©ÿ<þU~5Æ=¹?_óü«Â£ð÷í :|Vø#ǯìûãÃÏþ$Èéþv?~Ò]¾,|çŸÙïǧŸÃöœäW KÏȫ贞ÚsÏúÐÕ7ü²óµ¿Ìñ¯Û|cÃ?¿ì¸ŒýOÁO_çð¯ÙÊü‚ø¥ðãÆ  iþ%øÏð¦Âø¬xÇN}à‹í¥›Sÿ„[Å÷ÍûEêI-öo‹u)ü¨ÚãíÐÙKö¯")í®~ _~Öøº?³°Ï?ònºâW×ÃñO‰Ìq”+aªaÜ)á!BNs©ÏF&£²öNë–¬uÚ÷G­ÄÓÃÒœ*)ÞUÕ”^Ž8ïÍÞ/NÖ>Ý¢¾)_~Öÿ5OövãþM»âQÿß°íVYý¬ÈÏü-_ÙØݶüJÿ豯œ|;Ž[Õ¯û‰Wÿ”ÿ]¥üµ>èÿòg)ûC~Õ¾0ðĉ¿<¡è—Ÿµ‡³E¿À3~g¼mwâÏí'ã¿ÚoÁÓM¯i"kd¸ð_ÂO |ºøµâŸ²\Áy?„4«M–Ö¾Ûðö³¦jösÅaâ-#Ä·zä¾ñæue:[xLŠÕ¬/í¬nn—IÔá–T–ëHžQub'Š9TŒßŽ¿aÏŸ¿iÝöžñ?í/á[Ò¾èÿ®|à‚ÿ|!áÍsúOˆþ x„]êÖ‘ûPÅãÍ/U¼_ˆÚö‡u©ø7žÕŸÃw—†Î¤|;âiïØž ð§íðëÃZO‚üãÙ_Á~ÐíþË£øoò§tMM€³Hëi§iÿµu½¬FY]çžEŒIq<’O3É4Ží—ö/­\4ui^u]Òê¹h½KÙ÷H¯­ÓþZKí»Y­Q÷Mò(Ÿö³#?ð·?gaŸú¶‰_ýµ:ÃZ?üÕßÙØݳüJê{ÉÛÇÒ¡äÕÖøŒ/þˆ–k´*äŸü°úΊùLCûY“ø\³·þ#/įþ‹ŠlÿkFÇü^/ÙØgþ­“âWO_ù;‘Y¼²k|VáWÿ3ë ÿ˺ŸùOÿ–RÑ_-üøÏâxóã/Áω~"ðfµñ3áw‹ìíí%ð7üWà'Wð.£ðÏá/Œ Ö®4ÝÆ`µÔmuÿˆ·ú ÐÅã)fº¶Óí/“J´G™ÛêJá¯FT*J”œdÒ„” ÛŒ£R© .eZP”]¥É^Í&š6„”⤓Zµgk§âÓ³kFšÑµÙ…QYù5û4®go€|Íø[©ð>…“øž•úË_ˆ¿³Þ‡ñêo€ßeѾ$ü"°ÒeøGðÚM*ÇSø#ã-[Q²Ó_Áº+YZßê–¿´‹k©^ÛÚ˜â»Ô-ô}& ÙÒK˜tÛå[X¿GðýÚ9Æ©]åú»ÿÔuöOúó<,ëâÂúWü]ìØ“§ä>½ÏáþzV„IÓaŸ^çðÿ=+Ãcð÷íÆ>*üãŸÙÿÇ}=äåÇ^¾ÿ^Ãß´‡ø¯ð@gŽgßtõÿ“™zûþ5÷5wýäUûóè¿ð<þg”´IYùí¿Þ{¤IÓaŸ^çðÿ=+Äà\x“ö°õ\|wÿ«¯ã­Xÿ´Ÿø±ð<}g¯ž=äç^¾ÿs~þЯ¾!jøÅðwP›â_Šõok©â/ÙûÆ·‘ZjZÏŠ;Ô¯û‰Wô¢zëEê£Sÿÿ&}£E|jš¯íhý>+þÎÃþí³âWãÿ7eÚ¬-÷ífÃ?ð¶gooøÆ¯‰Gÿ~Ò³y-o[ º|u¿ùAkMëÉSî‡ÿ&}…E|ˆ³þÖŒ3ÿ oöv÷m?=ÿæí{TèkGÿš»û;û¶‰géÿ7l:ÿ*ÍäÕã¾# º|X‡ùaƱ1{S©÷Cÿ“>´¢¾SXkB@ÿ…Áû;_øÆo‰\æÜW7añ—âoïÿ ¾ünñ×ÂwBø³á\xZïÀß<{àm^ßÇ^ñwÁýDÑ®gÔ~/|Z³»Óµë?ˆú´²Íqa¡C§Üèöo.ªb¹xN3ËjB3’¯‡›„'Qº“8¹ÎÜô!¨ÆNÎJö²»²t±m.I«µ¾K]»+ÚmîÒÛð>ТŠ+Í7 ù ÆŸò~ÿ³Wýší½ÿ«›þ ó_^×È^4ÿ“÷ýš¿ìпmïý\ßðOšúöŠ( ¿i_ù,ßðOŸû;ßë~ÛÕ­ûeŒü Qëñ¯öYù´+'ö•ÿ’Íÿùÿ³½ñ§þ°Gí½Zß¶W?züký–?õ¨þW§’ÿÈç)ÿ±žÿR©b¿Ý±õâ·þ›‘çQ¯§Ðÿ_ë^{ñ³Áž0ø‰ð[âÿÃÿ‡ž6ºøiãïü.øàßüG±I$½ðŒ|Oá=[DðϬã…ã™î¼)­_YkÖë‘ÊÓX ÕÈaéQ¯§Ðÿ_ëTüCáxËÚÿƒü[¢i~&ð§Š´MWÃ^&ðæ¹coªhž ðþ»a>—­hšÆ™yÖšŽ—ªé·w6……ÔRÛ]Ú\Mo4oŒ§öªÏš2޾üeÜtjß»‹wÞ:¦îµHùh{¼¾M7¥õ^OGè'ßðOû_~É´ÂOØûö‡ý€ü%û:ÁJWöøïá¿Ù7öÂøkâïê?³·íÕ⇿ ¯5Ïkÿ´=¯¼cà™¾*ø®[{ /ƾ(‹òx—ĺN£}>±¤¯Á]\ðÅœþ¡ðëþ ¿ûA|oøe£x“á/ÿ‚+ㄟðJÏÛöáý°ô¿xgâ ݇>7ü—Æÿ ¾ü,ðý•ŸÄ½&ûÂZ^¿ñÀZ—‰éúÏ­ Å6Þ>ñ¬×þ=ñ­ÁKy´'IÖü9“c¤[ÜØüuJXÌ¡J8ŠT(JU”êsB„!NŒè:•ýŽ5k:”§JsT•IáæœÔ9MúQ•®Spœ¤”4´›”œ¤¦£9¸ÆÓŒÒæåS^í®’÷߀?ðX?Úþ+¨¿i/†ÿõŸ^Á|/ÿÒmþé¾;ðÞ½a¿—câï„WóøÓÅþ8µÖÛÄS¤š÷†u?H›ÂÞÃá=E¼y=ƒø»Qý%ÿ‚zübýµÿh/† ¾>þÐ6_²Ïü)ŸÚötøMñßá¬í~,ø[âwÃÝâ~“iâõøaã Æº¯|)ãý#Iðf¹¢Jß´|>½ŸÅÚ®Š~¦—%§ˆcî> þÅß±6¨ü*øóð‡á׆õŰýˆüûü0ñ}¿ügã¿k±-¬ú_Œ¼ðê+_ñn¿àßxoP隤~7Ö´½kÆž!Ó&[mKÅšŽ•w5¼Þ_ûüÿ‚nüý¥?i„ß²W‡?³?hïÙËÁß¾|sÒõcãÇŒµ„_ ¾*iZÏÅ¿ƒ_ ¼âŒº§ˆ|-á¿…º¥ˆÕ|Wcð×àf·¼9¨E kz«ÃalGNT}¶&!'8Å{W&â§xÞ—ïýØÕ|Ç’ ž2j6zrSpƒ‹÷nùtVi?µîûÎ6vw~ì’º¿ó7û~'Â7öÌÿƒ”¼Mñö6¾ý¡µë…°…~þж~ø9®Áûøëâì}uá_ üaÖ"þÕZ·Š|9ðOâw‹ô[|ñGˆ>øÏXøÕñ[àÿˆü?ñ?ƒ?à¿à­<1ûUxâJx—E‡B áíà:/†5ßê~øà¿}ºë⧉¬%¸Õ£Ò×Á0Gà-SP“ÅzG¿ðYOÚyAcàO€ÀÄ4gþ & ðÇÄí?€óÿ Gð¡É  ÿÉB$¨9ýý»¿à—ß³÷íeû>øßÁ¾øWà|kðçì‹ñ·ögý™|S.¯ãx3áÕ‡þ^ø_Á>ñ>“ðîa§ëß |!â«O x“Kðψ<ã}3Áº—‡ì|Oàÿ Åâ]:ÊcóOìõûÁ<~=øg¿±OÇ­Ã?ÿl¯ø'?ì û2~õÀðfµûCø C²ø{ñƒà&‘¨iÞ éÃá^ñkÀ>2>Ö>"xÙ5ýcá·ˆ ÒüW§Ã¯ˆöÖ/kÏ[ëJ¬—¶INÞ͹r©4½è$¡u.XÊVŠ•ÒrÓ޵Ǒ¥îß]l®ÒºÕë¶¶¾›íµþ[ø­ÿ°ý®môÿØçÁ?>øKÆŸþ2Á$~ÁM~ xoógíYûEÛ|BñgŽí¼=¥hß³·Â_þÎúž»âσvÞ(ñ“ø‡O¶øÝñ“QñoÃÅàÝ3Å×šŽ©o>­»ûDÁj?kß‚?< ¦|@ýŸ|!ûþΞ;øwû'øãÀŸ¿lo‚_´£øǺ÷ÆxcÄ_þ|Fý¤þ]ÞØ~ÈŸ¾ ê>#Ÿá½Ž‡ñölø°š‡4+ýGâ ß <1u¦_]~¹|Aÿ‚Yþ¼ðwá×>½Ç†>|´ý›þ/‡þ(üeð?ˆ->ZøJð³|ñg<ñÞ3øð»Qд]>Ë_ð?ÄïxÃþ$xç½ñŸ©ßß_Ý\Ûøƒÿ«ý€¾)ø¢ÛÅ>/ýœô˜x{á/„õÏ øcÅß<ð¿ÇÞø qeqðSß¾xÆ~øMñ“CøRÚn›€4¯ŠÞ ñ•…lôí?OÑà´°±µ¶‹Ž¤q6¾Nü¶m´­¥$×-ìÞº8¹håÖ/Tà¶ŽšéeÝm¯“ûö>=ÿ‚!ühý¬¾7ÜÿÁRn¿jOŽøÍ/Âø*—í)û9ø to‡zÏ€í¼#¦üÒ¼£êV³Ô>&øöÓ@øI¨Z_øvo‡ÿ š CÅ>Õ­|o®øßâïÆ=sÇçQð×îäkéßôÿ?ʾ{ø-û/| ýŸ ÚxWñ¡à¯ø¯Åv¶–Mã]_Á>ðå×ï4ý7RñkëZŽŸeuÑq¯§Ðÿ_ë\Îñ‚ŒÞ­¶Ûv»i¶õÛ¾Ök]ÊŠ»rµ•’JÖè¯ú«õÖö??¾ ÁB¿k‘é§xsÿUìÁ_¥µù©ð cþ û]M;Ãú¨¿f ý+®Ã\MûÐÁÿêj?Ãÿ·êÿéÙ…Q\F¡_•³2gösøO‚Ÿ óôÿ„Aþç¥~ªWåŸìÊ¿ñŽ_³ÿý‘?…Dÿá  àŸzý ]¡›¾œÙåŽüdø•àx´oxÙõ-KF¶·ž×Ä.±«xnÿYÑ¡¸Õ4m/ǾÓuojÚ½…µÝΗ¦x–êþ;K£oöy?¿gÚG]ýš|9ûqø_À_ðMo~ÂðU¯ÙÛö!›ãΧð£J»ñ†µûþÒ_¾x·N_ø¡àÿ‡ ¾%øCÁ:ï‹oï"Ö|?¥xº«üG:­ô~—âÇŒ Ó¼_ák_ê?âïÁ¯…ÿ´Ã?|øÓà}â?Âÿé_ØÞ0ð_‰­>Ù£ëV so}n“"žl:n‡4c$”­nhÅÐ[¶Ÿ¼£WKo8ùµÑ~ͱ~ØVÐøßOý­nÿfŸMm©è3|4ñ§ìå§üPð„:þ‡{ ÛMâk|3ø—ªxå¼#ªxsÄâëNÐuü[ñ½Ÿ´-õ›ýÀ7ñI¡OüÉxëJøsñ×àoü ûR~ÒºýŽ™û^~Í_µ'ísð·ö5ñæ»â¸|7ñ/àO†ÿdÏ€öŸ¿f ?ömÔî/lµß‡š—.4xÇĶÿgÓo¾)XZø†ó_‹Yµ¶ÔeÓ¿ìÙû~Ï?²m¿—àg/|9©|JÔô_â‹$xOáÇŽü+࿉>(ð¥Ü‘xÄß<9âmkÂI´zí”6°E>.•Z´éÇÜ”¢ê§ •'8>xÎ1Ÿ´öjR•;§*wÕûÎKšZR”c'ºMGXÅ'­ÌÖ¶ïÛD›Gæÿ‚¹~Õ-á­GÁŸ ®o¼;ÿÔ·üÎöïžñŒ~(¹ýªt†uÏ‚üJºOü9¤…4šÇ…´¿è^1”“‹/évÆ+(9ÿŠ¿ðZßÚæÞÃö:ðWÁÞñ§Ç?ŒðIO‡ðS/øsß³oíWûE[|Bñ_Ží¼=¥hß³¿Â_þÏž»â¿ƒ–Þ(ñ‹ø†ÂÛãoÆ=GÅŸ|ÿƒ´ÏÜÝê:¼ú·ìgÅø&wìsû@ ^üyøc}ñ[Å~ýžuÙjóǧümà¿ø×ৈl#¶×ü!ãñðŸ^øy x¦ÒÿTþ:;Ým|ãß+ÆŸ­|%â+6þÉß¿à–ß°§Å_üøyãOÏqá­?g†«áÿŠ|â ?ÙþÓÃú_…›à·‹|gàˆ~ñŸÄï…ú–‡£iözÿþ'kþ1ðï‰^9ï|A§êw÷××W>}Hc-$««h¡vùž±r½¢ù]“Õ9ô[7mã*m«Åùè»zê·}>v?<¾6ÿÁ[hŸ|gø»à­à׃<¡x;þ ïñ÷ü·FðOÆOøú×â烿h¿ø³UÒ4ïƒíâñ‡„"·ðw‡ím`Ñüqá¼)áÅâ{]N4ñ†Šé¶Þ%ñWþ gû]Úéÿ±¿‚~| ð—~:üeÿ‚Gü:ÿ‚œ|Aðç‡?f¿Ú»öŒ¶ø‡âÏ[x{JÑg_„ž ýµ={ÅŸ­¼Qã'ñŸmñ¿ã.£â߇ŽÿÁºg‹®nõ-RÞ}[ögãü#öý ®ôÏŠ?³öƒ©Iᯀ÷ì­¤[ø_Å>iÑþξ!´[kƒ×ºOÃ/øCKÕü£K¾¯àÍ/W³¾üMc¦ø·À’xsÄúe†¯mÄ?ø%ì!ñ_Àÿ>xÓàcÜx_à/ÁK?Ù·áªøâÆoø†Óö}´ðþ—ácðOž4ð7ÄOxÓâwÂíKBÑ´û=Àÿõÿøwį÷¾!Óõ;ûë뫞Ëï~ö6¼Ö—‹–Ð÷of•¯}¬®ÒÑ8$—.ß½uùŸ_üñÖ§ñWàÿŸŠß|Sð¿Yø‘ðÛÀ¾>ÕþxëN¾ÑümðïRñ‡†4¿ßøÆ:N¥e¦ê:_м%u¨Ë x‡NÔ4í>úÇVÓï-®ì­g†H#ù3ö¥ý¬¿a¿S¨üFÿÕ•û1â¾éðŸ…|9àŸ xwÁžдŸ øKÂ:‘០økA°¶Ò´?xw@°·Ò´= FÒìã†ÏNÒt2ÒÛOÓ¬-!ŠÚÎÎÞx#Ž(ÑGÃ_µHÇíeû zGâ>ðå~ÌçëšÍI¿j›»ú®3™ÿÜ¥oëoÔ~ÿçå+//k {ÿ^géÍQ_>w|…ãOù?Ù«þÍ öÞÿÕÍÿù¯¯kä/ÉûþÍ_öh_¶÷þ®oø'Í}{EPÈ_´¯ü–oø'Ïýï?õ‚?mê×ý²yøÿªÙû+ÿëR|¬ÚWþK7üçþÎ÷ÆŸúÁ¶õl~Ùü‹þËoì¯ÿ­Iðn½,›þGWýŒ°?ú•HÃþíˆÿ¯¿ôÜŽ5ôíÀúÿŸçWã^žß©ÿ<þUkÃîO_óïWã\~©ÿë~}+öZ’½üô^¯3å’¹¡¬ü"ÔµojúõoãÜŸ¯oËú{ÕøÓ×ê ÿ>õãcèýfš‚Ÿ³”f§N¥ªsÓ’Œ£ÏMÓ«FPšæz¹I8¹BP”dí×FJ“O—™5iGKI6›MJ2M]]+&šM4Ò?™ÚÇö—ñÖµûGþËéûwüfý àž?²oÄ_ø%~£ñÂ]Cà§ÄψŸþÐ?´?ƒþ$þÏúüwÅŸüY·¯ü#ñ5?|<øeàë¿üxø£ø{áÌZø3ZñMž¿ð¯Æ>Ðüc«øÏ\mcá–Ÿ­hž‡Ã_Þ4k“Ÿ^õ?çޯƸüÓüÿ*ñ18Õæ¾!ÞSHÍÁûHÉákaâÜ£V1´%WÚÖqqp¿½ÌºéÕQ²ä[$Õ×-”á7£‹kHò»·¥¥ÒÇñùÿý£n/ ÿÁI¿jxcöŠ·ým?aKÏø'ý~6~Ô>ðGÄè¼K¨é÷_l¼+û#þÎ?³ퟷ†§âï\k_ ü}câ;-;Rø!¡É¡ø¿N‹PÒlõ{­æ¿ø83âÏÄ}^÷þ 7ð—ãwÆ‹>ð¿ƒ>Á3ïÿaïún»¯éß ~-|;ñ?Åß j¿´§5ßÚÙÝøcÄ÷~øµc§i²|FÕ?³µ¯ êúW„¼i¯Çg¯§‡µßîš%Æ?/©=ßéWUzÏú×|çÑúÄ­^u%iEÉF3xò5í"¤£í¢àí½•4ù£¥´*(ò>Ex¨­¶äÕhìß+»×â“V»¿ñ7ÿlý²?hŸ€>#ÿƒŠü!iûIþÐ? ¿¯ëÓÿ¯\•píÊRöò³”Ü=Û8©ýnönm'­hì´¥moa5eîôK}½ŸKmû¾ïVÏáãã‡íEûPøSþ ÿŠøð«öؽñ_¾xÓö›öaø‰ð#ö°ý°?hŸˆ_õ_|Uø)¤|uðu§íçñ‡àìå«|VƒÄÉyc«Ýxáψüg?„ñÇ‹þøê ˜ì´Íoûaø]ðóMø[à}ÀzF»ã¯Øh1Þ„×þ%ø÷Åß¼oªK¨êwº½Ýλãok÷‰µw7š…ÂXÃy©Ég¢é‹e¡h–ºv…¥éºm§j‹È‡_§ÿ^¯F¹Ç¿'éþr8¸6ÜܽĶi$§96•ådù’²ÙA%¥’móY%moø%®,ÝüßÎh׿sÓéþyúUø×Óè?Çÿ¯õ¨#\ãßì_óíïW£_NܯùþuËR[ùêü’þ­ç©²]OøoÍŸž_Æ?à¡¿µØÿ¨w†ÿõQ~ÌúU_šÿF?à¡ÿµàôÓ¼5ÿª‡ö_¯ÒŠåÇ;×O¾ÿòÎt~û~¯þ˜QEÆjùyû1/üc‡ìüê‰|)?R|  ööþuú‡_˜Ÿ³ çöoýŸ8ÿš!ð£Sà='ðÿ=+ïx&\´ó_ñeúwÓcÆÍ·ÃzVüèžëcßúŸþ·øV„IÃõoóÓð÷¨bN˜úêŸëéZ'L}õ?Ïõô¯®«=ú÷ó}¾_ÒÐó£/7øy_¥É£LséüÿúßáïWâNÿO^½‡áßÞ¡2F:ÔÿžOøV„iŽî{Ÿéÿê¯2´õz÷×O›ùô:!z»~Ÿð{|ôdѧAøŸséøö­“úr~¾Ÿ‡ùëPD8çúÿõ¿úþµ}8·_þ¹ÿ=+Ω?—迯ø}ˆ«/_ëú]dÑ&y?äz~?Ë¥]wåõÿëTH½s×üûUøS§ùãôäÿõëΫ=ú~‹þôô6„z¿—ùÿ_©Ÿý~µv$éíú“þ¿JŠ5ÉÏà?©ÿ>õ~$é þ§üû×™V{þ_’ù/ëCX«»}þ„Ñ'OoÕ¿Ïô¯ÏŸÚ´cö²ý†?ì#ñ#óÿ…•û1güúb¿D"NŸþ§üôæ¿=?kÚËöÿ°Ä¯ÓâWìÓùšå„¯:‹þ¡±¿7õ:ÿ‘¬¶ý|¥åÿ/`~—ÑEãA_!xÓþOßöjÿ³Bý·¿õsÁ>këÚù ÆŸò~ÿ³Wýší½ÿ«›þ ó@^ÑEòí+ÿ%›þ óÿg{ãOý`Ûz¶ln~ @=~7~ÊßúÔ¿ëö•ÿ’Íÿùÿ³½ñ§þ°Gí½_Mø³Áþñï‡ïü'㟠øwÆžÕ~Ëý©á¯hšgˆü?©}†öÛR²û~¬ZÞiןcÔlí/í~Ñm'Ùïmm®¡Ù<H½XDp˜Ü*QsއÄJiJQ£ZbÞ‰ÉE¤Þ—z™Öƒ©J­4Òu)Î ½“”\S~JçÊ‘¯§AÓÜÿŸ×z5Æ=¹?_óü«Ëµ¿ø%ïì™­k:¾³ÿré?ÚÚþ§ý•¢x{á­†¦}¾î[¯ìý"Çþü¿bÓ,¼ß³XZy²}žÖ8¡ófã™ÿªý“?è^ÔðSð×ÿÝ}̸Ç+ÿ²â—Oùríÿ•䬲ªÿ—”ßþÿÈúýþGºF½úŸ¯§þ¾ ^EàçüþŸã_>ÿê¿dÏúµüü5ÿçwGü:«öLÿ¡{QÿÁOÃ_þwuÏ>)ÁNÿ¸Å/•-;ÿËÕ¹k/ª¾Ý?¾_üôþã铦;þƒ¹=:õÿõÕè×${`¯oóô¯˜áÕ_²gý Úþ ~ÿó»£þUû&н¨ÿà§á¯ÿ;ºæ—à¥Ýb•ÿéÝÿ¹Ñ¢ÁUOzoç/ÃÜ>®qøqø÷ÿ>þÕr$Ï'üOÇùs_"ÿê¿dÏúµüü5ÿçwGü:«öLÿ¡{QÿÁOÃ_þwuÏ,ï+û¸¥}?…EÿîÁU«ÞŸþ/þ@û'>>¿çúV„kŸNõ?çÞ¾)ÿ‡U~ÉŸô/j?ø)økÿÎîøuWì™ÿBö£ÿ‚Ÿ†¿üîëžy¦ W׺“Óÿ ‘j…T­jz~_ü¬ûŠ%Î=ù?Nߟõö«ñ®~§ùž}ø¯ƒ¿áÕ_²gý Úþ ~ÿó»£þUû&н¨ÿà§á¯ÿ;ºçž3+ûø¥wÿ@Ô^¿ÞבQ¥U;Ú›ÿ·åÿÊÐ×Ó¿éþ•_qϧüÏ|×ç‡ü:«öLÿ¡{QÿÁOÃ_þwtê¿dÏúµüü5ÿçw\ó©‚•ÿ}ŠWÿ¨Z/oû4QªŸÃOÿKÿ•uØgÀñø(íz?êá¯ýT²ý~“WËcÿ„³†¹©k¿ Z±“UÒ¯4›½6uðͶ’É}y£ÞO|,ô èluÚ„æY¤Ù O•…¡úž¹15#V¯<9¹U:âä”dÕ4èó8©MEË“›•J\··4­w¥8¸ÆÎ×rœµ^ôå+^Êö½¯e}ì¶ (¢°,+ó7ö_OøÆßÙïßàÂÀ dþ=ýuúe_ |Aÿ‚qþÉž?ÖmµŸøUÞðOÙ´ÈtÏ쯇þ øká½ãÉ»½ºþй±ÿ„÷ÍÔåûoÙ¦»óW̵´²‡ËFçú,‡9£”ÇÔªÕúËÃ8º|žï°X„ù”¥‹Û+Yý—}ÑÃŒÂËé8Ê1ö~ÒêW×Á«Y=¹5õ;h“¦;þƒ×üÿZÐqÐuàoþ¿_×5óÿü:«öLÿ¡{QÿÁOÃ_þwtê¿dÏúµüü5ÿçw^Ìø«/ùqŠ_*/Õÿns,¾ªwç¦þrÿäO£âN˜ÿ>§üý=+B4ÉíÓüûu¯˜?áÕ_²gý Úþ ~ÿó»£þUû&н¨ÿà§á¯ÿ;ºæŸ`§Ýâ—ýäô]?Žh°USÞ›·÷¥ùò3êø×ÀÔÿ­^‰02Éÿëõëä/øuWì™ÿBö£ÿ‚Ÿ†¿üîèÿ‡U~ÉŸô/j?ø)økÿÎî¹çàåw¯ÿN©mÛýçïù—õZ½éÿàRÿågÙQ&yõúôôúŸåÒ´zç©ÿ>?ýuñ/ü:«öLÿ¡{QÿÁOÃ_þwtê¿dÏúµüü5ÿçw\Ó̰sëŠ_÷‹ôÿ˜¥·âiìjö§ÿËÿ•ŸtÄ;gôçúUø“8íéì=Ïõ¯ÿáÕ_²gý Úþ ~ÿó»£þUû&н¨ÿà§á¯ÿ;ºæ–+/ùyŠ[ÿÌ5«ëþöTiÕŠøiÿàÉò£ô"$Î;z{_óýkóÃö¶ý¬¿a_}GâV>ƒâWìÁSÿê¿dÏúµüü5ÿçw]ŸÃïø'Oìíð¿ÆŽ<i¯i]/Vµ–ÎßÀÖ túN©e«Ác&•à>ò}:{Í>Õ®­£»€È"VI#•#•1öøXsÎ1“£^”a<=:q½j5(ÝÍbª5Ëí9´„¯ËË¥ù•òTvMA.hIµ96”gÚÞÎ7¿*ZµmüŸÞQEy¦á_!xÓþOßöjÿ³Bý·¿õsÁ>këÚù ÆŸò~ÿ³Wýší½ÿ«›þ ó@^ÑEòí+ÿ%›þ óÿg{ãOý`Ûz¾½¯–¿iŸ‡¿|]ª~Î7ø1¢ü8ñ?Š>üvÔþ'ßø[âüMðË@ñ­~Îÿþ ÝYÙø¿ÂŸ ¾0j6:ÅŽ£ñƒJ×míæð]Å•ý–‘¨ZI¨XO-´­ÿ §íïÿFÕû!âo|fÿé|Ð×´WÈ_ðš~Þÿôm_²þ&÷Æoþ—Íðš~Þÿôm_²þ&÷Æoþ—Í}{E|…ÿ §íïÿFÕû!âo|fÿé|Ñÿ §íïÿFÕû!âo|fÿé|Ð×´WÈ_ðš~Þÿôm_²þ&÷Æoþ—Íðš~Þÿôm_²þ&÷Æoþ—Í}{E|…ÿ §íïÿFÕû!âo|fÿé|Ñÿ §íïÿFÕû!âo|fÿé|Ð×´WÈ_ðš~Þÿôm_²þ&÷Æoþ—Íðš~Þÿôm_²þ&÷Æoþ—Í}{E|…ÿ §íïÿFÕû!âo|fÿé|Ñÿ §íïÿFÕû!âo|fÿé|Ð×´WÈ_ðš~Þÿôm_²þ&÷Æoþ—Íðš~Þÿôm_²þ&÷Æoþ—Í}{E|…ÿ §íïÿFÕû!âo|fÿé|Ñÿ §íïÿFÕû!âo|fÿé|Ð×´WÈ_ðš~Þÿôm_²þ&÷Æoþ—Íðš~Þÿôm_²þ&÷Æoþ—Í}{E|…ÿ §íïÿFÕû!âo|fÿé|Ñÿ §íïÿFÕû!âo|fÿé|Ð×´WÈ_ðš~Þÿôm_²þ&÷Æoþ—Íðš~Þÿôm_²þ&÷Æoþ—Í}{E|…ÿ §íïÿFÕû!âo|fÿé|Ñÿ §íïÿFÕû!âo|fÿé|Ð×´WÈ_ðš~Þÿôm_²þ&÷Æoþ—Íðš~Þÿôm_²þ&÷Æoþ—Í}{E|…ÿ §íïÿFÕû!âo|fÿé|Ñÿ §íïÿFÕû!âo|fÿé|Ð×´WÈ_ðš~Þÿôm_²þ&÷Æoþ—Íðš~Þÿôm_²þ&÷Æoþ—Í}{E|…ÿ §íïÿFÕû!âo|fÿé|Ñÿ §íïÿFÕû!âo|fÿé|Ð×´WÈ_ðš~Þÿôm_²þ&÷Æoþ—Íðš~Þÿôm_²þ&÷Æoþ—Í}{_!xÓþOßöjÿ³Bý·¿õsÁ>hÿ„Óö÷ÿ£jý¿ñ7¾3ô¾kÁ^ ý¦¼]ûMxã?Æ| øqá‡~;ü0°°øañßâÆ=Ä:ÿÆ?ˆ³gŠío/-|Wû6| Ó´-BÓ¾j°Ü\Cªë··÷ºîŸz}´×7,ö­Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ÿÙbuteo-syncfw-0.11.10/doc/src/Buteo_Sync_Solution_-_End_User_Usecases.jpg000066400000000000000000002451351477124122200261520ustar00rootroot00000000000000ÿØÿàJFIFHHÿÛCÿÛCÿÀº­"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?þà¾:|t‹àŒ_ ­­¾|Gø»â‹¿åøaàŸü0—ᥦ¿}¯Ú|4øñgP¼¼Ô>,üGøWàÝ;GÓ¼ð¯ÅwW^+KÙ¯SO°°Óï'¼?8ÿ†•øÍÿHùý¯ð´ý‚?ú7¨ý¥ä³Á>ìï|iÿ¬ûoWçïíáÿwø‰û!þÕ?¿f‡¿²F‹ñÑ~ÿÁ3|aÿ;øã}gö‹ÿ…Bmþ|8ø­â_‡ž<ð^‰áŸøR_Æ­â[ #Ãâ?Ý7ˆ,¡×õ+ø<9ue¡ÚE?‰”ô þWã7ý#çö½ÿÂÓöÿèÞ£þWã7ý#çö½ÿÂÓöÿèÞ¯ÊÁÀ_ 4±Ã/ |´ñ_€loþÅ>Ó¼AÅôÿ…ÇðÒëöñøg¦üKø)â¿|ðçÃOxÃ’ÛU]óJøñ÷á·Æ=vïCñgŠ|ðÆ? 4k?k?"þØð[¿ÚçâOüÛö¯ÿ‚ƒ~É¿uÏٓᾤøZý’?k‹ßˆ~/Ýø«ì·ÿ†ÿeoˆ>ø…û>øïÁo©|8ñî­á½3ÄÞ!ÒídðÏÅχ«áZÜEñ3Gøƒ§Å¡Ú€B?ðÒ¿¿é?µïþŸ°GÿFõðÒ¿¿é?µïþŸ°GÿFõ~hø7þ á²·ÄÚÛÀß<¤|<ƒöÆÔcÏ‚šÆ·ÃŸxóÅ:FŸ};ê~h¾!üYøwâÏê¶VÚ7ߟ ¾~Ñ|Mÿ ‡5ÝsÁ>ðÓxŸWð¯Í~ÿƒ‘“âçÁ?|QøûßøËÄ·ðNOÚkþ _ñ“Àþ;ý ì>Øü5ø1û2üeñ÷ìý¯èñ}ÁÿÿÂÏñ_ˆþ'|6ñ Ó`ŸÃžŽÏÁ‹câ)Ò}^ëþXÀ?p¿á¥~3Ò>kßü-?`þê?á¥~3Ò>kßü-?`þêü`Ñÿàâ=KÇ$ðw‡þÿÁ=¿hŽ×ZWì÷û |xý¢ô‚:ÇŒ^5ø_ÿ Çð§Âÿt/ ü:±øOû,øçÀ<†¿þÀ5O…þ6êÿ¶ï…> þÌþ>øûðÞçã…Ïìóð;â×Ãï|1øùû*þÏ·^ ñ7‹~ü øMðçÄ? þ$êµg‡|Eá3¥üÖ>4Øë|qáh¿üYãO|?Ò<'á'üÇö‰ø·ûO6‡qðá—þ |ýž¿oo~ÐÚ=ׯÏxsÄçÄ/Ù³Â?°_Åï x“á½ïí-û(þÌ—6>‡ÀÿµßÃé:ÏÆÿñ,?¾*üA×õ-'Áÿþk´êßü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_›Zÿü+â¿ÄÙ þ ¯¬h_¬>~П±ì/âïÚCáÿŠü7©üCø“àˉügð¯ö›¿øSâ]"OÚ#ö^ýωµŸxïötñ(ñ-Ãoˆo¦‡OÓt¿ˆ5•…áÙ¦âÚÇÇFûÃwÏ{û9Cð²ë\ñŠ¿g=?JñDºf¡ãŸLôT­{-Uí%m$šè$î¯f·Ñ«=_®»«3靸i_ŒßôŸÚ÷ÿ OØ#ÿ£zøi_ŒßôŸÚ÷ÿ OØ#ÿ£z¿|ûU~Üéñ‰þ#ñ§í®ßï¿àªß¿ekŸø«ÀÿðLKö ´øEkÿ.ø‡ûxÁ:&“ðö þ Ecâ{OðÂ_øçÅpÜXÙür»‹Æ¾1¹ñ†šóSõÿÚ3þ §ûCËðþ ãoÙ›àgÃ[oþߟ ?eoÚ?W²ø¡ñÃVÐ4/…Þ øûZÁ2þ ë¿íõMöuø—qã{ˆ~ß:ÃÝOâGü#^ ¼øoá=+Äÿ|9á_x¿Ið÷Ã]VF~›ÿÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕðv¡ÿ“Ót_‰_´_|XÐü¨Åã‰>ü+º´ñžâ¯¿|3㟠Â'w¥h·ºŸ©xoÆ>2ðþ¿§k sþWã7ý#çö½ÿÂÓöÿèÞ£þWã7ý#çö½ÿÂÓöÿèޯϟ‚_?nx×Âö?´‡>9/í âß |VŽÛö^ø¥ð£ökÑÿc<+ð¯Æž>ÑþþÆŸµÀ ^|Cøu×¹ño‡¦ý§ i>&Ò5SÁ~†?àªÏÄ_àíì¿û=韴¿Ž?~ |ðw‰+Ö?j_jŸ´®àÿø@õþÕŸ´Çì}sá/€ž)ÕþÉáÿ#ñ/ÄÿØûãî¯àÙþ ÜþÍž¸ðNƒá]SÆ^*ð6µâø<;§{oíÏûWÙjŸÀ»ÿŽºÁ×_ãÇŠ¿e/ ~Ìþ4ý¤t-jðXøLÞð·í]¢x·áÞ©ðºê9~$ÂÍÿ„SÀþ4ø´š½¿Ã¨<¡Ç¢]øÇVÓ=/þWã7ý#çö½ÿÂÓöÿèÞ£þWã7ý#çö½ÿÂÓöÿèÞ¯‚ÇüÃÆ_ þëšÆáè?l~Ì¿³¶£ûO~ØßuûøeOø/àí·ÆÚ#á£èš'À ¼ãhüeûN|9OÙwãž‹ñ§á–§«|ð忾ë±igÁš¿Œ¼;ð¿F£ð·þ uã¯üÿ‚—ø·â7‚ÇÄíöðí¯ûJiÚñ&à¯øYøyû}ÿÁZ¾ ü,ø!ö]À3Yx<ø'áŸüÿÂg¶ñ–³âcâá¬x‡A¾ñƒ©ê¾3ýÿ†•øÍÿHùý¯ð´ý‚?ú7¨ÿ†•øÍÿHùý¯ð´ý‚?ú7«ò£á—üïö™ð„ïÿh¿†:·ñÆÿà§ ~ü8ø;ñ—AÔþøÏÅÿà¬ß²GüÏötð—iôMjÃÃÏð‹ÃZÇÆßŠ_ ¼kñânàOÙãé+/ø(×í/â_Œ‘~Ë>ýŒþ뵇,ÿiõø¯á}gö·Ô<;ðo·ìñáÏØ[â‰ÿŸÅÈÿf}kÅ><ðçÅŸ‡··ÂÍODÖ.þ ø3]ð§‹-§ð¯Š|#m¦ožûþWã7ý#çö½ÿÂÓöÿèÞ£þWã7ý#çö½ÿÂÓöÿèÞªžý¼fGý“g_Û#ãÅ¿†ß³ÂÚWá¿Â?ˆ>Õh¯‰~øWe߯O†°|Sð×.üCâ¯i¾¼ñ´^:Ìú6“«_Mq­ßX ?N¸¹äŸø(WÅ/Ú‹Hý©ÿd¯‚Ÿ³Þ»û_Ûøâìÿûf|Rñÿ†b3þ Û/Å=_Xø?ñö ðŸuÝYÿ‚iòü2°øáÛ/Ž>;Óõ]3Àz­—Žõø£Â—M§ê~Ñ5k ìøi_ŒßôŸÚ÷ÿ OØ#ÿ£zøi_ŒßôŸÚ÷ÿ OØ#ÿ£z¾2ðçü#ã—„5½Ož-ý™|Iª|/øûK~Ä_±?ÆŒßþ<ü*²ý¤'ø§û^|)ýŽ5 ø·]ø)ð;á%ÇÀ=kÄžø§û`xÂìþüZðÏÃ}>Þ cÇ?åñ¶ŠSÀzì×ûp|vý¥?jOÙ2k¿‡:Â_Ù“ö•ý…jïÚ£áfoãŸ|BÖþ)øWFø«ÿÿÓþxïÇÐIðûÂþ#ø?ñIø{ñÃÇZž·ð·Ãþ#ñ§‚ÓKø¥ kx×Å>2ðåÞðüëÏøi_ŒßôŸÚ÷ÿ OØ#ÿ£zøi_ŒßôŸÚ÷ÿ OØ#ÿ£z¾?ÿ†¡øñÿ f>*ÿÂpGÄ3ÿ}?²ð׆<üð^ÓûŸ ø{ûû.«|;ÿ„œi¿ð˜ù øÛþøOGü%•ä¾ÿ‚Íxª/|yñÇÅÙûáÞ­ð™ü=¤i?4_о(ñÿíi㿈´OƒÿfŸ„Ÿ¾2ü´ø |TðÞ±ñ#Ç;ѵ Å?³7†?lo…úž‘£x¾ËÂ^>ñ‹-|áŸ~ÃJüfÿ¤|þ׿øZ~ÁýÔÃJüfÿ¤|þ׿øZ~ÁýÕòÀø)ÆŽÿþüÿ†3ñ'ÃO|FÓÿiøòûâæ³ñ÷à¦áÏþÍž0ý‹ôoøãáN‰ûA~È_¾4ü]ð犴?Û|-}âoƒ_mî~&ü2ñ€n&‹Ãw—<=õoƒ~)xëSý½ÿioƒž»æü.øûþÄ?¼#áÖÓ4hHñ×ÅÏŒßðP |I×¶áÓâׯÿ·¼=ðGáU‹éšž­{£hÃÃKw¢iú]î¹â\ÿðÒ¿¿é?µïþŸ°GÿFõðÒ¿¿é?µïþŸ°GÿFõ~`þÆßðU/þ6³ýƒþü^ø¥ëïÇ/ÙOöñ׉~=xóâ¾ðsÄÿü}ûBü ð‡¾'øëàÃøSzWÀoèß üE}⯈ÿôïÿ~2Ú¯‡ü^~þÏ~*ÐôÿÂ]Ó|$ÿ‚ÓjŸ|∺oì]ñÆÃÀ¾,ð×Âmwöañv¯àïÚ;À^ ø½ª~Ð~~ÏŸ|㯉ÿ?eO‚ÿü⯉^0øïðóÄšeÏÀï‰_µO‚¡ømkãÏ'Œ¯,ü'ikâÑøi_ŒßôŸÚ÷ÿ OØ#ÿ£zøi_ŒßôŸÚ÷ÿ OØ#ÿ£z¾`øáû{þÒß¼!à»_~È´ï^*ñÄ‹4ðe—í5âïx ^ð‡Ãˇ÷cÆ_ /þ ~Êÿÿj/ˆqx’ûâ.á›+ ïØÛÁvžñF…¬ØüCÖü¦ø‹á±ñOÍ>ÿÁ\þÊÿþxëâ§ÆÛ‡ž=ñö‰ûN|9Ò>#]\üðv­ðºoƒ_­þXê×:wÅO ŸÚcÂ.'ð—Œï¾üñΙká+¿}Õÿ +ñ›þ‘óû^ÿáiûôoQÿ +ñ›þ‘óû^ÿáiûôoWæïßø,ïÄ?‹? >.üeøuÿúý£<_áàd´'ÀmBÏÀµ/…ô?Šž¾ñ‡¼=§ÛøÇÆ¿cχ~ÒüMqá?Yüb‡Dý—'ø÷öý¯t|8ð‡‰|{ã=sþoØkTþÆðŸƒô[ßø‹UþÌÑ¿m-GXÔ³´}:òóì:VŸ}©]ù?g±³ºº’(¯ÿ†•øÍÿHùý¯ð´ý‚?ú7«â$ø³Cø{áÏx³Â¿³×íûa|øw¨x›IøQàŸ‡âñ_þxVÇYºð¯|/§jš­Î¨t¨.on ~ÎPÈ_ðÒ¿¿é?µïþŸ°GÿFõðÒ¿¿é?µïþŸ°GÿFõ}{E|…ÿ +ñ›þ‘óû^ÿáiûôoQÿ +ñ›þ‘óû^ÿáiûôoW×´PÈ_ðÒ¿¿é?µïþŸ°GÿFõðÒ¿¿é?µïþŸ°GÿFõ}{E|…ÿ +ñ›þ‘óû^ÿáiûôoQÿ +ñ›þ‘óû^ÿáiûôoW×´PÈ_ðÒ¿¿é?µïþŸ°GÿFõcêÿµÿ‹¼#/…®~"þÅÿµ/à øŸâ?¿†ãoøƒö9Ö´kÿþ%øOá7ƒ/5Í?áÿímã_I£Éã/èºÆáMvöÂÊâ{ó§Ë´Å~Õ¯¿mïù#> ÿ³½ÿ‚|ÿë{þÍTõíQ@!~Ò¿òY¿àŸ?öw¾4ÿÖý·«äÚ[þñðCö¾ý¾ïÿloÚ ÄúÏŒþêß°z~ĺ¿ìå¤]üEøršÄcãÆ§ñ–ëÇzÇÅφ<¯êÞÖ4gVøwâ?ƒú†®|/âmP»¸ñ«©ØLþ?H~Ú6^>Ô|wûYü0ñ/„<㙿kßaø‹Ç¾Ö¾#øONòÿa¯ÛJ]OûWÁžø…ð¯XÖ~×£¦¡ccö?è?ÙÚ•Õž«qý©kc>¨õÿð…þÞÿôr¿²þ!Æoþ˜5xÿ‹à’ðNÿüiðí¬~ͺ=ŸÅ_xëökø™á­cÂ^;ø¯àÚøëö>Ñõ?~Í#¿ø{à_xsáνyð‡Ã:Åÿ…ü5¿á=NÒO I†uk}KB²²Óíø­cþ—ÿ¼×|'ñÓÀÚ—ì§¢7„ÿhÝ+JðïÅ-ÓâGÆ.ÚO èßö²ðGÃÙ4¿‰w?<'ÇT?u/|Ÿá÷ƒu¯Ës¨k:ÿÚé_øBÿoú9_Ù ÿ‡ã7ÿLøBÿoú9_Ù ÿ‡ã7ÿL€8ÿÁ.ÿa_ˆ¿µ| KÛߊŸ´—ÃßÛÅ×6?~/h2GûNü-Ó#Ѽñ³ÁßðøÿK_†ž:³Òâ:ΧðÜxM<_nó§Œ ×¾ÓpeüØý¤?àÞ_Þ<ð7€¾~Ë^9ðçì»ðwÀ_³gÆÙAðV¯àŒ_õŸ x#ã÷|oñ↳áo‰z?íKðgâ'‰õ?xŸÇšŽ­‚?ioþÒŸ|?¬èþ×¼%ðwÃÚ½¾µu¯~³Âû{ÿÑÊþÈ_ø„?¿ú`ÔÂû{ÿÑÊþÈ_ø„?¿ú`ÔóŽ“ÿiÿ‚}¯‡> èþ3ø*ÿu?‚ÿÿg¬jþ8ø‰áý/â÷ƒÿe½+ÃÚÁ‘ûA|8ðG‹|3ð«ã¼Þ »ðÆ—¬è6ÿ|ã?DÔ`‹û"ÒÊÚÞÚÞ/п‹? | ñÇágÄ¿‚Ÿ´/øJ>|`øã/…¿|3ý§¬hŸð‘xâ‡5/ ø»BþÙðxƒHþ×ðþ¯¨iÿÚz«¦khûV™¨Y^ÅÌ^ÿ_íïÿG+û!âüfÿéƒQÿ_íïÿG+û!âüfÿéƒP)¥Á7?d 3Ÿ|)wàOøÅþ2ÿÂ> xûâíûIü^øß¨§ÂŸXø×áT'í ñOâçŒ>;ø&ÛáwŽ4ëOü6µð?Äo[x Çk7|'‘â‹«½Z|Mþ mû xn}fêÃàö·yqâ•øßާñ'Æ¿ž1—âu¯í'ð‹á_ÀßwÅÇñoÅ m¾.i?þüøL<]¤|K>*Ó5øBøµöHþ,C/¦ôoøBÿoú9_Ù ÿ‡ã7ÿLøBÿoú9_Ù ÿ‡ã7ÿL€5þÒ>5“ÃçÁ<ðç[×®þ7|Yñ/ˆ|['Ã/ø^>4—I³—Åú6¡©h ¤ø.ZxwÃ~·ðï®~Ô“k~ý–,n>%ø—ÂÚˆôO~̳|@ñ†…¡ÝøÁ7ÚÞ›ñËá+ø¯Äº?†µÿxâÿÁ^º¿†ÿTÓô=kÇž/»ðæ’ðØj^.ñÖsk7vá ý½ÿèåd/üBŒßý0j?á ý½ÿèåd/üBŒßý0jü“ñW†¾G Þê^ý£¼#ÿ 9ZÏÄÉâŸ|cðÚð'ÀÝ)ß±-íðý¦¾iú7ÄŸjŸ¥ñï‰/4¿‡ß~3xãÇÆè|Soð㬷Œ´ïøâŸÅ O |4} çÃz$žÔüSâ 5O ½KMÐ>Åc}¨ücýjÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨ÿ„/ö÷ÿ£•ý¿ñ~3ôÁªã7N A©òÝÊ”×+mrM§(^ö—#\ÊÊWI✣+Êñ½’””]ÕŸ4S´­Ó™;=UŽwÃðN¯ÙKÂ_¤ø›¥xgâµæ¨~0øÏö€µðWŠ?jÚ—Æÿ´>?øâ?Šþ'ø“áÏٯƟõÿÙãÃ(Ÿâw‹|GñJ¿ðïÂý/þŸj·!ðÂhú¢ÃsšZÁ!àŸzy¸};à·ŠtéÇ„“À¼Ó¿hoÚbÃPøsàK/Œ_ h ø+àΣiñŠïÞø}ñ›àoÈ? <7ðzãÀú7ÂwÃ×Mð¾ËÂ6ž&ñe®¹Ôü_ºý½þü%ø£ñGþÿì…¯¶øuãoaÃüfÒÿ¶¿áðÖ§â/ìŸí?øo­Gû;ûGû;ìoþÏ¿ûö±Ýy~Cü¯ÿ ƒþ ÿÿAŸø&×þÚÿžõzyvGšfЩS…öð¥5 ’öøj\²’æJÕëSnë[Å4º³ؼ>Æ5ªr9+¥É9]^×¼#$µïnûuØ+öM‰Þ1ø·yð5xñ¼}qâ=_ñ·Äo|$“Wø­áÉüñOÅZ7À]wÅúÀß xçâo…o5?üEñï…¾èÞ4ñÆ­ø‡OñF½ªÛx‹\Píg¿ÙGà_ì¹câ[?ƒ>×´™üa&ÿ .¿ãO‰_~0xÏU°ð–˜ú/ƒ|;7Žþ1xÏǾ4‹Á¾Ò%¹Ó|à{mzø.Æ÷P¶ð¾‡¤Å¨^­Ççü.ø+ÿýà›_ømÿiÿþ{ÔÂàÿ‚¿ÿÐgþ µÿ†ßöŸÿç½^ƒàî#[åéÜæËþ¢¼ÿ«3í<üþø*·ÿ+óüû3ï?†°ßìÕð{Æú|á/Zj> ‚îÛῆü‚÷D¾ð¼Ñü øã߉^&ø3ð3g„õMWÁÖð‡À^ k꺯„lZÛÃzö—qÉ~Íß°—Âß þøkÄ6Ú'Ž<ð›ã÷ÆoÚ¶ËÇ~ÐõŸ†Zü47ÇÛŽÄOèÓüeâ(tß Ûøö…ø‘ð×Àžñg‰<§ø;ÀrxbÆ=GS×ü! x’Ëã±ñ{þ æ3ÿÚ_†ÿ´ÿÿ=ê_ø[¿ðXú ÿÁ6ðÛþÓÿü÷©ªEÿBõÿ…˜þjí<üþø*·ÿ+óüû3ì/Á7cO˺ßÂmKgü$4x_Lø‚¿¾4øÇöˆø«á/‹ÚG†>"hú_Æ…Þ*øËñÆÞ8?¾-YøÓáF‰yâmWLðçƒ4 àéCÚ¾6þÍ? ?h3ữˆ–>8²×ü½eáoü)øÅñàÄßé^*“BŸÅZ“ñSà7~üG²ðÇŠ®|-ák¿ø^§‡¼C{á ^ë:eíׇô‰¬ÿ4ÿánÿÁ`?è3ÿÙÿÃoûOÿóÞ£þïüþƒ?ðMŸü6ÿ´ÿÿ=Ú?Õ"ÿ¡zÿÂÌÿ5öž þ?ü[ÿ•ùþ}™ö/ˆÿà›ÿ±Šà𠦵ðzg³øy¤\x~ÎÇNø•ñsB°ñׇ¯|qsñ?Rð÷Çk Ǻm§í% k/õoˆÚæ‰ûBCñ;K×<{¯ø›ÆZ½¥ïˆüM¯êZ—ñCþ Aû|d°ñ>•ñàŽ§©é>:²øÅ¦|@ÒtŒÿü¥|FÓ>:übø›ûAøûKø‘¦x/⇇¬~!é–¿¾2|Nø£ðÓOñ¥¾»gðGÆ~1Õ5¯‚°|>º[F¶ù¼|Zÿ‚ÁùŒÿÁ6×áÇí?ÿÏvø[_ðXú Á6?ðÜ~Ôü÷i>â¾/ûœÀóPià¿çóÿÁU¿ù_ŸçÙŸdßÿÁ:c­ZO>¯ðšóXÇW?´Mî³g«üOø¿ªéºmßí]ñ7áÆÏ7>Òµ\i¿ çñŸÆo ¾.ØKðâÓÂà_Š6±ñáëxSž6ñίâNçáìeû9| ñ†§áÇíAÿÏvõKˆ?è?øY€ÿæ¯?ϳí<üþø*·ÿ+óüû3õ‡á?Âß|øYðÓà§ÂÝ þ†_þø7áoï ÿiëßü#¾øáÍ7Â~п¶|E¨jþ Õÿ² xgPñìÛñoá>¯â_ø—WøOðçTºÑ¼Qy¬éÖú„t«ë kK”šY¿?¿ákÁ`ÿè5ÿ×ÿÃqûPóÜ£þ·üþƒ_ðMü7µÿ=Ê?Õ. ÿ ÿáfÿš¼ÿ>Ì?´ð_óùÿàªßü¯ÏóìÏÐ3ö6ýž4¿ ø‡ÁÃÁÞ Õôü`ýŸ>?x¶óÅŸþ-øÛÅ^*øËû-i߳Ηð?≼uãkž6×üAákoÙSà<ºÌÚ·ˆnàø‡¨ø&çXø“oâý_Æ^;¾ñ?9ðwöý’>|ZoŽ¾ÚøCâ’ø7âÃ{/Gãˆ:ÊhŸ ~&øÓÁ_|Uð×Ã:ˆ¼Y«øk¿#ñÇÃÿøŸÂ^ðÞ¥xCáÞ«uâ뇺7…ÿáaxý|Mñü-oø,ý¿àšÿøn?jþ{”áñSþ æ5ÿ×_‡µÿ=ÊO„¸o€Šÿ¹Ìÿ5ZöcY– íYÿàªßü¯Ìû•ÿa_Ù†OŒ0üq“À¾$“Æ6Þ<Šöþ‹ßà¬dYwü[‡öooˆGöx‹â»ÜÏ6¨ßSárøèë²Éâ¯ÿm»jÏô?ø&ì?áø|uiiðsR¿Ó|}à ~\hÞ&ø½ñÃÆZ€þÉâÍ+ljà_ñwÄoEý›<-gãx_Æf‹û=Ø|1±Ò|Oàïëºd6šŸ‚¼+s¤|·ÿ Oþ ÿA¿ø&·þÚ‡ÿžåð´ÿà°Ÿôÿ‚ká¸ý¨ùîRÿU3ÿúþ`?ù¨Ú8?ùúÿðUoþV}÷ð‹ö2ý>øŸÃž8øwànÛÇ~Ðþ/x~ÛǾ.ø›ñ[âo5Ë?ßÁ|V½ñç~&øßÆ(ø—âOß~Ï­“Åÿuoø¯@оèžðε£xe¯ô‹ÃãÇìkû?~ÒZÕˆþ)øwÆÍ¯ÚxjëÁºÏÃ>ê^+ðåÜú…ÏÃψ—Ÿþ#|:Ÿâwéo®ïîÏÃÿˆâ­Æ§«Ìš"¾­©5×À£â—üôÖ¿àšßøn?jþ{”ïøZðX_ú ÿÁ5ðÜþÔ?üöèÿU3ÿúþ`?ù¨?´póõÿàªßü¬û3Sÿ‚{þÈš¯Äï|Z“áMÖ›â?ë? |K øoÃ~,xKàìÞ+ø1¢i~øAã~ϾñÖðÇ3øW¡h>ÒþøÃÆß üCâoÚxSÂøoUÓáðÙÒò4_ø&Ïì[ éßtk/ƒ“]h<7}àÝWA×~%ü]ñ>àßj>.´øƒsáO‚‰|{«i?³—†—ÇÚn‹ã›M ö~³øg¥éþ2ðß…|Q§ÚÚëžðåþ—òWü,ÿø,/ýÿàš¿ønjþ{tÂÏÿ‚ÂÿÐoþ «ÿ†çö¡ÿç·Gú©ŸÿÐ ð³ÿÍAý£ƒÿŸ¯ÿVÿågÔ÷ŸðLïØÛQѼ9£ßü=ñåäþÖ¼u®ËâëÚ+ö”—â§ø¡cà}+âN•ñƒâóü^?þ9øKâðÇᦅãO|fñ‡<â}á×€´-k@½Ò<áË-6? Á1¿boë?õŸ ü$×4x¾X|³ø{á[OÃ_¶þÎ>Ð<ð#Åž<øO'Äé>ü^øðŸÂ^ðž‘àŠ<#ãˆÚðŸ…om>Ie©ë%ø™âKËë=CЬ5McÇž/Ð<;}úï_:üø ?ŸøJ ÿ³½ÿ‚|ÿë{þÍUõí|…ûoÉð_ýïüçÿ[ßöj ¯h¢Šù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^×È_´¯ü–oø'Ïýï?õ‚?mêúö€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€>vý¯¿äÓ?jû7oŸú­|M^^ÿû_ɦ~Ô?önß?õZøš¼: ýOÃù5‚Ì-×Oð¤ŸÎ?‹Gþ½¿ý)‘„'¯Ïÿ­þx©Óó=ii@' Í}éã‰EH×òÿþ<: RrKwòþ¿P÷ãÛ¿ùüéàÐ~=éiá ëÇׯåÿê¬ÜßM?¯ë¸ §'ØzŸóÿÖ÷©Ûñ?çùSªh@=þ¿áþ9§QOO^?Ÿÿ[üñI´·cQoo½Œ§„'¯^¿—ÿª¤ Aø÷¥¨s}4ó{ÿ_y¢‚[ëùÁþ´­:œžŸ™§„¯?çÓüsYݽÝË#žƒñíRùÿ?çÒŸES‚“ì=Oùÿë{Ô÷úÿ…K’^~€FžŸ™§„¯?ÊŸJ=C›{iùýÿðÀ|½ûcÿÉ×?ì|øÿ«×áµ~•üÿ’5ð“þÉ—€¿õÒ«ó_öÈL|×I<ÿÂyð7éÿ%×á·ùí_¥?ä|$ÿ²eà/ýEtªüï€ÿ¯Uÿôºgµ•|¿ÅÊG¦ÑEñ¬QEQEQEQEQEòí½ÿ$gÁöw¿ðOŸýoÙª¾½¯¿mïù#> ÿ³½ÿ‚|ÿë{þÍTõíQ@!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^ÐEPEPEPEPEPEPÎßµ÷üšgíCÿfíñ³ÿU¯‰«Â@' ü{W»~×ßòiŸµý›·ÆÏýV¾&¯¯Ôx J8,}ÿè*úi?œý{úSwçÛüÿõ©õóÁk÷W3Emmm ·77$0[ÛÀ,ÓÏ4Œ±Ã 1«I,²2Ç+;°PHòÝÆþ8ø¡•ø ðÞëÇšK3FŸ¼]«?ÿƒ®C:´oÜi:犼wk”f·Õ~xźt6²xªÊRY=¾ ⼇†0ñÄ繦.§;û(T”§ˆ®ãº¡†¥â+r»):T¥sGÆêül&'.L=Ôj×iZ¿óNVŒ~m7Òç«ÓÂü?óþq_:~Ò7Zì«ð'Æß´×í­û^Mð;àÃÏøF_Ç:§Àσ׺’x~?x¿ÃÞðí…Íî¥áŸŽÿ|Köÿø§CÑäÕ¼'à \Ä·ãQšÏCÓío/-º/Ù¯Aý‚¿jÍ;Ç7¿õ|kÖ>ø±¼ñSÂ?´%çí  øÿáï‹ÛO¶Õ#Ðüyû>ü~µð^·ðâçSÒîáÔ4ë[ß…ÞÓõ« e¹ÓRòÙgeü1ñï"£Ju²œ‹:Í0ð¨é3ø™ðûÂ-ÂSâøs@òÊ`8êÚ•¦Í¤®àØÚHÎ2+”ƒöŒø1~¡ô/GâøÛý\¾ÐüMñ)¿ë„¾ Ñuøî20Ëä<›‘•×(êÇîÿüøEðüA€¾|9ðJ[„XÂ^ðχ+vÿ±ôË=›2JíÆ $rM_ð÷įøŸÇ_>éºwíµÿ…ÿðˆÿÂK¨x‡áGÅ/øSÿ„ÛGŸ\Ñ¿á]üPñgƒ´_†þÅeo$.ÿ…Mâï¬4ñçü#zýͶ™/ÈÕñû<¯ÌðÎë†IY^6{ÑøçÇW<éÿ³‡í~Ù¼/àý1û¿xó@˜äœ|±0,ÅS G«À¿jÚ‡à_ìcð/Ç?´¯í+ãŸøVÿ>ÿÂ3ÿ §?áñ‡Œ?±¿á0ñ‡‡üáÏø§<áÿø³QþÑñgŠt'þ%: ÷Ù>Ýöûÿ²é¶·—–ükÆ~4ÅÔŒ(ÑÊiNrQ…,>µINRj0„#[^r”¤Ôb“m·evo‡vëJÊ÷H«[[û°Š^§ÌãÄßÜb×öCý .©ûöd²ùzóµ_Ú2ÂݰHv”çrFȮʿð‘|uÿ£6ý ?ð³ý‘?ú)+èïÙÓö³øûUéþ8»ø%ã=K\Ô>ø¬øâoƒ|aàˆß¾'ü7ñoØ-õX4/ˆ? >0øKÀü{¦]A¨éCÄÞÒâÕìY®ô¹o ŠYèêÕx­ÆŠNjàéÍ[š2Ëá/u=TÕÓM_£Mh/ìŒHÔkþ¾?ÒßÕþ_œð”|dþ>¿dÚÙ—™k?³mùzî¤~кŒS˜.ÖIåçËòüàb|}ãK^u?ÙËö†ÓÀÜž ðî¿•êX/„3•Ôƒ?Š>øO]¹R[~ø®µ-&æâVLJ“E*KÁeÖEVMû|Rgðk|IøYzOðÛâÇÄ Kˆ„Ï‚ïuý[áå×–0±ÿ]˜‘DQ‡tgÞÃx™Z|±Ådð·ÚžÓjé{´*á­~«›o4sÏ&þLG¢?ÎQ›ü bÒ…' ü{VUßìéñï‰,¾øå¡üIµˆƒ@øãà};MÖ¯6ÝÚ‰ !𮛤Fq±ïn¾x®äådh¤uq7Ÿj_u‡Ú•މñßÀš¯ÂNî-;Iñ„Ú…·ŠþkºÄ‚[7âv›ogo¢_^ÌE¶™¥üJÐþkzÍÖaÑt­D”gú¼¿Œ²L|£MÕ««6”iãéÆŒ[o•/¬Sl"”¤Ôa b#9É¥¶ìpVËñ4nÜUD·tŸ5´¿ÂÔgd·j6]ÏZ^—ÿ_üñO¢œŸo¯øŽ+ê[Kvq_×ßÐùoöÉÿ’®ÿØùð7ÿW·Ãjý%ø)ÿ$ká'ý“/ê+¥Wæ÷í– ~Ïú÷¯ü'Ÿ9ÿºíðÖ¿H~ ÉøIÿdËÀ_úŠéUù÷4ë`-ÿ>«¯üž™íe_ n¾ô?)]=M¢Š+âOX(¢Š(¢Š(¢Š(¢Š(¢Š+ä/Û{þHÏ‚ÿìïàŸ?úÞÿ³U}{_!~ÛßòF|ÿg{ÿùÿÖ÷ýš¨ëÚ(¢€>Bý¥ä³Á>ìï|iÿ¬ûoW×µòí+ÿ%›þ óÿg{ãOý`Ûz¾½ Š( Š( Š( Š( Š( Š( ¿kïù4ÏÚ‡þÍÛãgþ«_WŠ„ùöÿ?ýjö¯ÚûþM3ö¡ÿ³vøÙÿª×ÄÕãúw¹c¿ì*úf'ÏçÅ£ÿ^ßþ”ÏøÁ„vžÔ¼U¤^ø‡á^…ñNÖ>2x{O°¹ÕæÕ¼máïE Ýè–{¯5ýAñÕÇ‚¼WâÍ ÚßP}cÂz¹§¾‘­Aq.‘{úá_xkÅþÒüKàíwDñ/†5{Hî´]sú–­¢j6G+úv¡§M=•;T {y]‘“†RËAIíøŸóü«çýcà izþ©ãÿÜY?ƒ|Y«IFÚ§>ë¶z·ÃÿjÒ«2I®_øyõ $–e¾ûII“óß|(ÇqŽ:Ž}“cáý£C %L·7©Ò•IÓž¶±¡Uº²U)ÔJGiûHI5.Ì›5†›Ã×…©Ênq«y'%ÔãÕi£Z­šµšã¿à¼?²ÿÇOÛ7þ IûTþͳ_ÿád|kø‘ÿ ;þ¿ÿÂMàÿÿlÿÂûH|ñïˆÿâ£ñïˆ<-á=;û;Â~×µoø›kÖ?kûØl~Õ©]YÙÜ~AþÓÿðLø(/í5¬ÿÁ@¿i¿ ü>ñÁxlÛWþ ÑñÄŸ±æ¡ñölÔþ2|Jý“¿bσÚ×É> ×u[íwãì~|]ñ_Ä"Ó&è^Žßf1!ã’¡Ê–\ûW‡¿h?€~-|)ñ¿á‰Ä¤χ¾%x3ZŸÙºÕÎòUsÁ¡„3ìÚšöUp”iÉós*˜|Lj8ËK”œësµJ½9*2~ü#Z·¾ê:s§~Ê›ÕIôjÍ=£ËÛªjëfÒé£þPkOø$÷íwñz×öG“Oý–¾+鳯ÃïØ+âÀýöø1ûgþÎ_¼_û.~ÒšçÅ-Wžñ“þÓßðP_†Ÿ-SG½øyyáÿÝünøá]_ö€øsá{-3àþ¯à­+ýéÿWý…ÿà¢?´Wüoã§ì•ðçž>jÞ øû"ø'áÏÁ­GHû7í©|KøSñsödñ'ÄŸüNý¶>#ürøkðgââÜißþ+ø¢þö/ÙSö{¾ñEΫ¡IgŸªésè^.þ‰••Õ]]C#© ¬¬2¬¬ ¬ ‚A â¥O½ôýoë[RÎ1Nx9û<:x:ê½;Â¤Ü¦ëÆ»U%V¬ç'Ï¥UJ8‰ÆþÒ´¤Ü›tâ£-^±åèºr«Y/»á]ZÈ_íCÿÅÿ‚ƒþÓzÏüöœðÏÃïü‡öÂýµ¿àœßüIûj?f½Oã/Ä¿Ù7ö*ø;­ü/ø•à­wU¾×~2~ǧÅßZüøÑûPþÉZ×|û)~Ý?µ†“ÝÚêÿ |û5~Ì_ tOø³AµøàÿÙÇàíÿÄ_ |'ñ§_xÓÄö>þÞè¯[ ›âcì’¥†q§È”\'gáã…)þõ9ÃØAE¹7R÷~ÓSï«×ϯ75öïòò?‰{ïø"gí«ð«Æÿ´÷†¾üƒÇ¿³D¿àZ­×Ã+?ˆÿ 4È¿nÿÙ»ö ýšx_âtú¯ˆ.õïņfþsj›?ø(ÀM+ösñλñ?Gñgü“ÄÿjŸØÏö|ýŸ|añሿmÿÁ6kÏx‹Â_³ohÖø>§áx×öiøåà£ñO_ø¡áx¤øQÍ®³ÿ î5]7UI¿º+›«k(%º¼¸‚ÒÚß5ÅÌÑÁI7K4¬‘ƹ nf$s^9­~Ò¿³—…Ùlj¾?|ðñˆ•‘uÏŠžÒYdle¿×``ùãi‰øcXÔ|Oñ¡|©øWÄZWöƯ xSá·Ác`|}ðƒÄ"ñ7‰¯mkõYÿ‚Š~ÇšQ–;/‹ƒÆSÇI? ¼ñâZ\%R8/¼ á-KÝ+ác’kèmðD2BAâž"ÿ‚Ÿøaâh¾~Ïß¼]zX®¼^Þ øaá­¦[SÄz׋ћ‚Ü•“ä>,\êÇ–ÞÕ´¢ý”*Õž”éFö¤äõöjONW)M´âì—*Zínöïçë¾öë¹ú‰^añ§âÂ?†Ÿ|A¯|nÖ|3¤ü>¸±¹Ò5‹_Åm}eâ5Ô-.ü1i MÔÞ*Ôµ«u¸µµðΟc¨êÆ^ÚÞÆà–Jü»Ô?i¯ÛÓâÀ¸·ð_ƒüðgH»e6·šƒ«üHñE¬-Ÿøöñ·Äüà¸üÆëឯpB„Ìäô/Ù Äþ0ñE§þ9xÿÄ>8ñt *A­ø«\›Æ%Óm®žâÇÃÂxm¼!à;Y‹¸–ËÁ–›0$ËhîîÕïàr\çʰÙuXÂI~ÿcC•µÍ+I©M­ýœ½“’OÞM«òÕÄP¦ÛX]}˜¾iùh®×®Éž›û øÛTñWÃýWMÔ4}WFÓü=â_Ùkw3_júGÃoÅ#Ô~øG[½žæò[­{Áž þÂðö©4—w9´ƒí3Ü\ù×SýeXðž‡àí" öéú|$ÈUK<׸Q%ÕÔò–ææPªYXˆ‘&È£Ž4éBïõÿñÍ~Ñ–a«`²üYâ+aèBŒë;·?f¹cvÒr傌9ÚN|¼Í&Ú>n¼©Ô­R¤W,e+¨-Ö×òWwvé{&|­ûfÿ ý¯œqÿ çÀÎî»ü5¯ÑòF¾Ù2ðþ¢ºU~s~ÙÿòošÿýŸ?õ{ü5¯ÑŸ‚ŸòF¾Ù2ðþ¢ºU|ÿ^ëÿéTO+økiozû¥¯™é´QE|Yê…Q@Q@Q@Q@Q@|…ûoÉð_ýïüçÿ[ßöj¯¯kä/Û{þHÏ‚ÿìïàŸ?úÞÿ³U}{EPÈ_´¯ü–oø'Ïýï?õ‚?mêúö¾Bý¥ä³Á>ìï|iÿ¬ûoW×´QEQEQEQEQEQEó·í}ÿ&™ûPÿÙ»|lÿÕkâjñà {ŸSþúþõì?µ÷üšgíCÿfíñ³ÿU¯‰«È‚“Óó5ú_·õ,ož*7ÿÁ1<<×ø´¿ëÛÿÒ˜ÚP è*@€uçùSëíO,`AߟoóÿÖ¦Í4Å<.0ðÍKޏhÝYg±¥§'ØzŸóÿÖ÷¤íg{YèÓÙù[¯ £ðËáî¬Y¯|á÷wi`Ó­ì¦ovžÉmå-èÅ÷À W”ëß²'ìùâVwÔ¾h’É)Ì’ËÛ™‰fl•Õ´#ûÌN{Aä($“ô°@:óõÿÿ]y¯Å‹>ø9áoøJ.Õ<ÅÑ<àÍ[oÄzıH-­ch­lí!¼Ö5‹Í7CÓu=NÏËÅeùMhÊX¬¿V)^S­…¡+-5æ”9¼·WvÜÚñiS«U7¢Qœµì­{|QýcÏ„¾»ñ®¿ 7…ì⻳Ӵë hšCkž"ñ¯p-4_ xSAðþµâë÷ò¥†¤i‚{ûÛ™[j¾Ó2ð ÿ`-KX˜|Løã?ˆŸõ«×–ãÀ¿ ü=ñÅ«oð¯C»àHüA­øwÅšl>$ø•ªÙJá+Ô´é¿áÐÚY|5ác¨Z[ßø£ÄßWü+øCã/xÊÏã÷í ,®¾'Åm}ÆÚ}Üz§‚ÿgŸjð½½æ—¡^"Goâ‰ÚæœÉkñâd‘¹ x54ÏE(Ö¾® êþÏ­x‹…8'V®K§F•NRŒ’NuT9\\£xÆŠ·,$Ý[Ô—%‡ÅA(Ç9KyIµ$¿»§t¬¯.­{¶Š¼þ*‡öCÖm]ŸNý¤h­:F]¦KŽ?ìæeêRI¬¾2ZÈñŒlvU$Ua0ý“ümßö­ý¦‡ý×ïÚDÿïx¯´À¥›à^nÿØ8 ÷Pšü¦¿àöž9ÌDÿðò>Hø†_ØãW»i[Qý£¾?jM:•™ï~2|x¼’u#a[‰/¾3ݵÄe?vc”²ìÂc`ÛYs~ÁžÔwÇþ;ׯ6°Ö¼MãmcåèQ?¶üoª*!\«&ÒŒ  ³)ûÌzǵHwçÛüÿõª×ð¥?‡%Á«l’©ÿË?>€óo\D×ý»útå^ªÿ&|kÿêýÖTšûÖÂË=߇|1y2sº9um'T’7ÎNðû²Xä–9õö7øáÀ£Mðô¶ûz}˜iZn:ÐIÓ@è3´ƒž}QEtRá܆‹½§¦¾Æ2zyÊÿÖ„Tý³ÔÙïÄñÿçÀϯü—†•ú/ðSþH×ÂOû&^ÿÔWJ¯ÎÏÛGþMïÄö>| ÿÕñðÒ¿Dþ ÉøIÿdËÀ_úŠéUð|`Û«où+ÿéT_-Ú·¬?)›EWÆž˜QEQEQEQEQEWÈ_¶÷ü‘ŸÿÙÞÿÁ>õ½ÿfªúö¾Bý·¿äŒø/þÎ÷þ óÿ­ïû5P×´QE|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¯kä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{@Q@Q@Q@Q@Q@Q@;~×ßòiŸµý›·ÆÏýV¾&¯(¯Wý¯¿äÓ?jû7oŸú­|M^Vž¼?þ·ùâ¿Ià†– woö¨ÿ騞küZ_õíÿéLeð<5lš‡Œ~!øÊò ‹;¾ÓžX"–öxmn/5 JþæÇAðæi¨x‹Äº¦“ iš†¥mçß þx…|C/ÇŸ·ZF¡ñ‚ëL¼³Ñ4[ é/< ðÁÚ‡•-ׂ¼qw¬wúÅâC |Aø›sec¬xÒþ´¶‡EðvŸ¡ønÃWá_üÿWÔuk‹ÿ´çÆï‰¾>øë©h ¢ê|>ñHøoðûáæ}y¥uà†sx/Fð¿Œì´ß:++oxµ5Ý#[ø‡w¤éú¾¯c¥Ág£èš7Õ¶ÿ²?ìÖdÖ>ø;Æ×HÛÓQøŸiqñcWY8Ìˬ|L¹ñf¨³œ Ó ±+M~m˜x畬D–%Æc©Ò•¨ËŠ£ƒ¥t—ï¥RÆJ¤Ô®¡ÆKš2rjqõ)dU9ž"Ü—¿É7göSr…—wk»ë¦‡ÏºïíðÂî±x“ãgÂOÊÎ#HuŸˆþÓ&’Vݲ¡¼Ö!–Yœ«áIEfâ²£ý¤þ ]tŸ7‰sÊÂá¯xÌËÿ\G…4 dÍÿlƒöõ÷ׇ¼ àŸ¨O ø?ÂÞ@¥x{ÃúNŠ¡ ]6ÒØ!TÆÀ®¦¼šž7çumõl›+£ÿ_ç‹ÄoåN¶Ö뮾FñÈ0ëã¯Z_áP矜Cãÿ‚fðÏÇG?wûö[ý¦u¯1q’ñdü#½ó¢ù¥‹|jxg øõá1×À?´Ñÿ»7ý®1ÿªF¿GqŸSúœÓèŒ\QRËê9 nºas­[_í@y&;*˜:©Ó¿Oúrí×ožÇçý ¼ >ÕáÿŽof¿ìÉûIh¾[c!&¯Â{?"F¤sl‘Æ6©ÈÌþÓ?-?ä1ñÃÃ8øL´ÍÁ^H-çÿÂY¤èßgØé<ÿ/ËL;íBþ“Se‡·?çñ®Ú>+ñ šu094£ÕB†: ¥mœ³ ‰|âíæOö&¥Jÿ9Skð¦Ï¿ütøâ݇Âß~ø›ÌÛå|Cð޲_syjû;X¹ÜZL ¸*>n+ÕãdtW•ãuWGF ®Œ7++C+ °$Aïž%øgð߯‚UñÃïx±g '_øSA×DÁÐFâQªX] ¢ª0|†@ä+Ä®¿bŸÙi˾ðsÞ™Ù™®¾Þkÿ¯w³2-÷Â_Á·‰&X‘"N®¹ùX+ÝÂø§‰m,NKBi´œ¨c§IÅ}§ìêak)>ËÚõú˜TÉ£´q2_ÝtÓ^Jêq¶Ï¤½;Ó¥ž‚±îd­kJùþþÑÿ<4"È·Ñ/xgËÁ ìþ5ðÔŸnÌ_(Žhþ&ÚO nåºv ¼ž¡á_ÚÏÀ«æ_ø?á‡Ç}* o>ëW¿ üq2¨Éiàˆúˆ|rÎ7y‚ããV“²EO& Rá–Óê0^!d¸žX×¥ÀÉÙ9V¢«Ñæ{(Ë :ÕZ{sN4·••ÙË<®½?ƒ’§øeitÝME|“{3ÑÂzŸËüÿ…H+Æô޾ÕhÈéá ëÇó© ¥®‚O•?m5öyñ?è|øÉëÿ%çá¥~‡|ÿ’5ð“þÉ—€¿õÒ«óËöÓÿ“yñýŸÿõ|ü4¯Ðß‚ŸòF¾Ù2ðþ¢ºU|Gþ Gþ•HõrݪúÃò‘é´QE|qé…Q@Q@Q@Q@Q@|…ûoÉð_ýïüçÿ[ßöj¯¯kä/Û{þHÏ‚ÿìïàŸ?úÞÿ³U}{EPÈ_´¯ü–oø'Ïýï?õ‚?mêúö¾Bý¥ä³Á>ìï|iÿ¬ûoW×´QEQEQEQEQEQEó·í}ÿ&™ûPÿÙ»|lÿÕkâjó ôÿÚûþM3ö¡ÿ³vøÙÿª×ÄÕæá@éùžµú7ÿ¹bÿì)é˜küZ_õíÿéLå|aâ½ÀžÖ<]âK‰-tm×í7&ÞÞkÛë™eš;[3KÓ­RKÍSYÕõ ít­H²Šký[V¼³Ól žòêž„¿gÏ|]†~Ñm{¥øbõDÚGìë¢êæÁ§¹f€üg×4iÖoˆšõÄ,£UðMž¢>饥Òo4ÿKk‰æä¾,ÜÚé¾!ýŸõÝyÒ?h_´ƒ®üq$ìÎ mCÃÞ1ð÷€o¯ävCi¥|gÖ¾êò\Ïû›7°ŽòF…mþÑÑßþ9ÇðÎøÁº|Føçñ;äøkðÂÎøéÂö9­âÕümãm-o×Á <"÷–“x·ÆWvWr#ÜYøÃW‰|i­xÃ:¯â>8qOÃ9Ãpž_R¾ .«—SÆbeFnŒ³Zu£5[xrà°Ð¡/ihÒRJ•å%ª~¶E„ú2ÅÍFuUWój©r¨Û•kïÉËGnkYGw|ÿ‹_¼û:øwÁÞðW‚£ñ'ÄK/…þ|ð¶›¡Þø£P±…%º0Åé ø{á;YSTñÇŽ5h|?ác¿Ö/t]Wü\ÿ‚³þÈžÔ¿e]âçí=ûPøoá÷Æ[ßÚ7àˆ¼Oñâ7Âωÿd ý+þ#Õ5/þÈ_üàKY/>þ¾(ñÕ›|Qñ·Žu]D×~Å7¿oOÙgÇþk~ øãû5xÛöÂÓ|!'¿e¿Ú—ö2ñÏŽ4øYÚì£ñÿâ?†õo„ÿÞxSàO‚>Û5ï†üÚýäÚ¶©Ë|Rÿ‚‡Ábf?…?ðVŸ|Xý·5/ü\ÿ‚cx;þãâuðMÏìûû)Ûx7Åž<ý³ü7à[Ú#À>0ºðÿÁ]#]¸ðf®ø·\²Ðï<1«øoÅ1É¢èwºoˆô{Oím3VþÂ>0~ÆŸ³Çíñ3á§ÅŸŒþñÄ/|Ö|3â‡~×¾+|^ƒúŒ|ªk×…<{7ìÿkãËoïÄ_ê:íüÚGÄ?|8Õümci¶kÉg¢h¶úÔ•ôôó¬ãSû"jµ'b%R«'c¨â£×­OR¤¥‡Ž' Z»TçR©ÉÁ¼4UL=œ¶ö%µ®¾Ë[&´¿,’Õ]?æÓùåýŒ?n/Úãüïö—øñËöœÿ…qðãöŸý«~ü<ÿ‚}/ì¶þ&)ý—>ø àî·ð?ö®ÚÃZ=‡‰¾ ·Žµx‡UþÛøÏâü6øÐ/Ç€þxsÃÞ&ðÝíã|ƒÿTÿ‚“ÁPÿg¯Û³ãWì[û *x¿ÅÞ)øKðköÞøý¡ào‡š–‘àOÙ«à‡ÃoZ—íkðêêêëÁÚ–¥®jÿ¾ üð§†|yâíéš×޵­Ã"ðË]øDi«ß¶¯üÄÿ³?íqðÛö.ø[û>xCâÇÅï‰_³ßŠh? Oñƒö”ðßì­à߈ƒÃ¾4 µøðKÆ^-ø{ã¯|Fý£µëÄ›W²ø}¯ê|=§xuì5½wÇZ]…ãÍkï¾6ÿ‚™þÈÿüQá߇?üyð_â¼þøE®|KðF³áoøçFýžµ?·Pèÿü/ûC|føO¡ø÷à'ÁÝGÄ~)kŸ xgRñçŠÞ3Ôlä»ðn³¯iÙê7ØváZ†'û"•jU°Q,7îj'(T±~Ê[‘Ô‡,xÊuT§JX‰B\±Í{Vœevõ¾ß Ü­vµV²[¨ßSøòý£¿ooÛöèð7ì{ñÏÁ¾.Ö¼%'í_§ÁÇÞ0ý•.ü'ð#öuÕÿi†Ÿ?goÙÒÛà_Â_ƒŸu‚>+ø¥ðÛÄ2ñ7€ |øñ­xßTýœüce¬|3×¼ c¦|<ñ–™¦ü&ñD…Æ‹x éž2ñµïÅKÏü@Ô¯gþ ÿòðõ—í«ë¼Oeá¯Ùcã§ðö€ñ«þÏ´Ô¿¾übÑþ$x[á.¡à=gâ%ðEÞ¯|iá­&94]SÓî¬uâ[KÉü-c¨ëV˜:ßüÓöLOÚ£á÷ìÍá_‹ oî¯>#þ׿ >2k>5¼øõà Oá×ÄØßàÞñ£â¶…àÙßölñÁGàŸ ø†ÇSø£¬ø×ö‚ø/ x;Bš¿jŸ|Mößéþíe^ENžM*T鯼”y=×IN®"Q¨ç…\êzÔâùŸ¼£œ·«ÇÝÝK»¯[»+é.­vëmÍOø)OÅ¿´ü‹þ ›ñëÄÐxªO†ß~"ÿÁ5¾0ÿÁH øKi­Ø‘û%üLðV—ãoÚ%E†%Æ«gà4ñVµáí»T–k;O “W•ôˆ¯Üü¡ûjÝÿÁ4'¿ÿ‚vÛÁ4î<-/ƒm?àãø%Øø±ÁGø¢ß²µ¿Ž¦ð߯–±_€Ërá—c’Mþ ¯ì¯-µì©>&øHßIfþ‚¼ÿuÿ‚wx÷áoŒ¾1xSöŒ³Õ|àO|-ð.·f~üdÓþ!ßø³ãž—a®|Ñ|ðwSøyeñwâ=ÇÆ½ S²×>ËðóÀÞ(µø¡¢I&¯àYõí:Úææ ïø+7ì§øCáÏ‹£øáªë­ñ_Ç_~xÀø)ñÿÇõOˆ¿,Æ¥ñŸÁ7ÿ³§‚þkß<5âo„ºlj_´ü6Ño<¦ÞXj%‹M³Ô,f¸× :ô”`°x˜rVªÕ8:ƒu# œ•aì_´•8$àý×ËË.T–­¨½y£ªZ»=¶ŽújõßWmÙüÞü;ÿ‚°ÁS µÔ-¼Iðƒá¿‹¼u®xáEµì_ n^UÉN*7OJtáI=#¯/2J)+¤´@••½3•ñ§|ñÃ×¾ñÿ…<=ãO j!Eîƒâ}"Ã[Ò®3åJöZŒ ˆ‹Û\¢-Å´¸–ÞXäUqñ7޾xãöm²¼ñŸÃ½CÅ_~é05k÷÷¾+ø…ðçB´R÷Þ#øUâ}BKŸxÏBÑ,ѯ5O†ž+½×¼E%„·€|@'´Ó¼ «~Ô—ö:M…ö«ª^ZéÚf›is¨ê×ZYXXÙB÷7———S´p[ZÚÛÇ$÷:E Q¼’2¢’=Œ#‚­N´°øˆÙ{H|2Œuöu¡¤kQoâ§Rñ½¥Y¨Éc^«IÅJ:è÷MÛX½ã-7^Žé´|£êÚgˆt/^е =_EÖôû-[HÕtëˆîôýOLÔm£¼°¿²º…ž›KËY¢¸¶ž'hæ†D‘«uBüûŸþµ|9ûxÁ|IðóÅÖU­ÍŸì¼{ã/ü0²º HøaãøÏ\ø}áøÕ‚ùpi"Ão± ¼w‘ƱÅÜàÒ¿gË1ÿÚyvËìþµBYSMµ µjMÙÉFjJ-êÒNÊö>rµ'J¬é­y%ng¦–M>ÉÙ§eª»ZŸ)~ÚÀÙßÄXÿŠóàWþ¯Ÿ†uúðSþH×ÂOû&^ÿÔWJ¯Ï¯Û]Hý¼DOýŸ¸ÿºõðÏü÷¯Ð_‚ŸòF¾Ù2ðþ¢ºU|×üxðâ?:'¡—+*ºßXmé#Óh¢ŠùÒ (¢€ (¢€ (¢€ (¢€ (¢€ ù öÞÿ’3à¿û;ßø'Ïþ·¿ìÕ_^×È_¶÷ü‘ŸÿÙÞÿÁ>õ½ÿfªúöŠ( ¿i_ù,ßðOŸû;ßë~ÛÕõí|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¯h¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(çoÚûþM3ö¡ÿ³vøÙÿª×ÄÕç Ò½ö¾ÿ“Lý¨ìݾ6êµñ5|mñãN©á}kHøMð›C²ñßÇÙý·Eðíä×0ø_À~iä´»ø£ñ[S±WŸBðF—4sæéð”ñµØWÈÊ5m_Bý„jÆŽ)mõ¤•®Û“¥MF1ŠMÊRnÊ+VôG‹™Eʽ$•ß³}l—¼õmè’êî½LÿÚâ>ibß¼9á-'â×Å¯Šš©c¢ü+Ôçdðü>»Y4­gÇïí⺗Âß ´†™­µ=U­äÔ¼C¨˜|+àûMWÄ·°ZÅÈ| Òþ ~ÆskZçÄ=+Ä?´Õ—Œí<8ž:øï¡[êÚßÇo Ûè|zm‡†uëZƽ«øÃàß…‹ÝÞøNÃÁ:åÏ‹ü:º†±y­xGÇÞ(Ö&ñÖ³k‘XÄÐZ(³Ð<%áÛidÓ<àE¡x[IÌ6±\jz®©©{pBzñüÿúßçŠ|QÁùgेÎ!:uTZÂâ0ÒŒ18¹)ò{D𝠓…9סWžƒ•8{4ªSwŽ[5*-r}¸Í>Z›'hï+¨IZVmÉÙò¯Nø]ñ§áOƽMáW|7ãm>ÜÅ¢š5ú6«¢\L’ÇÄz È·×|5©á~•¯éÚn£ÖZ¡W§×çgÄ?Ù«á7Ä]b^h—žñõ˜'Nø‘ðûWÔ| ñOb-kâ¿ \iúº*›ÏÚw°D]à(Ç+g¢~ÚŸ Á_‡¿ô‹Z´em|5ñïÁ¶ÚΣj¬Öמ¿ðW‰næ?.íOÅ^#ñ ûÁ’H¥B`¯ç|ãÁ>%ËjJyUL>s…Nðp’ÃâÔVÊt*Ë‘ÉÿÓº³O²ÒÿEG:Â×·´æ£-š’¼>Sú¾©?Óõ =¿^ôµù©oûe~Ò>h-¾*þÈ7º´áo¼Cðoâ-޾“®ðLÐx{ÅÚ†4럖ֈÈîŸñü»ÊÄžÿ‚­~˺ω®ü'¨iß<9ªè·W–>/–ëá>½âÍ+Àš…­°¹MñvµðÂ_ivád† 2ÒêÿP·2Gu©Ûiúk‹êøºÜ3ŸeòpÆeú)¹9áªrÙk'΢㥴W¾ÚŒ+Ѩ¯ ´ä¿»$ÿ_ë^Ìæÿà¥_ðNÿ‰ðP? ê m¿h‡žø7âï…>/øeãO…??d†ÿ´v§kÞ(•’Ãã×Á¿]ø¯áwÄß„¿¼¦Ïu¦øSÄ­ãxMtÒõ±ðÅüE¦^ëâ_ÿÁ¼¿ u|Tðÿņþ ƒÆŸ do…/¿jØËà'í©ñwPµý‘ü á¿…ºW‹þ |Gøþºïƒ~øÏâ÷Ã_ éÞøÇ©k~+Ùø–ûoЬtí'Äšn…¤þ»hß·‡ìs­Uý£¾ènã勯Þ%¶ø{31*5‡ÇcÃ’™\°ÂÍ“ ±cíõÝöø âCxwãwÂ-}äû‹¢üIðfªÏÆãµlu©Ë`ð×^˜a¡äéB kÙý^’º©*rj¢t¯Q¹R¦ïS™§¤Õ¬)Æ _vÞ÷onÚÛgo™ù[mÿgðÍßì{ÿVý‘üYñ´ø—Kÿ‚™~Ö¿µígmâåø^šqøâOÒxXðfƒÿìŸ5%ø˜>øÃáþ…âC­ oáóøÌFl#Òü!"®¡_&|?ÿƒp|/¢øköYÐ>'~Ö¾"øð’Oø*•ÿíâè~Cá?þÔ!ÿ‚¤|“àgŒüSo©KñKÅ ð£Ä|•$W·6ÿ5-0NñøV9Ä0Iºˆ4\ Òµ½#S2ÆfŒiú••é’!ŒÊ‚ÚiwÆ22ë•<Ö½zt1øè©EWiNRœÿwM]ʃÃI¯rëš‹äj-+¨ÎÜðŒ–|‘í·¯GÏõ[6?~&ÿ‚ø×âÏìOð§ö9ý ÿm ?ZGìÇñöTñÇì—ˆ?dÿ†VŸ~韲GÃVøUá_üOø57‹u}öŒðçÅ_ _kÍñÏNø‹ñ†=#Å7ºÃ[ø'GøqáØ$ðýÚxÿþ9áþÎ_~ ÏãÿÙfÃ^ð_ÇoŠŸuht_ø&ÿìÿðÛà6§uñCEÑ<:žðgÃoÙ÷ľ>|%>Ò|!à„ð§Äþ×V¼C7…t¸>0|Eø©¢i~Ñ<;ûÕ{⯠éA«x“AÓ N!jÆdc•"77†#$ ógö“ý<:Ξ øûðWBhÉ.³ñOÀÚc!PKºìJ€I $×¥KŠVn¢§í4¥F)NJÒ’Q‚QºwvI977yÞBq¶¾–µÞº«'®ºÙk¸ábÙ¿XýÿeŸƒ_³~¿ñ¯âGí¬ü)ðÕÎ…¨üføµ¨\j>;ñµÅk­>¥-Þ¡«ÜÚéz:êËáß è÷¾¯6ƒá#BÐäÕu&ÓôÿS(É™ÿ?¥|s¬Á@¿c-äŒ~оñ Ç»#Àsê›x"ø}¦ø™nI?* s)‘ÁD à¨ó-kþ oðÉÊxSÁßþ"±Ëmá.­á$;¶®ÛŸŒWŸ `Û' ²<°‡s²ô®Ê-Õ“’½IMÊo‘s6äõj0N÷»vŠòKC+Y.‹mt_{ù£4WäMÿüöˆñªËkðö`Ñ´gi€‡Wñÿ޵_ÞÁݶ;Ÿü6ð¼¶rܶci↙c’ì"ñŸ‡oŸûãñ÷Æ]kÀ~»”I'‡þÁað_HU#kÛ[Fºñ_Æ_#Ëb¦ñÝ´Sv• òÖ Áe™–)Åa²ü][»s:R¥ÚÖçud–º¸ÂVõ²3jTõHGÖJÿu÷ò?Kþ4~Ô?¾C> i:gˆ/!2èÞÒ„þ$ø‹â"c‘â]ÀzZ‰õåhŒFý4ÔÒ­]‘¯ïí!&QùUñWãGÇ?Û’ý¾øwÂ:¯Ã/sKÕü=ý¼þ4ø…f³´‘7Å}oH’ãDðw‚Ý#‰î<¡êšÕξCÚëúåÝ´¯á£ë ÿa?…Þ’kýLjêWòý£V{å²—W¸-¹ä×¼Cu=ïŠ|C98?l½Õ¡¸oãȲô?èž°‹Kðþ•c£éñr–¶ÑÛÆ\€Y<µ 4Ï€džfy¤ošGfɯ´Ë¸7UÆy­Zth«9ah>yM^üµj?uÅèì”SÖ)Î,ókfîÕ¹Éí)&¢¶WKFþvîš8O„Ÿ 4ï…žƒ@µ•.¯g^j÷ÑÆ"ŽæôÅ";dÀh¬m! ´ˆà…3’yz˜t¢Šý ”iáéB(Ó§G[(¥¦ú¶õ»z·vÛlò¥Í6åRZ·w÷ýËË~ÇÊ?¶Ïü›¯ˆÿì|øÿ«ëá~€üÿ’5ð“þÉ—€¿õÒ«óÿöÙþ×ÄgÂyð'ÿW×Ã:ýø)ÿ$ká'ý“/ê+¥WÉñCnX&ûb?:£—ÚÕm{~ï~ºOSÓh¢Šù3Ò (¢€ (¢€ (¢€ (¢€ (¢€ ù öÞÿ’3à¿û;ßø'Ïþ·¿ìÕ_^×È_¶÷ü‘ŸÿÙÞÿÁ>õ½ÿfªúöŠ( ¿i_ù,ßðOŸû;ßë~ÛÕõí|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¯h¢Š(¢Š(¢Š(¢Š(¢Š(¢ŠùOãÆ¿Þx¾oÙ÷öx‡I×¾9]i¶Z‡‹¼O«[¾¥àÙßšÌr?Çb¶¸·}[ĺ´1O/Ã…6wvºïŽ.ᎥsáÏÚjþ*³ñ_ø(GÇi´‚?þü-Уøñƒ\ýž¾(kž(Ó…ÓÛxsáÂÛÏx¢×Uø•ñ#WŠ)ÓMV¶zµ—Ã& è4}U-õ KÅ~6ñ†¤¾"ø—ñ/ĆÚo|DñQ-›WÖî-¡‚ÖÎÂÂÕ#Ó<1á& OxC@‚ÓAðöealû¿Šß<'ðCö&ý«4]ãW×õÿü øçâ_ˆ?%ñ^½r%Ÿ†üá]×Pñ'‹üIz‘Jöš†ô½OU¸Š)§ŽÔÁòGãZ·Ç¯|GÔïüû.èšG®ì.î4Ïülñ2ß>½µ•­ïììoôÉ­5Œ^0Ó¦WŽoxþÛD±¼‚ëLñ—ÄOjQCguÛ|5ø áÿk³|Añ>±«üTøÉ¨ØË§êŸ¼j¶³kVšuÓÇ5ׇ< £ZEð×Áo4P±ð¿ƒltè5&¶¶Ôøs[[Ž>?°¹ÿ #Ç~½–/…Z%äd´¾ øi«ÝøÖxͳêÿ4 úǃè áoÃo xcIð_‡üàý'Âz¨²Ò<9eáÝ*-"Âß{Èéob-L ÓM$·2”i®®ešæâIgšI¿{ñþÏ­HãÞ’£ \×µ”•¥:©IÉ^ü¶²ŒctŸ$cÝs4äÛiÍéou-”tWïÝ¿6Û鶇”ßüøU©†^Б\e³‚M5JA4Ù­ `g¾q^c«þÅÿ³~µ¿íŸ t†Þ0ÁÐ^üª-þqÎdrxôú–”zW-l³,¬¿}€ÁT»Þxj-ßÕÂÿ‰¤k×ÃVªôœ­óÖÖ>ºÿ‚vþÊ—…Œß ôFÜÅØ?‡<0'¶~ÓáI‰Ú8RIlucYðíÙ?þ‰Þÿ„—Ãoþa+ïž§ðãÿë§€A\áü…¿ù`îðð]¼•ÿáû»è±x…ÿ/ê7ÙKÍ=ß§DöÜøR×þ Íû.ZäøNŒ(Ú<9à¶Ô'Ùüg‘’=º×q¤þÄ¿³îŠÊÖ>ò¶°Bš]@¼€§MÒ¬Y@<Œ6AèkëJpB}¾¿áþ8¢MM§ ³¶µ~¨O‰{ÖšNßiùl·}ÿSÄ,g_ƒ¶;Jx>;‡\|÷º¶¹t#0É©l˜X`k·Ó~|=Ò¶;Á·t ¤ÿØz|· Œ•?jžÞK’FN ”‘ž1]àP=Ï©ÿ?ýzuvà ƒ£¬0Øjo£…q}?–)ö!Õ¨ôç©6úÊrkî½­Û± 6ðÁÅ qÇ $q¢ÇEDT{ŠšŠ+W=-oÓÑ^„Û¬ßËîþ½nSÂ×çÿÖÿÿêûøe_}üÿ’5ð“þÉ—€¿õÒ«àŸÛtû8ø—þgÏ÷ÿ’ùðƾöø)ÿ$ká'ý“/ê+¥WÉq3»Á?,BÿÓ¥€¿ïoýÏý¼ôÚ(¢¾TôŠ( Š( Š( Š( Š( ¾Bý·¿äŒø/þÎ÷þ óÿ­ïû5W×µòí½ÿ$gÁöw¿ðOŸýoÙª€>½¢Š(ä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{_!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚ(¢Š(¢Š(¢Š(¢Š(¢¾6ø›ñƒÆÿ¥miâK[ŽÒ×XðÇÀ6ö8î?áÑ-®c¸Ò¼_û@kšd«sáÏݤú/4ë«_|H‰ì%𷃼zkâ÷Æ?x¯Ç¿³ì×q§ÂÕŠÆÊóâ—ÅmKNMwÁ_³w…õ«v›OÔu}<Í §Šþ/øŽÈý«á×Âùnb쯎%ñ_‰u‹†¾ñŒ¼gâ=AæÕ|QãêrMªx‡ÄZµÅÆ¡©ßLòK aŠ/K ¿kïù4ÏÚ‡þÍÛãgþ«_W¼ÿŸOñÍuß¶±Aû$~Ô“O$pÃìéñµä–WXãᧉ‹;»¨ª9,Ä9&¾~#|}šm'ö`°µÒ~¾è/ÿj/é3]ø:à+ª\ÇðGÁi÷ÿo‚×Ç:Æ‹ðžÒ.÷KÕ>#ý’ûçîøV´ià±»s–)òÓ‚½IÚ•-––I´œäãÝsI&y9„[«²TÕäôKÞ—^¯²W“è™ìÿ>6xá:M·ˆn5-_ž'{«o|7ð~›7‰~#xîúÒ1%ŧ…¼+b~×soh&ÕõýAôß øjÞTÔ#Ô~+Ø‹mA=cáOÀo|&—WÖôáªø§âŠDMãoŠÞ7¾_|FñœÑ1x£Õõ÷‚Þ;ÑÙŽàÿ Yh> ðäLm|9áÍ*×÷5êºÖ·¢økIÔ5ïjú^¡i6Ò^êºÖµi¥i:eœ#2Ýêô¶övVÑ ..fŠ$³_I%xʦ&P§J1r•7$©Æ1MÊUê>U4•Ü¢ùi%u%RÊgviA7&ÒR·½wÒ)^Îú'¬žrÝ Òtm/BÓ4ýDÓtýFÒ¬íôý/HÒlí´í3M°´‰`µ±°±´ŠQ!·¶¶†8!‰8‘Q@`ÐW‰éß¼_ñWþOÂü@ÓæÚ-þ!x¾éþü$‘_î\Ùø›]Òõ/ø«Oðjÿ~øÏC¸D Z70‰{+/üNÍ7Ä6Þ ³”å|7ð/Àú6Ÿq-²Ôë–±ÅÓuÄÓÅmdifXéÇñ‡V¼ûàí¬n@ÿ ®<(_ê~ ê¾úø0úAï:+ÄŸ‹üSˆ·&)ÃôýÞR]ÝÝl]Hè¶´UõÓc¦96o*ÒòsŠ_taóó>~,Mÿ?²/í}žPj~Íz^ôÁËŸíÿÚ#Hcòçò¦c÷"aÍ8x÷ãWØËö‰üÿÂ}ã/ø;þïŠ^1ûgü"? |ÿ £§ÂSãÿÿgjðŽx7CûˆµÏ°^fi×_eŸf”¼MâÊ%Ww²Ž }´»{]²^Wƒ‹vŒÝ´»›½ôô[®Ý}“‡ÄoŠPãퟲOídGþ›û;j†6 ¤è´®%Ï}·’’)Iñ¢öÇþCÿi ë<¿ƒºÿ‹|¼rßòO_Æ&r«ó¢ý£~vEæI”KÇûA|›ÄŸ|Æï„2ø¿ã£ñ'HøGáHþ%x1üIñOVø4÷Qü_ÒþèK­SÆú™,ocø“eá›]NçÀÏgt¾(‹KkyBt?¾+ü,øàmsâÆ¯‰^øAðÏÂÿهĿ~(øËÿü áïí­cOðîý»âïjZO‡ôíojúV‡¦}¿P·û~±©éúe¯›{{mž…x¥Ê*RÁÍÉû°ž ã÷¹,•:”äýõ(û­;¦–¨—•áßïš©~‹º}??Cãi?iσöA›_Ôükàƒ2Ÿˆÿ>0|4XÆãpÞ?ð†ÖÜ"°’C1ADLåb!ÏYá?Ž_|y*Áà‹ß |apåTZøgÇž×.ƒ·HÚ×LÕnn\äž5‘Xe µáÚóöNø…ð¿Å¼ûP~Îþ8ø/ày¦·ñ§Åï|kømâ_…þžÞ;YgƒÅ?Ñ|K{áMh"¾²’hõmZÑãŽòÕÝUn!/KÂZçìyûdøB_øXýšj¿ýºçGŸÅ>Ô>|rð‡ö¸I/4¹uÍoh¿nfîlžëí Q¥‰DŠO·‡ñ9Wx¬»RÒJJ’ÅašÑ7*•qIJÛ^/ü/®SË(¤”*Ô‹jêê¶«¢P¿^½‹!~=»ÿŸÎžss~Åßíyà5ø…ð‚æ?øöÿ…QñGÇžЭ‚0øûvûá” C ç‚®¢¶T j°&ày»ß‚´·ƒWÍð/ÆO|]°‡|;ñÇÂ6þñ%Ú(@ø¡ðŠÃOÐì_‘Œÿuv›1Hf…¢˜Ýýp5¹V/ŠÂ·kÎŒ©âéA>²iP®íÚžmë¦×åž[R:Óœ*?ï^oE»æŠÿÀ–‹n‡¥OJxOSøóþ}kÀ®¾:Oà)â±ý >x¯àY’QxÏY{üº™ˆËñkÂïu¢xj‰Ùj>'Øü:¿¼•Z;]>c³½Û\ÛÞ[ÛÝÙÏ Õ¥Ô1\ÚÜÛJ“ÛÜÛ΋,ÛÍõ½ÿfªúöŠ( ¿i_ù,ßðOŸû;ßë~ÛÕõí|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¯h¢Š(¢Š(¢Š(¢ŠøoÆß¼uûKø³_ø)û>øƒSðoÃo ë7ø÷ûKh­wvwú}Ã[xƒà¿À+ÙcšßQøš¯šW~%EƇð}^çIÒ_Uø¢’Úx,ÇÄ?о4øóâÿüý™|M'‡ ð®ªtÚ öÓ­`¿²øNè°É¨ü2øW%ý¥Þ‡â¯×Ö“¯f’Oí&þØñ„¾*“@ðn©ôßÂß…¾ø1à}áßà Âúw ušâöÿQÔoîe¿Ö|A¯ê÷ÒÜjž!ñ?ˆµ[‹½gÄž$Önïu­Z½½Õu[Û«ë©ç{¿>ø#á?‚ü?ðóáφôÏ x3ÂÖ+§èz“Gki ’IçšY%yn¯µ û¹®5 WUÔ'ºÔõ}NêïSÕ/.õ »›™t|_ã ü?ðÖ³ã?x—Aðw„|=e&£¯x›ÄÚ­Ž‡¡hö1•WºÔµ]J{k+8ºFy4Ž‘®éT€tuàÿh |6Ö¬ü¢ézßÅO:æžu üøx–:Œîô÷—ìÑxÄ÷·–ZÃ%Ö`¹ñçõ_ølÏéºUÖ¯â ,´KÏ)9øãûLfÛàô:çìýðF䕟㗌<7?~ iÏÁ‚ ¼[§Í´kÄ%­~'|cÐæÔ& øoá­¦jZWí~øOð[áÏÁMûGð‚lgÖïÿ¶|[â}ZÿPñŽ<{â'‰aŸÄþ?ñ¾½s¨x£Æ~"ž%XN­âRþæ HàÓìš×Mµµ³€àoÚ‹àþ)þÍ¿´?Ä/Ú¯ÄÚv¶þø#ñwÄÞýžþjš|ð&±£ø ÄZ†­ø§Q½²Ñ¼CñÓÇ:%å½®£e®øÇNÑ< ¢jö¶z§…>èºî‰.¾›DHÑ#8ãUDDP¨ˆ *¢*€ªª PºŸÚûþM3ö¡ÿ³vøÙÿª×ÄÕÍ„'¯^¿—ÿª¾÷…X,K{¼S髵*v_+»z³ÇÌ‹}=Ÿç)'ú^Úéäpßüogð÷—þ%¹Óõ né'ÓtÃz:G&µâ¯øƒQµÐü/ám9ž(§âzþÃK¶šêX,lÍË_êW6šm­ÝÔ6¾~ÎVÔt¯ˆÿ´,º_Ä?ˆ–—j¾ð’y÷Ÿ þ\£4–x/÷ñÅkâi‚O*ã⟉ôé¼Uwt³Íá¸<¢]/‡ ó¯Ž7–Þ›àçÄý`çÁß >2hÞ3ñó2³ÛÙxKTðg޾Üx’ýY—Jð&­ãÍâ­uŒiºg….õI•dâ¾öŠHåŽ9a‘%ŠDI"–6WŽHÝC$ˆêJº:ÊÊJ²A ŠüÇ> Îã›á2U­…ɧ¥Šä§)Búõ+TŒåZQ·µŽÓŒ#E·IóÛšw^ÎGB‡±{Fu½£…Þ®šJ-(§·5ïÍdÚÒú4>Š(¯ÂèÇm<×},—ù¯ø'·7²ùÿ_‰ùÿÿNøà?Ú;þ ûûMüø›ðãöøµàŸx'K}cÀ_²´^ºý õ‰<7ã/ ø³H¼ø_¦xçÄ>𮽯øw[дïMá[WÛâ+FÔ|;i¥ø‚ÿTµÐ5?æËÄmÙ«áÏÇø(Gˆ¼!ã°ßíWû*ø»Äß/?gÁ:þ1ÁUe?xü&øéàŸÚ‡önñ~ âÄÙ¨ø·ÃÚÀψ~;ðÅ·‡%¼Òµ›ß…žÓ&»’;¿íIõþŸä×ãwí¥ûgÿÁ=ü%ûb~ËŸ³—íwû0Cñ/âW‰>6øàÿìïñ{â?Áï€Þ:ð÷€¾3|oµÐµO'ÃûOˆ~1_–z&½{¥øCFñ'ÆŸ„ uÿ„Z/ì´ø·â>›ã_ ê‰öY*¤`ðk ñt¹ç^¥5iIRµ‰8·^Ñœ”¬£?hèÕ«GésÎ*êW³ÑuÕëË{vmµuåtœ¯øáðVíWTøµâ¯Šà§Z¾½ñþ)ûG~Úš=‡Ã/Œ_µ¦ðÓÂ_ðQÿþÑ~>øðCöyøwcàïG ißþ|1¿øàí;öðÌãWÕ´­çGÿ„bûÁZ½æ‰yöŸì•­ÁF¼IûaþÎz÷íŸmÿL³µÖþÿÁ,u¿‚6Ÿ4K üµ×5_Ù[Å׿ðP‹_ø(/‡üyc¥üÐãƒãôÖÑøßBñVcûUé7IàÛ€ÖvÐë/Ùß ?àâoØÛâ/ÂÏüeºø1û\|9ðÅoÙãöÄý£~ êß¼ðŠÐükпa=#Å!ý¢|ðþ? |tñb/Ä xÂZ¦±¦YøÂ_ x7W¶0ÛÇãX5(ï¬l¼ öÅÿƒ‰¼5àoØŸã·ÆoÙ·àÇŽ¼ ûGx7öSý”?mŸ‚žý±|£/‚þ-~Í´¿íð£à¤_ô»_‚_5]NæÇA›âÖw§j¾/ðV­kâ¶Ñõ/x[Ξóéâó:ó–yUr¬þ¬¥Ëª3«^4Ÿ³qºŠ…ZnnTùiª´§Ëfá• ’—´vV•µw²ºÑ½ìî¼Ú{Xé?ೞÿ‚ jµ_ìûðÃö ×h-À?¶÷ÀÍsöVø›ñKáÝßÅI~~Æ:ç…¾?|'ø…sûRëW^ šü9ñÖ£ð‡Ä?þè^)}K¾&ñ]¤?ÙºV·y¨xGB³µüÃð)ÿ‚ÑþÐ?~ ø§ö„ð÷ü7áGˆ|IÿŠÿ‚g~Êßþü3ñ?íWðºX?dOŸ³Ç‹| ûY|t´Õ~ëñ…>üzøâÛ¯üFøÏá¹ô/ê$ƒÃrÏãÍBûÀž»ÐÿiÓþ …ð«á—Ã/Û7â¿ÅÏ |Sø‹áÙsöçý®ÿeÝR_‡~|‡Âšìµ§|?Ôµë[ÿ|bý³üEà‹^"øâÖ/êŠþ|_ý n­Ã/ØóGÿ„#ÄÌ}ÏÃÿðYoÙóâgÄ}'áŸìñð;ö¹ý¨õKß…ß³ÇÄí{\ø ð‡@×ü3ðé?koƒþ"øáû4øWâ•ÿ‹>!x2÷áÞ¡ñgÀÞÃã¿iºÀÿ‡:†½á¸>3üZøkiª èz0ÓÅС K.¡%F.øžF¥¤¡&ç9r¨Ý<=5t®à¹•IÞŒùž¶ÓòKÏ}¶¾»̇ü¿þ ÿñÚÓRÿƒ}>|Køûwü(Ô¿gˆðZ­+öÌñ]ž™û[|,Ó~k|ÂSà_p¦‘áO†_þ-xgJ𭟆uÏÙßÇÃïxÓZñÕî‘©Ïñsŵ-CìïŽÿ‚~ÒŸðhÆ·àŽß ¿jOŠ·g‰t¿é$øiã…ßõ¯Ú{Ävþÿ‚Žøtønç_ø{wáçø›¬ê–ß¼+¢x¦ïWÔt[STð™ñ¦§wwiqs«Íõgì•ÿø¸øÙðãþ Ãñ‹öƒð]¯ìÃá/Û)à§¾-ñ-“|9Ó>)ø*óá?ìámÇ×<ð÷Æ/öŸð¿Š¾h¾ð¾“y£x²ëƳÄÏ|Xñÿƒ|sc |>ø/ðú|KñÑ^'ÿƒ‡?bÿüø¥ñ¿ÇŸ j¯Yü;ýžþ~Öþøwâ|.·ø•ñÇöXý¡¾2xSàWÃoßtÛ_7ž¼ð=çþÒ? >!þÎÿg_€;øUãüðÿˆåñgí]àÏüDøâñµçÇøàï‡Þ,ð?„¼Aâ][Æþ-ü6±ðžžÖߣð®©qka?èÿìûUx3öÇø=mñ›ÀÞ ø§à _ø£Â3xâß…l|=­ÿiøORm:÷Sðþµá½wÆ>$ø#U>]ï…¾&ü$ñïþx®ÊV“Ãþ.Ô'´Ômìé¼D¨ÃÛÓ’K–0©¤W,¡EÅrÂÐqTéÓP’\¼­nÔZ–×3³ï¦ï}ï¾­½}:ŸQ ã>§ôÿ9§Ò€¥-tQŽÚy¯’ÿ5ÿ憘e·¸Š)íçŠHg‚hÖXf†U),RÄá’H¤Fd’7VWV*À‚E|I㯀¿Á´Ô¾"~ÌZcÁaf'Õücû8ZÌað7¬¢g¹Ôî>iÒ±²øcñ)¡3M¤ØèCMðŒµiÞ)Ѭu=MwðOŒü7ñÂ>ñÏ„5(õ ø¯H²Öô]B4’qc} ÍšÞtŽæÎîÍo{cw7–‘Ogy 70Kuð?ì¯kz÷‚~!ÝÞéh:&½ñÅßü=áù‘âÿ„^Ëâ§|cãh|$°¿6¿ðé–‰a%ˆâÖu•HÞÎÍ÷èAߟåþ?Ê¿V˱³Ç`0˜¹ÓTêW¡ Ô…šQ¨Õ¦¢¥ªŠš—*•ß-®Ûêüøc_wüÿ’5ð“þÉ—€¿õÒ«áoÛÙ·ÄØÿ¡óà?þ¯ß†5÷OÁOù#_ ?ì™x ÿQ]*¼~"m¬ûâ÷\ëÁ¦[»»Sú^‡¦ÑEó'pQEQEQEQEQEWÈ_¶÷ü‘ŸÿÙÞÿÁ>õ½ÿfªúö¾Bý·¿äŒø/þÎ÷þ óÿ­ïû5P×´QE|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¯kä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{@Q@Q@„€ $$“€ä’OÉ'¥|¯k¾"ý³õ=KÀÿ õýSÂß²–•{w£|Kø¿á«û½'Ä/í$ò5?†¿‚'2Xü*ñËÙéšy…,|#㟠hÑÚxmyȼQûbxèËý…ðÇá'À]%ä+kªüYñn£ñÆÉßü4øWqá[º>K?š ’Q"‘"O;ÿá›|YâÁæ|cý¤þ6øÙffk¿ øZ²ýŸü³kM5~[hŸ…ƒ‚©,!ø¹âi¥E1=Ñ‚Ib’¸‡òn*Á}K4ÀK·,>#L5|%I$J5f½µ6Ò\ËØÔ§RÉNåVË ^¶|ô§-9¡ñ©Ç{;{¯ËÞ‹]Ñï_m?ÙÓÇúÖàÙü}eðßâv¥{g¥[ü"øÀá—ÄÛjùŒvúFá¯Mf|atò\xïÅdÒ2%½üÆDÝõU~rÃ~ɦÞi²~Ïÿ ®¤Ô²u=wPðÕž«ã-VR‚??XñƦ·ž1ÖnJº}W\½–B‘œ×;mû0üCøt7þÏŸ´¿ÅÆRÏÂ"Õí>&x"ÝB°ŽÏOÐ>!éþ+Òü9§&à¢ÛÚV›*ª†Žxæiü+5ðW3Ã7S&ÆÐÆÓߨbeìkÆÏH©ò*SIYs{®Z¾EdŸ¹K9£QÚ¬e Y]ÅsCo'uÕŸ¨€`è+òã—üCöSøõûeÙ~Ü>$ñÏÇï|T‹öƒýi½[ÃñgÃõøuâ?Š?±“âo|›QÓ¼Uð»Å~.Òü=wáïj?Ž|=áxfÇÄI–«n4ŸÇq¬\úd_¿à¡¾h!Õ<3ðã–•jq-æ‰~xŽú0Á‰ºÔ ÕüY¦›†Bȳißm L!k!š]¯øoøZ9ÇÅ_Ù/ã?‡¦„dÏàm[Á1Ò ‚7H/+è×ÄÅ‘ð¯Æeºñ?ƒéÆÓᇈ>9øxß|Zð—¼oq᯲éV—þH­ìôÿÒ½#þ Qû8ÞcûOøÕà³´’5¿‚uäSÆçøu¢xæÙ‰•æ[xÕË4`¦þÚÃþ û^GøÙ¥éŒ“â x÷ œg÷⯠èéë1—$9´–7;Sœ«GFr®ñ7ö§ìê:¾ÚÐ|©BÖN£ŠÒR”œùœ¥vãJÉE©h–÷ÒÉ]ùÙ%é²?;þ'Á¼¿±OÅOøYW:×ÄŸÚ{IÖ~(~Ó·íC«ëºþ-Þ‘â¿ø(¿Â=à·íའLÖþ ë~_'„4*‡­­è:çÄëvïÄ øn¯,î=Ïà§ü»övýž>*|-ø­ðwã'ícà¿|5ý“¾øóÁþøÇ§øCÁŸ´µ·ìQð“Tø'û?kÿ´u¿„< x“Æ÷^ð­-‰<áïx7àï.ìô›¯ü1Ö†‘§Coö-¯íËûÝö¥ø fY7ÿÄßâ—ƒ´0ƒ’S¬êÖNr°JRf+â÷ü6·ìmÿGkû2ÿáùøYÿÍ]k ~-ÇÙÏ>UYI%ÈÚ—-šÛžÓ]¥ï«JÌJ uøue÷ì¾~gæ×ïø ?ìià¿fo ÍãÏÚ#Ç_?d{OÛ‹Ãß>øçÄß .þhÿ¿à ÞÖ< û@|-×µ|ðßÅ/x5ìüGâ=[Á×~!ø“ãmV×.âÔ¼a®èú^…§àøŸþ ãýŒ YxZÏÀö~;ðfƒ.¯©|@ðÄ?k:e¤v·ž2k‹{+Ë_Ó9ÿn¿ØÎи?µÀ벬düGðζ‚KDtmBüMÇÍ4EâSÃ8ÓP×¾#|C:Rx‡S³ðÏÃ|:ø_àm;-FÒ´?ü1ø{àxwLÓmít_ Ùn¸y¸ cþ kû=Ú<‹áï |yñ°PÆ)4oƒ^'ðÜwÆß,üN1ù‡!MÈ·QîR2®|ïQÿ‚–ø—R¸û?ßÙKÇZœ’|°·ÄOˆ¾ðvâ͈Ë[øþ%â†\3¯ÙüÕ  ›5êQXº±PPÄU‹PÒjÎ>äc+ {°ŠI7Òú½L]–­¥¾í.×þ¿àŸ«”×tIcŽ5gwv ˆŠ 3»1 ªª f$$€+ñêããŸüsâd-‡<1ðãá-Œò‡ŽëEð¹â?Aß¶o|Oñ&…áIã]Ëæ]ÉðæS3&cŽÐnŒò×ß±—ÆoŒOç~Ñ_üaãËçK™ü?âŸÞ꾯W? <oà¿… …;"'E¹hÔ0畦÷°™6m^ÞËV)Û–uå4Ú[^^ü“òt×›ŠÔÎUèÂ÷©®‰ÝýÈû3âÇü'ötøsq}áÿ k÷?üj^øB¾-ŸŠþÅy²M°ø£Æ‚îÛáÿƒ–9•î/øžÓW‰$i£ßÈ<“ðN³í%ûux‹L»ø‰›á„Ú&«££|5ðýÅõÏ€´»û+–¸³× ÿ³½ÿ‚|ÿë{þÍTõíQ@!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^Ð_*üBñÄ/|j¾ø#àˆšÂ=3 ¼'ñ;Åþ/ðÞá {Ç:ÔŸ¼Uãß xSCð±øƒáïx3IÓ´–øeâ{ÿ^j^ñ ýÓj¾µÓ_GQ{=ç}ã¯Ú[örø_¯ÉáO‰Ÿþ ü;ñL6¶×ÒøoÇ_| á~++Õg³¼“G×õí?QK[´V{k†¶NªÍ¸×ÁÿµGí9û6隈ÿhß?¶?ì×gñßá_ÂG¤xa~'|5ñîñŸ@Ó,gñ…‡Â{ÁúW4ojŽ£â]*¼¬x_X²ñ«ëzœVpkZæ§¢êU­<·K/ÇRÃc#Ë+Ç UT)ÍJ¬!.e(TqM+8·¬n®kE.x¹Å¸íµÕÞ‰¾ë_ÅMü>øñáïÇ='ö{ø¯ãÛOŠ1|@ðŠþ!ü-ñíχt? øÉO€u é^2ðŽ´Ï Eaá]JäÚøÃC×|+âMÞ·¾µ³ñF™}£µÎ‘m}õìÓEo·ÇFóM4αE Q)y%–G*‘Ç+;»°TPYˆšüNý—~-üÕ¾"EûSþп·ìÝ­üL¿Ñ5è:ÇÅO†^_h^ëZdZ5‡†.|U¿Ãº(Kí_Rµ¶žë]ÖõÓ­[jÚ牯¾Ë§ÚÁö_Æ?±·Ç†Zφ¯þ9øsâ¿„-5Ÿ Ï⿇_³ïl¾%ø§â,7ƒÅ¥ü4Ö|ð®x»Äþñ½ú w¶–¶Ö^%Òí¯tí~äøU¼AÛd²­K‡¡ŽÆÒÄãR—3UáV¥®Üa)©IÕœcñO[íy[™•lç)F.1ÒÚ4¶ZÚÚ^ëO?32ïP×ÿn-N]'Ã:–¡áߨ®ÂY-¼Gâý*æïKñíc{ktðÞøWÁZ¬¶÷Ú7ìâ –ž'ñµ„j?UæÐ¼woðÙ¯üAã?@Õ¾=‹ÝBãà×ì•འâWŠ|ÑxS\×"•¼=û=üe¼6±é>-ñvg5¾±â ÜC¿Â†¶úÇŠíš+k¿ÃÍêA‘eðÛãÇ»k8~*%ïìùð&ÚÞÞ×Gýž¼®ZØüEñ^‹mÁgañ›âGƒoZÇÂZ Z¤0ËðŸàÆ­öq ËÄ_üS jƒ¡íu¿²ìÕa¡ü*>7øià)t+(ô¯ üð ½¶«ã8,-í±ð¯Á¯‡v:¿o-í”æHt/ \$-"™B¼Ê_Ù2-|7ýœ4Ý Äö¾-ø–ëã‡ÇH!ºŠÏ≴Ë};Cð%µúì¼Ðþ |?‚{Ýáv‡$$YÝßiòê~>ñ5”pGã¿xºKki`úV¾?OÚ[âwŒÁOƒ?²gÆÛËů‹>36…û8x(–ÿVotßÜ_ür´FáÝ¢øyåÇÃÏÛnÊžý³ ýž<5âÚ ÅvwØÈ³Öt¿ƒú?Œáp¸y®|Y>…ai,÷—–ð2ÈjÛþÃßuO&ãâÅ—hÝI9g¸ý£~ x«âæƒqs[¨>x‡Qoƒº¾hy~øu Ú¬¡]-Ô¤{>§Ð´ ÂúM†áIðî…¥À–ºf‹¡iÖzF“§ZÇ÷-¬4Ý>{;Hø!·†8ײŠù_Œÿµ—DCá'ì¡kàM*î ñø·ö§ø«¡xx¢l•¼±øiðvÃã_‰ï÷&Ó‘âÝkឨ²_.œèHÛàí1ãóç|`ý°üMỀ¯sàŸÙ{áß„>è@ØÏão§ÆoŠ×)ÜTðç‹ü uw7úBZiѰ‹í:(óGö‹ýŠ¿f/~δ—!øQ¤øËâ>™û?üh¾Óþ'ü\ÔüCñ«âvŸ¨GðëÄsCLøƒñoWñ§‹4{³qNHÕ¬#‰Ò5‚8£Š(ÓèÐÀp€àÒ¶?kïù4ÏÚ‡þÍÛãgþ«_V`O_ËüOø~uöÜ5þå_Ï+ü©Q±åcÿ‹ /ûµnËß—ÝòÕØŽž÷ãüÿŸZ: pôôG_Ì÷è¶éÑjÿ­€Aø÷§OJxOSùŸð©¥Cšé¯åþ×’²þºtùëäFÔþ_çüiàÐRÓÂ×çPäÞÿpÿ¯ëîès÷þðΪIÕ<;¡jE¾ñ¿Ò4ûÂq2n-ä'‘Ï©®Nëà¿Â«âLÿ¼+î¿eÒ-l?!b–Ø>àß<ר§æzÒÖ2£F*sÿ8ËóL¥)¯†R^¯Èñ9?g?‚òœ¿¬ó’w©ëÐŒ·'å‡UEû«Œ(á@Oþ—à‡ý ?ùrx»ÿ—õïVÂ`ºá0Íÿ׊Môþçknií*­êÔ^Jroó²ò<.?Ù§à”d•ðDg<3_ñL£A.¸à}F ïZ–ß~ÚœÅà-É9ÅËßÞ ôû·w“Œ³Œgœf½!ïÇ·óùÓÀ ¨ú®|8\4}(ROïP_…‡ÏY¯âM/9ɾž}ÖÚz‡Ã‡ZnÓcàOÚºò%ÚGŸIÎÖ†c‚NÝÒ½v–¶V–1,V¶ö¯ÝŠÚà‰ÝŽ%D_ÁEZž”ðž§ðãÿë­#Cà„cþ¨þIÜŸvï¯ÏD×ßmˆéá~?Ÿø:: Z õü?ÏËÐ@è?ô´ð„õãë×òÿõSÂî}OùÿëûÔ¹¥çéþc# OoÄÔëÏ×ü?ýtê+76ü½?Ïü¬È¿·Gü›WŠ?ì|ø ÿ«ÿá…}»ðSþH×ÂOû&^ÿÔWJ¯ˆ¿nù6¯ØùðÿWÿà ûwà§ü‘¯„ŸöL¼ÿ¨®•_?Ÿ|?ñb,1ׄޯ¥?Ρé´QE|éÚQEQEQEQEQEòí½ÿ$gÁöw¿ðOŸýoÙª¾½¯¿mïù#> ÿ³½ÿ‚|ÿë{þÍTõíQ@!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^Й|Cø7ðÓâ·ösxû¶ºôºOš4ûŸ¶êº]í¼sàËÛ´[ý:ò[Vaæ}–yä¶Y|± ~züËý°þþÊZ?€þ&| ðæñÇü>ñ?†4í;AñO‹µ ?†w.ðΩ§é>4ø‹}yâitÃLIÛ_±ðÝÚ_x£Å–Z}Ëh^Ô´»m[PÓ~¥ý¥|OûEj~%°øað»M¼ðW…µÛ8ÅïÄ ­µxŠi£-} x*ÒHΓàX,!ãUñ®½<úŠ’×L·ð_›¥ü@ƒ_à_ìá†Ú}µ×ˆl,µ-PÏ.¤ÚGqªi±ê·sÇ{yªëú¦¥¿Rñ·‰//¢ŠûRÖµ×”^êpÁ«Þêx’)¼OËUTÄ*”b:RR§R¬•¥8´ã(уî›^Ö¢åZ:pª¥ÍO•§»M4½5×ü—Þ™?d?þÌÿ|„¼wá)4ßÚ ÂÐjþø‹Ä~)ÐüQi§\ëW x‹J²Ó6~Îÿ>:Zi"‡Tð÷ü';ßø⟂µ |7ñ#À£$‰öß øžÈ ¸mçIe‡RѯÞ‡«ÚÍ5®§§\Å!Ã4ýã>º²üý¦õ/]isÏcyðóö›øãoüÔügw§JâßÃÿ4ïjº6¡à_KwVÖßÃ_3\ÛÚizTö6Ú=ÍBUa%NªuÒáì¯jÐ_þÜoJm7j-›NÛ¯šÿ'Õyn¼÷:Oøc? x¯þ.|zý¡nÜ)¸Ó|kñ+Pð7ÃÉx­¦øAð>ßá_ÂífÁ>t·ÆñV¢°1[½Röf’âOøqð{á/Áí)´?„ß >|1ÑÜ(“Lðƒ¼=àûŠ’CÜ[hvŸÄ…‰v–u’GviÙÙ˜ø'ü3OŽîðí³ûEép"…‹Cñö‹ðâ¶‚6ã-÷ˆ¾ZüGžN6±ˆâ7BÙ‹ÍÙ2ðŽþÞ>°ø©û.|W¶Oõv^&ø9ñ3àþ³ Œ^x«ÃŸ~*é¹ -§Ã«Ûä"†èö òøµû_x+âïÙÃ^*ðføûIxcÅOp£ñYülð7ìîHË‚ÖÒê2Ç ¥¢[눔]H/탦iÂÅýÿk†¤®7?¼IñnÞÙ»™ïÿf›ŸŽ:zB 3Ix.”©’âæ$ù¨ëê+å 7öèý/ï`Ò¯¿h†^×.˜¥·†þ'ø‚/„ž)¸)fŠ |PÂ!šU@]âMivP€šúWAñ'‡|U§Ç«x__Ñ|I¥Jq§ ê–:ÆŸ)*®wº|÷ÎJ:>Sò²·F€mQEó·í}ÿ&™ûPÿÙ»|lÿÕkâj¢=ãÚ¯~×ßòiŸµý›·ÆÏýV¾&¨+í8r\¸*ÚoŠŸþš¢yXëûXëoݯ_ŠÖÌ`@:óü¿úÿçŠ}(ô§„õü¿Äÿ‡ç^ëmîÎ;%þ}~ýÈê@‡¿ÝÿÏç\'޾'øá´:qñf¶–z†·4Ö¾ðÞg¯xÃÅ—ÖñyóiÞð~…k¨ø›Å:Œp:[= J¿¸†×¤P#ʼ„¿´oĵóü9økaý™ðïÀ> ðžÊ¡¬|ámÃŒ¡íô[(œ€«Ë!9Îy¯Ì1þ4ÁÊPÊ2YI]¨×Ì1·ÞÍá°éÚÚ6–+]b­¤Ÿ§O'ÙÕ­ëqÓÿ—Í| ¿‘ò”?´.‡«ñ࿆_´ŽÉÏ—6ð+âG‡t«Œ ƒgâoˆú¼){’Ïg®Ooœî•v±[ñøïã¶£ÿ ¯Ù3âVœ­þ®_üEø ¡ÆÀ`ïdð¯Å/^Dާ1¬¶kp¸··#5öµóÕ|Sâ¼K~Îx "z¥‡Á©òídž&x‡®¯W.Ú‘Ë0±I5R]ï;_ÏÜQ­ÏŒ–óö¯¹æÏàÂ{@Nø‹öÕì“…/ÿÿÀ(MÀ³”wuˆ«"I)haíGÁ_Ù›ÎíUñKqúgö4“Û,z‘Ô}HŸÒ§¬éñ÷O|ÚÛ+,Y¾—ÿ˜6ôýw `0q²z¾z·ÿÒúØøÀêµe¨-yû?|4¼\ço‡?h{«ùJàÄ?<%™êTÎ&Þ•yLÛâÆÝ4­þÉ?nbP|ëÏxëöñ¤~ô†ßYø»á zæ"¤yka¡Ý^;Sd +?ÚôW¥‡ã®(M:˜úu•Ÿ»SƒŠk§ð°ôýV!àpÏì5é9ëëy4φåý¤|¤ž5ðOÇ/‡Œ¿ëgñgÀŠ­¡Á½öøgÂÞ#ð4;qœÉâe¡¤ˆÉ;¯mà__þ'É$þ(xÆ×ûVáŸhzÆ«béŸ2GI²½—RÓn"ÚÂ{kë[{ˆYe‰HY êþÏzóˆþüV„Cñ3ᇀ|zU`›Å¾ÐõÛ»CŠ[ íFÆ{Ý>âÞEYm®¬§·¸¶™kyc•ÇÑ`üAÍ—*Å`ð8˜«'ìý¶¤»·?i‰‚~q —‘ŒòêVn3œlºòÉ/—ºþWù˜Á ëÇóÿëž*@ tüÏZòùÿe;O;àÏÅÏŠÿ %o#Ã×^%¸ø¯ðñÉÀ[y|!ñYüS{£i‘º=;À&ð$qÈ7$ª'øj þ"ü·Ô/õ;xUyâÚåÝïŒì£Ü0–_¼Mñ{T˜¸oìëxÑÊý^ ŒòÜ_,11­€›KZ‹Úaîí¢­JòŒVîuéQ‚³÷¶¿ðucwT^ZKÿ{ú&ß‘î´W!à¯ø/â>ý¿àoé>&Ò–êâÂâãKºY¤Óõ+9í'V³m—º>³§Î¯o¨èú¥µž§§Ü#ÛÞZA22Ä!>ß_ðÿWÕBp©Ô§8Ô§4¥ ÂJp”^ªQ”[Œ“Z¦›Låi¦ÓM5£MY§Ù§°ÚpR{~&¤ ¹õ?çÿ¯ïN¦Ú[± þÝ*ìÑâ“ÔÿÂyðÿWÿÂúûgà§ü‘¯„ŸöL¼ÿ¨®•_~ÝjGìÏâ’xÿŠóà/þ´ÂÿóÍ}«ðSþH×ÂOû&^ÿÔWJ¯ ÿ³½ÿ‚|ÿë{þÍTõíQ@!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½^Ýñ‡â¡ðËÀ—>+Òt ?ê§Ä¿ü+¦hºŽ»?†´û­CâÄ øÊkýrÛBñ5Οg§Üøš-FêK}SžH-­üÉVDÉ¡‚Ââq˜šžË „¡[ˆ«Ë9û:)Ê­Zœ”ã)Ë’œ%.XFS•­¶ÒwNœêÔ…*kšu'pÒæœä£y4•ÛJí¤·m#Ó诖?álþÐ?ôG>âAøÛÿ¡¦ø[?´ýσŸø~6ÿèi¯‡ÿˆ£ÀŸô?‡þó_þ`=OìÛþþÃÿòÓêz+åø[?´ýσŸø~6ÿèi£þÏíÿDsàçþ$¿úhÿˆ£ÀŸô?‡þó_þ`ìÛþþÃÿòÓ¡ø“{û@ø#ÄÓxãÀ~•ñ‹áä–Vqøƒàà #Â4™,Ã%Ö»ð£Æ—×Zw…¼KwyW¼øñ"m Û°÷šOÅ ¢ðÞ¡Ø|(øÕðïãN•©êÖ.%¿ðî 4oøG^Òõ? øóÀzÿ’· áÿx'ĺ‰<+«˜.m­õ}:Þ=JÂH5]&ký&êÒúøqûZîüX×¾ |Qðm—Ã?ÚjöZ…u½;Å·~0ð'üA7…|;ãÏ i^%Ôüà[›?Ùh¾+Ñ."Ño´d: šátË«™mÖ)½âïìåàŠú¶™ã{kíáŸÆO ØÉaàÿß ®ít?ˆš ›Jn?±5  Ç^ –éÍ÷Ã߈š'Šü{pßn“AMR+]BÛë2œç,Ïp‘Çe8ÊxÌ,¤áí ªBQœlÜ*R« u©JÍIF¥8¹BQœSŒ¢ßŸˆÃWÂTt±Ý*‰'ÊÜZiìÔ¢å.ŽÍÙ¦ž©£è +â¨>?|Gø ,z/íy¢èðx9$K]3ö©øwa¨Åðšé ‚+y~3xNæmSZýŸõ)C ¹×u[ÅÕ®.~"øbòþÇÂñ}—e{e©YÚj:uÝ®¡§ßÛAyceqÕíÌK5µÕ¥Ô$6׺Kð»Å,N²Fì¬ ôÌõ-/LÖl§Óu:ÇUÓ®”%͆¥io}epƒžÖê9`™CÁd€`2|Õ¯~ÄŸ²ˆµ 5«¿Ù¿àö›âFÅ^ð>‰à¹¤\x¿Ávº‰”Ç#¼Ð•ÕA†gi¢)+?QÑ@!Ãø?Hù¾üeýª¾8æ(ôoÚOâŽô«R>à±ðÇÇoâÏ„´ø#çmŽo§’I{G'4˜ýªü=σ¿lëìÿTŸgφ>9W·Mð:ëöb–QªZ¶r…òLŒ’Gõíù‘ûS^~Ü:'ìÅûFZø‹Ãß²¿Ä­O€ÿ ×5ïø³âÏÁ}rÏH“áÿˆ“RÕ4êžøë§jš•›Ksg¡_xÿEµÔ."[I¼E¦¤ÆîÇÿŒZGÿco‰gÌÕþø³àÄ­ ÎHµ—âw„<}9 nAkà ·/ RR±·Öµ÷üšgíCÿfíñ³ÿU¯‰©këòÉàêµRP¶&z%MÇJT]ß4¼¤•––woÍÆkV+•?Ý«¶Ú²æŸf—žÏæ|™ÿ «ð;M>_£ø·ðªEÏ›'Åo€<£G´áŠøÃ]ð¿îÕy.ú‰®ãE•y®›Oø»{ñÒò? þÌ:߆|K§µ­¥Ï‹~9ƒ‰~xËR´ŠöÇOÑa±½·‡Çÿu‹{ë ÚjºG†´ëˆtуïß ¾øoá?‚4?xZ+¦èÐJg¿Ô'kÍg_Öo§–ÿ^ñGˆµ)Ÿ«x—ÄÚÅÍê×%®5VúêêS™6Î(;ʰ_ë’Azf‰ ijM¦øCÃ6zƒ43<±è^Ób‘£>ÇEAuumcmq{{q¥¤]]ÝÝM½µ­µ¼m,÷ÊÉA<³M+¬qƬîʪHþkR«^¤ªÖ©:ÕjÉÔJ’”ç9ÉÞòœ›”¤Ûm¶ÝÝÏ¡’QJ1I$¶I$¬¬—É~?”ø)oÆ?ˆÿ³×í§ûH|søÇûWü{ÕÿcßCûiðé_°Oí¡á¯~ПðN=sÄúΕ¡Í¨üqýˆ¼E&­à/Ú;Á?µ‹µ?R‡Wø…à/ˆþ'±ðÚ•§‚ü-täÖìáø×ÿÊý¸~Cñ§ösðçÂßÙ²ãöåý“uø(§Ä_Újß\ð7ʼnþhÿ²ìðþŸÁŒZƒl¾2éþ6Ðãý¥áøðoÂ>Ôuˆí’ëgÆŒú6Ÿoq¦#ö‡ÅŸ?àŸõ¯~Ó~=øƒÿ×ø«â?üDðïÂ~Ð~/ñ_ì¿ãoÂÿ.£Õͤø‡ZK +ÇÚ¾©ygckÜá±_«ÒúæYV¤ð”!I'FpŒëFJö$iœkâ+¹J—Ö+}]ÊŒª,V"¿3NíFvæwÝh·~wnÑZ;+ëgÇòWÆ¿ðpí›ð áž¿ã?‹¿¿g/Š:ljàŠ¿³7üW᎛ð—Âÿ¼ máo~Ð~~ÏW¾ GâoŠ?î|_á ê¿Wâ^¥¬ø~Oj†t[ï <úÂÂjX¾/~П5Ïø$·üãÇÞ-øáû0x—ãÇÀŸ_··…ôŸ‹¿°į¿‡¼+âo…Ÿ¼gªxGPÔ!Ôõ­cÆ_¿hÜ6ïÆ? ­¾#xïPøuâ;M7QÓ<}%ìé׿h/ÚÏþÅû-øölø½ãëŸØºãÀ¿´ö‡áØà/| á'ðLj¿g'ñe–‹¯ü:µñ¦šñøgKýŠ> jOo«|[‚]r/‚ iÖšžŠ5Ÿ èš¿Øþ½ÿ‚x[áÅ¿ìÇàË¿Øëß¼oã‰_³=¯ì÷á‰þ i ü]ñ=b/Œ³ô ô§·ðÆ¿ã¬$ñ¾Ç ]ëQÙɬ/Š4 ^ƒÓ)áù0õiåU0íbGS‘òÕ†ÓŒéE5hºNSIZr©'VÒ¤®Ò•Ýê&¹mkìßWë£Mí²Ñ³ù–ø]ÿÓýªÿdïÙ+?оøuû[ë_ ¿à‰_±‡íåðÿâ'„¯~#Kã7ñ_Ä¿ˆ_ ÿf=cÁ_µ_мG㿈~;ñü^#ñ2üføƒã=þ JãFÑÅ{¥É¢x{â¤ÞžÓTø}?ƒRMuü35µÿ…M²xn,æø)WÅ¿ø%×üsö/ðì¿¿a/øËöZø±û@x_áÄ_³×À?Ùƒöxñ„õо#ð‡¼s£x·[øMâýKáÇýA¢Óþêç^S¨ø5i4­,g¦»Óý|=|4ñTr«Ö­Yò9A9N\´ã?gNMQ‹§(ÖN3ŒÜÔ¥ìêS‹0wµÜôImÓµÚÖÏMz[ªlüêý¢ÿàáï_³?ísñ;örñŸÀŸëÞý–kÃbø×Ã~ñä’|(ÿ‚sø’÷övоüp°¼yqçÄýkÄ?´^›mâ“ ¾±¢jV>Ôï´¿xv×Äöw:«ÿÝÿ‚€øƒÁ²­‡Ã_Ù¿à*|VøËÿ÷ø—ÿñ¾³ã[¿x7àÕ§ÃKŒÞ(ðïG⿎¿¶§ìÓá߇VzÃ;þ+øÛñþ!üb×<5»kâýöt¼ð¹Öí<+÷Ùý»?à“¾-ÿ‚†øCþ ñaû'øsÆßooÙã¾(ñ‡ÅK_Ùßà&¡ðwâ7Âà»ñÁ>;øÇS×íümãmà¿tOøG|!¨øÆ~ÐôwÀzu­ÅŽ•4ÒiuXéßðJÚáý®…ceÿøý¡þ~Ƕºe–imû8üZø}û-é¾ÒÂËOµÓ¡Oxsà•†ô ­­¤0øbßGÒ|>¶ñGog¥ƒ²/ J49òÉÂ^ÎG)¹J3Œ£8F|ŠQ²~~H¹Æð„²‹rV“nÕ4Õ.÷Ñï¯ÙµüÛî~2|^ÿ‚æ~Ú¶Ÿü{à߃²·Àȼ û=| ý‚>'ükµø«ñûöhðï‡ñF‰ã?øŽÁf–ݯtørûRÑ5{AqÐ>úâ42Ä_|n£¹ª¥ ;5fº;¦¹tI§æ#çߊ?³Ç†<{«7Žü/ªj? þ1[YÇi§üSðdVñêwÖö¤Ée¢øûBœ â_„ã²Â?âÛ{¹4è®.çð¶©ábtÖ!óoüEñ4(“áÆM#Mð·Å»=:ëWÒ®4V¹o|VðÆŸ<×~3øow}$×q‹n¬ãñwµk‰¼Oà[ÛëD¼—Zðæ¥áßøƒìºñ?¿ ÅÿÏ¥èúŠxoâ'†o#ñÂ_ˆÃÏ௉5½Ïö®ØŽG¸Ñ/¼ûŸøÇG añƒ5x~åM¾¦ä}U˜b²ÊŠt§FNõ°­ÞnÓœ©¦ùi×Kᨬ¦í®QIÇôcZ:Ú2[Oªéf÷qòéо÷ãüÿŸZ:ǽxçÀ‹¶¿>xSâZyÑ5=NѬ¼SáÇ”M7…üa¥9±ñ?‡'”$}'V†æÙ$É2±HØg*=’¿K¥VéS­JJtêÂ)Í^Ò„â¥k®±iÙê¶jçÉ$ÜZ³M§~oý#äÛ·þM›Å_ö>|ÿÖ€ø__hüÿ’5ð“þÉ—€¿õÒ«âïÛ·þM›Å_ö>|ÿÖ€ø__hüÿ’5ð“þÉ—€¿õÒ«Éÿ+ÿIÃXUË*‹û´ßãPôÚ(¢¼°(¢Š(¢Š(¢Š(¢Š(¢Š+ä/Û{þHÏ‚ÿìïàŸ?úÞÿ³U}{_!~ÛßòF|ÿg{ÿùÿÖ÷ýš¨ëÚ(¢€>Bý¥ä³Á>ìï|iÿ¬ûoWqûPÉ*³ÿ²Çû5ÿëG|(®ö•ÿ’Íÿùÿ³½ñ§þ°Gí½]ÇíAÿ$ªÏþËì×ÿ­ð¢¼*ÿ’cˆÿìCœê¿uà?ß°_ö†ÿÓÐEWð©ú¨QEóM€Ý¥_übÐ$I!‘­µ/Q¶øð*]?ZÑoã~™¬éwn´ýBܬ°Lƒ;âyb“Ò~ |gñG‚|S¦|ø÷ª í~û6ß >.O´Ó~,iöèvø_qþ‹¥üSÓ U–FE‡ÅP…Ô4Ðׯ$w|ëøâŽâ¿ë¾ øà/Jñï‰tß\i^*øYâ/êv¡cà_øXaÖtŸ‹þ ¶¸³¸¶ðe¥üq¾‰öóÞ\BפL9ˆ >-|Mð¶£áxÿàåö•~#‘$‡àíµ/Q¶o7OÖ´[øÿhŸ?LÖt»€·Z~¡nVX&Añ<±Iö<'ŸþÌ¡ŒÂVL-XÒ†;7URÄÒŒbšvƒP­Mó:U¢›„®½úrœ'çf}‹¥V-N<ΕUnjrn÷Ý^/Nh½ÖÖi5ú5,QOOsC4oÐÊ‹$RÅ"”’9#pRHäBUÑVRUŠøWý›¾!|¿»ñwìaâšd×WŸˆe¿›¸ÿgïÜ\Jn/§øy¤Ú^ë߳׊¯¤y¦ûo‚ì5¯†××òËy¯ü+¾Õonø‹Bñ7ÁÏŽz-‹jÿÁ?‰ö¶šW‹~Áˆ.¼EàRÆîû¿| Î"_ü9ÖüE¢[4¶öšãèš¼¯¥Eô•yOÅï‚? ~:øvÛÃ_|3¹m¥ê0ë~Ö,ïµ?ø¿Á~#µV[?xƾ¼ÒüYàŸÙ+º[kþÖ4­R8$šÔܵ¥ÅÄüâž"ý¢eØãƒÇñø‡ö¥ø §Å°üLðÞ†.¿io‡:\$¤R|Bøwá­=,~:èÖ0l7Þ2ø_¥è¿ã…Þ|*ñ•ÐÔüPÝfgÜ”Wðûâ7€þ+øSLñÏÃ_h7ðŽ®²ýƒ_ðÞ¥m©éòMo#Aye4–îÍg©i×)%ž©¥^%¾¥¥ßC=Ž£kmyÐ'i@;~×ßòiŸµý›·ÆÏýV¾&©×óéþ8¨ÿkïù4ÏÚ‡þÍÛãgþ«_U  é_]ÿ¹Tÿ°ªŸúfçbÒuUúSþ•3Àþ3mÓ|]û2øªïoö'…?híë+*À¾|Rø=ᆘì|}ñ' kI¿a1!G_´ëç¿xDø‘à¿xÄbèhþ&Ò®4Ë™ì.ÓSÓä³Õ´‹ä &Ÿ­h×ÑÛjÚ.¥óôíRÎÒö³@„f|ø¹¬j’üø»=­‡Æï iÍ3Ý,)§é?ü%a$6vÿ< c¶÷~ušxçÃ6¯5ïÃÿݾ•x²hz‡…µÍðßxw³,/ѧ:¸*¸ZX,\â®°µ¨În“šZÆxMF3z*°qrNpO×Ê1Tå‡mFJNpONh´¹­æšm§«Ogf}/^;ûD|=ñÅÏÙÿãŸÂŸê¾м[ñ7àïÄ߇¾Öüoá=+Ǿ ѼEã?k~Ñ5_ø]´¿Ñ |oøsûM'›â߀Ú=¸×~&x§ân›ªü ø²ËÀzGƒ–çÁwš×‚4O ø{N¾òßÚ«þ sû^|!ñ_Å/ þͶúåÿí£ûOÁNo_Œ? <ð‹áÇ]à?Ãoø'ŸüÃáä~ éŸþ>ê_ ¼ðcÀ?~ÝøOø™oðúÃâŠüGámkKðöµà­'Ä/âí;Q¯¶?à¥?¶·íeû,ÿÁ]>7jÿ>2x;LðGÁ_ø O¿jçøñçYø•®|ñ×~þÕ>2Ôµyü7ðËÂ?¾éü{ñƒ¼1€ü7ñ¶£«i>{ûk­3^ÒíWHgx«þ ýñ[þsö"øsà‡ß n|ûAüXÿ‚h|+øíð¿SðoÄ[ˆß%ÿ‚ü³ø³à»Íkã^¥ãÿøf×ÇÚ ¯7áw†~üUðæ«à¿ ?ˆ|Yñ³áÿŒ,k¾"ðg„¾&xsöTÒüE¡Mà]Çž ð7ÄoÂï,üA&­wãX~øÑÛ\ŠæâÿE»“RšîÌÚoþûcüný¬?k/Ú[á÷í#ðÛáLðžèŸµ§üÓš>!KÁ¿Û«ÅQþËן>$üVk…PÙKá=|þÏú¿ƒtÒ-¼q=ï‡üY‰u hºÄÚ-æßÀ_ø/gíWsãífßö“ø9ð"ËAÔ¾ ~Ý¿>ü=ø=§xÛÅð³õØãž)ø€<û5þ× þ'þÒÿ³¯íGª7†¼'s¢ü_µñƒû'ë_ ¼[¨ZéÚf‰ã}@麻Ëið_¯Û§Áß±ÏÇÿÚ“â×ìÅû>jpé°ßÁ¯ÛàFáo‰4_ ¯< ð‡MÒ ü ð‡ÃþÍþ3ñZÁ ¾#Á-¾:h_5ÿ‰–Þøwâo^/‰_¿jÙÿQÓ>x¿WñŽ´ÿëÞ&±°Ð¼Yáï…’ø§CÓü3syâ L³iV?¡¶§ü·Æß´7ìµÿ§ý–|/ãx×ð—í{û üTøÏâ:§‰´½WãÁÙƒáߊ¾|E‡Oð÷Œ%Ô¾'ü@°Öíïm4­nóBÐ/.nõQ©xÏLhà–ëÉÿà”ÿi/ˆÿðT/ø-Ãïÿ4ŒvŸ ­?à™±ø;Høc®xõ?gê6ý›¼«øêãàgÃüBø“ÃkOêÚ~©xòËJñôúç‹ì.¯õ‹ûû˜ h$aÿ‚Éþ×¾=ÿÁ(¿lïêšo‡~ øËÁŸð]Š×¿°ÿÀ?üNð¿‹‹áD½„_MÛNjöuhÊXxGª(ò©TÅ`jcãšM·N5!5Å«ÍÝÆñÕÚÝ”f¢öó¶ß#ÓþÿÁµßµ7ƒ|Eû8jhï…Zž­àMþ ;ð£âŸÅ¿øƒâ×Äý7àÇïø'¿ÃoØö/Ó~YÝü9Ð4Í_ÄüðêÛ^ñV®k¾ ±ðü·ó[xS^ñxŠ/Ó|-ÿï|o׿dŸ³ß޼Qð'ágÄ~¿ÿc/üyð'íûw~Ð~4ñ´?>.ø+âÜW¾4Ðþ;ø÷Eø?ðோîþhšDß> üñ x øÇâ¾ ø•káR‡š¦6™ÿÏÿ‚˜7ì•ñ‡ö¿ýœ¿e%ðÇìmð;öîðœþ3ød¾!¼øSâÿŒ ð§Å _üøkûx|oøñªxcMøoâ]Äžý¤üiá¿€Ú@ñÃïøâ'ÀŸø”ØèRñ“Áuþ:|Cý¡¿g?ö |#âÏÙ—Pø›ÿÏ“ö}Ó¾øÇö›ðŠ>1|ý€c|Oð‰|Iá_||‡àGÅO|Vñ´$Ðl%ø»ð3âFŸðî;H5O…š'üq6¥âK¿RÚ²|Ò©Iû7+:rƒJt9ñq§hÊ0vç’Qšj1ƒJ7…Üþï³é{ßghê÷í{ujûéõØÿ‚|dñÃxÔÙïàoíW£þÙ±í«ðvóBøÿûu~מñ/ÄØ¯ÃÚæ…áKOÚ+ãWíkã­cÆž%ÓüWaã_xsIÿ…mðwÀ³|8ðŠø?JÖäøÌþÒ%´µñ'þ 'ÿøï¯üpø¿ñOö‡ðk‡ííûþܳìiâ?Úcö¢ý£?e¯¿ì³uñ/Qø‘ðcXø—ñoÀzW‰<)ðûö†ñÄX¼]ªèß þYx?áö§àÿøÃ^¹ðw†¼-¢xw’Ô¿à¶_µ÷ƒaÿ…Ÿõ]'öø×ñ·ö–ø¿ÿúø{ðÇÁ³—‹5íK[ýœ,oƒñüZ°´ý±> üKøËà­7Á~$ð ¶¼ðÇÃGXý¨¾ø'ãKjþñGˆ5ïz*êIÁaÿà£z‚[~ϳL_gÌ—Ge•¼Ûë¨-•¶²°S]Ò$y$uŽ8ÕžI‚""ÎîÌBªª‚ÌÌ@P $N¯Ê¯Û³öŸ¼×ì­ðoSmCž%‡û ã?‹´IMÄ<#ª ‡RðVŸyjdSñKÆÚ|Í¥Zé°/|# ßÜøŽù,õ G}Ûu+EÊrjà¾)Ô–Ñ_ût­hÁ9ɨÆMLÞËæü—õóéÔÄÿ‚xø‘¼S¢|lñ”_gðÇŒ~.øëâo…à"Ú-âoÄ¿‰>-ÒÕ@ ± ÞxtEµW6âܨ£AIéùšùÿöføJ>|0Ó4[»(l5I£Ôµ+Q4¸Å¬šfˆ‚2c ¥éöðÆñ¦bŠî[µˆ´{]¾†¯Ñ²ÊrÂåø<4µƒm[e¦›­4¶–ØójIJr’Ù¿éü÷>=ý»Ù—Åg9?ðž|öÿ›ø]_füÿ’5ð“þÉ—€¿õÒ«ãoÛÁOü3'ŠÉãþ+Ï€zÿÉÀü.¯²~ ÉøIÿdËÀ_úŠéUÍœ6éaÿŸ˜¯ý' ^ã©þCÓh¢ŠðŽ ¢Š(¢Š(¢Š(¢Š(¢Š(¯¿mïù#> ÿ³½ÿ‚|ÿë{þÍUõí|…ûoÉð_ýïüçÿ[ßöj ¯h¢Šù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½]ÇíAÿ$ªÏþËì×ÿ­ð¢¸ÚWþK7üçþÎ÷ÆŸúÁ¶õwµü’«?û,³_þ´wŠðx«þIŽ#ÿ±qÿªüA×€ÿ~ÁØ^ÿO@þ=ü?⿃_|?ÿý ¾üxÿ‚MxköØøKãÏø.·ÄÏ‡Þ ÿ†aøQ¬ÿÁR¼?ãüNý»5ß kðº¼AûKjºö«ý• éV2þÍÖÿe=KH¾ý ÿ„R°¼ðÔ³xÞOÑïß´í/û<|rñ—ìÿñöÈñŸð†wýо |Fý®µï…_tþÌ ý ¬¿à¡:/‹ítH`ø]/Âx‡âçì…ðG᯼Iñ—À?µoÍñóÄ7-ñ_Œÿ³¼=y¥~óÑ_È8Ž&£Š¨êb2ºX‹Æ¤T±/Zµ(ʶ­Ьò¸¸ÓÃ*XˆÒ…hâ#Qâ¦ñ_X§J§èÔðR§B¼¡¬] ªÆ2j3ŒœãíÝÝKÁÉÅÁÅS^Ï’MI<¿iÚÂðLoÚ?Wø ðoãï<wü‡ãÃÚ‡@ðÁÂøÏ¥~׿·×‹~i¾#ýþ*|L´ý£­¾!k~3ð÷…žËá¿ìÕãíOÖ¼mᯠø¦`,|qoáœÚ{þ ]¯|Cñ§Ç;ÏØûöÔøkᯀ¿üÿðÔåø­bŸ ¬|—üû~Žž ý¥>'|ø¿ð3áþ·âO |%ø-áÖø‰ñ»Ãþ3ø#áø/]ø+ ¾øÝã;ëí;ú3¢±Yî^ñ8œUlV¥le|U4ëR”yqÜ6*tñ1¯ƒ¯ÍSÃË NXxà©S†"¼¡‡N¤“Uª¡G8¨Ó„»$ï s‚pp©å56¦êɸA9´‘ü·xëþ 'ûdjº?ìߪ|/ý¡¾xGÃ>/ýŸôω¿üuûG¾…ðCXý­>)ê¾/|=½øUaðÃáwìƒûkËûGÞx7Á>øQy©è¿±¿þëŸm>(éß¾ jÓü9ñ®ƒ¤ø öÃöñoʼn~ øÅñ'âŸÅ]sÇwûWþÚŸ üá;¯ ü=ð÷‡~xö{ý¶ÿio‚ÞÓôyü%á Äšî«uàx[H×µëúû^Ûx[Aº¶´±ñÇŒ|Aã¸h¬3ߌ¬>'Â`*:ò©ZñßìÉe/‹uO‹_|W?À_Œú½ÌWþ#×´-%5‡n`Ž8/Ž …þ£|@‘­"ŽÆ/éú…~*éqÃe üCÓ4Õ¸Ó®¨x?ö˜ºÐ|I¢ü.ý¦¼)mðCâvµqo¥ø_Ä ªI¬ü ø·ªÍ+Á¯Âωזš\Qx—PdYbø[ãû üH ÷  èþ/Ñ,[Ä÷_ZW7âÿxKâ†u¯øïÃ:Œ¼!â;'Óµÿ ø£H°×t jÂFW{MOHÔ๱½·2$rî ‘VXã•@‘€~×ßòiŸµý›·ÆÏýV¾&­é_þÔß ~8üýš¿hØþøž‹ß/~ü]µÕþ|Xñ5ýÇ~è÷žñ ½Þ§ðkâÞ¨5-KÄzƒm#ß…Ÿ¥Õ.$µ·küMðŦ›á[Ÿ ¾ümøsñ§OÕ.<¬\.µá«¨´ÏøÄZm÷…þ!ü?Ö¥‹Í'Ž|®Agâ ߺ-“ÞÙ ?Y³ ©èWÚ¦“=µüßY‘N 8¹%'‰¨ÒwW^Ê…ùzI«j•ÚVm$ÕüüRýêôî+ÓÞŸÝ}=~G¬Wñᇅ¾(höÚ_‰!¿µ¼Ò¯WXð¿Št FëAñ—‚¼C[Úø“Áþ%ÓÞ-GBÖmḞÝå·”Ûj:}Åæ¬ÚjZ.¡¨i·~ˆãÞ–½5|cøOøóáÝOâ×Ãk@ßãïõω´{£æããÂ=9oÖxR6{Ï|*µÖtÍFiFð„àVZú³á¿ÅO†¿|>ž'ø_㟠xïBóßøkW³ÔÖÂð ‘ôíZÞ ZïGÕ` ‹­'U‚ÏR³4WV°È¬ƒ>¾eøƒû&|$ñƽ'ô‹MoáwÄà ÅÄ¿„Úö©ðûÆ!Y@òou? ]iÏ©ÙHÀ»Íð_!x®¼ÄvñÌ÷Âl-iÏbVRn_QĹOÌúQ¬¹ªÒhÔUlõçJÈõhfrVxó/çŠJ_öôtOÕ[ÐöŠß²ì—ñãÅQøëãì»û:üeñ¼>Ó¼Œ~+|øiñÅQx3Gñ$¾2Ò<#ˆ|_ácWO i^/žoéÚÞ *ÇÄ“K®[ZEªH÷FÏŠÿdßÙ_ÇŸ´‹ž8ýš?gïüWð®­à{Âÿ¼Wðkáψ¾!xo]øgq{yðßZÐ|i«ønóÄš>­ðúïQÔ.¼¨éú•½ç…./ïgÐf°–êv™—Ký¼þ;Â%ñGáçíá¸DkñkÂvþñÀ·‰x¶´ñî¼3¤£m&ÔuýÅZ…ÁÛ,åçóešsûtøçÁFh¾7~Èÿü"°cv¹ðçWð¿ÄÏ ``<ŸmÖçøe¯Ì»ÎtÏ êÊï aðX®⌭ò×ÁbåNšpL4¥^’¦ùy¹]K–2å\ɨü)ÉY&z0Äáª|5!¯I{¯æ»þgÓÞý–fO|LñOÆþÎ<ñ‡Ç+~ž7ø±á„?ü7ñ/Æ)ªÌ—:šx«Çz7‡¬¼QâÔ®cŽâýu}Vì^MËp$u *ø/öCý“¾èŸ|3ðïö`ýž<ῌ°ÝÛ|_ðÿ‚þ ü6ð¶‰ñVÞþëkè>$é:†¬l"(ºí)ðX’O»›ñƒáõåÀ`d{x±ñ,Þ Ð4I5Û?éZ–£¦xNÛTk¨<;§ßÞÙhégmu.h_¾é ñWÄ]ü=ñÄÐøûO𽿊£ñŽ´ $Ðüe¬¦¬º‰ôyLÖîo¬™ >ˤxóÀúøS¡xËšÐvTC¤x‹HÔƒ»»Fˆ¦ÎòmÌ£%Y@$]Q* ³²¢¨,ÌÄ*ª¨%™‰ P $&½ R«vÜê^IBmÊW”RJÒw»V÷Rz[Bl»-6Ócç¿þÇß²WÃÏüPð—€eÏÙ×Àþøác©é<1á‚_ <5áï‹Úfµe¨éºÆñCEÑ|3e¦øþÇVÓõ^ÃS´ñ]¶­oeªj6·QËõÔrÚðŸì“û)ø >EàoÙ—ö|ðdWâ|Â>xu> 'ÅÈ®!ø®¿ GðÝšü8_‰ÐÝÝÅñ |4aãH®®#ñ Ô’izf­ñ3á¾€¥µßˆ>ÑQT1m[Åzœ¡v³-yj;dœmF9Â’<Ÿ\ý±dÏ ¬§Zý¦¾YM|Ù‹^¸Õ$dûÑ[i6ºìúåÀíoii4ìxXɯJœæµI.k6å6®ÜyÛjþãq×ì·™2ÚÉkä¶Ù½¾_xýö=ý’<7ðÏÆ|;û-þÎzÁ¿ˆz”úÇþè¿~é üq«Ý=´—:¯Œ<cáˆ<-âmJâK+7žûZÒ¯n¥{Kf’V0DQuoØãöEñÂ= àˆeÙÃ^ø á[ÕÔ|1ðKYøðËTøGáÍE&»¸Kýáµ÷†'ðn‘z“ßßÌ·Z~o:Í{w(p÷33ø¶·ÿ'ý”ôø¤>ñ~"Ý!*–¾øEñ7V·ŸÁ‡ÄZ…ô•b0¯'ˆãVûÀ”‡k_ðRëó>›ðsö^ñMåÍÄ ,u‹~6ðï…1rÙ 2øgá¼_µ‹èbO²]]ès!•ìšHýl<+ÔkÙÆ½GÌ鯭KKKɸÆIIéfÚodc¢Ý¥eÕ¥§õcõžð¯€ü5¢x3Àþðÿƒ<á6×Fðß…<)£iÞðׇô‹Ä6ZV‰¡iÖz^“¦Ù«­…­½­¼j(‘@ç_ÿhOƒ´û{ÿŠÿ´ Í~¥´m i¦Ô¼]â7c0øcÁš<:‡Š¼K8pUâÐô{ö‹ óãGuüÏ»—þ /ñå<½kÆð§¼7vª“é_ |=Ã&XÙ¾Oµx»ÆW¾1ø#(šûÂGÂ*ã茩'mðËþ íà_ j!ñ¦³{âOjryÚÞ¡Íýî·­Í•ÉñŽüI>©ãÁf’âîÊdb<¹»è°™.g]¦è,<[»ž!«ÛK8Â|ëÖ¤mÞ.ÖyN½8ý®gÚ?“ìÏ=ø‹ûY~Ðÿ´ö¡/ÃïÙóÃ>(ø;àEd·¾ñ<‚Íþ6ø‚ÂG0ÈúwÙg½ðïÁíâ"Kk·ºŽ¯âôŠXÞÜø+S‹xúöjý<-ðZÏNÕuk{;ÿÚ4·V6p<×zv‹{vï5Þ¡-åëI{âÝO,·7úþ£$³Iy4·ÓÝÿÄÊo«ü+௠ø'MM#ºŸ¡X.ÒÐØÀIÝA[Ë—/s{> ÅÜÓÌGLq]Htõ¹~K‡Á5Vm×ÄZÞÒv´uNЊIE]'îÆ)µ$ç#Ž¥yNé{±}ïN¯úÓM´#O^?Ÿÿ[üñR üÏô§OJxO_ËüOø~uì6–ïåÔÄøçöòÿ“cñgýŸÿõ ¾WØ¿?ä|$ÿ²eà/ýEtªøÿöô~Ì>-Àÿ™óàþ´ÂÚûà§ü‘¯„ŸöL¼ÿ¨®•^^lïG ÿéî)ä˜Sl?ÇSü4ÿ:‡¦ÑEâAEPEPEPEPEP_!~ÛßòF|ÿg{ÿùÿÖ÷ýš«ëÚù öÞÿ’3à¿û;ßø'Ïþ·¿ìÕ@^ÑEòí+ÿ%›þ óÿg{ãOý`Ûz¾ ñ/…ü3ãM÷Ã^1ðî…âÏj_fþÑðÿ‰t?]Ñ/þÇwý§Ût­RÞêÂëì·ö¶·¶Þ|yvÐ\Ŷhcuùö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^ÔÊ1œe Æ3„âã8I)FQ’jQ”ZjQ’m4ÓM6š°Ói¦›M4ÓNÍ5ªi­SOf| ­ÿÁ6¿f}kYÕõŸì}OIþÖÔïõ?ì­Oð†¦}¾î[¯ìý"ÇþY~ŦYy¿f°´ódû=¬qCæ>ÍÇ3þû3ÿÏ¿‰¿ïÏ€?ù¯Ðê+û3-ÿ¡~ÿ (ò¿%÷{zÿóú¯þ ŸùŸž?ðìÙŸþ}üMÿ~|ÿÌ ðìÙŸþ}üMÿ~|ÿÌ ~‡QGöf[ÿBüþPÿå~Kîo_þUÿÁ“ÿ3óÇþû3ÿÏ¿‰¿ïÏ€?ù£þû3ÿÏ¿‰¿ïÏ€?ù¯Ðê(þÌËè_ÿÂJü¯É}ÁíëÿÏê¿ø2æxÀ¯ÙÃÀ³Í·ˆ,¼>ºÖ~#þÉûU¦­.ŠÖÖŸÙjòÁý‹¡è‘[ý¢]nòK¿1'ódºyMæ™}öŠ+²1Œ#B1„!B)F1ŒRQŒb’QŒRI$’I$•ŒÛm¶Ûm¶Ûní·«m½[ovQEP‚Š( ¿kïù4ÏÚ‡þÍÛãgþ«_W‘üYýžü ñ_PÒüX×ï€~+xjÖ[O|føw}‡þ$xfÞYòicS’ÒûLñG….gmOÀ~8Ò|OàmZ@³j>¸ºŠÞâ\ý¯¿äÓ?jû7oŸú­|M[5õ94c, Ô’iâªèûªXvšìÓÕ5ªz­N Kj²iÛ÷qÿÒªÿÂúø•û?ÊšOíc£i÷~£·Ó¿jŸ‡ZMô_Ò"Â(døßàƒ>«¬ü¿såý¯ÅßxŸá’3Þê*ð)¹´ðì_diºŽŸ¬iö:¶‘eªéz¥½þ›©i·P_iú…ÜI=­å嬒ÛÝÚ\Âé5½ÄI Ñ:I²0&ëÀ“FñL‰$R£G$Nªé$n¥]du%YXe$ƒ_j?³o‹¾_Þø»ö=×ô_Åuw>©âÙÏÆ-~gÏ]\Ì÷óøb2Þ÷Xø âBI$•µï‡¶Þ »½’Kÿ|/ñ&¥;jpz^ý>õ ºií"½tSV×[OG­I4–>Ñ;?ÎÏðÿ >ËO^?HŸ™ë_:|)ý¤|5ñÄðßÅþñÁŸŽVrÞê¾#-¦»©XÚoKß|;׬.nü/ñ[ÁpË›üOà]SULF·‡Å6>Õf:THõ?—ùÿj¬$¯̾ë>©ßT×TÕÓѤÄÓZ?ëÍw]šÑ‘ÓÂ×ùçúž• tµ.Mï÷ⵆßuýí­x#ºœŽIk‹½L–ë'«-ѶûJ1ÉË$ªNO<בxƒöCý¼N]õo†šLÒH[­S`U$ª­¬—ÒY(ämÁéŠúJŠç©‡¡UZ­ 5÷éÂúTY¬=¢³S”}O§ŸçÛcá=Kþ ¿û&ješ†ÚZ–Ü3ý“ák’(RÔ<9zO}âÙÀ*1\ÓÁ.?dÅ—áöÎB§…¾áqè_ÀÞü±9öÀ¯Ñ è*@ƒ¿>ßçÿ­\sʲ¦ï,¿úñNý?»ÿnªÕ_òò96| §Á6e5ƒZø1')¤xØ’Jœ“càëRÚ£ ŽÎHz†‡û|ÐT%¯…nLxU0®«uaU$„eÑΙò‚O ŒuR $ý[€: R€OAøö¢8 ?áàðÔüãJûí¶Âu*=ç/½ù’<{HøðgDÚl¾ørRŸtê¶ÒëÄГ®M¨–#¨,I9¡¦húVÙt}/OÒ­¸ÿGÓl­¬`ùF÷V±E |¼j„ùþ_ãü©õ¼iÂ!ðÅGòH–ÛÝ·êÛ# êþÏ­H)ÁIéùšx@:óüªœ’ßîê"0 è*@ƒ¿>ßçÿ­O¢³som??¿þŠ)@'¥@~Þ¿òl>-ÿ±óàþ´ÂÚúÿà§ü‘¯„ŸöL¼ÿ¨®•_!þީكŤÿÐùð û¸?…¿çú×ן?ä|$ÿ²eà/ýEtªàÌÚt0¶wýî+ÿHÂaþ:Ÿá§ùÔ=6Š(¯ê (¢€ (¢€ (¢€ (¢€ (¢€ ù öÞÿ’3à¿û;ßø'Ïþ·¿ìÕ_^×È_¶÷ü‘ŸÿÙÞÿÁ>õ½ÿfªúöŠ( ¿i_ù,ßðOŸû;ßë~ÛÕõí|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¯h¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(çoÚûþM3ö¡ÿ³vøÙÿª×ÄÕÐAŠçÿkïù4ÏÚ‡þÍÛãgþ«_WL¼ÿ/þ¿ùâ¾£'—. ZoŠ«ÿ¦°çŸŠþ*ÿ¯qÿÒª€OAO ê/óþ%è¶ÞìçI½5ø¥ðá¿Æ/…¾$øZÇÄzmµä:¦‘tÒ]éž ðƹk͉<â"âÃÄžñ>œß>âO jºV·bÅ­ôAœ7ϰ_þг”Zkñø“ö¢ø#Q|]¦YZOûI|:ÓÐü%Òíìtß „J|ÝwÁvZ?Å…a†_üKÖ$½×ŸìÊòOˆ¿ü'ðóPÓ|1ömkÆŸüA\ø_á_, ×<{¯Û$Æ 5%Ó¦»°Ó|?á»iƒG}ã/jÞðv*‹{Ýz ©míæâÆâ°x 1˜ÌM,Jõ1gpK¤eͤÛÚ0³›nÔ×3FôéNmAEͽ “nýôÚÝ^Ýô:Ÿ|BðGÅ YøÃáïŠ4xnúK‹xµ]í.¡ŠöÊSo¨i—Ñ ·:f±¥Ý+ÙêÚ6¥ ¦«¤ßE5–£gkw °§-ñã¿Â?†öÚ/Œüs¤Ùxžý<Í/Á:Twþ)øƒ¬Ç´6íáï…,õ¿kƒk':Nyƒ$`ó"òöañ‡|câŠ/í¾ÝøâÂËOñW‚gífïFñ‡Œtë ¥ÿÂÛøËe›¬ëÚ}·—ke{ðóFð–½á˜#›DÓ¾%x‹@™’_ª>|ø[ðŠÆêÃᯀ¼3àäÔ$3ê÷šF™zοxÌ^MGľ ˜M®ø—TžBd¹ÕuíGQÔne&[‹©$%乿‹˜ZR•“õÙŸýsçC &”©áâÕyÁ­}ú”d¶³Ýzt²ÞµeÉÞ³’òrwI®É5ùÅŒ^.Ýÿ ÏöpñwØÝmI†‹ ¿~-YE»ãÖ~i7Ï—˜m·¢øûWø‹®|Yø7ðÞÎ\4¿|.ñŽuûLžV×Ç>7ñΡ\K¯„[qÌ+Ÿ¬”`{žOùÿ<æ_ Šãî+̾g<,Ò§‚¥O ¢®ÕXÅ×~MÖm;Ù«°ÁááoÝ©5ÖmÊþ©¾_Àùa?fÏ^Œø›ö©ý£5àØ-ic7ÁoØÄrK%¤žø3áq ç/uÝBåTn€jœ~É~ŸþBŸ?i=TŸ¾OíEñûAßÜü¾øƒá´-´þáb#nÅ"6‘ê +ÎþÕͫ˚¶g˜U“Õº˜ÜDÝ´ë*ë§_ÔÙR¤¶§MzB+òGÌ+û ü&Îcñ/í%¯Ì²Ãe~׳laÊ·“uñÂâÚ\±Ï±0ÈxÙr Ïì§áÛ~t_Œ´®†ãî:üwñ¿‰ü¾»@Ç÷ž/‚]¹ãíM»Ìß_P¨À÷=ÏùïN®Ú8ìz³úî2ë]15¯w·ÛLÊQƒoÜ…¶øV¿‡ô•[àÅÍ+÷žý­>)\ºÜiß¼ðCÆzeH(“Â)ðãá—‹n¡8+1ŸÆMw"ŸÝÞBÀ0¥%—í…áOšM7ö|øÓi̯¥ßø÷àºcóå¤j±|yÑ5+Â:C{⯠ÙÈı¼µUß[Ñ^þ>Ï(5ìó\l­d•jϭˉU¢¢—N_=ÕÌ¥BŒ·§’å|lÏŽ&ý¥¬¼$6üjøSñwàšDDw> ñ…¡ñ·ÃȘÿËÜÿ¾ê=ðÆƒ¥I‡hoüqsàùVúÎÂæHí›Ý|+âÿ øëE´ñ'‚|OáïxvýwXëÞÖ´íF¼\M¶§¥\ÝÙNe'Ê™±‘œf½aF¹ëþóÏ‹?eï„þ Ö¯|aá½;RøIñþQswñàõúxÄúÚ‡Üx¦ÚÆÖo üBHüÆ)cñ#Ã^/ÒÃl°—Š&O­Àq–:<±Æáéb¡³©E{ Ö[ɦåFot¢•µä·9ª`áfá'Ê^òôþoÌõ P è+ç ßüpø*ÀüPðéøÑðîþ)ü(ðíÜ><ðõ¨#ý/âÁ›9u;­fÒÞ<ßü'½Ön®fónOÂßiqÉ<^éá?x[Ç~ÒüYà¿èþ)ðεoö+]Ðu mOK¾„;ÄæÞòÒIaf†hä·¸‹p–Úæ)mçHçŠH×í°9¶ 0‡6¥æ—4èT^ε5t¯(;Þ7vö”Üé·¢›g éN›÷–i-bý'gÝá~}¿ÏÿZŸJ=)á=OåþÆ»›ßî!&ÿÍè¿¯Äø×öøÿ“_ñwýŸ¿õ þ××??ä|$ÿ²eà/ýEtªù/öù~˾/ÀÇüWŸ¿õ¡>WÖŸ?ä|$ÿ²eà/ýEtªãÌ?Ýð¿õûÿ¤a h[ž¥ýÚCÓh¢ŠòN ¢Š(¢Š(¢Š(¢Š(¢Š(¯¿mïù#> ÿ³½ÿ‚|ÿë{þÍUõí|…ûoÉð_ýïüçÿ[ßöj ¯h¢Šù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^×È_´¯ü–oø'Ïýï?õ‚?mêúö€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€>vý¯¿äÓ?jû7oŸú­|M]Mrßµ÷üšgíCÿfíñ³ÿU¯‰«©¯¦Ê?ÜŸý…VÿÓ8c‡ëkÒœ?ôªE øã R×þÓ.åˆ4ÖúOöÆ¡o}â+ø•¿²<7g¬k…·ÓæuèÅã0ø,6#ˆš… 5•ëMít¢ç7æùS²WmÚ+V…9J0‚»“QIumÙÜŸŽ¼wâßø¶OƒÚËþå²³Ô|wã­NÄêžø-á­U$m;TÕ,Z[h|IãßEíà%ÒŽ'ñGŠÞËÃ0YÁâZøWðoÁŸì5Ð!½Õ|Mâ) ¾ñ¿Ä\®±ãßêðFcMSÅ~!x¢šóìêòC¤èöqXøsÃV šG†4mG‚ÞÂ)>|/±øMà«_&¡/ˆ1ø7ãÙ<3ñ£àí_ã "ìüEsàÏY|4ÑàÑïõ¯ü[ñf3\Û~Kÿáý­ࣿ±Wíuà/„ÚvûRx3þ ÿà=öÿ‚[øÇKñ¯„4¿ ~ÐÚ?·ƒ>1|Gøóáøç\ð€<ÏþÊ~øoðUuÝ/ijx7ÄVz·¢øoV×uëB’uáò,¾¼hU§˜Ç C— EJÓ¡‰t§Š›¥)KØÎNnG:ÄN¤0³–"¤¡ìªÒ„:’M®Ngï4’jöWëwªººVæVê™þŠtWñ÷ñûþ -ûaØþÐÿ·Ü_~Y¿ì5ãOÛ¿þ ûGøwöcðïÄo„¾ð‡íyð+àÂïéÿ·w½7Á>%øƒ£xcC×¼uñ'Tð‡ˆ¼iÇI>h¿õo ®£s®x…|«ÖýIøßûþÓŸà‡~.ýfOØŸö‹ÖþXx_À_ Gí=ãoãá}•·Ä;ĺ×Á‘ûNx¾[Yô»Mî¢Ö«ôÛö“ýª¾þȾ ðß~?øÞè^5ø‹á„>³Ò|㟈¾1ñÿÅOOwoàχ?þü2ðÏŒ~ øïƾ%’Âüé>ð—†5^êÛ”´û=¥Ä±Ñ×ÿkÏÙ÷Â?ÿe¯þ,ñž¯á‹_¶~ñ'\ýœ|â‡?ôm{Æö_<¥üMø•·£àÛh¾jþð^³§êz—‡þ,Mà}y¯d›Ãöšeψ쯴›äÓÇ?ðEÚ;âÏĆÞ3³ý…<ðÏödðWŸø'ׯ/þÃ~4ñ/ì×ã1û1~ÇŸ>kžý¿|AzÚg޼Qð¾ÿÀ_´ßôφ¾)ñïÁ?†Þ*ñ~¯ñ(x#Ú׊< ©kV†×OýÎÿ‚Œ~Î_´>»ûoÁ,nŸ?µOÚMý†õÿÛ/Nø—ðcÁÞ5øcào‰>#Ñ¿jO€ZÃø‡ÁÚ§Æ_|9øquoá/è6Óø“OÕ¼k¤êr麌s趺œÖ÷/ª²ü9P§b«:”qu*N50ôéÓ­ +©ƒ ÚXÅËj5fêºr´•)òÚ±.¤ìýÞ]RJÎís.ikm9uZ_Ë¡ú5û/~Ô_ÿlÿ~ý¥f¯ÿÂÉø'ñ'þoøBüiÿÏŒ<ýµÿwŒ×ö·Ø}«Lº³¼¸÷êþ üKÿýºü ûþÂß ì~ Kñ§ãÁ¿ÙãÿÃOxjÃâwìãñ à·‚þ'|Yý¤þ&üÑ|9­ü-øé©|Ö¾krÙüGÓ|3â/ÛöEý¥Ÿã炟Øþøsâ ÊÇWñÕ¿´/ügþ ñ_ö©O‰¿|3ñwâgö×Áø'¿‡¾øÏömý¨ÿg†Ú?ìqñà‡†¼5¦~ÒV×?¿l„´ßí“á/jìõߊÞñ—ìݤø§Çÿì¯.<ûAÝx¤kê~ôÿ²ð*¤½ŽeEÓu1ÍTtÜù)×:\ÒUa*”åíSq¤œc'ݤeÍ-/{+Û»W}:=7gö7^ñçö¡øû0Ÿ‚Ç㟎á´7Çÿ‡?²ïÁïø¦|câ_øL>;|ZþÙÿ…}àoø£ü?âøGÿ·ÿáÕÏü$Þ(þÄðv•öOøøƒMûE¯ùÿ‹ýƒ¼aû0|Mÿ‚Ÿ|gøåðCÃ>øµûEÁP?lߊ_þ#5ï€|Qã?þÈßÚ÷Ä~²¸Lsùñkþ Oûrë¿¶¾—ñ"ÿö^ÿ…·â?àá_€·Ê~ÚgâÿÁH™ÿïð¼6CFø¾ñwÄâËÙéâ¹CàÛ=¼™SðÚ.£|`ß ƒÃºÕ!,T}8E©¯ghÚ‹’ƒ•IGÝ”¬Úr“³JòŽRI{º·æì»».¿%æYß¾>|ý—¾xßã×íñ Ãÿ þ|9Ó­uOøïÅÍ¢Ûj­†ƒ¥ÄékÕõö£¬ëÚ®• èzF™i{«kzæ§§húM•æ¥}kk/¯Wùâ|Yÿ‚ÁUÿhM#ö½OþÉ <'âω±M¦™á[]âìø>ëµï¿ø(·„ükàïø3Ä3|Cñ/ÆO|Rñ¿ìEiãí~Ó¿´ÅõÇÅÝzçâ—ÄO‡_~)húˆÿá·ý8ý¤`ø(?ÅOø(÷ìƒñçàŸìK¢üý>þÕ¿ðLïx¼+âïÙgÃ_~~Ëð6±à¿Ú¿áÄÍ/ãW‰õ¯xGᦋá?xWöný“uiÿgxK\×|U'…üyñRûQÕ­}Hà0ñåKJNÓrw§Ê”cG’1µVÛœ§5Ì›MBüªÚäç)o^¯vôè¼´ÖïSû¯–þ |×ü1¯jŸ?gvÓô_\WÇ ï®[Mømñ»brºÄQ$¶þ ø—4!Ñþ)é6msqÿëBü,¯«~ ÉøIÿdËÀ_úŠéUÑŽ|Ø|3Û÷ø¿ý7ƒ )*•þJ_SÓh¢Šò΀¢Š(¢Š(¢Š(¢Š(¢Š(¯¿mïù#> ÿ³½ÿ‚|ÿë{þÍUõí|…ûoÉð_ýïüçÿ[ßöj ¯h¢Šù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^×È_´¯ü–oø'Ïýï?õ‚?mêúö€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€>vý¯¿äÓ?jû7oŸú­|M]€tÇþ×ßòiŸµý›·ÆÏýV¾&®Ø ïÏ·ùÿëWÑeŸî_÷5_ÿMaN:ßÅõîúUB0 è3^ãp5ßÚKöoðuÌQËe£è_¾6ì¿ÚžÓ< ð·Mm¹ #À¿¯n¡WYwðÝÆ«=¬SEô5|ññ#o†¾>þÍ_.$òtûëŠ?¯ç(Æ;cñOBÐ;|Õ>øëÅ·º7Žäð7ÅÉ%Ð/¼/ñSW{7_Õu-à´ŸðQ¿~ÐðU?‚ZçÆX5ÝKÆ¿µ×í%ûÿÁ+¡›á7 y|ñãágíƒðà}Ÿ…4æÒþXÿÂʾðçÃ?Úßá÷%·ø¦þ6k­3áf¡>¢÷·Ú½ÔçõÁñ+öý”~/þÇ:wìñáWü$?²F“à… ôÿ„ßðœüHÒ~Ïà_÷ÞÔ¾èðžh~0Ó>&KÿÅï€<#7öœþ3“XÖ¿²|¿ê´Wúœw¾'¬ÿÁ3ÿàž u{ßÚïös¿½ñGÃ_ÚsãOüV-[@Ô¾?üPñ„´ïÄ FŸâ·ÄÏü:ðψ¼]â?øƒÅ6>ðëhŸüá-{@¸ñ‹¢'Ãï†ëâ–°Y¾¯™å2Œã<ªŸ<«Õ•5O †»¤£„xXJIÖN­ Š´)ÓPT«TŒ9åR£y:u.š¨ì¢¯yK{Ë™ú$ô}Ò¾‡™ÁjŽŸ¶ü“öUý¥¿i_ÂÈø×ñ#þü&ž4ÿ„gÁþþÚ>ý¤>0xßñNxÃþðžýá/ h:WüJtµý‡í÷ßjÔ®¯o.>øQÿ±øç«~Õ:Ÿì…ðçöxÕ?h‰Ÿ¿à§ŸðUÙ'áæ¥ñÛöŸøuð³ÂôØáÏÁ_‰e4}Gàçì7i¨ÁðÓ_Ñ>!ë)áÍÆÚOÅŸ‹Þ›F¸_|høÃ.¹¦[øoêO ~Ì?ðLŸø$Ƈá_ø)~•'ˆÿeßÙƒà‡ìG¦~Ì:.‰â/þÓÞ!ñ/ƒþ~ÑŸµ®Ÿñú9> x;âÏ>:[xŽãã‡ÄÝ>ÓOðn«ðÏÞ=øsã/é®o´]?Ã^øUöç¿à—Ÿ°·ÃoŽžý¥|ð;ûãg…¾?~Ô_µƒãOøYŸu°|tý³üáŸþÒ¾9ÿ„sVøá+¯øY>ðw‡4ŸøFo4ø;û;íþðÿ…µ;»ûË­T²ø×Æ×xZ³Ãâjb^£ì!B¿ï' ÙÖŠQú¸_i3v¦Ò峊–mͨ®ex¨¦ï{­.õ]m+7שù]û<ÿÁ“übÑbÏxÏö;O†Ÿ ÿnÿƒ_·'ă#‹öšðn±â7ÄÿðOßëþ*øÛáˆzw¾ü(ø{àokø[\Ò|ñ?]ø±me?Ù¢Ö¼yᯇúÜ_ÚsÞ ÿƒ´Û|Gð–…û0é>&øÕ¤þÕ¿°§ìðcGðwÇ\|øÕâø('…ügã?ƒ1OŒž<ý›¾xßÁþÓü7ð÷Å—Þ ñüñVªÅ‡u_‡º§ü)â7ÄÕúáÏø$üŸÂ¾ýœ>i?³nšþý’¼)ûQøà…5Ÿˆ¿#ð?ìõáw“_ý |ñwá•¿ÃmÄ+†|gû6üoø ñJÉô=O¹ñíÿ†[ðæµèR–M)JØjôâäÔœÜa‹œ¹§lDæïƒöt䢵­ÏË(¦ª%j–Ý>û]û©vIZW~ž–,Aÿë -×ÃwýˆuûÚnëöþý²ÿa ü ð—Äo‹¿ü?¬Iû|:ømãÿŒ?o5ÿ€²/ÅÏŒ-Çâ^•£h^ ð7ìëñEŽÚïÅ^ ñF…á ?ÄÞÙ~Пð_/|ðì÷ãÿÿÁ9¾7ü,Ó>2|ñÅ]~×ÇÄOÙƒÃ_þ#xsâ¿ðòOÙžçÆÓ~Îß|5¤|bñÞ¾ñŸƒõ/Ÿ³¿Âm[áÖ³à¯jŸtñuŒ>ÉðsþÕû~˲çÅ _ÚcUñÆ[ö‘ý¡?à¡'øÏàY>:|ñÏÂÿ|Bð¦›kñþj?¾+ø«ö”ðχm¾x/OðûøjÏãÄ?xòY`ñ§âíNþÞÙ~Ž·ÿ‚`Á0¾=üøÞ ø#áÉ> èÿ³ÜŸ >ë_~'ü^øGgâ¿Ùƒâ´PøÃQøqâ|ø…à}sâÏÁïˆòj¯â¿x3âV©âï x¿V×5_kÚf¡«kšµåçt³T¢ã‡«*jMJIÔQ’Q•¹9ê©GWË+ÊɶÚv%º‰|I;m¥ÖÛÚ-=žªÈýøaãÍ7âŸÃO‡Ÿ´hc¶Ò>#xÂ^<Ò­â×¼)☭ôßèˆ,aÄþ×¼UàÇ®£i¯x7Å$𦮪5ëÚÆ‘qg¨\w5ðÿì×û|ý–?h/Ú‹ãçÂ3Mð´ß´×…ÿejðÞ™¤xOÂ^ý‘þj? ~Xi¶ž Ö¬¢¾·ðέ6†ÃÂúw‚¼-má]Áš\>éÞ)ñ¯~ãAž}:}Ïóö§AKÜmÆÑ–±åi¸¦ák½!+Å;ûÖæ²æ²’@0üþ´´Q]ô¡·õ®šú <ëâÿ€­>)ü(ø—ðÖù"{_xÅž“Τmâ- ûJŠàÌrÚÍuÌ3!Y š(æ‰ÒHÑÇËß°ÄÛÿ‹?²¿Â?jÒ´ú©ð¦‹e}3ZF>¨YnÏ͹4½FÂ&ÜK3F\œµ{çíñ*/ƒß>/üL{msàÿ‡Þ&Õ4|†/uâC¦Okám2ÝT†’óWñ%Æ•¥ÙD¬¦[ËÈ# ¥²>[ÿ‚sx>ãÀÿ³?‡ô°c³Ô–ÆÎE$¶Ú†|1á†xÉë»Ð®€#Œ©A¯'RXɨü?Výç›ö‹Ø·÷W·mWSöå]ÓÓæµü-øß·ê‘û-xÀž?â¼øÿ­ ð³üó_Uüÿ’5ð“þÉ—€¿õÒ«æÛÖÒòûö`ñ}®Ÿey¨ÞI㟀͆iq¨jmíð¾coa§ÙÅ=íýä©-­”ÞÞNRÚÒ î%Š'ë~þÐÿ<;ðÓá߇õ+㞯¡xÂZ6«iÿ ãûCÜ}—RÓ4 >ÊúÛí¿ gµŸÈº‚X¼ëi¦‚]»á–HÙ\÷g9ÞK—C †Ì3|¯ˆ”±Õ fa„ÂÖtf°ôáYR¯Zt§:UaŠ<’*‘M¸I)ÃaqU¥RtpÕêÂЇ=*5*Cž.mÇš1k™)E¸ÞéJ-«5¯è¯ž¿á¨>ÏŸÆ?üF¿Ú;ÿEðÔ ¿çÏãþ#_íÿ΢¼/õ«†?è£Èðñ—ÿóA×õ wýbÿðš·ÿ } E|õÿ Að«þ|þ1ÿâ5þÑßüê(ÿ† øUÿ>ÿñÿhïþu­\1ÿECÿ‡Œ¿ÿšêïúÅÿá5oþ@úŠùëþƒáWüùücÿÄký£¿ùÔQÿ Að«þ|þ1ÿâ5þÑßüê(ÿZ¸cþŠ<‡ÿÿ4Ô1ßô‹ÿÂjßüô-ó×ü5¯ùóøÇÿˆ×ûGó¨£þƒáWüùücÿÄký£¿ùÔQþµpÇýyþ2ÿþh¨c¿è ÿ„Õ¿ùèZ+ç¯øj…_óçñÿ¯öŽÿçQZÞ ý¢þøïÆgáÞ‰â-ZÇÇM¢ŸÛøOÆ^ñÿÃwQÐÅÅųêzFŸñ ÂþŸYµŽkKµ™ô•½0­¥Ô’ªGm3¦Ô8‹‡ñU©áð¹îO‰¯VJ¨PÌðU«T›Ú4éS¯)ÎO¤cßbg‚ÆSŒ§S ‰„"¯)Î…XÆ+¼¤à’^mžßEW°s|…ûoÉð_ýïüçÿ[ßöj¯¯kä/Û{þHÏ‚ÿìïàŸ?úÞÿ³U}{EPÈ_´¯ü–oø'Ïýï?õ‚?mêúö¾Bý¥ä³Á>ìï|iÿ¬ûoW×´QEQEQEQEQEQEó·í}ÿ&™ûPÿÙ»|lÿÕkâjî“íõ®ö¾ÿ“Lý¨ìݾ6êµñ5z }]'·\Uý3…8ëÿ׸éU„¯?_ðÿõמ|Yøueñ[á÷ˆ¼w¨]h·¤6—šˆôð­©øOÅÚ£iâøÃIBWÂ^*ÒôiÉ)0Iy¦ÃÂÉnòÆÞ‹EkZœ1ªÐ¯V•js¥Vœ•ã:u"á8I,âÜdº¦Bn-I;4ÓMnšwMz3Ìþ|R¼ø‹áëý+ŶV¾ø·ðþò üXð”rC¥ø–;q,ö‚·$ÜÞøÆöJ¾&ð&´ÅÅî‰v,oZiö™§{‚ÿ—ø×Í¿>êþ Õô¯‰Ÿ 5«/|i𥌺~âëYî¼5ã¼Íuqðëâv›c%½Ö½à»ë¦’ëO¹‚Q®ø#[™¼Gái’yu}3\ÒøSûAè>:×.>xÃI¹ø[ñÇFÓ—P×¾ø–ê//4õw‚Oü=×’;}3âO'ž'û?‰|:ãO ‹´ kæ}ßù«Šx;ø©Ô¥ ÖÊ«TVÄ¥e;¬>"ßXí´£Y%(µ.xGÝÃbc^)6•D½è÷¶òtûoŸFþ„¢Š+çéCo“ÿ%ýyA_ÄgÆ_ø"çíÿâßÚŸööðFðwOÖ?bí¿ðV¿ÚOö,ÕÓâoÂkKOˆÿµüGöNð—Âá >Ô|ugâOÙøÆú¯ŒŸÃ:ÿŽô x#CÔí5ýU¼E›©hWÚ‡öèNzœ zW¿–ckà%RTcNN­?fÕXÊJ-éÏYÆÓPs§ÌÛJ'¥Ú’ʬT’Nú;ém¼þi?‘ü ~ÒßðEø(÷?eŸ od/øh/Šÿà¿ðG‚Ÿ³GŠOÇÙóK±ÇŠ¿d¯ ü-Ñl/k7Žþ,øz4oˆÞ,ðÖ·ãí‡Oâ‡*×Óxª]n .Îóô—ÁßðKÛ.Ëþ ­ãÚKâ‡þ;xãíßðQ[ÿÚ3á¯í=ðÿö€ý>|4ð‡ì}7†â‹@øãÏøH~üQý¼|u†´Û{…ÇìÉá¯ø ögñ拪Cã|aà/i—^0Ö¬ +ÜŽoŠ©GOgº…DÒ©ONN?½´}Ü57hÚ3“©*‘œ§sîúuìÛíæþZ+#øòý”ÿà“?ðPŸ |MðL>?ð¶¯ðûÇøGÿ=ð/í¿ûbøö†ð…µßø)Æ©ûSIâ_Ù>}Å —âgÅ¿‡Z¯ÀÓ«i:þ™ã‹_ìï>Þè6éà/ ø¾:UÖ¿üOö:ÿ‚™Á5ï?h/Š¿àŸ¶c´ý˜~ü>ð7Á_üýšþ|WøÅñMøËáý;^Óõˆÿ~!k?²çůü:øSªøßÇ·?µïí û:|!ý®>.jv–^Õ´›ÕuWÔ¿¯p2@õ©ÀÇ»#˜Vª§Ò¡(UQç…ªÅ8¬ªÛݪ¤¹¥9GÝk–Ÿ,)òFQ=œnÝÕìôì—Uå÷¶ÝÛ?0µÿÚëö½ð·ˆ¼W/Ç?ø'\ý–<'ãÿÛSOø§ñúçö³ø1ã oþË¿>i~6øûDÜ|$±Ñ`ñ.»ícªÍãKðz+ØüWð6? 7ˆ~!ßj:N»e|WüoáÅ¿ðHØ_á§Çu[/ˆú_Âkí{PÒuÔžgÃþñïŽü_ñá÷…µ[;“ö­6ÿ¿Cy‹iqcðþæhüiã·c5¶›âkO é“Éæ‡âÔ/†þ´øyào ø.żÈt .+YgÆ>Õ}#=Ö¥y·h¼Ôgºº@Ø& 3¶¾aý‘e 'àFƒg¬k±ø¾[) ´°Ž_¶EákKÇ’{èþÜòLú—ˆµie’}Z’k‰ç¸šæ(îî~Ó}y¨}³_S€Ã<-7)òºÕm*’W´RIFœy¬ù`’Z¥Ìù§ËNHã©.ge²ü[ÝÛ××ËCÀ?iUá–Ÿëÿ önäÿÙÅü*­zÊý¥¿ä˜éÿöXÿfïýh¿…U«_Ì;¶ø«+»¿ücø}ÿìc™ŸyÂò/¯ÿa“ÿÓŠ(¯ÄϨ (¢€ (¢€ (¢€ ùGÆ¿ ´_Š>!Ú^]ßè#Ð>|×<ã} Akâø–ׯߴCZkZèÃ! ‰¡a#}U³ßiv…LrEõux±?Œ¼'ñ‡Å~)Ó~øËÇz‰~ü2ðýµÿ„µ_†¶¿bÕü#â‹ÚŽ«i¨Zøãâƒ/uŸŒôI­.,­ï­fßsÍ¶ìµ “¥7R”Ý:°Q•9©¨J3HI8ɵi+]kЙ$Õ¤®ÓM]4ÓZ®Ç¬| øï¬kšÔÿ>3C§è?´ /mn,Úøkâφm‰|qàs!Ú' øŸÂáîz'–(›L!­~¬¯ÌŸŠ ®üOÑm,îþüqÐ[ø—Á>&´ÃYkz-ßü/PA «¡§ÈßcÕlŒ–whU£’/©¿eÿ‰ß~%xY‡âdžSÂÿ¾x²_x¾n4y­µ{ȼ/á_é~"†U×4­9õ¯xÃE»¾Ò¬µFßNÕ õ´Š¿©¼4ãùñ 5’æ®ùƃ ÿ³½ÿ‚|ÿë{þÍUúáó§×´QE|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¯kä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{@Q@Q@Q@Q@Q@Q@;~×ßòiŸµý›·ÆÏýV¾&¯A¯>ý¯¿äÓ?jû7oŸú­|M^Ž­{¸%‚ÿè+ÿ¦°‡oâ¿ú÷ý*¡R}¾µ @:óõÿÿ]:”zǵlæúiùÿ_ÕÌį2ø§ðsáïÆ}×Cñö€º˜ÒïSVðæ¹ay}¡ø³ÁÚì;Z×Ä> ñnqcâ ë¶ÌˆbÔ´]FÎyMµÁžÒImäõ ƒ¿?çüúSë´éÖ„éV„*Ó©Ó©8N/GFIÆI­ÓM 7šm5³‹³^mê®|W&¡ûY~ÏQlŽÉÿkÿ…Ö…Vn´þÑÞÓ“ C$­—>-µ¼+ ½£\/|Gu!š÷YÖõYÙ¤>‰ðÓöÐýþ&ê°xVßÇ)à_ˆRI¬¿ ~,i÷Ÿ ü~š‹­§Xhž,N‹ÄóÂì©%ǃ/|I¦èa¿•¾¯0ø•ðKá7ÆM2]#âÃß øÚÆXü–]{Hµ»cÑ^”[Èd‘ΩžJšøÏÃü·)VËêË/¨Û“¤Ó«†rþêrU)&ÿ–RŠû0KGèÑÆÕI*‘SZ.m¥o>úu¶¯w¹íˆ;úð>ŸçùSëó¹¿aýKÀ¥&ýhß¿"¶g–ËÂÐøžOü?´fP«àO.»à½2ܹ á›¶}Šx ²£7ü«áê-uŸ€?¬!bDú×…õxªâ!“›cBñW†¼9¥A —€&ŠùrI´¤Ÿ)[ƒsœ+|”©b ¾Þ¤[k¬¹*û9ÞÝ“Ý+¥¯BÄÓ›»|­ôkÓ®Çèåù±?íyû[xsjx·öº»XQEơோ:ö¯ ‘Ä’­¾­ðSG±´ ~híψoB©ÚnÜ‚ÕÁEo´â«âŸÙ'ö‚ÓX‰†“¨|Ôp0'Ä| ò9|³ElmÅÿw\ë(Ì©i<-[Gl=YéÿnBJïó-TƒÚqûÑú`ƒúôúõéõù—7üçÁꎰþÍ´©r¨—Mû9ÛÂbiaý¡îæ‰G;ŠZÎã“Åb\ÁI|U|Å|)û$üF¿fÅÿ ü£vÿá¹ñã*¹Ìk1*A Hetp8·oö'Ô …o¼’Šg¦[Æ ¾§P‰kf<׈"^]]”Fö=ãÚ¤;óþÏ¥{Øl *µ({ÎÜÓ“rœ­·4¤Üº&íÚŠKCÎRßEÛîéøô¿vÈÀ' §„õü¿Äÿ‡çRQ]-¥»%'Ñ|ÿàì¿?3À?i€Âû Ñcý›õ£>Wå_ü4¿Çødñ?þCÿ ùÿ‚²Ù kGÃ~ ~ø.aý‚Æ”tìrìЧÀð’ ;þß(ÿoÂsÿM~«~Ó*GÂëè±þͼÝÆü*òûþÄ¿³dŸ¡øÓ'‚|Bþ-·ñËüQ·ÐâÇÆø9Å9]ÿ!ýž[ÇÇà _^æiµ6øŽŸ Ƨ[–Mxë¿ÛN×çù—Æ\N—e¯FU92œ³O–…þÕa³ëÚ`êûZ´½ _µ‡µ©l£ìc͇­§/ÜpÔ*Ë_ÙÍ&ñ5á'Ï(òº”p¶©Ëštù_*|·æiN:ßóÓÃßðWÿÅàOŽ>4ø£û(ÞøUøVú“¥ü#Ñþ(x—Ç­|mãïÚÂ_³ŸÂ¯|_økð+BøáÝ[â*ø•ðÓâ7íE©ü+ðî…§kø;ð—â×ÀýFð/о0øGBñeïÅŸÚoàïˆ5З:çÂÿ |Að÷ˆ~êŸ|¿ÃßðWÿÅàOŽ>4ø£û(ÞøUøVú“¥ü#Ñþ(x—Ç­|mãïÚÂ_³ŸÂ¯|_økð+BøáÝ[âÑþMão‡š7Âïx?Jñ}Ï‚gñßÃÏ X|6Ö5=KÀ/uỎÂÿ°ÇüòÏÇŸ¾èÿçÕ¼c7Á/Xø¿À>!ø§ñÏźwÃ>"xËUðð_Iñw5 þÏ^Õþ'üñ»¡h_³Ú|=]Å? |3¯Ee¥ÞxWÁ7¶—KÂnŒ9²¬Â¥ZqÂORš«r­(âÜ?áV¢£êr¥ ÔŒÔjJƒŒi¾xTR†`¦í^ŠŒEN2³wq“§Ü'' )5¯;·£_7x—þ ¹ñÿ >|AÔÿbø|Vø‰ðÏâŠ>=ë´‡ìëðÀþð?…üu£xÁ~ |WýŠ4?g¿müQ/†¼ã/‰Ÿ³Áÿ†0øÃÀ¿´üFðµ¾…á½GÆ_Mürý¢¿h}ÆÿðLð¯À~Óü'ûTü~“Â_4O|O°Ä~ð¥Ïìñïã›x[Ãwÿ¼ñÁ^,Ô|>>ë^.>'ð·ôÄ>.økàχV»sàŒž%ø¥ðϯÕÿàœŸ²N·àÍ;Á7ž øZv³ã}zOèÿ´‡í/áïÚî¥ñ/NÐtoˆgÆ´>ñLøóãË/èÞð~‹â­'Æ¿õý']Ѽà½'R²¸Ó¼!á»m.­«þÄ¿~Á;o4kO†^%ñoŸÁ>‚ÅφqG­è?³Å‰_­ôko Â'­xoòLJþ*Yk^ø¥•à¯x}káŽ|?ª\x’ê˜Î¶G?gS•bœi¬Ê—R•jÐ䯅Ƽ ßü(ÔQ©…Qx‰rûÉ…•ERnIÔ¥Rº«ˆ¦œ½ƒ‡,£xT¥íRýÊn5äMókQG•sE.wöVý¹ñ‡ÄoÚsáõæ»ðvÓöˆÿ‚¬ü>ð߯?x“ÃãKŸøb¯‹_¶ÇÄŸ xþÞ…áM iß²ïìÁâOøâ-ÿÛÆ øƒðò{?x@'Št‰*ýø+ûþο³Ï‹#’ÖÓÃÏí7û0þÊ>,Öüaá<3<_´ ü?ñ„|45ßÉ¥|Añ2ëÌ,>¾~Ë¿?f›_[ü#ðïˆt믇íµíoÆŸþ(|]ñ]î‘á +­;ÁÞO|_ñŸŽüYcà¯Øê•·ƒ< §ëV¾ðŒz®¬<9¡é‡UÔMÏÐ5äf3ÀÔÆVž[J­ ¹=…*ͺµ(*œÍÖ®õª§%ûÙû­|? 袪ƜUiFuUù¥…ûÍ«{°Ú6_ Õ|¾Wýž?äúi/ûtŸýB?gúú¢¾Wýž?äúi/ûtŸýB?gúý'Á¿ù,£ÿb¼wç@ñx“þEþ¿ÒÿÛÓ ù öÞÿ’3à¿û;ßø'Ïþ·¿ìÕ_^×È_¶÷ü‘ŸÿÙÞÿÁ>õ½ÿfªþ²?>>½¢Š(ä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{_!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚ(¢Š(¢Š(¢Š(¢Š(¢Š(¢ŠùÛö¾ÿ“Lý¨ìݾ6êµñ5zXRz~f¼Óö¾ÿ“Lý¨ìݾ6êµñ5z{X?÷(ØV#ÿLáJÖö®úþîmöª_¨À€uçüúŽiôSÂ×óéþ8­[KvJR—’û¿à¾úýã)ÁIö§üÿõ½êP tüÏZZ‡>ËæÿËþµ¼ßwýÁó¯øS¨§„=øþçüâ¡¶÷w(eßçÿ­OÀ+77ÓOÏü¿0V[.gø}Û¿ÀŒ'©üùÿ>´ðè)i@' ü{Ti?‰ÙvÓþÒ÷>}ý¦ÿä—XÿÙcý›õ£~×ó1ñ—þ ½ûAx×ãoíácðëöe±ÒÇíQðÏöòÑŸöŽø™ñáUÌqk_~xÓá§ÂM3Âß>øËHý¦|yà]s_ñŽY§þìÙôÿ“øSþ¥;^ ý§ÿmtøįxÇöÈo…7ßðSÿг lj.¸×~4ךà>&TÌ(qU*Ø Ù})>Àá¥}\DYÖ̳z´iáaBIVÄ7‡“9ó9¥È¡8N¤_×äj„²ùF¤k5õʳN”`ì£G ¥)¹§Ë}^JÉotÔY柵O„¿h_‹Ÿ¶‡À_¿¿àŸþ#_üKøŸû*ü&ðÇìÛãŒÿ³Î»âÏŒ‘|ýà»>6ø×á_ üFñ7Â;I-´Ïz7ö/‚üuãÔø]ñƒÃ—º†¼mã? Á⯈Þøuå­û|sÕ~'|gø ìÓ‹~0Øcövÿ…âóñƒÂ?à›þ"ø‹ûmÁXüeû+x¦Þ÷Äí“x£þ/ᖱῇëãOƒ—^-øb~Eà/è:÷Êþ2×t½h?ø)çÇé>ÿÁ8¼eû8|øuoã¿Û¯áÏìÃûCj¶þ4꺉ðÓÁ¿¿jø&ÿ oá,–“û?|FŸÆw<›öè±ð¥ñþÏ]ü<ð®—âOŠ~ð¿‹¼Y¥èµ?B¿ÿ‚¼iú7Ä_Ñj³Ç ¯Ùëൕ§ˆ>=xwá§í¨iqjÿ±ž‡ñ3Pøµs¯xƒ\ý›<1û/éÞÕ"i¿µ¶‰àOÙ°Ž=Òà§×Zÿíqÿ /áM™ý¬þ ~Òß?i?~Éÿ² KÏ[üF³ üKñ;ö|ÑŽ—ñwÃ^ ø;ðÜþÎð“x7Æ7òxçQzçí-ÿ×Õ-~+þغ‡ìû5øOáœ_?cÿÙÂ:gÆ„º_ìãà¯x“Yø=û`|Nø—û]|ÔõOÚkz•ÏÄÚ»àoˆ¾xq |@»ð …Ÿí­½Ÿ„<3£k?¨_³Ÿíñ/â_þ(üøéð[Cø!ñ‹á‡‚¾|S»ð÷ƒþ,>Ôþ|t¼ø¥¢øPÆ’|>øaskã ?Äÿ>&xoƾÿ„ZëKÑï4K GÃÞ.ñ~ƒ®éú³|-ðgâ×í¥à¯xfËö†ñÇÆ¥øùâ¯üPŽßöiø›ð·ötÒd¿¿¼1ðÇÆ;Ò~~È_´¿Àÿ ]øûáúKq᫟è3~Ò>'ø©ñ+Xø=áßk_ô¿iZ¾§àÿ>–kž9N›žWBym~«*øŠ±ÆÓ©<6iJqމÃÖÃÓú•J^ÒŽ_„Ã×t¦èáej[:[)%^J´¥QBtœc<<“s„'ËÚIZÒ­Rqæ\ÓW9|=ý‚~.øÂ"›öVñ÷Ư„ÞøƒûKjþý•~?ü_ýŽüð‹ÇÿtÙëLø ñûûöMøqðãöbø%á—Ão†ÿg3ãÄŸ†š‡ÇÍWã—Ãɼañ>î÷Á~ö½OöJøãkû ÿÁ¾øƒöq¶øÍâÙÍûQ|»øð÷B´Á5h¿ÙûÇþmoSñðÄG›âG|?àˆ|5»ï‡ŸgÖ#мoâ àþ«ã?h>À?à§ZÇÄ=Wá—ìÕðMø¹¦|iøŸðwàÏ„¼EãŒü)Òl>"|KýŠþ6~Üž)Ó¼L-þüB½Ótÿ…_ <ðªÛÅéֺνwª|Tñ…†×_øe‡|wkáÇü;ãÆ»©<3ðoö>Óücñ#á–‡â[ö™ð6§ûEè^ÿ„PðŸíAûG~Éw>øâmWá«è?4~ܾ ðŸíqûDÉñgöÔÒ|-û8|pýº¼OáëmKÁÿðL{ߨçâ‡ÂßÙ#öÌø¯ðö€¾Ò|1ð÷]ý¾#ñ£ðSáÝ÷†$ñ'ŒO„.n¼oáÏëÖ.í¯|#н£Æß·oÅ¿†ž?øóûCêž-Ñ|Kûêþý¦>~Í>³Ò<2ãRø÷û'ü2³ñ´š‡Š4Û$ñˆŸãŒ¼ûZxËH¼×µ-8¾ü3›ÁöI«|HÖ#f™ve˜Î'[-«V„ñîªÃW­5WWõpêu0ñqÄC R•*8z•fãC.¬ER*“ª¨ÑNJ5£ª\¼ñŠq¥rFvSwƒ¨›”ãÜëGÝió¶W ð¿Kñ¶‡ðÏáÞ‹ñ+ÄßðšüFÑü á/Çþ2þÎÒtøK|m§è}§Š¼Mý“ Xiz™ý½®Ãªÿghºf¤Ù}¯ìÚu…¥œPÛÇÝWÆN*”T£5J*p¿$ÒmsG™F\²µãÍ»5tž‡¤Òvjé;;]y;6®ºÙµæò¿ìñÿ'ÓûIس¤ÿêû?×Õò¿ìñÿ'ÓûIس¤ÿêû?×êž ÿÉeûã¿:ƒÄŸò,õþ—þÜ~˜WÈ_¶÷ü‘ŸÿÙÞÿÁ>õ½ÿfªúö¾Bý·¿äŒø/þÎ÷þ óÿ­ïû5Wõ‘ùñõíQ@!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^ÐEPEPEPEPEPEPƒþÔÚ>­âÙ‹öÐ4 /Q×5Ýsà?ÅíEÑt{OVÖ5mOá÷ˆl´Ý/KÓl¢žóPÔu Éá´±±´†k›»™¢‚¤–DCášÇí¿û:øwR¹Ñ¼C¬üOÐu{?'íšV±û9~Ñúf¥köˆ"º·ûMïÂh.`óíg†æ6%ó š)“tr#ºë…Ö~ü3ñ¥s¬øƒáßuÝ^óÉû^«¬øK@Ôõ+¯³Á­¿Úo¯tùî§ò-`†Ú6Vò †(Slq¢Žü>2hû”gQ*“©B´i4çq’jTj¦­J-[–וïulgMÊ|ñ”SåQ|ÐrÙɦ­(ÛâwߦÇÇÃöûý–‡Oøóëÿ ö…ÏþªÊ_øoßÙoþ†ÿáý¡ùÖWÕ¿ð¥> Ñ$øeÿ„…ùUGü)Oƒ_ôI>áá_þUVŸ]ÃÐ5ü+§ÿÌböU?çä?ð\¿ùiò—ü7ïì·ÿC?ðÀþпüë)Ãöüý–»øÇÇŸ‡ÀÚÿ_ø×Õð¥> Ñ$øeÿ„…ùUGü)Oƒ_ôI>áá_þUQõÜ7ýWÿºüÆʧüü‡þ —ÿ->W·÷ì²:xÃÇŸø`?hoþut¿ðßÿ²Ïý<ÿÃûC󫯩ÿáJ|ÿ¢IðËÿ/ ÿòªøRŸ¿è’|2ÿ ¿üª£ë¸oú¯ÿ…tÿùŒ=•Oùùü/þZ|²?oÿÙg¿Œ|x?î€~ÐßÓá]<~ßÿ²°ÿ™ÇÇ„úŸ€´7ÿ:¿þ¿½}Gÿ Sà×ýO†_øAxWÿ•T”ø5ÿD“á—þ^ÿåU'ŒÃ?ù‡Äz,]?þbeSþ~CÿËÿ–Ÿ/ÃÀ¿eú|yÿ†ö†ÿçWGü< öXÿ¡ÇÇŸø`?hoþuuõü)Oƒ_ôI>áá_þUQÿ Sà×ýO†_øAxWÿ•U?ZÂÐ6#ÿ éÿó{*Ÿóòø._ü´ù|Á@e~þ2ñàÿ»ý¡Ïþò¯ëRø(ìª:xËÇŸSû?þÐùÿÕU_N”ø5ÿD“á—þ^ÿåUð¥> Ñ$øeÿ„…ùUGÖ°Ÿô ˆÿºüÄΧüü‡þ —ÿ-òÿ#æOøx'ì­ÿC—?ñÿhþuTÃÁ?eoú¼yÿˆÿûCÿ󪯦ÿáJ|ÿ¢IðËÿ/ ÿòªøRŸ¿è’|2ÿ ¿üª¤ñ8Wÿ0ø•é‹¥ÿÌCTê/·MúÒ—ÿ->gðPOÙLuñŸýÛÿíýU\ÿž)ÿððÙSþ‡?â?þÑüê«é_øRŸ¿è’|2ÿ ¿üª£þ§Á¯ú$Ÿ ¿ð‚ð¯ÿ*ª}¶þñ?øWKÿ˜ƒÙÕ{Ôƒòör·áWO‘óWü<öTÿ¡ÏÇŸøÿ´Gÿ:ª?áàÿ²§ý~<ÿÄý¢?ùÔ×Ò¿ð¥> Ñ$øeÿ„…ùUGü)Oƒ_ôI>áá_þUQí°ô‰ÿº_üÄ5«iÓ_÷ _ü¸ù¸ÁAÿeׯž"xöuñ‡Ç½wà†üK?ÄŸx‡Çš]îð×Mÿ„oź¤úÿ†ÓHÔÖ˜¿[¿áJ|ÿ¢IðËÿ/ ÿòª¼ò÷öEýµ Û»ùþÚÇ=íÕÅÜÑÙx‡ÅÚe’Ks+Í"ZiÚwˆ-4ý>Õ]ÊÛÙXZÛYZD [x`Ž8×òþ5àq^iG0ÁçØÜ’œ2êY}l4)<_ÖkzÓ|5³ð­¯‰1 ҥ¸Ó¼à»}r x:ÊòþßÃ:.•ýêÏË|5øgÿóøIã= Ç>ø—àË]CÁð]Û|<ðÞ¿ûZøãÇ ~Ãy£^øjdø+ð_Ç?Õ5O Xµ¿‡µÝ6¾ÿᎿgú'_ùwxïÿšz?Ꮏgú'_ùwxïÿšzÅø/Š”«Ê\iŽ”±QŒ12y}G,D#N1®ÞozÑ6áÔæJ Å.VÑ_ë54 –YI*nð^Ú6ƒ½ïõuß[«k®çæ×ìïð¯ö1ø'ðóá7‡uÿŸükã¿…Ÿ~/þÔ6~7ðÿ4†ú'ü/ߎ¶_4/ø«Bð%‡Ä¯C§xZßÁ_>!ü9ðG|Sâ/ØxGÀòxnÊ=CR×|'¡x†ÍÚªŒRœ“K‰)(¨fRqI$¥]KD •ù°í½!vÛ|‘»÷U¾eð_‹?c‡ÞñÃÿ üOø+§ø;Å^.ø»ã¿èW_áÎðOÿ.èÿ†‰ýŸ¿èº|ÿÃàŸþ]רÿÃ~Î?ôN¿òîñßÿ4ôÃ~Î?ôN¿òîñßÿ4õÏÿ"ŸýÿÃ4ùè_úÖÿèáKÿå—ÃDþÏßô]>áÎðOÿ.ëÈexÅŸ¶—í â ëº7‰tÿ Øýƒ[ðþ©e¬é¿eðÀk+¯²jZt÷6w?f¼¶¸´¸òf&ê ­äÛ,N‹õü1×ìãÿDëÿ.ïÿóO]×Ãÿ€Ÿ ¾k7> ð…?°µ{Í2mæïûwĺŸ™¦Ü]Ù^Ímö}gYÔ-S}ÖŸg/+:ù;UŽIQþ«ƒ¼/‡ g+7Žw,{XZøo«¼¹a“U¹=ÿj±ÕþO‡Ù»ßtpfYëÌ0Ïðªç óûn†úrû(o}ï§cØ+ä/Û{þHÏ‚ÿìïàŸ?úÞÿ³U}{_!~ÛßòF|ÿg{ÿùÿÖ÷ýš«õsçϯh¢Šù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^×Î?´?ÁÏ|T¸ø#â/†ü!ðãÇ?¾/Þ|VÐõ/ü3Ö¾+øOYþÔø-ñধ ê¾ð÷Å?ƒÚÂoÑþ0êÍŽ©gã8~É©h¶q\i×ö·S¢rð…þÞÿôr¿²þ!Æoþ˜5}{E|…ÿ_íïÿG+û!âüfÿéƒQÿ_íïÿG+û!âüfÿéƒP×´WÈ_ð…þÞÿôr¿²þ!Æoþ˜5ð…þÞÿôr¿²þ!Æoþ˜5}{E|…ÿ_íïÿG+û!âüfÿéƒQÿ_íïÿG+û!âüfÿéƒP×´WÈ_ð…þÞÿôr¿²þ!Æoþ˜5ð…þÞÿôr¿²þ!Æoþ˜5}{E|…ÿ_íïÿG+û!âüfÿéƒQÿ_íïÿG+û!âüfÿéƒP×´WÈ_ð…þÞÿôr¿²þ!Æoþ˜5ð…þÞÿôr¿²þ!Æoþ˜5}{E|…ÿ_íïÿG+û!âüfÿéƒQÿ_íïÿG+û!âüfÿéƒP×´WÈ_ð…þÞÿôr¿²þ!Æoþ˜5ð…þÞÿôr¿²þ!Æoþ˜5}{E|…ÿ_íïÿG+û!âüfÿéƒQÿ_íïÿG+û!âüfÿéƒP×´WÈ_ð…þÞÿôr¿²þ!Æoþ˜5ð…þÞÿôr¿²þ!Æoþ˜5}{E|…ÿ_íïÿG+û!âüfÿéƒQÿ_íïÿG+û!âüfÿéƒP×´WÈ_ð…þÞÿôr¿²þ!Æoþ˜5ð…þÞÿôr¿²þ!Æoþ˜5}{E|…ÿ_íïÿG+û!âüfÿéƒQÿ_íïÿG+û!âüfÿéƒP×´WÈ_ð…þÞÿôr¿²þ!Æoþ˜5ð…þÞÿôr¿²þ!Æoþ˜5}{E|…ÿ_íïÿG+û!âüfÿéƒQÿ_íïÿG+û!âüfÿéƒP×´WÈ_ð…þÞÿôr¿²þ!Æoþ˜5ð…þÞÿôr¿²þ!Æoþ˜5}{E|…ÿ_íïÿG+û!âüfÿéƒQÿ_íïÿG+û!âüfÿéƒP×´WÈ_ð…þÞÿôr¿²þ!Æoþ˜5ð…þÞÿôr¿²þ!Æoþ˜5}{E|…ÿ_íïÿG+û!âüfÿéƒQÿ_íïÿG+û!âüfÿéƒP×´WÈ_ð…þÞÿôr¿²þ!Æoþ˜5ð…þÞÿôr¿²þ!Æoþ˜5}{E|…ÿ_íïÿG+û!âüfÿéƒQÿ_íïÿG+û!âüfÿéƒP×´WÈ_ð…þÞÿôr¿²þ!Æoþ˜5ð…þÞÿôr¿²þ!Æoþ˜5}{E|…ÿ_íïÿG+û!âüfÿéƒQÿ_íïÿG+û!âüfÿéƒP×´WÈ_ð…þÞÿôr¿²þ!Æoþ˜5ð…þÞÿôr¿²þ!Æoþ˜5}{_!~ÛßòF|ÿg{ÿùÿÖ÷ýš¨ÿ„/ö÷ÿ£•ý¿ñ~3ôÁ«ñŸÀ/Úç⥿„<;ñ?öýœo< ¡|_øñ[\Ó|û"|Mð‹5ŸøQŸ|ñ¯LÐt¯ø‡öÖø£èÛúÇÃý?F¾Õ/<â/²i·×’ÛéÒ],€vQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEÿÙbuteo-syncfw-0.11.10/doc/src/Synchronization_Framework_Activity_Diagram.jpg000066400000000000000000003145531477124122200270700ustar00rootroot00000000000000ÿØÿàJFIFHHÿÛCÿÛCÿÀn"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?þýÒ4y$uŽ8ÕžI‚""ÌîÌBªª‚Y‰I ùþLñ<÷mð+àOÇÏÚ[A±¼žÂoü(Ò¾xwáÝíÍ«ÉËx[Ç¿¾,|ð¯Ä-: £06¹ðçTñv‚÷"KXuIn-¯"·?l?ÄúgÀŸMwyc þÒß4¯…9šÂwµ¹½øwáÏ…ß>=x÷ÂÍs‘Íñ ¿uO‡:ã@EËè>.Õ!µ’ÚâX¯-ÿ>à©ßðXÿÿÁ'¦ðö…qðS¿ü;áï‡^øâ_ èŸaðWÄ]áoˆ>,éÿ§ðËá7…~üKŽãGð.¥u¦6«­üd×ÿgO…zŒº¿†ü ðÛâ'þ \jþððèOü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_”ÿ¿à°¿´Î¿ð×þ wãÙö-ÐüYðÛö¸ý´¾j¿´—ÄÏÞðî‡áïÚö6ø]áoˆž;>7ø ƒ¤ñî³à-JÏÄzÅ·Âiþø›Åןücàè¼'ñ"OÙ÷Áþ-‹â¾ð©ÿ‚ñ~Ò¿³÷†~|Høãð‹_øÓ®Éÿ2ý˜?à¢< à <9ð[‡âÿígðÿàn±ñoLÐÿá‰m~0é#ð,~(ø·ÂšƘ¾øJżKð§Ãžñ.¥áÝ/ãÿˆ@?¤/øi_ŒßôŸÚ÷ÿ OØ#ÿ£zøi_ŒßôŸÚ÷ÿ OØ#ÿ£z¿›ÚÃþøÕsâq«þÊ §‡|9â¯ø,§€>ø“^øëà3ð/ö€±ÿ‚oþÉÞøÙ©ünø…á¯ØïÇÿµŸÚ\êúõïÁχÿ>?ü ·ñoŒ<44?Н¼âasàÏ¡¾Á|>&|ý”üâ_Û?öyÐï~7ëðKïÙwöãø#©ü7ø¿6©¦~ÚÞ%øÕ­xGá\žÓtøPþ ‹àçÄË¿‰~=ø_}âßx_Hø±eà»?ˆÚ“xz_x[ÁÖ ñPîGü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ^±âMGãÖ±û=kú¿€|5ðÏÁŸ´þ©ðgTÔ|àÿ‰ ñ‹>xSãÕ÷‚'¹ð熼{âŸéZŠüKðÏBø…-ž—âŸxWDѼC¬øZÖûQÐô­?R¹µ´‡ò›Á¿µ‡Ä_٧ß|qñ—âïíãíOáOÃý7âŸÇ_Ù³öùøIû,|.ø§ð»àO…¾)ü4µø×ûPþÏŸ¿bO„ ýž?h߇ÿ³—ìñã?ücý ~üÕl?ê>#ÔgŸ‚ÉâÏÙ§ão‰5†_€?@á¥~3Ò>kßü-?`þê?á¥~3Ò>kßü-?`þêùwQÿ‚|}ñïÇÛ_¿²çìÇðâU¶µâÛJÛÂ_~5þÔþ4øá_xWöñ§ì•ðㆳ§é>ý“?h/éž Ó?kÚ âÇÀ{_ ø“ÃÚ.ªiß³~«ñoÞ,ñ‚>)x·Aû7ÁB|wûb]x{â'ìßû4ÿÂMû2§ü3¶ŸñÅ~9øË£øöŽðž±ûI~Î?jê~ø ¾ ñ/Â_ü?øqð—ö¤ø?¬|W×uÚÃÀþ;¶ºÐþ6é? ¾üWÖ¼ðÇKøàôü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_“ßÿட´?ƒÿcßØÇŸ´7ì³â‰¿iÏÙƒö?ø“ðÃþï‹_ 4߉Ÿ´£ãŸŽ°WìñÛÆÞ0ðjøÀÿ~xƒÅŸnŸ‡_?foiŸ¼Cá_ˆßgÔtߎºçìQâ>ëÁZ?Ûú—íóñOÃ~;ÖtÿþÏŸí¾|ý ?eØïö™ñw‡ÿhë~;ð‡íOû[hÿ³þÓ>|:ÔgŸxãgìÿá~Ù_4Ïü`ñÿÅ/ÙÛâ+höÿõÝ/ö|½½ðOƒ¼;ñHè/øi_ŒßôŸÚ÷ÿ OØ#ÿ£zøi_ŒßôŸÚ÷ÿ OØ#ÿ£z¿7¼;ÿSøËâÏ…?²Œ~*~Ïþøsûoøö,ø÷û>·ÀŸÚRŒú‹|ø·ûhÁ>¾üLðçÇ ¯‹Ÿ±çÃÝ#À µÒ?o/…1Kᇞø£?|+Æ]Ãÿgßh? ~'êžÀ¿ðP¯ÚŸÂ_ð×W_d¯ÙÿLÑe/øUŸ/5¿ƒ¿¶gÄ_Š_ð–þÔÿáUjþøkáoþľ jÙŸþ6ü$ø…ã gáƒþ6üEñoü-/‡ßdÿ‚¿µoí5©øà_„€>Áÿ†•øÍÿHùý¯ð´ý‚?ú7¨ÿ†•øÍÿHùý¯ð´ý‚?ú7«ðÿÆŸðX_ÚŸÆ^#ø»«øà×ü)χÿ³‡ìÿMø…ûAøYÿÁ=~þ×?~(øsâ‡ÂÉþKàŸ„ügsû ~Ðz€~*øSãwÇMçÁ½_Ä_t=øã‡‚¼ukãÿüýœ|[kñ×Å¿³Åh¼aàù~ËáÍÃ’ü>Ö´_ˆ^'ÔüOâ?xòö2ý±l_þÂÿn¿Ú Ïöÿý¢|Añ?áÿüËúOÁ¿‹WðIŸ‡ >6øïöêøÅð á}ÿÄ_ÙZ÷öhðdž¾&øþË߉Uð/„lˆ4}kÞ2Ðü7ã™ô{Çß €?`á¥~3Ò>kßü-?`þê?á¥~3Ò>kßü-?`þêøûâ'üÆ??hï‡ÿ~"~Í^o‡î> ~Ì?¾4üEø[⟎ß4‡´'íCuð¯Ã¾ðÖ…âïþÈÒþÎVü;ã?Ž? -u9ÿk?ÚOö1ý¦5¯^ê|)û%k~ñ—ìÙíãÿ³ÿü[âgÅ™þë¿aO|&ø]ñ_àÿüKãZxÖÛöˆø{ñ[ð§…à¦ÿ|_û.|ÓõŸiÞФ¼ñçí é>%ðÍ®‹¯êv¡û#ÍâÏ‹ÿõ_ÿ´‡¼1û|Aý!ÿ†•øÍÿHùý¯ð´ý‚?ú7¨ÿ†•øÍÿHùý¯ð´ý‚?ú7«ó{áü£Ä~øÏãÕÏìYñƒÂß³æðŸµ^‘ñw[ðÇíAàÿ è?²<¾Œ>?ø±â¯Œÿ±×Áυן>~Íßµ¿ÚGÁ¿¿c‰¿¶ö‰ñÃNø+ñ‡Á>øºdá7о-ûÿ†?àª^ñ×Å¿|ðgÁïj?SöŸð'À?†«âÍ;FƒâG…dý¡ÿl?€Ÿ¾!]jë£j:G‚þÐ>¹ñOâGÁ?ŒŸµ?ìIû8ËkñŸHø›ðÏàßÃ/Ÿ´eû~ü ñ_…þhŸ¾2|"ñ/ˆô¯‰ž ñOí'ðÛÃþðïįúÿì½ûg|eñïí™ûOþÉß|5áùüoð÷ã‡õNÆ×ŰXü=ø3ðj×þ ýûüAñw„þ x×SøWàOþ× ƒö±ý¡5oí[‘à¯OðÇ៎ôÿ|xñ7Á Ÿ~Èÿ~<}Eÿ +ñ›þ‘óû^ÿáiûôoQÿ +ñ›þ‘óû^ÿáiûôoWç÷üËþ ƒ£þÆ?´'ÁÏß³ÿŸøáÿ‡iÚCÃ|]àO xÇÆ_¼SûL|ø1ðïÞ7×ôOü`ûìåoûüw±ðGìÇ¥ø§ãd_¿fÙÓÂÚ¬w~ø­£üý¨~¿ñíOûGxâïÇ_~Íÿ²ÿÃÿ‹Þý> xkàoÄ}Ç?´¥×Á¿Éñ³Æÿþ|rðý×…|¿~"øKYýŸü1á/Úàþ¥ñ_ân¡ñoCø» húwÆÙ>~ÍŸ|[à?†>øîè_ðÒ¿¿é?µïþŸ°GÿFõðÒ¿¿é?µïþŸ°GÿFõ~_~Ä?ðRŸŽÚ×?c­Oöžð÷ü$¿?dø$ŸŒ>7|Hо x:çÀž ñüXý¶¾|&øÅ§ü?ðçìßðóÇWŸ><üzøSû1ü-ø¥ð÷C×u¿…Ÿ µúf¡à»_ |2ýŸ~*ü^øÛÏÚÁLiÿŒ¿¶×ì1á?¾ðþ‰û>~Ñ^ ø1ãlxßã/‡àÓ¼û0|gð?ügÅ_>$ø¶²½ñ'Ã^ øÝð'ö0ø}û^kú~0øcÄñVð7öP¾ðOà ð—ð»þÏé³ÿ‰¾"~ÖZOü&ßÛðË?´ŽÞ!ýš-?gO·ÜxG@Ó>7ÿjümø ûqx+þïÃ[Ù<cÿ ›ÿ –3xKã¿Â{Xùá§ÇÚcÁ^Ô¿kÿ~Ò_>0øKÄðSÿ‹±N¥û0xËÀÿ³ß‡þxGágŠÿà®2ÿ‚v|'×þøÓá§Á?ümÑ> |$Ó.>xëRÕ~,|IøßáωðÿÄ¿j^ðÿ‹~#x7ã/Á Ðøi_ŒßôŸÚ÷ÿ OØ#ÿ£zøi_ŒßôŸÚ÷ÿ OØ#ÿ£z¿/¾ ÁeuKGýŠmüGðâ‹~ühøÿýðÇÄßsêÞ;øã¿ ülý¸¼ ð"ëÀ¶þ.ÕgߨÛMý‰|3öOþÑ ¦ø‹gñ»ã÷ì;ñvïGÕ|Eñ á7ìu}ðÿÅ¿²ÞŸûDtÏü[âfƒ¢~Ï~3—öñ‰|û\|øaûEþÌ÷~ ý¢>Ýx«âÂOŸµÇüãöcøoàsþ8ðÏÃÝ#À´ý®‘ûyhÿþ$ü;ñ‹OìñámGBð7üû\üL¶ñÇÄ|ý!ÿ†•øÍÿHùý¯ð´ý‚?ú7¨ÿ†•øÍÿHùý¯ð´ý‚?ú7«ãðTOü ý£¾ü ø‰û5y¾¸øû0üøÓñáoŠ~;|`Ñþ~Пµ ׿øcÃZ‹¼û#Kû9X|?ðïŒþ8ü0µÔçý¬ÿi?ØÇö˜Ö¼ {¨|Mð§ì•­øÆ_³d´gþÏÿðYo‰ŸgøG®üAý…/üSÕ~þÒ>ðÇìUñô‡þWã7ý#çö½ÿÂÓöÿèÞ£þWã7ý#çö½ÿÂÓöÿèÞ®wö`ñÏÄÏþÒ?ðR+?üIñˆüð‡öŸøKðSáÃkáî›á_†žƒöý’¾‰á¿Zxâ×ã×Ç»;]øšüm☴Ûm?JðvrÛ|ñ¬s5üÚSêºM‘ŸUðÈìÏü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_‚_³ßˆ¼eàzJÜøÄÞ8øí¬x>ÂÏÄ_<#ð9¿f} öOñoµ« NïÆþøÝcðXðWþxF÷À?|£k~2ñ—ÄÝ7âç…t=ž—㇌&ñíŸìñ |ðWŽlo~)èz¦•)ºrår§;Æ2R§8Î6’N׋ÒJö”]ši¦¶nc.e{J:µiEÅèí³Ý=ÓWMlÏ£á¥~3Ò>kßü-?`þê?á¥~3Ò>kßü-?`þêøûþ/ÿ {á×ö&©¨ÿÃOx·YýŸÿá-ý“|u}iÿ ?Æ?µÿÇkoÚŸþïöYø»à+Ϧ~Ê¿´Âÿ¿aß´_>%x[ჭ~;~Ñ_5xÿgïÙûÂ_þ/|6øYÏø‡þ Ç⯄ÞñßÇ_³g‡ü;û.i??à¡ß>x·áÇ}Gâ§Çßø«þ á§~Ù^0ñÆ¡ã?þ3øðOá߀|?ñ áßì)ñ·Sð¥Ö‡ûGüHÔtÿk? ü#«éI¡ø›Å¾;ø}™GÜ?ðÒ¿¿é?µïþŸ°GÿFõðÒ¿¿é?µïþŸ°GÿFõ|}ñÓö§ÿ‚‚|9ñoìwá}Sö_øáÿˆ¿kùþÉᄟ´§†>(üøÑàO~ÿ·GÄm7Bñ/Æo‹~ üZøEÿ ‡â×Á?|\øë©ø[öxñ±cð‹Nðøø¨~ÒßþƳÄÿÚ7öµñÿ‡>-xW^ƒÂZÀoŒ¿ðPÙÇâ߯ƒÖ¼?àÿ|añ³ñ“ö×ï>x“Ķ?¼+ãÿƒ^"ñ?޾/ßþÎ<à‚¿@>áÿ†•øÍÿHùý¯ð´ý‚?ú7¨ÿ†•øÍÿHùý¯ð´ý‚?ú7«ãïÿÁB¿jü^ð/죪þÉ_³ýÇíuñâÂ7FðÖŸûf|E—öq¶øYñƒàŸíëñoÃ>2Ôþ:ÜþÄŸaøc{ÿíø×áw᥯ìÉ{áËk_|-ñŸñcS›[ñg‡üçñÁN¾)ê–¿µwÄ_ ü,øoeû$þÏþø…ûDø[ãoíâ?üøkÿ öŽÿ‚£~ϵ_Œ>x¯àßì/ñÛã×ÄŸìo~—3øVëÄÞ²ÿ…»ðŠçÀÞ"ѾþÎßtOx/âÈèü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_Ÿß´'ü›ãgì¡àOø›ãÿìKýƒâÝ ö_Úoöºøqð·â¿ÅßÚOXýž~\ë;koh^ ýd‰¾½øeá/†^5¹ÔüOûXë?°çìâéz‡„þ ~Õß¼%ðïãïÄ?ð‡þ cûOø>ËÁ ûIxÃúÿÄŸˆ_?à¦ÿ ¾ø7áÇÆ_ß|$Ö|Uðëþ û$Á;f¿|Nñ6£û!øâ'‡°ÿ‚…~Ôþ-øígû%|=ý’¿gýOöšð—ü4'ü/M7Æ_¶gÄ_ | ð—ü)/~ÀôøTÿ4O؃Ç_>,Âeðÿþ 'ðv}Kþ€ß¿áñ„¾%øj×þÝL𷌼[ÏþÖ¿µçÅoŠŸ³§üÿâÿì{ûOøOHý»þ0x Yo üÐbóûPjß¼{û~Ôßµ.‡á=-?m|Uû.xcÄ^¹ðóÀïîu_´éáÿ ø§@ð7‰µ]_TÓ-u¸á¥~3Ò>kßü-?`þê?á¥~3Ò>kßü-?`þêùwöÅo‚~*ñGÀÿˆ? þ0|RÒ?g¯ØƒÀŸ·íûF~Ñßÿbÿ…¿<+áÿŠší‡5·Áè ´¯„¿²çü. kŸ³Æ“àý#Å:/‹| û/§ÃÛo‰^;ø§ûGøSWøoà»ÚgÀ< ÿ‹ø·ãOxWàU¿ì9âûKxËãÃ?‡G5þÐÿ¾ÚøWãWÀ/Ûƒâ×Ã߉گ‹?m¯ØŸö@øÉ?‡ü3âOØOâf™ñ­|!û8xÃNðçÃ=N×Åß±¬|ø=ã ÏØ»â‡¾6þÑÿd/Š_~GâüdÑõO~Öß¿j_‹×|o­þÉ¿iºGØôÏØoö›Ôt}3áŸìÍñX›ÀšìßãŠZÁmOâ'ÇÿþÉ¿@AÿTÖ,¼%áß~Êÿ>|@ø½ðÿáÑøà/ŠWž;ømuñ7ãgˆÿn/þÀ?ô wKøƒðcŸ¾~Ïÿ~-|oý¾(iŸ>)üðçÅ߉¿³íG§øûÁß²¬Ÿ>|BøEdöü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_xOþ ·ÿ ÎÇá×ì»ðþ¯ø_ßðËþø%¨üeø£ÿ [ÉñÛã×ìmñOþ /㟃¿®ü+ðë㇉~ÿžý‰|)ðâ|Bøkàÿ^ø…ñOöƒð—ÁÝ.ëCÿ„⿎<óÿÆ¿ø*ßÇoü ý½l¾üÿ…oñöQý€?i¯Žž?øóuñGÁÞ*ðwÁïŽß¼cÿ!ý™/|-ðûÁÞ#øu&µñ¯ì¿´×ìþ#ø;­ø·À ðwÄo:·Œ øñÀõþWã7ý#çö½ÿÂÓöÿèÞ£þWã7ý#çö½ÿÂÓöÿèÞ®ûöoøéÿ àMcâ–—áoøEü {ñÆþøpºž·öë^ø¬IàOjÿ¼ÚE…ïÀïˆ|Mðïô-Cà_Šu OâŸÃDдώú'ÁÿRüNýžþ~0~Ê_ðZχ¾:ŸöÓø™ã¿ßþ6|)ø}û0|mý¾þxövñÃ?ü[ðÿìñð ã/Ç¿ x—Âú÷ƒô¦¹ð¯ÄÏ칤þÁŸìþ~Ö~-ÞþÐÿµíá5Âß õßÿ²Àëü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_|Qý¥ÿࣞý ?àŸŸ üMû9~Ïÿ |?ñö¿Ôþü\×|ûXêŸ| ãÏ…“~ȵŸÅ!àÿ j~(ýüñ6Ïâ¯~ ë?¼Sksðÿá—‡.õ þÏ <'ñ‹ÆÞøëûCê³?û-ÿÁPµˆÿ ?fOkÿ |A©~м?ÿÞÕþx?ÅŸ|+¨ø«ãÁ¯Û'öx‡ã‡ŽÿhïxïÀ_~|ð·ˆ>xoàOü?XÖ~Áàï†ÓüJÔbû˜¼á†ÑþÒ?|%x÷ü4¯ÆoúGÏí{ÿ…§ìÿѽGü4¯ÆoúGÏí{ÿ…§ìÿѽ_“ßðM¯ø)í?ñCá|ßi¯ø~óà·iÿØ7à§…üz¿|?ã?ˆ_¼+ûbÁ:¿e‰¿ µˆú€¿d?ÙÛCø‡â‰µÇïÙï¾ ºð…tý;Ã??l‰Å4¯~Í?²¾ƒâýñ«ö¬ø™ñsþ ñðÛö‰ø+â~Î×?´_í?ûøá/ÄŸ ‡¾?ñUÏìÁûKÁF¾ |ðÇ/ é?>x§ÁÚˆ>>~Éßô¯‹º7‚>'|,Õ¼AðsQø“màÏhxßÁ:‘P¨¿á¥~3Ò>kßü-?`þê?á¥~3Ò>kßü-?`þêüþø‘ûl|EýŒh öhøÃñâíáÿÂ#ñžëãwˆ5Ùc௎üeð³â/ìÿiøË¨|3øêú¯Ã¿…³•Çü 3ÿ‚rkz·†¾(x7^ýŠ<9ãß„–+xÿÆ2üð¾ñþoü\½øúÿ +ñ›þ‘óû^ÿáiûôoQÿ +ñ›þ‘óû^ÿáiûôoWÃß?à¬þ4ø»â ª~ÆÞ øaàŸÙÃàÿÆ/ÿ;ø©àüOðìû^ëÞøá x÷Ášçü$ß°Ö—ý³á?h¶^!ðî«ý™¬þÚZv±§hèúçØu]>ÇR´ó¾Ï}gku°'_ÿ +ñ›þ‘óû^ÿáiûôoQÿùÿ“ýˆìпf¯ýS> ¯¯hä/øi_ŒßôŸÚ÷ÿ OØ#ÿ£zøi_ŒßôŸÚ÷ÿ OØ#ÿ£z¾½¢€>Bÿ†•øÍÿHùý¯ð´ý‚?ú7¨ÿ†•øÍÿHùý¯ð´ý‚?ú7«ëÚ(ä/øi_ŒßôŸÚ÷ÿ OØ#ÿ£zøi_ŒßôŸÚ÷ÿ OØ#ÿ£z¾½¢€>Bÿ†•øÍÿHùý¯ð´ý‚?ú7¨ÿ†•øÍÿHùý¯ð´ý‚?ú7«ëÚ(ä/øi_ŒßôŸÚ÷ÿ OØ#ÿ£zøi_ŒßôŸÚ÷ÿ OØ#ÿ£z¾½¢€>Bÿ†•øÍÿHùý¯ð´ý‚?ú7¨ÿ†•øÍÿHùý¯ð´ý‚?ú7«ëÚ(ä/øi_ŒßôŸÚ÷ÿ OØ#ÿ£zøi_ŒßôŸÚ÷ÿ OØ#ÿ£z¾½¢€>Bÿ†•øÍÿHùý¯ð´ý‚?ú7¨ÿ†•øÍÿHùý¯ð´ý‚?ú7«ëÚ(ä/øi_ŒßôŸÚ÷ÿ OØ#ÿ£zøi_ŒßôŸÚ÷ÿ OØ#ÿ£z¾½¢€>Bÿ†•øÍÿHùý¯ð´ý‚?ú7¨ÿ†•øÍÿHùý¯ð´ý‚?ú7«ëÚ(ä/øi_ŒßôŸÚ÷ÿ OØ#ÿ£zøi_ŒßôŸÚ÷ÿ OØ#ÿ£z¾½¢€>Bÿ†•øÍÿHùý¯ð´ý‚?ú7¨ÿ†•øÍÿHùý¯ð´ý‚?ú7«ëÚ(ä/øi_ŒßôŸÚ÷ÿ OØ#ÿ£zøi_ŒßôŸÚ÷ÿ OØ#ÿ£z¾½¢€>Bÿ†•øÍÿHùý¯ð´ý‚?ú7¨ÿ†•øÍÿHùý¯ð´ý‚?ú7«ëÚ(㫯ÚçVð´-ªü[ý“ÿjƒž“ûGÇþ‰ðSâv¢ÀͶMG]°ý›~9|rñ~£Y Mªk·þƒEÒ,ÄºŽ©g§[]ÝÁõw‡üA ø³CÑüOá}kJñ†üA¦Ùk:¿¡jš¶­i¼wz~©¥jv2Ïe¨i÷Ö²Åqiyk4¶÷H’Å#£:õñìÿ¦Zü.ý¡iŸ€,~išÁ¯Ú3Á>¶Š(4Ÿ?í¬ükð¿|+áËXBG§è7ž<øâ/ˆi¦GÛÙk_uŲXÉmghöuQ@!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ÎkÏø%—ìûxxŽãÅÿµGÀx¾$øžûám·Á=S\Óþ$|^øk©k ì~%h?´ßx†çáGüþ!ðö“ñ?ÃZ7´{Moû@èúí½ÅΘö‹¨êqÞkþÚ6^>Ô|wûYü0ñ/„<㙿kßaø‹Ç¾Ö¾#øONòÿa¯ÛJ]OûWÁžø…ð¯XÖ~×£¦¡ccö?è?ÙÚ•Õž«qý©kc>¨õÿð…þÞÿôr¿²þ!Æoþ˜5yý÷üÛöÕ~4|Ký uoÙ³Â:¿Äÿ‹ú?Åmâö­­xÛTð–¯Çhß>7jö? ¯üQqð·Ã>.øÃà/h¾ø£ãŸ ø3Dñ§´ >7ÅZþ«ndGâ|?ÿxÿ‚wxrÇXÓm¾kÚÅž¹ûëŸðO븼kñ÷ö‘ø†ö¿±Þ½â¹ø+á½SÇÿ|G¥ø{@ý³ü1¨ø/öšÒüG‰> jßð³m¾)xOU¾ðýðø¤Þ3éRGiàçðìv¶¢µø"WÂ_|yýŠ׿Ð| •¢|Aý¦>8þÑß¼A⯅^ñ€ôü4øMàŸ‡Ÿ |+á+=ÁÞ•uÂêu?ÐøBÿoú9_Ù ÿ‡ã7ÿLøBÿoú9_Ù ÿ‡ã7ÿL€>ñg„ü+ãß ø›À¾:ðχüiàŸxYðŸŒ|âÍNñ…|Yá_é×:?ˆ|3âokךF½áýwH¼¼ÒõT³ºÓµM:êæÆúÚ{i剾`ðìiðëàü&¾.ø%«ü@¸øÛ«ü?ñ‚¼ñKö©øåûSþÛð®¶þÅ©µ–‡§þП´Gˆ<[áÿ‡þ ño‡ü â/‹^øQñá'ü.øWÞƒÅ^#MOÀþ×|*ïøBÿoú9_Ù ÿ‡ã7ÿLøBÿoú9_Ù ÿ‡ã7ÿL€7¾~ȾøWö\ð÷…l¼A¯jÿ²ìÁÿ ‡ðƒÇ^,×§¹ñT¯4ï–>%²ñ5ž‡|­øƒÅ×?³‡Â-cYׇƒ¬ç±Ô|7s…bðÞ‘¯kº^£çþ ÿ‚r~Ç¿5…wž øeâéü?ø[û=éÿð|Ið¯ü ð·âŸìÇñ¯ÀšÚ—Æ"÷ÅØ_cÙ¿ÄßÚ~7¹ñ&±©ÿ¹þÆÖ5 CÃþ/ñæ•⎃Äß±·ìñâÿŒ¶ßµïx‚ãÆÉâ øÏVÐì¾)ü[Ѿ xÛ†ѭþ|Tø¡û:h¾:ÓÿgÏ‹?>GáO7€~/|Møaâ߉ž Ÿá_Á»¯ ø¯I¹ø5ð²o`ÿÂû{ÿÑÊþÈ_ø„?¿ú`ÔÂû{ÿÑÊþÈ_ø„?¿ú`ÔÙ?`¯Ù:_~ΓáNïþÉþü-ýžôÿøN¾$øWþø[ñOöcø×àM íKã{â¿ì/‰¿±¿ìßâoí?Üø“XÔÿá\ÿck†¡áÿøóJñGŸøþ û"|5ðç|áÝ'ö€Ô|%ãïøHïµß xÿö×ýµþ+ørÃÆ>*ø§gñÓSø»à­â—í ã3á¯íkñ¶ÂŒ^ý¢þZøWã·ƒ¾)½ïÄ/üDÐü[¨ê½×¡Âû{ÿÑÊþÈ_ø„?¿ú`ÔÂû{ÿÑÊþÈ_ø„?¿ú`Ôã£þ 'û$(··ø+â ü{áÿÚÃ?MøóûEésüTÓ¿kOƒ_þþÒ—?î´ï‹V·?¼Añ§Á?~ëž=ñgÄI¼Mâ­gã/„ãý£$ÖWöƒÖ2jš¯ŽîuŒ/¼UÑÂû{ÿÑÊþÈ_ø„?¿ú`ÔÂû{ÿÑÊþÈ_ø„?¿ú`ÔïºoÂßiüeñ¯OоÏñ7âÃÿ†Ÿ |]âoí=b_í|ñÅŸ|:п±§Ô%ðý‡ü#¾ øãñKPþÓÓ4«-cWÿ„£ìºî¡©Ùhž¶Ò8 /ÙsàNð'áìÑgào'à—ÀøgOøU> ÿ„›Æ2Â+ÿ ›ã‡~?ýŸÿ⣗Ä/âÝsþü(ð­ÿ½¬ÂUýöÿÂG¦jšÕ–£ÀÿÂû{ÿÑÊþÈ_ø„?¿ú`ÔÂû{ÿÑÊþÈ_ø„?¿ú`Ô߉°Wìñƒâž›ñ›â/ŸøH¼u¤|@øOñOoøN¾$é·øÙð;Ä~ ñÂß?ð¬t/éŸ ¥ý è>~ËŸ~‚ÿ…}àoøGÿá—ÿgýgö\øÿ7Œu_øAþøƒþ¯ö¿¿âwâKþoµÿÃ;üÿŠ›ÆðxÆßþÿô_Ãÿ жøøBÿoú9_Ù ÿ‡ã7ÿLøBÿoú9_Ù ÿ‡ã7ÿL€9ßÿÁ9?cß N²éÿ ¼Aym¦øƒáŽ¿à}ÄŸ¾9øÇ¿ ø9ñ—áÇÇÿ‡žýœ¼!â߉Zß…¿fƒú7Åoƒ|Cð7ö~Ñþüñ>ð‡áWƒ¼WàMsÁ?<áÍØ4ÿÙsàN‘ñwSøõ¥xû3ã·ñ]ø—¬øóOñ7Œlµ_Ä~(ø'ð·ö{ñ6›©­·ˆb²¿øâ/†_¾ C®ü'º´—áf¯ã¿ƒÿ ~0j ¸øÁðÿÂ~;Ò8øBÿoú9_Ù ÿ‡ã7ÿLøBÿoú9_Ù ÿ‡ã7ÿL€=÷Á¿ | ðÿÄ|Yá û#Ä~ i¿¾)jÚzÅÿü%;Ò>|4ø)§ë¿eÔõ Û-ìÿ ¾ü:ðÏög‡m´_øG¶gÓåñ¯®êºŸÌ ÿ‚r~Ç¿5…wž øeâé ø›Âþ:ðÏÅ?‹~Ôl5€0~ÑV? ­®lü㯠éLJìtÚÓö‰ðï‹<'­iÚ…~'øW↳áOŠ7Œ|7c ézNü!·¿ý¯ì…ÿˆCñ›ÿ¦ Gü!·¿ý¯ì…ÿˆCñ›ÿ¦ @÷Àïð¨|GûGx×PñOü%^-ý¥ÿh Kã§‹¥³Ñ?áðæ‡ýð³áGìÿðëÂÞÑgÕüG©ÇýðKàGÂÛ/ëzŸˆõøMþ)øÿBÒ>xKÅ^øSà??øiûþÌÿ >)ê_¼áˆâÛ߈>,iºGŒ¾?þП~xâŸÇ?øËÅ?>%ü'øñ/⟋¾ |øã]OâGÄ»]KÆ_ þx3Ävþø›ñ/Áún¥eá/ˆ^2Ñ5·Âû{ÿÑÊþÈ_ø„?¿ú`ÔÂû{ÿÑÊþÈ_ø„?¿ú`Ôç¶_ðKØONÿ…EŸÀÏ'Løÿ é}ð§ÃŸð³~1Iàï øÇöMÿ…weû?ü]ÿ„*_ˆ/á-sö€ð„¾xáOü4_ˆô]cã·Š¾èðÏþ5ø‰â?‚Z¦µà GæÙ÷þùà¿|[³ø¿û@ëßþ%êþ ðþƒá„¾øð·ãïÀ¿ øÃþý¡ÿgoÚ—À~ÙñsöÌý­¼I¤üøñ“öTøA®þËŸ³gÁoü ý—þéÚ×Ç=Óà·Š4ŒÚ•®…÷ü!·¿ý¯ì…ÿˆCñ›ÿ¦ Gü!·¿ý¯ì…ÿˆCñ›ÿ¦ @ ø—û~Éß>)鿾"ü)ÿ„‹ÇZGÄ„ÿôöÿ„ëâN‘à럼Gàß|-øóÿ ÇBñŽ™ðÊ_ÚÂö_ü#ðçþ¼þ—âî·ð'Hÿ†uñŒõ€·úŸÃ‹Ó@ý‚¿dï øs¾оý‡Ãþ øûü-ðÆŸÿ ×Ä›ŸìÏÁ;~)ê¿ÿc­ íWž1¸½½ÿ…?ñ7[ÔüMý§¨\Ýë>ÓýñKPñ·‡á·Ò¢wü!·¿ý¯ì…ÿˆCñ›ÿ¦ Gü!·¿ý¯ì…ÿˆCñ›ÿ¦ @ß¾ø«áÆÿÛ+âUçÄ/øŸÁ?µ?Ɔßü=à‹o‡Ú‡üUðÏÅ^ýš¾ þ;1ÓõŸˆüC×´ˆ~ñN‘ðÀ¾(ðͭ߇š‚õßXjš¯Ž­µMoþ>ülý¨|cð[W×ôO…Ÿ üñ³Y»øñûTÂm¤k´¿ÂºŸ€ü>-»ðßÚl<~Ó]k?ð”ZêºÌ?cŠìë}*)®<Øõ{&¬Ÿð…þÞÿôr¿²þ!Æoþ˜5ð…þÞÿôr¿²þ!Æoþ˜5~|ý¢>5jÿµ÷ˆ¾%üXðƒþü1ø—ðãÂ?õˆîl/ÙOÇ~øyÿ Õ~#ø›CÕ­4ÏèzwÄOmøƒÅ—FŸ`Úèþ¾ñŸ‹uv÷Äš^ Ñ~~àø3öbøEûE~Ì_²MÏÄ[Oˆgˆ<ðáäñïÁߟ?g/Šz6â‡Þ“Å~µø·û<|Bø]ñ5¾ø¾÷Ã~Ö|aðêKàOxÀ¿¼Mâ/ê~ øà½KBî¿á ý½ÿèåd/üBŒßý0j?á ý½ÿèåd/üBŒßý0jk~Ç^Ó|Gû"Çà»ÏøC>~ÈŸ>)üiðïƒmmõxÇÇß>$|,ø«ðz|Bø±âïë¾ ñgöχÿh¯ÚÇ¿µ_é¾%ø»ñ¿ã·Œ<ñoÅ?ìo|?ñDøÉÐj±ìÁ¯xWÃÞñÂø§Á>øÁûAüwÓ¼âËßx«Â·Ÿ?j½;ö†Ñÿh=SÄÞñ±©é3ðÿÄÝ#ö­øÿ¥ë>ñ}ž¹ðöÏNø‡sc¢x[K¶Ð<)ƒƒÿ_íïÿG+û!âüfÿéƒQÿ_íïÿG+û!âüfÿéƒP~~Â?³?Ã_ø?ƺG…þ x«â€þ ZüKð·ÄÏŒ_ÿhOßì+~×ß´gˆu ;PÓ¾$Üø:âÚOøsÁþðöü!·¿ý¯ì…ÿˆCñ›ÿ¦ Gü!·¿ý¯ì…ÿˆCñ›ÿ¦ @ ø}ûþÌÿ |Gàÿéøâ¯ˆøkñ/Âß>1|ý¡>?|S°ñð³ãÁMºø¡ñÓâŸÄ_ˆÏÃÿ |?ý¡>:Yx?á>·â=Gágƒ¼Gñsâü-àÝ#âе]yÿ?à—ÿ±gŽí|Wkªü:ø¢ÂqñÂß¼W{ðëö’ý§~kÇŽüûG~Ðÿµïƒ5Ùõï…ü­Cÿ§í5ûT|kø×áÝ2×P·Ñôë~Õtý>ßþ'¼ è_ð…þÞÿôr¿²þ!Æoþ˜5ð…þÞÿôr¿²þ!Æoþ˜5yïÄ_ø%ÿìYñ‡À|6ø»ðëâÅ IðÿÄ_ |Mmñ7ö’ý§|}¬|Pð&¹¬x»ÄÚ6…ñËÅž,øÇ«ø·ã÷ü*?øóÅþ6ý›5?šçµÙWÇzå׎f­CáG‹RÛYƒ ðNOØôÏâ‹©¾x‚öçÅ^ ý¡üX÷—ÆoŽz¤þñWíOñ—áíñ›Äß &Ô~%]IðGÄíð'á_Çok?ÀºÁ_Œ¾“âÁ;Ÿ‡ž6ñ‰õÍg¢ÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨ÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨{á7ìmû<|ñW‡<{à/x€üDðׇþ*øf?‰^9ø§ñoâÇÄÏéßµ‚7ÿ.~$üFø¯ã¯øÛâ׈5+oÙ·à/…ô?|Q×¼aâ¯ü=øOàŸ†^Ö|5ðïF·ð¸è<3û.| ðwƒ¿eÏxsÀßÙÞý‹¿áÿ†hÒá&ñçü+_øE~øçöhÐÓïüCu©øÇì¾%x×Á_ñ_Þø«í_Û_ð‘Þý£Åºv“¯Xp?ð…þÞÿôr¿²þ!Æoþ˜5ð…þÞÿôr¿²þ!Æoþ˜5zî·ðà׉üUñcÆ>*ø{áÿjÿ>ø7àÅû?C?Š<+ñà×€µŒ:§†¾ø›ÀºýÆ£à›ïÇsñûâìZÌ'ÃË?Š´ï\èþ*¸Ö´/B±Òüá¯ü“ö=øMñ3HøÕá†^ ¸øÉ¤øƒÂ,Ÿâ׎~3|sø©ñ3Å~*ðÃßÚ;á‚|Mñ'ÇŸ~%xÇÅ?¼Aáo…?µÆÿ†:³ñ3Uñn£§ü=Ö|à»{”ðßÁ¯‚úoÃî‹þ¿ÛßþŽWöBÿÄ!øÍÿÓ£þ¿ÛßþŽWöBÿÄ!øÍÿÓ tÿÁ9?cؼ+á i_ ¼Aákoüýž>|>ñ/¾3|sðÄχ ÿeM;âæ…ð7KømñÁ_´Šß6øyñ;Å>1ðN¿} Kìý—>ø_Gø¡hžû5—ìÝñÄ¿>O?‰¼c¨ëgÅ?xâßïüKñwˆµ/Þxƒâ·Äˆ~øóñ‡Pø‹ã/‹Ÿ¼GñǼEñKÆZ–·ñ6æ?EÀÿÂû{ÿÑÊþÈ_ø„?¿ú`ÔÂû{ÿÑÊþÈ_ø„?¿ú`Ô½âØÛöxñÃ6øKqàïhž‹ãÄïÚ.«àoŠþ|Lð_Æ_Œÿ¾#üNø§ñ á·Æ¯‡Þ:ðÏÆO…þ ñ¿‰>0üWÓuɾøóÃOð÷â?¾ÇoŸêÞ ºçí?`¯Ù:ËÀŸ>[ü)ÿŠKö€ýŸáý˜¾5ÛO㯉7zÇÄß„_Û¼MªÚx»Å—^1›Åº¯Äx·ö øýãoˆ¿&×?áv|MñßÅxçâÄ/x·û?Y²wü!·¿ý¯ì…ÿˆCñ›ÿ¦ Gü!·¿ý¯ì…ÿˆCñ›ÿ¦ @öµû.| ñÂÏÚàŽ§àmÿ jøZßðº|gâoé^ñü/?KáÏŒðŽYé^!²ÿ…eÿ 7íºï‹|kÿ µüÿ Å?xÿã.¡ç|Zø…ãøƒâ÷Àƒ_ ðeƇ¾ø—¤x Ä+ñ7‡¼;âÈgÕ<+&£ãŸƒ_?gÏÛx›Â’Ü øãÃþ%ø7ñ³âuŸ øÛKñ…o´ï\ÞO£6¯§èúŽä_ð…þÞÿôr¿²þ!Æoþ˜5ð…þÞÿôr¿²þ!Æoþ˜5sºü¯öUÐux…4Œÿ¾|`ðOÇ |QñïíWûW|JøËgãO‡^ø“àŸhº—ƈ1ü[—Qøiysñ3Çë¾Áðãö\øð“þü+ïÂ?ÿ ¿û?ë?²çÀ¿ø©¼cªÿÂð'Äð¥µü ÿ¿ê_ð“}¯þßàïüTÞ0ÿ„ƒÆ6ÿð‡ÿ¢ø†øH+îñ¿Å¿ŠšuŸÁ¯|ð‡ìp>x7â'ÅSÁÞøÀß²w„ôï…¾ø¹e£ßOy⯠ü1ø…ñÃÂÿm¾Çà­wëÿŽÿ>þÒŸ õO„_t¿jÞ Õ¼AàI„üñ á_Š´ß|+ø…áoŠÿüMៈŸ "x'¾*ѵŸ ø«FÔmõØ—¶{‹y¼‹þ¿ÛßþŽWöBÿÄ!øÍÿÓ£þ¿ÛßþŽWöBÿÄ!øÍÿÓ ÙþÁ_²t^ðç5¿…?ð±ùâÏü—áLþñ¿³þøGñÇÇž øU¨|Cý£þ5ø‹öÐý þ7øNø9§|nÓ<ªéÿ|ût~Íÿµ‡¾0hºÇ|-µø×iûEÜøªëöjÕõ_ÙoÄV¾"ø¾ð‚þ¢ÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨ÿ„/ö÷ÿ£•ý¿ñ~3ôÁ¨Ïeø'ìãû/ü"ñ?Ã|?ñoü-?‡þ!øoñ¯LƒÀ¶¾ø'ãx·ãgí7ûAê¿|#û<ÝjÞ)øàÙÿÃ?l?ÞøuðÂfñn±¥|"ñ‡~|BøñOþ-?Ä3t àœŸ±ïÂo‰šGƯ|2ñÇÆM'Äñdÿ¼sñ›ãŸÅO‰ž+ñW€>þÑßüâo‰><ø£ñ+Æ>)ø­â |)ý¬~7ü1ÐõŸ‰š¯‹u?áî³àŸÛܧ†þ |Ó~t_ð…þÞÿôr¿²þ!Æoþ˜5ð…þÞÿôr¿²þ!Æoþ˜5wßeÏ?³¿ö7ü)ßÂÿÿìÿð ö\Ò?â¦ñˆ>Éð'ö_ÿ…ÿ /ÀßñTø‡[ûGü ÿð¶þ ÿÅMuçxÇÄßðÅaâehŸÙ¿:é§ö$ºý«~)üJñ¥·Æ_ˆ¶Wík'Å/„ÿ þü8¼ðgŠu=FÙëàWì÷ðÏàw†t}kâOäñ§Š´Ï~κ?‰~"|Y¼Ôü àÏ´ßø»Æú—„>øF’ÓOôÏøBÿoú9_Ù ÿ‡ã7ÿLµ>~Îþ#ðÇÄünøëñ DøÛñÇX‚_xgÅ:/€/~ø+á_ÃwO’_|.ð«ãï‰÷~‡]Ö,ä×¼oâk¿êþ&ñµ÷öMž³¨ÂþÒ´°Ïö[øg­|ý™?gOƒž%žÒëÄ_ ¾|"øg¯ÝX1{kÀŸü=ámV{'$—´šûJžKv$–…’s^íEQEQEQEQEQEQEQEQEQEQEQEQEQEQEWÈ^ ÿ“÷ý¥ìпbý\ßðPjúö¾Bð_üŸ¿í+ÿf…ûÿêæÿ‚ƒP×´QE|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¯kä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Ü\Ai×W2ǽ¼o4óJÁ#Š(Ô³»³UA$šð|tŠÎ+Ñᮽu ›«kxvÂöí¼s][èÞ »³³šP«%ÐÓ.n!‰šA§Ìê!lÚ‡WÔ¬´/†:%…ýîoã‹Zf«Éaq%´×n‡àŸ|AK,†ÎÿRðVk`—vRÜZN$·žhŸÅTdÌ×Âñc0˜Éà02TAÖ®áN¤åR¬E q«Q…9BNRƒr”šI(>nŠT¢â¥-nÝ–©Y=ݬîÝúíøw?ðÑ?è|2ÿÃíâ¯þ‡Úô{/Ž—rÚ[=ÿ„-íošÞ½·³ñ,·Ö]´Jn`µ½›Ãºt×–ðÌ^8.¥°±–â%Id³¶g0§‚ÔÊ0¿?ŸÿZ¾~—ç±»žgV­ÖЦ.J>kÙ`©»¿6ב«¥Mý„½%?ÖLôÝãïÄ+{ä¿ <¬éÆÙK¿|UÖü5z—fID°&Ÿ§|#ñdl‘;¦Ôã–I$–&³‰aIg—Ãß¼sw2x³áŸƒô[¶f·¹ð÷Å-kÄ÷r݉b ÖZ—Âñ„Í#]&¡q*ËP‹6Ižx<ÍSøŸÒ¥$Z¸gÙã¨æóJÎ)ßÙ}W-ä×hÝ`½­·ÿ—œÝäέȽy§ý*ßí÷ßoÒÂîM/Â6—š’[Lö—þ&›M±¹»XØÛÁy¨[økUžÊÚYB$×Péš„°FÍ"YܲˆŸÏGÇß'§Á¿…ßÇô=×3S¨À™ÿ?¥i<÷;¨×.gVKSÃeÍ6ÞïÚ઻ôÑ¥åpT©¯°Ÿ¬§úIÓeñžòKKGÔ<)mk~öð5õµŸˆ¥¿´·»h”ÜÁk{?‡ôé¯-¡˜¼p]M§ØËq¬ÒYÚ»˜—ñLj¶×É„þø+ZÓM´o%߈~*ëž¾KÃ$¢XNÓ~øºÞKd‰`xîÛTŠY$’XšÊ%…%Ÿ‚AÎ}?¯ù52Œ+yç™Ä ¢³ ”Ú·ï!‡À¹»ouS 8{ÝmýÛ²§?+Êß„“üNóÃ~!]]Ìž.ømàÝOÌö÷>ø¥­ø¦ò[Ï6 °Mc©ü&ðt[yí5 ™VXâ„YºLóÁÔß|`ÔÆöM+¶wšœv³>Ÿi¨x’m2Ææñccmoy¨ÛxsV¸±¶–P©=Ü^£-¼e¥ŽÊå”BþCSF8çÓ·ùæµ¥fñ§Ë<}J’wµIáðJjûiO zn½ÇçpöTÿ‘/+Ëõ“‰ª¿¾7¶1ðwáW>¿|]ÿÐñ^¡eñZòK;GÔ|5mk¨=´}ke¯ËgmxÑ!º‚Òú}MžöÚËÇÜÚu„·1*M%•«¹‚?'AÔúp?ÏùëRŽH´èæù¼dùó*Õ“[TÃåéFÝW±ÁÒww³æm[k0t©¿°—¤§úÉ?ˆ¾0|I¶¾<#ðßÀúÞ–mcy.üGñW^ðµú^&Àšn™ð‡Æ6òZ¤KÇvÚ¬RË$²ÄÖQ,)5ÄÞø»ñòöxüaðëÁZž¶¬ö÷^ø£®ø²ö[ß6%Ky¬5O„Þ ‚ÞÕ 3È×i¨ÜÊ²Ç "ÉÒgžßòÞoÛ7ãþÔ>*ø%}ð+þ/ øCþû¯Ûø¢Çâ«üUý­uo|<×üE¡ü'ø âŸü6øAûéž:ñüW?ü!.™ûvüiñ¶ŸðwÀ^&Ò~1|ø-â•øÑ«þÊþ¦þÝ¿µį†>.¾øàO^ ý n!ŸK°º½XØÛ[ÞêVÞÖ.,-e—bOw•¨Ëo4±Ù\²ˆ_Í¿ápütÿ¢Að›ÿ׌?úkùÖ´ý³mmãÇŠþø‹û6|hÒ§øOÿ—øoà/ƒðŒ~п þ§h¯ø)¯í ûøÏ]ñ,þ/ñ¯Šüeû7|X¾¶ð÷Äqñóáö·ðÆŸ¾xôü+ø ã…ø·ªþÆž*×ÿio´¼OûrüMø]¤üWð÷ÄMwàM×ÄÿÁI¿àŸ°Oƒ/!Ð1øÓ¨øSÀºçÅk­ã» öý üuðÿÃWŒ|Kÿw‚| ]ø®Yø#ÆÞ!ñFõ±Ìå Ê¥&”SyIRo™WÁU’qu ›N1Õ´Ÿ,šÍ(kî&º]Ën›I-wëò?j¬>$^Ëeg&¥áû[MFK[w¿µ²×%Ô,­¯$7PZ_Ï¢i“ÞÚÃ9xíîæÓtùnbTšK+Ws|ˆþ*|L¶Ô/ü:ð.»¥›XÞ[ÏüT×ü)~—¦Y„¶ñéº_ÂÛÉj‹wŽñµX¥–If‰¬aXkçcâïí9ûsüVý”¿dÿŒÞøùû'| ÿ†ÂñßüŸãßÃ?é_>0Z|møcðËâßíûøzÿá^¹g¦þÓúü4ÿ4ßøio‡þý£>/h û9øRð–}ðŠoƒÿð×Ú'Œfîÿöšÿ‚©|Mø!û1ØüMÐíþËñ—[øíÿð/‡¾xš?öçÃø&çÃïø)ˆ<ã+–ôŸj~OŠ¿e¯ÙÏDø×âù|?ü-gMÒìü ©xÛÀ³x{gˆÍªSŒa”&ê8sÓ£„çNœy¥k„;;IIÆ“ƒåi4Ä”/~TÓÙ7+kèÓê·}}OÞß |Pø“w{4~2ø{à}N¬Ö×^ø£¯x¶ö[ß:Kyì5_„¾ ‚ÞÕ iäkÈõ+™VXá„XºNóÛõÚ—ÄF->öM#ÃöWÚ¬v³¾g©kÓéZ}ÕêÆÆÚÞ÷SµÐu‹>Öi‚G=äN¥5¼lÒÇctÈ!ÀoÚGöáÿ‚|ñöµà_üøMñ Uø û5ø?ö¶ý¥µ^|4Ó~Ùxcâ·ÄÚFÏÂÿî~:~Ò_¶ì‡sð;AøsàÏÙ»_мSû^IðoöÓüo>¥«üeÕ¿d߀ÚW…4¿‚Þ=áþ-þÚÿu¯Œÿ²¾½wñËöjýŸ>x·öÐÿ‚”|<Ð4Ÿk^0Ñ|OàÏ Á?¿gOø(¿ÁŸ|Iý tY~+xCõìÕ¨üQøoàŸÚ+â Ùj³Ÿû3ꇚž½ñ_Ä>,Ò~9ø éb3EcÝFÔßµ•<ª­4¹háaÜ)ø‹ÂŠ_4³ ­£ÒôŸƒþ7¶–Ò8VÞHï[WŠie–xZ·Ž{2@õ©ëib1•"¡]ZR\·«NžÍ¥½Õ\=Z^÷[SZü< £ïÊŸ“r·àÓüLß øÏâ}íõÂxßÀ~ðŒö·~øŸâ_M|&„%´úv¯ð“À¶öÖn×µìz¥ÔÉ4PÀ,'{‹~ÇRñ&§}&¤XßêñÚ\>™c©ëNw|±1µ·¿Õ-4]nçN´š`‘Ü^Ûèú¤ÖÑ3M…Û ñÔ`R äŸN?ÏùïZÓ­Š…7 bêÕ–¿½©O ª+íeKN—»ÒôŸ÷¹Æ-ß•/$åoÅ·øž}ÿ Ïí ÿD—à×þïÿô6ׯiúíü¶Rjšm¥–§%¥³ê6vœÚ•¥ûÂwme¨ÜizUÅý¤H­¯gÒôÙ®¡Dž[ 7‘­ãÇQ’OUBXªWu1ØŒO2VU©àâ¢ûÇêøJïgÌä»$e'ýؤ“èäï÷Éès~(ñWÅk]J8¼à?‡¾ Ò ¤O5÷о)ø“ÁÚ’_´³‰­£Ò´ƒþ:µ–Ò8ÚH¯[X†ie–x[O…-ãžæÏ„|GñNúþxüsàxsKlö·žøŸâ/_ÍçBÚãMÖ>x ÞÚÑ­ÚæV½U»™&Šžép÷6ÝkÀ§?‡ÿª® êþÏZ¸,S¬ê¼~%Óæ¿ÕýžØÛùy– b9VÿÆæþ÷@ºµ¹c~÷•ý~+~×m­»ÄrðÖU±ž¸à÷ z䌑S×Í_´Å¬ð+â^¹gyq¦k^ð¾§ñ+ÃZ­‘U½ÓP§JÛëü>k齺\ì¢ÿv¼›_ÿQÊ2@©ê4OáþJ™FHüÍx»¨À¥AÉ>œŸóÞ£©”`~?þµmeë¨Q’çþJž£AÔþçô©@ÉÖµ‚»¿oÌ P`~ÏáS êóúTu:ŒùŸóúV«V—pHµi9ì8çéUÐsŸOëþM\xדþ~•дIvuߟÏÿ­R ç>Ÿçüi•2 ¯?çùþ5¬÷ü¿¯È’­qþ|2øÑàká§Æ/‡^ø±ðãÄŸÙ¿ð‘|?ø—áøïÁ:ÿö6­a¯éÛ^ñNŸªèZ¯öV»¥izÖ›öë þëi¶•¯•ygo4}ºIôãüÿžõ äëZÂüɦӋRM;4ÓѧÞö&nÑ~z_+žmû.~ÌÖÚ¯…·Â?E«ø7á—Âohþ|:𮤞[Ïøá§Ä ø_âÃÿéYøÁ¾6ðׇüWáÝ?M×tm:þÛ£ñÀOž1ñõ¿Å_|øO⟉öz…¼-iñ#ļ­øö×Ã>ø¤|aðO‡müa©è÷^!‡BðÅÏè?ü-¤G¨®ŸáÿˆÚ&‘ã}&ÞÓÄÚm–©ªô©`~Ïá]Òþi}ïüü‘‰óuÏìeû_iÿô‹ÏÙGök¼Ò¿h{KñOí¦]| ø_>ŸñÓÄú'‰ï¼k¢øã”¾{o‰Úö‘ã=SSñn—«øÚ=oPÓüO¨ßkÖ—ê·wrtž ý–¿fOøƒÇ^,ñ_ìéð'Äþ*ø¡ö?øYž&ñÂ/‡ú׈>"gü1ñÁ;øNµKÃ×:‹¾Ãðkâ'Ä„–Û÷:‡Ù¾xëÆ>‡g…|O­éWÞäƒ'éÏùþ…LHµºœÒ^ü¶³÷Ÿh«oýدHÅl•ƒÊ¾ |øñsÄß ¼mñ_à·Âo‰Þ3ø7¯7оx»âßøÓÄÿ |Nú†ƒ«7ˆþkþ$ѵ-WÀÚój¾ðƦÚdž.ô½Aµè7¦àÜé|¶òÚ|øiñ'WøÇið[á5¯ÅßkÞñV¿ñRßáÏƒàø®xŸÁÞñOŸøWñÄZ2øŸS×¼-ð¿Ç^6øoáÍ^÷T›PÑ<ãø;L¸µð÷ˆ5m:óÕúT±Ž>§óõÍkMÊÏÞ•¬ãk»r¶¤Õ¯k7fÖ×WÂ3ëþÆ­(À¡Qȇòµ=iv¼µû€•õéþÏJ‘FHÐ0ô©Pu?‡ùý+`$©ÀÀÒ¢Q–Üþ_ýzš·Š²_¨ êþÏZ™FHüÏùý)Š0æÏéR êþÏJÖ wòþ¿$©”`~?þµDHµ=kv¿AIÙ6Hƒ©ü?ÏéR’­5F"séÓüÿžµ¹aSéÀÿ?ç­\Q€?3þJ¯ô¼ŸçüªÍmd¼õûÀó¶°^üøÁiušÚëáwÄ kˆ˜°Ãqá=^)c%J°2’¬AÁŸìuÿ&ãðëþæïýNüO]/ÇËæÒ¾|kÔÖ13iÿ >$_¬,ÅSiàífác.yaKb “ƒŒW5ûɸü:ÿ¹»ÿS¿Ö¸iÑu«SŠ^Þ4¨N£å´Ï)ÎÞòS§ˆå3änNËžòvvO£m/T•ôôk×ä}5_!x/þOßö•ÿ³BýˆõsÁA«ëÚù Áò~ÿ´¯ýšìCÿ«›þ ]¢>½¢Š(ä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{_!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚ(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢ª_ߨévw:ާ{i§iöP½ÅåõýÌ6–vF3$÷7WBƒ—’WDQË0›QNRiE&Ûm$’Wm·¢IjÛÑ -Ñ^+'í'û:C#Å/Çß‚±K2IŸü ’FêJ²:6ºYH!•€ ‚™ÿ /û8ÿÑÀ|ÿíàOþ_Wö®WÿC,þaÿùgšûËösþIÿà/ü¼×Þ{mâ_ðÒÿ³ýÁ/ü:ÞÿåõðÒÿ³ýÁ/ü:Þÿåõ/íl¯þ†YþaÿùgšûÃÙÏù'ÿ€¿òó_yí´W‰ÃKþÎ?ôp¿ðëxÿ—ÔÃKþÎ?ôp¿ðëxÿ—Ôkeô2Ëÿð³ÿË<×ÞÎÉ?üÿ—šûÏm¢¼Kþ_öqÿ£€ø%ÿ‡[ÀŸü¾£þ_öqÿ£€ø%ÿ‡[ÀŸü¾£û[+ÿ¡–_ÿ…˜þYæ¾ðösþIÿà/ü¼×Þ{mâ_ðÒÿ³ýÁ/ü:ÞÿåõðÒÿ³ýÁ/ü:ÞÿåõÚÙ_ý ²ÿü,ÃÿòÏ5÷‡³ŸòOÿåæ¾óÛh¯³ý¢¿gÝFæ-?ã¯Á»ëˇÛÚYüNðMÍÌò‰ k,®{*#1ì+Ø‘ÒTI#u’9^9ƒ££€ÈèÊJ²²ÊÊH`Aƒ]41XlJ“Ãb(bl¤èV§UE½R“§)%uµí~„¸Ê;¦¯µÓ_™óí;©_ØÛüµ³º–ÞßZø¿6›ªÅ/¬!ø9ñYŽÖpA&$Õ46ô*|ë8Np>=^ýûCxvkGð¥-İ?„>![E#%ä×>ñï„ÚÞrܤKoâ{‹°ñüækXPþíÞ¼ FHŸÒ¿1âXMg˜¹Jü²ŽTõºQúµ´•ô÷ãSNíÊÞõß]á¯þ÷úXùÿDøyàxçã.¡â¯øCÄÚ„´k(o¼AáY¼†Ê?ƒÿ î#³ŽçQ²¹ž;X繸• W,·H¨W-Û'Á/ƒGþi'Ã#“ŸxWÿ•_çÏø«~6ú©šHÿÌ5ðŸé^­ôö¯ÿ¯šù<. 8Õœðô'9b±—”©BRoëuµmŶöß] å)&¬Ú÷a³Ëò[â§í½ûüý¼>ÿÁ=|QðN;ߌ_ôÿÍcã áÃï„^ Öügeã«ïø3â‰îµ«èÞ1ñ¥¿€u'ðÞ¦x?\±êš%Ô·6ÖSßÝißmiïûj5¿‚š|Ÿ²Þ¡ñ“Ã2xÄß lŸá5×ÄßøzH­g]×< “Å:FŒð^ÙLšþ•obÑÝÛH³•¸ˆ¿áÇoø"÷íwñzoŽ_´~›ûGx;Hý±üeû||-ý®¾ø6ïVÑåý›4¿ þÎúçü#_³¾•â¯?ìÉ}ûIiÞ2ø{ðWSñ.„m¼'â%ø{uâNU“×1_]k©ïžÿ‚PüsÓþ$~Ü:±«~Êz‡ÂÚÏâíÛñoÃ_´¹àÏøÓö×ø1â?Ûoà§‚þ\x á±ucá |*ðÃëŸß\k~"°ñŒo¾*xRÛÃ~ |?’ÆmV¾Ÿû'#ä¦à°ªP •T¨BWÄESsšN”}Çí%F ¹û7%$Ô¢²ö•½º¶¯n‹}ôM·¢Ñt?Mt_ÿÁ<|CðïÅÿtþÆç‡ú…Æ“ã߉úGˆ~ê<ªZ5º]i¾.ñ­äþðÕý³ÞZ%Åž³©Y\B×6ë$jgˆ7KáÍcö ñEÇÂ[O êß²ˆ®þ>Úx®ÿàM®‰ðgW¸øÕcà=9u^|%‚Â[‰~#Úx7JuÕ…>ø‹¦iµ^—û |7²øyk¡þÚÿ¯|iâo§Ä%Z†-4ƒÚÕ·ÁË -'ÃñÜ|JMbÞÇÃ/ø$íeðoãWì§ûUøCÆÿ³ßˆ¾-ü-ý²ÿࣵ7Ä_ƒ:ï‹~%øwà¿„´Ÿø(G€¼5࿇ÿüm¦|-×|Kªi"ð­·ˆtësáÏ€í|y«ëzà–ãÁ"d¹}ã“äÖ’K Ýêr/«Q³å åM9rZÓ¨£SH¤ìÒjáí*iñt¾¯ºMü’Û}ú'íÿšµÒômjïã·üºÛFñ…¤ñÖ«Ü|OýšáÓ5ÏCãKá”Þ1Ñïå××Sð¬_4]cáôž!²–}! Þ|TÐt˜-­ï'Õ5¯‡¶ï'‹´½:K»[©ooôˆ-c¶¹·åM7Éßo/ø#—ÀOü.ñO‹>#þÆž"Ñ>.ütÐÿgß j?.þøßOÓ¼i¨é~*Ö|c«i‹èþ ðÂm?\Ò¼CñƒÆÞ$Ôtíáþ©è©«Ê5Ÿx[HÖÿ8þ*Áÿm?Ÿ¶ö­ñ›Æÿ´' /“ø§þ §øBO ë>8ðç>|&ý½¿f=SáG€4Ï |Ñ>i ãø“àŠ~'ñgŽ~+|FoˆVÞ)øË=ý—ŒªÇþÿûbÉû9þÉ^›Cÿ‚xø_ãìWûWÿÁ8~:øNïᆟñGÁwß´×…`Oƒ÷ß ä‹öžý¢ŸáŽ­âCÆþ9‡S¹¿ðºoÀiß|Yøsd¿kø«ðÏÀp^é2j=M÷Æoø%&|85ŠÿðO],ø»À—_ü(5/~ÍöGÄÿ ¬t½K\½ø‹ááuªÅýµàK=GÕõk¯é¿iðý¾—¥j:„º‚ZXÜËóñûAÁ¿n¯ˆ~øíâ?„¿f?þÑ¿¿à¤ŸðRÚ/Ã:¶§ãŠòx7Mý’ࣴ/„¾k^$Ó¾ IâñF¼9aé¶~ºð¤V“>µeâÿí;X4Ù>†ø¥ÿDý­5ÚöNñÂߌáý’?e_ÚGþ …ñ{ÀŸ5=gÆ¿ üQ¢xSö.øeÿ ·ãhÖ|à_…šï„¾2üXø“¤xÀ2ü<ø‘ñÆk¾ð¾‰7«mOÃ>S5ÿ\2Œ™Ùý_¹“ºXZ:4ÒI^›½îš×^Yj­Ò§yn¾Óëkõé¯áæ~˜þÏÿ¶'üCöˆý—ì?l_ 럲o>_üAñ_ÂùðΓmð xã㿆~x÷â±§üÑ|EªøvË⇈5=Î! Úhš°Óï.õ=+íZ¥…âÞ§â—€?à…_µ×?f¯ø'wÁÛ¯üñv±ÿ×ø¯û^]xOFð÷íGûUþÏý¥¾þÕº¿‹õ¯øI¼YñCà_ÃãìáñOá´Þ%OÒ´_|[ð÷‰´—ñ.‘®xÍ|=â½[Ãòt¾7ÿ‚þÑÞð_…t¿€òþÆñ\k_ðIÚSþ ³ão†>(ñ/í/¤| ø;â?Ž_<[ñ§LøÃðTøŠj‹:ôÚWŠ|c}á;è> øËLÔ,´+ wïe§C§|4Ѻ£”dü×X<Š“I<.áMòû܉¦â¢îãö­ñ&„êT¶ò¾Ÿiï§Ÿ›ëÐýøýžü3û)þÒþþÐÞýž¾é~ øõðá·Æiž+øMðâËÅo…¾(ø7EñLJì¸L;ÒúË¿‘“©6ïÏ/ü ùy÷W?>?jŸ ü=ø¥ü!ø‹ð·á_Âÿ ø¢ÇâãiŸnѼ¡èr]éÚ¯ÂO‹6×v·>·Ñµì¼›¿²ý¹ kÛ;;‰cÛ¢×Ôh9ϧõÿ&¾sÿ‚‡ |.ø^}~5Xê­ø¯ÿׯ£Ð`~Ïá^M\5 =Lb¡B•¼ÂK–8Ó–W”5îÁ(ï)=®îîh¤ÚÛvWµ>ãºÕ¤@?ý_ãøT2~œÿŸçøU´^Ïóüj’²KËñê3Î>+xÚûÀÞ[½ÖÞ÷Å Õôï xJÒð9±mwXiv^ê6ŽGÓ4M>ÛP×õ(¢’9îlt«›kyâh˜|ò>øOZs}ãû(~'ë÷I{­xúÚÛÄAîX|ÿÙZ6¡ƉáË,Â×KЬ,m-£lm–f–y}OãÀÿ‰ÀµÆâïßqÔ> |f`‡¡Ž@¤‰qn?×üû×Éf·Äã«R­j”pþÊ•*3\Ô”§JiÖ”ã*²öªš“M´9yêsïv)­»mhôvKÑZþ¯ÉsÀÿƒþiÃÐ ø‡Ÿ_ùÿœÕèþü?óH>€gáÿ„Ï>¿ò ÿ9¯H‰qn?×üû×–~ÐZ7Æ/üøÃáÿÙïUð®ñË_øoã á/ˆ¼o©ëG„¼7ãýgB½Ó|3â]zÿ@Ð|O­EaáÝRêßZ’-;AÔ.nÚÉ-8Dæâhåø9J+ê¸Uw§*¬®Òæo“D´mö¹\Ò_j_&ÿÏÉ}ÈüïýŽ?nø'¯í»ñïö’ýž¾|µÓ¼Sû3Þ_&·â_|ø[£xâvƒ¤øïÅŸ õ|Ö´kÄ:‡‰ü¥x³Â7z}æ·®h¾Üu-/ì¶×2J-7£øÕûxÿÁ¾xᇉüWñ+ö<ñ‰ño㎅û?øcQø}{ð7ÆÚ~Ÿã;ýGI´ñNµãWIÔŸGðg€¾iúÞ—â‹Þ5ñ¡§èÞÑõ=5yF±â_ éßç_€ÿà‚~ ø³àÕ¯ÂoÚuï~j¿ðNÚSöý£.$jRüsñ‘ñÒÛ¼sÅã-WP¹’ÎÛ¥±ÿ‚CþØ-û:þÉþ›Cÿ‚{x_ãìaûVÁ9þ9øNë᎟ñCÁ—ß´¿…ÿ`¯„Wß d‹öšý¡ßáž«âCÆÞ8‹S¸¾ð$ÚoÀNø= Cÿq¿ø‚·Òx‚Ï裔ä.¢”)a+AFÆ’•õƒr”èèå(©´£h)[™§,ý¥Tµnë­Ý¬¬ú5²Òú]­´?Uþ|mÿ‚~xÿÄß¼¨hŸ²ßÃï|0øñ¯á­ß„ü]ã_ØëZñ/ˆ¡ýŸl´KâŽôÍ áwÅ/ˆú§†ü1á=7Y¶Ô›gá+¯ Åi3ëV^/þÓµƒN“èoŠðDïÚÇPý§ÿe-{áÅï‚PþɲÇíÿÈø»à_‡ºž³ãO‡>&Ñ|-û|4? þ5cÁÞø[®xOãÅo‰Nà)~|Gøã85Ï ø_E›áe¶§á¯©šÿª>JÜZ§ƒø]ãõj:J.)$Ý9_›™4Û_ í{E<ÝJw.Ÿií§gÓ_ÃÌý.ýŸÿl?ø$ßí û2X~Øñì§ào€wÞ?ñWÑâíOáï‹<%âk­O‡\‚û÷ßf_èzG‰¼/ð«àO‰|7â 6ËWÐñ.“'‰4oÆKáÿêÞ“úýf†ý¿dO‚³ŒšOÃëᧆõ+]CDøEqñrïá–‘«øÅ÷Œ53À÷_þ$|[ø³uáÛSÄ7–ÖøÇÇš¥ÜÑÆ$Óôï è­¦øOCÞYNSºXL$×´”bží)(6ù»Š„¾½þ]% &)Ôë)àOËÏ×îó=’?ÙÛàëð7àñè~ø+“ß®‰þsW£ý?gÓ×àWÁÃÐ ü1ðO'¿]üæ½n5Æ=¸ú“þ•hD½1Û©?çðâ·§•åÚ°`z%þÉC}5þƒç’ûrÿÀŸùž)wðCDðݼš¿ÁKm7áW‹¬PͦÁáÛc¥x\•e:GŒ<¦5¶ƒ©iz‘O²Ï©A§Áâ=!'’óDÕ-nUÖKøQñ#Jø¯à­3źm¼ÚuIJ]izþ…tè÷þñ>‘pö ðî S n´­F …D»·^¢ ˜óØÂ™`o×=Îz‘_~Æ—w ñö³ÓZV6V_|ewmo’#Šæûâ_ÅK{©Q>ê´Ðé–Q¶ÊÛÆ9 1Ù†¡O §GÑ¡[Š«:4¢¡EÕÃWÀS…HÒ¡NnªŠ¤ “ª•5QËÙC—IÎ-ËY)E'ÖÍJé½ÚÑZ÷¶½Ùô·í i=ÿÀŽVªæ÷à÷ÄËKt,¨­=Ï‚õ¸bRìB iAf!T’®{ö:ÿ“qøuÿswþ§~'®ßãA ðwâË1 «ðÏÇŒÌÄU_ j¤’O $ð&¸ØëþMÇá×ýÍßúøž½º! µq ËÚU¥B”Õ×*… שM¥k©7‰¨¤Ûi¨ÆÉ4Û‹»(ôM¿›I?ý%MWÈ^ ÿ“÷ý¥ìпbý\ßðPjúö¾Bð_üŸ¿í+ÿf…ûÿêæÿ‚ƒWX¯h¢Šù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^×È_´¯ü–oø'Ïýï?õ‚?mêúö€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ (¢€ üåñ֤߾ xªïÄ jð‹u_x/Â7$M¡Ükž½“Hñ?ŒµÍ9·ZêºÚø¢×UÑô/¶¥Ä™¤¥ÕŒ6ú–§5~W毃F[Ç~ßhOÌüvøŠ+â8Ö¥GG„æj…z•êׂ~ío«ª*:‹iÒS¯íÓ©Nœ­x#£då.©%Ö7zµçeoFÎÖÚâ"Š4Š$ ‘Ǫ"" EPUG`V¤c¦~§>ƒ×ÛüjœKú`©ÿ?­ywíðßÅÿ>ü`øOàˆ‡áŒ>&|8ñ‡€|?ñA<9'‹®~ßx·B½Ð‡‹l<9ˆ¼(Ú®©¡Å}&¡¤ÀÞ!Ó#MJ YæšH¢{y¾>„”T¤¢›MɦÔSi^ÑWj+V’mÚÉ3 üeø!ÿçðÇÆƒÿºÓ¿fíKCÐÿd¯Ù—ã§íuû>kš‡Åhå¶ý²¾ü ñoÅk~8Ð" íäøg ê¾7økiáû]AĤµŸ^ºy¡•ô4·Ö¾õø5ÿSý•ü}áÙ"ãÇÞ)ŸáWÄÏÚÏÁ_³Æ¡áßMቾ.ð7†~-~Ñÿ¼;ñ‹ÁµoºgÃ}?á¿Å-GÂþ"µ¾ðß„¼M¯x?Æž.ЦҼG§x>ÚÃ]Óc—à=sþ öøo¥/€¡øûCøïáž“ûþÒ_°¯ÄÝ;Æðµþ?i^?ð—ÇÏ„úÇ‚ô¿øKKøûA‚º?>"뚇Å×øaà h|âMZVÑí¢ð´¯&¾ôSþ ÿÕ¦ø³ûüIÔ¿mMgÄZg샮ÿÁ65ß øOÅ?.5¨í"ÿ‚~ü=ÿ„Ä>øU¨Ÿv¶ŸüûKÞÁ¥øóÇš,z®t_i6:´ú§‹¡·¶µ·úuO)Ý9Î’³q‹g5$¹#uV/§VNêÜÜ©ÂÊÎõWDöímuou¶©zuÜýÑÿಟðN­gàÏ‚¿hM3ãgŒ®þ üHø±áO_¼tŸ³GíRšwþ-øÕü‡üà›)~ Ǭx¯Užçá‡,µt:þÃ@Ô4û ;Äš]ï‰<3o¬v>ÿ‚·ÿÁ?~"ø«Á~ ð×Ç f?ø÷ã5Ïìá¡Ûø§àoí à+?ö„µ¹ÔmÀ¯k~<øQá­ á¯Æ+Éô›ñ¤ü3ø©xWÆšÜvþ~¢ßA,KñÿÃ_ø"§ü ±?üKö;ÿ†•þÖÿ‡wþÝ~ýµÏÄ_øS`ÿ…À|ñkãOÅðÐøGþ­çü+ó©‹ãBÿ„Ëþ}‹þßí?øE.ÿµ¿³ô˱ÿÁ3øÉ|cþ ´?àµßòF¿óZ?ä«{ÉeÿÌS]P¥€¼”jÕ²œáúÁJÔæ×°ZÉ%)-¼¶M7&îÒÖߥúúýÝO£~%ÿÁOôïÁIü+ÿèðÏÁûíCT´ø {ûKükøåñ/Pø©ð¿á‡ÃO„zV§äêú·†u7àÄ_ xá4Ý BòçÇ)ñŸÂ€øÂÑ> Ü|m·øÕ4Ÿí»ø|ürßá?Åo÷¿´zi¿ ¾ øoá÷Ž<{¬jß >;i:ªü>ø«ã3á¯ÃŸ‰^ ðn¥ðÂׯ¿~xÃÇ:ÆáÝâoÂÿøÇÀW7W"câ$³Š{˜³>2ÿÁ2|+ñóöÒø¹ûSøëân¢<ñ«þ ƒãßø&gŒ~i°ð·Ä‹¯Ä_üMÒþ(¿‰§[=Gû#X¼ðµ—…ÛáüËorbñ ñ4?±[ó«â/ü§wñ‹àÄ„ÿ¿læÕ|wûþÍðO/‚ôÙÚ IøUû.~Í´?ÃÿÚBÓ¼eðþ/wð¶>(x£Xøy£hZßíüaðëK¶µmÓüVW½”a…j Êq´iFMs6Û¼zÅ­IY$­uÎÛB|ÚÚÏ{}ÊÝ{ßþÛú;øcñÃ?<¡|Aðrø•|5â8ïgÒ‹ü ã†Þ ’ JóJ’kÏ|Gðï„üe¤Ã=Í„Óé²êÚŒz¾—%–·¥5î©i×÷^“ôö¼WÂÿðP©%·øgðªx]¢šÚd±H„«Ç*|/ø¬ñº‘Ède €j±0Qú®&)F½6{üNÒGþaŸ„ŸãúWƾÿ‚¥þÍÿà >>ÿ‚rx~Ãâ_ü.o‡Ú.»uã›Ïør?‚þ#ñ…<;àŸø»á÷„üYoã ¯jž;ð–‰ã«¯é7 Óìt¶Òµ˜gÕ„±iÉ©ø} •iÖtá)ª5qµª8«òB8Ê©Î]’rMþVNÚͤÕÝ® —›pVGé\c¿ Àÿ?ç­]z~g·ùì+ô_ÚCövñÏÄëãßÁmnóà’ݿƋM#â—õ+Ÿ„I`—’_7Äø,µÙåðÙG§j vÞ+M$[­ãLP[NR•×íkû*iŸ ôïZ—í5û>iÿuDhúGÆkïŒÿ->jº±¹¸³^›ñGá íDÞYÝÚ‹+]b[“siq•æÁ*§§N•M³Ü’ø%{É'¶í{ÉuNëA]wþ–ÿqôt “ŸÃÐ~¤þU¦ƒŸ ÿëW…ÜþÑÿ³¶‘¥j¿þ iš¦•ðbïöŽÔôÝCâ—¬µ ;öyÓî$³¾øóes®ÇqgðbÊê­®þ(ÜGí®"’µÔ‘G¿·GìFº¶ ?íû+.»«‡m¥è­ûBü$][S_‹Þ¶ñ§Âs§éÇÅâòð|Pðuݧ‹>›hdÿ„×ÃWVÚï†Î¥¥ÏÓvÓ¥Qê©Í¤¿–OM{mÒþk¸®–í}çåÿü¯^ý¢|AÿGÿ‚:~Ìžø÷¯|4ýÿhkö¯×~*øÀ:—ÆO†þ(ñ­ßìñð¯Kø©[x§âÁŽß ¼e¨hú¾ªÚé´ *ç@м ã‹}CÆßôß¾¿±øY¢xWÁ/ø+ÿí£ãø&Wì“ûjøïÃ?³ü,OÛ³ö¹ømû%|.³ðïߊÚÂÙîçÅŸ>/|!×~.üpŸXøçâMwÇ´‡áÍ垇áíw᜖º¶£ocyâ‹´Ö!:?é?¯ø(üëö¬Ô~#þКÅOÙ§V¸ý‚¾.x·àì_´Å=Wá6Œ~êß<5¦éïŒþ|Kñ^¨uü,øÍg¯à=â6‰©xÃ_À/Ó¼=wâ¿ èêSü6ðìEãÏÙ†ËÀŸ <û+xÏö/Ôtyôÿü9ðçÂOþ̆ˆÞ!Ô¼Câw³ð÷†lï>ÝéOâÉ5w^0YÉfÞ!}OSÔIÔšêsíBJèÒ­…•é:jmÅFþýz²[/~q©‡Nþô£FÍòÈÂNòm4ÓÛ[íÊ¿G÷Ÿ†žÿ‚¾þÜ¿hÙöPøsá¿ÙgÁŸþ+þÖ¿ðTÙ'â—Æoü2ø¹ã¯…)½ÿ‚xÀ:Ð>2| ðnƒñëÀú埃> Zø£YÒ5xâŒf‹]Ò¤°²ñ¤V7W—=üóþ û[þÒ~7ÿ‚IÜüað'ìçcðçþ ‹áÏÛ¢Îᯆ~&hž5øGâÿ؆ÇV:Ÿˆ¤ñWŠ~'x³BñG‡>&ê>Ô–ÇÁ‰àÝ+Tð]•Í‹ÜxóÅ“¤èß©¾ ø©ÿš²ð‡þ+xâGüºÏÀŸ±Zê:/…¾%x?Ƴ\ý’£øÇ=·µm'Ãþ1ÑuѾ'Å;‰­<¨ØiÚ—…WÇÍoáû˜µ9$ŠÌùOÇÚ?þ [ûkÿ³….¾þκоþÔ_ÿdÿ†Þø!à¿ÙÎ×ݯÿnÿxãÅËã?hÒk¾ Ôf/†?¼'áOx›âˆ´m?WñçZñ~šÚÍ¿\9'á$¤Ô¢›M;µY©^×VupÎÝ>U¤ýéÕ+Ý[Moþ¾zýçì,c©üùüªìkÓØgñÿõôúWÌ·íŸûxsá†>6x‹ö±ýštƒ>3Ô.t¯ü\Ö¾;|-Òþø³U³šâÞóLðׯ¼S…5íBÖæÒîÞæËKÕ®î`šÖâ)cW‚P½^ŸûS~ÌZ–ŸãÝ[Ný£~ßé ü áÿ‰ÿ5+/‹ÿ®´ÿ†ÿ <[¡]x£ÂŸü}yˆ^ÛÁþñ7†l¯|Gáÿø†M;@ÖtK_NÔ.tûynÓ„¿–VZ'g½Ò·¯O]7±GÐ1®úòjê§ÓþÏZù&?ÛÃöÝç†á³eøHôßÝüI¿ðÿü4OÂí»‡V“Ç—Þ>¼Ò¿á0û}¯‚¬¼ ¾3»ñTÖé¡[xN9-øa/>4øïÁ_ "Ñ|oá_ê<>ñe¿Œ¼G§ŸxƒÅz5®“ãÿøSÅ/£ø¿Søwâïø¢ëÃÚ|:õ½ºöÓ§+/uë²ÛªZ.¬™I$ìõéøšgè”kÊNÿ]jD¸zñý?žkäoþݱÂ[ÏØ|Vý²eO†Wþ,ðNñ3¶_¿ho„~ »ñ7Ãj7Z?‡¾ x~ßÄ~/ÓfÖ|¯jöWš^â½9.tSQ³º²²¿žæÞX“Õ>~Ò_³§Æø³Á¿~>üø±âÿÇg'Ž|+ðÓâ§|wâOǨäiïâÍÂÚxu/°Â͵‹[5¹ ù%ñ]‹ÞÎïM½?ÍŸ8ÿÁDGüZï…þƒãVž?óüWþUôu|éÿø[ð·ßã^ž?…Ÿ?¥}9 z׃‹_í¥Û0Ÿá•äÿ©´vøöù“F8úŸþ·øÕÄØOò*Ç?AÿÖÿ´ƒ‚}xÿ?çµ`Qà?¹Õ>Ù_Ô?õH|g§Ä¿§êOùã¿JOŽŸòøÿePÿÕñ¢¬D¸Ç·'êÃúWÊã#ÿ 8í?åíóx,-ÿ¯3xüôúT‹1/Nøþgüœ~£ déüÏáù{âªÄ¸Ç·'êÃúW|jø÷á?€_ð§‹,¾Ùÿ §ã§Ãÿ€>ÿŠûà§€þËâ߈ßÚÿØ×Ÿñz~*ü-ÿ„ÏÈþƹÿŠ á7ü,o~"ÏüP ü_öMWû?j4ÜšQWv²Z+´®Þ¯Mâ?ëï?¾$ÿÁuþ&|7ý¶üiðSDðÃoŒ¿|*ÿðRo j6þ ð‡ü1ãMâ§üãà¥ñê÷À6üK㫸>%x“ÅM®‹¡øçM±ý¼áoxÅú~“à?‰?lô=S^¸ë4oø+¯í]ì»ðâ&­¨þÀÞ0ø¹ûXþÐðNÿ„>‡áN»ñ#ÄP~ʺ'ííðÖßâJj¿¶WÀëŸ'‰¼?¨ü?‚+í3ÁÓhÿ¼;iñ’ÂóLñ@³ønÍ ]þ¥|&Ö¿à•¿~9i~ kðOÏŠß´®­mâÿˆZŽþê³ŸŽ¾9jvšÆ™?Ã_øßHñ?„¦Õ<}{mªhÚMÏ€<_â[+éb¾Ó4¹¼+­^ËkföüÇã_¿ðOŸ…¾=ý¡ÿ`ÿÁ:|Gñ£Áÿ u‚Þ&ý¯¼û5þÇß<ð—Áš·ílõŸ†úÿÄO‚šv§¤øÿâî£w¦YXx³^Õ>|ø¹sáMÊ_Äsé§N”Zý%EòB)©S§KÚsY6ÕF¥&¥Ê’|ð¦´^ÖM){Ï\euöïwÓå§Ï_N›/|oÿ‚ÁþÔ°ðφ¼ uûx›Å¾ÿ‚j|pÿ‚‹|YøÅà¿|BøÝû-ütà‡Ä½Oá½ïÀoÙOÇ:?Š~jqxËÄsèwº¥þ½â_øN×À—·ÖÞ—Ã0}>moPòÏŒßð_ÚCÁÿ¿à©_ü'ð£à§„o¿d„ðHÏŠ³ÃÿŠÚŽî|M©IÿðO€%øV_jÿ âñf«§øoþKO‡çE¸ÑÞ_Aâ$·¹Vý§ñþ·ÿ¢×~ üñÇÅcþ é¬þÎþñ¡á¯€^0ø‰¨~ÍÚÁø³Ápê±j¾ø?âÍ7´øJÛÁäz†•à»»MKA‡ÂªÜÛÚ&x-$Õ,ÿà‘ŸµÆ©ñÇÞ%·ÿ‚qþӺׅ¾è—ÅOk‘~Ì¿uO|E²ñ§…¯¾ xŽüx–ïGøj©o§x³@ºñå¿…ÂÛÙkÚsâ8.×®‹ ”[ÂI¨ÊÒzïí¡6µ²Ö*TÚvåSilF¿ÍùvJÿ'¯ÎÇä]ïüöò—Æ$}'Eý‘tÏ\Á{<{ÿUðu¾£ðŸã&¹ã "ÓS¶ºáÇŸÞZþÑ>Ñ|Gu໵²½ñç‚4» Eñ6Þ[/Ãþ øG$k7 ŸðooÆÿÚ#ö•ÿ‚VüøñûNüV~%|Jñí¬/Š/|?s¢ø¢ÏCÓ>>üIð½¾âûÓâ-[Hñ£¦êÚµ'‡o7| øC¯üRÿ‚†jÞ'øàoŸ­|Qñçá…õïx³ãï‚5­6H<­þÓ"×ÃºŽ™cñRçÅoŒ¿¶–;=Kƺdb{˜>×à±ßðLïØwÁÚŸŽ<û+|Høq¥|Yý„~ÁY¼aoð?àwÀê>1øqûMüvøkû;xQ|k•ñCÂVÞ ý -|cñ?ÃÚ©w©h0øVÇW»Ó~#ëú¥½–‰¨vÅ)ÁÂsÅsrÆ÷I§]Í6ü’{èm«—Oò×úîD‘.1žßÌÿ‡øV„KŒ{3þáU¢\cÛ“õÿ?ž+FÉñ?_Ï·ëŠÖ”vòÓæÿáÿfô·}ÿ¯ëbíº`güçòþ¾•ðìgÿ%;ö½ÿ²ÓâýZ?ëœþ=‡áúâ¾ý8ø¡û_ú­^(ÿÕ¥ñŽŸ/ü)a{gæ)ÿáVPÙ à—ø£ùLú_ö‘ñŽÏýQOŠƒÿ,]tŸéXŸ±×ü›Ã¯û›¿õ;ñ=vÿ-`»ø'ñ‚Îæ1-µ×Â߈×11 K þÕâš6(U€xÝ•`À85Ä~Ç_òn?¿înÿÔïÄõßF„£‹Äb[%\6„b¯Ì¥‡«©6Õ­ÊÖ& 6mÞ3ºI&ÆýÕÒ“ÿÀ”þÚϦ«ä/ÿÉûþÒ¿öh_±þ®oø(5}{_!x/þOßö•ÿ³BýˆõsÁA«´“ëÚ(¢€>Bý¥ä³Á>ìï|iÿ¬ûoW×µòí+ÿ%›þ óÿg{ãOý`Ûz¾½ Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( ¿"<{ªøÓ@ø+ûMk¿ ï4­?â&‹®~Øš¿€µ sÃ^*ñ–‰cãM7âWÅËÏ ^kð&™­xßÅzU®» „ú‡†¼£jÞ*×mm/ÃÚeþ¯uik/ë½~zøëMOƒ>:ñ<^$tÓ<ãÏjž.ð‹®ÊÛè6:ï‹o%ÕüQàí{S”¥¦“¬Üx¢ãW×t¾{k]sOÖO°šçUÒ¯¡o‹ãSöy~/“š†­juåkÆš¯ìe Ôºj4œ¨:Rœ½Õ:”âþ#¢ƒÖK«I¥Þ׺]Þ··dßCøæø_ÿkÿ‚ЧìáûokÞøyûKübø;û|8øÃágÃß þ üLøaá?kü7à/ˆ^.²ñÂOü ñ_ÂísCð&¹â¿ˆ¶ÿ±¯í?û;ê|§|<½×|kñ RÐìî´Ïû?Œ¼]âŸÚGöÿÿ‚$¯?ooüw¹—â¿ü;ÁÞý°|+û7x7á_‰-|;áïÙãÀ; Eñ‚µ?€ÿ¯ô EÔü/oñãÀ áø7ã¶íøE¯tMDÜÿZö¯ñÇ42G4R¨’9bu’9##*ÈêJ2°ä2’ ‚kVÉŸôÐ×O0£Í)ÒÀÒ¢ä«År:Vä­„xkJø{¸ÂR•U¸FnR…E$ã(kÈúɵ¦÷é$ûõµ¯¿UmoüIøkþ ÿ(ø•û8~Äž7Õ~9üý›´ßб×íñS[ý¥þ$iz?‚ü ñöøEûQüJø/£ü*]Ký“?jÙ¼gâ;_‡ðçî¾|ð'¿ˆŸîüS¬[x âG…/tÝÃ:Ç?ÿ¨ý±?iŸ‰ÿ³Ïü'àí'ñoCø?£øOöcÿ‚_xëàÏÀ |6AðçíSâŠÞ:øaãoÚGâg‡õŒ4_ÚOо |SÓ?áÒt‰$ø}q Øé¶ÚÄÏÁâëëËX¿ºX—öéù~#úV”KÓØcñ?äסGB5aRTù&êEFQºÿh…hZN‹wŒ#ì®ïhɸ*m$¢Q’Nóoe×_vϯÏómê$º¯ükö´ð÷ÅTø®~Ðv:GÆ&ÿƒ<-ûŸ†š‡Ãß„¶ž2Oø&?åH<áÙt)¼—Ðx3Åò; |f–(þ%x…`»}#âävWmæìIûu~ÚŸ³¯ü»þ ÕðƒàçÆ…Ÿ²wÁ-OöHý³þ2hÿ¾0Í™áˆß´Nûxü{Э~Fò~Éßµî¿ñVÐ<—ã_€ ¼ðßâ×Å«ÞYxGâÖ…¬é:>‰¯ ÌKýþ¿—©n¹9ü玿•vÒÅQQäú­;9S©%xÙÊ©íìí½K«óZÍjÝÖ|¯¿õ§Ÿ–»n?ð[oÛ+öø¡û:ÁD?gßÚ_âö…ðsFðì½ÿ´ñïÁoÙóÂÿ #Ð|5ûWø‡â×>xãö•ø›áýcã/íö‰Ó´‚4Áá]'Hy>Üè:e¶…ñ;ÁPx¾þòÖ?èCþ [ûRþÔ²Þ•û,ë¾6ø;ூ|]ã‰Z7Æ ½zëÀ¿üIâÃaà«]SáþáÚ7ö„øñßö?ø)<:œz槨ÙþЫà¾)µžàŸ†Þ1Ó5Éu[í?÷%Î=Ïè?É­(—§ýõþÒº©Õ‹öIQŠTù›Õ¥)Ó¥MÝrhß³rmó>i;5dzë½¾Vm÷ó·o#øÓÖÿ૟ðRï|ýŸµËøÛÇž1ÿ‚£~ÊÞ øoû j>:ý—¾|Õ>þÝúí ¡üñž³â‡úTŸ,m|%㟃Þ9Ó?i ÿâ'ŒüwàŸOàû«ÿ xSÂ×§ð^µðÃþ !ÿðwŠ<+¯øçöÅÖ>-hú7üÝsÿq׼⟳w†¼;ãÙÆþÊÞÝüK©êøOáŸiž?Ònm¾ÝáÝKÃÞ&Ò-`žûRÄ–þ+´m.ÓIþ¡uØßözñíáßÚ¿Å>ñŒ>7ø.=@|?×µèBpÙRоH«6öM-’²½“m¹Y2%u¥Û¿K¾–׿î??ðDÏÛö…ý¯|Eã¹ÿiÚlk?¢ð¯Ä}[ã'쿲ãøUb¯xgö¥øµðËÁÚ/ü4†t}FVñ7à xné¾üc¸ñ§ÆýD“ñsJñM·ÃíR-(~“ÿÁD†>ü.÷ø×`ó|Xþ•÷6£«i:Ÿs«kš¦¢éVQ™/5=ZöÛNÓí!Q––êòòXm @Ë<²¢’M~N~ÐÿíjŠß>|&yuÏøw]œAƒMñ/Œ5;y4™¯ô`èçÞðüÚËM¯€¶ZŒº•ü–&m;NŠú÷,léÉáðñqúÅ\n¥:iÍÑÂãpØŒE^UðÒ¥J”œªI()rSæujB3"ž¯¢Œ“ok¸´—›mè–»½“kôËãHÏ…´ÿû-Iü4íWÿÕ_2×®~ÓZý„?-,·Öþ/\iš¬1úÂ/ƒõ˜ígbMSHÓo@R§Î³„ç©òTaíÏùükâxž¢žqˆŠM:PÃÓ•ífÝUºò媖¶wO¥›è£ð/6ÿ;~‡“xLê1ëG·²»ÕãøfúU®¥w>Ÿ¦ÜêKðGá1±ƒP¿µ²Ô¦¹Çwwm§jÛ@ÒM •܈°Iø ðþ¹ûVü%ñßì)ûJZþÒ~ñŸí)ð£öºø±ûF~Õš‰o´-;àî¯cûQÁq£~Ó÷ß¼UáÙ·Jøáâx¯Âšo„t=FøÑâÛÏ <:HŠÖç¶¶ÐMû‘­xîóàþ½ñ#Wñ€|cªè.ñî©hz÷‡®| qep¿ð¬|¡=¬¶z·´mnÞêÿ k!üý%-LQC$WR „’¿µß„WþißŘƒáßÿRq¸Îpʱ؊sƒ­‰‡,jÓÃN’§^Mºr÷“‹»V5¨¡&¯(«FNq‹ºPèäžëóó?|=ÿ?ý£ì!ûB~Âz÷Šÿd .ßUøEâ_†_jŸø+ÇZŸí_ñµÛÄ?µ®¦|øy¥è‰àÿ„W}§x7^øMá;OЦMn÷^ø‡Œg-ƒ®>¥ñ¿ü£ö­ñW‰¿eŸÚ7KðGüçŸgÚö´ø½¬þÍ?´Ÿ‰eoŠš_íGðê/†§Ä>![ü<ñ_‹¾!~Ð~´±µñ.§ñvïàw‡-¼q©ßßé¿ð‹øR+tÕoB5ۣῇµ/i:Ÿ€>+ÇyãÜøcÃé‡Ãé’ãX´ð§‰üi4SÈŸ¶Úĺ„5¹ÖyŠÆÓA °&[ˆ•º¨ÿlo)Ë|9ø¨~–ÿOóø=ÿJöÖ/3rrž2|ò«QÿÂ6e«¯MR®šŽ8ª°¼½×ïSäº!F íÃd¿‰8ý®š~7½™ø1áÿø!GíiðGÀº'„¾|^øñRóÄ?ðGÚþ {ñVøÍâ‰~ ëÿ~2üBøågñ À«á†ÿ&ñ/„|7ªøþ_‡ZW‡5É|#y¢øKEÒµÈ[V¸…¼,ÜÞ£ÿý¬uÙCþ ð\xëöboˆÿµ'ì×ÿkø5ð‹ÄÓø›âCiñüßáÂ?üy»ñN±ÿ jMwBðÿ5_‡—— §ðΕâïéÿØòøÏKð]Ç›ieý /íŸàr~|Vàq‹o‡üò½*uýµ<¸Ï˧ýá¿?ù’ÿ ê†a™&œ°Y““œjJO&̯)Bµ:ñ½°‰i:qVI+]wbp…´”6²^ÒU¿šû>ÿ–Ÿþ'ÿ‚;þÐúŸÃÏÛKájIû8ø¿Jø»ÿSñ?ü ö~ñÞ³ñoöøcñÂ>'ñäºD§„­õO„>Ñ5ÿ~iöZ„Ÿ ~;i~!ý¡|=6¥©¼úÿÀöÖwßt~ÃÿðOïŠÿÿaÚ3ö\ý¢|Uð“â/‰hþÑúµñ°ðèÖ|=oá?Ž^_ [xwã/Ž|áÙ{Æß´‡Œ¥·ûm÷ÅŒ÷ºÂ_ˆÿ$Õîl­µ/ fh—ö?L¯íµàUÎ~üZçþ~óÌ« ûoø HÏÃo‹œ ô_†¿üóGjÓ똹ÖX<É.hÉÂ>gt⢓Má®ÒnÉ]§}4Yò¥³†Öþ$<¿½ä;—¿ðo¯íãOÙËö®ø®ü`ýžü!añKöNø'û>üÐäñ_ÄqxgÄŸ?h‡Ÿ4ˆ4O¾ x{öˆø#û33áâøG³ü'Ÿ´n‹à[ïÝø¬xÇÄ÷Z‡§¯¬ülÿ‚4ÿÁ@þ;~ÕŸÿkmsQýŠ4oüGý¿ÿà”_¶, ßâ‡Æ¿x:ן°?Àoÿ ~#xZÖï?gMÿW¿ñf³ñÂð‹²h6šoˆt8¼Mw®Ë໨tÝÿ÷R?Û›À ×á¯Åï|Z|4õ÷øœ;b­¯íÛðôdŸ†Ÿ3ÿ^ ÿùèWl3 Eß60Õ;ÿÂ>g»t¹¿æîî”nïMÖ¡È´÷¡¥¿åä:möº}Úì~%]Á ¿i'øi¦øªü ·ý¤µÛ×öÒý²¦~:þÑߟà.û`é>Ðu?†?³í!àÏëÑišf—§ø'ÃñøÖ×âïìmñÃ?tO^ðµ·‡¼k%ü£ãÿø%7í ¨xãö ý‰¼#&»©kz÷ÀØeŸø+/ÅÏ…²—‹¿g_Ù;Wý—ü5ñãÁ¿µç‚áøSñOøsðÇà·Šþ$5¥×Š~.¥ð¹|Mâ¿Ë⯉_ƒ5#âÛ8?¦´ý¼þ®3ðÏãôåðÈóøüQõæ¬/íñðäuøeñ¯8²øcÓÿ•oO0¨­Í†Ìwm'”fvÛ²×ô‹“’Z{Önö"µ¹¡Óþ^CËûÝR±ù3üwãï…¿oýã·Â={öqø]û9Zþܺí‡âéš¿Äoèºý«i>_ˆíí¿dŸ~ø¿ðËáçí=¬Ý Zþ×_ÿhƒo xA¾Ïáߺ=Õ¦•âÿà„Ÿµç€ÿfø'WÁÛ¯|ñv±ÿÔø±û_ÜøKFðïíKûV~ξý¦>~Õú¿‹õ¿øJ=Ñ< «xµ¼I®þÒ¾.xk⋾ èšn¡ð÷ÃÚ.©ðÃX±ÒµO°x¯YÖ´ŸjÅâiøÊ‹‹ØþàOø(gÃÿ…_ñ§ÿ>óÖ«)ÿø^¿óK¾5ôÀŇÂßþzõÑÆŸÚ¥˜m¯ü$æš½:ýOóó¾»O#ïüþKÏóìÉ?ࢃþ-oÂãÿU³Où‹>,gúWÑh9ϧùÿüÜý¨ÿjO ü|ð—‚<-áüCÐ.´ˆx·PÔ<[oà‹m7û:ßÁ9ðù‚áÿøŽþ[é/üG§˜£:zAöt¹’K˜Ú4Ž_ÒhÇÜúvÿ<×^^Òxšªzp©©*~ß_ )Æ9vUMÎ0ÄS¥QÃÚBpçQår„¢›qvÑ+$®Ÿº¶jV÷§½›³òܰƒzŸò*ÈzTH9Üÿ…Mֲл^ZýÃ<ãªì¼ø%xãÖÝLH Ô~|XÐìw“Óí¶«§ÙFKMuÿã^Ÿ™ÿóèkÓ¼iá +Ç^Ô¼/¬ˆíoÖÚX/l¤êV§§]A¨èúÖ™;$‹§£j¶–z„¯±-Õ¬^|3@d…þMñW¾"|ü:×üm£À¡m> |6MkR4Üox{^Õ´Fðž¢QTÜÚǪêúlÓ3¾zcÝmkàcð•á«^+W¥‰Tæž\EHV…8Q•9Q£ÔåtéBp¨ ãwR3pq‡´Ú2\©6“WZ´•›ºwm+Ý´ÕûyÛÝbLà~'üóøû_žŸðQØëâgí}ÿ >kžÐÿá—à¤_²ïí‡ñþOÄgö¿Ã?‚ðœÂU¡x?ûÃ&þÐñÞ¡ÿ 5‡ü#úfµÿþwäÞixŸIòáûG«Åûcø-Íðçâ©çµ·ÃËŸ‰ü“WSöÐðBõøqñ\ó“‹o†ÿ€çâWùɪìUÆqÀfŠQ»WÊ36®×+½ð›ôß ß+Vs‡Oùy/ïZögãßì/ÿgý¢ÿeû¯ø"…ï‹|Yû?Ý^Á;þ I'íwà­wÇ3Ý|AÚú[ü%o†÷ŸÂí_¿†-õM.߯ã§ð;h°Y¼>>&Ž u—èø(7üoö¥ý¨h>*üŸöVøã+_øSQ|0ýµ¼'â?? ?lï‚z_„¼QaªüTðÏŠôØø«áímáhvWzƒü#ñXøMáߨkš…ž«¦ø®{{m]¿CSö×ð*õøoñdó“‹_†ß€çâgùÉ«Iûox zü6ø¶yÉŧÃ_Àsñ7üäשf-ÕUg‚̹ùf¿äQ™¥ûÚÓ¯$Ò¤״©?uÞ.ä“ÓÅÅkiBÚËÈl’KívKç±üäiðL_‹_¶GíÿÓ~x{Ç_ÿbk‡¶—Ž¿b­#ö–ø=ñsöw²?·ü?ösѾüKÖ?áø›àMâTþkVž8ñ•¬…¶±xo[ø“ssà ][_\7±|`ÿƒ?h¿‹~ Ѽeñcàwí>ßþGû9Á7u[Ã:‡Žf»Ô?j_ß´ÃÚ¿‹&Óâøy¢ÃðCÇqü>¼ð¶­ã «ø~"MoâÙî¾N¦Håý¤ø•ÿ/ø)ðwÀž$øãüb³ðß…ì õûÚéß îon¤H-l4ûcñJ/´ê÷rÃgg K fi”Í4,³'eáÏø(oÂ_hZ?‰tü[Ô´MM²Õô«û{?†žU柨[Çsk:øž’'™ ˆZ9%‰‹G"$ŠÊ;áŽÄ% }S0ŒchÅ<«1”¹  Û’ú§3æo™ÍÅFRså~ì”g‘lÜ?ðd:ö÷¼–9þÇßü}û~׳‡Æï ~É¿|mû^éÿµâXbïkú_Ãß[~Ð~Õ¼‹ü[ªx²ÛÂºÇÆ¿Œë§ê1ëþ)_xCáªxçS‚ÖÚ?èÐéëy{øKñ·þûs~гï<⯈?²o„¾(øWþ gûÁ.>Ûè=øÁ¯xÆýšÿko„_´Oˆ¾5üSñ£ð3EñÃßø“Bø\šð÷Â^ø™§C«ßî½ñ¬VJncþ‰öõør½~|bëÎ,¾þ_òT‡ùÍZOÛóáºõøcñ“¯8²øaùÉSç5µt î°ÙŒnÔÿäQši+­¿Ù/gdÛÑn7÷”–þ$<¿½ä}ëôüÏôýo­j[§ñïþxÿ<×À ÿøh½~|fëÎ,~þ_òUùÍ^Oø(gÃ…ÿ}ñað»ŸüÊÃü“]ñ”•¯C1VWÿ‘Ni¿þôý<ŒÜdÝï/ÞCoü Ï_Ÿf~‚Ľ?3ýÿ[ë_žß±¡g­xûö«Öté’ëMÖ~,kΛu Ö›ªüFø»¨X]FA!£¹´¹†hØ H¤g5æÿlψ4û¿‡Ÿ¾x§ÂòøŠÓ¯|M©6Ÿ©øÞ;¥hní|3 xZë^ÓôÍBæЧˆ.µË¹4È¥–âÎÂ;èíïm~µý’>Ý|ø{5¶³6¾%ñÖwz„$Ñé:}»Ûèú3O?áÚ?³ýM¿ùbó_4~É¿ðOß>5ø[â­c]›Å“ÞÙþÒÿ¶—„ q'ƒ¥Û£øöÆøïàOúóÁ—soáÿi–æ4•-a1¬m¬ì’ÞÎÙêù ö!ÿ’3ãOû;ßø(7þ·¿í+Oû++µ¿³pºvúž×[;{=×NÁí'üóÿÀŸùù/¸ó_øvìãÿSoþXŸüÁÑÿÑýœêmÿËÿ˜:ý¢—öNWÿBÜ¿ÿðÿü¯É}Áí'üóÿÀŸùù/¸üøÿ‡hþÎ?õ6ÿå‰ÿÌðíÙÇþ¦ßü±?ùƒ¯Ðz(þÉÊÿè[—ÿáÿ•ù/¸=¤ÿžøÿ?%÷ŸiÿÔý"u’'ñ„n§*èÞGR:eð PA¯¦¾þÏß ~ ¬“xCF‘õyá6÷"Ög]G\–ØM´w+{[v*¾le”W#k…•£Œ¯µQ]40¸\*’Ãa¨aԭ̨Q§IJ×·2§Þ×v½íwb\¥-ÛvÚí¿ÌðÚðëzO€u)ne‚O|A"¶Š4FKÉ®|ãß 5´å¹Ž%·ñEÅØxþs5¬(vï^ƒŒúÿOòkß¾=ë¶N‰àí>ñ¥>(ñÄz’#ˆÈ¨[ø;Æ>&‘gp@†!¥øwRq+d–q™A8zWæÜL©ÿl×ä·4¡‡ulîùýŒæZÙû%MÛOvÎÚÝõQø«·ßþw£$~güþ•5Fƒ©üùÿ=*daíÏùükÇ5>cøü1ñ[öŸµ‹?õ‹kÚú™SøŸÒ¾Zøÿÿ%_ö ÿ³¡ñoþ±oí{_T(À»$­K çFOïÄWÞ^¿¢£,=¹ü¿úõ51úñþÏj‘FH µ¿eøÿWŸ×üš¶ƒïÏøVéY%Ù:3ëý?É©sŸOóþ5zTè0>¼ÿŸçøÖÐZ7ßòþ¿ S€¥Dƒ,=¹ÿ?Mֵл^ZýÀJƒŒúÿŸñ©Te‡·?—ÿ^š8zT¨8'×óþ{VÀ>§JOÓŸóüÿ –·JÉ//ǨœŸðRO…—þ7ýœ¿hÿx‡U´›ÀŸ c¯ÚŸ^𷃣Šs5ÿÅ­sà·Žü?µÉ_ÒÛø/ÁWzÞ—á+¼Å:¯µÝbþ5½Ðü5q×¾_ü(Ö>&ézn«g?Ã?xÎëâ|2°Mï55Æ©ñ#ÃÖÒsm7…õ_½çŽ´(”‹3Rñ‡‰tdŽ NÐ-¡óÛècöýµýZWíÿªwÆUõÚ3ëý?É®ˆ6à»sI.Ú(½»ë{ù¾ìÊv¿Ÿ_Ãü‡Õˆ× üOóçùT*2GæÏéVu?‡ùÿ=ª¢®×Þý, àŸ^?ÏùíR(Éš•*§ðÿ?¥n•:Œ*%aíÏåÿש«x«%øúæ¿ ¯Á¿‹nÄ*¯Ã/’Ì@Uº©,Xð’x“Åyÿìuÿ&ãðëþæïýNüO]'í(1û8ü~ÿ²'ñTŸÇÀšõs±×ü›Ã¯û›¿õ;ñ=MîXšønK*40Õùïñ4ÿ³½ÿ‚ƒë{þÒµõí|{ûÏ _ þ*xuäUÖ¼-ûa~Ü0xƒO9:\Þ0ý­þ2|RðÜwq0jþñÿƒüOg¹@ŸJ×´û¸‹ÁsŽöQ@Q@Q@#ñ÷ÀÚß¼k?…aŽëÅÞñã éóK¼zÅæ™k¨éz¶ö™± ¥Çˆ<-­xƒC°¼™ã·³Ôu K«·ÎÎw«t¡%·ºÓ¯Òi/´]Re­iÝ[Et–z¾›#ì/R)£2A(èÁãi"d‘¾ý¯ø«û?ü2øÅäÜx»F•5›hEµ·ˆôkìíre,Ën÷Tö×°FÎí :•¥ìvìò5ºÄe¿Îfœ9C1ÅÕÖäIò¿`éÆ MÙJ?Xj)¶¥;Yëíãm¥~Ú[§[ß¿NÝôùßãèÏÅŸØ|ÕÐø·ÿX³ö½5õE|“ûC~Ÿ¬~(~ĺ!Ö|{}aã/ÚÅžÕâÔ5 Ü…°¶ý‹ÿkÏFöÑŸ$"àê^Óâf¹Šêg-Ú, ;Ásoô?ü;Göqÿ©·ÿ,Oþ`ë£Ã8ÏÜÇ _ Ré8JUZæujOÝ…:X”â”Ò»šwMr¥¨£Z:ÝI]ô³è—WÌí€ÀÒ¥AÔþçô¬5ÿ‚}üETO|LDU ªºÇ…UTaUTx, •yÿÞýŸu ÚæþïÆ÷×,ª­qy?‚ngeAµ¥›À¯!U(-… ºœ/^×ÕñTjÔæWhN„Z÷š©¬I´ÒJ.šR»nQµ˜««ë—“MýÎß™ßF½§'ùÿ:ž¼þÇþ ËðKiMÔ|y§<ª«+XÝø2Ñ¥U$ªÈÖþŒº©$¨b@$‘‚M[¹ÿ‚z|¼‚Kk½{â=մ˶[{SÂSÁ*ä6Ù"—ÁM®@8e#  USášþËš¦&Œk¥&©B3'%~DëËÙÍ'§3ú»q»´gmG]_H»i«i?=5^šëäwŠ0æÏéR êóúW’ôgú›òÄÿæ·×þ ÷ðaQ ^A%µÞ½ñêÚeÛ-½Î©á)à•rl‘Kà¦× 2‘P+þ£û8ÿÔÛÿ–'ÿ0uurWì0R“·4~²ÔVªRµ›ç¾ÕûY[§[ß¿NÝôú‚§JùŽçþ éðJò -®õïˆ÷VÓ.ÙmîuO O«ÛdŠ_4n¹á”Œ€zX¿ðíÙÇþ¦ßü±?ùƒ«­€ÆBqú²ÃU…““¯^®JWÙFž¥Yó9ÅÞë–ÊìS^eè“üÜ|ÿ­¾¼AÆ}ÏøÔª2ÃÛŸËÿ¯_.¯üïàÂ*¢x“âb"¨UUÖ<(ªª£ ª£Á`08¬«Ïø&÷ìû¨N×7÷~7¾¹eUk‹Éüs;* ¨­,Þy ¨áAl(à`Võ°XˆÁ:J—W…i΄lùš© X‰6’‹¤“M·(ÚÍ)«êš^Voîm~eÛïþLKöÕÿ³Jý£¿õNøÊ¾¼JüŸý´?`_‚ÿcÛž¼ñ•®§áïÙwãæ½k¸ðŒ—WzŸêvp_¥‡ƒl®'´k‹tY£Žæ m Šh]„‹õeÏüÓà•ä[]ëßî­¦]²ÛÜêžž W!¶É¾ hÝrÃ)õ´§†¯õtª*0®”Ú§ “©IÉü)Ö•sJV\Òö ÆîÑŸ*æ‰4äÝݺkç¥ÞÞºùl¾¹AÔþçô«±¯ z þ?þºøþ£û8ÿÔÛÿ–'ÿ0u¾¿ðO¿ƒª‰âO‰ˆŠ¡UWX𢪪Œ*ª€ÀâŒ5 Uçõ˜aéh¹ E\E÷ææU0Øn[ifœï­Ômª|½}ï¿)Jý{~:}‘S¨À™ÿ?¥|7yÿÞýŸu ÚæþïÆ÷×,ª­qy?‚ngeAµ¥›À¯!U(-… –Çþ ËðKiMÔ|y§<ª«+XÝø2Ñ¥U$ªÈÖþŒº©$¨b@$‘‚M8ÓÆ{{J–aÔŸïV*¬«8+ò·Aàã);)Gë E6Ôåk7îÛy_·*·N¼×ïÓ·}>åAÁ>¼ŸóÚŸ9b€Kp’I<9'_\ÿÁ=> ^A%µÞ½ñêÚeÛ-½Î©á)à•rl‘Kà¦× 2‘P+þ£û8ŽŸð–ÿå‰ÿÌi]c#$°ÔpÕaËïJ¾*®JWz(ÓÁâT£k>g8»ÝrÙ]‹—«’ôŠœ£çýmköÃøýá Oø‡áW‡5[/xÆ6sh:ôzeÊÝÚèºàòµ{{û«Wh¾ÝªZtµÓRSq½ÕÅÅÒÀÙ.¾Œý›<%ªxàÃßkpµ¶©o¥Þj7–²)I­ÄΥ⳸F£¹´‹UŽÚæ6Žx¤CÊæ¸¯‡±ßÁ‡:½®»ka¬x›SÓäŽ}2_ßYjúmÄ$47Ú~¦i:sÏ {y/-.šÚDI­ÌS"H¿SWTa·%©Ê1Œ¦’æ”`äã+&ã9¸§¢r“I9;Ïõý}È+ä/ÿÉûþÒ¿öh_±þ®oø(5}{_!x/þOßö•ÿ³BýˆõsÁAªÀúöŠ( ¿i_ù,ßðOŸû;ßë~ÛÕõí|…ûJÿÉfÿ‚|ÿÙÞøÓÿX#öÞ¯¯h¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢ŠñÿÚã_…f¿€_?hÏiþ Õ¼ðàÿÄ¿~1Òü'k§_x«Rð¯Â¿ë^:ñŸá›cUÐt‹Ï^iå¾kªkš6q¨Ém ö«§[<·€{ò§íÓû8Þëm£ñŸö„¾ ~Ïÿ´ïí!?ÄïEkðÿÀŸ ¼ ûøïàŸÃÚ?ÆÖø…}á_üøðçÅ¿4ÝÇ>ñï„ô]cá–±ðÿ⿆~-Çðÿž ›Ã÷¾ÿð·âÇÂÏŽ>о)|ø—ðÿãÃ/iÿÂ3ñáoŒ¼9ñÀž"þÄÖ5ë?Ø^.𞥫øWþÈñ‘ªèZŸö~¡qö cLÔ4˯*öÊæ€=Š( Š( Š( Š( Š( ¾døƒû.øwÅž<¿ø«à_‰¾üO×,´ý?Å~2ø3®øfÕx¿VÐôõþÌÑüK¯xûÅv#Ñ­µ¤Ò­­,­þ›¢€>Bÿ†jøÍÿIý¯ð‹ý‚?úhÿ†jøÍÿIý¯ð‹ý‚?úkëÚ(ä/øf¯ŒßôoÚ÷ÿ¿Ø#ÿ †øf¯ŒßôoÚ÷ÿ¿Ø#ÿ †¾½¢€>Bÿ†jøÍÿIý¯ð‹ý‚?úhÿ†jøÍÿIý¯ð‹ý‚?úkëÚ(ä/øf¯ŒßôoÚ÷ÿ¿Ø#ÿ †•fÏŒÊÁ¿áàŸµÓí ì“Á_°YFÁÎ×þÄ‘I±º6É#|µÑ°ÃëÊ(äO„>ø­á?ŒÚ÷ì×ñ»_Ó¾ ê­ðù¾-|"ø½§øzËÂ:‡¼¦xŠÇÂ~;ð÷Ž<1¥?ü#ö~<øo¯øƒÁm¨k^¶Ò¼5âmÇž»·ðç‡5;-RÒãëºù ÆŸò~ÿ³Wýší½ÿ«›þ ó_^ÐÈ_´¯ü–oø'Ïýï?õ‚?mêúö¾Bý¥ä³Á>ìï|iÿ¬ûoWѾ>øƒào…žÖ¼{ñ#žð?‚ü;l·Z߉¼OªZhú>’ÇoÏ{{,Q‹»© ²°´Œ½ÕýõŽ”3ÝÜC €…ñü_·/ÀÛ˜ÒãOðïíM¬ØÌ¢K]W@ý„n?èš„ ÌwZ^¹¢~ÎÚ†‘ªÙL¸h/tëÛ«IЇ†gBŸÿ ½ðgþ„¿Ú÷ÿóû{ÿô5P×´WÈ_ðÛßèKý¯ñ_?·¿ÿCUðÛßèKý¯ñ_?·¿ÿCU}{E|…ÿ ½ðgþ„¿Ú÷ÿóû{ÿô5Qÿ ½ðgþ„¿Ú÷ÿóû{ÿô5P×´WÈ_ðÛßèKý¯ñ_?·¿ÿCUðÛßèKý¯ñ_?·¿ÿCU}{E|…ÿ ½ðgþ„¿Ú÷ÿóû{ÿô5Qÿ ½ðgþ„¿Ú÷ÿóû{ÿô5P×´WÈ_ðÛßèKý¯ñ_?·¿ÿCUðÛßèKý¯ñ_?·¿ÿCU}{E|…ÿ ½ðgþ„¿Ú÷ÿóû{ÿô5Qÿ ½ðgþ„¿Ú÷ÿóû{ÿô5P×´WÈ_ðÛßèKý¯ñ_?·¿ÿCUðÛßèKý¯ñ_?·¿ÿCU}{E|…ÿ ½ðgþ„¿Ú÷ÿóû{ÿô5Qÿ ½ðgþ„¿Ú÷ÿóû{ÿô5P×´WÈ_ðÛßèKý¯ñ_?·¿ÿCUðÛßèKý¯ñ_?·¿ÿCU}{E|…ÿ ½ðgþ„¿Ú÷ÿóû{ÿô5Qÿ ½ðgþ„¿Ú÷ÿóû{ÿô5Pÿÿ“ý·¿ìпi_ýS>4¯¯kóŸãwÄm[ö¾ø{®þξ|nÓô?ŒVGÀÿ~)|]øñ_à'„|ð{^™tïŠÙX|pð‡ÃßxׯÞ(ð\ºÇ„ü£x;ú¥®±®ÛëÞ"Ö´m#I¸kÑŠ(¢Š(¢Š(¢Š(¢Š+ä/ÿÉûþÒ¿öh_±þ®oø(5}{_!x/þOßö•ÿ³BýˆõsÁA¨ëÚ(¢€>Bý¥ä³Á>ìï|iÿ¬ûoW×µòí+ÿ%›þ óÿg{ãOý`Ûz¾½ Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( ¼»âïÆ?üð¢x»ÇÚ…ü·Ú½‡†ü;¢h&±â¯xÓź¸œèÞðGƒü;e©x‡Å~(Õ…µÌ–šF§ÝN–v—Ú¥áµÒtíFþ×Ôkã߈pCª~Ý?²¶›ÝYhŸ³Ÿí™ãý* rÑØøÃHñ¿ìoà-;^ Øš…¯„~(xóB†ä/›ŸâmVÝc»dt_´çÅ«¸ÒçNý€ÿl ëÔIiy.¿ûèR\ÀÜÇ3èþ)ý²4é¬ë‚lõ­LÔa?%ÍœÿðÒ¿¿é?µïþŸ°GÿFõ}{E|…ÿ +ñ›þ‘óû^ÿáiûôoQÿ +ñ›þ‘óû^ÿáiûôoW×´PÈ_ðÒ¿¿é?µïþŸ°GÿFõðÒ¿¿é?µïþŸ°GÿFõ}{E|…ÿ +ñ›þ‘óû^ÿáiûôoQÿ +ñ›þ‘óû^ÿáiûôoW×´PÈ_ðÒ¿¿é?µïþŸ°GÿFõ|Áûnø«öý¥?bÿÚïösð/ìûNé>6øýû0|}ø)àíSÅž?ý†l|+¦ø«â§ÂŸxÃÚ‡‰¯´Û'^Õìü?g«ë¶wÍÖ—¡ë:¾ÌÖ:V£r‘YÍú¹E0Ÿ´wì•û]xËö¾ ~Èl¾iÛ?<3ð;Ç>.ýм9k þÔöÿ¶ŸüöÂÖ|-á_øOöËñ‡üû?þÓ^ ÿ‚yêº÷Å}oOøm­üEøaûG|uøÛûJÝi´ÅïÆ Ÿ‡þÿý’|gû]|;ºý¥>'ükÿ‚wü}ðÿÄßÚ£ö€Óþ:x›áßÂߌŸ±WÄ¿|0ÿ„köqýžfÂÚÅO~Ó¿kßü-?`þê?á¥~3Ò>kßü-?`þêúöŠù þWã7ý#çö½ÿÂÓöÿèÞ£þWã7ý#çö½ÿÂÓöÿèÞ¯¯h ¿á¥~3Ò>kßü-?`þê?á¥~3Ò>kßü-?`þêúöŠù þWã7ý#çö½ÿÂÓöÿèÞ£þWã7ý#çö½ÿÂÓöÿèÞ¯¯h ¿á¥~3Ò>kßü-?`þê?á¥~3Ò>kßü-?`þêúöŠù þWã7ý#çö½ÿÂÓöÿèÞ£þWã7ý#çö½ÿÂÓöÿèÞ¯¯h ¿á¥~3Ò>kßü-?`þê?á¥~3Ò>kßü-?`þêúöŠù þWã7ý#çö½ÿÂÓöÿèÞ£þWã7ý#çö½ÿÂÓöÿèÞ¯¯h ¿á¥~3Ò>kßü-?`þêí1ñz0ÒÝþÀ?µí­´HòOpñŽŠa]sÁ¾6ð¦·mg¬økÄÚC\[½Î¨ZÆ.,®ì5}6kýSÓ5ÏQ¯=ü@›EšUfÓ5kë]ZÓÊÔ,lî`Èøßã xÇã'ì*þÖlõøFn/‰¾ñvÌâmÄúìûnCªèÚ…¼Éö×–Þm½Ê,‘„»°»±ÔìÞãO¾³ºœ›Æ>ñ÷í¯û/x¯ÁºÕ—ˆ¼7¨þÉ·„vµ§;˧ß'ã×ì £^ËcpÈ‹whº†Ÿw½õ¿™e}iyc=Í”ð\J÷Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@|…à¿ù?ÚWþÍ ö!ÿÕÍÿ¯¯kä/ÿÉûþÒ¿öh_±þ®oø(5}{EPÈ_´¯ü–oø'Ïýï?õ‚?mêúö¾Bý¥ä³Á>ìï|iÿ¬ûoW!ûwÿÁCÿgŸø'¿ÃÝ7Æu=WRñŠ¥½³ø}ðËÂZj7ñ½æž¶çPšÆÚúóOÓô½Gûe¤šïˆµ{ëK ¹·µµ–µ{¦h÷àvQ_ÉnÿRxNçÄv6¾(ýŠ|C£xJKèãÔµÍãΛâOYé†@%¼±ð¾¡ð—ºn£|æDÓ§ñ~™’jq)ó‡ôŸû0þÔ¿l„zÆ¿,‹Å^ ÖešÂê9`k {à ²Ž 5O ø³E™šçEñ–.mÞâÒF–ÚîÎêÇXÒ/5-SÓ5;À è¢Š(¢Š(¯+ø»ñFËáW†`ÕN.¿âsSƒÃ¾ð½½Ìvw"ñÜ7qZµä©,zv›cae}«ëZ¤‘Lšv“ayp]\‹k;ŸÏ¯Œ?´íÏÁÙüAý¤?l¯‡³?…µzßÃzn£­j?¾|-8¿hÏŠ£'xgö~ÏàyöÅ]ŽÃâ»c?´oÅŽsÓÃ_³ço¯ÀÚÖ*7_Ú;â×Lñá¿Ùïúü ¬ÿÜø¯ÁžÖ|[ãOÚËÇþð§‡tû[Ä'ñ5ŸìÓ x{BÒíÉw©ë:Ö­ðJ×MÒôûXÁ’âòöæ h#å•T[GˆéÎÖÊóMvÿ‘g—ýLúßB}—÷áÿ“ÿòÛtWÊ á_‰­ŒþÒ?9=¼;û=¼?û:g߯À3ëZÇ6œ¶Ê³í¬ò¿/ú™ù‰Á-H}Óÿä?«zEÑ_6j^!øðrâ/ø¾(|1´1Ÿë:†…£h~=ð6š [¯]Ÿ ÙiñO†tÁ²ãÄV>ÐuLKÍjÖmj;WÓèè'†ænm¦ŠâÞâ(ç‚x$IažPIÐËd’)•ã‘‘у) ƒ]¸ld1.¤=Z©(J¥ ê ¤!S›ÙÍJ•J´jS©É5Ò«8©BtäãR„eÆÖwM;Ù«ÚëuªNêët·Of‰h¢Šë$(¢Š+ä/ÉûþÍ_öh_¶÷þ®oø'Í}{_øçÄ ?ðPÙ¯I›[Ò"ÕGì•ûfZ2MJÉ5w­|\ý…/ô{SfÓ ‘q«XøgÄ—šl>_™iáýnâÕe‡I¿{tÚ[´®ì®ívö^¯°jQEsþ,ѵøWÄÞÑüYâjú÷‡õKñׄí¼+yâ¯ê:¦sccâÏ ÙøëÃ>4ðM׈<9sø­£|'ý™âñ·ŠüU©ê?³þ£ðKÅZ¿íS§ê^)×>üEøZ_±u¿‰zg‹t/Û›öð?áOø)ÿíâ/ƒ_¼g7ü×ã>xƒá]µ­Œý¿tŸƒZß…~'Ïã;6ÖaÕ>'ÿÁ5þþÚ1ñ€õÜi?¼3û9þÀ´Váþ+|ñ¹âˇzÿÇo~γÔWüø¨ügø5ð÷âž©áÿørçÆþ‡\ŠßÁŸü+ñ‡áî·§O=Äz?Ž~üWðsE¤|Gø?ñHŠÇâÂ^h¾ ñW‰~ø›Âš·Ž¾|/ñµ×ˆ~øcØ(¢¾løÑñ£Yð¶µgðëáÝž•ãÛý)5ÝWU×RæçþðíÍÍÍ•†«ªØY\Ù^kZ¶µyeo h÷úp¸]?PÔ5 BÎÎÒ(ïü15_W½¹ý ü}®I“á‚Ú|l@ùm`Õ>kwñÅÔ…¹Õ/e í_3⬿‰©„T1˜Ê´eÉZXHa:Ul›¥)âqXe9Å5Ω{HÂW§9F¤eí3’M¸Å=W5î×{F2ví{]j¯¡ú E~eøâƯñ?þÿøVÿ¶^±ñþ÷Ž5ÿ†>ÿ„_Ù‹ÅŸð„|Kð§Ù?á)øyâÿì„Z‡ü#^9ð×Ûì?·ü%¬ý‹_ѾÛiý£amö˜wú7ÅæëûEüRêýŸúÿá5θà {eæéÝ+8å‰ÝÛ£Ìﳿ þ¯/ç§ÿ“ùsú·¥þñ¢¾ŽßâÓŸÚ3â·>žýŸGׯÀóþErø…wñKþøV¿¶–»ñþçŽüCð»âü ·?²ï‹áø™á/²ÂUðëÆ?ØuøFËûðÿÉÿùì +ääð·ÄæëûH|_è:xsöxêî„¶žø–Ýi/Œ=@ÿ‘wöwëÿ†ûV±ÏT¶Ê³=¯ñe{iÿS?0ö_߇þOÿÈSQ_2'‚>$7_ÚOã''xwövüÿä‚ÿœU´ðÄ~?i/‹îàåRã߳ܖìTçlÑÛü ´ãlaÖ«y œ$ѶkÞrÛ*Ìz}¬¯¯ýԄ饽H}Óÿä¤(¯ðçŽü[á?h¾ø«s¤êcÅMsoà/ˆºM‰Ðì|E«YÛÜ_Ïàïèow{âÿì›[SKºÓïFñE­Žªm,´+ÛÒî½ê½ .*ž.œ#8JœÝ*ÔjÅF­ª1›§Q'(·É8N3§9Ó© Æ¥9ÎŒœJ./£M]5³]×]îši4ÓM&˜QEÒ ¢Š(¯•'ý«4ßí=~ÃJø;ñƒÄÞñO‹<#.³¦?Áûm6ÿRðo‰u_ kéñk¿tm_ì?Úú5òZI¥X\OG9¶dQ_U׿§ƒ—æñÙÿªÛûBüο@ýyüëäøŸ3ÇeïeGÛ¼K«/gJ£jÕùRö°©¯ÞÊöÛåÕ$ïµF\ÜÊöµµk{ök±ôþÔnÝ>üm?öùð ?Ž5 ý§n§À§¿ü~üÏã•!ß࡟¶Î‡ñ»Tñÿ‰~;ê¿þê_ðqÄÿø$¬?³î¯ðÏà¶›à¿üÕì®ux×@ñgƒ~økâèñïÃI¼Õ»Õ!k~ø‰ðïâWíû |+ø…âx| â?‡÷^ ø“á}Gà7†Žƒ{ªéºÍ—ˆõmQÑ.õ­Áo ðÞ¡ðÀ¯ã߃²ZÛX\êÞ)×µo†¾'ӤԾϧx#^ûÓ6ÐÄqW–2¥¯Ò]öRv_WmÝmkݶ•Ý’¢¾Íöë=uK{Ûþù[ë߆?¾0xSöÂñO‰u¿ƒ¿¯cßø_àÍçÁß[j¿Å0ø“á?޾?xûáÿ€üIm/Ǧ‡_ ð•»džøig²o‡Ú¤—߬ëû@ë÷g§?õýŸG|wøð+ùÈÒ?ààÞø FøÅ7칩Ú|øåð#þ ñ³ö0ñ7ü.'ñ7ÆÓÿåðÏ‹$ý«¿hO†¿³ßÄ?Ù®Ëàž»ñŸþ Óð—þ MðÃZÐ>5¯ÅË ï„üU¢x ø[ÅÖó|,øg7†h¨Á%çhmÊŸÎ~O¤­Õ%ëÜßøŸðã_Œl/ xŸÃÿ>#ØþÇþ)ð—Æ9þ3ø ãYø¾)›ÄøÁžøiû|:ø}ñ7⹤øËáŸì¯á ÃV:‹­mþ|6ñï†þ-|HÔ5K­FþÒºýºXÝi¿–°÷üÿö§ð/„< ûA~Úÿõߎ? ZY{•G)MóYàd“÷]="ùVóIŸ»_a}óþï÷¼ïó~IE¿µ×ÂÚKâ§ÇOƒ_~ü)ø§ð÷ÃZ¿ÄI5Ú:K|²ñ œ1|øÉðÓâÂ÷³øìð[üQ“áÆ {Á2jW„·Û¼7ðsUø–xþÞïêw¸Õ<û@þ̾4Ó¿gŸˆ~ ü0ø%ñ3öb·ŽmKà£Zø_Vøëñö5ÐþÛYh~ø»¯ê/á=2/…z¶›¬\ØYÜÞi .ŒmôËø&¼šÃý€?m‰Ÿ¶?l>*þÈŸÿe¿ü&Öügc©xûÁ|=ðÏâÞ‰ã]óUµÖ¾x—ö‹ýÿe¯ˆÞ#¸ð¦¡¥j¾øƒ¤ê4øFµtÒ.¬µ {Añ.…¬ÞûŸí´›fOœr|Að›ÿW€Oëž•8‰ãðøjõþ¿ˆsÃa«ÖP,…IS§:öœ˜8ÊÍòŪS§x%kM¹4¹%8ÅAZRнçuv“jò·}ÓÕ¾–Kíš+̾ ÉøIÿdËÀ_úŠéUéµôfAEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP_!x/þOßö•ÿ³BýˆõsÁA«ëÚù Áò~ÿ´¯ýšìCÿ«›þ @^ÑEòí+ÿ%›þ óÿg{ãOý`Ûz¿‡ïø8'ÆÞ*ñOüãO‡õýJê÷Fømá?ƒ¾ð=œÜA¤xoRøSáˆ7Öv` SøÇÆþ)Ô¥“æf¸¾–2ûbDOîö•ÿ’Íÿùÿ³½ñ§þ°Gí½_‘¿ð[/ø#ߊÿmGGý¤ÿfôÒ_㿆¼5…ügà=BêÃE‡â¯‡t—¹ŸÃ—ºF³xÖze§Ž4#w>“+xŠþÚÃ[ð×öu¬z¦uá‹ ;^þ«úMÿƒrþ>|_ømñ‡ãw€<%ðsâ¿Çø³á”Þ,ÔBÿ†•øÍÿHùý¯ð´ý‚?ú7«Ï¾'|cøÝã Ýi6_±Çíáð¿Q´¹¶Ötßxâ?ü×OÖ4;Ý0¼Ñ\\[xƒöË×<+®èì†DÕ4h:ׇõ b~×`f†ÚæÛïêá~"|3ð'ůüHðÝ—‹ü'.¡c¨ßxgW{©4 f]:S=­§ˆ´ˆ®"°ñ&çm–çÃúý¾¥¡_I}§\µ¼0Å¿ƒ_µ§ÅßÚoâ燼?ñ3àψ|;¡ü2ðŸÅÓáoŽÉðå>üdÔ[ø;¤Ü\øzËáÇů~‡Ä·Ôµ-+Äø#âÄ/ »ËÔ“VðΤçÂv ÿÁi<#ã­{Ã_ðMø3á¯ÅO‰š_Áø+oìwñÃâ}¯Â…Ÿþ1x—Â? ¾X|TÔ¼iã«ï|.ðÏ‹¼as¡èÓZÇs.›¡^Í5ýþ™¥YÁsªjz}Ïïçíð÷R›Nð?ü¡¾¡yð¼jútþÑ,ãûf£ðÿÄvº\öá­:‰$Ôô›Ïøc\Óô¨6BÏC»Ò, —Q¼Óã¯ð׉ü9â«O¶øsYÓµ‹esÆÆê9e´™8–ÒúØ0¹Óï`}Ñ\ØÞÅoym2¼7E*:Ëó—SʬDéYWž½ ¹%ˆ¥  Rq“æ\ÔêS©Nq‚÷iNp^Ö2©ÙMsRŠOk§·ºùÛWëªi¦÷»Km?{o‚ßðQŸ‡ŸtWáö—ûEüý“ÿhø*Gü+ö†ñç‡þ|ý¤5ÿŽ? ¾üLÑü:ß²]ž½ðö[ø¥ð#öªð߃õ?é:×3üJð«ûN…÷Þ ‡Pøn~&xÏâÄ 3IÑ®üs¡¼Õ<#aý}D:~'úéZ/+ì?õÇó­<Òr³t(¦œåu¤ï)4Üù\Üùd¡)sZp…;ÅJÍò.ïïÓîÚÝWfÛÖçñÿâ 'þ /á ^¶ýžuÛO\ø‡ñ{þ?ðã‹n>0Þ|Tñ.áïø(6­ñÿáíÇ_ xO‰‰/ƒ¾ þÐoÀ=Câ*h_¼žÔ,uK=#V—Á’kv‘j'£ñ&‰ÿ#ñ»à¿üÔà¦:Gì]¯ÿÁ`àœº/ÂÝSâ¥ûPhµ¦—û2j¿> XþÝ:ƽkÆ.Ÿ´Ù²Ïâ”þ¹±Ô¾02|<Óï"7zs =ëÚ!í‘Àü_éšÒ„tã¹?—OÖ»iãÞŸìôSµïn¯ÙÙ½5”U5=9a:±Ióû±(ÚÞóô馯ï{÷Ó±ø›ÿûÐÿl¨e¿ø*?ÂïˆÚÿíQ£ø»Àÿ¶·íáð¿ö'ñÇÅ{]Ç?à ‡…¼>¿³¿Œ~øöŒñ‚>3èöšî©ªÞ|6ñw~!¿„´Ðíno4ïçCâ÷ìéÿ=ý¤a/Û{àg‹~ÁCþ8K¥þΟ³_Ä/^üCñ'íá ?üsðí)àSã x‹örý¥.3ø«ûwø¯ã—ÃoÙÿh? ë<7ð IñÇÄÏ xÇþO†º§„þ |&ð ¼=¡k¯|<ñ›¥ëzÅÏ÷çñ§5¥éøŸé^…,Cj 4ãWy7ÌÔ"Ó¼¿¼åwÓ™h’Ð\ºëýmþGñ÷?Œ¿à¦¶¿ôÿ…çFÿ‚’Ϫêðt7ƒ>4§ŠmxhêCß~ø?ÃÞðG‡ÿ¶u}CÄÁÑ<'á];JÐt¯ím{VÕ5½Oì}¿Ûµ}JÿRºóoo.&“¾DãnHÝ«ßÕ4úy¦µÑ®¶2–ï[í¯Èô Í:ÏVÓï´­FÞ;» JÊçN¾´™CÃsg{ –÷Vò©Èhæ†GÔðUˆ<ü¬ý‰?jo7_¼9£ÿÃ~Ómü#p|1§ø«Ã,ýŽí´Ë½:×IÒ5 (R?~Ö^ ñX`ÔÔ٭ƇÃ¥>ŸlþMÔVVŸL~Óßµ„>ø?_ð„|A§ëßõí>óFÑt]ò ù¼'5ük'Š|S-«Ë‹g¢Ç)½´´¼1ßëékecjðÉwye§û x#PðGÀ=6ø$¶Äåçˆ4¨fCÃF:V‰¡iÓ:[Ø´3}nØÄ¶—0L™IÖt'ù‡5§ .µ EH»ÇÛ׫‡: Q|²©Fz’­yQU©míZtÓP×íI4ºÙ'­º'ugÖϱsþWã7ý#çö½ÿÂÓöÿèÞ£þWã7ý#çö½ÿÂÓöÿèÞ¯¯h¯Xƒä/øi_ŒßôŸÚ÷ÿ OØ#ÿ£z¾øÑûaþÒ_³×´¯øcödý©üIÅ?C³7ÅO~Æþ*Öµ«‰%µ·Öu_€ºŸÂÏÚ³Çÿ4txeLjô߇ÿ~ØZD¢Ö?…Zlš†ºA®¿mߨ¾ÇNø×¬_~׳ž‘û5øƒJðŸíª]|}øSo§|ñV»â­CÀº†~5ßMâĶøWâ gÆÚN©àí+FñÔº£¨ø«MÔ<=gm6¯gqg>ø»ö=ñ¶·Å_Ùÿâ'À¯‰>"ý¢tøÓIøðïâG„~%k<#ð›Å€üu©xOź_ˆ«fáZÎîÝ£oµ|x×cüµøÛñûÇ¿‡ìÝ>ƒá/è¿|gñÁžøaeáï\è°x Æ^øÕu®é¾Ót¿ßx‡Ã‘ø?ÄZGŠ ²ñÖ·¢E¤Ý_>§b“þ||`{†º?Á/ü5ÿ‚Ž|'´ð?íS&ŽŸ³F‡ñãÁí7âŸÇ ?Ÿ ¦…§~Íßµýn ŸŒz~·ÿ ß‚äÓnü/½â‰âñ_„£ðö¾ç^ÒRÿ—ÙÊ-¹ÓX†î”›2Oì¸Tj´n›÷ºÓê-zëçþiém5·Ü{—~:jÿ²YY[|[³ø±§\¬o¦üø‰¨êšÿÅñbÛ¶7‚|¡Øk¾%m=XC'ÅÝYÓ&•RÞãâ/‡­Wr{އûY|Lñ.™m¬ø{öýª5ý&ñ7Ûjº/ÿ`›Ý:èq–·™ÿm›yöƒÖ;«[KȾåÕ¥´ááO“¿gÿÙÓöNÓ¼Yo¡xÃã÷ÂüS×¼O¡OðóÃ_<9ªø“]ñÆ™©ükÒµ [º“^ŸÇÞ1ñ^Ÿ¬þÍŸ´f›©é›-5-SàÆÝ:î)ßágŽbÐþïŸö“ý¾ÍðÇÀ—´ìßðòëâ§|]ð“àß„o~,|3ðµçÄ¿Š_üwÂß|5øm¢]x‚ÊçÆß¼ñ:æÛáߌ¼!á«mSÄÞñíÄ>×tûOL–¥(Næ’Qv夛”anÓ—¼ï¢åJ0ŠVŒV­‰[ü»_%ä|ï®ÉÇëÏø%¿í;®éµ/ˆ.Aá]gÃWðM¯ø(ë> ðω‡ÆþÛÿ ¼aûPxsQðnâͶÞý®¼UÿJÖ?j?†~Ð4?ˆ¿ô /Âþ/ø_¶¾øÕñßBFGÇO‹¶~4ý\ðÅ…Ÿ5xwÀ_þøÛÄóÿá,м#ã/x“Xð¿Ù¼wñámÏü$Zf©^ÞèŸgø›ðâßéÿ´ ¶ò¼wð»â/„dÛâø›OÓ=¶ðÂ߈~+ø/àM á·Ãoø&çí{áÿ xûN{kiþ#þÄ Ö5]cÄÆ¡âox»ÅÞ,ñ7í߬x·Çu¿^*³»ºF¶Ñüá-KöîÑ5ïîo]mt?|>ñW„|l/®-l^çÄÚb¯…ïVkÏuï…¼Sã |Gñ7„´¯øËÀÖ×vþ Ö5´›U_ I~ÍöÝOÃZeôÓéˆ.áw²¸ñ6›amâ´Æm*MM´Òmhò»önøÇã¯Ú‰Ÿþ%üñÀøƒÆ¾Ó¯~øæâÎã\ÑôÝ/à÷à › ˆd·]¦«Ïªê:æ—±§i:¬qjŽgÓü§‚òóê(—§¹Éã°þ‡úÖ>¤3ñ›öˆÿ²›á`>§àÁOóøÖôKéì°ïý?*üc“Ì37eøUÍ]““M¬Çûͻɮf¯Ê¤ÚŠŒmè/†õîŸþ‘ËoÏSð3þ [ãkßÙÇâWügáGƃ¿µWƒ|Qñçþ û^|OøQªÃþÖž"øk⿆¿uð'Ä?ø\þø-®üм ®j:©sÿ o‰|u£èfkÿ ^±¨iþžÛX›ã¿ÙÊûþ ã¦xÓÃÚ¯‰4oÛk_øÙ¦|ÿ‚–j·ï‡¼S©x·Dø®xÞÒ/\~Àº7ì¬|WÑ,Ôµ(ü5g¥j¿<7âMÃL–òçâÖ‰«ˆä±¸þ²"^žçôä׿·íSÿUø3û"üsñ‡À|ý¤>'xÃáïìªþÜÞ9Õ>ø[á–³á ~Ïžñö³ðÿž!Ôï•ãïêZµ¢ßi:ß‹üQ¥xCÅ>2Ò¯í|AâûdÖ5Kþÿ‚äøB_Ûã_Â?øqkß¿à”?cü"ð•Çü&^!×ÿ঳—Œ¾7èþ2øÛs㿉:>˜žÓ$ð¼&Î÷Á~°ñ>…£ßCiwá?jK{eôýüƒöiÖ±wâÙö‰ñwìxš½—…m>x}¼Q¨èè~'?à£ÚÇ‚>xgÁßà¨^1øUã?ø+Ïì ¡øªöãà‡íû-xÓÀß³>±ðÛâíW5¿Š~)þÖ¿´—í¥û<&©àY~"ø‹ö‚Õ¼ð¿Á¿4¹ï¾ê^!ðþ“qáÿ¯ãÿƒÿfEÓ?á áøïÿ à?aøkoøVCöøÿ ·þR?`C©ÂÑÿ†Éÿ„þGÏôßøTßðªäQÿŠÿþ×öÏüYúúkãWüŸÂ:·Ä ~|@ðWí‹á/ÚËö5ýŒô…´‡‡ü9§èÐüDý¸dŸ_ø9ãN†_|Ii¬x"ïávƒã/6‡Œ<1ã$›@·ðω¬|{¨ÉycÛˆæwÃB<Õ=í’^Êæm](>UxrÂ>ÑÇ^ehÓMz+~ +yöwoä|CñÛá×ü¿ÂŸðV¯þÉÿõïÛŸPýˆ>4|rý†h™~;Z|Aý¡¼[ðïàïÁÿÙÓáoÅ}ö…ýž|AñÓP×5x<:ß´Š<á-oÅžñŒ Ôü_ªêZTÚ½®«ã>mCæÏ„Ú7üeüûKë_þ)Á@àý¦×öSÿ‚Ÿ§Å/…žýœ?h6ø1qñøâ4_²­×ÁÚÆµ„ÿ cñ¢x¦o‡š¯ìÙü»öa´ñ«}ŸXðoÅëy5gñ.ÞÏÿÁd?f/ˆßµïŠcoxã߉=èþŠþÁñgàgðþ§¨øJ-WV×u8ñç€`ßÙM~.k_µ¿~3ýŸþøÿã‹þѾ?ø§ãÏŠZoÆ_|!ð+üGÑ5µø»­k'ðLº_‰­oí¯¼ z.ŸáýtkÍ¢Ûkº†·syú e‡ Àÿ?N§Z£ ôüÿ>ŸãZöëŸóþzŽkziÉó>­ËåÑ—“3žöécåŸÛgu§ìó¯k¶®öÚ¿†¼ið›YÐuXÇu¥jƒâ§ƒ´Å¿³™pðÜ­–¥{n²¡ ©<€M}Að÷^¹ñW€|â‹ÕD¼ñ'„<5¯]¤`,is¬h¶ZŒêŠ8TYn(ò‡í÷§Ýj²oÄ­>ÇZÔü9ww«ü)··×tX´iõ]*Y>1|?Åí„>!ÒuíK˜OÍêš6¥fOúÛIGÎü"ýœ¾0Ýü'øauíóûYé\ü<ðUÄ:u—ƒaY,´ø¦ðÖ™$vV’j?±f¡¨=­¢0‚ÝïïïoZ(Ñ®®îg2LúaU³ f«ÞÁåòqWÝÖÌ—3Ò×qŒc{·h$ôQþÇ?ÊžŸ×sôŠù þ«ã7ý$ö½ÿÂ/öÿè!£þ«ã7ý$ö½ÿÂ/öÿè!¯PƒëÚ+ä/øf¯ŒßôoÚ÷ÿ¿Ø#ÿ †øf¯ŒßôoÚ÷ÿ¿Ø#ÿ †€=ò/Šßåøuð‰¼[¥ZüLµÑ!ñ,~ Ôd—K×uOË&½áÛ]F+_øJ4{Y-u}KÃoªÙè—ÒG§ëXÞÉ»|5àµÿ‘ìÿÕmý¡åñÛâ6?^kÆÿi_ÙGö…øÓt> øoö‹ýª¼eq`Úoˆ[ã?ÅoþÃÞøoð£W•é¾ ð³áOØÓGø·âŠL.÷Zj|)Õ| ˜LÖzŸÆ?j¯ µÇWû9ø;^øðÓþxûÄ_½ñkãWŒ¼ eñ÷âªK~1è|cñ_øEáoˆúåœßÙ«âï øIÖ´" -#D»Ó´Í>ÆÒß•ð?üïþ Áà+ŸKáŸÙ‡ÃðYø÷à׌¿g¿øwUñ¿Å_x-¾ x÷â´Ÿ|KàÀž'ñÞ±àÏ i|Y–Oé3xKAÑ5/ jîá¾Ñ­¶£Ð§OóÉëùÒµí“'8à!þpG½|ý,F%ïªj£gïj£Í'²nVÝ›òDzû——ù/¹è?ðJߨGD¶ð”6ÿouCÁ_´?Â/Ú¯EñW‰þ.üqñ—Ä Ÿt+ï |ñŠ>$ø·âV·ãÿé¿ |9©_è^ð'¼Iâ‡z^“rö1xLÛyþÈŸðLŸ‡ßþ |ñ§„´â—íû}xãþ 5ðcÀ·Ÿ~/·ˆ¾ þÜ ¼wãßünÐìmüm;?Â>³:ž½àëÓkðFÅï4:_W\Ñl¯ñÿà¯?²§Ä/ÚïöPѾ|5ð¯Œ|Õ2ü3ø]ÿ×ÄŸ„ß²g„?aŸ‡þ$ÿ„Ûâ.³ýû,xÄ:wм)ð»û_ñv« ê¿ÙZö•§ßÂm­éZ—Ä[ãoö]KÅ×–rÏo'ñMñGþÿDøÿ¤þÖIãoÙOáw…üUñö4´Ó¼1k£xóàøu«~Ö_ à Þñ—„|Eàý~oøâ÷‰¾&øÓö/´ñÞ’?iOÚ>öãâ¾»uñ7âÃÄ?Ø0~–~ÒŸðKŸÚÏãgí¯|\Ñ>|{ðŸÁßø ö'пco|øáû~ÏWŸðO?à”:f‘ñ#á÷ˆÐSöµñF‡ñÊI¾üA:׆n4¹<+êFŒl“Çs·ÍÍ列R¥Ë½£½Ü§«|­Aì÷Êïù;tüvéeÓô?z®¿à”ß°N¥.µy/À©¬õö¢ø¡ûg_x›Dø¯ñ¯Ã¾1ƒöøÙ¤éÚůèž4Ð>#é¾,ðÖñBÒtíÅ¿ ü9¬é_ µ2Ò+ ¿½®è›áßü—þ ¡ðÏBO xSö[Ї†—öiñoì|tøÿâ÷Žôi¿goüs×?i_xçLñßÄYÝÍuñÃÄz·Ä \Ã7ôFK];Ãþ)Òô-/KÒ¬¿Ká^ŸŸãÛükRéùRkjR¨Ò\ó¶ŠÜÒzõ{ô»ûÙNÉ6Ò·§§üçÙ“ö6ý?d{oCðÀWÞÔ~&jº³ñž)ñïÄ‹_|qyá]۾ž/ø¿ÇŸõí7³ƒAð†‹©øžçGð®²iþ°Ó­§ž)3¿oVÓ@ý“þ$ë7±jsÙ隿««ˆt]Yñ«$Q|bð:Øh>°Õ5ÍVäÿË;-/O¼»”ü°ÀçŠúâÙ?ˆÿŸOñóí¸ý˜¼wÿcÂaÿ™áù?ýjÓ¯—fWì8¦õÕÅaæ’»Nϳ³Ó¾Ætÿ‰ñÇÿJGð‹öÓø=§ü'øaa?ƒk9'²øyà«I¤²ý¿n­NÉå¶ðÖ™ i¨éß³•ÞŸ¨Z³¡k{Û «›+¸ŠOkq4G#zü6÷ÁŸúÿkßüWÏíïÿÐÕ^áðSþH×ÂOû&^ÿÔWJ¯M¯xƒä/ømïƒ?ô%þ׿ø¯ŸÛßÿ¡ªømïƒ?ô%þ׿ø¯ŸÛßÿ¡ª¾½¢€>Bÿ†Þø3ÿB_í{ÿŠùý½ÿú¨ÿ†Þø3ÿB_í{ÿŠùý½ÿú«ëÚ(ä/ømïƒ?ô%þ׿ø¯ŸÛßÿ¡ªømïƒ?ô%þ׿ø¯ŸÛßÿ¡ª¾½¢€>Bÿ†Þø3ÿB_í{ÿŠùý½ÿú¨ÿ†Þø3ÿB_í{ÿŠùý½ÿú«ëÚ(ä/ømïƒ?ô%þ׿ø¯ŸÛßÿ¡ªømïƒ?ô%þ׿ø¯ŸÛßÿ¡ª¾½¢€>Bÿ†Þø3ÿB_í{ÿŠùý½ÿú¨ÿ†Þø3ÿB_í{ÿŠùý½ÿú«ëÚçü'âm;ÆžðÏŒt{oYé,ðþâm.ÏÅžñW€¼Uk§kºu¶©cmâoøëFðç¼â-®¢‹YðŸŒ|=¡x«ÃšŠÜèþ!Ñ´½^ÎòÆÊ_Š?ðR[Ùó\Ô]˧kþ½1²Émuñ^_]@Åd—ÀÚœ õÕ‡ìýðî?‰÷ŸüC¯ã߈bêâO ë^<Õ$ñ¯Ã+ ˜¬ºG¯Í>ðÛï‡UÕü=¤Úø¯Ä‘0Å^"× Q_ÆoÙ£á—ÆÛ?]×íõŸx÷B¶’×Ã_ü¬]øgÇ.þk[[êÖMåßiÒIÍÆ“«ÛjuÂ4‘½°19UI%É+$ýèß‘ÍvUn±I½¹£¸Ÿõÿý?4|£cû=øwW¾_xÅ_¼Añ;–ƒâí׎õÍ?â•#í‡otIô½Ã:#> þÐtKß"¬zž…‘_²ñÇψÿ³³ñ§ZðçÅ_†Bt³¶ñl~ð'ÆkWlùv×»¹Ò<ñJø ÞãÀóxKÄ“¢yzoõÛÇ%þkø«©~Ù¿>!x?à„õ/üW½ñÿÃߊ<9ñVÜGñCðǯ| ðž½m­øÊê k ›Rø×áfÒõ­8.•¥–£>©á‹¬3§KðÃþ ûâ¯k±|Cý¡|a­j~"¸@d“SÕ!ñ »³HÖjžðV›—;4 éíf‘±‹ìÖS.á³nÞÆœ¨K¬ÛŠ[ë͹ªÒëyZééQJáè­ýuÞïú¹ú«áýnËĺ‰â=5/£Ó¼A¤iºÝ‚jšf¡¢êieªÙÃjšŽ«[YêºMòÁ]“b’Dh‰…Mu+Ù]ÝÛWk]÷¶¶ô¸ÂŠ(¦EPEPEPEPEP_!x/þOßö•ÿ³BýˆõsÁA«ëÚù Áò~ÿ´¯ýšìCÿ«›þ @^ÑEòí+ÿ%›þ óÿg{ãOý`Ûz¾½¯¿i_ù,ßðOŸû;ßë~ÛÕõíQEQEQEóÅßÙàGÆVOx¯Âöž&ŸoÚüG¡Ã¦Úê—ûUQZýo´íKO½˜"$k¸°’ôƉ¸1G/Ó´VU¨PÄCÙâ(Ò¯ ©rV§ °æ[>Y©+«»;] 6Ói÷M§øŸðíÙÇþ¦ßü±?ùƒ£þ£û8ÿÔÛÿ–'ÿ0uúErdåô-ËÿðÿÊü—ÜW´ŸóÏÿçä¾ãóãþ£û8ÿÔÛÿ–'ÿ0tôgú›òÄÿæ¿Aè£û'+ÿ¡n_ÿ„xþWä¾àö“þyÿàOüü—Ü~|ôgú›òÄÿæøvìãÿSoþXŸüÁ×è=dåô-ËÿðÿÊü—ÜÒÏ?ü ÿŸ’ûÏøvìãÿSoþXŸüÁÑÿÑýœêmÿËÿ˜:ý¢ìœ¯þ…¹þáÿù_’ûƒÚOùçÿ?óò_qùñÿÑýœêmÿËÿ˜:?áÚ?³ýM¿ùbó_ ôQý“•ÿз/ÿÂ6ðGì%ðÁ„ðéºçˆ#¶‘f‡Jñæ”ta4dy´íDÐâ½U n·¾76’®Rkyí¯±£Ž8cH¢DŠ(‘cŽ8ÕR8ãE ˆˆ *"( ª *¨>Šî„!N§N§%BP„b¶QŒRQK¢I"oVîû°¢¾`ý·~5ø«öký‹ÿk¿Ú3Àº‡õo|ý˜>>ükðv—âË]Fûº—оü)ñg޼=§øšÇGÕt^óÃ÷š¾…go¬Úézæ¨ÜiÒ\Ãcªé×/ä?„?¶—ü»ö§øA„üEmâÏÙÿÅþ øcðÿVý¾|®þÏzïÄ[ÿÙÇâ_ÂÏ‹ðJø./Å/„Ÿ³ç58üyïíEðÿáÿÄߨcÂ[öѧø#áÏ–º÷<]áŸÙçà'ˆ<¦ê½¿á?ø#§í]¤üPøâKâÁñáo€Þøðò[øñûUxÃNñ´ࢿðL¯ÚÓXøÅðãàŒln¿fïØ§Ãÿ¾~Æßôþò‡Ã/üý¼UiðÇÀZ7ƯŒ? uŸÙ§êÚƒö)ø§/‹~8x÷ÀZÿÄü`ý§ÿoÿ¨ñ÷ìãªøÃ?~þËaßÙ;ö(ý³áb|S‡Â–v_ á^|2ø ñoö¹øGý¥âø¾jµìñû jòÂÍøÁaà_ÙÿÅñÇöùø§íÙªþÁ¿³ÿˆ?gø<[«ÿÃ%ø*Ëâ'Œ<=â?‹Ÿð¢>)üeø;ÿ=ý§¼eñ‹á_‚¾0|,Ôü[ý¥ðKöøSaðûÀxûà׈ü9ÿ ú×ã>µâ?xJ|?×¾ø ÿ>ý¯ÿiߟ ¼3ð¯Yý<âßÚâ…ð’×â>˜hÚóöqÒü à?‹¿ðqéþ%ø[áý+öŽýœ¼%ñþ¿ Á6>Ûiÿ|)£|!Ö>*Zøã]ñgŒü?©xKCø)ðÇáì÷íSû2Oão€_¾üø[ð|[|ñ„n~|7›âgÆ_ØòxWÃ^ ñÃ7FøûL~Êw?¿dxoÂÞ$:<&ømð÷Çw‹þ Gñ öaÕ¼'¡ø'ãf³ãþ`|Mÿ‚K|sø“8ÕñÕÏ<¤|=ñ¦±ðǺ¯ ÚV>0“ãÞ—¨ü ïôø'û*üeðŸÃÈ>øOö±¼ý·ÿkø(Á‰Ú)þÜ¿³ÿü·VÕi?i{¯Œ>ðWü"^ ý¾|3­x‹áç‹?gÿŒ>#ø—uðsÅÒÜücðïÆ>ÿáëÿìWû4Ã/ü:ø±áÝCIøiâß‹µÿí©ûKø»]ð‡Ùÿá0ÿ†ˆý©þ,|Røu«x×SŸAðþ§âˆø%â…¿¼G©Á©eÂgá ^Öü%áŸê}_Í‚?à²ß´þ§ã À^'ømð}.j_ƒÿ>7~É^-¶Ñ|?¦xWÁŸ ÿiÏÛköDý~|UÖ|-¢þØh?ÚGàþ£'í“§|Qðψþ3|ÿ‚NüBø›§|ñgá7Ã|Dñ½‡ì¥èÕ¾-þОÿ‚†êß¾"|`ý¡¼Sð+þ ?áßÙ³Á_ ¿`ÿÚÃö‡øðk\ø™®þËÿðN/ƒv>'ñÄoºÇü4ìñðá/ÅüTø§ûOüðßÄ‹eÉü}ûPë_üû_üVø#ðûâ\€Ðõçÿ ü;ã¿|,øiá?Š_áp|Mð¿Ãÿøwâ/Å¿øDt‡ÿð´|w¢xsMÓ|]ñþ/ÏsáÿÿÂmâ mCÄßðˆèW7?†ÿ´ÿ±´Éå²²‚Fô øÄö²è¿¾1é×êb›Åz‡„>"h…U½Ðßáï„>\µ»–Y4ýwÀWË|ˆ|ËD¿ÓtD¾´y¶"^žÃõ?ä×Òÿþx{⎛e£s¨è~!Ф¹ºð§Œt ƒÄ½»Ž8îšÑ® º°¿Óµá†-_BÕìïômZ(`û]›\ZÙ\Úü+â/†_¶ß†/[Oðì? þ"i‘1ÚùÑeÒ5 ˜†I©Ø^üNðÅ­µã™aÓ­¦´FÊÇ4ŠÍóÌéc±U0˜Yc¨b±Uñq•*ØZS¦ñ5§ˆ©N¬1XŒ:¼*Ôœa*n¢•>G'óEvB¬cÍ%Æ6jM5¢šqRÝ+»Û[ÚúËÿ@?¯ô¯Æ_Ú¯þ IâÿÚëþ âïÚÅ_µï†?³oÄø&~©û xãIø?â›=ãoŠu/ü}Ö~ x»Ãú¥§Œ~øëÀgà÷‹¾kÚ‡‡u­BÓXµñÔ÷٥Э´g´·ñ£+á¿Ûíq‡_ 8Ï[{ÿ÷]…Y]öÿ\cáÇž=m¬ÿùüQ†Àg¸i9C(Å)J.-ýc)vNÏKæ6ºqNý: ΓÑÔ_uOþCÍþ>GÀÚ÷ü·ö;Õ|Sã?xcÅ¿¾kúÿÄ_Ø3â§€.¼ âÏ>øÓþ Çð£Xø1û8j_ ¬|að»ÅösAmàmsQ³ñŽ›ñR‰ú~½y$WvVÚ/”‘ø‹ÿT×þøáßÄ¿Øïâ6¿âŠúíûU~Ùž(øƒñ¿ã]·Âß‹|Hý­>øá×Ä­áÿÅŸ~Ëü£ü2ñ.‡àÉ`ñ¿‚|Sû8ø«ÆÚüšŽŸ¨xKã?Ã{Ý C¬þµ®›ÿ^Ÿ þôÇ6¶Ÿüþje´ÿ‚‚/O†ß ?K_Ëb½ tóèÛ›,ÆÉ(ò¸Ë•4ãÈ©ò¿øR»Š‚J×Ö×Ý»äÝ6ôœRÝi?_äï©øŸðCþ ¡øì—àŸß´oƯŒ¿ÄAûØïâ>½ðK\ðg†¼þ—öó¸ÿ‚„ÿjø*ÏÇŸ <[®ÇâkŠïgà£âvYt­_áí­Ò¯ô/ßëé¿iþÖ?ðGOüYø‡ñ÷ö¤ø/ã­oHý¬~+~Ò_°'íiá¦ø£©é·¼)ñgþ ë¦ÞøGᕎ“¦øgÀ­ã-AñÇÃßxÇ@ñÅøöêgY³×t]2ÞÃOŸÃš‡Ü¨¿ðPt9 ~­¥¿ôøú?É« ?ü-z|4ø?×<ÙÁÿÏúº¢ó®niå˜ÖÛ”šöùM¯9ÂrVYŽÎP×h¥ê¿wüñû§Ómy/§sçÏÿÁ fÿеƒ?kˆ?øýã|;øï ~Ò>ðŠ®½mk~¿ •m ‚?jÿ‚pþÇZçìoðcâ&‹ãÝ{@ñGÆÚö“øñûY|z×ü"o›Â7¿>>]Úù‰ÕÅ­Ä9|.¹SÕ ˜¸ï”ãú/âå[iÿS/êÆ|©ÿËÈkå?þCú³ò¿Gû~x¿JÑþAà™¦‰µßˆ~0ð…¶‘b$Qrl¼âÇ:æ©ää;YXÛè6Ö€cŠïXÓabæ ßUüŠH>ü)‚dh¦‡á·b–7^9#ð¾–ŽŽ§ÈÊUäE~}xö<ø½ñ#â_¿iKq·ŠæÆçPÒïõ;Í:Öo´ÇáÝ&ÏßñLøSÃSL^KÈ´©MåÌ4’Ä׳ÿiEú—qÃpÃEH±Åj©q¢…HÑDEUTU®Ü*ÜõñXŠjZê•8PRŒåG CÚ:Q«(9S•yT­^¥Og)S‚œ)Fu?k5&¬¢Ò»½š¼¥kÚúÛD•Òn×i^ÉôQEz…Q@~Bxßã·ÃÙ·Kø‘­|s»ñwÃ}Nø£ñ÷ÄÒëz÷ÉçD»Ð.¾0xË_³Õ4]bÃÁ׺oˆmnôwF»¶}ëRkí]>U…ÿ^ëò£þ µðvÿâÿìéñßQñF‰ Mð§à¯ì“ûOüS·žî;ÝsÄ¿bøIâë/é‘E$\é~ð´¹ø‡¨7™Ö|{kð¾þÊEÿ„/QŽãÅÍòhæ²ÂÉâ†uSJ’¨ªB³¢çâSä—îcË?y+»ÂZ[Ju99´½íÖÖjþNûí§©KNý¬¾êvš…‹µ‹Ëëh/l¯m~üM¸µ»´»‰g·º·ž±«|3Õmî4·ºKO‡):µ‚îÕþÄŸ5_øjkÏ ê7ûÿ Sà×ýO†_øAxWÿ•UäG…jÇlÆ“¾¸ yvÇ.ß×]}ºþGÿö¾¿Ò×áXÿkO€«Œø«ÄþéÅ^§éà“ÐUøÿk¿€ ÷¼YâÐÉ/ø®xOw¯¶ÿáJ|ÿ¢IðËÿ/ ÿòª²µßƒÿ ¬4=fûDøðË_Ö¬´­FïHЄ|¥kT¶´šk $êwzCZéßÚWiŸÛîU­ìüï´L¦8Øãø˜í˜PÚÚà*y_þf¿–°êÅ»¸?ü ys×úZüA¦þß_²mçŒ5ZüP½¸ñ§‡ô}/Äφâøkñaõ=;GÖ¦º·Óo§„x;‰läÜŠÍ5²Ie5ÌpéiÒ]w‘þÙ_³²õñ—ˆoù%ŸÿÇ_ÿ]| ªþȾýžÿh_ üHñf›ð”|eø—à/„úæ›âiš†þüUøÅá‰uŸÚSà jZÍ—ö…iã?…?ü§~Ïú¯4W°'À?ê‚=rÛᆲ‰ûd> ü ð—á‰ð…H ò#JÁrë] %ÅE®Lv$£~lY6ÒWi¬Â6N\Í+7Òr“\Í{Hõ‹ÿÀ’ÿÛò¼¶‡ìâ¿{Æž ÿä•|]?Nžüjô¶·ìÚ½|mâÛþIOÅó×’äBü«é¿øRŸ¿è’|2ÿ ¿üª£þ§Á¯ú$Ÿ ¿ð‚ð¯ÿ*«x司mŽÁ¿\º·—ýLýW§¦£œZ³Œ¾S^_ÜÕ»kóÄ_¶ÿì̀㟠râÓ|büéý+Â?j_Ú›àÄÿÞ(ð7üQ®k(Ö5¿‡éÚ|ß>&èÑMñ7ÁÚö©,šž»àí3I´ŽÓIÓ/¯ï/­ÕÒeçx¢¿¿áJ|ÿ¢IðËÿ/ ÿòªøRŸ¿è’|2ÿ ¿üªª©€ÇÖ£WSƒöUéNNL¾´j{:pŸ$¥™N1Ÿ,¥Ë)BiI¦ã$šr¥µ%]4Õæšºiê¹·Íz÷> ÉøIÿdËÀ_úŠéUéµVÊÊËL²´Ó´ëK]?OÓímì¬,,­â´²²²´‰ µ´´µ#‚ÚÖÚã†ÞÞÒ(bDŽ4TP¢Õ{$Q@Q@Q@Q@Q@4?²×üÿãÂÿÁ85þÇÞ »ø­ðcö`ý†~|Bø·¬üOýš¼Kð÷á“ð_àÃï|R°ðŸÇÄ:'íýû8|`ðï¼#âÏ\þÎ_³<¿´wüGö¦øg­ø›Ãß¼7áþÜ¿´÷Ç/Òõxí;ñÓþ×áßÄ[o Âkâ OâÁ?ƒ¾ð´úßü"ú>³ñOöøÙð÷öxøIkâïǤx’÷Â×âoÅ ÏñÆ7„|uâ? ø/x‹Ã?~ xƒLÓ|®€~0ÁfaÿÚ«ö ñV»ãÙçៈ$ñ‡Œ?k¿üXñ·ìçâ[MðR/Ù›â¯ü›Ãß¾7øïã/†µö·ý£~ |;øgû0üpøÐxoþ c£ÞÂsâÙ{áþƒâÚöÿÿ‚¯Áû}x÷Á³øÀŸþ5Á>ÿkøyôŸ <#ñcâßÃAø›ñáÿŒ¯~'þÇZΛðêßÅš¿‰üâ=ᧉ¯<;á/|:—¾€°ÿ‚…~Ôþ-øígû%|=ý’¿gýOöšð—ü4'ü/M7Æ_¶gÄ_ | ð—ü)/~ÀôøTÿ4O؃Ç_>,Âeðÿþ 'ðv}Kþ€ß¿áñ„¾%øj×þÝL𷌼[Îh_ðW{ïˆþð?ǃŸ²æ»âÏÙ“Åàž8Ýø~/k¼ûD|<ñퟆþ/iYü>ÿ‚¶|S½øEû;üEøÁû)ü?ðGˆ?mŸÙÿà÷ÇOØóÂß ¿iÏüUÑõ¿ø]?c_ÙãÃý¥|WâÙ‹àÝïÀÏì·ïìß>½­ü.ðí9öY|nñŸ¤j^ ð€üñ€ÏþÿÁ?4Ú›âŸì§ûT~ÙŸ²gÃÿŠ–_gÿÛKö•ý£<ûKü.ð'Š¿áGþÑßµˆÿà›þÍuo„ŸôÆšÛâìßû"þÏ2þÊÿ|;ðÃÁW^(ÿ…¯xÿânƒà¿‰¿õí7Ä\ÿìwû"þÐþ Öþkß¶ìoâÚ'ö±¸ðÿì1â=öèñgÇ„“x«öJðÿÂïÙöVøuûGüñ7í Å gö¾jß¾þÕßuŸ„ß|ñGö_ý¨µÚRçÂÿ¾*é¾ý¢i­wÀß@x×ö¬øßñcáïí©xÛÄ?`_öHý§üû)øÇÁ³Pýš¿hoŒ¿´gÇߌŸ ÿcWö}ðÿÂ/‰ß´_Ã/ü ð—Áÿ‹_kH<áñGá€Bý¥ä³Á>ìï|iÿ¬ûoW×µòí+ÿ%›þ óÿg{ãOý`Ûz¾½ Š( Š( Š( Š( Š( Š( Š( Š( Š( Š( ý¡~ xWö”øñÃösñÖ¡â 'Á?¾üKø)ãSÂwZuŠ´ß üTð^µà_ê¾Ö4­{H³ñž‘®Þ\h×Z¦‡¬éÖúŒvÓ_iZ²Kg1áŸÙëà‚õcÁß¾øOW¼øÁâÏÚóTðÏÃOè:×ÇßøWYð/޾8Üßiz-­ÌÿÝ~ο¼ðÿöaøe«ÿ¦x§ÃÿÿfسWðçŒü à?üBøáO…¾)øqûCþÌþßü?Ð~0|NñwÆmOƒáͦ±áÿŠz¾¯âÿëºEïŠ|h¾%öƒ±çÀ/‚zw†%ÑüáÿüDðïˆx‹ÅŸ³Oìÿâ|øâï‹ 5ß|øu­ë ¾)ü@ñܼ{ñ/áÖ§©xræ÷Á?ø£âoÁÿøK¿áR|Eñƒ|9­øïáwü, ø÷þ׋µ-6çÄ ÿ„ÛÃöÖÚ‹¿áÔ4ÏøI4{x4Ígí¶QGúãÿ?g¯€_³_…uþξüðN­â ¯jžø)ðÓÁ ü+©xªûNÒ´{ïjð.‹ éž ¼Ò4-KºÖn,äÔn4íJ±šåí´ë8¡ÀÓ?dïÙcDñßÂߊZ7ìÓû?é~|?Óþüø‹¦|øua㿃ÿ 4]ðî•ðÓáo‹­|9ˆ>ü?Ó,Oxá§‚ôo ¯Š¾ éß4ƒ>&_iÚ-¶¾ øG¤|øE¥ü1ÖE˜Ô|§|+øqcá[&ÛÀþ‹Kö (¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¯¿à ßòa¶÷ýší+ÿªgÆ•õí|}ÿ –4ýƒmÕfÕ?ez÷ZñÂÿh:—l§™oumgQ°Ó,-×2\ÞÝÛÛÆI°h¢Š(¢ŠÀñ?…¿`’I?±ì†I$’f¿ƒ$’y$“༒O$žµõåòü;çöÿ£!ý¿ñ¾ ÿóGü;çöÿ£!ý¿ñ¾ ÿó_^Ñ@Œ·/ìKû|ø{kûjx{öFý˜¡ð·ìqáÿŠß¿h…šÀ?…Péß¿f 7ៈ5o‹þ Ò´[¿ ]x&Œ ¹ð烾7|ñ^±áëj¾*øUuû:EñSà—ŸÚSãOĈ>4þË÷¿³†±ðSà_¿ø'§ì…û@þÕ¿µWü5ÇígñW\Ó?e?ÙËãÏ> øáGŽÿgÏøsàÂ߆ß¿iïø&—„Ÿáÿà ~Ðß~iÿü)ñáæ±ñXøA®ülñŸì•¨|Sý¦þ,øçá¯ô·âÏ øWǾñ7|uáŸøÓÁ>4ðþ³á?ø;Åš6â? ø³Â¾#Ó®tøgÄÞÖ-¯4{Ãúî‘yy¥ë:6©gu§jšuÕÍõ´öÓËyÿÆ¿Ùëàí)á]?À¿´gÀïƒÿ¼¤ø‚×Åš_ƒ¾5ü4ð_ÅO é¾*±Óµ]ÇÄÚ‡¼u¢ëÚEŸˆ,ôw\Òíu›{8õ};YÕla¹KmFò)€?< §|'øƒñ3ö}ñd¿°'üÀ?³wÄŸþËz?¿³>~Ï?´_…t/ÿ´wÃ߆^>Ÿà‡ˆmO‚þ>m#àçÆ m#âæ¬þÏšˆb/ˆŸ³Wío§Gû<økÁß¶Ï€>(þÝß ¼ðëÿø(§ÁŸØËö8ý•uÿþý‡¿àœGWð÷ÆÙSÁ7—¾ü*øiðkLð¯Æ¯Ú»àŸÁkŸ¾&iu‹Ÿ†~Ð<ñÄ:ÜßeðçŒ4ï‡óéöþ/Ö< ã#F¾ð†µúy¬þÏ_¼Gñ—´gˆ~|×h?x~çÂ~øí¬ü4ð^©ñ—Á~¼ƒÄÖ·žðŸÄûíxsÃ÷VÞ4ñ½Î£ë–ztðx³ÄÐËlÑëÚ¢Ýyü1OÂÏøRÿð¢ÿ·þ Â%ÿ ÿ ­ý£ý«áÏøHÿáiÿÃ}ÃÅÿ°>×ÿ§ögü+ÿø]¿ñKedÂGÿ ³þ$Ÿð•ÿÂ[ÿÅ~Aü!²ý–ôÚÇÇŸ²÷í3û Á0¬éŸôïü>ðþ½ª|8Ò<ÿþ‰û?|/ýª¼ûN|zý¥ÿfŸØ ÅŸð²¾ ~É~?ðW€þþÅÞðw‡>Ââÿ‚`~Â_´'‰<àöñÿÄ/Œ¾GÃø?árè0ÚøsI´ðïŸñNÃãÆ ûÛ¯ø]–~ø_ûyã„°ïÀO„_<3âπ߳ÿþ |$ý > OðÁ:gÀ \xá?íñoãf•ðÛàÇ‹¾øÃ ¾Ó>ü@Õ~6üh¶¶Óþ"øgFÑ.¼1¬xã]ñf³â-L¸ñ¹ |:øAû8üñÜú7Áï€ßþx·âWÃÿÁ®xƒáGÀ‹_húÿ?g Â? ~xGÇ?üàË Zÿµð—ŒtO üøuã/ZkøÃÅVß ü;uá/xå¼8øàßé7žø±âÍcþ ÝÿÂñµî™ðÿMý©~ü$øWûøÅ1øÕû'x'âŸÃMKâ~·ûø÷á¿Ä¾ÿ‚ŠÂÍø âoøgàoÄ-¿aO‰>øí/죬þÒ±§Ãÿ†_¶ÇÃøü ÐiŸ <1ûCü}øoàOÙÃöMÿ‚Qü%ø7ñËÃÿ¶ÿÆÿ~0Á9tÿZߎþ~É~4ý…~øKÆPi>ø÷ðÂ×¾ý ~+|uøëñÃà¿ÅøÃž ø‹û#ß~Ì^-Ó4M+Äž-ñƒ÷sDý˜~ ü?ÿ…é¬ü øwðÿönø›ûEkê¾5ü øWð‹ÂüQã½Gþ›­?â—Šu]Oáÿˆ-øgâ/ˆõ^çÄ»e­ëº~§¿ð¿àÁ¯ƒ^ø7àï‡?|? é³çÁû/€?o.aŸÄ^*ðÁ«=;À:[ü=Ѽuâ{gÆÏáýZÛá_ÃY|M ÿˆo'ñn£à? ë'¸Õõ}N¾·üý“¢ø ûbxß´‡¿bø%À_ÙóIñìQà|øÉû7ø2ÿâ‹üUûcþËÿ±Ïíg{àÚ¶ÄxSÁ>ñ‡®mÏ|)ø_ðoXý“üq¨ü~ø™ðÂËÂñ|PøS'í ¥ß|ýžÿ‡|þÁôd?²þ#WÁŸþbë¾Ó?dïÙcDñßÂߊZ7ìÓû?é~|?Óþüø‹¦|øua㿃ÿ 4]ðî•ðÓáo‹­|9ˆ>ü?ÓéÞ Òü/ûü"Õ-¼áÿøgB—âNŸ«üeÔoôïˆ~*Ðüc¯ÜøÛXø¿«øçWø—â ?Wðõ…Ÿ¼YñûÅ^*øaá[sÀ¿ uŸ x'Äþ%ðö­ò¹ÿ)ýŸu/þñž‰áoÙ“Á_³ÿÃýàg„4†ÞýŸ¾.ê_uo| ñ¯>-iú?~3xãö¼ñÀ¯x‹âįÃáÏŽüaûë?¾)þÍ~+ñ×À|Xi'ø¥¬xàƒ¼ñKCÐuØ>|ðçÃï†z¶•áïx/ÄÕ¼ é²úâïüãá‰>ü;øMðsÃþøƒà?ìÿcû1~Íþ&ø—¦ülø­áσ¢ñìéâÉ-4ÝÁ´OÀ‰·Ÿ<={û-|ñšWÆï ülø7ñOáçƒ~*øâŸˆ4ÍbÛÄž…ãOù?Ù«þÍ öÞÿÕÍÿù¯¯hàØÛö ðçì³ð³â‚õ_ÂEâߌ_´‡ÿiŠ^"øP>)üðæ±ñOÂ~ø-á?VÓ`Ôþ6ü\øÛ«ÿÂy¦|ð?Šÿhëÿ?´?ÆÏþÓŸüYñÇÅ?õïxKã»ðêÇïú( Š( Š( Š( Š( Š( Š( ¾Bð_üŸ¿í+ÿf…ûÿêæÿ‚ƒW×µò‚ÿäýÿi_û4/؇ÿW7ü€>½¢Š(ä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{_!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚ(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š+á üRý¨?hĽñ§À ‚ >>§«éžñÿÆxëã/‹¾,Yèz¥æsãm#á߃>'üÓ¼àröÊâ_jz¯Äë¾'ÐR×Ä×>Ьu]:ÞO¹ëä/ø'Ñ'ö ýˆØœ´Ÿ²/ìÝ+±åžI~x2Idryg’Fi$rK;³3Ä’Âû{ÿÑÊþÈ_ø„?¿ú`ÔÂû{ÿÑÊþÈ_ø„?¿ú`Õõíòü!·¿ý¯ì…ÿˆCñ›ÿ¦ Gü!·¿ý¯ì…ÿˆCñ›ÿ¦ _^Ñ@!Âû{ÿÑÊþÈ_ø„?¿ú`ÔÂû{ÿÑÊþÈ_ø„?¿ú`Õõíòü!·¿ý¯ì…ÿˆCñ›ÿ¦ Gü!·¿ý¯ì…ÿˆCñ›ÿ¦ _^Ñ@!Âû{ÿÑÊþÈ_ø„?¿ú`ÔÂû{ÿÑÊþÈ_ø„?¿ú`Õõíòü!·¿ý¯ì…ÿˆCñ›ÿ¦ Gü!·¿ý¯ì…ÿˆCñ›ÿ¦ _^Ñ@!Âû{ÿÑÊþÈ_ø„?¿ú`Õsû8übø¥«x_þ{㧃~"xÂ^#мgoð§àÿÁ]Sà·‚¼_⯠êÖÚ÷„o¾&MâߌŸ|Uâ½Ãý–â éž$ð¿‡u wJÒ®üI§ë¶–I§¿ÙÔPEPEPEPEPÀÿeÏø]·×ìiñŸÅÞÿ„·á/ìÿû?þ×ßk½½ñ7Ù<9¥|vñoÆØ Æ¿á#ð>!±ÿ…ý•ÿ _â/ÄÏÂEáø;áÏÅ?…þø¡ü#?¼%ðÄv?ˆ?à–ŸµÇ^1²Ö<û@x‡âjü?ø ¡~ÙxÿÆŸðNßü,ÿ‚ëý©ÿfO‹´¾Ÿà­7örøaጵïü4‡Áÿ„µÂÿ|Rÿ‚½üdøiã¶ðçí7{à‹Úf£eûT~Ö>=øIý^Ñ@ÌìUðã·Â?ÚïöœøÝðOö;û™ðóö€ý¤gM#öVµý üßð ÿáªÿbø!gůwß¼uâNïÂ^ýŸþxKöp¾ð—Æ/‡²ê~Ñ?ðÌ?ð’x?à×ì_ð‹ö—ø%ðïD×-¼ÿöMÿ‚Z~Ô_¾"þÆ&øÑà?Ú~&ü)øÿÛµð—Ä?xÓþ Ûgð³ögømû7þ˲×ÃßÚölñ¯ÇÏ|0ø­ÿð·ü%?~þÓw>#øûk7?²wí áÏ6~ñWÄ߇–_´7íMñÂÕíü¡xÛþ âÝÂ_í¯ÿà˜ÿð±²hÙãwˆt?‰þ%ý‡|_ã¿þ'ð'íÅðWÆß´ƒðßâN™ã_‡:/íÕðÿPý™|=ñÓFøûJÿÁX¼'ðþ ¥hÿ~/xgÇ?~4ÿÃ}~ÐÞø ú?ÿBý‹þ&~ÖŸe™t?„ø½ðoÞ°ðŸÇ Å—¿gð­÷…u/ø*‡ü£ãÿ‹|3âoxïX´Æ^ÕÿgÏÙkö‹ñ³£C£ëšv±§x.çÁ××^$ñƒü9âoÙê(òƒAýþ2øötÿ‚«~γ…—‡ÿeÍ#â·ˆ>%èßðNȼ ¯AðÃáŸÁ=;âìû;ø:ËÅŸ ´ƒ±_j?|?§~Úüuø‹®[xGÂ:ЇÄ+ÿ|`Ó<3«jþ7¶ñ‰0~|ñ§‚ÿࢾ'ø©ðöñÀo~Í~ý€h{Ø_Àß~[üLºø{ã…ÿð^/Ù'T? ¾Ùü\Ò¿aÏ€Þ ¾ø¹ñÈü`×>ü6ý¤->øáî›ãoÚ·Š&ý¬~0øëà¼ÿÔíü‘x«þ {ûf|HÕ¡ñ/ÆÏ…_-¼Sâ_~ÕšŸÁÏþËÿ¿àŸÞ'ð¯ì‡ñÏâçü»öùý£5OûFþÙßükñÛöwðÿþ||ý•µÿþÓðOO„ ûaZiß/µßŠ?´OŠß?gï‡ãéÿޱÿüÆ^Ñ?fo‚¿ á[^øö¿ÿ‚¶þÐþý´%øñáxsAÖ?oo?ðUËßÙsÅ? t^øƒãÕ¿ü*_~ÙŸ<û@x¯YðÃoü0ø§¦hºÀ/þÑ¿ ®¼Mñáßô}E~@ÿÁ)?d]Sö\ÿ…õ{eð«ö€øðÿâü*ë] áOÇEÿ‚qü?òücàïøX²øÏâ…¿gÿø%ŸÂÏ ~̾ÿ„·Eño€<9­übñ'ücñÛâ÷ü+ý#Á^5ðÃÿ‡ÿ~k__¨¢€ (¢€ (¢€ (¢€>Bñ§üŸ¿ìÕÿf…ûoêæÿ‚|×áÿü5WíðßÂÿð‰üEøÑñTðgí#ÿÿ„Kölø…¯ÝiÞ#ðf±ð‹þ9ÿ…ñËö-¼øƒj|/6§ðÿâì‹/„|Mû=üžçâWŽüAð³áïíù£x‹XÑ?g/„ß ¾xk÷ÆŸò~ÿ³Wýší½ÿ«›þ ó^ûÿ Ÿágü#Ÿð‡ÿ´øÿ—ü,ø[ð‹ÂáÏøG?áiÿÂÓÿ…éÿ /ûû7û3þü.ßø¼_ð™}—þ?øZñp¿´¿á-ÿ‰½~p~Å_¶oÇß¾*ý”ï>.Ãð~ïÁ?·ÇìAãÛÃàχ¾xÆž ñWìåá_ê?²\¯ðWâwŒ|Oñ_âN‘ûHøƒRÒ?lŸ دÅÏ øöfÓ¬µ„~ ÕÂ[ËoŠzvð·õ~¼áÇìõð à犾'xëáÀïƒÿ ümñ³Ä âÏŒÞ1øqðÓÁ~ñWÅÏ&£â aÑt½_Ǿ ]_Åž*ÕYñUæ­¨®£â__ ‘s¬ê2ÜûQEQEQEQEQEQEò‚ÿäýÿi_û4/؇ÿW7ü¾½¯¼ÿ'ïûJÿÙ¡~Ä?ú¹¿à ÔõíQ@!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^ÐEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP_!Á>äÂ?bû4/Ù«ÿTÏ‚ëëÚù þ óÿ&ûÿÙ¡~Í_ú¦|@^ÑEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEò?äýÿf¯û4/Û{ÿW7üæ¾½¯¼iÿ'ïû5Ù¡~Ûßú¹¿àŸ5õíQEQEQEQEQEQEQEò‚ÿäýÿi_û4/؇ÿW7ü¾½¯¼ÿ'ïûJÿÙ¡~Ä?ú¹¿à ÔõíQ@!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚù ö•ÿ’Íÿùÿ³½ñ§þ°Gí½_^ÐEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEyŒþ=|(ð°þñŠZ_ÃSÝxsÉüuâ+'­æÔ´h¾"Õ´Ø®ù–ò_ÙÛ%ÄA¥„¼hÌ91ûU|=%øž~Ÿ³ÿÇãÿ¼Ê¼Ê¹ÖOB¤èÖͲÊ5iÉÆ¥*¸ü-:’ÑÆpU(É=Ô’kª-S¨ÒjiìÔdÓôiy¯¼ú.ŠùØ~ÔÿGø¢~Ÿ³÷Çóÿ¼Æž?j?„g§ü-CôýŸ?hÿ¼Â£û{#{g9KÿºŽÿ—ÙUÿŸu?ð —šûÏ¡«ä/ø'Ïü˜GìCÿf…û5ê™ð]wCöŸøNzGñ\ý?gŸÚÿï/¯›¿c?Œ¾øiûþÊ_ üq¡|^Ðçÿ€Ëü|¢¼~Ò_ ÏKŒéû9~Ñÿym?þ?á±é¦üc?÷n´Oÿ:ÊÛ9;Û5Ë_ýÏaùh{:Ÿóîø ¿È÷z+‡íðäôÒþ2Ÿ§ìÝûEýå•«¢|xøa®jÖÖ5¿júµÀ³Ñì<}à_ü4ŸZ½e.¶Z(ø‡á ®¯|裳ÓZêæEI"a•¨æÙ\åÃ2Ëç)5Æ8Ì<¥)I¥Å*·&ÒI&Ûi-X{9­á5×á{-ÞǰQEsþ,¶ñUç…|Mgà]gÃþñµ×‡õ›ox‡ÅžÔ|iá] ÅSé×1x{Yñ7ƒ´x Wñg‡ô½]¬ïµŸ é~:ð^£®éÐ\év>,ðåÍÔZÅŸ AÐQ_˜üXÕ4}?Ç~9ø}ÿ áÿìáû?üRøåÿ?ºñv‡ã¿Ý~ɾ;øqàM3U“öSðî¡¡èžÿ…•ñëíþ6øåÅo†ÚÅ-Xýœ~|:ñ¾‡ð²ïáÿíùû&ük›È<)ÿÒýŽ|aðkâ÷Ž&ÇÄW?üAð¯FñÇ‚to_°×Ž4íNøÓ?Œí>x³ÅŸ´çÃÚûÆŸ°Â/øŠÿáŸÄÚÛãÇíkð§ÅCÅ^ðÏ‚†Oþ;~ÌþøÔû=EyÿÂßÿÂÏð&…ãY<ñá½î§ý§g¬øâ—‡?áñ߃|GáýcPð牼9®ÙÛ^êþÕÿ²8øÁñ‡öuøMûEë6 ÇÁ¯‹¿´?ìýʼn¾3ü𞇤|PÕÿh?øƒÀr~ÏŸ´E­Ï‹þ*ü øsð“ÄSüñ3x/â7‰m¼aðzãâ`ÛôQE“­ëÚ†téõëZO‡ô‹]¿iÕu½FÏJÓ­÷©çß_Íomæ!WÌ•w“ZÕù‰m|>4ê©ñkÅñ¦±m©ÜÜÞü4ÐïÂÝèÞ ðcË$z î™§ÊѵóQâ¼ÖVýÖ_þœbškþø¼ÿžÞÂç÷¯þG×úZû§ü4¿ìãÿGðKÿ·?ù}Gü4¿ìãÿGðKÿ·?ù}_0üLø‘௃? >!ü`ø•­Â7ðëáO¼[ñ+Çþ"þÎÕµìø@Ô|ø¯àO…Þøáwâ§Áóu¬x2+Ÿ}ªx_Â~*Òíµ½4x?Z¸Ðµ›Ðm{¨g™Þ!ÇÙPÁÉJq¦¤°¸žE)¸Æ*SxÕÝÉ%Í$µWvÞ]*KyIiŠ7²ÞË–ýÿ¥¯ïïü4¿ìãÿGðKÿ·?ù}Gü4¿ìãÿGðKÿ·?ù}_›ÿÁJ¿b~3ñ«¯íÖ?àšòN~-Éìvø+ÿ"ÿÃÿ$—ž|y^7ãø-ì£Úø“JðÇ]#Äž:‹ÁŸµ/‰þÁ®|;ý¤t„ß®¿d‡*ø“ñ©´¿¾øñÃZŸƒ<¤xCY_øÓáΗñ:V“OÔ´ÏxsÇÞ0µ¶ð…þðÍsÉÚÔp›ÿÐ/D•ÛoëÉ$’“wÙ+ìµ— iÚòöô|¿»ý]|ÿbÿá¥ÿgú8‚_øu¼ ÿËê?á¥ÿgú8‚_øu¼ ÿËêüyðßü¿þ ïÿÍ­ÿÄŽÚo…üE¢xöXñ¿Å¶ðßÃÚkÅß þEû_|9ð¯Ä‚÷Ú¿Åý_ö{ð6•cà/i^/Ñcмwãýá·ÙgÕtm'Çþø{ã-E¼iû cçŽÿLÿ…mý£œFÜÿR…ÞœØJºJ-µ|Á_Fž—ÒQ{5uËMÿ3ÿ·£ÿÈy?ék/ü4¿ìãÿGðKÿ·?ù}Gü4¿ìãÿGðKÿ·?ù}Vâ\ãßùñÿ Љsä?Çü+Xc³Y[÷¹z»ÿ FÝ_üŒ½­ß$;Oÿ_ü‡¯ôµ‡Fø÷ð/Äz„O‡¾4|&×u[§XítÍâ7ƒõMBæF`ŠYXëÜÌì̪«lŘ W¬W”jÞÐüK§\i"Ñ´½{I½¢»Ó5>×S°¹…ÁWŽ{KÈf‚Tu$ñAå~ñOü*¯ŠºÀÝ[P¾»ð§ô-KÄÿoukÉonôk âÖu9 ×™‹Î2Ì UC‹§N»Š›£Ô­V0“´e:taRtã&Ÿ$§©Z\­ò»\iÎJñ‹jö»²Wì›i7ÞÛnϪ¨¯–Gí?3tøñ´÷ÿ߀ÿ×ã•H?i«£ÓàÆßüø ?ŸÇ:æ\G“½±S~˜<ø‰º~ΟOÿ·ì÷ÓñøóRŽ~&=?g/¿ø7ýžGóøõMgY{Úx§é—f>_õ çýYÙÏ´ð8ò^Ÿf} E|þ>7x¨ôýœ>6ŸûŒ~ÏüþêQñ«Å\?g/±Æß/ö§ìÿ?–¹ŸÉ´øíqu(QÉK{y¦`1nØSK7Á=¾·ÿ†ÜÊÝ5oê–K]Þ‹[ìÅìä¿—ÿ‡—÷¼ÿ«3Þè® Àÿ|/ãôÔ"Ñ¥¿±ÖtW†/x[Ä:mÞƒâ­[Ÿ7ì§TÑ5âºKKß"s¦êÖ¢ëEÕÒ ¥ÒuØby½®ê5©b)Ƶ «Jwå9)EÙ¸É]7¬dœdž±’q’M4¥¦ši®Œ(¢ŠÔAEPEPEP_!x/þOßö•ÿ³BýˆõsÁA«ëÚù Áò~ÿ´¯ýšìCÿ«›þ @^ÑEòí+ÿ%›þ óÿg{ãOý`Ûz¾½¯¿i_ù,ßðOŸû;ßë~ÛÕõíQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEãÿüi¬xá?мCá׊ÊÚ†|9sð¾y©A¨Ïû}ã?‰ß >ÿÂÿ 'â¾ÿÂÂñχþxþèÿ„ãâ_‹>×ÿ·ÃÏojð’øçÄßÙ÷ßðøKEûn¿¬ýŠóû;O¹û4Û?'´/ø Ç쇤j á‹â_í9?Ã?‚š?íáo„%øà;…ÿ|ûwøÅ^øçàÿ ͦü*°ø•’¶~3×u¿jŸo|Q¡ø‚KI¯¼C­iz}ž“½Ž*£ÄÆmÊ…E¸ëF´\•š¼ãYД[÷b£6õåDË›N_Ÿß¯•¯¶»¿áþ°Þ•࿎¾(ñ…¿Äk¿³ÿÅŸ‚¿1øÓá 3á¤> ñB|K¸ðÆ‘sÕðüwöSøqû7è_þ~Ñßü}ûMüDý¦¾x3áçÂm?à·«øwâGìá_øç⧇1º|iý¢¼ ø ã‡Ã?~áyt߇:Ã= |#ð?ám§Á|.¸»„Üø­u7´¹õ£¿WN5¤’níµ%CÝ‹´¦ñWrSI%ªÔÆNm¤íuÑ[«Oðö_‰ç¿à»ß±¯Å›_€¾$¼ð‡íð—áí-ð¯ö‘ø¯ð{ã'ÅøJø{âM#ö@ðÆ£âÿÚ[E¸_ |NñŸ´}ká>‹¢ërê×ú¯ƒ-<¯\hÚ¯ƒ<[âY¢e¡âø8ö9ð'Á_‰ß|uð»ö¢ðm§Ãÿ€_?jï ü?×¼ ðʈÿÿeÿÿü-ðCá×Ç„zu·Æ;¯ Þx.ïÇ1Ð"Õ´ïø¯áÿŽ4}:î;›¿ ‰í,†ÿðCØ×À^ý‰~ϯünñ߀¿a~Û¾øáÏø§À·v þÑ´ÏÆ}2Óö†ø}û8ÿÁ?~ |Aø·ñ§à‡…þ#øCá‡Âï‹úÆÿ¿²ìá¨ê¿ ô? k—çŽ>YIg x‹Â~,øËMÑî-­õë»>+Ë.úqÁ9®UW—žöÝò*ÒI®¼ÎŠƒ’iË–Û)÷¼¶ù_OŸsôûöWýº¾þÔÿ?iß:7Ãߌ ¾0þÈúßÂý/ãÃÿŒzƒ´­^ÏNøÓàûßü,ñ>‹{à?üBÐo´_xRÂëT‚ÎïXÓ|S¢>(ðÞ…¨Ÿ²WÝQ/OÏòéþ}ëã߃_±—µGíû^xS]ñî¡ñ+öÝÿ†z´Ojžºð?‡Ïì×ðçSøcàoøWºfá}'^Òµ´ZâóÅŸð’x›ÅŸnÕÒû Éd°“诉>|ð6·ñ?ã?ÄŸü"øiáŸìÁâOˆüaáïøÃÃYÖ4ÿhç\ñoŠõ'@Òµµý[JÐôß·êÿoÖ5-?Lµóoo- “hF.K‘;8ÂVz¾wó%ÖÜ÷åÝÚÅ/?ë·üÒa^ŸŸçÓüišÏ†ô?hz‡|K¥YëZ&­m%¦£¦ß³[\Á Á § ’FpðÏGqo2G=¼‘Mr-˜—8÷? íüëfÀQøŸéùñù×l)Âqpœ#8N.3„â¥)«J2‹MI4Úi¦šÑ˜·ï]i®o¦Ívîx/ÀˆW×÷Ä¿þ'Ôn5O|× Òì5›éÚçQñ'€õ}3L×ü©j—/‰/5ý3CÖô­'ÄWÎ7j©©+µÖ£r‘}7_ÏÆßÚcãÂÏø)Æ/†~ðt‹i~ ÐÓâîŽÿ%»¼¾Ôþü™<+ðÒß~ü/‡â̶Ëi£?5Ÿßý³Î}7áôÖkØ?O<ñ¿ã'ƒ¼1£xe?a_Û‹ÄßÙkm'ˆðþ·ð§àLJ¥¹¹‹Ä7^*³ñVµâmcGñ6™m>‰¦h^mûøÐxWYµÕ¿nÿÛ^ø­yâ ë>ø÷sâÏ€Zм§x[Nñf‘iá=à‚go þÅþ'ðþ©§|Cø‚ž&¹ø§û-|CñW‰u{Â~$Ö¼My⟀³¹ðO¢ÿ†•øÍÿHùý¯ð´ý‚?ú7©¯ûJühÅàŸµÂ°V*ÓøÓöòT€pÓ}‹öÔ¿¼òæO²XÞÝlìö—ì…ûÿ®ÿ‘¯üø)á_ÙÏà×Ãß‚ Ô© -χüU£k2ÿ®×ôŸhÓÙx¯Nñ Ã|÷zí¶³«xåšîî}òÂ5gUsRŒ\:9ÊÎ]ÒQRpÿ·í$ôpê+ßU¯ÏþÏæðMÙÇöømû`|1øœß.´ÏÛ+âïÅ/x—Ãþ(µðÇŽþxîÿöNøkûÜ뿼[£h¶ºŸƒ¾ßðKáªøĺgˆÆ:?ôˆ?þ|GÓüaðKâW‰¾Ýô ý„>ø/ã.‰ñ>Ûâ_Æ cÁ>øÁñ‡öŠøMû:k7Ÿ ÏÁ¯„_´?íň~3üf𞹤|/Ò?h?xƒÇ’~дEÕÏ„>*üvøð“óülñ2ø/áφ­¼ðzßះøÃö¯×ÿeÝv×Áw;´ý§÷I᩹ҴïÚÂöï±a:‡-ÓOøc¨Y"¼bø¹uð¾þXwÞ^øï[›~|àgí§ûC~ÒÞ5¾ñf­û6þÓº>›ðß^2§ìåð‹ÆŸ±§‡õkMwƒ¯üpÖ¾'þÕ¾ø»â-7_¶‹ûgÃ.™ðÿá‡ÃÍRÎV3Kñ[JO×jéÔU9’MJÒM§gÛš-Åü×ÚIè Üý¯¢¾Bÿ†•øÍÿHùý¯ð´ý‚?ú7¨ÿ†•øÍÿHùý¯ð´ý‚?ú7«AŸ^×åÇÁÏÁ¿„žÿ ¼ù éYÿ>Õä??l?ÚKözñö•â ~ÌŸµ?‰#ø§âHbƒöfø©âïØßÅZÖµq$¶¶úΫðSøYûVxÿã&ƒ £Xñƒ{ðÿâ¯Ãû H”ZÇð«M“P×O¬þÏóÍ{ð+à­åÅ…Ö•qyð›áÅÄúeóYÉ{¦Ë?ƒôie±¼}>êúÁî¬ÝšÞᬯo-X­®® )3üE¼^Lô²¡šÅê¯yTÊšj7æi(»É'è¤Ó”oÓ‡Ú~°ÿÛÏb‰søþ€uÿÊ´"\þ? Ãò¯æëþ Aûmþ×?²OÆ?GðƒãÞ…ðŸá-ïìãã_ éºÃm3âg‰þ+øÅSÆÚ¦‡¯þÒ¿|Oð;㵎“áÏì‹[?Ù/áoÆÏƒ´ïoï¯5? êñ鯢µç›ê?·wüJoÚ#á¿ì9eãßé?¿m¿þÇÿ´_ìïñw\ýŸþø/Ä¿co|5ÖüyûexG\øm©èþ:ðŽƒã?ƒ¾#øa­x3Cð¿ÄkâgŽlíþ&ØÚêŸõWKÑuçòèeuªR§YT £RšR”Ô£Nœ¢ªJKÙ´Ôç’‹“䄤“³FŽi;4ï·M{[^»+ÛS÷Ïöèøcã_?°÷í—ð{ᮌKý–¿à¨ŸðS¯„?ðK¿‹º§ÆKÏÚkÇ·/ìýÿ×uŸ‚zÏÁσ^ÒuO‰ß°Ï„~&xŸà  Ÿá?ÃïxÍußø‹Ãz'…|K`uÛVÒ%¶·Ó¼?c¯µßˆ/ùoŠ¿´wÄÿÚëömÿ‚WøƒÄÿ¶ÕÏÇÿ‰·ü?þ ¨x‚þÏöbѾÜþÊ?~&ü øâ‰ ìþ i^Ñ> ü~ŸáÏõK™àÐtï Íâ„ÒY'ÿZ§ŠgL‡(Iê›ÓfÒVi>ŽïGÒöòÜý‹Õ?àˆÞ%ºøªž пk['àêÿÁa|/ÿ‹ µÙóûgÆOñrÎT|2—â¤?´‹àÝ~8Ö jpü6ƒPð€šíõ{?Èömcùð«þ©ûmxÛ]ý˜ÿgh7ßeßÙ—à×ü§áo‡|[â¹> üB_ØÿÁE>xïáÖ‹¦ø Ä>;ji3á|DÔ|Uw⟊??cÉ'𶃧hƒÂ–:åýåàí~Á@?à ÿnÙÓö*Ôk¯ø{ÓÁJÿà³?±÷>*h_fwø—ñ'á‡ìAð_àŸÄ§ˆ Ö> êŸt?Xê>.ñ«ê~ð/„´­bËPµMúkWO¡­ÿà£_·3ÿÁB›áø»w‰×þ ¬ß²Yý€OÃ®Ïø&òü9mq¿à¡gÆ£ã9Å⯈×Äðž¦ü:ð/ü$ÿðÂìOøL?áuÿÂu_ì_íß †çU_ÚÞ=ûÛ/?¦˜—¦;ð>ƒüþ8¯à»Cÿ‚ŸÿÁB|Eû ~Å?¾.~ѺoÆÛ_ø(ì#ÿâ¾ø­ðûâ'ìóû1ÝxI×aß‚ÿõÿ„ºî›áÝ+àþ™câ?íiü=c¤xûÂ><‡Å? |aáx†‰«x ynµKWû—öYý¼üuãÚþ Qû2xþ 3áÿ€ö2þÇß°gÅïÚßà§ŒôØáÁÛëÿ‰³ïÁ«Ÿ†_±ßì™ðÀþÎÞ øÁyñö‚Õ5[?kºOÃ߈·ø?àOÙèÿ t¿Ýj¿üá­å‡ÄKø•a>GST—³å¡7îÓ‹ÿ˜xÁ]$ÜSNóm´â­m/eù>ÿÞüÏëò%éŽü ÿ?Ž+B%éŽü ÿ?Ž+øôýˆ¿à¥ðTo?|Om㟳·…>$j? ¿oKÿþÆ>$ðOÅ߉ß¿eßü³ñ\_µ-KáWìÿû Úx»á§‡ô¯Øø&×UÒ¾7~Õ_üAûNxCÅw¿´Û/‰6öÿ —êoø!×ü+ö«ý©þ?|Gø=ûA|k¿øõi ~Ìžø¥y⯠øGàWŠ>h¿.¼iÿεŸñ_àçƒ?g¯ü×üQÍÍÝ·ì]ûP~Îw_´'€tßßk¾"ø©qkj-êŠvmèÝ´Ñ]éä¿Cyþ“ïæN±/Lwãèùüx¯ˆ¿i×’ßöŽý‹Þ'hݼSâØ‹!Á1OãoM#ªÉ ’Dã8db×Üð&[Û·áþB+ñ÷þ ±ñ»Æÿ¼qû"xãÀß µˆš·ˆ¼vó]ZÉ¡.ƒàØ,¼Uð>í|G⸵¯ø29´kwO2asâ_ h+½Çü$^:ð]›E«Tâ Ý*6¶˜ì³w«C2ÂIë&“vM$µ”½Ø§&‘œ^­ÿvúLÛ:+ó[àgÇ?Žvoj³'í¡ñëPø€¶#Åãßø'm¿€f²’Ðg[ü.ðÏ…nü7áﬗӚ-GÄž Õ¡hnüOã/j¦¤}³þWã7ý#çö½ÿÂÓöÿèÞ¯|“ëÚ+ä/øi_ŒßôŸÚ÷ÿ OØ#ÿ£zøi_ŒßôŸÚ÷ÿ OØ#ÿ£z€<ßÇ—Sø¯ã×Ä[ýMÄ? ›Ãÿ¼'i&ßK:Ï‚¼/ãÿêö‘’Â=G]oé:]íØÛ#Øx~ÊÕvÄ&óÿÿà¥ðQ߉Ÿ³î¹ñãöfýœ~E­|sðgügö‹ýºuo‹_ èÿ¼à(õïxCÅžð¦¹ðÿâ“ñsÆZÄKUÖ'ð¾¾ðüú6’>Ûªê¢ý´šýVðŠ5Ïø÷ãψ|GðÛÆŸ µk¿ŠZwøƒ}ðïRñN–¶¿~ [A=õß¿|LðL‘jPÄšÓƒñ+J±øƒ¡ü_ý²¾ø[ÇZ'Äm{àçþß|6ðuïßjöÑè^)ø«ðŸâ6¥ýŸâ=á×Ámsáž§ø§Pé¾ÁzµŠžøñÃÁ_°Ç­köxÐ>~×ÿ¾|j‡Fø×cà]oþCÂþ:ñ5¿‡~1|A׿f­à—ÂäøÇÿ ó_Ñ<«ü3øÅûEKáÿ$ñÞáÏ͆Ÿï;Oø$güÒׯ? ¼}eû=Ea⿃z—ìÁ¬øRÓþ)|jÓà³×?c/ Oà¯Ù—]ñ“eñ ÇšçŸ]Má/YñîŸâmOZÐÜi¾*º×­R8“§Ó¿à”_° ®¡ãûëÙîÁ"ø‘àÏ¿õüAø³'„¼5áOÚGK¸Ñ~דR·}oÆžh/c¶íüUÿüð. xËâ¿…¿fÿxö{øðöøÇûWxûWø‘cáŸü)Ñà¢z7…üGðoLøuð¶ÛÁ>$´øÏ©xoAñ†‹ãò~ |=¶ÓšK›=]zòØÂߤÁ4ÿbƒ×à·_Ø\ÿÁ5Oü\o‹?òdÿôE¿ä{ïù(ßòVê{¯žü ûÿÁ%þ.ü~ñ‡Àoü/Ó*·‡µÏÙÏJø>¿ðXÿÁÇÄí?öƒMcÅòü]¶¸hüñ>/…“üÑìσµÈ"’çÅZ\ßáÔ<$&±‡F¼ñÔ³^;7öêÿ‚­xÏöÿ‚üi±ø‘¬ë¾'ýþ Á´ŸÚª‚žðß‚×YñíâÛ³Ãÿ³„u›_Ýh ã=>=ZËÅ:„ïl®9þÌ1|A‡â¤pøÊÃÃ֚ݿí¦iÖöž+Ó`µð]¥¾¥may“òr8M-[|Ð|ÉÊråvRNJÖrV[™ÚVß]5û¯Ó½ÏŒõø,Σà»ÚKáÇÅÙïÀŸ j_ÙãGìíðsÇ õÚFëXø?©ß~Ôÿ 5Ï‹/<ñoÿõ/ŠÿõÿéÔü(>|*ý˜~!|IOM¦Ãká«ï/ˆ|yáÏ‘5x·þ ]ûcÁAu¿øã⇆"²ý›<5ðÇÿÿg=7ÄÓõ?…~0Ð~?xŸMø×àGÄVñðûDÕ¼§ØÜëKVçÃP~ÃYÿÁ)?`ˆ|¿ æø.©£¿h£â_|Vø×âO‹RümøQfšgÃOˆ—|AñSøÙs­øFWð÷…Rãâº~‹á››ï ØÙC ßÞéÓòqÿÁ¿à™ëð»á×Á›OÙºm'áïÂφ¿> ø?LÐ~5~ОÖGÂ_޾+×¼uñOῊ|g¡üWÓümñ Á>#ñ—‰õÏ[xo∼Q¥xk]½þÒ𥮇umg%¿u†V~ÎmóEk¬yT=í=¦©ÎÒI·hèܬÔ{ºýwô¶Úl~bx'þ éñ'á'c ||ý›ïµë߉_²§ìñÅŸ´ŸÅo‰ºOìÑá¯ÚÇ´oÁχ¾0ø¯¯~ÏZŽ¿ðgKý“5;…úŸ‰o¯¼uᯉ?´‡ìÛêÐê~øs¢ëÙ‚Þ/ê’ÉQéüÏQüñ_™·üÿþ Óâ+ iú¯ìéš…~ü øM7…m>)|lÒüñ áçìÍk¤Y|ðßÇ_é_,¼%ûEÛü+¶Ðth|#{ñ÷Fø“©Ø 6Ñì¯cúl½ÿóúNk²<’å䋎·•öi[ev’Ý[O’²YÊ÷³w±ógí3ªOðËAð÷ǽ(“Ä_|Mám#VUQø§À^8ñ>“á-{Âòõ–­i~"ÓžQ)Ó5­ÖöÝFû¨®~­Ñuk-GÒuÝ6C6­i–¶Ÿ1L¶Z•¬W–²ÉÚ^ £b2qœf¾Dý¸þ1ÇGþ¦„Çñ?üÒ¾€ø)ÿ$ká'ý“/ê+¥SÁÇÙã±Ôã¤*PÀ⥳­VXÊŸ¬©a(Eô÷/k¶ÛzÂ/¯4ãòJ /¾LôÚ(¢½RŠ( Š( Š( ¾Bð_üŸ¿í+ÿf…ûÿêæÿ‚ƒW×µò‚ÿäýÿi_û4/؇ÿW7ü€>½¢Š(ä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{_!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚ(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š+ç/Ú³ð¦®3À~’}øûðÈ’O`I=ä×ѵÃüJð=—ÄŸø—Á7×siɯiæ]RÞ5–çFÕ­g‡PÐõ»hdeŽkY´°Õmá•„SMf‘Ëû¶a\®¦/+̰´Ru±8 f’mE:•°õ)Á9=r’»z%«.œ”jBOhÎ2~ŠI³òÇöÊѾ)x‡ö<ý«´·$³ø×®~Ítƒ÷~ Ö.¼;âû_ŠZ—ÂïÙ|>¸ð¯ˆ,nôûÝ Äø¶m"MX³¿²ºÓ55µ½·»¶š™?‘ÏÚGÇ?ðV?| I> hðUľÿ‚þÇ^ׂlx‡Yý¼<'ûuþÎúÇ}wC‚M3KÖ|QñÊÿáOü'ëâiÐÝxËÅÿ `ñ~°Ú®¯ðî{ÍJëû5Ô> Y|=Ô#ð¯Æyì>ø²ÙMη;Xx3ÄpÀR6Öü%âëåƒDÔ4Û²ñÊ4û‹Û}IyVÇYÓ-.LÖ#øáðPuøÁð´d÷øƒá!Àÿ¸¿ëòüiC.“¥ˆ†5¡QT•,\£Fµ9'*u)T´ãð¨Ù¥gÍ£æÓ±Ó”ÕÕìÕ¯S½¶kN«¿CùSý¤>þÛW·˜øYiáÛ;ã/ì½ðSþ WÿWøÝðOÄÿôïŸ4øûàÆÍgö¯ñÿ†>&øªÏÄ;~ø+â-ÎÄ9ôÝq>|%Õõ7AkoÇ}m§K蟳…÷üÛKñ¯‡uOèß¶ïˆ>6é¿?à¦ÿðPx«Rñv‰ð#\ñ½¤^"¸ý€´oØ#Yø¯¢x£ötÒ!ø›%ô¿´ÇÐ4fQþÌ"ø÷ð(c?~Ž;üFðxäÿÜg°â´#øýðuøÙðŒqù) üzë>µß ûwjø&଱4lùa6ï'y»ss»µ)&®ÕÞ^ÎË?¹÷O¶Ú~gó·ûq|eý±~Á±ž)ø½ãO~Ó¿³¯íqáßüƒVñŒ¾-ˆ¿iÏ Ëyûaü/ðŒ7^'ø¥ð¿Âßoâ×üWðæöõh†ô/GáÿMáˆSk¾''Õuo—þ(|(ý¹þ)|wøbúWÃÛ£â/ì{ðïþ óÿ¢ø§û=ÇûGxGãŒ~1xÁ øGñ<~ÙŸ=þÎ:ÖŸ©|jø'}§ßÛOgc{ñÀ—Vw¶w1<6—v·ËÁsms ’C=¼Ñ¼SBïŠÈÄW’üÒÿàœ¿³´Þ!¹ø§þÄÿî<_,3ø²ƒÖ¿¾Mâ‰íži-eñ¾ D}j[y.'’ 5&¹x^yš2­+“èRÎrÆîñ˜ï{,V'ïÊošó½““QZÙYy¹•9¥uéýײ­·‘üëèÿ‚ŸhÆ ][þ ;y­·ü-âo„)á-c^ý¥õß„ðÿÁ'¼_p×ÇŠ—á&¯-ïÃ×ý–­±m†~-Oáù~øÚÔGà_xm®õ9/¿²X—'êqø¿çÚ¼ßöŽýÔdüzø.¿Å/ƒùnýG>ÕÂüFýµ~øG¹›Ã¾0Ñþ*x™íœèÞøsªÙx—í×dm‰5?io{ xvÅe(×—š­ôsCmæIga¨\,vrô,Û,i?¯àÛJêñªÔ›Ó–4éR”ªU©'nZpŒ§9IF1ri<ýœßØ—Í4½[vIk«nÉjÏ#ð?†ü5ã_Û‡ö¬ð·‹tÅž×¼ ¥izï‡|E¥Økº³a7¾Ãw§jºN¥ÖŸ¨ZHU£¸´»·š*RHÉÐ?xCÃþðÆ‹àß YM¦øoök§hºtÚŽ©ª?OÝíìa¼Ö/u @ÙÙ«ýžÂÖK§ƒO²ŽÞÂÉ ±µ··‹óÇöð¯ŒÞ!´ð¿‚þ3üOø§á?Z–™ã;Í7WÕô½ÂüG¯xaÓIJéZ»¬Xh6Ú¥Ì×6:6«~4ÙítûÉ"ø÷â¿ÂŸ‹ú}¶³àíöÐðÆ»ðã¿üðwÆ?Úž0ð/Ã߉~ø‹ñ8ø>ü)ø›ãý>êÛP_xΊ¿ cð†¬ß|wÿ ;ÀßeÓ¼B'ø‹ûÿe~Ù?³ÿ‡4ÿÿeüðìÿð§ãíá/„Ÿ ¿áHþÆÚÿíOû1XÅðGö/ºðgÁ­R_xKQþÑ𗊾)xÿ]ð~™ã_‹¿~_Á>à–wºïÄ/†úg¿ƒ/ñ¿9R§7w^§(ó.Š|­s¥m®¼µb²ßþßßæaþÉ>ÿ‚nk> ø‰ñ Á¿µ¯ì­û@xsà~„ž6øÓ®|/øíð³Å? >h2éÚþ¶Þ,ø¯¬øgÅÚ¤:&“&™á/ê²x›â&§¤é7úo†üCsjÐèú”Öÿ¢µý–ÿi áWÇß„ºŸÂ‹þð캳üøÕðsÄþñ6… ¶•«ê>ñ.àÿˆßuIôýKÃI®é:žâß Ùëž¼Õ´{­+Ä:UÅæ–Ööÿ•züÿãì¿?à™> ³ñÁùñÆ—ðGâ]ÍŽ±à˜¿Wÿg¿|SÓ4x·ã_€?gÿ†¿~)|@oø›Âÿ³Ýçˆü_£Åý—àO|3ѯ|yñŸÅž øSâ?/xCž0ñ%¥YxçÇ÷0ÜxÃÆR¤ÚŠuØíÝ?Lº×õ9¯5Xü?¤‚ãEð͵Ôѳÿdé–hŸÌøàjçàÇÂ#ÿTÃÀ á)¤äÿŸzý;¯Ìï‚°½§Â¯i¯—¨xgÂúOƒµ‹sÃÙøƒÁö©áXJ¬G5޳¤ÞÚJŽªèñ2º«ÁñŠo“»Ì>j¯Ñ9UÊl›ó³'m™Ó‡Ú~°ü¦z¬Kéôãþ}ëçï~È< ñÓÅ¿´¶áOjßüg¤jþÔ>"øÿâÅoŠšÆ…á-wÄoâíSÀÿí¾'xÛÅúOÂ\x…—QOü.Ó¼àÛ_³ÙÚÙèvöVVÐ}ãù©ëþ}ëZÀ÷íøÿŸÔŠðh¹E5J*IBII¥%¦’Ku¶ŽëÈÞ˱n5éùñÿëýk€øÉñ›áoìíð¯Æÿ~5ø×Gøwð¯áƇ7ˆ|gã-u®³´]*a¶Y+{½Bþòòòâ×NÒô­.Î÷VÖ5KË=+J±¼Ôo-­eôh—È}O_óï_&þ߱τÿoßÙãwì‰ã_êþ о1è6žž/Ðí Ôu kþñw‡¼}á-mt»›‹8u‹=;žÑ.µMïôÓ¬éqÞéi©é¯v·öþ†s¦ª7nqŒåy(¶¹ä–º¨¶ÒÖìΣÒË}_ù_úì|ƒðKþ /ðâÇÇ?Û;ᮡáøKÀ²Ö§û)h^ñ´>øãâŠßµÿÚoáŽ~-ÉáÝöT±øcñßÃ^$ø¦ü<ñ@½ðõ·‡¼e­jÞÑuÏÞØøgÃú5ë.Âïø-×ìgñ—ö©ð÷ìûðÃQñŸ‰þk_±Çl¹¿jY<ñ'Bø%¦øÁ^'}U´¼Ö5ÏX®›áýËDñÊxÛâ·‰nü9ðÇÁ¿ü/À¯ßüw¿Ô¾ø{æÏÿÁþ.|FñÿíCñgâ?íÞš§jϰOÅ߉v>ýœn¼ð»Åš7ìQàOü>Õþ üFø}í¬]øÿáÆÝ#Å–·:ç†%ñn‚t CÃZ4Ú…×c1KŸû(ÿÁ|Oû%iß á[~Ûº¥¯Š|'ûþÑßðOÏxÞÃàeׇüGª|<øïûHüRý§4‰ß¯tÏŽbƒ¾xãâE½¦›â v‹ú±cáÈ.bðχõ+ÔºÓ½ÈSÀÙ¸Ô—7-8F>ÿ/?±´›“¤›~×™}˜¶Ý£ZØûÚzêôÚý5íý\ý£ýœ¿lÙ×ö©Ö~)øcà§ugÆ5O èÿ| ã?†>|@ð׎´9(]xÃÅŸ5_^|@ñ¦¡pÚ<Ðx¶ïZ¼’]aµ“-—íKúp=Éÿ?­kÉMMªRr‚QQ”•›n1r¾‘ÚM­µ]÷m^ÚîY}>ƒüúÿZÐ}>ƒüúÿZ­ôöà}O_óïíZP¦Höãü{÷þ¹í]”£¶Ý—®šÿ]Ù}>ÿÓúô.À˜ß§ùÇù9¯„¿j¡Ú7ö,ôÿ„·Å~;øþ~µ÷´kÓÛîÏó¯Ï?Úƒ\°¿ý«¿d¿ ÚÈ&Ôü3«ÿljñ'ÍökO|Cømo£çcÎ|®Iµˆo*“1¬dW±¡¬¥˜eŠë'O0ÃV©e×–•:•%m¡ Éè›&;¿ðÏÿIkóiz³îü*øwðÎëÅW?¼'¥x9|k¬Ÿø“NðòM¦h7þ!•ouؼ9m*hn³«³›SÒ´Ûß^„¿×'Ô/cŽá}Š+Û$(¢ŠüùÔ_Œÿ´2°*Ïñ#—  ´ ðàÜ 2ƒŒÆÓÚÜÂeLJ™Ý½ôÿ¾¿ÃúW}ñ¯á‰SÄoñWáæ™ÿ ¥>‘i£øëÁ\ÛY_ø›MÒ$¹—FÖü/q{5®˜<[£Ç{{e5Ž«weiâ=ììΧaw¢é©uòK~Òÿ ì%–Ë\ºñ‡µkY CEÕ¾üCŽ›qĶ—‹§øbþÕ."l¤‰ ÜʬßÅ~I™`ë`srÄÓ”!ˆÇbñTk8µB­,^&®&*V¹H*¾Î­>eR3ƒ|žÎTç>øIN1qÖÑŒZêœb£ªÞÍ­Ϧ©¥ôLKŒ{ þ'×ðþU¯n˜ü?ÿ^:ù¢?ÚŸàzã>%ñç'þ-§ÅÝ?æL?äÖ”µÀ•xƒú¦_?ž ú{qQN­/V’»¾³ŽÚo¯Ý껕gÙÿ_ðëï?š/ø+À~ÒðWÿŒü û9ÚGâ'Œÿà^)Ð>èâx@|2ø¡®~×¾2ðÿÃïŽ+¬|Hñ7„ô5o…~*Ö4­y×MÕ&ñU¬FMWÃV©«éÖÖrò#ÿ‚PÿÁHtÚÄ?5o†süvÔ¾þØðoïí'ˆ´ßŠß ¬¼QûMZþÀ²?þþÖ·ƒâÄ Åq|GñTWÚr|c½øs‹a¸»Ô4kýE»ÿSQþÖ¿WñWˆýнOÓÁ>æ®Çû]|^¾,ñÀÇü’ÿŠÿ‰ãÁüš÷¨çáN5W £J›ç¬¤Ý¥JWV©gNê[7våÊãÏ*mÉ»Iëueä­Ó]úùw×ùñïìûñgöbø¹àŸ‰ß·_ øoàÅoø,ßüö¾ñìÇñWãwÁ?xâoÿŽ_þhß³ÏÅO|]×>!XþÎz‰þxõ5½gÃÿüyñ#ß5Æ:Þ·ðŸá÷Šï4 V+?‰>ÿÁ%¿à¡´Oü›ÁZÇÂ_ÙÃY×,þ"ÿÁ¿áBx>-Ç ~êz·Åfÿ‚ñÜ~×ÐxiôO‰9ðŽµ¥Ú^þβ|PÓõßK;ÃÆ1ýþ I8I·^·‹“ŠQöŠßij|ÎNË[r¨Ï³–Ö•½÷mÓÉ}çÃðOoØÓâ_ì©ÿÿ‚±x–Çá}—ÂÏØóãžµûø›öYÓ|=â/ ËásÄžøâ ö“ñ›à= ÄZ†¿àýRø‘q¦Íã kÆðî§ñW+â {¿Cš”´1.?Äõÿ>õò|¶wìæ1Ÿø€s“ÿ«âáü?äDÿ9«±þÚ³zã>5ñîâÕ|^ëÛ§€þžÜUÇ1ÁNJRÆàÓä„ÞhêãÁ;ºV’rwÕ»ìÊP’Ú2ûŸ_—š·ÈúÒ%þ€_ÏŠØ0 zÿ!ýG#ŸJù/ÛgökR7xßÄ¿ñj>0~#Sþ5vOÛ—öi‚e‹Æ>%»–8Ù£µƒáOŘæ¸p2"ŠKÏÚZ$ŽFÕk›¨!æIQrÕÛO1Ë–ù† ¢ÿz¡~šÛÚ]î­nþ†.wnÿÀ_ùy¯À±ûsÏ ?³7Œ¢–DIo|Mð¦ XØáçš?‹^½x¢]ÒÒÎêå€é ¹á ¯ ~ ÉøIÿdËÀ_úŠéUùQñ3â‡ÿm?x[À^ðž« ü?еDÕ­m5u…õmKTxe°_xÈØMyaáíÃö7—«¥i){wuquu-ÝÔ²ê ¥é–±Ðm|-á¯xbÉÞK/hzNƒhò${]"ÂßO·w ;En…€$'ݽjøœb…JtªÃ† ªÂtçVžâ*:þ΢HBu1S§R1sZ)Ó«NL–‘Œ.›NRvÖÎJ*×NÎÊ*öÙ»nÊ(¢½2Š( Š( Š( ¾Bð_üŸ¿í+ÿf…ûÿêæÿ‚ƒW×µò‚ÿäýÿi_û4/؇ÿW7ü€>½¢Š(ä/ÚWþK7üçþÎ÷ÆŸúÁ¶õ}{_!~Ò¿òY¿àŸ?öw¾4ÿÖý·«ëÚ(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Š(¢Šæ¼Wàß øçJ“Dñ~¥ø‡K‘„ŸdÔíc¸X¦ªÜ[HÀMirªYRæÖHn3 ø[~dz‹ÇáÈÉ$¾-ñÒŒžxUñ0U@ WÓ4PÌ¿ðÇ_³ý¯ü»¼wÿÍ=ðÇ_³ý¯ü»¼wÿÍ=}5E|Ëÿ uû8ÿÑ:ÿË»ÇüÓÑÿ uû8ÿÑ:ÿË»ÇüÓ×ÓTPÌ¿ðÇ_³ý¯ü»¼wÿÍ=ðÇ_³ý¯ü»¼wÿÍ=}5E|Ëÿ uû8ÿÑ:ÿË»ÇüÓÑÿ uû8ÿÑ:ÿË»ÇüÓ×ÓTPÌ¿ðÇ_³ý¯ü»¼wÿÍ=[±ý‘ÿg}:îÛ†ö’Mnë$i}¯ø³T´fRØjzõÝÊ>h®-å†C!ƒô}ZÎÎÓOµ·±°µ¶±²´†;{K;8"¶µµ·‰BE½¼*à hÇh¨Šª€«4Q@|û]~ÒÿþüSøcð·án­û?ø#íß³ÿíKû]|Eø‹ûKßøJøYÂÏÙijg‡|]ðÇVñw‡uíþgü,/øi]?]¿ý©uÝ?ã‡>xsáν©ê³gÆøHàƒÃןøÿá?ÂÏŠÿð…ÂÒøiðÿâWü+_ˆø±ðëþÿøsÆ?ð€üSðwÛáø—à¯øH´ÝGþ_ˆþÑÔ?áñ–…öèo½þÌÔ­~Õ>ðÀØSöùý©õÙöZø­¬øƒáÿŒ>|&ÿ‡B~Çôˆ¾ø‹ãoÚ;ãçÅ?Û—öÿ‚vêÚ¯í!í/uñ‚ÇÃþ ÿ„Kķφu¯|<ñgìÿñ‡Äî¾xº[ŸŒ~½øÑ§ßü 4Ïø*GíÅàÙŸöUø©ñÂß³ÿlj¿·ßìð_ö„ø á¼[ðwþߎþ/þПðNÿÙÆÓÀ~)Ò>0~×Þ0ðÿíåøƒþ ;áè‹?ÅïØÏGÔ¿áGêþ ñŽüeñ–‰_?oôÏÙ;öXÑ"éŸþXxïàÿÂÍ#G×|;¥|4ø[âë_Eâ‡ÿôÏø£ÄÚŸàß êG‡,´kºe¶›–¯¨Aq¿sû=|¼ð®à[Ïßî¼áσþ&ýž¼=à럆ž ŸÂºÀ/iÞÑücð;Fðôº+i_Áÿið.—âo†–6px/^Ó¼á;SEº¶ðæ˜å ~,ý£¿ioÙÇÆúíAðÇö€Ó>6üý 4eOÚ2ëöQºø£ã¿¶¿³|ß ¾/þÑ—°GíWû\|mý–þüÓ><_]|Nð[þÖ¿t};öqƒÅŸ¶—>xãSÔþ|=øSúýðŸÃ¾;ð‡Âφžø¥ñþÄß ü?ðo‡~"ü[ÿ„GGøÿ GÇz'‡4Ý7Åßáðì÷>ðOü&Þ ¶Ô+¼·…"¿»x5 \’8®5-:×Skfo«è®,v_…Ìhª8ªnqŒÕJsŒ¥N­*‰8ª”ªA©F\²”d®á8JP©ÂR‹¨ÎPw‹òk£]šþšÝYŸ“—i].êK;ŸÙ;Ä3MnÅ${x³R´v1†ûLøE}e:’¯ou2Œ;H¿¿håÇüb7‹N?ê/ã±ÿ¼X×ëã®Á­±xõm½ìÿ1W~V×ÛËùa÷Kÿ’þ®ü­ùH¿håÇüb'‹N?ê3ã±ÿ¼TÕ…øáûG.?ã|ZqÿQ¿ýâf¿Tè«\9‡­Ì¼ð_üÃë÷¿+KªÛ»Œ?òo/ïy~/Êß–©ñãöŽ\Æx´ãþ£¾;õÿš$jÚ~Ð_´rÍø´ÿÜÇc“ßþH…~ŸÑZ,†”ml~`­ÿ`üÁë÷¿+Õÿ$?òþKú»ò·æB~Ñ_´rÍœx´ÿÜÃã±Éïÿ$:¬'í#ûG'üÙ¯‹OýÌ^;žÿòCkô¾ŠÑdÊ6¶c˜+yeÿüïõûß•jÿ’ù?ÿ%ý]ù[óa?iÚ9?æÌüZîdñØä÷ÿ’V¢ý¨hèÿæÌ#Oñãã¯Ç[‹Ë\^>³mm¬½»kš¦¶ÖâÒÊþþÊÐ MFÐ-6ÁáÿÀ°5‹Aj«kg˜ߥTWE:U#Z¥jøªÔÔ•)â%NÔ¹“Œ:T)P¡Ê.Qu½“­É9ÓöžÎR‹NZY%÷Júú¶ÛµÕí{_[\(¢Šî$(¢Š+‘×þøÅw wâøGÄ—q Ž;­Ãz6±pˆ$Ú•ÌŠ€pX: 먠2ÿ…)ðkþ‰'Ã/ü ¼+ÿʪ?áJ|ÿ¢IðËÿ/ ÿòª½6Šó/øRŸ¿è’|2ÿ ¿üª£þ§Á¯ú$Ÿ ¿ð‚ð¯ÿ*«Óh 2ÿ…)ðkþ‰'Ã/ü ¼+ÿʪ?áJ|ÿ¢IðËÿ/ ÿòª½6Šó/øRŸ¿è’|2ÿ ¿üª£þ§Á¯ú$Ÿ ¿ð‚ð¯ÿ*«Óh 2ÿ…)ðkþ‰'Ã/ü ¼+ÿʪ?áJ|ÿ¢IðËÿ/ ÿòª½6ŠÄм3á¿ Z5‡†|?¢xrÅßÍ{- J°Ò-LcÌk}>ÞÞ|q¼¡lqœVÝPEPEPEPEP_!x/þOßö•ÿ³BýˆõsÁA«ëÚù Áò~ÿ´¯ýšìCÿ«›þ @^ÑEòí,Büeÿ‚|³~×Þ2“€ þÁ¶äh <ò:F£ø•FY€?_W‚~Ñ com.meego.msyncd and at the path /synchronizer. A dbus method can be invoked from cmdline as \code dbus-send --session --type=method_call --print-reply --dest=com.meego.msyncd /synchronizer com.meego.msyncd.startSync string:'abc.com' \endcode Following are the signals available in msyncd - Signal to give status of an ongoing sync session \code \endcode - Signal to indicate the transfer progress (of the number of items transfered and the kind of items) \code \endcode - Signal to indicate if a profile has changed \code \endcode - Signal to indicate a backup operation in progress. If a backup service is ongoing then msyncd does not start sync until the backup is complete. This signal can be use to keep track of an ongoing backup operation \code \endcode -Signal to indicate that a backup operation is done \code \endcode - Signal to indicate that restore operation is in progress \code \endcode - Signal to indicate that the restoration process is completed \code \endcode - Signal to indicate that the results of the just finished sync operation are available \code \endcode - Signal to indicate that the status of the sync operation has changed. The status could be that the state has changed from in progress to failed with also the failed reason \code \endcode Following are the dbus methods of msyncd - Method to start sync given a profile name. The profile name is the name of the Sync profile XML file. The startSync operation is valid only for client plugins \code \endcode - Method to cancel an ongoing sync. The profile id has to be specific to cancel an ongoing sync operation \code \endcode - Method to remove a profile. A profile can be removed if it is not longer required, like for example, removing a sync account \code \endcode - Method to update a given sync profile. The profile to be updated should be in the form of an XML and the XML object should be sent so that it can be updated. If the updation fails, then false is returned, else true \code \endcode - Method to request storages to be loaded. This is useful if a plugin makes use of existing storage plugins (contacts, calendar, notes) to perform the sync operation. If a plugin has its own way to manage the interaction with plugins, then this method need to be used \code \endcode - Method to release a storage that has already been requested using "requestStorage" method \code \endcode - Method to check if connectivity for a specific type is avialable. Currently supported type: USB=0, Bluetooth=1 and Network=2 \code \endcode - Method to get a list of all running sync sessions \code \endcode - Method to get the current state of backup/restore. True=backup/restore ongoing, false otherwise \code \endcode - Method to set the sync schedule \code \endcode The format of the sync schedule is: \code "" "" "" \endcode \b time is the time starting which the sync schedule has to be enabled \n \b interval is in minutes is the frequency of sync \n \b days is the days on which the sync has to run \n \b rush is the sync setting for peak hours of the day. The idea is to run sync at a higher frequency in rush days than in the normal days. The rush begin time and end time and the rush days can be specified \n - Method to save the sync results. This method can be used to persist the sync results given a profile id /code /endcode - Method to get the sync results of the last finished sync session. The sync results are returned as an XML string, which then has to be parsed to retrieve the results. One can use the SyncResults class of libbuteosycnfw to handle any of the XML data received \code \endcode - A profile can be made a hidden so as not be be displayed in a UI. This method fetches the profiles that have been marked as visible \code \endcode - Method to fetch a sync profile object given a profile id. The profile is returned in XML and the SyncProfile class of libbuteosyncfw can be used to parse the sync profile \code \endcode - Method to fetch a specific sync profile based on a key. The key can be anything like sync direction, the URL of the target service etc. If there are multiple profiles matching the key, then all of them are returned \code \endcode - Method to fetch a sync profile given a type of sync. The type can be client, server, storage \code \endcode - Method to start a sync session with an account Id. This is useful if Buteo is integrated with AccountS&SSO. The account id is passed to start the sync \code \endcode - Method to stop a sync given an account id \code \endcode - Method to return the list of account id of all the ongoing sync sessions. This method is useful when Buteo is integrated with Accounts&SSO \code \endcode - Method to return the status of a sync given an account id. This method is useful if Buteo is integrated with Accounts&SSO \code \endcode \section libbuteosyncfw-api libbuteosyncfw API If one needs to use the API of the syncfw, then you can do so by using the library libbuteosyncfw. The main use of this library is to use the plugin API and the profile API. The profile API provides an easy way to manage the sync profiles. Following is the main API available in this library - Logging - Profile management - Plugin management - A dbus library interface for client apps, like GUIs \subsection logging Using Logging API The library uses the following Qt logging categories: - buteo.core (for the library) - buteo.msyncd (for the msyncd process) - buteo.plugin (for the plugin runner) - buteo.trace (for function tracing) By default, logging is disabled. Logging can be enabled as per Qt logging features. For example, to enable msyncd logging, run it with the buteo.msyncd logging enabled: QT_LOGGING_RULES="buteo.plugin.msyncd.debug=true" /usr/bin/msyncd To enable debugging for all categories under buteo.plugin.*: QT_LOGGING_RULES="buteo.plugin.*.debug=true" /usr/bin/msyncd In addition, LogMacros.h has a FUNCTION_CALL_TRACE macro that can be used to trace the entry and exit of functions. \subsection profile-api Profile Management API Profile management API can be used to manage the profile information. For example, it can be used to retrive profiles, create profiles from a template, update profiles and save them back, fetch the results of the last sync, fetch the logs of the sync results etc. The main class that handle profile management are: - Buteo::ProfileManager, - Buteo::Profile, - Buteo::SyncProfile, - Buteo::SyncSchedule, - Buteo::SyncResults, - Buteo::StorageProfile, - Buteo::SyncLog \subsubsection update-profile Update a given profile Following is a code snippet to update a given profile \code using namespace Buteo; ... ... ProfileManager pm; SyncProfile *sp = pm.syncProfile(profileName); if (sp) { sp->setSyncDirection(SyncProfile::SYNC_DIRECTION_FROM_REMOTE); if (pm.updateProfile(sp).isEmpty()) { qDebug() << "Update failed for profile " << profileName; } else qDebug() << "Profile successfully updated"; } ... ... \endcode \subsubsection sync-schedule Handle sync schedule ProfileManager can be used to set the schedule of a sync session \code using namespace Buteo; ... ... ProfileManager pm; SyncProfile *sp = pm.syncProfile(profileName); if (sp) { SyncSchedule sched = sp->syncSchedule(); if (!sched.isEmpty()) { sched.setInterval(45); sched.setRushEnabled(false); DaySet days; days << Qt::Tuesday << Qt::Thursday << Qt::Sunday; sched.setDays(days); sched.setScheduleEnabled(true); sp->setSyncSchedule(sched); } else { SyncSchedule newSched; newSched.setInterval(15); newSched.setTime(new QDateTime()); newSched.setScheduleEnabled(true); sp->setSyncSchedule(newSched); } pm.updateProfile(sp); } \endcode */ buteo-syncfw-0.11.10/doc/src/introduction.dox000066400000000000000000001175711477124122200211060ustar00rootroot00000000000000/*! \page introduction Introduction to Synchronization Framework \section aboutsyncfw About Synchronization Framework The Buteo Synchronization framework provides a generic framework for creating sync plugins that can perform sync operations across devices, PC, Cloud. The framework provides the capability to manage the lifecycle of a sync plugin. It also provides scheduling, bearer sensing (USB, BT, Network), support for both client and server plugins and also storage plugins. The client plugin gets loaded for the instant of the sync session and gets unloaded thereafter. A server plugin stays alive until the sync engine is alive. The storage plugin provides a generic interface for fetching the objects, changed objects. The storage plugin can be used across multiple plugins and avoid duplication of code.

Contents

Introduction

Personal computing, cloud computing and mobile computing have increasingly made users to store their personal data in personal computers, Internet based cloud services and mobile devices. The kind of personal data varies from normal text files to a varied range of media data like pictures (JPEG, GIF, etc.), music files (mp3, wav, etc.), movie files (wmv, mp4, etc.), data file (.doc, .ppt, .pdf etc.) and non-file formats like vcard (format for contact card), vcal (format for calendar entry) which has contacts and calendar information. As the data gets more and more distributed, it become the more important to keep the data in sync among the various computing entities.Various protocols(SyncML, MTP, REST etc.) have evolved to keep the user data in sync among the entities.

These various protocols work over different kinds of transports (like Bluetooth, USB, IP etc.). So it is no more enough that a particular mobile device supports only a single protocol to keep the user data in sync. It also so happens that two different protocols are also used to synchronize the same kind of data (for example, SyncML and REST API can be used to synchronize calendar data). The choice of the protocol to use largely depends on the kind of target service from where the data is fetched. Also, the device UI has to provide a coherent view to the end-user without having to expose the internal protocols being used to keep the user data in sync. In order to handle the various synchronization protocols, it is important to have a framework that dynamically chooses the protocol to use for a particular sync session and also provide various kinds of platform based services to the sync session. The Buteo Synchronization Framework tries to create a unified architecture for any kind of synchronization protocol and also enables application developers to create a unified user interface to merge all the synchronization services in the device (here the device can be a PC, mobile device or any other computing device).

The Buteo SyncFW does not provide any synchronization capabilities by itself. It is only the plug-ins and the corresponding protocol stacks that provide the synchronization capabilities.

Note: The name Buteo is the Latin name for a Hawk (more from here: http://en.wikipedia.org/wiki/Buteo)

Requirements

Following diagram shows the end-user usecases for Buteo Sync solution.

\image html Buteo_Sync_Solution_-_End_User_Usecases.jpg

The high level end-application use-cases are taking into consideration while creating the architecture

  • Supporting synchronization of data between mobile devices
  • Synchronization of data between mobile devices and PC
  • Synchronization of data between mobile device and the cloud based services. The cloud based services are any internet based online services

The main usecases are to allow a 3'rd party synchronization application developer to create synchronization services and deploy to the framework and to provide various other features like deploy/undeploy synchronization plug-ins, develop storage plug-ins for already existing synchronization services etc.

Functional Requirements

  • The solution should provide a d-bus API for on device application to interact and use the framework features and properties
  • The framework should provide the end-user a means to initiate, abort, suspend/resume, getting status and logs of synchronization. These API methods would end up in the GUI functionality for the end-user
  • As a user of the solution, it should allow me to deploy a synchronization service on the fly
  • As a user of the solution, it should allow me to remove an already deployed synchronization service. Even after remvoal of all the deployed services, there should not be any affect in the working nature of the computing device
  • It should allow integration to the platform databases as pluggable components, so that support for the synchronization of databases can be added and removed on the fly
  • There should be a facility to schedule sychronization sessions. Scheduling is used as an automated mechanism to initiate synchronization with online services at periodic intervals of time without the end-user intervention
  • It should be possible to run multiple synchronization sessions in parallel
  • If it is not possible to run multiple synchronization sessions, then the requests should be queued and the requests should be executed in the order of the queued requests
  • There should be a mechanism to activate/de-activate synchronization based on the context. The context mainly is related to network availability, in which case, synchronization should be intiated when the network is enabled and available and should be disabled and scheduled for later incase the network is not available
  • In low battery conditions, synchronization should be disabled and should be rescheduled for a later session
  • It should be possible to backup the configuration files of the framework and restore it another machine/device. This requirement needs to be considered carefully, as it cannot be satisfied to the fullest extent
  • The framework should support synchronization with transport requirements for network based, PC based and device-device synchronization. The PC based synchronization could use sort range connectivity solutions like USB and Bluetooth
  • The architecture should support any kind of data and also different kinds of data. For example, it should support Contacts in Vcard format, but it should also be able to support Vcard versions 2.0, 2.1 and 3.0
  • Optional: should support "Push Based Sync"
  • Optional: Should support changed based synchronization

Performance Requirements

  • There should not be a theoritical limit on the number of synchronization services that the framework can support. But practically this could be limited by the resources available on the target device
  • Usage of the network resources on a device at random intervals of time could increase the number of times the processor is woken up, because of which the use-time of the battery could reduce drastically. For this reason, the framework should optimize the usage of the network through some mechanism available in the device

Non-Functional Requirements

  • The solution should be generic in nature and should not be tied to or depend on any synchronization protocol
  • The solution should be as much portal as possible across all Linux distributions (GNOME, KDE etc.)
  • The framework should provide backward compatibility so as to allow any synchronization services that are deployed with an older API
  • The solution should provide a simple API for 3'rd party developers
  • The framework should be implemented in a transport independent manner
  • It should be possible to change the end-user graphical user interface without the need to change the core engine. The architecture should be extensible and scalable for any kind of data to be synchronized and for any kind of protocol

Following diagram depicts some more usecases of the framework

\image html ButeoFW_Usecase_Diagram.jpg

Architecture Details

The Buteo Synchronization framework architecture is created taking into consideration the extensibility and scalability of the solution for current and future use cases. Effort is made to make use of the existing components as mush as possible.

Framework Components

Plug-in Manager

A plug-in based architecture is the logical choice to satisfy the requirement of the ability to deploy/undeploy sync services in the device. The plug-in manager forms the core concept of the Buteo SyncFW. Even though the component is named as plug-in manager, the component does not perform any handling of the plug-ins itself. It is the sync daemon that performs the actual functionality of loading/unloading of the plugins. These plugins could also be called as sync agents, since they handle the sync sessions by using the corresponding sync protocols.

In the synchronization world, typically, there are two kinds of sync services - one that acts as a client and initiates a sync towards a sync server; the typical scenario is where a device sync’s PIM data with an online service (like Ovi.com) and another that acts as a sync server that accepts incoming sync requests or one which has to have a persistent connection towards a sync service. In order to satisfy these two kinds of modes, two kinds of plug-ins are designed. One is a Client Plugin and another is a Server Plugin. A client plugin is loaded on demand either by a GUI application or through a scheduled sync session and is unloaded once is sync is completed. On the other hand, a server plugin is always loaded for accepting incoming sync requests and is never unloaded. In terms of interface methods, the only difference between these plugins would be a “listen†method, which the server plug-in would have. The client/server role decision is made at the time of writing of the plugin and is defined the plugin configuration file. Since the Buteo SyncFW is a generic architecture for MeeGo platform, which is not just targetted for mobile devices, the server functionality could as well be used for a netbook kind of usecase, where the sync service in the netbook acts as a server and the Buteo SyncFW acts as a client.

Another plugin that is defined is the storage plugin. Typically every sync service would involve synchronization of different kinds of user data, the formats of which are profoundly different. A sync protocol like OMA DS SyncML would be able to synchronize different kinds of data with different formats, which would mean that the framework should be able to handle synchronization of these different kinds of data formats. The concept of “Storage Plugin†is created to cater to this kind of usecases. Backend storage is defined as a component in the platform that holds the user personal data. Some examples are contacts, calendar, SMS, MMS, music files, audio files, photos etc. Storage plug-in would be loaded along with the corresponding client or server plug-in. The decision of which storage plug-in to load is made based on the profile information of the deployed service and also the protocol request for the storage. More information about a profile is described in the following sections. This kind of plug-in based mechanism for storage provides a scalable architecture where it is possible to provide many storage plug-ins for the same kind of backend storage. The following figure describes the context of the plugins and the plugin manager.

\image html Buteo_FW_-_Plugin_Manager.jpg

The main functionality of storage plug-in would be to obtain the raw data in the protocol message and perform a transformation of the data to the format suitable for the backend storage in the device. For example a SyncML message would provide the Contact information in the form of a vcard, while the storage in the device could be in the form of a SQLite database with an API layer. In this case, the storage plug-in would obtain the vcard from the protocol, use the API layer over the Contacts database and store the vcard entry to the database. A reverse mechanism would be used to retrieve the vcard from the storage and return it to the target sync entity.

Apart from the implementation interfaces, a plugin should also have a configuration file that defines the properties of the plugin, like, whether the plug-in should be visible or hidden to the user, the storage plugins that client/server plugin should use, the target service address, like the ovi sync URL etc. The plugin configuration file has only static information, which is read by the synchronization daemon and used accordingly. The configuration file could also provide extensions, which could specifically be used by the plug-in, but not the framework.

Profile Manager

Profile Management is quite important both from managing the deployed plug-ins as well as holds the information to be displayed to the end-user. From the end-user point of view, a profile is defined as something that provides information about the synchronization service that the user has synchronized with. A profile has only dynamic information that is created using the plug-in configuration and other information that is generated after a sync has been initiated. For example, if the user has synchronized his data with Ovi.com, then a profile entry would be created with information like the databases the sync session was initiated, time of sync, sync status etc. A profile is also used to display the sync status in a user-friendly manner in a GUI application.

The profile information is stored persistently, so that it could be used across sync sessions and also be available for any GUI application to query the sync status. The profile is the only object that moves across all the synchronization entities (daemon, GUI’s etc.). Some of the properties of a profile can be modified by the user and some properties are only modifiable by the framework. Some of the identified properties of a profile manager are as under:

  • Profile ID - a unique identifier generated by the framework using some of the fields of a profile that uniquely identify a profile (like profile name, target address etc.)
  • Profile name - is the name of the profile (need not be unique)
  • Transport type - the transport type used for the profile (USB/BT/HTTP/HTTPS/TCP). Note that a transport type can also be a combination of two transports. For example, when synchronizing with PC Suite, it is possible to use both BT and/or USB to synchronize with PC Suite
  • Sync Content - the databases that this profile is used to synchronize (like contacts, calendar, notes, music etc.)
  • Sync direction - the direction of synchronization (1-way send, 1-way receive, 2-way sync)
  • Synchronization status - the status that identi#es the last synchronization status. This inturn will have the following entities:
  • Last sync status (success/failed/cancelled)
  • Last sync time
  • Last sync log (sync item count)
  • Credentials - the username/password, if any used for synchronization with the service. These items are expected to be returned from Accounts application
  • Synchronization type - a flag to represent manual/automatic synchronization. When manual sync is set, the user has to manually initiate the sync. Incase of automatic sync, the framework will initiate sync as per the automatic sync frequency
  • Sync frequency - the frequency at which synchronization should happen (incase of automatic sync)

Scheduler

Another important component of Buteo SyncFW is the synchronization scheduler. The Scheduler is responsible for scheduling synchronization sessions and also for handling parallel synchronizations. It has a queue mechanism to queue the incoming sync requests. The periodic scheduling of the Sync sessions is handled by using an alarm kind of functionality. The frequency is set by the end-user through a well-known GUI interface. The frequency is defined as a repeat event like everyday, a particular day every week, the hour setting etc. This event is converted to an alarm event and the alarm is triggered whenever the timer expired.

\image html Buteo_SyncFW_-_Sync_Scheduler.jpg

One of the main properties of a sync protocol is to provide data consistency. Even though the backend datastores may provide data consistency through database ACID properties simultaneous synchronization sessions might result in inconsistent results in the end-user data. For example, if sync session1 adds 10 data elements, and a simultaneous sync session2 might result in the addition of the same set of data from a different source, resulting in duplication of data. The framework provides a functionality, wherein simultaneous sync requests that access the same backend datastore are queued and are executed in the first-in-first-out order. In the above example, the two sync sessions are queued and are executed one-after-the-other. More details on how this is achieved is explained in the low-level design.

Accounts Integration

Accounts&SSO (http://wiki.meego.com/Single_sign-on) is a component in MeeGo platform that provides a one-stop shop for the end-users to configure online accounts. Examples of online acconts are Ovi, Google etc. Most of the online services have Single Sign On enabled which enables the user to enter the credentials only once and be able to access all the services (like Ovi Music, Ovi Sync, Ovi Sharing etc.) without having to re-enter the credentials again and again. The Accounts&SSO component on the device is the counterpart of the online service accounts management. This subsystem also stores the user credentials in a secure location which is not readable by a non-root user. Since the Buteo SyncFW supports synchronization of user data with online services, it needs to be integrated to the Accounts&SSO subsystem to fetch the SSO token and provide it to the online sync service.

The Accounts&SSO subsystem provides a pluggable interface to hook-in a new online account service. The pluggable component uses a XML file definition similar to Buteo FW. The Buteo SyncFW uses the account identifier defined in the accounts XML definition to link the sync plug-in with the corresponding account. The plug-in developer is also given the option to not use to the Accounts&SSO subsystem, but rather provide the credentials in the service XML.

Synchronization Daemon

The synchronization daemon is the only always running process in the sync framework. It provides the functionality of loading/unloading the plug-ins, sending d-bus signals, handle profile management (like creating profiles, deleting them etc.) integration with Accounts, hooking up with the hardware layer to register for interested signals (like bluetooth availability, USB and network connectivity etc). The daemon would also load the server plug-ins which would allow external devices to connect to the sync service in the device and perform synchronization. Following is the component context diagram of synchronization daemon depicting the various components that it interacts with. The synchronization daemon is the central component in the framework and is responsible for managing the various states of the framework.

\image html Buteo_SyncFW_-_Component_View.jpg

In the above figure, the boxes in blue represent the Buteo SyncFW components and the boxes in yellow represent the components in MeeGo. The framework provides handlers for each of the external component services that it uses. The main functionality of the daemon is:

  • to maintain the state machine of the framework
  • to load/unload synchronization plugins
  • to initialize the adapters towards external components and maintain the interaction with external components (mainly the interaction is over d-bus)
  • to make available the d-bus API of the framework

Following is the state machine diagram of the framework, of which the daemon forms the central component Following are the various states and activities that occur in the synchronization framework:

\image html Synchronization_Framework_Activity_Diagram.jpg

  • The daemon gets started (by systemd) during the startup of the device. Once the necessary initialization steps are done (signal handlers and so on), the daemon checks the DB to see if there is a need to load any server plug-ins. If there are any server plug-ins to be loaded, the daemon loads them in a separate thread and goes into an idle state.
  • If the daemon receives client initiated sync, it checks to see if there is an ongoing sync. If there is, it puts the sync request in queue; else it loads the client sync plug-in in a separate thread. Once the client plug-in finishes sync, it releases its resources and sends a signal to the daemon to unload the plug-in.
  • If the daemon receives a schedule sync alert from alarm daemon, it takes the same sequence of actions as of initiating a sync by the user from UI.
  • Whenever the user activates an account from the Accounts UI, a signal is sent to the daemon, whereby it activates the sync account in the sync UI Note that it might be possible that the daemon has to unload and load a sync plug-in for a sync request in queue to perform the same synchronization. This would be not good for performance. While implementation, this should be taken into consideration and some sort of heuristics should be used to avoid unloading/loading of plug-ins.

Developer API

As part of the framework, the solution provides an API for 3'rd party developers to create plug-ins for the MeeGo platform. The framework provides two kinds of APIs:

  • A plugin API that allows developers to create new synchronization plugins. A client-side DLL API is provided for anyone not willing to understand d-bus
  • A d-bus API that any application can use to interact with the framework (the API typically includes methods to start sync, abort sync, get sync status etc.). For application that deal with profile data, a client-side API is provided using which the clients can handle the communication with the sync engine.

Hardware Hookup

For most of the synchronization services, it is important to initiate the sync on the availability of the underlying transport. For example, when the device is connected to a PC via USB, when bluetooth is switched on in the device, on the availability of the network (wifi/GPRS/3G..) etc. In order to support these usecases, the framework hooks into the hardware notifications services in the device, like Context Framework, Hardware Abstraction Layer (HAL) etc. The Context Framework provides context aware information like Bluetooth availability, network availability etc. The USB connectivity is obtained using HAL. Whenever these events happen, the hardware hookup adapter notifies the daemon to take the appropriate action. The daemon checks all the plugin profiles that have registered to be invoked whenever a particular transport is available and loads those plugins. Once loaded, the plug-ins check if the transport suits their requirements, perform their work and exit, after which they are unloaded by the daemon. There could be some server plugins that are always loaded as long as the underlying transport is available.

Device State Based Sync

The device state based sync is mainly related to handling of the scenarios where sync cannot continue because of low-device-resource conditions. Examples of low resource conditions are less disk space, low memory, low-battery level etc. The framework makes use of Context framework to fetch information related to some of the device state variables, like battery level, but for other conditions like disk space, low memory the native Linux methods should be used. Details are available in the low-level design chapter. Note that if the sync is initiated from the UI, most of these checks should be done in the UI itself to avoid round-trip checks by the engine. This is a performance improvement measure

Change Based Sync

Another important feature of any sync solution is to initiate sync whenever the user changes any of his/her personal data. The general term for this is change based sync. There could be two methodologies used to support this kind of feature:

1.If the backend datastores have the ability to send notification signals whenever the database is modified. But it cannot be expected that each datastore supports the notification mechanism. This leads to the next possible option 2.The synchronization daemon polls the various databases at periodic intervals of time to know if some changes have been done to the data- stores. If this methodology is chosen, the timing of the interval becomes quite important. In this methodology, the sync cannot be real-time, since if:

   the change in the datastore happens at t0, and
   the periodic datastore check interval is at t1, then
   the sync would occur only after every n*t1, where n is the periodicity of the sync

This is a reasonable limitation, since the user might not expect a real-time sync operation. One of the possibilities to implement this functionality is to provide a plug-in that would be loaded in a periodic interval and which would be able to check the backend datastores for any changes. The scheduling functionality of the sync can be used to periodically load this plugin. This design avoids the sync framework to directly access the backend datastores.

Handling Deleted item list

Most of the synchronization protocols support a feature called “fast sync†[Ref: TBD] wherein the consecutive sync’s after the first sync only synchronize the changes from the previous synchronization. The changes include the data that was added/modified/deleted. The protocol implementations make use of the backend datastores capability to return the list of added/modified and deleted items. Though most of the datastores are able to provide information related to added and modified items, they do not keep track of the deleted items and purge them for good. In such cases, handling the list of deleted items becomes the job of the synchronization service.

Since this is a feature that is required by most of the storage plug-ins, the framework could as well provide an interface which the storage plug-ins would implement and store a list of deleted items. Couple of mechanisms exists to keep track of the deleted items:

  • Keep track of deleted items for a particular datastore. This involves maintaining a list of previous list against the current and finding the difference between these two
  • Maintain a complete list of the items and during every sync session, find the difference between the sync identifier map against the backend datastore id maps. The difference, if any is the list of deleted items. Note that only the ids of the items are stored and not the complete items

Profile Deletion

Apart from a profile being created whenever the user enables the account or synchronizes with a target device, at some points of time, it could also be deleted by the user. Any profile specific data in the sync framework could be handled by the framework itself, but most of the times, the protocol stacks and the plugins also maintain the corresponding profile specific data. For example, the SyncML stack maintains the anchor information and the id mappings, which have to be purged when the corresponding profile is deleted. The SyncFW cannot directly delete this information from the protocol stack, since the data is protocol specific. A good solution to handle this case is to provide a plug-in interace method (say “cleanupâ€), which every plug-in has to implement to cleanup the plug-in and stack specific data. In order to invoke this method, whenever the user performs a “delete†operation, the plug-in is loaded and the “cleanup†method is invoked. This is a clean approach to handle the plug-in specific operations and shows the strength of the extendability of the plug-in method.

Framework Client Library

The framework provides a d-bus API [Developer API] that the client applications can use to interact with the synchronization daemon. This works out as long as the just use simple d-bus API methods like “startSyncâ€, “stopSync†etc., but for complex API methods like “fetchProfileâ€, “getSyncLogs†the UI clients have to unmarshall the complex d-bus message, parse the message and then take an appropriate action. The more then number of clients towards the framework, the more code replication there is. In order to avoid this, it makes more sense to provide a synchronization framework client library, which the applications can use to interact with the framework. The framework would be responsible for performing the task of un-marshalling the d-bus message, parsing the message and then signal the UI component to fetch the result. More detailed information is provided in further sections.

System Context

Following is the system context diagram

\image html Buteo-SyncFW-SystemContext.jpg

  • Accounts & SSO subsystem (for accounts integration) that belongs to the “Personal Services†layer
  • Bluetooth (for Bluetooth related information) that belongs to the “Comms Services†layer
  • Qt Core (for using the Qt core API classes) (QtAPI) that belongs to the visual services
  • The d-bus messaging services (for providing the d-bus services) (FreedesktopDbus) that belongs to the “kernel†layer
  • Interfaces

    Interfaces Used

    Interface Name Interface Type Description
    libaccounts Dynamic link library The Accounts&SSO library that provides the functionality of accounts registration
    libdbus Dynamic link library The d-bus library that is used to use as well as provide the d-bus interface for the framework
    libbluetooth d-bus The BlueZ d-bus API is used to get notifications about availability of bluetooth

    Interfaces Provided

    Interface Name Interface Type Description
    libbuteosyncfw Dynamic link library The library provides the framework functionality of Buteo. The API provided by this library includes support for writing plugins, managing profiles and obtaining sync results.
    Framework d-bus API d-bus Apart from the library libbuteosyncfw, this d-bus API is for applications to interact with the synchronization daemon. An application using this API will have to understand d-bus and unmarshall and parse the d-bus messages itself.
    Profile XML API XML file A plug-in could be deployed to the framework with a default profile XML file. The format of the profile XML file also forms as part of the API
    \section support-oop-plugins Support for out of process plugins Buteo syncfw version 0.1.0 supported only dynamic link library plugins which the framework loads into the same process memory as msyncd. This architecture has a problem that if any one of the plugin misbehaves (crashes, for example), msyncd would also crash and there is a probability that it would never recover. To avoid such situations, an out of process plugin architecture was devised and implemented. In this architecture, each of the plugins would be running as separate processes and msyncd process would interact with each of the processes to handle the sync life cycle and operations. From Buteo syncfw version 0.10.0, the plugin architecture uses Qt Plugins (https://doc.qt.io/qt-5/plugins-howto.html). Plugin files installed in /usr/lib/buteo-plugins-qt5 are launched in-process; plugin files installed in /usr/lib/buteo-plugins-qt5/oopp are launched out-of-process using the buteo-oopp-runner binary. Plugins should publicly inherit from Buteo::ClientPlugin or Buteo::ServerPlugin. In dll based plugins, the .so file is opened using dlsym and the binary is loaded into memory and then the ClientPlugin object is created. In this mechanism, the plugin process is started and then the OOPClientPlugin object is created, which then talks to the process plugin over d-bus. D-bus with its inbuilt capability to marshall data, invoke signals/slots, seamless API is chosen as the communication mechanism between the plugins and msyncd. A dbus interface is created for communication between msyncd and the plugins. This interface is same as the interface provided by both ClientPlugin and ServerPlugin. Using the xml interface description (common for both client and server plugin), dbus client proxy and server adaptor code is generated (using qdbusxml2cpp tool). For facilitating that none of the plugin code changes and also to commonalize the code across all the plugins, the class PluginServiceObj (that implmenets the dbus skeleton adaptor class) is created. This class ensures that none of the existing plugin code has to be changed and also performs any transformation of the data between msyncd and the plugins (over dbus). This class is a peer class of OOPClientPlugin and OOPServerPlugin. Apart from this class, the plugin_main.cpp provides the main() function required to generate an executable binary. This class takes the plugin name and the profile name as arguments and then initializes the PluginServiceObj and registers it as a dbus service. All signals emitted by the plugin (and msyncd) are automatically relayed by Qt dbus. The life cycle of a process plugin is similar to that of a dll plugin. */ buteo-syncfw-0.11.10/doc/src/mainpage.dox000066400000000000000000000013721477124122200201350ustar00rootroot00000000000000/*! \mainpage
    Getting Started API Reference Developer Resources
    */ buteo-syncfw-0.11.10/doc/src/plugin-guide.dox000066400000000000000000000065141477124122200207500ustar00rootroot00000000000000/*! \page plugin-guide Writing plugins for Synchronization Framework \tableofcontents \section kinds-of-plugins Kinds of plugins that Buteo supports -# Client plugins Plugins which act as clients to services running outside the machine (device/PC/ tablet) domain. These plugins are loaded whenever there is a need for starting sync and are unloaded once done. More than one client plugin can be loaded at any point of time, provided that they deal with different kinds of storages (contacts, calendar, notes etc.) -# Server plugins Server plugins are alive forever and act as server for clients that would like to connect to the server. A typical example of a server plugin is a SyncML server plugin that acts as a server for an external client -# Storage plugins A storage plugin enables a generic API for accessing the data from the device databases, like Contacts, File system, Calendar etc. These plugins can then be used across multiple protocols like Dropbox, Google services, Caldav etc. \section create-plugin Creating a plugin See the DummyClient and DummyServer classes in unittests/dummyplugins for plugin examples. \section libsyncml Device sync with libsyncml libsyncml is a desktop based sync engine that support sync using SyncML protocol URL: https://libsyncml.opensync.org/ It comes with a cmdline tool syncml-ds-tool to perform sync. Following instructions help in performing the sync over bluetooth and USB Buteo works with libsyncml over USB as well as bluetooth. These instructions are w.r.t the sailfish OS, but as and when more Linux based OS's are supported, these instructions will be extended \subsection usb-sync Sync over USB -# Connect the Sailfish based device to your PC over USB. -# Select the MTP mode when asked for a USB mode selection -# From the PC (Ubuntu or a version supporting libsyncml), install libsyncml-utils (in debian, 'sudo apt-get install libsyncml-utils) -# Run the following sync command for USB: \code sudo syncml-ds-tool -u 1 --identifier "PC Suite" --sync text/x-vcard Contacts \endcode Note: The command has to run as 'sudo', else you will have to add the USB device into your udev list -# The above command should fetch all the contacts in the Sailfish phone to your PC and dump the output to the screen. For more options of syncml-ds-tool, look at its help or the libsyncml website \subsection bluetooth-sync Sync over bluetooth -# Pair your bluetooth enabled PC/laptop with Sailfish based device. Also enable the bluetooth options of "connect automatically" and "trust" -# Find out the bluetooth address of the sailfish based device by running the hcitool. \code hcitool scan \endcode -# Run the following sync command: \code syncml-ds-tool -b 26 --identifier "PC Suite" --sync text/x-vcard Contacts \endcode 26 is the channel number of the SyncML bluetooth profile Replace with the bluetooth address of your sailfish device (something like B4:EE:D4:F6:19:E7) -# The above command should fetch all the contacts in the Sailfish device to your PC and dump the output to the screen. \subsection sailfish-N9-contacts Synchronization of Contacts between Sailfish and N9 -# In N9, go to Settings -> Sync & Backup -> Sync -> Add new sync target (+ below) -# Choose the Sailfish device in the list of bluetooth devices -# Start sync */ buteo-syncfw-0.11.10/libbuteosyncfw/000077500000000000000000000000001477124122200173405ustar00rootroot00000000000000buteo-syncfw-0.11.10/libbuteosyncfw/clientfw/000077500000000000000000000000001477124122200211535ustar00rootroot00000000000000buteo-syncfw-0.11.10/libbuteosyncfw/clientfw/SyncClientInterface.cpp000066400000000000000000000102341477124122200255530ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "SyncClientInterface.h" #include "SyncClientInterfacePrivate.h" #include using namespace Buteo; QSharedPointer SyncClientInterface::sharedInstance() { static QWeakPointer sharedObj; QSharedPointer obj = sharedObj.toStrongRef(); if (!obj) { obj = QSharedPointer(new SyncClientInterface); sharedObj = obj; } return obj; } SyncClientInterface::SyncClientInterface(): d_ptr(new SyncClientInterfacePrivate(this)) { } SyncClientInterface::~SyncClientInterface() { delete d_ptr; d_ptr = nullptr; } bool SyncClientInterface::startSync(const QString &aProfileId) const { return d_ptr->startSync(aProfileId); } QDBusPendingCallWatcher* SyncClientInterface::requestSync(const QString &aProfileId, QObject *aParent) const { return d_ptr->requestSync(aProfileId, aParent); } void SyncClientInterface::abortSync(const QString &aProfileId) const { d_ptr->abortSync(aProfileId); } QStringList SyncClientInterface::getRunningSyncList() const { return d_ptr->getRunningSyncList(); } QDBusPendingCallWatcher* SyncClientInterface::requestRunningSyncList(QObject *aParent) const { return d_ptr->requestRunningSyncList(aParent); } bool SyncClientInterface::removeProfile(const QString &aProfileId) const { return d_ptr->removeProfile(aProfileId); } bool SyncClientInterface::updateProfile(const Buteo::SyncProfile &aProfile) { return d_ptr->updateProfile(aProfile); } bool SyncClientInterface::setSyncSchedule(const QString &aProfileId, const SyncSchedule &aSchedule) { return d_ptr->setSyncSchedule(aProfileId, aSchedule); } bool SyncClientInterface::saveSyncResults(const QString &aProfileId, const Buteo::SyncResults &aSyncResults) { return d_ptr->saveSyncResults(aProfileId, aSyncResults); } bool SyncClientInterface::getBackUpRestoreState() { return d_ptr->getBackUpRestoreState(); } bool SyncClientInterface::isValid() const { return d_ptr->isValid(); } Buteo::SyncResults SyncClientInterface::getLastSyncResult(const QString &aProfileId) { return d_ptr->getLastSyncResult(aProfileId); } QList SyncClientInterface::allVisibleSyncProfiles() { return d_ptr->allVisibleSyncProfiles(); } QDBusPendingCallWatcher* SyncClientInterface::requestAllVisibleSyncProfiles(QObject *aParent) const { return d_ptr->requestAllVisibleSyncProfiles(aParent); } QString SyncClientInterface::syncProfile(const QString &aProfileId) { return d_ptr->syncProfile(aProfileId); } QStringList SyncClientInterface::syncProfilesByKey(const QString &aKey, const QString &aValue) { return d_ptr->syncProfilesByKey(aKey, aValue); } QDBusPendingCallWatcher* SyncClientInterface::requestSyncProfilesByKey(const QString &aKey, const QString &aValue, QObject *aParent) const { return d_ptr->requestSyncProfilesByKey(aKey, aValue, aParent); } QStringList SyncClientInterface::syncProfilesByType(const QString &aType) { return d_ptr->syncProfilesByType(aType); } QStringList SyncClientInterface::profilesByType(const QString &aType) { return d_ptr->profilesByType(aType); } QDBusPendingCallWatcher* SyncClientInterface::requestProfilesByType(const QString &aType, QObject *aParent) const { return d_ptr->requestProfilesByType(aType, aParent); } buteo-syncfw-0.11.10/libbuteosyncfw/clientfw/SyncClientInterface.h000066400000000000000000000316371477124122200252320ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCCLIENTINTERFACE_H #define SYNCCLIENTINTERFACE_H #include #include #include #include #include #include #include class QDBusPendingCallWatcher; namespace Buteo { class SyncClientInterfacePrivate; /*! \brief * SyncInterface Class - Main Entry Point for SyncFW Clients * * This Class Provides Interface API towards SyncFW. * This Class has Methods to start and abort a sync , set schedule for a profile * and use the fw functionality to automatically schedule it, get a list of * running syncs( to display in UI ) and get status of the synchronization. * It also has Signals that the clients can connect to know the last sync results , * to keep track of profile changes by SyncFw. */ class SyncClientInterface: public QObject { Q_OBJECT public: /*! * \brief Constructor */ SyncClientInterface(); /*! * \brief Destructor */ ~SyncClientInterface(); /*! * \brief Requests to starts synchronizing using a profile Id * * A status change signal (QUEUED, STARTED or ERROR) will be sent by the * daemon when the request is processed. If there is a sync already in * progress using the same resources that are needed by the given profile, * adds the sync request to a sync queue. Otherwise a sync session is * started immediately. * * \param aProfileId Id of the profile to use in sync. * \return True if a profile with the Id was found. Otherwise * false and no status change signals will follow from this request. */ bool startSync(const QString &aProfileId) const; /*! * \brief asynchronous version of startSync() * * \param aProfileId Id of the profile to use in sync. * \param aParent set the parent of the returned QDBusPendingCallWatcher. * \return a newly created watcher on a QDBusPendingReply. */ QDBusPendingCallWatcher* requestSync(const QString &aProfileId, QObject *aParent = nullptr) const; /*! * \brief Stops synchronizing the profile with the given Id. * * If the sync request is still in queue and not yet started, the queue * entry is removed. * * \param aProfileId Id of the profile to stop syncing. */ void abortSync(const QString &aProfileId) const; /*! * \brief Gets the list of profile names of currently running syncs. * * \return Profile name list. */ QStringList getRunningSyncList() const; /*! * \brief asynchronous version of getRunningSyncList() * * \param aParent set the parent of the returned QDBusPendingCallWatcher. * \return a newly created watcher on a QDBusPendingReply. */ QDBusPendingCallWatcher* requestRunningSyncList(QObject *aParent = nullptr) const; /*! * \brief Sets Sync Schedule to the profile * * This function does the following * 1. sets the sync type of the profile to schedule * 2. Adds the schedule to the profile * 3. saves the profile. * 4. schedules the synchronization , so that sync automatically starts next time * * \return status of the operation */ bool setSyncSchedule(const QString &aProfileId, const SyncSchedule &aSchedule); /*! * \brief Save SyncResults to log.xml file. * \param aProfileId to save result in corresponding file. * \param aSyncResults to save in the \code .log.xml \endcode * \return status of the saveSyncResults */ bool saveSyncResults(const QString &aProfileId, const Buteo::SyncResults &aSyncResults); /*! * \brief This function should be called when sync profile has to be deleted * * \param aProfileId Id of the profile to be deleted. * \return status of the remove operation */ bool removeProfile(const QString &aProfileId) const; /*! * \brief This function should be called when sync profile information has * been changed by the client * * \param aSyncProfile Modified Profile Object.If same profile already exists it * will be overwritten with the changes from this object. * \return status of the update operation * */ bool updateProfile(const Buteo::SyncProfile &aSyncProfile); /*! * \brief This function returns true if backup/restore in progress else * false. */ bool getBackUpRestoreState(); /*! * \brief Use this function to understand if the dbus connection to msyncd * is working or not. * \return - status of the dbus object created for msyncd */ bool isValid() const; /*! \brief To get lastSyncResult. * \param aProfileId * \return SyncResults of syncLastResult. */ Buteo::SyncResults getLastSyncResult(const QString &aProfileId); /*! \brief Gets all visible sync profiles. * * Returns all sync profiles that should be visible in sync ui. A profile * is visible if it has not been explicitly set as hidden. * \return The list of sync profiles. */ QList allVisibleSyncProfiles(); /*! * \brief asynchronous version of allVisibleSyncProfiles() * * \param aParent set the parent of the returned QDBusPendingCallWatcher. * \return a newly created watcher on a QDBusPendingReply. */ QDBusPendingCallWatcher* requestAllVisibleSyncProfiles(QObject *aParent = nullptr) const; /*! \brief Gets a sync profile. * * Loads and merges also all sub-profiles that are referenced from the * main profile. Loads the log of finished synchronization sessions with * this profile. * \param aProfileId Name of the profile to get. * \return The sync profile as Xml string. */ QString syncProfile(const QString &aProfileId); /*! \brief Gets a sync profiles which matches the key-value. * * Loads and merges also all sub-profiles that are referenced from the * main profile. Loads the log of finished synchronization sessions with * this profile. * \param aKey Key to match for profile. * \param aValue Value to match for profile. * \return The sync profiles as Xml string list. */ QStringList syncProfilesByKey(const QString &aKey, const QString &aValue); /*! \brief asynchronous version of syncProfilesByKey(). * * \param aKey Key to match for profile. * \param aValue Value to match for profile. * \param aParent set the parent of the returned QDBusPendingCallWatcher. * \return a newly created watcher on a QDBusPendingReply. */ QDBusPendingCallWatcher* requestSyncProfilesByKey(const QString &aKey, const QString &aValue, QObject *aParent = nullptr) const; /*! \brief Gets profiles matching the profile type. * * \param aType Type of the profile service/storage/sync. * \return The profiles as Xml string list. */ QStringList profilesByType(const QString &aType); /*! * \brief asynchronous version of profilesByType() * * \param aType Type of the profile service/storage/sync. * \param aParent set the parent of the returned QDBusPendingCallWatcher. * \return a newly created watcher on a QDBusPendingReply. */ QDBusPendingCallWatcher* requestProfilesByType(const QString &aProfileId, QObject *aParent = nullptr) const; /*! \brief Gets a profiles matching the profile type. * * \param aType Type of the profile service/storage/sync. * \return The sync profile ids as string list. */ QStringList syncProfilesByType(const QString &aType); /*! * \brief creates a process singleton * * Use this instance to share a same D-Bus connection to the daemon * between several clients in a process. * \return a singleton instance, don't free it. */ static QSharedPointer sharedInstance(); signals: /*! \brief Notifies when the synchronisation service is available or not. * * This signal is sent when the synchronisation daemon is rnning or not. */ void isValidChanged(); /*! \brief Notifies about Backup start. * * This signal is sent when the backup framework is backing the sync related * data */ void backupInProgress (); /*! \brief Notifies about Backup done. * * This signal is sent when the backup framework has completed backing the sync related * data. */ void backupDone(); /*! \brief Notifies about Restore start. * * This signal is sent when the backup framework is restoring the sync related * data */ void restoreInProgress(); /*! \brief Notifies about Restore Done. * * This signal is sent when the backup framework has restored the sync related * data */ void restoreDone(); /*! \brief Notifies about a change in profile. * * This signal is sent when the profile data is modified or when a profile * is added or deleted in msyncd. * \param aProfileId Id of the changed profile. * \param aChangeType * 0 (ADDITION): Profile was added. * 1 (MODIFICATION): Profile was modified. * 2 (DELETION): Profile was deleted. * \param aChangedProfile changed sync profie as XMl string. * */ void profileChanged(QString aProfileId, int aChangeType, QString aChangedProfile); /*! \brief Notifies about the results of a recent sync for a profile * * This signal is sent after the sync has completed for a profile. * \param aProfileId Id of the changed profile. * \param aResults - Results of the sync * */ void resultsAvailable(QString aProfileId, Buteo::SyncResults aResults); /*! * \brief Notifies about a change in synchronization status. * * \param aProfileId Id of the profile used in the sync session whose * status has changed. * \param aStatus The new status. One of the following: * 0 (QUEUED): Sync request has been queued or was already in the * queue when sync start was requested. * 1 (STARTED): Sync session has been started. * 2 (PROGRESS): Sync session is progressing. * 3 (ERROR): Sync session has encountered an error and has been stopped, * or the session could not be started at all. * 4 (DONE): Sync session was successfully completed. * 5 (ABORTED): Sync session was aborted. * Statuses 3-5 are final, no more status changes will be sent from the * same sync session. * \param aMessage A message describing the status change in detail. This * can for example be shown to the user or written to a log * \param aStatusDetails * When aStatus is ERROR, this parameter contains a specific error code. * When aStatus is PROGRESS, this parameter contains more details about the progress * \see SyncCommonDefs::SyncProgressDetails */ void syncStatus(QString aProfileId, int aStatus, QString aMessage, int aStatusDetails); /*! \brief Notifies about progress in transferring items * * \param aProfileId Id of the profile where progress has occurred * \param aTransferDatabase Database to which transfer was made. One of the following: * 0 (LOCAL_DATABASE): Transfer was made from remote database to local database * 1 (REMOTE_DATABASE): Transfer was made from local database to remote database * \param aTransferType Type of transfer that was made. One of the following: * 0 (ADDITION): Addition was made to database * 1 (MODIFICATION): Modification was made to database * 2 (DELETION): Deletion was made to database * 3 (ERROR): Addition/Modification/Deletion was attempted, but it failed * \param aMimeType Mime type of the processed item * \param aCommittedItems No. of items committed for this operation */ void transferProgress(QString aProfileId, int aTransferDatabase, int aTransferType, QString aMimeType, int aCommittedItems); private: SyncClientInterfacePrivate *d_ptr; }; }; #endif buteo-syncfw-0.11.10/libbuteosyncfw/clientfw/SyncClientInterfacePrivate.cpp000066400000000000000000000246361477124122200271210ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include #include #include #include #include #include #include "SyncClientInterfacePrivate.h" #include "SyncClientInterface.h" #include "LogMacros.h" using namespace Buteo; static const QString SYNC_DBUS_OBJECT = "/synchronizer"; static const QString SYNC_DBUS_SERVICE = "com.meego.msyncd"; SyncClientInterfacePrivate::SyncClientInterfacePrivate(SyncClientInterface *aParent) : iParent(aParent) { FUNCTION_CALL_TRACE(lcButeoTrace); iServiceWatcher.addWatchedService(SYNC_DBUS_SERVICE); iServiceWatcher.setConnection(QDBusConnection::sessionBus()); connect(&iServiceWatcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &SyncClientInterfacePrivate::onServiceOwnerChanged); iSyncDaemon = new SyncDaemonProxy(SYNC_DBUS_SERVICE, SYNC_DBUS_OBJECT, QDBusConnection::sessionBus(), this); connect(iSyncDaemon, SIGNAL(signalProfileChanged(QString, int, QString)), this, SLOT(slotProfileChanged(QString, int, QString))); connect(iSyncDaemon, SIGNAL(resultsAvailable(QString, QString)), this, SLOT(resultsAvailable(QString, QString))); connect(this, SIGNAL(profileChanged(QString, int, QString)), iParent, SIGNAL(profileChanged(QString, int, QString))); connect(this, SIGNAL(resultsAvailable(QString, Buteo::SyncResults)), iParent, SIGNAL(resultsAvailable(QString, Buteo::SyncResults))); connect (iSyncDaemon, SIGNAL(syncStatus(QString, int, QString, int)), iParent, SIGNAL(syncStatus(QString, int, QString, int))); connect(iSyncDaemon, SIGNAL(transferProgress(QString, int, int, QString, int)), iParent, SIGNAL(transferProgress(QString, int, int, QString, int))); connect(iSyncDaemon, SIGNAL(backupInProgress()), iParent, SIGNAL(backupInProgress())); connect(iSyncDaemon, SIGNAL(backupDone()), iParent, SIGNAL(backupDone())); connect(iSyncDaemon, SIGNAL(restoreInProgress()), iParent, SIGNAL(restoreInProgress())); connect(iSyncDaemon, SIGNAL(restoreDone()), iParent, SIGNAL(restoreDone())); qRegisterMetaType("Buteo::Profile"); qRegisterMetaType("Buteo::SyncResults"); } SyncClientInterfacePrivate::~SyncClientInterfacePrivate() { FUNCTION_CALL_TRACE(lcButeoTrace); delete iSyncDaemon; iSyncDaemon = nullptr; } void SyncClientInterfacePrivate::onServiceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner) { Q_UNUSED(serviceName); Q_UNUSED(oldOwner); Q_UNUSED(newOwner); emit iParent->isValidChanged(); } bool SyncClientInterfacePrivate::startSync(const QString &aProfileId) const { FUNCTION_CALL_TRACE(lcButeoTrace); bool syncStatus = false; if (iSyncDaemon && !aProfileId.isEmpty()) { syncStatus = iSyncDaemon->startSync(aProfileId); } return syncStatus; } QDBusPendingCallWatcher* SyncClientInterfacePrivate::requestSync(const QString &aProfileId, QObject *aParent) { FUNCTION_CALL_TRACE(lcButeoTrace); return new QDBusPendingCallWatcher(iSyncDaemon->startSync(aProfileId), aParent ? aParent : this); } void SyncClientInterfacePrivate::abortSync(const QString &aProfileId) const { FUNCTION_CALL_TRACE(lcButeoTrace); if (iSyncDaemon && !aProfileId.isEmpty()) { iSyncDaemon->abortSync(aProfileId); } } QStringList SyncClientInterfacePrivate::getRunningSyncList() { FUNCTION_CALL_TRACE(lcButeoTrace); QStringList runningSyncList; if (iSyncDaemon) { runningSyncList = iSyncDaemon->runningSyncs(); } return runningSyncList; } QDBusPendingCallWatcher* SyncClientInterfacePrivate::requestRunningSyncList(QObject *aParent) { FUNCTION_CALL_TRACE(lcButeoTrace); return new QDBusPendingCallWatcher(iSyncDaemon->runningSyncs(), aParent ? aParent : this); } bool SyncClientInterfacePrivate::removeProfile(const QString &aProfileId) { FUNCTION_CALL_TRACE(lcButeoTrace); bool status = false; if (iSyncDaemon) { status = iSyncDaemon->removeProfile(aProfileId); } return status; } bool SyncClientInterfacePrivate::updateProfile(const Buteo::SyncProfile &aProfile) { FUNCTION_CALL_TRACE(lcButeoTrace); bool status = false; if (iSyncDaemon) { QString profileAsXmlString = aProfile.toString(); status = iSyncDaemon->updateProfile(profileAsXmlString); } return status; } void SyncClientInterfacePrivate::slotProfileChanged(QString aProfileId, int aChangeType, QString aProfileAsXml) { FUNCTION_CALL_TRACE(lcButeoTrace); emit profileChanged(aProfileId, aChangeType, aProfileAsXml); } void SyncClientInterfacePrivate::resultsAvailable(QString aProfileId, QString aLastResultsAsXml) { FUNCTION_CALL_TRACE(lcButeoTrace); QDomDocument doc; if (doc.setContent(aLastResultsAsXml, true)) { Buteo::SyncResults results(doc.documentElement()); emit resultsAvailable(aProfileId, results); } else { qCDebug(lcButeoCore) << "Invalid Profile Xml Received from msyncd"; } } bool SyncClientInterfacePrivate::setSyncSchedule(const QString &aProfileId, const SyncSchedule &aSchedule) { FUNCTION_CALL_TRACE(lcButeoTrace); bool status = false; if (iSyncDaemon) { QString scheduleAsXmlString = aSchedule.toString(); if (!scheduleAsXmlString.isEmpty()) { status = iSyncDaemon->setSyncSchedule(aProfileId, scheduleAsXmlString); } } return status; } bool SyncClientInterfacePrivate::saveSyncResults(const QString &aProfileId, const Buteo::SyncResults &aSyncResults) { FUNCTION_CALL_TRACE(lcButeoTrace); bool status = false; if (iSyncDaemon) { QString resultsAsXmlString = aSyncResults.toString(); if (!resultsAsXmlString.isEmpty()) { status = iSyncDaemon->saveSyncResults(aProfileId, resultsAsXmlString); } } return status; } bool SyncClientInterfacePrivate::getBackUpRestoreState() { FUNCTION_CALL_TRACE(lcButeoTrace); bool status = false; if (iSyncDaemon) { status = iSyncDaemon->getBackUpRestoreState(); } return status; } bool SyncClientInterfacePrivate::isValid() { return (iSyncDaemon && iSyncDaemon->isValid()); } Buteo::SyncResults SyncClientInterfacePrivate::getLastSyncResult(const QString &aProfileId) { FUNCTION_CALL_TRACE(lcButeoTrace); if (iSyncDaemon) { QString resultASXmlString = iSyncDaemon->getLastSyncResult(aProfileId); QDomDocument doc; if (doc.setContent(resultASXmlString, true)) { Buteo::SyncResults result(doc.documentElement()); return result; } else { qCCritical(lcButeoCore) << "Invalid Profile Xml Received from msyncd"; } } return SyncResults(QDateTime(), SyncResults::SYNC_RESULT_INVALID, SyncResults::NO_ERROR); } QList SyncClientInterfacePrivate::allVisibleSyncProfiles() { FUNCTION_CALL_TRACE(lcButeoTrace); QList profilesAsXml; if (iSyncDaemon) { QStringList profilesList = iSyncDaemon->allVisibleSyncProfiles(); if (!profilesList.isEmpty()) { foreach (QString profileAsXml, profilesList) { profilesAsXml.append(profileAsXml); } } } qCDebug(lcButeoCore) << "allVisibleSyncProfiles " << profilesAsXml; return profilesAsXml; } QDBusPendingCallWatcher* SyncClientInterfacePrivate::requestAllVisibleSyncProfiles(QObject *aParent) { FUNCTION_CALL_TRACE(lcButeoTrace); return new QDBusPendingCallWatcher(iSyncDaemon->allVisibleSyncProfiles(), aParent ? aParent : this); } QString SyncClientInterfacePrivate::syncProfile(const QString &aProfileId) { FUNCTION_CALL_TRACE(lcButeoTrace); QString profileAsXml; if (iSyncDaemon) { profileAsXml = iSyncDaemon->syncProfile(aProfileId); } qCDebug(lcButeoCore) << "syncProfile " << profileAsXml; return profileAsXml; } QStringList SyncClientInterfacePrivate::syncProfilesByKey(const QString &aKey, const QString &aValue) { FUNCTION_CALL_TRACE(lcButeoTrace); QStringList profileAsXml; if (iSyncDaemon) { profileAsXml = iSyncDaemon->syncProfilesByKey(aKey, aValue); } return profileAsXml; } QDBusPendingCallWatcher* SyncClientInterfacePrivate::requestSyncProfilesByKey(const QString &aKey, const QString &aValue, QObject *aParent) { FUNCTION_CALL_TRACE(lcButeoTrace); return new QDBusPendingCallWatcher(iSyncDaemon->syncProfilesByKey(aKey, aValue), aParent ? aParent : this); } QStringList SyncClientInterfacePrivate::syncProfilesByType(const QString &aType) { FUNCTION_CALL_TRACE(lcButeoTrace); QStringList profileIds; if (iSyncDaemon) { profileIds = iSyncDaemon->syncProfilesByType(aType); } return profileIds; } QStringList SyncClientInterfacePrivate::profilesByType(const QString &aType) { FUNCTION_CALL_TRACE(lcButeoTrace); QStringList profileIds; if (iSyncDaemon) { profileIds = iSyncDaemon->profilesByType(aType); } return profileIds; } QDBusPendingCallWatcher* SyncClientInterfacePrivate::requestProfilesByType(const QString &aType, QObject *aParent) { FUNCTION_CALL_TRACE(lcButeoTrace); return new QDBusPendingCallWatcher(iSyncDaemon->profilesByType(aType), aParent ? aParent : this); } buteo-syncfw-0.11.10/libbuteosyncfw/clientfw/SyncClientInterfacePrivate.h000066400000000000000000000206301477124122200265540ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCCLIENTINTERFACEPRIVATE_H #define SYNCCLIENTINTERFACEPRIVATE_H #include #include #include #include "SyncDaemonProxy.h" #include namespace Buteo { class SyncProfile; class SyncSchedule; class SyncResults; class SyncClientInterface; //! Private implementation class for SyncClientInterface. class SyncClientInterfacePrivate: public QObject { Q_OBJECT public: /*!\brief Constructor * *@param aParent - pointer to the parent object */ SyncClientInterfacePrivate(SyncClientInterface *aParent); /*! \brief Destructor */ ~SyncClientInterfacePrivate(); /*! \brief function to start the sync * * @param aProfileId - id of the profile to start the sync */ bool startSync(const QString &aProfileId) const; QDBusPendingCallWatcher* requestSync(const QString &aProfileId, QObject *aParent); /*! \brief function to abort the sync * * @param aProfileId - id of the profile to abort the sync */ void abortSync(const QString &aProfileId) const; /*! \brief function to get Running sync list * * @return - list of running sync profile ids */ QStringList getRunningSyncList(); QDBusPendingCallWatcher* requestRunningSyncList(QObject *aParent); /*! \brief function to remove a profile * * @param aProfileId id of the profule to remove to add */ bool removeProfile(const QString &aProfileId); /*! \brief function to update an existing profile * * @param aProfile profile object to add */ bool updateProfile(const Buteo::SyncProfile &aProfile); /*! \brief function to add a profile * * @return returns the backup restore state. */ bool getBackUpRestoreState(); /*! \brief function to check if the interface is valid * * @return status of the validity of the interface */ bool isValid(); /*! \brief this function converts the SyncSchedule object to an xml * file of the below format * \code * * \endcode * * @param aProfileId - profile id * @param aSchedule - schedule object */ bool setSyncSchedule(const QString &aProfileId, const SyncSchedule &aSchedule); /*! \brief this function converts the save the syncResults into * log.xml file corresponding to profileName. * \code * * \endcode */ bool saveSyncResults(const QString &aProfileId, const Buteo::SyncResults &aSyncResults); /*! \brief To get lastSyncResult. * \param aProfileId * \return SyncResults of syncLastResult. */ Buteo::SyncResults getLastSyncResult(const QString &aProfileId); /*! \brief Gets all visible sync profiles. * * Returns all sync profiles that should be visible in sync ui. A profile * is visible if it has not been explicitly set as hidden. * \return The list of sync profiles. */ QList allVisibleSyncProfiles(); QDBusPendingCallWatcher* requestAllVisibleSyncProfiles(QObject *aParent); /*! \brief Gets a sync profile. * * Loads and merges also all sub-profiles that are referenced from the * main profile. Loads the log of finished synchronization sessions with * this profile. * \param aProfileId Name of the profile to get. * \return The sync profile as Xml string. */ QString syncProfile(const QString &aProfileId); /*! \brief Gets a sync profiles which matches the key-value. * * Loads and merges also all sub-profiles that are referenced from the * main profile. Loads the log of finished synchronization sessions with * this profile. * \param aKey Key to match for profile. * \param aValue Value to match for profile. * \return The sync profiles as Xml string list. */ QStringList syncProfilesByKey(const QString &aKey, const QString &aValue); QDBusPendingCallWatcher* requestSyncProfilesByKey(const QString &aKey, const QString &aValue, QObject *aParent); /*! \brief Gets profiles matching the profile type. * * \param aType Type of the profile service/storage/sync. * \return The profiles as Xml string list. */ QStringList profilesByType(const QString &aType); QDBusPendingCallWatcher* requestProfilesByType(const QString &aType, QObject *aParent); /*! \brief Gets a profiles matching the profile type. * * \param aType Type of the profile service/storage/sync. * \return The sync profile ids as string list. */ QStringList syncProfilesByType(const QString &aType); public slots: /*! \brief this is the slot where we will receive the xml data for profile from msyncd. * The XML Data received will be of the following format * \code * * * * * * * * * * * * * * * * \endcode * @param aProfileId - id of the profile * @param aChangeType - change type whether addition , deletion or modification * @param aChangedProfileAsXml - changed profile arrives as xml * */ void slotProfileChanged(QString aProfileId, int aChangeType, QString aChangedProfileAsXml); /*! \brief this is the slot where we will receive the xml data for results from msyncd * the xml looks like this * \code * * * * * *\endcode * @param aProfileId - id of the profile * @param aLastSyncResultAsXml - last sync result as xml */ void resultsAvailable(QString aProfileId, QString aLastSyncResultAsXml); signals: /*! \brief Signal that gets emitted on receiving profileChanged from msyncd * * @param aProfileId - id of the profile * @param aChangeType - change type whether addition , deletion or modification * @param aChangedProfile - changed sync profie as XMl string. */ void profileChanged(QString aProfileId, int aChangeType, QString aChangedProfile); /*! \brief Signal that gets emitted on receiving resultsAvailable from msyncd * * @param aProfileId - id of the profile * @param aLastResults - last results as SyncResults Object */ void resultsAvailable(QString aProfileId, Buteo::SyncResults aLastResults); private: void onServiceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner); SyncDaemonProxy *iSyncDaemon; QDBusServiceWatcher iServiceWatcher; Buteo::SyncClientInterface *iParent; }; }; #endif buteo-syncfw-0.11.10/libbuteosyncfw/clientfw/SyncDaemonProxy.cpp000066400000000000000000000031341477124122200247620ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ /* * This file was generated by qdbusxml2cpp version 0.7 * Command line was: qdbusxml2cpp -p SyncDaemonProxy -N -c SyncDaemonProxy com.meego.msyncd.xml * * qdbusxml2cpp is Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * This is an auto-generated file. * This file may have been hand-edited. Look for HAND-EDIT comments * before re-generating it. */ #include "SyncDaemonProxy.h" /* * Implementation of interface class SyncDaemonProxy */ SyncDaemonProxy::SyncDaemonProxy(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent) : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent) { } SyncDaemonProxy::~SyncDaemonProxy() { } buteo-syncfw-0.11.10/libbuteosyncfw/clientfw/SyncDaemonProxy.h000066400000000000000000000231531477124122200244320ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ /* * This file was generated by qdbusxml2cpp version 0.7 * Command line was: qdbusxml2cpp -p SyncDaemonProxy -N -c SyncDaemonProxy com.meego.msyncd.xml * * qdbusxml2cpp is Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * This is an auto-generated file. * Do not edit! All changes made to it will be lost. */ #ifndef SYNCDAEMONPROXY_H_1280213538 #define SYNCDAEMONPROXY_H_1280213538 #include #include #include #include #include #include #include #include /*! \brief Proxy class for interface com.meego.msyncd */ class SyncDaemonProxy: public QDBusAbstractInterface { Q_OBJECT public: //! \brief returns Interface Name static inline const char *staticInterfaceName() { return "com.meego.msyncd"; } public: //! \see SyncDBusInterface::SyncDBusInterface() SyncDaemonProxy(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = 0); //! \see SyncDBusInterface::SyncDBusInterface() ~SyncDaemonProxy(); public Q_SLOTS: // METHODS //! \see SyncDBusInterface::abortSync() inline Q_NOREPLY void abortSync(const QString &aProfileId) { QList argumentList; argumentList << qVariantFromValue(aProfileId); callWithArgumentList(QDBus::NoBlock, QLatin1String("abortSync"), argumentList); } //! \see SyncDBusInterface::addProfile() inline QDBusPendingReply addProfile(const QString &aProfileAsXml) { QList argumentList; argumentList << qVariantFromValue(aProfileAsXml); return asyncCallWithArgumentList(QLatin1String("addProfile"), argumentList); } //! \see SyncDBusInterface::allVisibleSyncProfiles() inline QDBusPendingReply allVisibleSyncProfiles() { QList argumentList; return callWithArgumentList(QDBus::Block, QLatin1String("allVisibleSyncProfiles"), argumentList); } //! \see SyncDBusInterface::getBackUpRestoreState() inline QDBusPendingReply getBackUpRestoreState() { QList argumentList; return asyncCallWithArgumentList(QLatin1String("getBackUpRestoreState"), argumentList); } //! \see SyncDBusInterface::getLastSyncResult() inline QDBusPendingReply getLastSyncResult(const QString &aProfileId) { QList argumentList; argumentList << qVariantFromValue(aProfileId); return asyncCallWithArgumentList(QLatin1String("getLastSyncResult"), argumentList); } //! \see SyncDBusInterface::isLastSyncScheduled() inline QDBusPendingReply isLastSyncScheduled(const QString &aProfileId) { QList argumentList; argumentList << qVariantFromValue(aProfileId); return asyncCallWithArgumentList(QLatin1String("isLastSyncScheduled"), argumentList); } //! \see SyncDBusInterface::lastSyncMajorCode() inline QDBusPendingReply lastSyncMajorCode(const QString &aProfileId) { QList argumentList; argumentList << qVariantFromValue(aProfileId); return asyncCallWithArgumentList(QLatin1String("lastSyncMajorCode"), argumentList); } //! \see SyncDBusInterface::lastSyncMinorCode() inline QDBusPendingReply lastSyncMinorCode(const QString &aProfileId) { QList argumentList; argumentList << qVariantFromValue(aProfileId); return asyncCallWithArgumentList(QLatin1String("lastSyncMinorCode"), argumentList); } //! \see SyncDBusInterface::lastSyncTime() inline QDBusPendingReply lastSyncTime(const QString &aProfileId) { QList argumentList; argumentList << qVariantFromValue(aProfileId); return asyncCallWithArgumentList(QLatin1String("lastSyncTime"), argumentList); } //! \see SyncDBusInterface::releaseStorages() inline Q_NOREPLY void releaseStorages(const QStringList &aStorageNames) { QList argumentList; argumentList << qVariantFromValue(aStorageNames); callWithArgumentList(QDBus::NoBlock, QLatin1String("releaseStorages"), argumentList); } //! \see SyncDBusInterface::removeProfile() inline QDBusPendingReply removeProfile(const QString &aProfileId) { QList argumentList; argumentList << qVariantFromValue(aProfileId); return asyncCallWithArgumentList(QLatin1String("removeProfile"), argumentList); } //! \see SyncDBusInterface::requestStorages() inline QDBusPendingReply requestStorages(const QStringList &aStorageNames) { QList argumentList; argumentList << qVariantFromValue(aStorageNames); return asyncCallWithArgumentList(QLatin1String("requestStorages"), argumentList); } //! \see SyncDBusInterface::runningSyncs() inline QDBusPendingReply runningSyncs() { QList argumentList; return asyncCallWithArgumentList(QLatin1String("runningSyncs"), argumentList); } //! \see SyncDBusInterface::saveSyncResults() inline QDBusPendingReply saveSyncResults(const QString &aProfileId, const QString &aSyncResults) { QList argumentList; argumentList << qVariantFromValue(aProfileId) << qVariantFromValue(aSyncResults); return asyncCallWithArgumentList(QLatin1String("saveSyncResults"), argumentList); } //! \see SyncDBusInterface::setSyncSchedule() inline QDBusPendingReply setSyncSchedule(const QString &aProfileId, const QString &aScheduleAsXml) { QList argumentList; argumentList << qVariantFromValue(aProfileId) << qVariantFromValue(aScheduleAsXml); return asyncCallWithArgumentList(QLatin1String("setSyncSchedule"), argumentList); } //! \see SyncDBusInterface::startSync() inline QDBusPendingReply startSync(const QString &aProfileId) { QList argumentList; argumentList << qVariantFromValue(aProfileId); return asyncCallWithArgumentList(QLatin1String("startSync"), argumentList); } //! \see SyncDBusInterface::syncProfile() inline QDBusPendingReply syncProfile(const QString &aProfileId) { QList argumentList; argumentList << qVariantFromValue(aProfileId); return callWithArgumentList(QDBus::Block, QLatin1String("syncProfile"), argumentList); } //! \see SyncDBusInterface::syncProfilesByKey inline QDBusPendingReply syncProfilesByKey(const QString &aKey, const QString &aValue) { QList argumentList; argumentList << qVariantFromValue(aKey) << qVariantFromValue(aValue); return asyncCallWithArgumentList(QLatin1String("syncProfilesByKey"), argumentList); } //! \see SyncDBusInterface::syncProfilesByType inline QDBusPendingReply syncProfilesByType(const QString &aType) { QList argumentList; argumentList << qVariantFromValue(aType); return asyncCallWithArgumentList(QLatin1String("syncProfilesByType"), argumentList); } //! \see SyncDBusInterface::profilesByType inline QDBusPendingReply profilesByType(const QString &aType) { QList argumentList; argumentList << qVariantFromValue(aType); return asyncCallWithArgumentList(QLatin1String("profilesByType"), argumentList); } //! \see SyncDBusInterface::updateProfile() inline QDBusPendingReply updateProfile(const QString &aProfileAsXml) { QList argumentList; argumentList << qVariantFromValue(aProfileAsXml); return asyncCallWithArgumentList(QLatin1String("updateProfile"), argumentList); } Q_SIGNALS: // SIGNALS //! \see SyncDBusInterface::backupDone() void backupDone(); //! \see SyncDBusInterface::backupInProgress() void backupInProgress(); //! \see SyncDBusInterface::restoreDone() void restoreDone(); //! \see SyncDBusInterface::restoreInProgress() void restoreInProgress(); //! \see SyncDBusInterface::resultsAvailable() void resultsAvailable(const QString &aProfileName, const QString &aResultsAsXml); //! \see SyncDBusInterface::signalProfileChanged() void signalProfileChanged(const QString &aProfileName, int aChangeType, const QString &aProfileAsXml); //! \see SyncDBusInterface::syncStatus() void syncStatus(const QString &aProfileName, int aStatus, const QString &aMessage, int aErrorCode); //! \see SyncDBusInterface::transferProgress() void transferProgress(const QString &aProfileName, int aTransferDatabase, int aTransferType, const QString &aMimeType, int aCommittedItems); }; #endif buteo-syncfw-0.11.10/libbuteosyncfw/common/000077500000000000000000000000001477124122200206305ustar00rootroot00000000000000buteo-syncfw-0.11.10/libbuteosyncfw/common/BtCommon.h000066400000000000000000000022771477124122200225270ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2020 as part of an update to use bluez5 by deloptes@gmail.com * */ #ifndef BTCOMMON_H_ #define BTCOMMON_H_ #include #include #if HAVE_BLUEZ_5 namespace Buteo { namespace BT { static const QString BLUEZ_DEST = "org.bluez"; static const QString BLUEZ_MANAGER_INTERFACE = "org.freedesktop.DBus.ObjectManager"; static const QString BLUEZ_ADAPTER_INTERFACE = "org.bluez.Adapter1"; static const QString BLUEZ_DEVICE_INTERFACE = "org.bluez.Device1"; static const QString BLUEZ_PROPERTIES_INTERFACE = "org.freedesktop.DBus.Properties"; static const QString GETMANAGEDOBJECTS = "GetManagedObjects"; static const QString GETPROPERTIES = "GetAll"; static const QString PROPERTIESCHANGED = "PropertiesChanged"; static const QString INTERFACESADDED = "InterfacesAdded"; static const QString INTERFACESREMOVED = "InterfacesRemoved"; } // namespace BT } // namespace Buteo typedef QMap InterfacesMap; typedef QMap ObjectsMap; Q_DECLARE_METATYPE(InterfacesMap) Q_DECLARE_METATYPE(ObjectsMap) #endif /* HAVE_BLUEZ_5 */ #endif /* BTCOMMON_H_ */ buteo-syncfw-0.11.10/libbuteosyncfw/common/LogMacros.h000066400000000000000000000025321477124122200226710ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef LOGMACROS_H #define LOGMACROS_H #include "Logger.h" /*! * Creates a trace message to log when the function is entered and exited. * Logs also to time spent in the function. */ #define FUNCTION_CALL_TRACE(loggingCategory) \ QScopedPointer timerDebugVariable( \ Buteo::isLoggingEnabled(loggingCategory()) \ ? new Buteo::LogTimer(QString::fromUtf8(loggingCategory().categoryName()), QString(Q_FUNC_INFO)) \ : nullptr) #endif // LOGMACROS_H buteo-syncfw-0.11.10/libbuteosyncfw/common/Logger.cpp000066400000000000000000000051371477124122200225610ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2013 - 2021 Jolla Ltd. * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include #include "Logger.h" using namespace Buteo; static bool forceDisableTraceLogging = false; LogTimer::LogTimer(const QString &categoryName, const QString &func) : m_categoryName(categoryName.toUtf8()) , m_func(func) , m_category(m_categoryName.constData()) { qCDebug(m_category) << m_func << ":Entry"; m_timer.start(); } LogTimer::~LogTimer() { qCDebug(m_category) << m_func << ":Exit, execution time:" << m_timer.elapsed() << "ms"; } bool Buteo::isLoggingEnabled(const QLoggingCategory &loggingCategory) { return loggingCategory.isDebugEnabled() && !forceDisableTraceLogging; } void Buteo::configureLegacyLogging() { bool hasLegacyLoggingLevel = false; const int legacyLoggingLevel = QString(qgetenv("MSYNCD_LOGGING_LEVEL")).toInt(&hasLegacyLoggingLevel); if (hasLegacyLoggingLevel) { if (legacyLoggingLevel >= 8) { // LOG_TRACE: enable all logging, including trace debugs QLoggingCategory::setFilterRules(QStringLiteral("buteo.*=true")); } else if (legacyLoggingLevel == 7) { // LOG_DEBUG: enable all logging except for trace debugs. QLoggingCategory::setFilterRules(QStringLiteral("buteo.*=true")); forceDisableTraceLogging = true; } else if (legacyLoggingLevel == 6) { // LOG_INFO, LOG_PROTOCOL: enable logging for non-debug levels QLoggingCategory::setFilterRules(QStringLiteral("buteo.*.info=true")); } } } Q_LOGGING_CATEGORY(lcButeoCore, "buteo.core", QtWarningMsg) Q_LOGGING_CATEGORY(lcButeoMsyncd, "buteo.msyncd", QtWarningMsg) Q_LOGGING_CATEGORY(lcButeoPlugin, "buteo.plugin", QtWarningMsg) Q_LOGGING_CATEGORY(lcButeoTrace, "buteo.trace", QtWarningMsg) buteo-syncfw-0.11.10/libbuteosyncfw/common/Logger.h000066400000000000000000000034651477124122200222300ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2013 - 2021 Jolla Ltd. * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef LOGGER_H #define LOGGER_H #include #include namespace Buteo { /*! * \brief Helper class for timing function execution time. */ class LogTimer { public: /*! * \brief Constructor. Creates an entry message to the log. * * @param func Name of the function. */ LogTimer(const QString &categoryName, const QString &func); /*! * \brief Destructor. Creates an exit message to the log, including * function execution time. */ ~LogTimer(); private: QElapsedTimer m_timer; QByteArray m_categoryName; QString m_func; QLoggingCategory m_category; }; bool isLoggingEnabled(const QLoggingCategory &loggingCategory); void configureLegacyLogging(); } Q_DECLARE_LOGGING_CATEGORY(lcButeoCore) Q_DECLARE_LOGGING_CATEGORY(lcButeoMsyncd) Q_DECLARE_LOGGING_CATEGORY(lcButeoPlugin) Q_DECLARE_LOGGING_CATEGORY(lcButeoTrace) #endif // LOGGER_H buteo-syncfw-0.11.10/libbuteosyncfw/common/NetworkManager.cpp000066400000000000000000000265421477124122200242710ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include #include #include #include #include "NetworkManager.h" #include "LogMacros.h" namespace { Sync::InternetConnectionType convertNetworkConnectionType(QNetworkConfiguration::BearerType connectionType) { switch (connectionType) { case QNetworkConfiguration::BearerEthernet: return Sync::INTERNET_CONNECTION_ETHERNET; case QNetworkConfiguration::BearerWLAN: return Sync::INTERNET_CONNECTION_WLAN; case QNetworkConfiguration::Bearer2G: return Sync::INTERNET_CONNECTION_2G; case QNetworkConfiguration::BearerCDMA2000: return Sync::INTERNET_CONNECTION_CDMA2000; case QNetworkConfiguration::BearerWCDMA: return Sync::INTERNET_CONNECTION_WCDMA; case QNetworkConfiguration::BearerHSPA: return Sync::INTERNET_CONNECTION_HSPA; case QNetworkConfiguration::BearerBluetooth: return Sync::INTERNET_CONNECTION_BLUETOOTH; case QNetworkConfiguration::BearerWiMAX: return Sync::INTERNET_CONNECTION_WIMAX; case QNetworkConfiguration::BearerEVDO: return Sync::INTERNET_CONNECTION_EVDO; case QNetworkConfiguration::BearerLTE: return Sync::INTERNET_CONNECTION_LTE; case QNetworkConfiguration::Bearer3G: return Sync::INTERNET_CONNECTION_3G; case QNetworkConfiguration::Bearer4G: return Sync::INTERNET_CONNECTION_4G; default: return Sync::INTERNET_CONNECTION_UNKNOWN; } } } using namespace Buteo; int NetworkManager::m_refCount = 0; bool NetworkManager::m_isSessionActive = false; NetworkManager::NetworkManager(QObject *parent /* = 0*/) : QObject(parent), m_networkConfigManager(0), m_networkSession(0), m_isOnline(false), m_errorEmitted(false), m_sessionTimer(0), m_connectionType(Sync::INTERNET_CONNECTION_UNKNOWN) { FUNCTION_CALL_TRACE(lcButeoTrace); m_networkConfigManager = new QNetworkConfigurationManager(); // check for network status and configuration change (switch wifi, ethernet, mobile) a connect(m_networkConfigManager, SIGNAL(onlineStateChanged(bool)), SLOT(slotConfigurationChanged()), Qt::QueuedConnection); connect(m_networkConfigManager, SIGNAL(configurationAdded(QNetworkConfiguration)), SLOT(slotConfigurationChanged()), Qt::QueuedConnection); connect(m_networkConfigManager, SIGNAL(configurationChanged(QNetworkConfiguration)), SLOT(slotConfigurationChanged()), Qt::QueuedConnection); connect(m_networkConfigManager, SIGNAL(configurationRemoved(QNetworkConfiguration)), SLOT(slotConfigurationChanged()), Qt::QueuedConnection); connect(m_networkConfigManager, SIGNAL(updateCompleted()), SLOT(slotConfigurationChanged()), Qt::QueuedConnection); connect(&m_idleRefreshTimer, SIGNAL(timeout()), SLOT(idleRefresh()), Qt::QueuedConnection); m_idleRefreshTimer.setSingleShot(true); // check connection status on startup idleRefresh(); qCInfo(lcButeoCore) << "Network status:"; qCInfo(lcButeoCore) << "\tOnline::" << m_isOnline; qCInfo(lcButeoCore) << "\tConnection::" << m_connectionType; m_sessionTimer = new QTimer(this); m_sessionTimer->setSingleShot(true); m_sessionTimer->setInterval(10000); connect(m_sessionTimer, SIGNAL(timeout()), this, SLOT(sessionConnectionTimeout())); } NetworkManager::~NetworkManager() { FUNCTION_CALL_TRACE(lcButeoTrace); delete m_networkSession; m_networkSession = 0; delete m_networkConfigManager; m_networkConfigManager = 0; } bool NetworkManager::isOnline() { FUNCTION_CALL_TRACE(lcButeoTrace); return m_isOnline; } Sync::InternetConnectionType NetworkManager::connectionType() const { return m_connectionType; } void NetworkManager::connectSession(bool connectInBackground /* = false*/) { FUNCTION_CALL_TRACE(lcButeoTrace); if (m_isSessionActive) { qCDebug(lcButeoCore) << "Network session already active, ignoring connect call"; m_refCount++; emit connectionSuccess(); return; } else if (!m_networkSession) { QNetworkConfiguration netConfig = m_networkConfigManager->defaultConfiguration(); m_networkSession = new QNetworkSession(netConfig); m_errorEmitted = false; connect(m_networkSession, SIGNAL(error(QNetworkSession::SessionError)), SLOT(slotSessionError(QNetworkSession::SessionError))); connect(m_networkSession, SIGNAL(stateChanged(QNetworkSession::State)), SLOT(slotSessionState(QNetworkSession::State))); connect(m_networkSession, SIGNAL(opened()), SIGNAL(connectionSuccess())); } m_networkSession->setSessionProperty("ConnectInBackground", connectInBackground); if (!m_networkSession->isOpen()) { m_networkSession->open(); // Fail after 10 sec if no network reply is received m_sessionTimer->start(); } else { slotSessionState(m_networkSession->state()); } } void NetworkManager::sessionConnectionTimeout() { if (!m_errorEmitted && m_networkSession) { if (!m_networkSession->isOpen()) { qCWarning(lcButeoCore) << "No network reply received after 10 seconds, emitting session error."; slotSessionError(m_networkSession->error()); } } } void NetworkManager::slotConfigurationChanged() { // wait for 3 secs before update connection status // this avoid problems with connections that take a while to be stabilished m_idleRefreshTimer.start(3000); } void NetworkManager::idleRefresh() { FUNCTION_CALL_TRACE(lcButeoTrace); QList activeConfigs = m_networkConfigManager->allConfigurations(QNetworkConfiguration::Active); QNetworkConfiguration::BearerType connectionType = QNetworkConfiguration::BearerUnknown; QString bearerTypeName; bool isOnline = activeConfigs.size() > 0; if (isOnline) { // FIXME: due this bug lp:#1444162 on nm the QNetworkConfigurationManager // returns the wrong default connection. // We will consider the connection with the smallest bearer as the // default connection, with that wifi and ethernet will be the first one // https://bugs.launchpad.net/ubuntu/+source/network-manager/+bug/1444162 connectionType = activeConfigs.first().bearerType(); bearerTypeName = activeConfigs.first().bearerTypeName(); foreach (const QNetworkConfiguration &conf, activeConfigs) { if (conf.bearerType() != QNetworkConfiguration::BearerUnknown && (conf.bearerType() < connectionType || connectionType == QNetworkConfiguration::BearerUnknown)) { connectionType = conf.bearerType(); bearerTypeName = conf.bearerTypeName(); } } } const Sync::InternetConnectionType convertedConnectionType = convertNetworkConnectionType(connectionType); qCInfo(lcButeoCore) << "New network state:" << isOnline << " New type: " << bearerTypeName << "(" << convertedConnectionType << ")"; if (isOnline != m_isOnline || convertedConnectionType != m_connectionType) { m_isOnline = isOnline; m_connectionType = convertedConnectionType; emit statusChanged(m_isOnline, m_connectionType); } } void NetworkManager::disconnectSession() { FUNCTION_CALL_TRACE(lcButeoTrace); if (m_refCount > 0) { m_refCount--; } if (m_networkSession && 0 == m_refCount) { if (m_sessionTimer->isActive()) m_sessionTimer->stop(); m_networkSession->close(); delete m_networkSession; m_networkSession = nullptr; } } void NetworkManager::slotSessionState(QNetworkSession::State status) { FUNCTION_CALL_TRACE(lcButeoTrace); switch (status) { case QNetworkSession::Invalid: qCWarning(lcButeoCore) << "QNetworkSession::Invalid"; m_isSessionActive = false; break; case QNetworkSession::NotAvailable: qCWarning(lcButeoCore) << "QNetworkSession::NotAvailable"; m_isSessionActive = false; emit connectionError(); break; case QNetworkSession::Connecting: qCDebug(lcButeoCore) << "QNetworkSession::Connecting"; m_isSessionActive = false; break; case QNetworkSession::Connected: qCWarning(lcButeoCore) << "QNetworkSession::Connected"; if (m_networkSession->isOpen() && m_networkSession->state() == QNetworkSession::Connected) { m_isSessionActive = true; emit connectionSuccess(); } else { emit connectionError(); } break; case QNetworkSession::Closing: qCWarning(lcButeoCore) << "QNetworkSession::Closing"; m_isSessionActive = false; break; case QNetworkSession::Disconnected: qCDebug(lcButeoCore) << "QNetworkSession::Disconnected"; m_isSessionActive = false; break; case QNetworkSession::Roaming: qCWarning(lcButeoCore) << "QNetworkSession::Roaming"; m_isSessionActive = false; break; default: qCWarning(lcButeoCore) << "QNetworkSession:: Unknown status change"; m_isSessionActive = false; break; } } void NetworkManager::slotSessionError(QNetworkSession::SessionError error) { FUNCTION_CALL_TRACE(lcButeoTrace); // Emit network errors only once per request if (m_errorEmitted) { m_errorEmitted = false; return; } else { m_errorEmitted = true; } switch (error) { case QNetworkSession::UnknownSessionError: qCWarning(lcButeoCore) << "QNetworkSession::UnknownSessionError"; emit connectionError(); break; case QNetworkSession::SessionAbortedError: qCDebug(lcButeoCore) << "QNetworkSession::SessionAbortedError"; emit connectionError(); break; case QNetworkSession::RoamingError: qCWarning(lcButeoCore) << "QNetworkSession::RoamingError"; emit connectionError(); break; case QNetworkSession::OperationNotSupportedError: qCWarning(lcButeoCore) << "QNetworkSession::OperationNotSupportedError"; emit connectionError(); break; case QNetworkSession::InvalidConfigurationError: qCWarning(lcButeoCore) << "QNetworkSession::InvalidConfigurationError"; emit connectionError(); break; default: qCWarning(lcButeoCore) << "QNetworkSession:: Invalid error code"; emit connectionError(); break; } } buteo-syncfw-0.11.10/libbuteosyncfw/common/NetworkManager.h000066400000000000000000000074711477124122200237360ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef NETWORKMANAGER_H_ #define NETWORKMANAGER_H_ #include #include #include "SyncCommonDefs.h" class QNetworkConfigurationManager; namespace Buteo { /*! \brief Class for managing network sessions * * This class provides APIs to open and close network sessions. It internally * uses QNetworkSession set of classes to manage network sessions. The user * while creating a new session can choose whether to pop-up the internet * connectivity dialog or not. The class also signals about online status of * the device. */ class NetworkManager : public QObject { Q_OBJECT public: /*! \brief Constructor * * @param parent Parent object */ NetworkManager(QObject *parent = 0); /*! \brief Destructor * */ ~NetworkManager(); /*! \brief Returns if the device is currently online, i.e, a data * sessions is possible. * * @return True if device is online, false if not */ bool isOnline(); /*! \brief Returns the type of connection used by the device. * * @return Sync::InternetConnectionType the type of connection. */ Sync::InternetConnectionType connectionType() const; /*! \brief Connects a new network session. If a session was already * open, the signal connectionSuccess will be emitted immediately, * else the function will return and the signal connectionSuccess or * connectionError will be emitted accordingly. The caller can * choose whether to show the network connectivity dialog, or just * open the default network configuration in the background using * the parameter connetInBackground. * * @param connectInBackground If true, the connection is opened * without popping up the connetion dialog */ void connectSession(bool connectInBackground = false); /*! \brief Disconnects an open session */ void disconnectSession(); signals: /*! \brief This signal is emitted when the device's online status changes * * @param aConnected If true, the device is online */ void statusChanged(bool aConnected, Sync::InternetConnectionType aType); /*! \brief This signal is emitted when a network session gets connected */ void connectionSuccess(); /*! \brief This signal is emitted when opening a network session fails */ void connectionError(); private: static bool m_isSessionActive; static int m_refCount; // Reference counter for number of open connections QNetworkConfigurationManager *m_networkConfigManager; QNetworkSession *m_networkSession; bool m_isOnline; bool m_errorEmitted; QTimer *m_sessionTimer; Sync::InternetConnectionType m_connectionType; QTimer m_idleRefreshTimer; private slots: void slotSessionState(QNetworkSession::State status); void slotSessionError(QNetworkSession::SessionError error); void sessionConnectionTimeout(); void slotConfigurationChanged(); void idleRefresh(); }; } #endif//NETWORKMANAGER_H_ buteo-syncfw-0.11.10/libbuteosyncfw/common/SyncCommonDefs.h000066400000000000000000000074321477124122200236760ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCCOMMONDEFS_H #define SYNCCOMMONDEFS_H #include #include #include #include namespace Sync { const QString syncConfigDir(); const QString syncCacheDir(); // deprecated enum SyncStatus { SYNC_QUEUED = 0, SYNC_STARTED, SYNC_PROGRESS, SYNC_ERROR, SYNC_DONE, SYNC_ABORTED, SYNC_CANCELLED, SYNC_STOPPING, SYNC_NOTPOSSIBLE, SYNC_AUTHENTICATION_FAILURE, SYNC_DATABASE_FAILURE, SYNC_CONNECTION_ERROR, SYNC_SERVER_FAILURE, SYNC_BAD_REQUEST, SYNC_PLUGIN_ERROR, SYNC_PLUGIN_TIMEOUT }; // UI needs to display a detailed Progress for the Current ongoing sync enum SyncProgressDetail { SYNC_PROGRESS_INITIALISING = 201, SYNC_PROGRESS_SENDING_ITEMS, SYNC_PROGRESS_RECEIVING_ITEMS, SYNC_PROGRESS_FINALISING }; enum TransferDatabase { LOCAL_DATABASE = 0, REMOTE_DATABASE }; enum TransferType { ITEM_ADDED = 0, ITEM_MODIFIED, ITEM_DELETED, ITEM_ERROR }; enum ConnectivityType { CONNECTIVITY_USB, CONNECTIVITY_BT, CONNECTIVITY_INTERNET }; enum InternetConnectionType { INTERNET_CONNECTION_UNKNOWN = QNetworkConfiguration::BearerUnknown, INTERNET_CONNECTION_ETHERNET = QNetworkConfiguration::BearerEthernet, INTERNET_CONNECTION_WLAN = QNetworkConfiguration::BearerWLAN, INTERNET_CONNECTION_2G = QNetworkConfiguration::Bearer2G, INTERNET_CONNECTION_3G = QNetworkConfiguration::Bearer3G, INTERNET_CONNECTION_4G = QNetworkConfiguration::Bearer4G, INTERNET_CONNECTION_CDMA2000 = QNetworkConfiguration::BearerCDMA2000, INTERNET_CONNECTION_WCDMA = QNetworkConfiguration::BearerWCDMA, INTERNET_CONNECTION_HSPA = QNetworkConfiguration::BearerHSPA, INTERNET_CONNECTION_BLUETOOTH = QNetworkConfiguration::BearerBluetooth, INTERNET_CONNECTION_WIMAX = QNetworkConfiguration::BearerWiMAX, INTERNET_CONNECTION_EVDO = QNetworkConfiguration::BearerEVDO, INTERNET_CONNECTION_LTE = QNetworkConfiguration::BearerLTE }; // These are values that can be used for the SyncSchedule::interval, to specify sync intervals // that should be specially handled (instead of treating them as minute intervals). This allows // special intervals to be handled without additional SyncSchedule attributes. enum ExtendedSyncInterval : unsigned int { // Sync is scheduled one month after the last successful sync. SYNC_INTERVAL_MONTHLY = 365 * 24 * 60 * 2, // Start the named interval values at an unlikely minute-based interval ((365 * 24 * 60 * 2) = 1051200 minutes = 2 years) // Sync is scheduled on the first day of each month. SYNC_INTERVAL_FIRST_DAY_OF_MONTH, // Sync is scheduled on the last day of each month. SYNC_INTERVAL_LAST_DAY_OF_MONTH }; } // namespace Sync Q_DECLARE_METATYPE( Sync::SyncStatus ); Q_DECLARE_METATYPE( Sync::TransferDatabase ); Q_DECLARE_METATYPE( Sync::TransferType ); Q_DECLARE_METATYPE( Sync::ConnectivityType ); #endif // SYNCCOMMONDEFS_H buteo-syncfw-0.11.10/libbuteosyncfw/common/TransportTracker.cpp000066400000000000000000000231051477124122200246450ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * 2019 Updated to use bluez5 by deloptes@gmail.com * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "TransportTracker.h" #if __USBMODED__ #include "USBModedProxy.h" #endif #include "NetworkManager.h" #include "LogMacros.h" #include #include #include #include using namespace Buteo; TransportTracker::TransportTracker(QObject *aParent) : QObject(aParent), iUSBProxy(0), iInternet(0), iSystemBus(QDBusConnection::systemBus()) { FUNCTION_CALL_TRACE(lcButeoTrace); iTransportStates[Sync::CONNECTIVITY_USB] = false; iTransportStates[Sync::CONNECTIVITY_BT] = false; iTransportStates[Sync::CONNECTIVITY_INTERNET] = false; #if __USBMODED__ // USB iUSBProxy = new USBModedProxy(this); if (!iUSBProxy->isValid()) { qCCritical(lcButeoCore) << "Failed to connect to USB moded D-Bus interface"; delete iUSBProxy; iUSBProxy = nullptr; } else { QObject::connect(iUSBProxy, SIGNAL(usbConnection(bool)), this, SLOT(onUsbStateChanged(bool))); iTransportStates[Sync::CONNECTIVITY_USB] = iUSBProxy->isUSBConnected(); } #endif #ifdef HAVE_BLUEZ_5 // BT qDBusRegisterMetaType (); qDBusRegisterMetaType (); // listen for added interfaces if (!iSystemBus.connect(BT::BLUEZ_DEST, QString("/"), BT::BLUEZ_MANAGER_INTERFACE, BT::INTERFACESADDED, this, SLOT(onBtInterfacesAdded(const QDBusObjectPath &, const InterfacesMap &)))) { qCWarning(lcButeoCore) << "Failed to connect InterfacesAdded signal"; } if (!iSystemBus.connect(BT::BLUEZ_DEST, QString("/"), BT::BLUEZ_MANAGER_INTERFACE, BT::INTERFACESREMOVED, this, SLOT(onBtInterfacesRemoved(const QDBusObjectPath &, const QStringList &)))) { qCWarning(lcButeoCore) << "Failed to connect InterfacesRemoved signal"; } // get the initial state if (btConnectivityStatus()) { if (!iSystemBus.connect(BT::BLUEZ_DEST, iDefaultBtAdapter, BT::BLUEZ_PROPERTIES_INTERFACE, BT::PROPERTIESCHANGED, this, SLOT(onBtStateChanged(const QString &, const QVariantMap &, const QStringList &)))) { qCWarning(lcButeoCore) << "Failed to connect PropertiesChanged signal"; } // Set the bluetooth state to on iTransportStates[Sync::CONNECTIVITY_BT] = true; } else { qCWarning(lcButeoCore) << "The BT adapter is powered off or missing"; } #endif // Internet // @todo: enable when internet state is reported correctly. iInternet = new NetworkManager(this); iTransportStates[Sync::CONNECTIVITY_INTERNET] = iInternet->isOnline(); connect(iInternet, SIGNAL(statusChanged(bool, Sync::InternetConnectionType)), SLOT(onInternetStateChanged(bool, Sync::InternetConnectionType)) /*, Qt::QueuedConnection*/); } TransportTracker::~TransportTracker() { FUNCTION_CALL_TRACE(lcButeoTrace); } bool TransportTracker::isConnectivityAvailable(Sync::ConnectivityType aType) const { FUNCTION_CALL_TRACE(lcButeoTrace); QMutexLocker locker(&iMutex); return iTransportStates[aType]; } void TransportTracker::onUsbStateChanged(bool aConnected) { FUNCTION_CALL_TRACE(lcButeoTrace); qCDebug(lcButeoCore) << "USB state changed:" << aConnected; updateState(Sync::CONNECTIVITY_USB, aConnected); } #ifdef HAVE_BLUEZ_5 void TransportTracker::onBtStateChanged(const QString &interface, const QVariantMap &changed, const QStringList &invalidated) { FUNCTION_CALL_TRACE(lcButeoTrace); Q_UNUSED(invalidated); if (interface == BT::BLUEZ_ADAPTER_INTERFACE) { for (QVariantMap::const_iterator i = changed.begin(); i != changed.end(); ++i) { if (i.key() == "Powered") { bool btOn = i.value().toBool(); qCInfo(lcButeoCore) << "BT power state " << btOn; updateState(Sync::CONNECTIVITY_BT, btOn); } } } } void TransportTracker::onBtInterfacesAdded(const QDBusObjectPath &path, const InterfacesMap &interfaces) { FUNCTION_CALL_TRACE(lcButeoTrace); for (InterfacesMap::const_iterator i = interfaces.cbegin(); i != interfaces.cend(); ++i) { if (i.key() == BT::BLUEZ_ADAPTER_INTERFACE) { // do not process other interfaces after default one was selected if (!iDefaultBtAdapter.isEmpty()) break; iDefaultBtAdapter = path.path(); qCDebug(lcButeoCore) << BT::BLUEZ_ADAPTER_INTERFACE << "interface" << iDefaultBtAdapter; QDBusInterface adapter(BT::BLUEZ_DEST, iDefaultBtAdapter, BT::BLUEZ_ADAPTER_INTERFACE, iSystemBus); if (!iSystemBus.connect(BT::BLUEZ_DEST, iDefaultBtAdapter, BT::BLUEZ_PROPERTIES_INTERFACE, BT::PROPERTIESCHANGED, this, SLOT(onBtStateChanged(const QString &, const QVariantMap &, const QStringList &)))) { qCWarning(lcButeoCore) << "Failed to connect PropertiesChanged signal"; } if (adapter.isValid()) { updateState(Sync::CONNECTIVITY_BT, adapter.property("Powered").toBool()); qCInfo(lcButeoCore) << "BT state changed" << adapter.property("Powered").toBool(); } } } } void TransportTracker::onBtInterfacesRemoved(const QDBusObjectPath &path, const QStringList &interfaces) { FUNCTION_CALL_TRACE(lcButeoTrace); for (QStringList::const_iterator i = interfaces.cbegin(); i != interfaces.cend(); ++i) { if (*i == BT::BLUEZ_ADAPTER_INTERFACE) { if (path.path() != iDefaultBtAdapter) continue; qCDebug(lcButeoCore) << "DBus adapter path: " << iDefaultBtAdapter ; if (!iSystemBus.disconnect(BT::BLUEZ_DEST, iDefaultBtAdapter, BT::BLUEZ_PROPERTIES_INTERFACE, BT::PROPERTIESCHANGED, this, SLOT(onBtStateChanged(const QString &, const QVariantMap &, const QStringList &)))) { qCWarning(lcButeoCore) << "Failed to disconnect PropertiesChanged signal"; } else { qCDebug(lcButeoCore) << "'org.bluez.Adapter1' interface removed from " << path.path(); } iDefaultBtAdapter = QString(); break; } } } #endif void TransportTracker::onInternetStateChanged(bool aConnected, Sync::InternetConnectionType aType) { FUNCTION_CALL_TRACE(lcButeoTrace); qCDebug(lcButeoCore) << "Internet state changed:" << aConnected; updateState(Sync::CONNECTIVITY_INTERNET, aConnected); emit networkStateChanged(aConnected, aType); } void TransportTracker::updateState(Sync::ConnectivityType aType, bool aState) { FUNCTION_CALL_TRACE(lcButeoTrace); bool oldState = false; { QMutexLocker locker(&iMutex); oldState = iTransportStates[aType]; iTransportStates[aType] = aState; } if (oldState != aState) { if (aType != Sync::CONNECTIVITY_INTERNET) { emit connectivityStateChanged(aType, aState); } } } #ifdef HAVE_BLUEZ_5 bool TransportTracker::btConnectivityStatus() { FUNCTION_CALL_TRACE(lcButeoTrace); QDBusInterface manager(BT::BLUEZ_DEST, QString("/"), BT::BLUEZ_MANAGER_INTERFACE, iSystemBus); QDBusReply reply = manager.call(BT::GETMANAGEDOBJECTS); if (!reply.isValid()) { qCWarning(lcButeoCore) << "Failed to connect BT ObjectManager: " << reply.error().message() ; return false; } ObjectsMap objects = reply.value(); for (ObjectsMap::iterator i = objects.begin(); i != objects.end(); ++i) { InterfacesMap ifaces = i.value(); for (InterfacesMap::const_iterator j = ifaces.cbegin(); j != ifaces.cend(); ++j) { if (j.key() == BT::BLUEZ_ADAPTER_INTERFACE) { if (iDefaultBtAdapter.isEmpty() || iDefaultBtAdapter != i.key().path()) { iDefaultBtAdapter = i.key().path(); qCDebug(lcButeoCore) << "Using adapter path: " << iDefaultBtAdapter; } QDBusInterface adapter(BT::BLUEZ_DEST, iDefaultBtAdapter, BT::BLUEZ_ADAPTER_INTERFACE, iSystemBus); return adapter.property("Powered").toBool(); // use first adapter } } } return false; } #endif buteo-syncfw-0.11.10/libbuteosyncfw/common/TransportTracker.h000066400000000000000000000073751477124122200243250ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * 2019 Updated to use bluez5 by deloptes@gmail.com * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef TRANSPORTTRACKER_H_ #define TRANSPORTTRACKER_H_ #include "SyncCommonDefs.h" #include #include #include #include #include #ifdef HAVE_BLUEZ_5 #include #endif namespace Buteo { class USBModedProxy; class NetworkManager; /*! \brief Class for tracking transport states * * USB state is tracked with HAL, BT with Context Framework and Internet states with Buteo::NetworkManager. */ class TransportTracker : public QObject { Q_OBJECT public: /*! \brief Constructor * * @param aParent Parent object */ TransportTracker(QObject *aParent = 0); //! \brief Destructor virtual ~TransportTracker(); /*! \brief Checks the state of the given connectivity type * * @param aType Connectivity type * @return True if available, false if not */ bool isConnectivityAvailable(Sync::ConnectivityType aType) const; signals: /*! \brief Signal emitted when a connectivity state changes * * @param aType Connectivity type whose state has changed * @param aState New state. True if available, false if not. */ void connectivityStateChanged(Sync::ConnectivityType aType, bool aState); /*! \brief Signal emitted when a n/w state changes * * @param aState New state. True if available, false if not. * @param aType Connection type. The type of connetcion with the Internet. */ void networkStateChanged(bool aState, Sync::InternetConnectionType aType); /*! \brief Signal emitted when a network session is successfully opened */ void sessionConnected(); /*! \brief Signal emitted when opening a network session fails */ void sessionError(); private slots: void onUsbStateChanged(bool aConnected); #ifdef HAVE_BLUEZ_5 void onBtStateChanged(const QString &interface, const QVariantMap &changed, const QStringList &invalidated); void onBtInterfacesAdded(const QDBusObjectPath &path, const InterfacesMap &interfaces); void onBtInterfacesRemoved(const QDBusObjectPath &path, const QStringList &interfaces); #endif void onInternetStateChanged(bool aConnected, Sync::InternetConnectionType aType); private: QMap iTransportStates; USBModedProxy *iUSBProxy; NetworkManager *iInternet; QDBusConnection iSystemBus; QString iDefaultBtAdapter; mutable QMutex iMutex; /*! \brief updates the state of the given connectivity type to input value * * @param aType Connectivity type * @param aState Connectivity State */ void updateState(Sync::ConnectivityType aType, bool aState); #ifdef SYNCFW_UNIT_TESTS friend class TransportTrackerTest; friend class SynchronizerTest; #endif #ifdef HAVE_BLUEZ_5 bool btConnectivityStatus(); #endif }; } // namespace Buteo #endif /* TRANSPORTTRACKER_H_ */ buteo-syncfw-0.11.10/libbuteosyncfw/common/USBModedProxy.cpp000066400000000000000000000062561477124122200240110ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ /* * This file was generated by qdbusxml2cpp version 0.7 * Command line was: qdbusxml2cpp -v -p USBModedProxy -c USBModedProxy usb_moded.xml * * qdbusxml2cpp is Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * This is an auto-generated file, with several edits. * If this file is to be regenerated, the changes must be backed up and merged */ #include #include "USBModedProxy.h" #include "LogMacros.h" using namespace Buteo; static const QString USB_MODE_SERVICE("com.meego.usb_moded"); static const QString USB_MODE_OBJECT("/com/meego/usb_moded"); static const QString SYNC_MODE_NAME("pc_suite"); static const QString MTP_MODE_NAME("mtp_mode"); /* * Implementation of interface class USBModedProxy */ USBModedProxy::USBModedProxy(QObject *parent) : QDBusAbstractInterface(USB_MODE_SERVICE, USB_MODE_OBJECT, staticInterfaceName(), QDBusConnection::systemBus(), parent), m_isConnected(false) { FUNCTION_CALL_TRACE(lcButeoTrace); initUsbModeTracking(); } USBModedProxy::~USBModedProxy() { } void USBModedProxy::initUsbModeTracking() { if (!QObject::connect(this, &USBModedProxy::sig_usb_state_ind, this, &USBModedProxy::slotModeChanged)) { qCCritical(lcButeoCore) << "Failed to connect to USB moded signal! USB notifications will not be available."; } QDBusPendingReply reply = this->mode_request(); QDBusPendingCallWatcher *watch = new QDBusPendingCallWatcher(reply, this); connect(watch, &QDBusPendingCallWatcher::finished, this, &USBModedProxy::handleUsbModeReply); } void USBModedProxy::handleUsbModeReply(QDBusPendingCallWatcher *call) { QDBusPendingReply reply = *call; if (reply.isError()) { qCWarning(lcButeoCore) << "Call to" << USB_MODE_SERVICE << "failed:" << reply.error(); } else { slotModeChanged(reply.value()); } call->deleteLater(); } void USBModedProxy::slotModeChanged(const QString &mode) { FUNCTION_CALL_TRACE(lcButeoTrace); bool isConnected = (mode == MTP_MODE_NAME || mode == SYNC_MODE_NAME); if (m_isConnected != isConnected) { m_isConnected = isConnected; emit usbConnection(m_isConnected); } } bool USBModedProxy::isUSBConnected() { FUNCTION_CALL_TRACE(lcButeoTrace); return m_isConnected; } buteo-syncfw-0.11.10/libbuteosyncfw/common/USBModedProxy.h000066400000000000000000000072001477124122200234440ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ /* * This file was generated by qdbusxml2cpp version 0.7 * Command line was: qdbusxml2cpp -v -p USBModedProxy -c USBModedProxy usb_moded.xml * * qdbusxml2cpp is Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * This is an auto-generated file, with several edits. * If this file is to be regenerated, the changes must be backed up and merged */ #ifndef USBMODEDPROXY_H_1272105195 #define USBMODEDPROXY_H_1272105195 #include #include #include #include #include #include #include #include namespace Buteo { /*! \brief - Proxy class for interface com.meego.usb_moded */ class USBModedProxy: public QDBusAbstractInterface { Q_OBJECT public: /*! \brief - returns the static interface name */ static inline const char *staticInterfaceName() { return "com.meego.usb_moded"; } public: /*! \brief - Constructor * *@param parent - pointer to parent object */ USBModedProxy(QObject *parent = 0); /*! \brief - Destructor * */ ~USBModedProxy(); /*! \brief - function to check if usb is connected or not * *@return - Returns true if the USB cable is connected in * the Ovi Suite mode,false, if it's in any other mode, * or if it isn't connected */ bool isUSBConnected(); public Q_SLOTS: // METHODS /*! \brief - connected to usbmoded proxy's sig_usb_state_ind signal * * @param mode - new mode to which usb has changed to * */ void slotModeChanged(const QString &mode); /*! \brief - method to make a DBUS call to USB moded daemon * *@return - result of the request as QString */ inline QDBusPendingReply mode_request() { QList argumentList; return asyncCallWithArgumentList(QLatin1String("mode_request"), argumentList); } Q_SIGNALS: // SIGNALS /*! \brief - overridden signal from usb moded proxy. * *@param mode new mode */ void sig_usb_state_ind(const QString &mode); /*! \brief - this is emitted on receiving sig_usb_state_ind * from usb moded daemon * * @param bConnected - boolean flag to indicate whether the connection is successful or not */ void usbConnection(bool bConnected); private: /*! \brief - handle replies to async usb mode queries */ void handleUsbModeReply(QDBusPendingCallWatcher *call); /*! \brief - helper for initializing usb mode tracking from the constructor */ void initUsbModeTracking(); /*! \brief - cached pc_suite/mtp_mode usb connection status */ bool m_isConnected; }; } namespace com { namespace meego { typedef Buteo::USBModedProxy usb_moded; } } #endif buteo-syncfw-0.11.10/libbuteosyncfw/common/usb_moded.xml000066400000000000000000000023621477124122200233160ustar00rootroot00000000000000 buteo-syncfw-0.11.10/libbuteosyncfw/libbuteosyncfw.pro000066400000000000000000000072111477124122200231220ustar00rootroot00000000000000TEMPLATE = lib TARGET = buteosyncfw5 DEPENDPATH += . clientfw common pluginmgr profile INCLUDEPATH += . clientfw common pluginmgr profile VER_MAJ = 0 VER_MIN = 1 VER_PAT = 0 QT += sql xml dbus network QT -= gui CONFIG += dll \ create_pc \ create_prl #DEFINES += BUTEO_ENABLE_DEBUG DEFINES += DEFAULT_PLUGIN_PATH=\"\\\"$$[QT_INSTALL_LIBS]/buteo-plugins-qt5\\\"\" # there might be something still here which shouldn't really be publicly offered PUBLIC_HEADERS += \ common/Logger.h \ common/LogMacros.h \ common/SyncCommonDefs.h \ common/TransportTracker.h \ common/NetworkManager.h \ clientfw/SyncClientInterface.h \ pluginmgr/ClientPlugin.h \ pluginmgr/DeletedItemsIdStorage.h \ pluginmgr/PluginCbInterface.h \ pluginmgr/PluginManager.h \ pluginmgr/ServerPlugin.h \ pluginmgr/StorageChangeNotifierPlugin.h \ pluginmgr/StorageChangeNotifierPluginLoader.h \ pluginmgr/StorageItem.h \ pluginmgr/StoragePlugin.h \ pluginmgr/StoragePluginLoader.h \ pluginmgr/SyncPluginBase.h \ pluginmgr/SyncPluginLoader.h \ profile/BtHelper.h \ profile/Profile.h \ profile/ProfileEngineDefs.h \ profile/ProfileFactory.h \ profile/ProfileField.h \ profile/ProfileManager.h \ profile/StorageProfile.h \ profile/SyncLog.h \ profile/SyncProfile.h \ profile/SyncResults.h \ profile/SyncSchedule.h \ profile/TargetResults.h \ pluginmgr/OOPClientPlugin.h \ pluginmgr/OOPServerPlugin.h \ pluginmgr/ButeoPluginIface.h HEADERS += $$PUBLIC_HEADERS \ clientfw/SyncClientInterfacePrivate.h \ clientfw/SyncDaemonProxy.h \ profile/Profile_p.h \ profile/SyncSchedule_p.h \ SOURCES += common/Logger.cpp \ common/TransportTracker.cpp \ common/NetworkManager.cpp \ clientfw/SyncClientInterface.cpp \ clientfw/SyncClientInterfacePrivate.cpp \ clientfw/SyncDaemonProxy.cpp \ pluginmgr/ClientPlugin.cpp \ pluginmgr/DeletedItemsIdStorage.cpp \ pluginmgr/PluginManager.cpp \ pluginmgr/ServerPlugin.cpp \ pluginmgr/StorageItem.cpp \ pluginmgr/StoragePlugin.cpp \ pluginmgr/SyncPluginBase.cpp \ pluginmgr/SyncPluginLoader.cpp \ profile/BtHelper.cpp \ profile/Profile.cpp \ profile/ProfileFactory.cpp \ profile/ProfileField.cpp \ profile/ProfileManager.cpp \ profile/StorageProfile.cpp \ profile/SyncLog.cpp \ profile/SyncProfile.cpp \ profile/SyncResults.cpp \ profile/SyncSchedule.cpp \ profile/TargetResults.cpp \ pluginmgr/OOPClientPlugin.cpp \ pluginmgr/OOPServerPlugin.cpp \ pluginmgr/ButeoPluginIface.cpp usb-moded { message("Building with usb-moded") DEFINES += __USBMODED__ HEADERS += common/USBModedProxy.h SOURCES += common/USBModedProxy.cpp } # clean QMAKE_CLEAN += $(TARGET) $(TARGET0) $(TARGET1) $(TARGET2) QMAKE_CLEAN += $(OBJECTS_DIR)/moc_* QMAKE_CLEAN += lib$${TARGET}.prl pkgconfig/* # install target.path = $$[QT_INSTALL_LIBS] headers.path = /usr/include/buteosyncfw5/ headers.files = $$PUBLIC_HEADERS INSTALLS += target headers QMAKE_PKGCONFIG_DESTDIR = pkgconfig QMAKE_PKGCONFIG_LIBDIR = $$target.path QMAKE_PKGCONFIG_INCDIR = $$headers.path QMAKE_PKGCONFIG_VERSION = $$VERSION pkgconfig.files = $${TARGET}.pc buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/000077500000000000000000000000001477124122200213445ustar00rootroot00000000000000buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/ButeoPluginIface.cpp000066400000000000000000000014441477124122200252400ustar00rootroot00000000000000/* * This file was generated by qdbusxml2cpp version 0.8 * Command line was: qdbusxml2cpp -v -c ButeoPluginIface -p ButeoPluginIface.h:ButeoPluginIface.cpp com.buteo.msyncd.baseplugin.xml * * qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). * * This is an auto-generated file. * This file may have been hand-edited. Look for HAND-EDIT comments * before re-generating it. */ #include "ButeoPluginIface.h" /* * Implementation of interface class ButeoPluginIface */ ButeoPluginIface::ButeoPluginIface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent) : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent) { } ButeoPluginIface::~ButeoPluginIface() { } buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/ButeoPluginIface.h000066400000000000000000000075611477124122200247130ustar00rootroot00000000000000/* * This file was generated by qdbusxml2cpp version 0.8 * Command line was: qdbusxml2cpp -v -c ButeoPluginIface -p ButeoPluginIface.h:ButeoPluginIface.cpp com.buteo.msyncd.baseplugin.xml * * qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). * * This is an auto-generated file. * Do not edit! All changes made to it will be lost. */ #ifndef BUTEOPLUGINIFACE_H_1391581887 #define BUTEOPLUGINIFACE_H_1391581887 #include #include #include #include #include #include #include #include #include /* * Proxy class for interface com.buteo.msyncd.baseplugin */ class ButeoPluginIface: public QDBusAbstractInterface { Q_OBJECT public: static inline const char *staticInterfaceName() { return "com.buteo.msyncd.baseplugin"; } public: ButeoPluginIface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = 0); ~ButeoPluginIface(); public Q_SLOTS: // METHODS inline QDBusPendingReply<> abortSync(uchar aStatus) { QList argumentList; argumentList << QVariant::fromValue(aStatus); return asyncCallWithArgumentList(QLatin1String("abortSync"), argumentList); } inline QDBusPendingReply cleanUp() { QList argumentList; return asyncCallWithArgumentList(QLatin1String("cleanUp"), argumentList); } inline QDBusPendingReply<> connectivityStateChanged(int aType, bool aState) { QList argumentList; argumentList << QVariant::fromValue(aType) << QVariant::fromValue(aState); return asyncCallWithArgumentList(QLatin1String("connectivityStateChanged"), argumentList); } inline QDBusPendingReply getSyncResults() { QList argumentList; return asyncCallWithArgumentList(QLatin1String("getSyncResults"), argumentList); } inline QDBusPendingReply init() { QList argumentList; return asyncCallWithArgumentList(QLatin1String("init"), argumentList); } inline QDBusPendingReply<> resume() { QList argumentList; return asyncCallWithArgumentList(QLatin1String("resume"), argumentList); } inline QDBusPendingReply startListen() { QList argumentList; return asyncCallWithArgumentList(QLatin1String("startListen"), argumentList); } inline QDBusPendingReply startSync() { QList argumentList; return asyncCallWithArgumentList(QLatin1String("startSync"), argumentList); } inline QDBusPendingReply<> stopListen() { QList argumentList; return asyncCallWithArgumentList(QLatin1String("stopListen"), argumentList); } inline QDBusPendingReply<> suspend() { QList argumentList; return asyncCallWithArgumentList(QLatin1String("suspend"), argumentList); } inline QDBusPendingReply uninit() { QList argumentList; return asyncCallWithArgumentList(QLatin1String("uninit"), argumentList); } Q_SIGNALS: // SIGNALS void accquiredStorage(const QString &aMimeType); void error(const QString &aProfileName, const QString &aMessage, int aErrorCode); void newSession(const QString &aDestination); void success(const QString &aProfileName, const QString &aMessage); void syncProgressDetail(const QString &aProfileName, int aProgressDetail); void transferProgress(const QString &aProfileName, Sync::TransferDatabase aDatabase, Sync::TransferType aType, const QString &aMimeType, int aCommittedItems); }; namespace com { namespace buteo { namespace msyncd { typedef ::ButeoPluginIface baseplugin; } } } #endif buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/ClientPlugin.cpp000066400000000000000000000022731477124122200244510ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "ClientPlugin.h" using namespace Buteo; ClientPlugin::ClientPlugin(const QString &aPluginName, const SyncProfile &aProfile, PluginCbInterface *aCbInterface) : SyncPluginBase(aPluginName, aProfile.name(), aCbInterface), iProfile(aProfile) { } ClientPlugin::~ClientPlugin() { } buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/ClientPlugin.h000066400000000000000000000040551477124122200241160ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef CLIENTPLUGIN_H #define CLIENTPLUGIN_H #include "SyncPluginBase.h" #include "SyncProfile.h" #include namespace Buteo { class PluginCbInterface; /*! \brief Base class for client plugins * */ class ClientPlugin : public SyncPluginBase { Q_OBJECT public: /*! \brief Constructor * * @param aPluginName Name of this client plugin * @param aProfile Sync profile for the client * @param aCbInterface Pointer to the callback interface */ ClientPlugin(const QString &aPluginName, const SyncProfile &aProfile, PluginCbInterface *aCbInterface); /*! \brief Destructor * * Call uninit before destroying the client plug-in. */ virtual ~ClientPlugin(); /*! \brief Starts synchronization * * Init must be called before calling this function. * @returns True on success, otherwise false */ virtual bool startSync() = 0; /*! \brief access to profile owned and used by this instance */ SyncProfile &profile() { return iProfile; } protected: //! Sync Profile Object that the plugin is currently operating on SyncProfile iProfile; }; } #endif // CLIENTPLUGIN_H buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/DeletedItemsIdStorage.cpp000066400000000000000000000206341477124122200262270ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "DeletedItemsIdStorage.h" #include "LogMacros.h" #include #include #include using namespace Buteo; DeletedItemsIdStorage::DeletedItemsIdStorage() { FUNCTION_CALL_TRACE(lcButeoTrace); } DeletedItemsIdStorage::~DeletedItemsIdStorage() { FUNCTION_CALL_TRACE(lcButeoTrace); } bool DeletedItemsIdStorage::init(const QString &aDbFile) { FUNCTION_CALL_TRACE(lcButeoTrace); static unsigned connectionNumber = 0; const QString connectionName = "deleteditems"; if (!iDb.isOpen()) { iConnectionName = connectionName + QString::number(connectionNumber++); iDb = QSqlDatabase::addDatabase("QSQLITE", iConnectionName); iDb.setDatabaseName(aDbFile); iDb.open(); } if (!iDb.isOpen()) { qCCritical(lcButeoCore) << "Could open deleted items database file:" << aDbFile ; return false; } if (!ensureItemSnapshotExists() || !ensureDeletedItemsExists()) { return false; } return true; } bool DeletedItemsIdStorage::uninit() { FUNCTION_CALL_TRACE(lcButeoTrace); if (iDb.isOpen()) { iDb.close(); iDb = QSqlDatabase(); QSqlDatabase::removeDatabase(iConnectionName); } return true; } bool DeletedItemsIdStorage::getSnapshot(QList &aItems, QList &aCreationTimes) const { FUNCTION_CALL_TRACE(lcButeoTrace); const QString queryString("SELECT * FROM snapshot"); QSqlQuery query(iDb); query.prepare(queryString); if (!query.exec()) { qCWarning(lcButeoCore) << "Could not retrieve item snapshot: " << query.lastError() ; return false; } while (query.next()) { aItems.append(query.value(0).toString()); QDateTime t = query.value(1).toDateTime(); t.setTimeSpec(Qt::UTC); aCreationTimes.append(t.toLocalTime()); } return true; } bool DeletedItemsIdStorage::setSnapshot(const QList &aItems, const QList &aCreationTimes) { FUNCTION_CALL_TRACE(lcButeoTrace); // Clear existing snapshot const QString deleteQueryString("DELETE FROM snapshot"); QSqlQuery deleteQuery(iDb); deleteQuery.prepare(deleteQueryString); if (!deleteQuery.exec()) { qCWarning(lcButeoCore) << "Could not clear item snapshot: " << deleteQuery.lastError() ; return false; } if (!aItems.isEmpty()) { const QString insertQueryString("INSERT INTO snapshot VALUES (:itemid, :creationtime)"); bool supportsTransaction = iDb.transaction(); if (!supportsTransaction) { qCDebug(lcButeoCore) << "SQL Db doesn't support transactions"; } QSqlQuery insertQuery(iDb); insertQuery.prepare(insertQueryString); QVariantList itemIds; QVariantList creationTimes; for (int i = 0; i < aItems.count(); ++i) { itemIds << aItems[i]; creationTimes << aCreationTimes[i].toUTC(); } insertQuery.addBindValue(itemIds); insertQuery.addBindValue(creationTimes); // Insert new snapshot if (insertQuery.execBatch()) { qCDebug(lcButeoCore) << itemIds.count() << "items set to snapshot" ; } else { qCWarning(lcButeoCore) << "Could not set items snapshot" ; qCWarning(lcButeoCore) << "Reason:" << insertQuery.lastError() ; } if (supportsTransaction) { if (!iDb.commit()) { qCWarning(lcButeoCore) << "Error while committing : " << iDb.lastError(); } } } return true; } void DeletedItemsIdStorage::addDeletedItem(const QString &aItem, const QDateTime &aCreationTime, const QDateTime &aDeleteTime) { FUNCTION_CALL_TRACE(lcButeoTrace); const QString queryString("INSERT INTO deleteditems VALUES(:itemid, :creationtime, :deletetime)"); QSqlQuery query(iDb); query.prepare(queryString); query.bindValue(":itemid", aItem); query.bindValue(":creationtime", aCreationTime.toUTC()); query.bindValue(":deletetime", aDeleteTime.toUTC()); if (query.exec()) { qCDebug(lcButeoCore) << "Added item" << aItem << "as deleted at time" << aDeleteTime << ", creation time:" << aCreationTime ; } else { qCWarning(lcButeoCore) << "Could not add item as deleted:" << aItem ; qCWarning(lcButeoCore) << "Reason:" << query.lastError() ; } } void DeletedItemsIdStorage::addDeletedItems(const QList &aItems, const QList &aCreationTimes, const QList &aDeleteTimes) { FUNCTION_CALL_TRACE(lcButeoTrace); const QString queryString("INSERT INTO deleteditems VALUES(:itemid, :creationtime, :deletetime)"); QSqlQuery query(iDb); bool supportsTransaction = iDb.transaction(); if (!supportsTransaction) { qCDebug(lcButeoCore) << "SQL Db doesn't support transactions"; } query.prepare(queryString); QVariantList items; QVariantList creationTimes; QVariantList deleteTimes; for (int i = 0; i < aItems.count(); ++i) { items << aItems[i]; creationTimes << aCreationTimes[i].toUTC(); deleteTimes << aDeleteTimes[i].toUTC(); } query.addBindValue(items); query.addBindValue(creationTimes); query.addBindValue(deleteTimes); if (query.execBatch()) { qCDebug(lcButeoCore) << "Added" << items.count() << "items as deleted" ; } else { qCWarning(lcButeoCore) << "Could not add items as deleted" ; qCWarning(lcButeoCore) << "Reason:" << query.lastError() ; } if (supportsTransaction) { if (!iDb.commit()) { qCWarning(lcButeoCore) << "Error while committing : " << iDb.lastError(); } } } bool DeletedItemsIdStorage::getDeletedItems(QList &aItems, const QDateTime &aTime) { FUNCTION_CALL_TRACE(lcButeoTrace); const QString queryString("SELECT itemid FROM deleteditems WHERE creationtime < :creationtime AND deletetime > :deletetime"); qCDebug(lcButeoCore) << queryString; QSqlQuery query(iDb); query.prepare(queryString ); query.bindValue(":creationtime", aTime.toUTC()); query.bindValue(":deletetime", aTime.toUTC()); if (!query.exec()) { qCWarning(lcButeoCore) << "Could not retrieve deleted items:" << query.lastError(); return false; } while (query.next()) { aItems.append( query.value(0).toString() ); } qCDebug(lcButeoCore) << "Found" << aItems.count() << "deleted items" ; return true; } bool DeletedItemsIdStorage::ensureItemSnapshotExists() { FUNCTION_CALL_TRACE(lcButeoTrace); const QString queryString("CREATE TABLE IF NOT EXISTS snapshot(itemid varchar(512) primary key, creationtime timestamp)"); QSqlQuery query(iDb); query.prepare(queryString); if (!query.exec()) { qCWarning(lcButeoCore) << "Query failed: " << query.lastError(); return false; } else { qCDebug(lcButeoCore) << "Ensured database table: snapshot" ; return true; } } bool DeletedItemsIdStorage::ensureDeletedItemsExists() { FUNCTION_CALL_TRACE(lcButeoTrace); const QString queryString("CREATE TABLE IF NOT EXISTS deleteditems(itemid varchar(512) primary key, creationtime timestamp, deletetime timestamp)"); QSqlQuery query(iDb); query.prepare(queryString); if (!query.exec()) { qCWarning(lcButeoCore) << "Query failed: " << query.lastError(); return false; } else { qCDebug(lcButeoCore) << "Ensured database table: deleteditems" ; return true; } } buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/DeletedItemsIdStorage.h000066400000000000000000000071621477124122200256750ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef DELETEDITEMSIDSTORAGE_H #define DELETEDITEMSIDSTORAGE_H #include #include namespace Buteo { /*! * \brief Persistent storage for storing deleted item IDs */ class DeletedItemsIdStorage { public: /** * \brief Contructor */ DeletedItemsIdStorage(); /** * \brief Destructor */ ~DeletedItemsIdStorage(); /*! \brief Initializes backend * * * @param aDbFile Path to database to use as persistent storage * @return True on success, otherwise false */ bool init(const QString &aDbFile); /*! \brief Uninitializes backend * * @return True on success, otherwise false */ bool uninit(); /*! \brief Retrieves persistently stored snapshot of item id's * * @param aItems Items of the snapshot * @param aCreationTimes Creation times of the items * @return True on success, otherwise false */ bool getSnapshot(QList &aItems, QList &aCreationTimes) const; /*! \brief Store a snapshot of item id's persistently * * @param aItems Item id's to store * @param aCreationTimes Creation times of the items * @return True on success, otherwise false */ bool setSnapshot(const QList &aItems, const QList &aCreationTimes); /*! \brief Adds a deleted item to backend * * @param aItem Item Id * @param aCreationTime Time when item was initially created * @param aDeleteTime Time of deletion */ void addDeletedItem(const QString &aItem, const QDateTime &aCreationTime, const QDateTime &aDeleteTime); /*! \brief Adds deleted items to backend * * @param aItems Items Ids * @param aCreationTimes Times when the items were initially created * @param aDeleteTimes Times of deletion */ void addDeletedItems(const QList &aItems, const QList &aCreationTimes, const QList &aDeleteTimes); /*! \brief Returns the deleted items after given time * * @param aItems Returned deleted items * @param aTime Items deleted after this time are considered deleted * @return True on success, otherwise false */ bool getDeletedItems(QList &aItems, const QDateTime &aTime); protected: /** * \brief Checks whether snapshot table exists and creates it if needed * @return True on success, otherwise false */ bool ensureItemSnapshotExists(); /** * \brief Checks whether item id table exists and creates it if needed * @return True on success, otherwise false */ bool ensureDeletedItemsExists(); private: QSqlDatabase iDb; ///< Database handle QString iConnectionName; ///< Database connection ID string }; } #endif buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/OOPClientPlugin.cpp000066400000000000000000000170261477124122200250310ustar00rootroot00000000000000/* * This file is part of buteo-sync-plugins package * * Copyright (C) 2013 Jolla Ltd. * * Author: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include "OOPClientPlugin.h" #include "LogMacros.h" #include using namespace Buteo; OOPClientPlugin::OOPClientPlugin(const QString &aPluginName, const SyncProfile &aProfile, PluginCbInterface *aCbInterface, QProcess &aProcess) : ClientPlugin(aPluginName, aProfile, aCbInterface), iDone(false) { FUNCTION_CALL_TRACE(lcButeoTrace); // randomly-generated profile names cannot be registered // as dbus service paths due to being purely numeric. QString profileName = aProfile.name(); int numericIdx = profileName.indexOf(QRegExp("[0123456789]")); QString servicePath = numericIdx == 0 ? QString(QLatin1String("%1%2%3")) .arg(DBUS_SERVICE_NAME_PREFIX) .arg("profile-") .arg(profileName) : QString(QLatin1String("%1%2")) .arg(DBUS_SERVICE_NAME_PREFIX) .arg(profileName); // Initialise dbus for client iOopPluginIface = new ButeoPluginIface(servicePath, DBUS_SERVICE_OBJ_PATH, QDBusConnection::sessionBus()); iOopPluginIface->setTimeout(60000); // one minute. // Chain the signals received over dbus connect(iOopPluginIface, SIGNAL(transferProgress(const QString &, Sync::TransferDatabase, Sync::TransferType, const QString &, int)), this, SIGNAL(transferProgress(const QString &, Sync::TransferDatabase, Sync::TransferType, const QString &, int))); connect(iOopPluginIface, SIGNAL(error(QString, QString, int)), this, SLOT(onError(QString, QString, int))); connect(iOopPluginIface, SIGNAL(success(QString, QString)), this, SLOT(onSuccess(QString, QString))); connect(iOopPluginIface, SIGNAL(accquiredStorage(const QString &)), this, SIGNAL(accquiredStorage(const QString &))); connect(iOopPluginIface, SIGNAL(syncProgressDetail(const QString &, int)), this, SIGNAL(syncProgressDetail(const QString &, int))); // Handle the signals from the process connect(&aProcess, SIGNAL(error(QProcess::ProcessError)), this, SLOT(onProcessError(QProcess::ProcessError))); connect(&aProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(onProcessFinished(int, QProcess::ExitStatus))); } OOPClientPlugin::~OOPClientPlugin() { delete iOopPluginIface; iOopPluginIface = 0; } bool OOPClientPlugin::init() { FUNCTION_CALL_TRACE(lcButeoTrace); QDBusPendingReply reply = iOopPluginIface->init(); reply.waitForFinished(); if (!reply.isValid()) { qCWarning(lcButeoCore) << "Invalid reply for init from plugin" ; return false; } return reply.value(); } bool OOPClientPlugin::uninit() { FUNCTION_CALL_TRACE(lcButeoTrace); QDBusPendingReply reply = iOopPluginIface->uninit(); reply.waitForFinished(); if (!reply.isValid()) { qCWarning(lcButeoCore) << "Invalid reply for uninit from plugin" ; return false; } return reply.value(); } bool OOPClientPlugin::startSync() { FUNCTION_CALL_TRACE(lcButeoTrace); QDBusPendingReply reply = iOopPluginIface->startSync(); reply.waitForFinished(); if (!reply.isValid()) { qCWarning(lcButeoCore) << "Invalid reply for startSync from plugin" ; return false; } return reply.value(); } void OOPClientPlugin::abortSync(Sync::SyncStatus aStatus) { FUNCTION_CALL_TRACE(lcButeoTrace); QDBusPendingReply reply = iOopPluginIface->abortSync((uchar) aStatus); reply.waitForFinished(); if (!reply.isValid()) qCWarning(lcButeoCore) << "Invalid reply for abortSync from plugin" ; } bool OOPClientPlugin::cleanUp() { FUNCTION_CALL_TRACE(lcButeoTrace); QDBusPendingReply reply = iOopPluginIface->cleanUp(); reply.waitForFinished(); if (!reply.isValid()) { qCWarning(lcButeoCore) << "Invalid reply for cleanUp from plugin" ; return false; } return reply.value(); } SyncResults OOPClientPlugin::getSyncResults() const { FUNCTION_CALL_TRACE(lcButeoTrace); QDBusPendingReply reply = iOopPluginIface->getSyncResults(); reply.waitForFinished(); if (!reply.isValid()) { qCWarning(lcButeoCore) << "Invalid reply for getSyncResults from plugin" ; return SyncResults(QDateTime::currentDateTime(), SyncResults::SYNC_RESULT_INVALID, SyncResults::PLUGIN_ERROR); } QString resultAsXml = reply.value(); QDomDocument doc; if (doc.setContent(resultAsXml, true)) { SyncResults syncResult(doc.documentElement()); return syncResult; } else { qCCritical(lcButeoCore) << "Invalid sync results returned from plugin" ; return SyncResults(QDateTime::currentDateTime(), SyncResults::SYNC_RESULT_INVALID, SyncResults::NO_ERROR); } } void OOPClientPlugin::connectivityStateChanged(Sync::ConnectivityType aType, bool aState) { FUNCTION_CALL_TRACE(lcButeoTrace); QDBusPendingReply reply = iOopPluginIface->connectivityStateChanged(aType, aState); reply.waitForFinished(); if (!reply.isValid()) qCWarning(lcButeoCore) << "Invalid reply for connectivityStateChanged from plugin" ; } void OOPClientPlugin::onProcessError(QProcess::ProcessError error) { if (!iDone) { onError(iProfile.name(), "Plugin process error:" + QString::number(error), SyncResults::PLUGIN_ERROR); } } void OOPClientPlugin::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { if (!iDone) { if ((exitCode != 0) || (exitStatus != QProcess::NormalExit)) { onError(iProfile.name(), "Plugin process exited with error code " + QString::number(exitCode) + " and status " + QString::number(exitStatus), SyncResults::PLUGIN_ERROR); } else { onError(iProfile.name(), "Plugin process exited unexpectedly", SyncResults::PLUGIN_ERROR); } } } void OOPClientPlugin::onError(QString aProfileName, QString aMessage, int aErrorCode) { if (!iDone) { iDone = true; emit error(aProfileName, aMessage, static_cast(aErrorCode)); } } void OOPClientPlugin::onSuccess(QString aProfileName, QString aMessage) { if (!iDone) { iDone = true; emit success(aProfileName, aMessage); } } buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/OOPClientPlugin.h000066400000000000000000000036301477124122200244720ustar00rootroot00000000000000/* * This file is part of buteo-sync-plugins package * * Copyright (C) 2013 Jolla Ltd. * * Author: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef OOPCLIENTPLUGIN_H #define OOPCLIENTPLUGIN_H #include #include namespace Buteo { class OOPClientPlugin : public ClientPlugin { Q_OBJECT public: OOPClientPlugin(const QString &aPluginName, const Buteo::SyncProfile &aProfile, Buteo::PluginCbInterface *aCbInterface, QProcess &aProcess); virtual ~OOPClientPlugin(); virtual bool init(); virtual bool uninit(); virtual bool startSync(); virtual void abortSync(Sync::SyncStatus aStatus = Sync::SYNC_ABORTED); virtual Buteo::SyncResults getSyncResults() const; virtual bool cleanUp(); public slots: virtual void connectivityStateChanged(Sync::ConnectivityType aType, bool aState); void onProcessError(QProcess::ProcessError error); void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); void onError(QString aProfileName, QString aMessage, int aErrorCode); void onSuccess(QString aProfileName, QString aMessage); private: bool iDone; }; } #endif // OOPCLIENTPLUGIN_H buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/OOPServerPlugin.cpp000066400000000000000000000161371477124122200250630ustar00rootroot00000000000000/* * This file is part of buteo-sync-plugins package * * Copyright (C) 2013 Jolla Ltd. * * Author: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "OOPServerPlugin.h" #include "LogMacros.h" #include using namespace Buteo; OOPServerPlugin::OOPServerPlugin(const QString &aPluginName, const Profile &aProfile, PluginCbInterface *aCbInterface, QProcess &aProcess) : ServerPlugin(aPluginName, aProfile, aCbInterface), iDone(false) { FUNCTION_CALL_TRACE(lcButeoTrace); // randomly-generated profile names cannot be registered // as dbus service paths due to being purely numeric. QString profileName = aProfile.name(); int numericIdx = profileName.indexOf(QRegExp("[0123456789]")); QString servicePath = numericIdx == 0 ? QString(QLatin1String("%1%2%3")) .arg(DBUS_SERVICE_NAME_PREFIX) .arg("profile-") .arg(profileName) : QString(QLatin1String("%1%2")) .arg(DBUS_SERVICE_NAME_PREFIX) .arg(profileName); // Initialise dbus for server iOopPluginIface = new ButeoPluginIface(servicePath, DBUS_SERVICE_OBJ_PATH, QDBusConnection::sessionBus()); iOopPluginIface->setTimeout(60000); // one minute. // Chain the signals received over dbus connect(iOopPluginIface, SIGNAL(transferProgress(const QString &, Sync::TransferDatabase, Sync::TransferType, const QString &, int)), this, SIGNAL(transferProgress(const QString &, Sync::TransferDatabase, Sync::TransferType, const QString &, int))); connect(iOopPluginIface, SIGNAL(error(QString, QString, int)), this, SLOT(onError(QString, QString, int))); connect(iOopPluginIface, SIGNAL(success(QString, QString)), this, SLOT(onSuccess(QString, QString))); connect(iOopPluginIface, SIGNAL(accquiredStorage(const QString &)), this, SIGNAL(accquiredStorage(const QString &))); connect(iOopPluginIface, SIGNAL(newSession(const QString &)), this, SIGNAL(newSession(const QString &))); // Handle the signals from the process connect(&aProcess, SIGNAL(error(QProcess::ProcessError)), this, SLOT(onProcessError(QProcess::ProcessError))); connect(&aProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(onProcessFinished(int, QProcess::ExitStatus))); } OOPServerPlugin::~OOPServerPlugin() { FUNCTION_CALL_TRACE(lcButeoTrace); delete iOopPluginIface; iOopPluginIface = 0; } bool OOPServerPlugin::init() { FUNCTION_CALL_TRACE(lcButeoTrace); QDBusPendingReply reply = iOopPluginIface->init(); reply.waitForFinished(); if (!reply.isValid()) { qCWarning(lcButeoCore) << "Invalid reply for init from plugin" ; return false; } return reply.value(); } bool OOPServerPlugin::uninit() { FUNCTION_CALL_TRACE(lcButeoTrace); QDBusPendingReply reply = iOopPluginIface->uninit(); reply.waitForFinished(); if (!reply.isValid()) { qCWarning(lcButeoCore) << "Invalid reply for startSync from plugin" ; return false; } return reply.value(); } bool OOPServerPlugin::startListen() { FUNCTION_CALL_TRACE(lcButeoTrace); QDBusPendingReply reply = iOopPluginIface->startListen(); reply.waitForFinished(); if (!reply.isValid()) { qCWarning(lcButeoCore) << "Invalid reply for startListen from plugin" ; return false; } return reply.value(); } void OOPServerPlugin::stopListen() { FUNCTION_CALL_TRACE(lcButeoTrace); QDBusPendingReply reply = iOopPluginIface->stopListen(); reply.waitForFinished(); if (!reply.isValid()) qCWarning(lcButeoCore) << "Invalid reply for stopListen from plugin" ; } void OOPServerPlugin::suspend() { FUNCTION_CALL_TRACE(lcButeoTrace); QDBusPendingReply reply = iOopPluginIface->suspend(); reply.waitForFinished(); if (!reply.isValid()) qCWarning(lcButeoCore) << "Invalid reply for suspend from plugin" ; } void OOPServerPlugin::resume() { FUNCTION_CALL_TRACE(lcButeoTrace); QDBusPendingReply reply = iOopPluginIface->resume(); reply.waitForFinished(); if (!reply.isValid()) qCWarning(lcButeoCore) << "Invalid reply for resume from plugin" ; } bool OOPServerPlugin::cleanUp() { FUNCTION_CALL_TRACE(lcButeoTrace); QDBusPendingReply reply = iOopPluginIface->cleanUp(); reply.waitForFinished(); if (!reply.isValid()) { qCWarning(lcButeoCore) << "Invalid reply for cleanUp from plugin" ; return false; } return reply.value(); } void OOPServerPlugin::connectivityStateChanged(Sync::ConnectivityType aType, bool aState) { FUNCTION_CALL_TRACE(lcButeoTrace); QDBusPendingReply reply = iOopPluginIface->connectivityStateChanged(aType, aState); reply.waitForFinished(); if (!reply.isValid()) qCWarning(lcButeoCore) << "Invalid reply for connectivityStateChanged from plugin" ; } void OOPServerPlugin::onProcessError(QProcess::ProcessError error) { if (!iDone) { onError(iProfile.name(), "Plugin process error:" + QString::number(error), SyncResults::PLUGIN_ERROR); } } void OOPServerPlugin::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { if (!iDone) { if ((exitCode != 0) || (exitStatus != QProcess::NormalExit)) { onError(iProfile.name(), "Plugin process exited with error code " + QString::number(exitCode) + " and status " + QString::number(exitStatus), SyncResults::PLUGIN_ERROR); } else { onError(iProfile.name(), "Plugin process exited unexpectedly", SyncResults::PLUGIN_ERROR); } } } void OOPServerPlugin::onError(QString aProfileName, QString aMessage, int aErrorCode) { if (!iDone) { iDone = true; emit error(aProfileName, aMessage, static_cast(aErrorCode)); } } void OOPServerPlugin::onSuccess(QString aProfileName, QString aMessage) { if (!iDone) { iDone = true; emit success(aProfileName, aMessage); } } buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/OOPServerPlugin.h000066400000000000000000000034561477124122200245300ustar00rootroot00000000000000/* * This file is part of buteo-sync-plugins package * * Copyright (C) 2013 Jolla Ltd. * * Author: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef OOPSERVERPLUGIN_H #define OOPSERVERPLUGIN_H #include #include namespace Buteo { class OOPServerPlugin : public ServerPlugin { Q_OBJECT public: OOPServerPlugin(const QString &aPluginName, const Profile &aProfile, PluginCbInterface *aCbInterface, QProcess &process); virtual ~OOPServerPlugin(); virtual bool init(); virtual bool uninit(); virtual bool startListen(); virtual void stopListen(); virtual void suspend(); virtual void resume(); virtual bool cleanUp(); public slots: virtual void connectivityStateChanged(Sync::ConnectivityType aType, bool aState); void onProcessError(QProcess::ProcessError error); void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); void onError(QString aProfileName, QString aMessage, int aErrorCode); void onSuccess(QString aProfileName, QString aMessage); private: bool iDone; }; } #endif // OOPSERVERPLUGIN_H buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/PluginCbInterface.h000066400000000000000000000073641477124122200250530ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef PLUGINCBINTERFACE_H #define PLUGINCBINTERFACE_H #include #include "SyncPluginBase.h" namespace Buteo { class StoragePlugin; class Profile; /*! \brief Interface which client and server plugins can use to communicate with * synchronization daemon */ class PluginCbInterface { public: virtual ~PluginCbInterface() {} /*! \brief Tries to reserve the given storage to the caller. * * Server plug-ins must reserve storages before using them. For client * plug-ins this is done automatically by the sync daemon when sync starts, * based on profiles. * Release must be called when the successfully requested storage is not * needed anymore. * @param aStorageName Name of the storage backend to reserve. * @param aCaller Object calling this function * @return Success indicator */ virtual bool requestStorage(const QString &aStorageName, const SyncPluginBase *aCaller) = 0; /*! \brief Releases the given storage. * * Call this function after a storage requested with requestStorage is not * needed anymore by the caller. * \param aStorageName Name of the storage backend to release. * \param aCaller Object calling this function. */ virtual void releaseStorage(const QString &aStorageName, const SyncPluginBase *aCaller) = 0; /*! \brief Creates a storage plug-in instance. * * Server plug-ins must reserve the storage backend before creating a * plug-in that uses it. Otherwise simultaenous access of the same backend * may lead to inconsistent state. * \param aPluginName Name of the storage plug-in to create. * \return Created plug-in instance. NULL if failed. */ virtual StoragePlugin *createStorage(const QString &aPluginName) = 0; /*! \brief Destroys the given storage plug-in instance. * * \param aStorage Storage plug-in to destroy. */ virtual void destroyStorage(StoragePlugin *aStorage) = 0; /*! \brief Returns whether connectivity domain is available * * @param aType Type of connectivity domain * @return True if connectivity domain is available, otherwise false */ virtual bool isConnectivityAvailable(Sync::ConnectivityType aType) = 0; /*! \brief tries to fetch a profile object based on the remote party's * address (BT address for eg) * * @param aAddress remote party's address * @return profile object - to be deleted by caller */ virtual Profile *getSyncProfileByRemoteAddress(const QString &aAddress) = 0; /*! \brief Get a value for a property describing the remote device * * @param aAddress remote device's address * @param aKey profile key identifying the property * @return value for the property */ virtual QString getValue(const QString &aAddress, const QString &aKey) = 0; }; } #endif buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/PluginManager.cpp000066400000000000000000000456561477124122200246210ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2014-2021 Jolla Ltd. * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "PluginManager.h" #include #include #include #include #include #include "StoragePlugin.h" #include "ServerPlugin.h" #include "ClientPlugin.h" #include "StorageChangeNotifierPlugin.h" #include "OOPClientPlugin.h" #include "OOPServerPlugin.h" #include "SyncPluginLoader.h" #include "StoragePluginLoader.h" #include "StorageChangeNotifierPluginLoader.h" #include "LogMacros.h" namespace { // Location filters of plugin maps const QString STORAGEMAP_LOCATION = "-storage.so"; const QString CLIENTMAP_LOCATION = "-client.so"; const QString SERVERMAP_LOCATION = "-server.so"; const QString STORAGECHANGENOTIFIERMAP_LOCATION = "-changenotifier.so"; bool killProcess(const QString &exePath, const QStringList &args) { const QByteArray expectedCmdLine = (exePath + args.join(QString())).toUtf8(); const QString exeFullPath = QFileInfo(exePath).absolutePath(); const QDir proc("/proc"); QStringList entries = proc.entryList(QDir::Dirs | QDir::NoDotAndDotDot); for (const QString &entry : entries) { int pid = entry.toInt(); if (pid) { QString exe = QFile::symLinkTarget(proc.filePath(entry).append("/exe")); if (exeFullPath == exe) { QFile cmdlineFile(proc.filePath(entry).append("/cmdline")); if (!cmdlineFile.open(QFile::ReadOnly) || cmdlineFile.readAll() != expectedCmdLine) { continue; } if (kill(pid, SIGTERM) == 0) { qCDebug(lcButeoCore) << "Process" << pid << "has been killed"; return true; } else { qCWarning(lcButeoCore) << "Failed to kill" << exePath << args << "[" << pid << "]" << strerror(errno); return false; } } } } return false; } } using namespace Buteo; PluginManager::PluginManager() : PluginManager(QStringLiteral(DEFAULT_PLUGIN_PATH)) { } PluginManager::PluginManager(const QString &aPluginPath) : iPluginPath(aPluginPath) { FUNCTION_CALL_TRACE(lcButeoTrace); if (!iPluginPath.isEmpty() && !iPluginPath.endsWith('/')) { iPluginPath.append('/'); } loadPluginMaps(iPluginPath, STORAGECHANGENOTIFIERMAP_LOCATION, iStorageChangeNotifierMaps); loadPluginMaps(iPluginPath, STORAGEMAP_LOCATION, iStorageMaps); loadPluginMaps(iPluginPath, CLIENTMAP_LOCATION, iClientMaps); loadPluginMaps(iPluginPath, SERVERMAP_LOCATION, iServerMaps); const QString ooppPath = iPluginPath + "oopp/"; loadPluginMaps(ooppPath, CLIENTMAP_LOCATION, iOopClientMaps); loadPluginMaps(ooppPath, SERVERMAP_LOCATION, iOoPServerMaps); } PluginManager::~PluginManager() { FUNCTION_CALL_TRACE(lcButeoTrace); for (int i = 0; i < iLoadedDlls.count(); ++i) { iLoadedDlls[i].cleanUp(); } } StorageChangeNotifierPlugin *PluginManager::createStorageChangeNotifier(const QString &aStorageName) { FUNCTION_CALL_TRACE(lcButeoTrace); if (!iStorageChangeNotifierMaps.contains(aStorageName)) { qCCritical(lcButeoCore) << "Library for the storage change notifier" << aStorageName << "does not exist" ; return nullptr; } QString libraryName = iStorageChangeNotifierMaps.value(aStorageName); if (StorageChangeNotifierPlugin * plugin = qobject_cast(acquireLoadedPlugin(libraryName))) { return plugin; } QPluginLoader *pluginLoader = new QPluginLoader(libraryName, this); if (StorageChangeNotifierPluginLoader * notifierPluginLoader = qobject_cast(pluginLoader->instance())) { StorageChangeNotifierPlugin *plugin = notifierPluginLoader->createPlugin(aStorageName); if (plugin) { addLoadedPlugin(libraryName, pluginLoader, plugin); return plugin; } } qCWarning(lcButeoCore) << "Unable to load plugin " << libraryName << " from name " << aStorageName; pluginLoader->unload(); delete pluginLoader; return nullptr; } void PluginManager::destroyStorageChangeNotifier(StorageChangeNotifierPlugin *aPlugin) { FUNCTION_CALL_TRACE(lcButeoTrace); if (aPlugin == 0) return; QString storageName = aPlugin->name(); if (!iStorageChangeNotifierMaps.contains(storageName)) { qCCritical(lcButeoCore) << "Library for the storage change notifier" << storageName << "does not exist" ; return; } unloadPlugin(iStorageChangeNotifierMaps.value(storageName)); } StoragePlugin *PluginManager::createStorage(const QString &aPluginName) { FUNCTION_CALL_TRACE(lcButeoTrace); if (!iStorageMaps.contains(aPluginName)) { qCCritical(lcButeoCore) << "Library for the storage" << aPluginName << "does not exist" ; return nullptr; } QString libraryName = iStorageMaps.value(aPluginName); if (StoragePlugin * plugin = qobject_cast(acquireLoadedPlugin(libraryName))) { return plugin; } QPluginLoader *pluginLoader = new QPluginLoader(libraryName, this); if (StoragePluginLoader * storagePluginLoader = qobject_cast(pluginLoader->instance())) { StoragePlugin *plugin = storagePluginLoader->createPlugin(aPluginName); if (plugin) { addLoadedPlugin(libraryName, pluginLoader, plugin); return plugin; } } qCWarning(lcButeoCore) << "Unable to load plugin " << libraryName << " from name " << aPluginName; pluginLoader->unload(); delete pluginLoader; return nullptr; } void PluginManager::destroyStorage(StoragePlugin *aPlugin) { FUNCTION_CALL_TRACE(lcButeoTrace); if (aPlugin == 0) return; QString pluginName = aPlugin->getPluginName(); if (!iStorageMaps.contains(pluginName)) { qCCritical(lcButeoCore) << "Library for the storage" << pluginName << "does not exist" ; return; } unloadPlugin(iStorageMaps.value(pluginName)); } ClientPlugin *PluginManager::createClient(const QString &aPluginName, const SyncProfile &aProfile, PluginCbInterface *aCbInterface) { FUNCTION_CALL_TRACE(lcButeoTrace); if (!iClientMaps.contains(aPluginName) && !iOopClientMaps.contains(aPluginName)) { qCCritical(lcButeoCore) << "Library for the client" << aPluginName << "does not exist" ; return nullptr; } if (iClientMaps.contains(aPluginName)) { QString libraryName = iClientMaps.value(aPluginName); if (ClientPlugin * plugin = qobject_cast(acquireLoadedPlugin(libraryName))) { return plugin; } QPluginLoader *pluginLoader = new QPluginLoader(libraryName, this); if (SyncPluginLoader * syncPluginLoader = qobject_cast(pluginLoader->instance())) { ClientPlugin *plugin = syncPluginLoader->createClientPlugin(aPluginName, aProfile, aCbInterface); if (plugin) { addLoadedPlugin(libraryName, pluginLoader, plugin); return plugin; } } qCWarning(lcButeoCore) << "Unable to load plugin " << libraryName << " from name " << aPluginName; pluginLoader->unload(); delete pluginLoader; return nullptr; } else if (iOopClientMaps.contains(aPluginName)) { // Start the out of process plugin const QString libraryName = iOopClientMaps.value(aPluginName); QProcess *process = startOOPPlugin(aPluginName, aProfile.name(), libraryName); if (process == nullptr) { qCCritical(lcButeoCore) << "Could not start process" ; return nullptr; } // Create the client plugin interface to talk to the process return new OOPClientPlugin(aPluginName, aProfile, aCbInterface, *process); } return nullptr; } void PluginManager::destroyClient(ClientPlugin *aPlugin) { FUNCTION_CALL_TRACE(lcButeoTrace); if (aPlugin == 0) return; QString pluginName = aPlugin->getPluginName(); if (!iClientMaps.contains(pluginName) && !iOopClientMaps.contains(pluginName)) { qCCritical(lcButeoCore) << "Library for the client plugin" << pluginName << "does not exist" ; return; } if (iClientMaps.contains(pluginName)) { unloadPlugin(iClientMaps.value(pluginName)); } else if (iOopClientMaps.contains(pluginName)) { // Stop the OOP process qCDebug(lcButeoCore) << "Stopping the OOP process for " << pluginName; QString path = iOopClientMaps.value(pluginName); stopOOPPlugin(path); delete aPlugin; } } ServerPlugin *PluginManager::createServer(const QString &aPluginName, const Profile &aProfile, PluginCbInterface *aCbInterface) { FUNCTION_CALL_TRACE(lcButeoTrace); if (!iServerMaps.contains(aPluginName) && !iOoPServerMaps.contains(aPluginName)) { qCCritical(lcButeoCore) << "Library for the server" << aPluginName << "does not exist" ; return nullptr; } if (iServerMaps.contains(aPluginName)) { // Load the plugin library QString libraryName = iServerMaps.value(aPluginName); if (ServerPlugin * plugin = qobject_cast(acquireLoadedPlugin(libraryName))) { return plugin; } QPluginLoader *pluginLoader = new QPluginLoader(libraryName, this); if (SyncPluginLoader * syncPluginLoader = qobject_cast(pluginLoader->instance())) { ServerPlugin *plugin = syncPluginLoader->createServerPlugin(aPluginName, aProfile, aCbInterface); if (plugin) { addLoadedPlugin(libraryName, pluginLoader, plugin); return plugin; } } qCWarning(lcButeoCore) << "Unable to load plugin " << libraryName << " from name " << aPluginName; pluginLoader->unload(); delete pluginLoader; return nullptr; } else if (iOoPServerMaps.contains(aPluginName)) { // Start the Oop process plugin const QString libraryName = iOoPServerMaps.value(aPluginName); QProcess *process = startOOPPlugin(aPluginName, aProfile.name(), libraryName); if (process == nullptr) { qCCritical(lcButeoCore) << "Could not start server plugin process" ; return nullptr; } return new OOPServerPlugin(aPluginName, aProfile, aCbInterface, *process); } return nullptr; } void PluginManager::destroyServer(ServerPlugin *aPlugin) { FUNCTION_CALL_TRACE(lcButeoTrace); if (aPlugin == 0) return; QString pluginName = aPlugin->getPluginName(); if (!iServerMaps.contains(pluginName) && !iOoPServerMaps.contains(pluginName)) { qCCritical(lcButeoCore) << "Library for the server plugin" << pluginName << "does not exist" ; return; } if (iServerMaps.contains(pluginName)) { // Unload the server plugin library unloadPlugin(iServerMaps.value(pluginName)); } else if (iOoPServerMaps.contains(pluginName)) { // Stop the OOP server process QString path = iOoPServerMaps.value(pluginName); stopOOPPlugin(path); delete aPlugin; } } void PluginManager::loadPluginMaps(const QString &pluginDirPath, const QString &aFilter, QMap &aTargetMap) { FUNCTION_CALL_TRACE(lcButeoTrace); QDir pluginDirectory(pluginDirPath); QStringList entries = pluginDirectory.entryList(QDir::Files); QStringList::const_iterator listIterator = entries.constBegin(); while (listIterator != entries.constEnd()) { QString file = (*listIterator); if (!file.endsWith(aFilter)) { ++listIterator; continue; } // Remove filter from end file.chop(aFilter.length()); // Remove lib file.remove(0, 3); aTargetMap[file] = pluginDirPath + (*listIterator); ++listIterator; } } QProcess *PluginManager::startOOPPlugin(const QString &aPluginName, const QString &aProfileName, const QString &aPluginFilePath) { FUNCTION_CALL_TRACE(lcButeoTrace); static const QString exePath = "/usr/libexec/buteo-oopp-runner"; bool started = false; QStringList args; args << aPluginName << aProfileName << aPluginFilePath; if (killProcess(exePath, args)) { qCInfo(lcButeoCore) << "Killed runaway plugin" << aProfileName; } qCDebug(lcButeoCore) << "Starting out-of-process plugin " << aPluginFilePath << " with plugin name " << aPluginName << " and profile name " << aProfileName; QProcess *process = new QProcess(); process->setProcessChannelMode(QProcess::ForwardedChannels); process->start(exePath, args); const QString clientPluginDBusServiceName(QString(QLatin1String("com.buteo.msyncd.plugin.profile-%1")).arg( aProfileName)); const QString serverPluginDBusServiceName(QString(QLatin1String("com.buteo.msyncd.plugin.%1")).arg(aProfileName)); bool pluginHasRegistered = false; for (int i = 0; i < 30; i++) { // wait for up to thirty seconds for the process to register with dbus QThread::sleep(1); // sleep for a second to wait for the process to be scheduled, init and register with dbus QDBusReply clientServiceRegistered = QDBusConnection::sessionBus().interface()->isServiceRegistered( clientPluginDBusServiceName); if (clientServiceRegistered.isValid() && clientServiceRegistered.value()) { pluginHasRegistered = true; break; } QDBusReply serverServiceRegistered = QDBusConnection::sessionBus().interface()->isServiceRegistered( serverPluginDBusServiceName); if (serverServiceRegistered.isValid() && serverServiceRegistered.value()) { pluginHasRegistered = true; break; } } if (process->state() == QProcess::Starting) { started = process->waitForStarted(); } else { started = process->state() == QProcess::Running; } if (started) { DllInfo info; info.iPath = aPluginFilePath; info.iHandle = process; iDllLock.lockForWrite(); iLoadedDlls.append(info); iDllLock.unlock(); qCDebug(lcButeoCore) << "Process " << process->program() << " started with pid " << process->pid() ; if (!pluginHasRegistered) { qCDebug(lcButeoCore) << "Process " << process->program() << " with pid " << process->pid() << "was unable to register DBus service: " << clientPluginDBusServiceName << "|" << serverPluginDBusServiceName ; } connect(process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(onProcessFinished(int, QProcess::ExitStatus))); return process; } else { qCCritical(lcButeoCore) << "Unable to start process plugin " << aPluginFilePath << ". Error " << process->error(); delete process; return nullptr; } } void PluginManager::stopOOPPlugin(const QString &aPath) { FUNCTION_CALL_TRACE(lcButeoTrace); QProcess *process = nullptr; iDllLock.lockForWrite(); for (int i = 0; i < iLoadedDlls.size(); ++i) { if (iLoadedDlls[i].iPath == aPath) { process = iLoadedDlls[i].iHandle; break; } } iDllLock.unlock(); // We must terminate the process outside of the locked section because // onProcessFinished handler below will want to acquire the same lock. // It will also schedule the deletion of the QProcess object. if (process) { process->terminate(); if (process->waitForFinished() == false) process->kill(); } } void PluginManager::onProcessFinished(int exitCode, QProcess::ExitStatus) { FUNCTION_CALL_TRACE(lcButeoTrace); QProcess *process = (QProcess *)sender(); qCDebug(lcButeoCore) << "Process " << process->program() << " finished with exit code" << exitCode ; iDllLock.lockForWrite(); for (int i = 0; i < iLoadedDlls.size(); ++i) { if (iLoadedDlls[i].iHandle == process) { iLoadedDlls.removeAt(i); break; } } iDllLock.unlock(); process->deleteLater(); } void PluginManager::addLoadedPlugin(const QString &libraryName, QPluginLoader *pluginLoader, QObject *plugin) { DllInfo info; info.iPath = libraryName; info.iPluginLoader = pluginLoader; info.iLoadedPlugin = plugin; info.iRefCount = 1; iDllLock.lockForWrite(); iLoadedDlls.append(info); iDllLock.unlock(); } QObject *PluginManager::acquireLoadedPlugin(const QString &libraryName) { QObject *plugin = nullptr; iDllLock.lockForWrite(); for (int i = 0; i < iLoadedDlls.count(); ++i) { if (iLoadedDlls[i].iPath == libraryName) { iLoadedDlls[i].iRefCount++; plugin = iLoadedDlls[i].iLoadedPlugin; break; } } iDllLock.unlock(); return plugin; } void PluginManager::unloadPlugin(const QString &libraryName) { iDllLock.lockForWrite(); for (int i = 0; i < iLoadedDlls.count(); ++i) { if (iLoadedDlls[i].iPath == libraryName && --iLoadedDlls[i].iRefCount == 0) { DllInfo info = iLoadedDlls.takeAt(i); info.cleanUp(); break; } } iDllLock.unlock(); } void PluginManager::DllInfo::cleanUp() { delete iLoadedPlugin; if (iPluginLoader) { iPluginLoader->unload(); delete iPluginLoader; } } buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/PluginManager.h000066400000000000000000000121551477124122200242520ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2014-2021 Jolla Ltd. * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef PLUGINMANAGER_H #define PLUGINMANAGER_H #include #include #include #include #include class QPluginLoader; class QProcess; namespace Buteo { class StorageChangeNotifierPlugin; class StoragePlugin; class ClientPlugin; class ServerPlugin; class PluginCbInterface; class SyncProfile; class Profile; class ClientPluginTest; class ServerPluginTest; class StoragePluginTest; /*! \brief Manages plugins * * Is responsible for creating and destroying storage, * server and client plugins. */ class PluginManager : public QObject { Q_OBJECT public: PluginManager(); /*! \brief Constructor * * @param aPluginPath Path where plugins are stored */ PluginManager(const QString &aPluginPath); /*! \brief Destructor * */ ~PluginManager(); /*! \brief Creates a new storage change notifier plugin * for the storage aStoragName * * @param aStorageName well-known name of the storage */ StorageChangeNotifierPlugin *createStorageChangeNotifier(const QString &aStorageName); /*! \brief Destroys a storage change notifier plugin instance * * @param aStorageName well-known storage name of the plugin to be destroyed */ void destroyStorageChangeNotifier(StorageChangeNotifierPlugin *aPlugin); /*! \brief Creates a new storage plugin instance * * @param aPluginName Name of the plugin * @return Storage plugin if success, otherwise NULL */ StoragePlugin *createStorage(const QString &aPluginName); /*! \brief Destroys a storage plugin instance * * @param aPlugin Plugin to destroy */ void destroyStorage(StoragePlugin *aPlugin); /*! \brief Creates a new client plugin instance * * @param aPluginName Name of the plugin * @param aProfile Sync profile * @param aCbInterface Callback interface * @return Client plugin on success, otherwise NULL */ ClientPlugin *createClient(const QString &aPluginName, const SyncProfile &aProfile, PluginCbInterface *aCbInterface); /*! \brief Destroys a client plugin instance * * @param aPlugin Plugin to destroy */ void destroyClient(ClientPlugin *aPlugin); /*! \brief Creates a new server plugin instance * * @param aPluginName Name of the plugin * @param aProfile Server profile * @param aCbInterface Callback interface * @return Server plugin on success, otherwise NULL */ ServerPlugin *createServer(const QString &aPluginName, const Profile &aProfile, PluginCbInterface *aCbInterface); /*! \brief Destroys a server plugin * * @param aPlugin Plugin to destroy */ void destroyServer(ServerPlugin *aPlugin); protected slots: void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); private: class DllInfo { public: void cleanUp(); QString iPath; QProcess *iHandle = nullptr; QPluginLoader *iPluginLoader = nullptr; QPointer iLoadedPlugin; int iRefCount = 0; }; void loadPluginMaps(const QString &pluginDirPath, const QString &aFilter, QMap &aTargetMap); QProcess *startOOPPlugin(const QString &aPluginName, const QString &aProfileName, const QString &aPluginFilePath); void stopOOPPlugin(const QString &aPath); void addLoadedPlugin(const QString &libraryName, QPluginLoader *pluginLoader, QObject *plugin); QObject *acquireLoadedPlugin(const QString &libraryName); void unloadPlugin(const QString &libraryName); QString iPluginPath; QMap iStorageChangeNotifierMaps; QMap iStorageMaps; QMap iClientMaps; QMap iServerMaps; QMap iOopClientMaps; QMap iOoPServerMaps; QList iLoadedDlls; QReadWriteLock iDllLock; QString iProcBinaryPath; #ifdef SYNCFW_UNIT_TESTS friend class ClientPluginTest; friend class ServerPluginTest; friend class StoragePluginTest; #endif }; } #endif buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/ServerPlugin.cpp000066400000000000000000000022671477124122200245040ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "ServerPlugin.h" using namespace Buteo; ServerPlugin::ServerPlugin(const QString &aPluginName, const Profile &aProfile, PluginCbInterface *aCbInterface) : SyncPluginBase(aPluginName, aProfile.name(), aCbInterface), iProfile(aProfile) { } ServerPlugin::~ServerPlugin() { } buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/ServerPlugin.h000066400000000000000000000047461477124122200241550ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SERVERPLUGIN_H_4399340943904399349843 #define SERVERPLUGIN_H_4399340943904399349843 #include "SyncPluginBase.h" #include "Profile.h" #include #include namespace Buteo { /*! \brief Base class for server plugins * */ class ServerPlugin : public SyncPluginBase { Q_OBJECT public: /*! \brief Constructor * * @param aPluginName Name of this plugin * @param aProfile Server profile * @param aCbInterface Pointer to the callback interface */ ServerPlugin(const QString &aPluginName, const Profile &aProfile, PluginCbInterface *aCbInterface); /*! \brief Destructor * */ virtual ~ServerPlugin(); /*! \brief Start listening for sync requests. * * Init must me called before this function. * @return True on success, otherwise false */ virtual bool startListen() = 0; /*! \brief Stop listening for sync requests */ virtual void stopListen() = 0; /*! \brief Suspend activity * * Implement this if upon being asked to suspend for some reason, any ongoing * activity can be suspended */ virtual void suspend() = 0; /*! \brief Resume suspended activity * */ virtual void resume() = 0; signals: /*! \brief Signal sent when a new sync session is received by the server * * @param aDestination Sync destination address, for example BT address * or URL. */ void newSession(const QString &aDestination); protected: //! Profile Object that the server plugin operates on Profile iProfile; }; } #endif //SERVERPLUGIN_H_4399340943904399349843 buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/StorageChangeNotifierPlugin.h000066400000000000000000000041011477124122200271020ustar00rootroot00000000000000#ifndef STORAGECHANGENOTIFIERPLUGIN_H #define STORAGECHANGENOTIFIERPLUGIN_H #include #include namespace Buteo { /*! \brief Implement this class to notify about changes in * a specific storage - contacts/calendar/sms, or even custom * ones like a facebook storage, if there's such a storage on * the device */ class StorageChangeNotifierPlugin : public QObject { Q_OBJECT public: /*! \brief constructor * @param aStorageName pass the well known sync storage name */ StorageChangeNotifierPlugin(const QString &aStorageName): iStorageName(aStorageName) {} /*! \brief destructor */ virtual ~StorageChangeNotifierPlugin() {} /*! \brief the name should be a well-known name * which buteo sync-fw knows about as a storage that * could be synced, for eg hcontacts for contacts storage * * @return well-known storage name */ virtual QString name() const = 0; /*! \brief Check if this storage has changes since the * last time it was asked for the same * * @return true if there are changes, false otherwise */ virtual bool hasChanges() const = 0; /*! Call this after the change notification has been * received, either via a signal or by calling hasChanges() * manually */ virtual void changesReceived() = 0; /*! \brief Enable listening to storage changes */ virtual void enable() = 0; /*! \brief Disable listening to storage changes * * @param disableAfterNextChange if set to true, then we * disable listening only if the next change occurs and * if enable() hasn't been called before this change is receivied. * This helps to get one notification in case items are added in batches. */ virtual void disable(bool disableAfterNextChange = false) = 0; Q_SIGNALS: /*! \brief emit this signal when there's a change in this * storage. It's upto the plug-in when and how frequently * it wants to emit this signal */ void storageChange(); protected: QString iStorageName; }; } #endif buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/StorageChangeNotifierPluginLoader.h000066400000000000000000000025021477124122200302340ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2021 Jolla Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef STORAGECHANGENOTIFIERPLUGINLOADER_H #define STORAGECHANGENOTIFIERPLUGINLOADER_H #include namespace Buteo { class StorageChangeNotifierPlugin; /*! \brief Base class for storage change notifier plugin loaders * */ class StorageChangeNotifierPluginLoader : public QObject { Q_OBJECT public: virtual StorageChangeNotifierPlugin *createPlugin(const QString &aStorageName) = 0; }; } Q_DECLARE_INTERFACE(Buteo::StorageChangeNotifierPluginLoader, "com.buteo.msyncd.StorageChangeNotifierPluginLoader/1.0") #endif // STORAGECHANGENOTIFIERPLUGINLOADER_H buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/StorageItem.cpp000066400000000000000000000030301477124122200242670ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "StorageItem.h" using namespace Buteo; StorageItem::StorageItem() { } StorageItem::~StorageItem() { } void StorageItem::setId(const QString &aId) { iId = aId; } const QString &StorageItem::getId() const { return iId; } void StorageItem::setParentId(const QString &aParentId) { iParentId = aParentId; } const QString &StorageItem::getParentId() const { return iParentId; } void StorageItem::setType(const QString &aType) { iType = aType; } const QString &StorageItem::getType() const { return iType; } void StorageItem::setVersion(const QString &aVersion) { iVersion = aVersion; } const QString &StorageItem::getVersion() const { return iVersion; } buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/StorageItem.h000066400000000000000000000065541477124122200237520ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef STORAGEITEM_H #define STORAGEITEM_H #include #include #include namespace Buteo { /*! \brief Class to describe a storable item * */ class StorageItem { public: /*! \brief Constructor * */ StorageItem(); /*! \brief Destructor * */ virtual ~StorageItem(); /*! \brief Sets the id of the item * * @param aId The id of the item */ void setId(const QString &aId); /*! \brief Returns the id of the item * * @return Id of the item */ const QString &getId() const; /*! \brief Sets the id of the parent of this item * * @param aParentId The id of the parent of item */ void setParentId(const QString &aParentId); /*! \brief Returns the id of the parent of this item * * @return Id of the parent of this item */ const QString &getParentId() const; /*! \brief Sets the type of this item * * @param aType Type of this item */ void setType(const QString &aType); /*! \brief Gets the type of this item * * @return Type of this item */ const QString &getType() const; /*! \brief Sets the version of this item * * @param aVersion Version of this item */ void setVersion(const QString &aVersion); /*! \brief Gets the version of this item * * @return Version of this item */ const QString &getVersion() const; /*! \brief Write (part of) the item data * * @param aOffset Offset to start writing from * @param aData Data buffer to write. All bytes from buffer are written * @return True on success, otherwise false */ virtual bool write(qint64 aOffset, const QByteArray &aData) = 0; /*! \brief Read (part of) the item data * * @param aOffset The offset in bytes from where the data is read * @param aLength The number of bytes to read * @param aData Data buffer where to place data * @return True on success, otherwise false */ virtual bool read(qint64 aOffset, qint64 aLength, QByteArray &aData) const = 0; /*! \brief Sets the length of the item data * * @param aLen Length to set for item data * @return True on success, otherwise false */ virtual bool resize(qint64 aLen) = 0; /*! \brief Get the size of the item data * * @return The data size in bytes */ virtual qint64 getSize() const = 0; private: QString iId; QString iParentId; QString iType; QString iVersion; }; } #endif buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/StoragePlugin.cpp000066400000000000000000000026661477124122200246450ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "StoragePlugin.h" using namespace Buteo; StoragePlugin::StoragePlugin(const QString &aPluginName) : iPluginName(aPluginName) { } StoragePlugin::~StoragePlugin() { } const QString &StoragePlugin::getPluginName() const { return iPluginName; } QString StoragePlugin::getProperty(const QString &aProperty) const { QString returnValue = ""; if (iProperties.contains(aProperty)) { returnValue = iProperties.value(aProperty); } return returnValue; } void StoragePlugin::getProperties(QMap &aProperties) const { aProperties = iProperties; } buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/StoragePlugin.h000066400000000000000000000174061477124122200243100ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef STORAGEPLUGIN_H_89943984899843 #define STORAGEPLUGIN_H_89943984899843 #include #include #include #include namespace Buteo { class StorageItem; /*! \brief Base class for storage plugins * */ class StoragePlugin : public QObject { Q_OBJECT public: /*! \brief Status of operation performed by storage plugin * */ enum OperationStatus { STATUS_INVALID_FORMAT = -6, /*!< Operation failed because data is in invalid format*/ STATUS_STORAGE_FULL = -5, /*!< Operation failed because storage is full*/ STATUS_OBJECT_TOO_BIG = -4, /*!< Operation failed because object is too big*/ STATUS_ERROR = -3, /*!< General error occurred during operation*/ STATUS_DUPLICATE = -2, /*!< Operation was not performed as object was duplicate*/ STATUS_NOT_FOUND = -1, /*!< Operation failed as object was not found*/ STATUS_OK = 0 /*!< Operation was completed successfully*/ }; /*! \brief Constructor * * @param aPluginName Name of this storage plugin */ StoragePlugin(const QString &aPluginName); /*! \brief Destructor * */ virtual ~StoragePlugin(); /*! \brief Returns the name of this plugin * * @return Name of the plugin */ const QString &getPluginName() const; /*! \brief Returns the value of the given property * * @param aProperty Property * @return Property value if property found, otherwise empty string */ QString getProperty(const QString &aProperty) const; /*! \brief Returns the properties set for this plugin * * @param aProperties Properties that are set */ void getProperties(QMap &aProperties) const; /*! \brief Initializes the plugin * * It is recommended that the plugin should do not do any thread insecure * initializations inside constructor, instead it should do inside this method. * Parameters that were read from storage profile are passed as parameter to this * function. * * @param aProperties Properties that should be set for this plugin */ virtual bool init(const QMap &aProperties) = 0; /*! \brief Uninitializes the plugin * */ virtual bool uninit() = 0; /*! \brief Returns all known items * * @param aItems Array where to place items * @return True on success, otherwise false */ virtual bool getAllItems(QList &aItems) = 0; /*! \brief Returns id's of all known items * * @param aItems Array where to place item id's * @return True on success, otherwise false */ virtual bool getAllItemIds(QList &aItems) = 0; /*! \brief Returns all new items since aTime * * @param aNewItems Array where to place items * @param aTime Items with creation time > aTime are returned * @return True on success, otherwise false */ virtual bool getNewItems(QList &aNewItems, const QDateTime &aTime) = 0; /*! \brief Returns id's of all new items since aTime (creation time > aTime) * * @param aNewItemIds Array where to place item id's * @param aTime Items with creation time > aTime are returned * @return True on success, otherwise false */ virtual bool getNewItemIds(QList &aNewItemIds, const QDateTime &aTime) = 0; /*! \brief Returns all modified items since aTime * * @param aModifiedItems Array where to place items * @param aTime Items with modification time > aTime and creation time <= * aTime are returned. * @return True on success, otherwise false */ virtual bool getModifiedItems(QList &aModifiedItems, const QDateTime &aTime) = 0; /*! \brief Returns id's of all modified items since aTime * * @param aModifiedItemIds Array where to place item id's * @param aTime Items with modification time > aTime and creation time <= * aTime are returned. * @return True on success, otherwise false */ virtual bool getModifiedItemIds(QList &aModifiedItemIds, const QDateTime &aTime) = 0; /*! \brief Returns id's of all deleted items since aTime * * @param aDeletedItemIds Array where to place item id's * @param aTime Items with deletion time > aTime and creation time <= * aTime are returned. * @return True on success, otherwise false */ virtual bool getDeletedItemIds(QList &aDeletedItemIds, const QDateTime &aTime) = 0; /*! \brief Generates a new item * * Returned item is temporary. Therefore returned item ALWAYS has its id * set as empty ID (""). ID will be assigned only after addItem() has been * called for the item. * * @return On success pointer to the item generated, otherwise NULL */ virtual StorageItem *newItem() = 0; /*! \brief Returns an item based on id * * @param aItemId Id of the item to return * @return On success pointer to the item, otherwise NULL */ virtual StorageItem *getItem(const QString &aItemId) = 0; /*! \brief Returns items based on ids * * @param aItemIdList List of id's * @return List of items */ virtual QList getItems(const QStringList &aItemIdList) = 0; /*! \brief Adds an item to the storage * * Upon successful addition, item is updated with its * assigned ID. * * @param aItem Item to add * @return Operation status code */ virtual OperationStatus addItem(StorageItem &aItem) = 0; /*! \brief Adds items to the storage * * Upon successful addition, items are updated with its * assigned ID. * * @param aItems Items to add * @return Operation status codes */ virtual QList addItems(const QList &aItems) = 0; /*! \brief Modifies an item in the storage * * @param aItem Item to modify * @return Operation status code */ virtual OperationStatus modifyItem(StorageItem &aItem) = 0; /*! \brief Modifies item in the storage * * @param aItems Items to add * @return Operation status codes */ virtual QList modifyItems(const QList &aItems) = 0; /*! \brief Deletes an item from the storage * * @param aItemId Id of the item to be deleted * @return Operation status code */ virtual OperationStatus deleteItem(const QString &aItemId) = 0; /*! \brief Deletes an item from the storage * * @param aItemIds Id's of the item to be deleted * @return Operation status codes */ virtual QList deleteItems(const QList &aItemIds) = 0; protected: //! Name of the plugin QString iPluginName; //! Properties of the plugin as read from profile xml QMap iProperties; }; } #endif // STORAGEPLUGIN_H_89943984899843 buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/StoragePluginLoader.h000066400000000000000000000023011477124122200254230ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2021 Jolla Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef STORAGEPLUGINLOADER_H #define STORAGEPLUGINLOADER_H #include namespace Buteo { class StoragePlugin; /*! \brief Base class for storage plugin loaders * */ class StoragePluginLoader : public QObject { Q_OBJECT public: virtual StoragePlugin *createPlugin(const QString &aPluginName) = 0; }; } Q_DECLARE_INTERFACE(Buteo::StoragePluginLoader, "com.buteo.msyncd.StoragePluginLoader/1.0") #endif // STORAGEPLUGINLOADER_H buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/SyncDBusInterface.h000066400000000000000000000332241477124122200250340ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCDBUSINTERFACE_H #define SYNCDBUSINTERFACE_H #include #include #include #include namespace Buteo { /*! * \brief Defines a D-Bus interface for the sync daemon. * * A XML file describing the interface can be generated from this class using * qdbuscpp2xml tool. This XML file can then be used to generate interface * adaptor and proxy classes using qdbusxml2cpp tool. */ class SyncDBusInterface : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "com.meego.msyncd") public: signals: /*! * \brief Notifies about a change in synchronization status. * * \param aProfileName Name of the profile used in the sync session whose * status has changed. * \param aStatus The new status. One of the following: * 0 (QUEUED): Sync request has been queued or was already in the * queue when sync start was requested. * 1 (STARTED): Sync session has been started. * 2 (PROGRESS): Sync session is progressing. * 3 (ERROR): Sync session has encountered an error and has been stopped, * or the session could not be started at all. * 4 (DONE): Sync session was successfully completed. * 5 (ABORTED): Sync session was aborted. * Statuses 3-5 are final, no more status changes will be sent from the * same sync session. * \param aMessage A message describing the status change in detail. This * can for example be shown to the user or written to a log * \param aMoreDetails * When aStatus is ERROR, this parameter contains a specific error code. * When aStatus is PROGRESS, this parameter contains more details about the progress */ void syncStatus(QString aProfileName, int aStatus, QString aMessage, int aMoreDetails); /*! \brief Notifies about progress in transferring items * * \param aProfileName Name of the profile where progress has occurred * \param aTransferDatabase Database to which transfer was made. One of the following: * 0 (LOCAL_DATABASE): Transfer was made from remote database to local database * 1 (REMOTE_DATABASE): Transfer was made from local database to remote database * \param aTransferType Type of transfer that was made. One of the following: * 0 (ADDITION): Addition was made to database * 1 (MODIFICATION): Modification was made to database * 2 (DELETION): Deletion was made to database * 3 (ERROR): Addition/Modification/Deletion was attempted, but it failed * \param aMimeType Mime type of the processed item * \param aCommittedItems No. of Items committed for this operation */ void transferProgress(QString aProfileName, int aTransferDatabase, int aTransferType, QString aMimeType, int aCommittedItems); /*! \brief Notifies about a change in profile. * * This signal is sent when the profile data is modified or when a profile * is added or deleted in msyncd. * \param aProfileName Name of the changed profile. * \param aChangeType * 0 (ADDITION): Profile was added. * 1 (MODIFICATION): Profile was modified. * 2 (DELETION): Profile was deleted. * \param aProfileAsXml Updated Profile Object is sent as xml * */ void signalProfileChanged(QString aProfileName, int aChangeType, QString aProfileAsXml); /*! \brief Notifies about Backup start. * * This signal is sent when the backup framework is backing the sync related * data */ void backupInProgress (); /*! \brief Notifies about Backup done. * * This signal is sent when the backup framework has completed backing the sync related * data. */ void backupDone(); /*! \brief Notifies about Restore start. * * This signal is sent when the backup framework is restoring the sync related * data */ void restoreInProgress(); /*! \brief Notifies about Restore Done. * * This signal is sent when the backup framework has restored the sync related * data */ void restoreDone(); /*! \brief Notifies about the availability of Results for a recent sync * * This signal is sent when the results are available for the last sync * only recent results ( SyncResults object) are sent as xml. * \param aProfileName Name of the profile for which results are available * \param aResultsAsXml results as an xml object */ void resultsAvailable(QString aProfileName, QString aResultsAsXml); /*! \brief Notifies sync status change for a set of account Ids * * This signal is sent when the status of a sync for a particular * account ID changes state Upon receiving this signal, the client is * expected to call the status method to check whether sync is * running/stopped for this account ID * * \param aAccountId The account IDs that changed state * \param aNewStatus The new status of sync for this account * \param aFailedReason This is an out parameter. In case the last sync has * failed, this will contain the code indicating the failure reason (TODO: * Define error codes). In case the last sync has not failed, this must be * ignored * \param aPrevSyncTime This is an out parameter. The previous sync time. * Invalid time is returned if there was no last sync. * \param aNextSyncTime This is an out parameter. The next sync time. */ void statusChanged(unsigned int aAccountId, int aNewStatus, int aFailedReason, qlonglong aPrevSyncTime, qlonglong aNextSyncTime); public slots: /*! * \brief Requests to starts synchronizing using a profile with the given * name. * * A status change signal (QUEUED, STARTED or ERROR) will be sent by the * daemon when the request is processed. If there is a sync already in * progress using the same resources that are needed by the given profile, * adds the sync request to a sync queue. Otherwise a sync session is * started immediately. * * \param aProfileId Id of the profile to use in sync. * \return True if a profile with the given id was found. Otherwise * false and no status change signals will follow from this request. */ virtual bool startSync(QString aProfileId) = 0; /*! * \brief Stops synchronizing the profile with the given name. * * If the sync request is still in queue and not yet started, the queue * entry is removed. * * \param aProfileId Name of the profile to stop syncing. */ virtual Q_NOREPLY void abortSync(QString aProfileId) = 0; /*! * \brief This function should be called when sync profile has to be deleted * * \param aProfileId Id of the profile to be deleted. * \return status of the remove operation */ virtual bool removeProfile(QString aProfileId) = 0; /*! * \brief This function should be called when sync profile information has * been changed by someone else than the sync daemon. * \note If profile does not exist prior to calling this function, a new profile file is created * * \param aProfileAsXml - Modified Profile Object as XML. * \return status of the update operation */ virtual bool updateProfile(QString aProfileAsXml) = 0; /*! * \brief Requests sync daemon to reserve storages for the caller. * * This function must be called if an external sync entity (like Active * Sync engine) wants to use the same storages that the sync daemon uses, * because concurrent access might lead to data corruption. If none of the * requested storages is currently used by the sync daemon, they are all * marked as reserved and can not be used by the daemon until the storages * are freed by calling releaseStorages. If one or more of the requested * storages is already in use, none of them is reserved. * * \param aStorageNames Names of the storages to reserve. * \return Success indicator. True if all requested storages were * successfully reserved. False if request failed and no storages were * reserved. */ virtual bool requestStorages(QStringList aStorageNames) = 0; /*! * \brief Releases the given storages so that sync daemon can again use * them freely. * * This function must be called after a successful requestStorages call, * when the reserved storages are not used by the caller any more. */ virtual Q_NOREPLY void releaseStorages(QStringList aStorageNames) = 0; /*! * \brief Gets the list of profile names of currently running syncs. * * \return Profile name list. */ virtual QStringList runningSyncs() = 0; /*! * \brief This function returns true if backup/restore in progress else * false. */ virtual bool getBackUpRestoreState() = 0; /*! * \brief sets the schedule for a profile * * This Function helps in setting a schedule to profile * this Function is to be used by the SyncInterface Client Library to * expose a user friendly API by abstracting the dbus mechanisms * involved with synchronizer * * \param aProfileId - Id of the profile for which schedule has to be set * \param aScheduleAsXml - Over the dbus the schedule object is transmitted as xml * * \return bool - status of the operation */ virtual bool setSyncSchedule(QString aProfileId, QString aScheduleAsXml) = 0; /*! * \brief Save SyncResults to log.xml file. * \param aProfileId to save result in corresponding file. * \param aSyncResults to save in the \code .log.xml. \endcode * \return status of the saveSyncResults */ virtual bool saveSyncResults(QString aProfileId, QString aSyncResults) = 0; /*! \brief To get lastSyncResult. * \param aProfileId * \return QString of syncResult. */ virtual QString getLastSyncResult(const QString &aProfileId) = 0; /*! \brief Gets all visible sync profiles. * * Returns all sync profiles that should be visible in sync ui. A profile * is visible if it has not been explicitly set as hidden. * \return The list of sync profiles. */ virtual QStringList allVisibleSyncProfiles() = 0; /*! \brief Gets a sync profile. * * Loads and merges also all sub-profiles that are referenced from the * main profile. Loads the log of finished synchronization sessions with * this profile. * \param aProfileId Name of the profile to get. * \return The sync profile as Xml string. */ virtual QString syncProfile(const QString &aProfileId) = 0; /*! \brief Gets a sync profiles matching the key-value. * * Loads and merges also all sub-profiles that are referenced from the * main profile. Loads the log of finished synchronization sessions with * this profile. * \param aKey Key to match for profile. * \param aValue Value to match for profile. * \return The sync profiles as Xml string list. */ virtual QStringList syncProfilesByKey(const QString &aKey, const QString &aValue) = 0; /*! \brief Gets a profiles matching the profile type. * * \param aType Type of the profile service/storage/sync. * \return The sync profile ids as string list. */ virtual QStringList syncProfilesByType(const QString &aType) = 0; /*! \brief Starts sync for all profiles matching the given account ID. * * \param aAccountId The account ID. */ virtual Q_NOREPLY void start(unsigned int aAccountId) = 0; /*! \brief Stops sync for all profiles matching the given account ID. * * \param aAccountId The account ID. */ virtual Q_NOREPLY void stop(unsigned int aAccountId) = 0; /*! \brief Returns the list of account IDs for which sync is ongoing * * \return The list of account IDs currectly syncing. */ virtual QList syncingAccounts() = 0; /*! \brief Returns the status of the sync for the given account Id * * \param aAccountId The account ID. * \param aFailedReason This is an out parameter. In case the last sync has * failed, this will contain the code indicating the failure reason (TODO: * Define error codes). In case the last sync has not failed, this must be * ignored * \param aPrevSyncTime This is an out parameter. The previous sync time. * Invalid time is returned if there was no last sync. * \param aNextSyncTime This is an out parameter. The next sync time. * \return The status of sync: 0 = Sync is running, * 1 = Last sync succeeded, 2 = last sync failed */ virtual int status(unsigned int aAccountId, int &aFailedReason, qlonglong &aPrevSyncTime, qlonglong &aNextSyncTime) = 0; }; } #endif // SYNCDBUSINTERFACE_H buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/SyncPluginBase.cpp000066400000000000000000000037601477124122200247440ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "SyncPluginBase.h" #include "SyncResults.h" #include "PluginCbInterface.h" using namespace Buteo; SyncPluginBase::SyncPluginBase(const QString &aPluginName, const QString &aProfileName, PluginCbInterface *aCbInterface) : iCbInterface(aCbInterface), iPluginName(aPluginName), iProfileName(aProfileName) { // register various metatypes used in DBus arguments qRegisterMetaType("SyncResults::MinorCode"); qRegisterMetaType("Sync::SyncStatus"); qRegisterMetaType("Sync::TransferDatabase"); qRegisterMetaType("Sync::TransferType"); qRegisterMetaType("Sync::ConnectivityType"); qRegisterMetaType("QProcess::ProcessError"); qRegisterMetaType("QProcess::ExitStatus"); } QString SyncPluginBase::getPluginName() const { return iPluginName; } QString SyncPluginBase::getProfileName() const { return iProfileName; } SyncResults SyncPluginBase::getSyncResults() const { return SyncResults(); } buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/SyncPluginBase.h000066400000000000000000000137761477124122200244210ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCPLUGINBASE_H #define SYNCPLUGINBASE_H #include "SyncCommonDefs.h" #include "SyncResults.h" #include "ButeoPluginIface.h" #include #include #include #include #define DBUS_SERVICE_NAME_PREFIX "com.buteo.msyncd.plugin." #define DBUS_SERVICE_OBJ_PATH "/" namespace Buteo { class PluginCbInterface; /*! \brief Base class for client and server plugins. * */ class SyncPluginBase : public QObject { Q_OBJECT public: /*! \brief Constructor * * @param aPluginName Name of this plugin * @param aProfileName Profile name * @param aCbInterface Pointer to the callback interface */ SyncPluginBase(const QString &aPluginName, const QString &aProfileName, PluginCbInterface *aCbInterface); /*! \brief Returns the name of this plugin * * @return Name of the plugin */ QString getPluginName() const; /*! \brief Returns profile name * * @return Profile */ QString getProfileName() const; /*! \brief Initializes the plugin. * * It is recommended that the plugin should do not do any thread insecure * initializations inside constructor, instead it should be done inside * this method. * * @return True on success, otherwise false */ virtual bool init() = 0; /*! \brief Uninitializes the plugin * * @return True on success, otherwise false */ virtual bool uninit() = 0; /*! \brief Aborts synchronization * * Derived plug-in should implement this function and abort the sync * session that is in progress when this function is called. A final signal * (success or error) is still expected from the aborted session before * it terminates. */ virtual void abortSync(Sync::SyncStatus aStatus = Sync::SYNC_ABORTED) { Q_UNUSED(aStatus); } /*! \brief Cleans up any sync related stuff (e.g sync anchors etc) when the * profile is deleted * * Derived plug-in should implement this function and perform any cleanup * operations if required when the profile is deleted */ virtual bool cleanUp() = 0; /*! \brief Gets the results of the last completed sync session. * * This function should be called only after the sync session has finished, * after an error or success signal has been emitted. * The default implementation returns empty results, so derived plug-in * should implement this function. * @returns Sync results. */ virtual SyncResults getSyncResults() const; signals: /*! \brief Emitted when progress has been made in synchronization in * transferring items between local and remote database. * * @param aProfileName Name of the profile being synchronized * @param aDatabase Indicates if progress has been made to local or remote database * @param aType Type of progress made (item added, modified or deleted) * @param aMimeType Mime type of the processed item * @param aCommittedItems No. of items committed for this operation */ void transferProgress(const QString &aProfileName, Sync::TransferDatabase aDatabase, Sync::TransferType aType, const QString &aMimeType, int aCommittedItems); /*! \brief Emitted when error has occurred in synchronization and it * cannot be continued. * * @param aProfileName Name of the profile being synchronized * @param aMessage Message data related to error event * @param aErrorCode Error code */ void error(const QString &aProfileName, const QString &aMessage, SyncResults::MinorCode aErrorCode); /*! \brief Emitted when synchronization has been finished successfully. * * @param aProfileName Name of the profile being synchronized * @param aMessage Message data related to finish event */ void success(const QString &aProfileName, const QString &aMessage); /*! \brief Emitted when a storage is requested and accquired. * * @param aMimeType Mime type of the processed item */ void accquiredStorage(const QString &aMimeType); /*! \brief Emitted during Sync Progress to indicate the detail of the current ongoing sync * * @param aProfileName Profile Name * @param aProgressDetail Progress in Detail \see Sync::SyncProgressDetail */ void syncProgressDetail(const QString &aProfileName, int aProgressDetail); public slots: /*! \brief Slot that is invoked by sync framework when changes occur in * connectivity domains * * @param aType Connectivity domain * @param aState True if connectivity domain is now available, otherwise false */ virtual void connectivityStateChanged(Sync::ConnectivityType aType, bool aState) = 0; protected: //! Pointer to synchronizer PluginCbInterface *iCbInterface; struct ReceivedItemDetails { int added; int deleted; int modified; int error; QString mime; }; QMap receivedItems; ButeoPluginIface *iOopPluginIface; private: QString iPluginName; QString iProfileName; }; } #endif // SYNCPLUGINBASE_H buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/SyncPluginLoader.cpp000066400000000000000000000020771477124122200253000ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2021 Jolla Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "SyncPluginLoader.h" using namespace Buteo; ClientPlugin *SyncPluginLoader::createClientPlugin(const QString &, const SyncProfile &, PluginCbInterface *) { return nullptr; } ServerPlugin *SyncPluginLoader::createServerPlugin(const QString &, const Profile &, PluginCbInterface *) { return nullptr; } buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/SyncPluginLoader.h000066400000000000000000000032001477124122200247320ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2021 Jolla Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCPLUGINLOADER_H #define SYNCPLUGINLOADER_H #include "SyncProfile.h" #include namespace Buteo { class PluginCbInterface; class ClientPlugin; class ServerPlugin; class SyncPluginBase; /*! \brief Base class for sync plugin loaders * */ class SyncPluginLoader : public QObject { Q_OBJECT public: virtual ClientPlugin *createClientPlugin(const QString &aPluginName, const SyncProfile &aProfile, PluginCbInterface *aCbInterface); virtual ServerPlugin *createServerPlugin(const QString &aPluginName, const Profile &aProfile, PluginCbInterface *aCbInterface); }; } Q_DECLARE_INTERFACE(Buteo::SyncPluginLoader, "com.buteo.msyncd.SyncPluginLoader/1.0") #endif // SYNCPLUGINLOADER_H buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/buteoplugin-dbus-if-gen.sh000077500000000000000000000004371477124122200263420ustar00rootroot00000000000000# For client interface qdbusxml2cpp -v -c ButeoPluginIface -p ButeoPluginIface.h:ButeoPluginIface.cpp com.buteo.msyncd.baseplugin.xml # For server interface qdbusxml2cpp -c ButeoPluginIfaceAdaptor -a ButeoPluginIfaceAdaptor.h:ButeoPluginIfaceAdaptor.cpp com.buteo.msyncd.baseplugin.xml buteo-syncfw-0.11.10/libbuteosyncfw/pluginmgr/com.buteo.msyncd.baseplugin.xml000066400000000000000000000050201477124122200274020ustar00rootroot00000000000000 buteo-syncfw-0.11.10/libbuteosyncfw/profile/000077500000000000000000000000001477124122200210005ustar00rootroot00000000000000buteo-syncfw-0.11.10/libbuteosyncfw/profile/BtHelper.cpp000066400000000000000000000073371477124122200232230ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * 2021 Updated to use bluez5 by deloptes@gmail.com * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include #include #include "BtCommon.h" #include "BtHelper.h" using namespace Buteo; // big help was found here // https://github.com/zeenix/bluetooth-demo/blob/master/blueconnect.cpp BtHelper::BtHelper(const QString &deviceAddress, QObject* parent) : QObject(parent), m_SystemBus(QDBusConnection::systemBus()) { FUNCTION_CALL_TRACE(lcButeoTrace); #ifdef HAVE_BLUEZ_5 qDBusRegisterMetaType (); qDBusRegisterMetaType (); QDBusInterface managerInterface( BT::BLUEZ_DEST, QString("/"), BT::BLUEZ_MANAGER_INTERFACE, m_SystemBus ); QDBusReply reply = managerInterface.call(BT::GETMANAGEDOBJECTS); if (!reply.isValid()) { qCWarning(lcButeoCore) << "Failed to connect to ObjectManager: " << reply.error().message() ; } else { ObjectsMap objects = reply.value(); for (ObjectsMap::iterator i = objects.begin(); i != objects.end(); ++i) { InterfacesMap ifaces = i.value(); for (InterfacesMap::iterator j = ifaces.begin(); j != ifaces.end(); ++j) { if (j.key() == BT::BLUEZ_DEVICE_INTERFACE) { QString path = i.key().path(); QDBusInterface dev(BT::BLUEZ_DEST, path, BT::BLUEZ_DEVICE_INTERFACE, m_SystemBus); if (dev.property("Connected").toBool() && dev.property("Address").toString() == deviceAddress) { qCDebug(lcButeoCore) << "[BtHelper]Device connected(" << dev.property("Address").toString() << ") at" << path; m_devicePath = path; } } } } } #else Q_UNUSED(deviceAddress) #endif } BtHelper::~BtHelper() { FUNCTION_CALL_TRACE(lcButeoTrace); } QVariantMap BtHelper::getDeviceProperties() { FUNCTION_CALL_TRACE(lcButeoTrace); #ifdef HAVE_BLUEZ_5 if (m_devicePath.isEmpty()) return QVariantMap(); QDBusInterface deviceInterface(BT::BLUEZ_DEST, m_devicePath, BT::BLUEZ_PROPERTIES_INTERFACE, m_SystemBus ); if (!deviceInterface.isValid()) { qCDebug(lcButeoCore) << "Device interface is not valid"; return QVariantMap(); } QDBusReply reply = deviceInterface.call(BT::GETPROPERTIES, BT::BLUEZ_DEVICE_INTERFACE); if (!reply.isValid()) { qCWarning(lcButeoCore) << "Failed to get device properties: " << reply.error().message() ; return QVariantMap(); } return reply.value(); #else qCDebug(lcButeoCore) << "Bluetooth is not supported"; return QVariantMap(); #endif } buteo-syncfw-0.11.10/libbuteosyncfw/profile/BtHelper.h000066400000000000000000000031061477124122200226560ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * 2021 Updated to use bluez5 by deloptes@gmail.com * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef BTHELPER_H #define BTHELPER_H #include #include #include namespace Buteo { /*! \brief Implementation for bluetooth helper utils. */ class BtHelper : public QObject { Q_OBJECT private: QDBusConnection m_SystemBus; QString m_devicePath; public: /*! \brief Constructor. * \param deviceAddress Bluetooth address of remote device * \param parent Parent object */ BtHelper(const QString &deviceAddress, QObject *parent = 0); /*! \brief Destructor */ ~BtHelper(); /*! \brief To find remote device BT properties. */ QVariantMap getDeviceProperties(); }; #endif // BTHELPER_H } buteo-syncfw-0.11.10/libbuteosyncfw/profile/Profile.cpp000066400000000000000000000324261477124122200231130ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "Profile.h" #include "Profile_p.h" #include #include "ProfileFactory.h" #include "ProfileEngineDefs.h" #include "LogMacros.h" using namespace Buteo; const QString Profile::TYPE_CLIENT("client"); const QString Profile::TYPE_SERVER("server"); const QString Profile::TYPE_STORAGE("storage"); //const QString Profile::TYPE_SERVICE("service"); const QString Profile::TYPE_SYNC("sync"); Profile::Profile() : d_ptr(new ProfilePrivate()) { } Profile::Profile(const QString &aName, const QString &aType) : d_ptr(new ProfilePrivate()) { d_ptr->iName = aName; d_ptr->iType = aType; } Profile::Profile(const QDomElement &aRoot) : d_ptr(new ProfilePrivate()) { d_ptr->iName = aRoot.attribute(ATTR_NAME); d_ptr->iType = aRoot.attribute(ATTR_TYPE); // Get keys. QDomElement key = aRoot.firstChildElement(TAG_KEY); for (; !key.isNull(); key = key.nextSiblingElement(TAG_KEY)) { QString name = key.attribute(ATTR_NAME); QString value = key.attribute(ATTR_VALUE); if (!name.isEmpty() && !value.isNull()) { d_ptr->iLocalKeys.insertMulti(name, value); } else { // Invalid key } } // Get fields. QDomElement field = aRoot.firstChildElement(TAG_FIELD); for (; !field.isNull(); field = field.nextSiblingElement(TAG_FIELD)) { d_ptr->iLocalFields.append(new ProfileField(field)); } // Get sub-profiles. ProfileFactory pf; QDomElement prof = aRoot.firstChildElement(TAG_PROFILE); for (; !prof.isNull(); prof = prof.nextSiblingElement(TAG_PROFILE)) { Profile *subProfile = pf.createProfile(prof); if (subProfile != 0) { d_ptr->iSubProfiles.append(subProfile); } } } Profile::Profile(const Profile &aSource) : d_ptr(new ProfilePrivate(*aSource.d_ptr)) { } Profile *Profile::clone() const { return new Profile(*this); } Profile::~Profile() { delete d_ptr; d_ptr = 0; } QString Profile::name() const { return d_ptr->iName; } void Profile::setName(const QString &aName) { d_ptr->iName = aName; } void Profile::setName(const QStringList &aKeys) { d_ptr->iName = generateProfileId(aKeys); } QString Profile::type() const { return d_ptr->iType; } QString Profile::key(const QString &aName, const QString &aDefault) const { QString value; if (d_ptr->iLocalKeys.contains(aName)) { value = d_ptr->iLocalKeys[aName]; } else if (d_ptr->iMergedKeys.contains(aName)) { value = d_ptr->iMergedKeys[aName]; } else { value = aDefault; } return value; } QMap Profile::allKeys() const { QMap keys(d_ptr->iMergedKeys); keys.unite(d_ptr->iLocalKeys); return keys; } QMap Profile::allNonStorageKeys() const { QMap keys; foreach (Profile *p, d_ptr->iSubProfiles) { if (p != 0 && p->type() != Profile::TYPE_STORAGE) { keys.unite(p->allKeys()); } } keys.unite(allKeys()); return keys; } bool Profile::boolKey(const QString &aName, bool aDefault) const { QString value = key(aName); if (!value.isNull()) { return (key(aName).compare(BOOLEAN_TRUE, Qt::CaseInsensitive) == 0); } else { return aDefault; } } QStringList Profile::keyValues(const QString &aName) const { return (d_ptr->iLocalKeys.values(aName) + d_ptr->iMergedKeys.values(aName)); } QStringList Profile::keyNames() const { return d_ptr->iLocalKeys.uniqueKeys() + d_ptr->iMergedKeys.uniqueKeys(); } void Profile::setKey(const QString &aName, const QString &aValue) { if (aName.isEmpty()) return; // Value is not checked, because it is allowed to have a key with empty // value. if (aValue.isNull()) { // Setting a key value to null removes the key. d_ptr->iLocalKeys.remove(aName); d_ptr->iMergedKeys.remove(aName); } else { d_ptr->iLocalKeys.insert(aName, aValue); } } void Profile::setKeyValues(const QString &aName, const QStringList &aValues) { d_ptr->iLocalKeys.remove(aName); d_ptr->iMergedKeys.remove(aName); if (aValues.size() == 0) return; unsigned i = aValues.size(); do { i--; d_ptr->iLocalKeys.insertMulti(aName, aValues[i]); } while (i > 0); } void Profile::setBoolKey(const QString &aName, bool aValue) { d_ptr->iLocalKeys.insert(aName, aValue ? BOOLEAN_TRUE : BOOLEAN_FALSE); } void Profile::removeKey(const QString &aName) { d_ptr->iLocalKeys.remove(aName); d_ptr->iMergedKeys.remove(aName); } const ProfileField *Profile::field(const QString &aName) const { QList fields = allFields(); foreach (const ProfileField *f, fields) { if (f->name() == aName) return f; } return 0; } QList Profile::allFields() const { QList fields = d_ptr->iLocalFields + d_ptr->iMergedFields; return fields; } QList Profile::visibleFields() const { QList fields = allFields(); QList visibleFields; foreach (const ProfileField *f, fields) { // A field with VISIBLE_USER status is visible if a value for the field // does not come from a merged sub-profile, but from the top level // profile. This is the default visibility for a field. // In practice this means that the field value is not hard coded and // it should be possible for the user to modify it. if (f->visible() == ProfileField::VISIBLE_ALWAYS || (f->visible() == ProfileField::VISIBLE_USER && !d_ptr->iMergedKeys.contains(f->name()))) { visibleFields.append(f); } } return visibleFields; } QDomElement Profile::toXml(QDomDocument &aDoc, bool aLocalOnly) const { // Set profile name and type attributes. QDomElement root = aDoc.createElement(TAG_PROFILE); root.setAttribute(ATTR_NAME, d_ptr->iName); root.setAttribute(ATTR_TYPE, d_ptr->iType); // Set local keys. QMap::const_iterator i; for (i = d_ptr->iLocalKeys.begin(); i != d_ptr->iLocalKeys.end(); i++) { QDomElement key = aDoc.createElement(TAG_KEY); key.setAttribute(ATTR_NAME, i.key()); key.setAttribute(ATTR_VALUE, i.value()); root.appendChild(key); } // Set local fields. const ProfileField *field = 0; foreach (field, d_ptr->iLocalFields) { root.appendChild(field->toXml(aDoc)); } if (!aLocalOnly) { // Set merged keys. for (i = d_ptr->iMergedKeys.begin(); i != d_ptr->iMergedKeys.end(); i++) { QDomElement key = aDoc.createElement(TAG_KEY); key.setAttribute(ATTR_NAME, i.key()); key.setAttribute(ATTR_VALUE, i.value()); root.appendChild(key); } // Set merged fields. foreach (field, d_ptr->iMergedFields) { root.appendChild(field->toXml(aDoc)); } } // Set sub-profiles. foreach (Profile *p, d_ptr->iSubProfiles) { if (!p->d_ptr->iMerged || !p->d_ptr->iLocalKeys.isEmpty() || !p->d_ptr->iLocalFields.isEmpty()) { root.appendChild(p->toXml(aDoc, aLocalOnly)); } } return root; } QString Profile::toString() const { QDomDocument doc; QDomProcessingInstruction xmlHeading = doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""); doc.appendChild(xmlHeading); QDomElement root = toXml(doc, false); doc.appendChild(root); return doc.toString(PROFILE_INDENT); } bool Profile::isValid() const { // Profile name and type must be set. if (d_ptr->iName.isEmpty()) { qCDebug(lcButeoCore) << "Error: Profile name is empty" ; return false; } if (d_ptr->iType.isEmpty()) { qCDebug(lcButeoCore) << "Error: Profile type is empty" ; return false; } // For each field a key with the same name must exist, and the // key values must be valid for the field. QList fields = allFields(); foreach (const ProfileField *f, fields) { QStringList values = keyValues(f->name()); if (values.isEmpty()) { qCDebug(lcButeoCore) << "Error: Cannot find value for field" << f->name() << "for profile" << d_ptr->iName; return false; } foreach (QString value, values) { if (!f->validate(value)) { qCDebug(lcButeoCore) << "Error: Value" << value << "is not valid for profile" << d_ptr->iName; return false; } } } // Enabled sub-profiles must be valid. foreach (Profile *p, d_ptr->iSubProfiles) { if (p->isEnabled() && !p->isValid()) return false; } return true; } QStringList Profile::subProfileNames(const QString &aType) const { QStringList names; bool checkType = !aType.isEmpty(); foreach (Profile *p, d_ptr->iSubProfiles) { if (!checkType || aType == p->type()) { names.append(p->name()); } } return names; } Profile *Profile::subProfile(const QString &aName, const QString &aType) { bool checkType = !aType.isEmpty(); foreach (Profile *p, d_ptr->iSubProfiles) { if (aName == p->name() && (!checkType || aType == p->type())) { return p; } } return 0; } const Profile *Profile::subProfile(const QString &aName, const QString &aType) const { bool checkType = !aType.isEmpty(); foreach (Profile *p, d_ptr->iSubProfiles) { if (aName == p->name() && (!checkType || aType == p->type())) { return p; } } return 0; } const Profile *Profile::subProfileByKeyValue(const QString &aKey, const QString &aValue, const QString &aType, bool aEnabledOnly) const { bool checkType = !aType.isEmpty(); foreach (Profile *p, d_ptr->iSubProfiles) { if ((!checkType || aType == p->type()) && (aValue.compare(p->key(aKey), Qt::CaseInsensitive) == 0) && (!aEnabledOnly || p->isEnabled())) { return p; } } return 0; } QList Profile::allSubProfiles() { return d_ptr->iSubProfiles; } QList Profile::allSubProfiles() const { QList constProfiles; foreach (Profile *p, d_ptr->iSubProfiles) { constProfiles.append(p); } return constProfiles; } void Profile::merge(const Profile &aSource) { // Get target sub-profile. Create new if not found. Profile *target = subProfile(aSource.name(), aSource.type()); if (0 == target) { ProfileFactory pf; target = pf.createProfile(aSource.name(), aSource.type()); if (target != 0) { target->d_ptr->iMerged = true; d_ptr->iSubProfiles.append(target); } } if (target != 0) { // Merge keys. Allow multiple keys with the same name. target->d_ptr->iMergedKeys.unite(aSource.d_ptr->iLocalKeys); target->d_ptr->iMergedKeys.unite(aSource.d_ptr->iMergedKeys); // Merge fields. QList sourceFields = aSource.allFields(); foreach (const ProfileField *f, sourceFields) { if (0 == target->field(f->name())) { target->d_ptr->iMergedFields.append(new ProfileField(*f)); } } } // Merge sub-profiles. foreach (Profile *p, aSource.d_ptr->iSubProfiles) { merge(*p); } } bool Profile::isLoaded() const { return d_ptr->iLoaded; } void Profile::setLoaded(bool aLoaded) { d_ptr->iLoaded = aLoaded; } bool Profile::isEnabled() const { return boolKey(KEY_ENABLED, true); } void Profile::setEnabled(bool aEnabled) { setBoolKey(KEY_ENABLED, aEnabled); } bool Profile::isHidden() const { return boolKey(KEY_HIDDEN); } bool Profile::isProtected() const { return boolKey(KEY_PROTECTED); } QString Profile::displayname() const { return key(KEY_DISPLAY_NAME); } QString Profile::generateProfileId(const QStringList &aKeys) { if (aKeys.size() == 0) return QString(); QString aId = QString::number(qHash(aKeys.join(QString()))); return aId; } buteo-syncfw-0.11.10/libbuteosyncfw/profile/Profile.h000066400000000000000000000307531477124122200225610ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef PROFILE_H #define PROFILE_H #include #include #include #include #include "ProfileField.h" class QDomDocument; class QDomElement; namespace Buteo { class ProfileTest; class ProfilePrivate; /*! \brief This class represents a single profile, a collection of settings or * data releated to some entity. * * A profile can contain keys, fields and other profiles as sub-profiles. * Functions for accessing all these in different ways are provided. A profile * object can be created from XML and exported to XML, but otherwise the class * interface does not use XML. New classes can be derived from this class for * different profile types to add helper functions for accessing specific keys * and fields known by the profile type. */ class Profile { public: //! String constants for different profile type names. static const QString TYPE_CLIENT; //! Server type . static const QString TYPE_SERVER; //! Storage type static const QString TYPE_STORAGE; //! Service type //static const QString TYPE_SERVICE; //! Sync type static const QString TYPE_SYNC; /*! \brief Default Constructor * */ Profile(); /*! \brief Constructs a Profile object with given name and type. * * \param aName Profile name. * \param aType Profile type. Prefer using predefined constants like * TYPE_SYNC. */ Profile(const QString &aName, const QString &aType); /*! \brief Constructs a Profile object from XML. * * \param aRoot Root element of the profile XML. */ explicit Profile(const QDomElement &aRoot); /*! \brief Copy constructor. * * \param aSource Copy source. */ Profile(const Profile &aSource); /*! \brief Creates a clone of the profile. * * \return The clone. */ virtual Profile *clone() const; //! \brief Destructor. virtual ~Profile(); /*! \brief Gets the name of the profile. * * \return Profile name. */ QString name() const; /*! \brief Gets the display name of the profile. * * \return Profile display name. */ QString displayname() const; /*! \brief Sets the name of the profile. * * \param aName Name of the profile. */ virtual void setName(const QString &aName); /*! \brief Sets the name of the profile. * * \param aKeys Keys required to generate the profile name. */ virtual void setName(const QStringList &aKeys); /*! \brief Gets the type of the profile. * * \return Profile type. */ QString type() const; /*! \brief Creates a XML representation of the profile. * * \param aDoc Parent document for the created XML elements. The elements * are not inserted to the document by this function, but the document is * required to create the elements. * \param aLocalOnly Should only local profile elements be present in the * generated XML. If this is true, elements merged from sub-profiles are * not included. * \return Generated XML node tree. */ virtual QDomElement toXml(QDomDocument &aDoc, bool aLocalOnly = true) const; /*! \brief Outputs a XML representation of the profile to a string. * * Merged sub-profile data is also included in the output string. * \return Generated XML string. */ QString toString() const; /*! \brief Gets the value of the given key. * * \param aName Name of the key to read. * \param aDefault Default value. * \return Value of the key. If the key was not found, the default is * returned. If there are multiple instances of the key with the given * name, the value of the first instance is returned. */ QString key(const QString &aName, const QString &aDefault = QString()) const; /*! \brief Gets all keys and their values. * * \return Map of key names/values. */ QMap allKeys() const; /*! \brief Gets all keys that are not related to storages. * * \return Map of key names/values. */ QMap allNonStorageKeys() const; /*! \brief Gets the value of the given boolean key. * * Returns true if the key exists and its value equals "true". If * the key does not exist, the default value is returned. * \param aName Name of the key to read. * \param aDefault Value to return if the key does not exist. * \return The boolean value of the key. */ bool boolKey(const QString &aName, bool aDefault = false) const; /*! \brief Gets the values of all keys with the given name. * * If the key does not exist at all, an empty list is returned. * \param aName Name of the key to read. * \return List of values associated with the key. */ QStringList keyValues(const QString &aName) const; /*! \brief Gets the names of all keys. * * \return List of key names. */ QStringList keyNames() const; /*! \brief Sets the value of a key. * * If the key does not exist yet, it is created. * \param aName Name of the key. * \param aValue Value of the key. */ void setKey(const QString &aName, const QString &aValue); /*! \brief Sets multiple values for a key. * * All previous (local) values of the key are removed. A key entry for * each of the provided values is then created. * \param aName Name of the key. * \param aValues Values for the key. */ void setKeyValues(const QString &aName, const QStringList &aValues); /*! \brief Sets the value of a boolean key. * * The key value is set to "true" of "false". If the key does not exist * yet, it is created. * \param aName Name of the key. * \param aValue Value of the key. */ void setBoolKey(const QString &aName, bool aValue); /*! \brief Removes a key from profile. All instances of the key are removed. * * \param aName Name of the key to remove. */ void removeKey(const QString &aName); /*! \brief Gets the field with the given name. * * If the field does not exist, NULL is returned. * To get/set the value associated with the field, use the key handling * functions with the name of the field. * \param aName Name of the field. * \return Pointer to the field. */ const ProfileField *field(const QString &aName) const; /*! \brief Gets all fields. * * \return List of pointers to the fields. */ QList allFields() const; /*! \brief Gets all visible fields of the profile. * * Each field can define its visibility. This functions returns only * fields that are visible. * \return List of pointers to the visible fields. */ QList visibleFields() const; /*! \brief Checks if the profile is valid. * * A profile is valid if: * 1. Name and type are set (not empty). * 2. For each field there is a key with the same name, and the key value * (or all values, if multiple keys with the same name exist) is valid for * the field. * 3. All sub-profiles are valid according to these three rules. * \return Is the profile valid. */ bool isValid() const; /*! \brief Gets the names of all sub-profiles with the given type. * * \param aType Type of sub-profiles to get. If this is empty, all * sub-profile names are returned. * \return Names of the sub-profiles. */ QStringList subProfileNames(const QString &aType = "") const; /*! \brief Gets a sub-profile with the given name and type. * * \param aName Name of the sub-profile to get. * \param aType Type of the sub-profile to get. If the type is empty, * any type is accepted. * \return The first sub-profile that matches the criteria. NULL if no such * sub-profile was found. The returned sub-profile is owned by the main * profile and the user must not delete it. */ Profile *subProfile(const QString &aName, const QString &aType = ""); /*! \brief const method for subProfile \see Profile::subProfile * */ const Profile *subProfile(const QString &aName, const QString &aType = "") const; /*! \brief Gets a sub-profile by key value. * * Returns the first sub-profile that has a key with the given value. * \param aKey Name of the key. * \param aValue Required value of the key. * \param aType Type of the sub-profile. If empty, any type can match. * \param aEnabledOnly Should only enabled sub-profiles be compared. * \return First matching sub-profile, NULL if no match. */ const Profile *subProfileByKeyValue(const QString &aKey, const QString &aValue, const QString &aType, bool aEnabledOnly) const; /*! \brief Gets all sub-profiles. * * \return List of sub-profiles. The returned sub-profiles are owned by the main * profile and the user must not delete them. */ QList allSubProfiles(); /*! \brief Gets all sub-profiles as const * * \return List of sub-profiles. The returned sub-profiles are const and are owned by the main * profile and the user must not delete them. */ QList allSubProfiles() const; /*! \brief Merges a profile to this profile. * * The source profile and all its sub-profiles are merged as direct * sub-profiles of this profile. This function is mainly used by the * ProfileManager, when it constructs a single profile from multiple * sub-profile files. * \param aSource Profile to merge. */ void merge(const Profile &aSource); /*! \brief Checks if the profile is fully constructed by loading all * sub-profiles from separate profile files. * * A profile can have sub-profiles defined directly inside it, but * typically the sub-profiles are made complete by checking if there is * a separate profile file with the same name and type, and then loading * and merging the keys and fields defined in these files to the ones * defined directly in the main profile. * \return Is the profile fully loaded. */ bool isLoaded() const; /*! \brief Sets if the profile is fully loaded. * * This function is used by the ProfileManager. The purpose of this flag * is to avoid loading the same sub-profile multiple times, if there are * more than one references to it in the sub-profile tree. * \param aLoaded Is the profile loaded. */ void setLoaded(bool aLoaded); /*! \brief Returns if the profile is enabled. * * \return Is the profile enabled. */ virtual bool isEnabled() const; /*! \brief Set is the profile is enabled. * * \param aEnabled New enabled status. */ void setEnabled(bool aEnabled); /*! \brief Checks if the profile is hidden. * * A hidden profile should not be visible in sync ui. * \return True if hidden. */ bool isHidden() const; /*! \brief Checks if the profile is protected. * * A protected profile can not be removed using the ProfileManager. * \return True if protected. */ bool isProtected() const; private: Profile &operator=(const Profile &aRhs); ProfilePrivate *d_ptr; /*! \brief Generates a profile id based on keys * * \param aKeys List of keys to generate profile id * \returns Profile name * */ QString generateProfileId(const QStringList &aKeys); #ifdef SYNCFW_UNIT_TESTS friend class ProfileTest; #endif }; } #endif // PROFILE_H buteo-syncfw-0.11.10/libbuteosyncfw/profile/ProfileEngineDefs.h000066400000000000000000000120741477124122200245050ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2014-2019 Jolla Ltd. * Copyright (C) 2020 Open Mobile Platform LLC. * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef COMMONDEFS_H #define COMMONDEFS_H #include // Define string constants for different XML elements. namespace Buteo { const QString ATTR_NAME("name"); const QString ATTR_TYPE("type"); const QString ATTR_VALUE("value"); const QString ATTR_DEFAULT("default"); const QString ATTR_LABEL("label"); const QString ATTR_VISIBLE("visible"); const QString ATTR_READONLY("readonly"); const QString ATTR_ADDED("added"); const QString ATTR_DELETED("deleted"); const QString ATTR_MODIFIED("modified"); const QString ATTR_UID("uid"); const QString ATTR_STATUS("status"); const QString ATTR_TIME("time"); const QString ATTR_INTERVAL("interval"); const QString ATTR_BEGIN("begin"); const QString ATTR_END("end"); const QString ATTR_DAYS("days"); const QString ATTR_MAJOR_CODE("majorcode"); const QString ATTR_MINOR_CODE("minorcode"); const QString ATTR_ENABLED("enabled"); const QString ATTR_SYNC_CONFIGURE("syncconfiguredtime"); const QString ATTR_EXTERNAL_SYNC("externalsync"); const QString TAG_FIELD("field"); const QString TAG_PROFILE("profile"); const QString TAG_KEY("key"); const QString TAG_OPTION("option"); const QString TAG_TARGET_RESULTS("target"); const QString TAG_SYNC_RESULTS("syncresults"); const QString TAG_SYNC_LOG("synclog"); const QString TAG_LOCAL("local"); const QString TAG_REMOTE("remote"); const QString TAG_ADDED_ITEM("addedItem"); const QString TAG_DELETED_ITEM("deletedItem"); const QString TAG_MODIFIED_ITEM("modifiedItem"); const QString TAG_SCHEDULE("schedule"); const QString TAG_RUSH("rush"); const QString TAG_ERROR_ATTEMPTS("attempts"); const QString TAG_ATTEMPT_DELAY("attemptdelay"); const QString KEY_ENABLED("enabled"); const QString KEY_DISPLAY_NAME("displayname"); const QString KEY_ACTIVE("active"); const QString KEY_USE_ACCOUNTS("use_accounts"); const QString KEY_SYNC_SCHEDULED("scheduled"); const QString KEY_PLUGIN("plugin"); const QString KEY_BACKEND("backend"); const QString KEY_ACCOUNT_ID("accountid"); const QString KEY_USERNAME("Username"); const QString KEY_PASSWORD("Password"); const QString KEY_HIDDEN("hidden"); const QString KEY_PROTECTED("protected"); const QString KEY_DESTINATION_TYPE("destinationtype"); const QString KEY_SYNC_DIRECTION("Sync Direction"); const QString KEY_FORCE_SLOW_SYNC("force_slow_sync"); const QString KEY_CONFLICT_RESOLUTION_POLICY("conflictpolicy"); const QString KEY_BT_ADDRESS("bt_address"); const QString KEY_REMOTE_ID("remote_id"); const QString KEY_REMOTE_DATABASE("Remote database"); const QString KEY_BT_NAME("bt_name"); const QString KEY_BT_TRANSPORT("bt_transport"); const QString KEY_USB_TRANSPORT("usb_transport"); const QString KEY_INTERNET_TRANSPORT("internet_transport"); const QString KEY_LOAD_WITHOUT_TRANSPORT("load_without_transport"); const QString KEY_CAPS_MODIFIED("caps_modified"); const QString KEY_SYNC_SINCE_DAYS_PAST("sync_since_days_past"); // sync from this many days before the current date const QString KEY_SYNC_ALWAYS_UP_TO_DATE("sync_always_up_to_date"); const QString KEY_SYNC_EXTERNALLY("sync_externally"); const QString KEY_SOC("sync_on_change"); const QString KEY_SOC_AFTER("sync_on_change_after"); const QString KEY_LOCAL_URI("Local URI"); const QString KEY_ALWAYS_ON_ENABLED("always_on_enabled"); const QString KEY_REMOTE_NAME("remote_name"); const QString KEY_UUID("uuid"); const QString KEY_NOTES_UUID("notes_uuid"); const QString KEY_STORAGE_UPDATED("storage_updated"); const QString KEY_HTTP_PROXY_HOST("http_proxy_host"); const QString KEY_HTTP_PROXY_PORT("http_proxy_port"); const QString KEY_PROFILE_ID("profile_id"); const QString KEY_INTERNET_CONNECTION_TYPES("internet_connection_types"); const QString BOOLEAN_TRUE("true"); const QString BOOLEAN_FALSE("false"); const QString VALUE_ONLINE("online"); const QString VALUE_DEVICE("device"); const QString VALUE_TWO_WAY("two-way"); const QString VALUE_FROM_REMOTE("from-remote"); const QString VALUE_TO_REMOTE("to-remote"); const QString VALUE_PREFER_REMOTE("prefer remote"); const QString VALUE_PREFER_LOCAL("prefer local"); // Indent size for profile XML output. const int PROFILE_INDENT = 4; const QString PC_SYNC("PC-SYNC"); //For account online_template const QString SYNC_ONLINE_TEMPLATE("online_template"); } #endif // COMMONDEFS_H buteo-syncfw-0.11.10/libbuteosyncfw/profile/ProfileFactory.cpp000066400000000000000000000037421477124122200244420ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "ProfileFactory.h" #include #include "SyncProfile.h" #include "StorageProfile.h" #include "ProfileEngineDefs.h" using namespace Buteo; ProfileFactory::ProfileFactory() { } Profile *ProfileFactory::createProfile(const QString &aName, const QString &aType) { if (aType.isEmpty()) return nullptr; Profile *p = nullptr; if (aType == Profile::TYPE_SYNC) { p = new SyncProfile(aName); } else if (aType == Profile::TYPE_STORAGE) { p = new StorageProfile(aName); } // Entries for each class derived from Profile can be added here. else { p = new Profile(aName, aType); } return p; } Profile *ProfileFactory::createProfile(const QDomElement &aRoot) { Profile *p = nullptr; QString type = aRoot.attribute(ATTR_TYPE); if (type == Profile::TYPE_SYNC) { p = new SyncProfile(aRoot); } else if (type == Profile::TYPE_STORAGE) { p = new StorageProfile(aRoot); } // Entries for each class derived from Profile can be added here. else { p = new Profile(aRoot); } return p; } buteo-syncfw-0.11.10/libbuteosyncfw/profile/ProfileFactory.h000066400000000000000000000046331477124122200241070ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef PROFILEFACTORY_H #define PROFILEFACTORY_H #include "Profile.h" namespace Buteo { /*! \brief ProfileFactory handles creating Profile instances. * * ProfileFactory knows all the classed derived from the Profile class and * creates an instance of a correct class based on the profile type given as a * parameter for the create function. By using the ProfileFactory it is * possible to create profiles from a component that is not aware of the * different derived profile classes, especially from the Profile class itself. */ class ProfileFactory { public: //! \brief Constructor. ProfileFactory(); /*! \brief Creates an empty profile with the given name and type. * * An instance of the correct class derived from Profile is created based * on the given profile type. If the type is not recognizer as a specific * derived class, an instance of the Profile base class is created. * \param aName Name of the profile. * \param aType Type of the profile. * \return The created profile. */ Profile *createProfile(const QString &aName, const QString &aType); /*! \brief Creates a profile from XML. * * An instance of the correct class derived from Profile is created based * on the profile type read from XML. If the type is not recognizer as a * specific derived class, an instance of the Profile base class is * created. * \param aRoot Root element of the profile XML. * \return Created profile. */ Profile *createProfile(const QDomElement &aRoot); }; } #endif // PROFILEFACTORY_H buteo-syncfw-0.11.10/libbuteosyncfw/profile/ProfileField.cpp000066400000000000000000000130631477124122200240530ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "ProfileField.h" #include "ProfileEngineDefs.h" #include namespace Buteo { //! ProfileField Visbility Const string for always const QString ProfileField::VISIBLE_ALWAYS = "always"; //! ProfileField Visbility Const string for never const QString ProfileField::VISIBLE_NEVER = "never"; //! ProfileField Visbility Const string for user const QString ProfileField::VISIBLE_USER = "user"; //! ProfileField Visbility Const string for boolean const QString ProfileField::TYPE_BOOLEAN = "boolean"; // Private implementation class for ProfileField. class ProfileFieldPrivate { public: //! \brief Constructor ProfileFieldPrivate(); //! \brief Copy Constructor ProfileFieldPrivate(const ProfileFieldPrivate &aSource); //! \brief Name of the ProfileField QString iName; //! \brief Type of the ProfileField QString iType; //! \brief DefaultValue of the ProfileField QString iDefaultValue; //! \brief List of Options of the ProfileField QStringList iOptions; //! \brief Label of the ProfileField QString iLabel; //! \brief Visibility of the ProfileField QString iVisible; //! \brief Write Access Specifier of the ProfileField bool iReadOnly; }; } using namespace Buteo; ProfileFieldPrivate::ProfileFieldPrivate() : iReadOnly(false) { } ProfileFieldPrivate::ProfileFieldPrivate(const ProfileFieldPrivate &aSource) : iName(aSource.iName), iType(aSource.iType), iDefaultValue(aSource.iDefaultValue), iOptions(aSource.iOptions), iLabel(aSource.iLabel), iVisible(aSource.iVisible), iReadOnly(aSource.iReadOnly) { } ProfileField::ProfileField(const QDomElement &aRoot) : d_ptr(new ProfileFieldPrivate()) { d_ptr->iName = aRoot.attribute(ATTR_NAME); d_ptr->iType = aRoot.attribute(ATTR_TYPE); d_ptr->iDefaultValue = aRoot.attribute(ATTR_DEFAULT); d_ptr->iLabel = aRoot.attribute(ATTR_LABEL); d_ptr->iVisible = aRoot.attribute(ATTR_VISIBLE); d_ptr->iReadOnly = (aRoot.attribute(ATTR_READONLY).compare( BOOLEAN_TRUE, Qt::CaseInsensitive) == 0); // Parse options. QDomElement option = aRoot.firstChildElement(TAG_OPTION); for (; !option.isNull(); option = option.nextSiblingElement(TAG_OPTION)) { QString optionStr = option.text(); if (!optionStr.isEmpty()) { d_ptr->iOptions.append(optionStr); } else { // Empty value. } } // Options for boolean type are inserted automatically. if (d_ptr->iOptions.empty()) { if (d_ptr->iType == TYPE_BOOLEAN) { d_ptr->iOptions.append(BOOLEAN_TRUE); d_ptr->iOptions.append(BOOLEAN_FALSE); } } } ProfileField::ProfileField(const ProfileField &aSource) : d_ptr(new ProfileFieldPrivate(*aSource.d_ptr)) { } ProfileField::~ProfileField() { delete d_ptr; d_ptr = 0; } QString ProfileField::name() const { return d_ptr->iName; } QString ProfileField::type() const { return d_ptr->iType; } QString ProfileField::defaultValue() const { return d_ptr->iDefaultValue; } QStringList ProfileField::options() const { return d_ptr->iOptions; } QString ProfileField::label() const { return d_ptr->iLabel; } bool ProfileField::validate(const QString &aValue) const { // Value is valid if it exists in the list of options, // or if options have not been defined. if (!aValue.isEmpty() && (d_ptr->iOptions.contains(aValue) || d_ptr->iOptions.empty())) { return true; } else { return false; } } QDomElement ProfileField::toXml(QDomDocument &aDoc) const { QDomElement root = aDoc.createElement(TAG_FIELD); root.setAttribute(ATTR_NAME, d_ptr->iName); root.setAttribute(ATTR_TYPE, d_ptr->iType); root.setAttribute(ATTR_DEFAULT, d_ptr->iDefaultValue); root.setAttribute(ATTR_LABEL, d_ptr->iLabel); if (!d_ptr->iVisible.isEmpty()) root.setAttribute(ATTR_VISIBLE, d_ptr->iVisible); if (d_ptr->iReadOnly) root.setAttribute(ATTR_READONLY, BOOLEAN_TRUE); if (d_ptr->iType == TYPE_BOOLEAN) { // No need to specify true/false options, field parser will add // them automatically. } else if (!d_ptr->iOptions.isEmpty()) { foreach (QString optionStr, d_ptr->iOptions) { QDomElement e = aDoc.createElement(TAG_OPTION); QDomText t = aDoc.createTextNode(optionStr); e.appendChild(t); root.appendChild(e); } } return root; } QString ProfileField::visible() const { if (d_ptr->iVisible.isEmpty()) { return VISIBLE_USER; } else { return d_ptr->iVisible; } } bool ProfileField::isReadOnly() const { return d_ptr->iReadOnly; } buteo-syncfw-0.11.10/libbuteosyncfw/profile/ProfileField.h000066400000000000000000000104031477124122200235130ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef PROFILEFIELD_H #define PROFILEFIELD_H #include #include class QDomDocument; class QDomElement; namespace Buteo { class ProfileFieldPrivate; /*! \brief This class represents a profile field. * * Profile field is a bunch of information about a setting whose value must * be defined as a separate key/value pair in some profile. The key name must * be same as the profile field name. * The class includes functions for accessing the name, * type, description and possible values of the setting. Only the name is a * mandatory field. The class also has a function for validating a given value * against the possible values defined by the field. A ProfileField can be * constructed from XML and exported to XML. */ class ProfileField { public: //! Field should be always visible in UI. static const QString VISIBLE_ALWAYS; //! Field should never be visible in UI. static const QString VISIBLE_NEVER; //! Field should be visible in UI if a value for the field has not // been pre-defined in the sub-profiles loaded by the main profile. static const QString VISIBLE_USER; //! Field type for boolean fields. static const QString TYPE_BOOLEAN; /*! \brief Constructs a ProfileField from XML. * * \param aRoot Root element of the field XML. */ explicit ProfileField(const QDomElement &aRoot); /*! \brief Copy constructor. * * \param aSource Copy source. */ ProfileField(const ProfileField &aSource); /*! \brief Destructor. */ ~ProfileField(); /*! \brief Gets the field name. * * \return Field name. */ QString name() const; /*! \brief Get the field type. * * \return Field type. */ QString type() const; /*! \brief Gets the field default value. * * \return Field default value. */ QString defaultValue() const; /*! \brief Gets the allowed values for the field. * * \return List of valid values. */ QStringList options() const; /*! \brief Gets the field label. * * The label can be for example displayed in the UI that asks for the field * value. * \return Field label. */ QString label() const; /*! \brief Checks if the given value is in the list of allowed values. * * If allowed values have not been defined, any value is accepted. * \param aValue The value to validate. * \return Is the given value in the list of allowed values (options). */ bool validate(const QString &aValue) const; /*! \brief Exports the field to XML. * * \param aDoc Parent document for the created XML elements. The created * elements are not inserted to the document by this function, but the * document is still required for creating the elements. * \return The root element of the created XML node tree. */ QDomElement toXml(QDomDocument &aDoc) const; /*! \brief Gets the visibility of the field. * * \return String defining the visibility. See VISIBLE_ constants for * predefined values. */ QString visible() const; /*! \brief Checks if the field is read only. * * UI should not allow modifying the value of a read only field. * \return True if readonly. */ bool isReadOnly() const; private: ProfileField &operator=(const ProfileField &aRhs); ProfileFieldPrivate *d_ptr; }; } #endif // PROFILEFIELD_H buteo-syncfw-0.11.10/libbuteosyncfw/profile/ProfileManager.cpp000066400000000000000000001134371477124122200244100ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "ProfileManager.h" #include #include #include #include #include "ProfileFactory.h" #include "ProfileEngineDefs.h" #include "SyncCommonDefs.h" #include "LogMacros.h" #include "BtHelper.h" // implement here in lack of better place. not sure should this even be included in the api const QString Sync::syncConfigDir() { // This is the root for all sorts of things: data, config, logs, so using .local/share/ return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/system/privileged/msyncd"; } const QString Sync::syncCacheDir() { // IF we need a cache dir for something, this should return such, but now just // returning config as this has been used for such qWarning() << "Sync::syncCacheDir() is deprecated, use Sync::syncConfigDir(). Or not even that if possible"; return Sync::syncConfigDir(); } static const QString FORMAT_EXT = ".xml"; static const QString BACKUP_EXT = ".bak"; static const QString LOG_EXT = ".log"; static const QString LOG_DIRECTORY = "logs"; static const QString BT_PROFILE_TEMPLATE("bt_template"); static const QString DEFAULT_PRIMARY_PROFILE_PATH = Sync::syncConfigDir(); static const QString DEFAULT_SECONDARY_PROFILE_PATH = "/etc/buteo/profiles"; namespace Buteo { class ProfileManagerPrivate { public: ProfileManagerPrivate(); /*! \brief Loads a profile from persistent storage. * * \param aName Name of the profile to load. * \param aType Type of the profile to load. * \return The loaded profile. 0 if the profile was not found. */ Profile *load(const QString &aName, const QString &aType); /*! \brief Loads the synchronization log associated with the given profile. * * \param aProfileName Name of the sync profile whose log shall be loaded. * \return The loaded log. 0 if the log was not found. */ SyncLog *loadLog(const QString &aProfileName); bool parseFile(const QString &aPath, QDomDocument &aDoc); void restoreBackupIfFound(const QString &aProfilePath, const QString &aBackupPath); QDomDocument constructProfileDocument(const Profile &aProfile); bool writeProfileFile(const QString &aProfilePath, const QDomDocument &aDoc); QString findProfileFile(const QString &aName, const QString &aType); bool createBackup(const QString &aProfilePath, const QString &aBackupPath); bool matchProfile(const Profile &aProfile, const ProfileManager::SearchCriteria &aCriteria); bool matchKey(const Profile &aProfile, const ProfileManager::SearchCriteria &aCriteria); bool save(const Profile &aProfile); bool remove(const QString &aName, const QString &aType); bool profileExists(const QString &aProfileId, const QString &aType); QString iConfigPath; QString iSystemConfigPath; QHash > iSyncRetriesInfo; }; } using namespace Buteo; ProfileManagerPrivate::ProfileManagerPrivate() : iConfigPath(DEFAULT_PRIMARY_PROFILE_PATH), iSystemConfigPath(DEFAULT_SECONDARY_PROFILE_PATH) { } Profile *ProfileManagerPrivate::load(const QString &aName, const QString &aType) { QString profilePath = findProfileFile(aName, aType); QString backupProfilePath = profilePath + BACKUP_EXT; QDomDocument doc; Profile *profile = 0; restoreBackupIfFound(profilePath, backupProfilePath); if (parseFile(profilePath, doc)) { ProfileFactory pf; profile = pf.createProfile(doc.documentElement()); if (QFile::exists(backupProfilePath)) { QFile::remove(backupProfilePath); } } else { qCDebug(lcButeoCore) << "Failed to load profile:" << aName; } return profile; } SyncLog *ProfileManagerPrivate::loadLog(const QString &aProfileName) { QString fileName = iConfigPath + QDir::separator() + Profile::TYPE_SYNC + QDir::separator() + LOG_DIRECTORY + QDir::separator() + aProfileName + LOG_EXT + FORMAT_EXT; if (!QFile::exists(fileName)) { return 0; } QFile file(fileName); if (!file.open(QIODevice::ReadOnly)) { qCWarning(lcButeoCore) << "Failed to open sync log file for reading:" << file.fileName(); return 0; } QDomDocument doc; if (!doc.setContent(&file)) { file.close(); qCWarning(lcButeoCore) << "Failed to parse XML from sync log file:" << file.fileName(); return 0; } file.close(); return new SyncLog(doc.documentElement()); } bool ProfileManagerPrivate::matchProfile(const Profile &aProfile, const ProfileManager::SearchCriteria &aCriteria) { bool matched = false; const Profile *testProfile = &aProfile; if (!aCriteria.iSubProfileName.isEmpty()) { // Sub-profile name was given, request a sub-profile with a // matching name and type. testProfile = aProfile.subProfile(aCriteria.iSubProfileName, aCriteria.iSubProfileType); if (testProfile != 0) { matched = matchKey(*testProfile, aCriteria); } else { if (aCriteria.iType == ProfileManager::SearchCriteria::NOT_EXISTS) { matched = true; } else { matched = false; } } } else if (!aCriteria.iSubProfileType.isEmpty()) { // Sub-profile name was empty, but type was given. Get all // sub-profiles with the matching type. QStringList subProfileNames = aProfile.subProfileNames(aCriteria.iSubProfileType); if (!subProfileNames.isEmpty()) { matched = false; foreach (const QString &subProfileName, subProfileNames) { testProfile = aProfile.subProfile(subProfileName, aCriteria.iSubProfileType); if (testProfile != 0 && matchKey(*testProfile, aCriteria)) { matched = true; break; } } } else { if (aCriteria.iType == ProfileManager::SearchCriteria::NOT_EXISTS) { matched = true; } else { matched = false; } } } else { matched = matchKey(aProfile, aCriteria); } return matched; } bool ProfileManagerPrivate::matchKey(const Profile &aProfile, const ProfileManager::SearchCriteria &aCriteria) { bool matched = false; if (!aCriteria.iKey.isEmpty()) { // Key name was given, get a key with matching name. QString value = aProfile.key(aCriteria.iKey); if (value.isNull()) { if (aCriteria.iType == ProfileManager::SearchCriteria::NOT_EXISTS || aCriteria.iType == ProfileManager::SearchCriteria::NOT_EQUAL) { matched = true; } else { matched = false; } } else { switch (aCriteria.iType) { case ProfileManager::SearchCriteria::EXISTS: matched = true; break; case ProfileManager::SearchCriteria::NOT_EXISTS: matched = false; break; case ProfileManager::SearchCriteria::EQUAL: matched = (value == aCriteria.iValue); break; case ProfileManager::SearchCriteria::NOT_EQUAL: matched = (value != aCriteria.iValue); break; default: matched = false; break; } } } else { if (aCriteria.iType == ProfileManager::SearchCriteria::NOT_EXISTS) { matched = false; } else { matched = true; } } return matched; } ProfileManager::SearchCriteria::SearchCriteria() : iType(ProfileManager::SearchCriteria::EQUAL) { } ProfileManager::SearchCriteria::SearchCriteria(const SearchCriteria &aSource) : iType(aSource.iType), iSubProfileName(aSource.iSubProfileName), iSubProfileType(aSource.iSubProfileType), iKey(aSource.iKey), iValue(aSource.iValue) { } ProfileManager::ProfileManager() : d_ptr(new ProfileManagerPrivate) { FUNCTION_CALL_TRACE(lcButeoTrace); } ProfileManager::~ProfileManager() { FUNCTION_CALL_TRACE(lcButeoTrace); delete d_ptr; d_ptr = 0; } void ProfileManager::setPaths(const QString &configPath, const QString &systemConfigPath) { if (!configPath.isEmpty()) { d_ptr->iConfigPath = configPath; if (d_ptr->iConfigPath.endsWith(QDir::separator())) { d_ptr->iConfigPath.chop(1); } } if (!systemConfigPath.isEmpty()) { d_ptr->iSystemConfigPath = systemConfigPath; if (d_ptr->iSystemConfigPath.endsWith(QDir::separator())) { d_ptr->iSystemConfigPath.chop(1); } } } Profile *ProfileManager::profile(const QString &aName, const QString &aType) { return d_ptr->load(aName, aType); } SyncProfile *ProfileManager::syncProfile(const QString &aName) { FUNCTION_CALL_TRACE(lcButeoTrace); Profile *p = profile(aName, Profile::TYPE_SYNC); SyncProfile *syncProfile = 0; if (p != 0 && p->type() == Profile::TYPE_SYNC) { // RTTI is not allowed, use static_cast. Should be safe, because // type is verified. syncProfile = static_cast(p); // Load and merge all sub-profiles. expand(*syncProfile); // Load sync log. If not found, create an empty log. if (syncProfile->log() == 0) { SyncLog *log = d_ptr->loadLog(aName); if (0 == log) { log = new SyncLog(aName); } syncProfile->setLog(log); } } else { qCDebug(lcButeoCore) << "did not find a valid sync profile with the given name:" << aName; if (p != 0) { qCDebug(lcButeoCore) << "but found a profile of type:" << p->type() << "with the given name:" << aName; delete p; } } return syncProfile; } QStringList ProfileManager::profileNames(const QString &aType) { // Search for all profile files from the config directory QStringList names; QString nameFilter = QString("*") + FORMAT_EXT; { QDir dir(d_ptr->iConfigPath + QDir::separator() + aType); QFileInfoList fileInfoList = dir.entryInfoList(QStringList(nameFilter), QDir::Files | QDir::NoSymLinks); foreach (const QFileInfo &fileInfo, fileInfoList) { names.append(fileInfo.completeBaseName()); } } // Search for all profile files from the system config directory { QDir dir(d_ptr->iSystemConfigPath + QDir::separator() + aType); QFileInfoList fileInfoList = dir.entryInfoList(QStringList(nameFilter), QDir::Files | QDir::NoSymLinks); foreach (const QFileInfo &fileInfo, fileInfoList) { // Add only if the list does not yet contain the name. QString profileName = fileInfo.completeBaseName(); if (!names.contains(profileName)) { names.append(profileName); } } } return names; } QList ProfileManager::allSyncProfiles() { FUNCTION_CALL_TRACE(lcButeoTrace); QList profiles; QStringList names = profileNames(Profile::TYPE_SYNC); foreach (const QString &name, names) { SyncProfile *p = syncProfile(name); if (p != 0) { profiles.append(p); } } return profiles; } QList ProfileManager::allVisibleSyncProfiles() { FUNCTION_CALL_TRACE(lcButeoTrace); QList profiles = allSyncProfiles(); QList visibleProfiles; foreach (SyncProfile *p, profiles) { if (!p->isHidden()) { visibleProfiles.append(p); } else { delete p; } } return visibleProfiles; } QList ProfileManager::getSyncProfilesByData( const QString &aSubProfileName, const QString &aSubProfileType, const QString &aKey, const QString &aValue) { FUNCTION_CALL_TRACE(lcButeoTrace); QList allProfiles = allSyncProfiles(); QList matchingProfiles; foreach (SyncProfile *profile, allProfiles) { Profile *testProfile = profile; if (!aSubProfileName.isEmpty()) { // Sub-profile name was given, request a sub-profile with a // matching name and type. testProfile = profile->subProfile(aSubProfileName, aSubProfileType); } else if (!aSubProfileType.isEmpty()) { // Sub-profile name was empty, but type was given. Get the first // sub-profile with the matching type. QStringList subProfileNames = profile->subProfileNames(aSubProfileType); if (!subProfileNames.isEmpty()) { testProfile = profile->subProfile(subProfileNames.first(), aSubProfileType); } else { testProfile = 0; } } if (0 == testProfile) { // Sub-profile was not found. delete profile; profile = 0; continue; // Not a match, continue with next profile. } if (!aKey.isEmpty()) { // Key name was given, get a key with matching name. QString value = testProfile->key(aKey); if (value.isNull() || // Key was not found. (!aValue.isEmpty() && (value != aValue))) { // Value didn't match delete profile; profile = 0; continue; // Not a match, continue with next profile. } } // Match, add profile to the list to be returned. matchingProfiles.append(profile); } return matchingProfiles; } QList ProfileManager::getSyncProfilesByData( const QList &aCriteria) { FUNCTION_CALL_TRACE(lcButeoTrace); QList allProfiles = allSyncProfiles(); QList matchingProfiles; foreach (SyncProfile *profile, allProfiles) { bool matched = true; if (profile == 0) continue; foreach (const SearchCriteria &criteria, aCriteria) { if (!d_ptr->matchProfile(*profile, criteria)) { matched = false; break; } } if (matched) { matchingProfiles.append(profile); } else { delete profile; profile = 0; } } return matchingProfiles; } QList ProfileManager::getSOCProfilesForStorage( const QString &aStorageName) { FUNCTION_CALL_TRACE(lcButeoTrace); QList criteriaList; // Require that the profile is not disabled. // Profile is enabled by default. Comparing with enabled = true would // not work, because the key may not exist at all, even if the profile // is enabled. SearchCriteria profileEnabled; profileEnabled.iType = SearchCriteria::NOT_EQUAL; profileEnabled.iKey = KEY_ENABLED; profileEnabled.iValue = BOOLEAN_FALSE; criteriaList.append(profileEnabled); // Profile must not be hidden. SearchCriteria profileVisible; profileVisible.iType = SearchCriteria::NOT_EQUAL; profileVisible.iKey = KEY_HIDDEN; profileVisible.iValue = BOOLEAN_TRUE; criteriaList.append(profileVisible); // Online service. SearchCriteria onlineService; onlineService.iType = SearchCriteria::EQUAL; //onlineService.iSubProfileType = Profile::TYPE_SERVICE; // Service profile name is left empty. Key value is matched with all // found service sub-profiles, though there should be only one. onlineService.iKey = KEY_DESTINATION_TYPE; onlineService.iValue = VALUE_ONLINE; criteriaList.append(onlineService); // The profile should be interested // in SOC SearchCriteria socSupported; //socSupported.iSubProfileType = Profile::TYPE_SERVICE; socSupported.iType = SearchCriteria::EQUAL; socSupported.iKey = KEY_SOC; socSupported.iValue = BOOLEAN_TRUE; criteriaList.append(socSupported); SearchCriteria storageSupported; storageSupported.iSubProfileType = Profile::TYPE_STORAGE; storageSupported.iType = SearchCriteria::EQUAL; storageSupported.iKey = KEY_LOCAL_URI; storageSupported.iValue = aStorageName; criteriaList.append(storageSupported); return getSyncProfilesByData(criteriaList); } QList ProfileManager::getSyncProfilesByStorage( const QString &aStorageName, bool aStorageMustBeEnabled) { FUNCTION_CALL_TRACE(lcButeoTrace); QList criteriaList; // Require that the profile is not disabled. // Profile is enabled by default. Comparing with enabled = true would // not work, because the key may not exist at all, even if the profile // is enabled. SearchCriteria profileEnabled; profileEnabled.iType = SearchCriteria::NOT_EQUAL; profileEnabled.iKey = KEY_ENABLED; profileEnabled.iValue = BOOLEAN_FALSE; criteriaList.append(profileEnabled); // Profile must not be hidden. SearchCriteria profileVisible; profileVisible.iType = SearchCriteria::NOT_EQUAL; profileVisible.iKey = KEY_HIDDEN; profileVisible.iValue = BOOLEAN_TRUE; criteriaList.append(profileVisible); // Online service. SearchCriteria onlineService; onlineService.iType = SearchCriteria::EQUAL; //onlineService.iSubProfileType = Profile::TYPE_SERVICE; // Service profile name is left empty. Key value is matched with all // found service sub-profiles, though there should be only one. onlineService.iKey = KEY_DESTINATION_TYPE; onlineService.iValue = VALUE_ONLINE; criteriaList.append(onlineService); // Storage must be supported. SearchCriteria storageSupported; storageSupported.iSubProfileName = aStorageName; storageSupported.iSubProfileType = Profile::TYPE_STORAGE; if (aStorageMustBeEnabled) { // Storage must be enabled also. Storages are disabled by default, // so we can compare with enabled = true. storageSupported.iType = SearchCriteria::EQUAL; storageSupported.iKey = KEY_ENABLED; storageSupported.iValue = BOOLEAN_TRUE; } else { // Existence of the storage sub-profile is sufficient. storageSupported.iType = SearchCriteria::EXISTS; } criteriaList.append(storageSupported); return getSyncProfilesByData(criteriaList); } bool ProfileManagerPrivate::save(const Profile &aProfile) { FUNCTION_CALL_TRACE(lcButeoTrace); QDomDocument doc = constructProfileDocument(aProfile); if (doc.isNull()) { qCWarning(lcButeoCore) << "No profile data to write"; return false; } // Create path for the new profile file. QDir dir; dir.mkpath(iConfigPath + QDir::separator() + aProfile.type()); QString profilePath(iConfigPath + QDir::separator() + aProfile.type() + QDir::separator() + aProfile.name() + FORMAT_EXT); // Create a backup of the existing profile file. QString oldProfilePath = findProfileFile(aProfile.type(), aProfile.name()); QString backupPath = profilePath + BACKUP_EXT; if (QFile::exists(oldProfilePath) && !createBackup(oldProfilePath, backupPath)) { qCWarning(lcButeoCore) << "Failed to create profile backup"; } bool profileWritten = false; if (writeProfileFile(profilePath, doc)) { QFile::remove(backupPath); profileWritten = true; } else { qCWarning(lcButeoCore) << "Failed to save profile:" << aProfile.name(); profileWritten = false; } return profileWritten; } Profile *ProfileManager::profileFromXml(const QString &aProfileAsXml) { FUNCTION_CALL_TRACE(lcButeoTrace); Profile *profile = nullptr; if (!aProfileAsXml.isEmpty()) { QDomDocument doc; QString error; if (doc.setContent(aProfileAsXml, true, &error)) { ProfileFactory pf; profile = pf.createProfile(doc.documentElement()); } else { qCWarning(lcButeoCore) << "Cannot parse profile: " + error; } } return profile; } QString ProfileManager::updateProfile(const Profile &aProfile) { FUNCTION_CALL_TRACE(lcButeoTrace); // Don't save invalid profiles. if (aProfile.name().isEmpty() || aProfile.type().isEmpty()) { qCWarning(lcButeoCore) << "Malformed profile, missing name or type."; return QString(); } // We must have a profile existing before updating it... bool exists = d_ptr->profileExists(aProfile.name(), aProfile.type()); QString profileId(""); // We need to save before emit the signalProfileChanged, if this is the first // update the profile will only exists on disk after the save and any operation // using this profile triggered by the signal will fail. if (d_ptr->save(aProfile)) { profileId = aProfile.name(); } // Profile did not exist, it was a new one. Add it and emit signal with "added" value: if (!exists) { emit signalProfileChanged(aProfile.name(), ProfileManager::PROFILE_ADDED, aProfile.toString()); } else { emit signalProfileChanged(aProfile.name(), ProfileManager::PROFILE_MODIFIED, aProfile.toString()); } return profileId; } SyncProfile *ProfileManager::createTempSyncProfile (const QString &destAddress, bool &saveNewProfile) { FUNCTION_CALL_TRACE(lcButeoTrace); qCDebug(lcButeoCore) << "createTempSyncProfile(" << destAddress << ")"; if (destAddress.contains("USB")) { //USB - PCSUite no requirement to save profile qCInfo(lcButeoCore) << "USB connect - pc"; SyncProfile *profile = new SyncProfile(PC_SYNC); profile->setBoolKey(KEY_HIDDEN, true); profile->setKey(KEY_DISPLAY_NAME, PC_SYNC); qCDebug(lcButeoCore) << "USB connect does not require a sync profile to be created."; return profile; } saveNewProfile = true; BtHelper btHelp(destAddress); QString profileDisplayName = btHelp.getDeviceProperties().value("Name").toString(); if (profileDisplayName.isEmpty()) { //Fixes 171340 profileDisplayName = QString ("qtn_sync_dest_name_device_default"); } qCInfo(lcButeoCore) << "Profile Name :" << profileDisplayName; SyncProfile *tProfile = syncProfile(BT_PROFILE_TEMPLATE); tProfile->setKey(KEY_DISPLAY_NAME, profileDisplayName); QStringList keys ; keys << destAddress << tProfile->name(); tProfile->setName(keys); tProfile->setEnabled(true); tProfile->setBoolKey("hidden", false); QStringList subprofileNames = tProfile->subProfileNames(); Q_FOREACH (const QString &spn, subprofileNames) { if (spn == QLatin1String("bt")) { // this is the bluetooth profile. Set some Bluetooth-specific keys here. Profile *btSubprofile = tProfile->subProfile(spn); btSubprofile->setKey(KEY_BT_ADDRESS, destAddress); btSubprofile->setKey(KEY_BT_NAME, profileDisplayName); btSubprofile->setEnabled(true); } } return tProfile; } void ProfileManager::enableStorages(Profile &aProfile, QMap &aStorageMap, bool *aModified) { FUNCTION_CALL_TRACE(lcButeoTrace); QMapIterator i(aStorageMap); qCInfo(lcButeoCore) << "ProfileManager::enableStorages"; while (i.hasNext()) { i.next(); Profile *profile = aProfile.subProfile(i.key(), Profile::TYPE_STORAGE); if (profile) { if (profile->isEnabled() != i.value()) { profile->setEnabled(i.value()); if (aModified) { *aModified = true; } } } else { qCWarning(lcButeoCore) << "No storage profile by key :" << i.key(); } } return ; } void ProfileManager::setStoragesVisible(Profile &aProfile, QMap &aStorageMap, bool *aModified) { FUNCTION_CALL_TRACE(lcButeoTrace); QMapIterator i(aStorageMap); qCInfo(lcButeoCore) << "ProfileManager::enableStorages"; while (i.hasNext()) { i.next(); Profile *profile = aProfile.subProfile(i.key(), Profile::TYPE_STORAGE); if (profile) { // For setting the "hidden" value to correspond visiblity, invert the value from map. if (profile->boolKey(Buteo::KEY_HIDDEN) == i.value()) { profile->setBoolKey(Buteo::KEY_HIDDEN, !i.value()); if (aModified) { *aModified = true; } } } else { qCWarning(lcButeoCore) << "No storage profile by key :" << i.key(); } } return ; } bool ProfileManager::removeProfile(const QString &aProfileId) { FUNCTION_CALL_TRACE(lcButeoTrace); bool success = false; SyncProfile *profile = syncProfile(aProfileId); if (profile) { success = d_ptr->remove(aProfileId, profile->type()); if (success) { emit signalProfileChanged(aProfileId, ProfileManager::PROFILE_REMOVED, QString("")); } delete profile; profile = nullptr; } return success; } bool ProfileManagerPrivate::remove(const QString &aName, const QString &aType) { FUNCTION_CALL_TRACE(lcButeoTrace); bool success = false; QString filePath = iConfigPath + QDir::separator() + aType + QDir::separator() + aName + FORMAT_EXT; // Try to load profile without expanding it. We need to check from the // profile data if the profile is protected before removing it. Profile *p = load(aName, aType); if (p) { if (!p->isProtected()) { success = QFile::remove(filePath); if (success) { QString logFilePath = iConfigPath + QDir::separator() + aType + QDir::separator() + LOG_DIRECTORY + QDir::separator() + aName + LOG_EXT + FORMAT_EXT; //Initial the will be no log this will fail. QFile::remove(logFilePath); } } else { qCDebug(lcButeoCore) << "Cannot remove protected profile:" << aName ; } delete p; p = 0; } else { qCDebug(lcButeoCore) << "Profile not found from the config path, cannot remove:" << aName; } return success; } void ProfileManager::expand(Profile &aProfile) { if (aProfile.isLoaded()) return; // Already expanded. // Load and merge sub-profiles. int prevSubCount = 0; QList subProfiles = aProfile.allSubProfiles(); int subCount = subProfiles.size(); while (subCount > prevSubCount) { foreach (Profile *sub, subProfiles) { if (!sub->isLoaded()) { Profile *loadedProfile = profile(sub->name(), sub->type()); if (loadedProfile != 0) { aProfile.merge(*loadedProfile); delete loadedProfile; loadedProfile = 0; } else { // No separate profile file for the sub-profile. qCDebug(lcButeoCore) << "Referenced sub-profile not found:" << sub->name(); qCDebug(lcButeoCore) << "Referenced from:" << aProfile.name() << aProfile.type(); } sub->setLoaded(true); } } // Load/merge may have created new sub-profile entries. Those need // to be loaded also. Loop if sub-profile count has changed. prevSubCount = subCount; subProfiles = aProfile.allSubProfiles(); subCount = subProfiles.size(); } aProfile.setLoaded(true); } bool ProfileManager::saveLog(const SyncLog &aLog) { FUNCTION_CALL_TRACE(lcButeoTrace); QDir dir; QString fullPath = d_ptr->iConfigPath + QDir::separator() + Profile::TYPE_SYNC + QDir::separator() + LOG_DIRECTORY; dir.mkpath(fullPath); QFile file(fullPath + QDir::separator() + aLog.profileName() + LOG_EXT + FORMAT_EXT); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { qCWarning(lcButeoCore) << "Failed to open sync log file for writing:" << file.fileName(); return false; } QDomDocument doc; QDomProcessingInstruction xmlHeading = doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""); doc.appendChild(xmlHeading); QDomElement root = aLog.toXml(doc); if (root.isNull()) { qCWarning(lcButeoCore) << "Failed to convert sync log to XML"; return false; } doc.appendChild(root); QTextStream outputStream(&file); outputStream << doc.toString(PROFILE_INDENT); file.close(); return true; } void ProfileManager::saveRemoteTargetId(Profile &aProfile, const QString &aTargetId) { FUNCTION_CALL_TRACE(lcButeoTrace); qCDebug(lcButeoCore) << "saveRemoteTargetId :" << aTargetId; aProfile.setKey (KEY_REMOTE_ID, aTargetId); updateProfile(aProfile); //addProfile(aProfile); } bool ProfileManager::rename(const QString &aName, const QString &aNewName) { FUNCTION_CALL_TRACE(lcButeoTrace); bool ret = false; // Rename the sync profile QString source = d_ptr->iConfigPath + QDir::separator() + Profile::TYPE_SYNC + QDir::separator() + aName + FORMAT_EXT; QString destination = d_ptr->iConfigPath + QDir::separator() + Profile::TYPE_SYNC + QDir::separator() + aNewName + FORMAT_EXT; ret = QFile::rename(source, destination); if (true == ret) { // Rename the sync log QString sourceLog = d_ptr->iConfigPath + QDir::separator() + Profile::TYPE_SYNC + QDir::separator() + LOG_DIRECTORY + QDir::separator() + aName + LOG_EXT + FORMAT_EXT; QString destinationLog = d_ptr->iConfigPath + QDir::separator() + Profile::TYPE_SYNC + QDir::separator() + LOG_DIRECTORY + QDir::separator() + aNewName + LOG_EXT + FORMAT_EXT; ret = QFile::rename(sourceLog, destinationLog); if (false == ret) { // Roll back the earlier rename QFile::rename(destination, source); } } if (false == ret) { qCWarning(lcButeoCore) << "Failed to rename profile" << aName; } return ret; } bool ProfileManager::saveSyncResults(QString aProfileName, const SyncResults &aResults) { FUNCTION_CALL_TRACE(lcButeoTrace); bool success = false; SyncProfile *profile = syncProfile(aProfileName); if (profile) { SyncLog *log = profile->log(); if (log) { log->addResults(aResults); success = saveLog(*log); //Emitting signal emit signalProfileChanged(aProfileName, ProfileManager::PROFILE_LOGS_MODIFIED, profile->toString()); } delete profile; profile = 0; } return success; } bool ProfileManager::setSyncSchedule(QString aProfileId, QString aScheduleAsXml) { FUNCTION_CALL_TRACE(lcButeoTrace); bool status = false; SyncProfile *profile = syncProfile(aProfileId); if (profile) { profile->setSyncType(SyncProfile::SYNC_SCHEDULED); QDomDocument doc; if (doc.setContent(aScheduleAsXml, true)) { SyncSchedule schedule(doc.documentElement()); profile->setSyncSchedule(schedule); updateProfile(*profile); status = true; } delete profile; profile = nullptr; } else { qCWarning(lcButeoCore) << "Invalid Profile Supplied"; } return status; } bool ProfileManagerPrivate::parseFile(const QString &aPath, QDomDocument &aDoc) { bool parsingOk = false; if (QFile::exists(aPath)) { QFile file(aPath); if (file.open(QIODevice::ReadOnly)) { parsingOk = aDoc.setContent(&file); file.close(); if (!parsingOk) { qCWarning(lcButeoCore) << "Failed to parse profile XML: " << aPath; } } else { qCWarning(lcButeoCore) << "Failed to open profile file for reading:" << aPath; } } else { qCDebug(lcButeoCore) << "Profile file not found:" << aPath; } return parsingOk; } QDomDocument ProfileManagerPrivate::constructProfileDocument(const Profile &aProfile) { QDomDocument doc; QDomElement root = aProfile.toXml(doc); if (root.isNull()) { qCWarning(lcButeoCore) << "Failed to convert profile to XML"; } else { QDomProcessingInstruction xmlHeading = doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""); doc.appendChild(xmlHeading); doc.appendChild(root); } return doc; } bool ProfileManagerPrivate::writeProfileFile(const QString &aProfilePath, const QDomDocument &aDoc) { FUNCTION_CALL_TRACE(lcButeoTrace); qCWarning(lcButeoCore) << "writeProfileFile() called, forcing disk write:" << aProfilePath; QFile file(aProfilePath); bool profileWritten = false; if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { QTextStream outputStream(&file); outputStream << aDoc.toString(PROFILE_INDENT); file.close(); profileWritten = true; } else { qCWarning(lcButeoCore) << "Failed to open profile file for writing:" << aProfilePath; profileWritten = false; } return profileWritten; } void ProfileManagerPrivate::restoreBackupIfFound(const QString &aProfilePath, const QString &aBackupPath) { if (QFile::exists(aBackupPath)) { qCWarning(lcButeoCore) << "Profile backup file found. The actual profile may be corrupted."; QDomDocument doc; if (parseFile(aBackupPath, doc)) { qCDebug(lcButeoCore) << "Restoring profile from backup"; QFile::remove(aProfilePath); QFile::copy(aBackupPath, aProfilePath); } else { qCWarning(lcButeoCore) << "Failed to parse backup file"; qCDebug(lcButeoCore) << "Removing backup file"; QFile::remove(aBackupPath); } } } bool ProfileManagerPrivate::createBackup(const QString &aProfilePath, const QString &aBackupPath) { FUNCTION_CALL_TRACE(lcButeoTrace); return QFile::copy(aProfilePath, aBackupPath); } QString ProfileManagerPrivate::findProfileFile(const QString &aName, const QString &aType) { QString fileName = aType + QDir::separator() + aName + FORMAT_EXT; QString primaryPath = iConfigPath + QDir::separator() + fileName; QString secondaryPath = iSystemConfigPath + QDir::separator() + fileName; if (QFile::exists(primaryPath)) { return primaryPath; } else if (!QFile::exists(secondaryPath)) { return primaryPath; } else { return secondaryPath; } } // this function checks to see if its a new profile or an // existing profile being modified under $Sync::syncConfigDir/profiles directory. bool ProfileManagerPrivate::profileExists(const QString &aProfileId, const QString &aType) { QString profileFile = iConfigPath + QDir::separator() + aType + QDir::separator() + aProfileId + FORMAT_EXT; qCDebug(lcButeoCore) << "profileFile:" << profileFile; return QFile::exists(profileFile); } void ProfileManager::addRetriesInfo(const SyncProfile *profile) { FUNCTION_CALL_TRACE(lcButeoTrace); if (profile) { if (profile->hasRetries() && !d_ptr->iSyncRetriesInfo.contains(profile->name())) { qCDebug(lcButeoCore) << "syncretries : retries info present for profile" << profile->name(); d_ptr->iSyncRetriesInfo[profile->name()] = profile->retryIntervals(); } } } QDateTime ProfileManager::getNextRetryInterval(const SyncProfile *aProfile) { FUNCTION_CALL_TRACE(lcButeoTrace); QDateTime nextRetryInterval; if (aProfile && d_ptr->iSyncRetriesInfo.contains(aProfile->name()) && !d_ptr->iSyncRetriesInfo[aProfile->name()].isEmpty()) { quint32 mins = d_ptr->iSyncRetriesInfo[aProfile->name()].takeFirst(); nextRetryInterval = QDateTime::currentDateTime().addSecs(mins * 60); qCDebug(lcButeoCore) << "syncretries : retry for profile" << aProfile->name() << "in" << mins << "minutes"; qCDebug(lcButeoCore) << "syncretries :" << d_ptr->iSyncRetriesInfo[aProfile->name()].count() << "attempts remain"; } return nextRetryInterval; } void ProfileManager::retriesDone(const QString &aProfileName) { FUNCTION_CALL_TRACE(lcButeoTrace); if (d_ptr->iSyncRetriesInfo.contains(aProfileName)) { d_ptr->iSyncRetriesInfo.remove(aProfileName); qCDebug(lcButeoCore) << "syncretries : retry success for" << aProfileName; } } buteo-syncfw-0.11.10/libbuteosyncfw/profile/ProfileManager.h000066400000000000000000000334451477124122200240550ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef PROFILEMANAGER_H #define PROFILEMANAGER_H #include "SyncProfile.h" #include "Profile.h" #include #include #include namespace Buteo { class ProfileManagerPrivate; /*! \brief * ProfileManager is responsible for storing and retrieving the profiles. * * It also constructs top level profiles by loading and merging all referenced * sub-profiles. The ProfileManager hides the actual storage from the user, so * that it makes no difference if the profiles are stored to simple XML-files * or to a database. Profiles can be queried by name and type. */ class ProfileManager: public QObject { Q_OBJECT public: //! Search criteria for finding profiles. struct SearchCriteria { //! Enum to identify if a member type exists or not enum Type { //! Sub-profile (and key) exists. EXISTS, //! Sub-profile (or key) does not exist. NOT_EXISTS, //! Key value is equal. EQUAL, //! Key value is not equal. NOT_EQUAL }; //! \brief Constructor. SearchCriteria(); //! \brief Copy constructor. SearchCriteria(const SearchCriteria &aSource); //! Search criteria type. Type iType; //! Sub-profile name. If this is empty but profile type is given, //! matching is tried with each sub-profile of correct type. If both //! profile name and type are empty, mathing is done with keys of the //! main profile. QString iSubProfileName; //! Sub-profile type. If this is empty but profile name is given, //! matching is done with the first sub-profile having the correct name //! regardless of the type. QString iSubProfileType; //! Key name. If this is empty, key comparison is not made. QString iKey; //! Key value. This must be given if criteria type is EQUAL or NOT_EQUAL. QString iValue; }; //! \brief Enum to indicate the change type of the Profile Operation enum ProfileChangeType { //! a New Profile has been added PROFILE_ADDED = 0, //! a Existing Profile has been modified PROFILE_MODIFIED, //! Profile has been Removed PROFILE_REMOVED, //! Profile log file Modified. PROFILE_LOGS_MODIFIED }; /*! \brief Constructor. */ ProfileManager(); /*! \brief Destructor. */ ~ProfileManager(); /*! \brief Gets the names of all available profiles with the given type. * * \param aType Type of the profiles to get. * \return The list of profile names. */ QStringList profileNames(const QString &aType); /*! \brief Gets a sync profile. * * Loads and merges also all sub-profiles that are referenced from the * main profile. Loads the log of finished synchronization sessions with * this profile. * \param aName Name of the profile to get. * \return The sync profile. NULL if the profile is not found. Caller becomes * the owner of the returned object and is responsible of deleting it after * use. Changes made to the profile are not saved to the persistent profile * storage, unless save function of this class is called. */ SyncProfile *syncProfile(const QString &aName); /*! \brief Gets all sync profiles. * * \return The list of sync profiles. Caller is responsible for deleting * the returned profile objects. */ QList allSyncProfiles(); /*! \brief Gets all visible sync profiles. * * Returns all sync profiles that should be visible in sync ui. A profile * is visible if it has not been explicitly set as hidden. * \return The list of sync profiles. Caller is responsible for deleting * the returned profile objects. */ QList allVisibleSyncProfiles(); /*! \brief Gets profiles with matching data. * * \param aSubProfileName Name of a required sub-profile. If this is given, * the sub-profile must exist and key comparison is made with the keys * of the sub-profile. * \param aSubProfileType Type of a required sub-profile. If this is given but * sub-profile name is empty, the first sub-profile with matching type is * used in comparison. * \param aKey Name of a required key. If this is empty, key comparison is * not made and existance of the sub-profile is enough. * \param aValue Value of the required key. If this is empty, any value * is accepted as long as the key itself exists. * \return List of matching profiles. Caller is responsible for deleting * the returned profile objects. */ QList getSyncProfilesByData(const QString &aSubProfileName, const QString &aSubProfileType, const QString &aKey = "", const QString &aValue = ""); /*! \brief Gets profiles with matching data. * * \param aCriteria List of criteria to use in the search. Each criterion * in the list has to match for a profile to be returned as a result. * \return List of matching profiles. Caller is responsible for deleting * the returned profile objects. */ QList getSyncProfilesByData( const QList &aCriteria); /*! \brief Gets profiles based on supported storages. * * Returns all enabled and visible sync profiles of online destinations * that support the given storage. Device-to-device sync profiles are not * returned. * \param aStorageName Name of the storage that must be supported. * \param aStorageMustBeEnabled True if the supported storage must be * also enabled. Only enabled storages are included in sync session. * \return List of matching profiles. Caller is responsible for deleting * the returned profile objects. */ QList getSyncProfilesByStorage( const QString &aStorageName, bool aStorageMustBeEnabled = false); /*! \brief Gets profiles interested in sync on change for a storage * * Returns all enabled and visible sync profiles of online destinations * Device-to-device sync profiles are not returned. * \param aStorageName Name of the storage * \return List of matching profiles. Caller is responsible for deleting * the returned profile objects. */ QList getSOCProfilesForStorage( const QString &aStorageName); /*! \brief Expands the given profile. * * Loads and merges all sub-profiles that are referenced from the main * profile. * \param aProfile Name of the profile to expand. */ void expand(Profile &aProfile); /*! \brief Saves the given synchronization log. * * \param aLog Log to save. * \return True if saving was successful. */ bool saveLog(const SyncLog &aLog); /*! \brief Saves the results of a sync session to the log. * * This is a convenience function that loads the log associated with the * given profile, appends the given results to the log and then saves the * log. * \param aProfileName Name of the profile used in the sync session. * \param aResults Results. * \return True if saving was successful. */ bool saveSyncResults(QString aProfileName, const SyncResults &aResults); /*! \brief Gets a profile. * * \param aName Name of the profile to get. * \param aType Type of the profile to get. * \return Pointer to the profile. If the profile is not found, NULL is * returned. Caller is responsible for deleting the returned object. * Changes made to the profile are not saved to profile storage, unless * updateProfile function of this class is called */ Profile *profile(const QString &aName, const QString &aType); /*! \brief Gets a temporary profile (saved if sync is sucessfull). * * \param btAddress Address of the remote device bt address/usb . * \param saveNewProfile If to save the profile or not (e.g pc suite profile) * \return Pointer to the profile. * Changes made to the profile are not saved to profile storage, unless * save function of this class is called */ SyncProfile *createTempSyncProfile (const QString &btAddress, bool &saveNewProfile); /*! \brief Updates the existing profile with the profile * given as parameter and emits profileChanged() Signal with appropriate value * depening if profile was newly added (0) or updated (1) * * NOTE: only Sync Profiles can be updated using ProfileManger * * \param aProfile - Profile Object * \return profileId - this will be empty if the update Failed. */ QString updateProfile(const Profile &aProfile); /*! \brief Deletes a profile from the persistent storage. * * This will emit a signalProfileChanged with ChangeType * as Removed if Removal is successful * NOTE: only Sync Profiles can be updated using ProfileManger * \param aProfileId Profile to be remove. * \return Success indicator. */ bool removeProfile(const QString &aProfileId); /*! \brief Renames a profile, and the associated log too * * \param aName The old name of the profile * \param aNewName The new name for the profile * \return Returns true if the rename was successful */ bool rename(const QString &aName, const QString &aNewName); /*! \brief Enables sync'd storages in profile * * \param aProfile Profile of the remote device * \param aStorageMap Map of storage names(hcalendar, hcontacts) and if sync * enabled value true/false * \param aModified Whether the profile was updated as a result of this function call, * and thus requires writing to disk */ void enableStorages (Profile &aProfile, QMap &aStorageMap, bool *aModified = NULL); /*! \brief Sets storage subprofiles hidden status for the given profile * * \param aProfile Profile of the remote device * \param aStorageMap Map of storage names (hcalendar, hcontacts) and visibility status. With value \e true * the storage will be set visible (equals profile attribute hidden=false) * \param aModified Whether the profile was updated as a result of this function call, * and thus requires writing to disk */ void setStoragesVisible(Profile &aProfile, QMap &aStorageMap, bool *aModified = NULL); /*! \brief Sets remote target in profile * * \param aProfile Profile of the remote device * \param aId remote device id * */ void saveRemoteTargetId (Profile &aProfile, const QString &aId); /*! \brief Sets/Overwrites the schedule to a profile * * \param aProfileId Profile Id * \param aScheduleAsXml SyncSchedule Object as an xml string * */ bool setSyncSchedule(QString aProfileId, QString aScheduleAsXml); /*! \brief checks if a profile has retries info and stores the same * * @param aProfile sync profile */ void addRetriesInfo(const SyncProfile *aProfile); /*! \brief gets the next retry after time for a sync profile * * @param aProfile sync profile * @return next retry interval */ QDateTime getNextRetryInterval(const SyncProfile *aProfile); /*! \brief call this to indicate that retries have to stop for a certain * sync for a profile - either the no. of retry attempts exhausted or one of the retries succeeded * * @param aProfileName name of the profile */ void retriesDone(const QString &aProfileName); #ifdef SYNCFW_UNIT_TESTS friend class ProfileManagerTest; #endif // for testing purposes only // configPath is the root of primary profile directory, used for writing changes // systemConfigPath is for read-only system profiles void setPaths(const QString &configPath, const QString &systemConfigPath); /*! \brief Gets a profile object from an xml document. * * \param aProfileAsXml Name of the profile to get. * \return Pointer to the profile. If the xml is not valid, NULL is * returned. Caller is responsible for deleting the returned object. * Changes made to the profile are not saved to profile storage, unless * updateProfile function of this class is called */ static Profile *profileFromXml(const QString &aProfileAsXml); signals: /*! \brief Notifies about a change in profile. * * This signal is sent when the profile data is modified or when a profile * is added or deleted in msyncd. * \param aProfileName Name of the changed profile. * \param aChangeType \see ProfileManager::ProfileChangeType * \param aProfileAsXml Updated Profile Object is sent as xml */ void signalProfileChanged(QString aProfileName, int aChangeType, QString aProfileAsXml); private: ProfileManager &operator=(const ProfileManager &aRhs); ProfileManagerPrivate *d_ptr; }; } #endif // PROFILEMANAGER_H buteo-syncfw-0.11.10/libbuteosyncfw/profile/Profile_p.h000066400000000000000000000056251477124122200231000ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef PROFILE_P_H #define PROFILE_P_H #include #include #include #include "ProfileField.h" namespace Buteo { //! Private implementation class for Profile class. class ProfilePrivate { public: //! \brief Constructor ProfilePrivate(); //! \brief Copy Constructor ProfilePrivate(const ProfilePrivate &aSource); //! \brief Destructor ~ProfilePrivate(); //! Profile name. QString iName; //! Profile type. QString iType; //! Is the profile fully loaded and constructed. bool iLoaded; //! Is the profile merged created by merging from sub-profile. bool iMerged; //! Local keys, that are not merged from sub-profiles. QMap iLocalKeys; //! Keys that are merged from sub-profile files. QMap iMergedKeys; //! Local fields, that are not merged from sub-profiles. QList iLocalFields; //! Fields that are merged from sub-profiles. QList iMergedFields; //! List of sub-profiles. QList iSubProfiles; }; } Buteo::ProfilePrivate::ProfilePrivate() : iLoaded(false), iMerged(false) { } Buteo::ProfilePrivate::ProfilePrivate(const ProfilePrivate &aSource) : iName(aSource.iName), iType(aSource.iType), iLoaded(aSource.iLoaded), iMerged(aSource.iMerged), iLocalKeys(aSource.iLocalKeys), iMergedKeys(aSource.iMergedKeys) { foreach (const ProfileField *localField, aSource.iLocalFields) { iLocalFields.append(new ProfileField(*localField)); } foreach (const ProfileField *mergedField, aSource.iMergedFields) { iMergedFields.append(new ProfileField(*mergedField)); } foreach (Profile *p, aSource.iSubProfiles) { iSubProfiles.append(p->clone()); } } Buteo::ProfilePrivate::~ProfilePrivate() { qDeleteAll(iLocalFields); iLocalFields.clear(); qDeleteAll(iMergedFields); iMergedFields.clear(); qDeleteAll(iSubProfiles); iSubProfiles.clear(); } #endif // PROFILE_P_H buteo-syncfw-0.11.10/libbuteosyncfw/profile/StorageProfile.cpp000066400000000000000000000037611477124122200244400ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "StorageProfile.h" #include "ProfileEngineDefs.h" namespace Buteo { // Private implementation class for StorageProfile. Currently not needed, but // reserved for future usage. class StorageProfilePrivate { public: StorageProfilePrivate(); StorageProfilePrivate(const StorageProfilePrivate &aSource); }; } using namespace Buteo; StorageProfilePrivate::StorageProfilePrivate() { } StorageProfilePrivate::StorageProfilePrivate( const StorageProfilePrivate & /*aSource*/) { } StorageProfile::StorageProfile(const QString &aName) : Profile(aName, Profile::TYPE_STORAGE), d_ptr(new StorageProfilePrivate()) { } StorageProfile::StorageProfile(const QDomElement &aRoot) : Profile(aRoot), d_ptr(new StorageProfilePrivate()) { } StorageProfile::StorageProfile(const StorageProfile &aSource) : Profile(aSource), d_ptr(new StorageProfilePrivate(*aSource.d_ptr)) { } StorageProfile::~StorageProfile() { delete d_ptr; d_ptr = 0; } StorageProfile *StorageProfile::clone() const { return new StorageProfile(*this); } bool StorageProfile::isEnabled() const { return boolKey(KEY_ENABLED, false); } buteo-syncfw-0.11.10/libbuteosyncfw/profile/StorageProfile.h000066400000000000000000000042071477124122200241010ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef STORAGEPROFILE_H #define STORAGEPROFILE_H #include "Profile.h" namespace Buteo { class StorageProfilePrivate; /*! \brief Storage Profile Class */ class StorageProfile : public Profile { public: /*! \brief Constructs an empty StorageProfile with the given name. * * \param aName Name of the profile to create. */ explicit StorageProfile(const QString &aName); /*! \brief Constructs a profile from the given XML. * * \param aRoot Root element of the XML node tree. */ explicit StorageProfile(const QDomElement &aRoot); /*! \brief Copy constructor. * * \param aSource Copy source. */ StorageProfile(const StorageProfile &aSource); /*! \brief Destructor. */ ~StorageProfile(); /*! \brief Creates a clone of the profile. * * \return The clone. */ virtual StorageProfile *clone() const; /*! \brief Returns if the profile is enabled. * * Storage profile is disabled by default. This means that if there is no * key for the enabled status, the profile will be disabled. * \return Is the profile enabled. */ virtual bool isEnabled() const; private: StorageProfile &operator=(const StorageProfile &aRhs); StorageProfilePrivate *d_ptr; }; } #endif // STORAGEPROFILE_H buteo-syncfw-0.11.10/libbuteosyncfw/profile/SyncLog.cpp000066400000000000000000000123741477124122200230710ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "SyncLog.h" #include "LogMacros.h" #include #include #include "ProfileEngineDefs.h" namespace Buteo { bool syncResultPointerLessThan(const SyncResults *&aLhs, const SyncResults *&aRhs) { if (aLhs && aRhs) { return (*aLhs < *aRhs); } else { return false; } } bool isSyncSuccessful(const SyncResults &aResults) { return (aResults.majorCode() == SyncResults::SYNC_RESULT_SUCCESS && aResults.minorCode() == SyncResults::NO_ERROR && !aResults.syncTime().isNull()); } // Private implementation class for SyncLog. class SyncLogPrivate { public: SyncLogPrivate(); SyncLogPrivate(const SyncLogPrivate &aSource); ~SyncLogPrivate(); // Name of the profile this log belongs to. QString iProfileName; // List of the sync results this log consists of. QList iResults; // Last successful sync result as stored in the log. SyncResults *iLastSuccessfulResults; void updateLastSuccessfulResults(const SyncResults &aResults); }; } using namespace Buteo; SyncLogPrivate::SyncLogPrivate() : iLastSuccessfulResults(0) { } SyncLogPrivate::SyncLogPrivate(const SyncLogPrivate &aSource) : iProfileName(aSource.iProfileName), iLastSuccessfulResults(0) { foreach (const SyncResults *results, aSource.iResults) { iResults.append(new SyncResults(*results)); } if (aSource.iLastSuccessfulResults) iLastSuccessfulResults = new SyncResults(*aSource.iLastSuccessfulResults); } SyncLogPrivate::~SyncLogPrivate() { qDeleteAll(iResults); iResults.clear(); delete iLastSuccessfulResults; } void SyncLogPrivate::updateLastSuccessfulResults(const SyncResults &aResults) { if (isSyncSuccessful(aResults) && (!iLastSuccessfulResults || *iLastSuccessfulResults < aResults)) { delete iLastSuccessfulResults; iLastSuccessfulResults = new SyncResults(aResults); } } SyncLog::SyncLog(const QString &aProfileName) : d_ptr(new SyncLogPrivate()) { d_ptr->iProfileName = aProfileName; } SyncLog::SyncLog(const QDomElement &aRoot) : d_ptr(new SyncLogPrivate()) { d_ptr->iProfileName = aRoot.attribute(ATTR_NAME); QDomElement results = aRoot.firstChildElement(TAG_SYNC_RESULTS); for (; !results.isNull(); results = results.nextSiblingElement(TAG_SYNC_RESULTS)) { addResults(SyncResults(results)); } // Sort result entries by sync time. //std::sort(d_ptr->iResults.begin(), d_ptr->iResults.end(), syncResultPointerLessThan); } SyncLog::SyncLog(const SyncLog &aSource) : d_ptr(new SyncLogPrivate(*aSource.d_ptr)) { } SyncLog::~SyncLog() { delete d_ptr; d_ptr = 0; } void SyncLog::setProfileName(const QString &aProfileName) { d_ptr->iProfileName = aProfileName; } QString SyncLog::profileName() const { return d_ptr->iProfileName; } QDomElement SyncLog::toXml(QDomDocument &aDoc) const { QDomElement root = aDoc.createElement(TAG_SYNC_LOG); root.setAttribute(ATTR_NAME, d_ptr->iProfileName); if (d_ptr->iLastSuccessfulResults && (d_ptr->iResults.isEmpty() || *d_ptr->iLastSuccessfulResults < *d_ptr->iResults.first())) { root.appendChild(d_ptr->iLastSuccessfulResults->toXml(aDoc)); } foreach (const SyncResults *results, d_ptr->iResults) root.appendChild(results->toXml(aDoc)); return root; } const SyncResults *SyncLog::lastResults() const { FUNCTION_CALL_TRACE(lcButeoTrace); if (d_ptr->iResults.isEmpty()) { return 0; } else { return d_ptr->iResults.last(); } } QList SyncLog::allResults() const { return d_ptr->iResults; } const SyncResults *SyncLog::lastSuccessfulResults() const { return d_ptr->iLastSuccessfulResults; } void SyncLog::addResults(const SyncResults &aResults) { FUNCTION_CALL_TRACE(lcButeoTrace); // To prevent the log growing too much, the maximum number of entries in //the log is defined const int MAXLOGENTRIES = 5; if (d_ptr->iResults.size() >= MAXLOGENTRIES) { // The list is sorted so that the oldest item is in the beginning delete d_ptr->iResults.takeFirst(); } d_ptr->iResults.append(new SyncResults(aResults)); // Sort result entries by sync time. //std::sort(d_ptr->iResults.begin(), d_ptr->iResults.end(), syncResultPointerLessThan); d_ptr->updateLastSuccessfulResults(aResults); } buteo-syncfw-0.11.10/libbuteosyncfw/profile/SyncLog.h000066400000000000000000000067521477124122200225410ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCLOG_H #define SYNCLOG_H #include #include #include "SyncResults.h" class QDomDocument; class QDomElement; namespace Buteo { class SyncLogPrivate; class SyncLogTest; /*! \brief History of completed synchronization sessions and their results. * * Each SyncProfile has its own SyncLog associated to it. Loading and saving of * SyncLog objects is handled by the ProfileManager. SyncLog is composed of * SyncResults objects, one for each completed sync session. */ class SyncLog { public: /*! \brief Constructs an empty log with the given profile name. * * \param aProfileName Name of the profile this log is related to. */ explicit SyncLog(const QString &aProfileName); /*! \brief Constructs a SyncLog from XML. * * \param aRoot Root element of the XML representation of the log. */ explicit SyncLog(const QDomElement &aRoot); /*! \brief Copy constructor. * * \param aSource Copy source. */ SyncLog(const SyncLog &aSource); /*! \brief Destructor. * */ ~SyncLog(); /*! \brief Sets the name of the profile that owns this log. */ void setProfileName(const QString &aProfileName); /*! \brief Gets the name of the profile that owns this log. * * \return Profile name. */ QString profileName() const; /*! \brief Exports the log to XML. * * \param aDoc Parent document for the created XML elements. The created * elements are not inserted to the document by this function, but the * document is still required for creating the elements. * \return Root element of the created XML. */ QDomElement toXml(QDomDocument &aDoc) const; /*! \brief Gets the most recent results in the sync log. * * \return The results. NULL if the log is empty. */ const SyncResults *lastResults() const; /*! \brief Gets all results in the sync log. * * \return List of results. The results are ordered by time so that * the oldest results object is first in the list. */ QList allResults() const; /*! \brief Gets the last successful results in the sync log. * * \return The results. NULL if no successful result have already * been registered. */ const SyncResults *lastSuccessfulResults() const; /*! \brief Adds results to the sync log. * Also makes sure that log size doesn't exceed given size limit * * \param aResults Results to add. */ void addResults(const SyncResults &aResults); private: SyncLog &operator=(const SyncLog &aRhs); SyncLogPrivate *d_ptr; }; } #endif // SYNCLOG_H buteo-syncfw-0.11.10/libbuteosyncfw/profile/SyncProfile.cpp000066400000000000000000000416011477124122200237430ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2014-2015 Jolla Ltd. * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "SyncProfile.h" #include "ProfileEngineDefs.h" #include "LogMacros.h" #include namespace Buteo { // Private implementation class for SyncProfile. class SyncProfilePrivate { public: SyncProfilePrivate(); SyncProfilePrivate(const SyncProfilePrivate &aSource); ~SyncProfilePrivate(); SyncLog *iLog; SyncSchedule iSchedule; struct SyncRetriesInfo { QList iRetryIntervals; quint32 iIntervalIndex; void init() { iIntervalIndex = 0; } void addInterval(quint32 interval) { iRetryIntervals.append(interval); } quint32 retries() { return iRetryIntervals.count(); } qint32 nextInterval() { qint32 next = -1; if (iIntervalIndex < retries()) { next = iRetryIntervals.at(iIntervalIndex); ++iIntervalIndex; } return next; } QList intervals() { return iRetryIntervals; } SyncRetriesInfo &operator=(const SyncRetriesInfo &rhs) { if (this != &rhs) { iIntervalIndex = rhs.iIntervalIndex; iRetryIntervals = rhs.iRetryIntervals; } return *this; } } iSyncRetriesInfo; }; } using namespace Buteo; const quint32 DEFAULT_SOC_AFTER_TIME(5 * 60); SyncProfilePrivate::SyncProfilePrivate() : iLog(0) { iSyncRetriesInfo.init(); } SyncProfilePrivate::SyncProfilePrivate(const SyncProfilePrivate &aSource) : iLog(0), iSchedule(aSource.iSchedule) { if (aSource.iLog != 0) { iLog = new SyncLog(*aSource.iLog); } iSyncRetriesInfo = aSource.iSyncRetriesInfo; } SyncProfilePrivate::~SyncProfilePrivate() { delete iLog; iLog = 0; } SyncProfile::SyncProfile(const QString &aName) : Profile(aName, Profile::TYPE_SYNC), d_ptr(new SyncProfilePrivate()) { } SyncProfile::SyncProfile(const QDomElement &aRoot) : Profile(aRoot), d_ptr(new SyncProfilePrivate()) { QDomElement schedule = aRoot.firstChildElement(TAG_SCHEDULE); if (!schedule.isNull()) { d_ptr->iSchedule = SyncSchedule(schedule); } QDomElement retriesElement = aRoot.firstChildElement(TAG_ERROR_ATTEMPTS); if (!retriesElement.isNull()) { QDomElement timeElement = retriesElement.firstChildElement(TAG_ATTEMPT_DELAY); while (!timeElement.isNull()) { bool ok = false; int parsedTime = timeElement.attribute(ATTR_VALUE, "-1").toUInt(&ok); if (ok && parsedTime > 0) { d_ptr->iSyncRetriesInfo.addInterval(parsedTime); } timeElement = timeElement.nextSiblingElement(TAG_ATTEMPT_DELAY); } } } SyncProfile::SyncProfile(const SyncProfile &aSource) : Profile(aSource), d_ptr(new SyncProfilePrivate(*aSource.d_ptr)) { } SyncProfile::~SyncProfile() { delete d_ptr; d_ptr = 0; } SyncProfile *SyncProfile::clone() const { return new SyncProfile(*this); } QDomElement SyncProfile::toXml(QDomDocument &aDoc, bool aLocalOnly) const { QDomElement root = Profile::toXml(aDoc, aLocalOnly); QDomElement schedule = d_ptr->iSchedule.toXml(aDoc); if (!schedule.isNull()) { root.appendChild(schedule); } if (d_ptr->iSyncRetriesInfo.retries()) { QDomElement retries = aDoc.createElement(TAG_ERROR_ATTEMPTS); for (quint32 i = 0; i < d_ptr->iSyncRetriesInfo.retries(); ++i) { QDomElement retryInterval = aDoc.createElement(TAG_ATTEMPT_DELAY); qint32 nextInt = d_ptr->iSyncRetriesInfo.nextInterval(); if (-1 != nextInt) { retryInterval.setAttribute(ATTR_VALUE, nextInt); retries.appendChild(retryInterval); } } root.appendChild(retries); d_ptr->iSyncRetriesInfo.init(); } return root; } void SyncProfile::setName(const QString &aName) { // sets the name in the super class Profile. Profile::setName(aName); // Here we also must set name for the log associated to this profile, // as that is only set during log construction. Changing SyncProfile name does not // reflect there if (d_ptr->iLog) d_ptr->iLog->setProfileName(aName); } void SyncProfile::setName(const QStringList &aKeys) { // sets the name in the super class Profile. Profile::setName(aKeys); // Here we also must set name for the log associated to this profile, // as that is only set during log construction. Changing SyncProfile name does not // reflect there if (d_ptr->iLog) d_ptr->iLog->setProfileName(Profile::name()); } bool SyncProfile::syncExternallyEnabled() const { return boolKey(KEY_SYNC_EXTERNALLY, false); } bool SyncProfile::rushEnabled() const { return d_ptr->iSchedule.rushEnabled() && d_ptr->iSchedule.scheduleEnabled(); } bool SyncProfile::syncExternallyDuringRush() const { return d_ptr->iSchedule.scheduleEnabled() && d_ptr->iSchedule.rushEnabled() && d_ptr->iSchedule.syncExternallyDuringRush(); } bool SyncProfile::inExternalSyncRushPeriod(QDateTime aDateTime) const { return d_ptr->iSchedule.inExternalSyncRushPeriod(aDateTime); } QDateTime SyncProfile::lastSyncTime() const { QDateTime lastSync; if (d_ptr->iLog != 0 && d_ptr->iLog->lastResults() != 0) { lastSync = d_ptr->iLog->lastResults()->syncTime(); } qCDebug(lcButeoCore) << "lastSync:" << lastSync; return lastSync; } QDateTime SyncProfile::lastSuccessfulSyncTime () const { QDateTime lastSuccessSyncTime; if (d_ptr->iLog) { const SyncResults *success = d_ptr->iLog->lastSuccessfulResults(); if (success) lastSuccessSyncTime = success->syncTime(); } return lastSuccessSyncTime; } QDateTime SyncProfile::nextSyncTime(QDateTime aDateTime) const { QDateTime nextSync; if (syncType() == SYNC_SCHEDULED) { if (aDateTime.isValid()) { nextSync = d_ptr->iSchedule.nextSyncTime(aDateTime); } else { nextSync = d_ptr->iSchedule.nextSyncTime(lastSyncTime()); } } return nextSync; } QDateTime SyncProfile::nextRushSwitchTime(const QDateTime &aFromTime) const { QDateTime nextSwitch; if (syncType() == SYNC_SCHEDULED) { nextSwitch = d_ptr->iSchedule.nextRushSwitchTime(aFromTime); } return nextSwitch; } const SyncResults *SyncProfile::lastResults() const { if (d_ptr->iLog != 0) { return d_ptr->iLog->lastResults(); } else { return 0; } } SyncLog *SyncProfile::log() const { return d_ptr->iLog; } void SyncProfile::setLog(SyncLog *aLog) { delete d_ptr->iLog; d_ptr->iLog = aLog; } void SyncProfile::addResults(const SyncResults &aResults) { if (0 == d_ptr->iLog) { d_ptr->iLog = new SyncLog(name()); } d_ptr->iLog->addResults(aResults); } SyncProfile::SyncType SyncProfile::syncType() const { // Sync schedule is enabled for peak or manual -> it is scheduled type. return !syncExternallyEnabled() && (d_ptr->iSchedule.scheduleEnabled() || d_ptr->iSchedule.rushEnabled()) ? SYNC_SCHEDULED : SYNC_MANUAL; } // TODO: seems effectless since d6d974e (Added functions to enable/disable normal scheduling.) void SyncProfile::setSyncType(SyncType aType) { setBoolKey(KEY_SYNC_SCHEDULED, aType == SYNC_SCHEDULED); } SyncSchedule SyncProfile::syncSchedule() const { return d_ptr->iSchedule; } void SyncProfile::setSyncSchedule(const SyncSchedule &aSchedule) { d_ptr->iSchedule = aSchedule; } QList SyncProfile::internetConnectionTypes() const { QSet types; const QStringList typeStrings = key(KEY_INTERNET_CONNECTION_TYPES).split(','); foreach (QString typeString, typeStrings) { bool ok = false; int typeInt = typeString.toInt(&ok); if (!ok || typeInt < Sync::INTERNET_CONNECTION_UNKNOWN || typeInt > Sync::INTERNET_CONNECTION_LTE) { continue; } types.insert(Sync::InternetConnectionType(typeInt)); } return types.toList(); } void SyncProfile::setInternetConnectionTypes(const QList &aTypes) { QStringList typeStrings; foreach (Sync::InternetConnectionType type, aTypes) { typeStrings.append(QString::number(int(type))); } setKey(KEY_INTERNET_CONNECTION_TYPES, typeStrings.join(',')); } QStringList SyncProfile::storageBackendNames() const { QStringList enabledStorageBackends; QStringList storageNames = subProfileNames(Profile::TYPE_STORAGE); foreach (QString storage, storageNames) { const Profile *p = subProfile(storage, Profile::TYPE_STORAGE); if (p->isEnabled()) { // Get backend name from the storage profile. If the backend name // is not defined, use profile name as the backend name. enabledStorageBackends.append(p->key(KEY_BACKEND, p->name())); } } return enabledStorageBackends; } /* QString SyncProfile::serviceName() const { QStringList serviceNameList = subProfileNames(Profile::TYPE_SERVICE); if (serviceNameList.isEmpty()) return QString(); return serviceNameList.first(); } const Profile *SyncProfile::serviceProfile() const { QList subProfiles = allSubProfiles(); foreach (const Profile *p, subProfiles) { if (p->type() == TYPE_SERVICE) { return p; } } return 0; } Profile *SyncProfile::serviceProfile() { QList subProfiles = allSubProfiles(); foreach (Profile *p, subProfiles) { if (p->type() == TYPE_SERVICE) { return p; } } return 0; } */ const Profile *SyncProfile::clientProfile() const { QList subProfiles = allSubProfiles(); foreach (const Profile *p, subProfiles) { if (p->type() == TYPE_CLIENT) { return p; } } return 0; } Profile *SyncProfile::clientProfile() { QList subProfiles = allSubProfiles(); foreach (Profile *p, subProfiles) { if (p->type() == TYPE_CLIENT) { return p; } } return 0; } const Profile *SyncProfile::serverProfile() const { QList subProfiles = allSubProfiles(); foreach (const Profile *p, subProfiles) { if (p->type() == TYPE_SERVER) { return p; } } return 0; } Profile *SyncProfile::serverProfile() { QList subProfiles = allSubProfiles(); foreach (Profile *p, subProfiles) { if (p->type() == TYPE_SERVER) { return p; } } return 0; } QList SyncProfile::storageProfiles() const { QList storages; QList subProfiles = allSubProfiles(); foreach (const Profile *p, subProfiles) { if (p->type() == TYPE_STORAGE) { storages.append(p); } } return storages; } QList SyncProfile::storageProfilesNonConst() { QList storages; QList subProfiles = allSubProfiles(); foreach (Profile *p, subProfiles) { if (p->type() == TYPE_STORAGE) { storages.append(p); } } return storages; } SyncProfile::DestinationType SyncProfile::destinationType() const { DestinationType type = DESTINATION_TYPE_UNDEFINED; QString typeStr; typeStr = this->key(KEY_DESTINATION_TYPE); if (typeStr == VALUE_ONLINE) { type = DESTINATION_TYPE_ONLINE; } else if (typeStr == VALUE_DEVICE) { type = DESTINATION_TYPE_DEVICE; } else { type = DESTINATION_TYPE_UNDEFINED; } return type; } SyncProfile::SyncDirection SyncProfile::syncDirection() const { SyncDirection dir = SYNC_DIRECTION_UNDEFINED; QString dirStr; const Profile *client = clientProfile(); if (client) { dirStr = client->key(KEY_SYNC_DIRECTION); } if (dirStr == VALUE_TWO_WAY) { dir = SYNC_DIRECTION_TWO_WAY; } else if (dirStr == VALUE_FROM_REMOTE) { dir = SYNC_DIRECTION_FROM_REMOTE; } else if (dirStr == VALUE_TO_REMOTE) { dir = SYNC_DIRECTION_TO_REMOTE; } else { dir = SYNC_DIRECTION_UNDEFINED; } return dir; } bool SyncProfile::isSOCProfile() const { bool aSOCProfile = false; QString enabled = this->key(KEY_SOC); enabled = enabled.trimmed(); if ("true" == enabled) { aSOCProfile = true; } return aSOCProfile; } quint32 SyncProfile::syncOnChangeAfter() const { quint32 syncOnChangeAfterTime = DEFAULT_SOC_AFTER_TIME; QString time = this->key(KEY_SOC_AFTER); if (!time.isEmpty()) { bool ok = false; syncOnChangeAfterTime = time.toUInt(&ok); if (false == ok) { syncOnChangeAfterTime = DEFAULT_SOC_AFTER_TIME; } } qCDebug(lcButeoCore) << "Sync on change after time from profile :" << syncOnChangeAfterTime; return syncOnChangeAfterTime; } void SyncProfile::setSyncDirection(SyncDirection aDirection) { QString dirStr; switch (aDirection) { case SYNC_DIRECTION_TWO_WAY: dirStr = VALUE_TWO_WAY; break; case SYNC_DIRECTION_FROM_REMOTE: dirStr = VALUE_FROM_REMOTE; break; case SYNC_DIRECTION_TO_REMOTE: dirStr = VALUE_TO_REMOTE; break; case SYNC_DIRECTION_UNDEFINED: default: // Value string is left as null. Key gets deleted when the value is set. break; } Profile *client = clientProfile(); if (client) { client->setKey(KEY_SYNC_DIRECTION, dirStr); } else { qCWarning(lcButeoCore) << "Profile" << name() << "has no client profile"; qCWarning(lcButeoCore) << "Failed to set sync direction"; } } SyncProfile::ConflictResolutionPolicy SyncProfile::conflictResolutionPolicy() const { ConflictResolutionPolicy policy = CR_POLICY_UNDEFINED; QString policyStr; const Profile *client = clientProfile(); if (client) { policyStr = client->key(KEY_CONFLICT_RESOLUTION_POLICY); } if (policyStr == VALUE_PREFER_REMOTE) { policy = CR_POLICY_PREFER_REMOTE_CHANGES; } else if (policyStr == VALUE_PREFER_LOCAL) { policy = CR_POLICY_PREFER_LOCAL_CHANGES; } else { policy = CR_POLICY_UNDEFINED; } return policy; } void SyncProfile::setConflictResolutionPolicy(ConflictResolutionPolicy aPolicy) { QString policyStr; switch (aPolicy) { case CR_POLICY_PREFER_REMOTE_CHANGES: policyStr = VALUE_PREFER_REMOTE; break; case CR_POLICY_PREFER_LOCAL_CHANGES: policyStr = VALUE_PREFER_LOCAL; break; case CR_POLICY_UNDEFINED: default: // Value string is left as null. Key gets deleted when the value is set. break; } Profile *client = clientProfile(); if (client) { client->setKey(KEY_CONFLICT_RESOLUTION_POLICY, policyStr); } else { qCWarning(lcButeoCore) << "Profile" << name() << "has no client profile"; qCWarning(lcButeoCore) << "Failed to set conflict resolution policy"; } } bool SyncProfile::hasRetries() const { return d_ptr->iSyncRetriesInfo.retries() ? true : false; } QList SyncProfile::retryIntervals() const { return d_ptr->iSyncRetriesInfo.intervals(); } SyncProfile::CurrentSyncStatus SyncProfile::currentSyncStatus() const { //Fetch the last sync result const SyncResults *syncResult = lastResults(); SyncProfile::CurrentSyncStatus syncStatus = SyncProfile::SYNC_NEVER_HAPPENED; if (syncResult) { if ((syncResult->majorCode() == SyncResults::SYNC_RESULT_SUCCESS) && (syncResult->minorCode() == SyncResults::NO_ERROR)) syncStatus = SyncProfile::SYNC_SUCCESS; else if (syncResult->majorCode() == SyncResults::SYNC_RESULT_FAILED) syncStatus = SyncProfile::SYNC_FAILED; else if (syncResult->majorCode() == SyncResults::SYNC_RESULT_CANCELLED) syncStatus = SyncProfile::SYNC_CANCLLED; } return syncStatus; } buteo-syncfw-0.11.10/libbuteosyncfw/profile/SyncProfile.h000066400000000000000000000301651477124122200234130ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2014-2019 Jolla Ltd. * Copyright (C) 2020 Open Mobile Platform LLC. * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCPROFILE_H #define SYNCPROFILE_H #include "Profile.h" #include "SyncLog.h" #include "SyncSchedule.h" #include "SyncCommonDefs.h" namespace Buteo { class SyncProfilePrivate; /*! \brief A top level synchronization profile. * * SyncProfile is derived from Profile. It represents a top level * synchronization profile, which contains all settings needed for a * synchronization session. A SyncProfile typically has sub-profiles for the * used service, client/server plug-in and storage plug-ins. SyncProfile * extends the Profile inteface with functions for accessing information about * synchronization schedule and history of finished synchronization sessions * with this profile. */ class SyncProfile : public Profile { public: //! Synchronization types. enum SyncType { //! Synchronization is started manually. SYNC_MANUAL, //! Synchronization is triggered automatically based on a defined //! schedule. SYNC_SCHEDULED }; //! Sync destination type. enum DestinationType { //! Destination is a device (N95, Harmattan, OviSuite etc.) DESTINATION_TYPE_DEVICE, //! Destination is an online service. DESTINATION_TYPE_ONLINE, //! Destination type is not defined. DESTINATION_TYPE_UNDEFINED }; //! Sync direction for device-to-device syncs. enum SyncDirection { //! Two way sync. SYNC_DIRECTION_TWO_WAY, //! Data is copied from remote device only. SYNC_DIRECTION_FROM_REMOTE, //! Data is copied to remote device only. SYNC_DIRECTION_TO_REMOTE, //! Sync direction is not defined. SYNC_DIRECTION_UNDEFINED }; //! Conflict resolution policy for device-to-device syncs. enum ConflictResolutionPolicy { //! Prefer local data in conflict situation. CR_POLICY_PREFER_LOCAL_CHANGES, //! Prefer remote data in conflict situation. CR_POLICY_PREFER_REMOTE_CHANGES, //! Conflict resolution policy is undefined. CR_POLICY_UNDEFINED }; //! Current status enum enum CurrentSyncStatus { //! NOT_SYNCED - no sync has been done for the profile yet SYNC_NEVER_HAPPENED, //! SYNC_SUCCESS - the last sync has been successful SYNC_SUCCESS, //! SYNC_FAILED - the last sync has failed SYNC_FAILED, //! SYNC_CANCELLED - the last sync has been cancelled SYNC_CANCLLED }; /*! \brief Constructs an empty SyncProfile with the given name. * * \param aName Name of the profile to create. */ explicit SyncProfile(const QString &aName); /*! \brief Constructs a SyncProfile from the given XML. * * \param aRoot Root element of the XML node tree. */ explicit SyncProfile(const QDomElement &aRoot); /*! \brief Copy constructor. * * \param aSource Copy source. */ SyncProfile(const SyncProfile &aSource); //! \brief Destructor. ~SyncProfile(); /*! \brief Creates a clone of the sync profile. * * \return The clone. */ virtual SyncProfile *clone() const; /*! \brief Sets the name for the profile and associated log. */ virtual void setName(const QString &aName); /*! \brief Sets the name for the profile and associated log. */ virtual void setName(const QStringList &aKeys); //! \see Profile::toXml virtual QDomElement toXml(QDomDocument &aDoc, bool aLocalOnly = true) const; /*! \brief Checks if schedule is controlled by a external process (e.g always-up-to-date). * * \return True if schedule is controlled by a external process. External process will control the sync, * buteo schedule is disabled in this case. */ virtual bool syncExternallyEnabled() const; /*! \brief Checks if rush/off-rush schedule is enabled. * * \return True if rush/off-rush schedule is enabled. False, if rush/off-rush scheduling is off. */ virtual bool rushEnabled() const; /*! \brief Checks if external rush schedule is to be obeyed. * * \return True if rush hour schedule is to be used by a external process, The external process will control the sync, buteo will just call * the corresponding plugins when a switch from rush to offRush or vice-versa is necessary, corresponding plugins should be prepared to do any needed * changes. * False, if rush hour scheduling is controlled by this process or if rush hour scheduling is off (i.e. manual mode). */ virtual bool syncExternallyDuringRush() const; /*! \brief Checks if a given time is inside rush hour and if the sync is controlled by a external process. * * \param aDateTime DateTime to check, current DateTime used by default. */ virtual bool inExternalSyncRushPeriod(QDateTime aDateTime = QDateTime::currentDateTime()) const; /*! \brief Gets the time of last completed sync session with this profile. * * \return Last sync time. Null object if this could not be determined. */ QDateTime lastSyncTime() const; /*! \brief Gets the time of the last successful sync session for * this profile * \return Sync time of the last successful session. Null if this * could not be determined */ QDateTime lastSuccessfulSyncTime() const; /*! \brief Gets the next scheduled sync time. * * \return Next sync time. Null object if the sync type is manual or the * time could not be determined for some other reason. */ virtual QDateTime nextSyncTime(QDateTime aDateTime = QDateTime::currentDateTime()) const; /*! \brief Gets next time to switch rush/off-rush schedule intervals. * * \param aFromTime From time to calculate next switch, usually current time. * \return Next time to switch rush/off-rush schedule intervals. Null object if schedule is not defined for rush/off-rush * or if the rush and off-rush intervals are the same. */ QDateTime nextRushSwitchTime(const QDateTime &aFromTime) const; /*! \brief Gets the results of the last sync from the sync log. * * \return The results. NULL if not available. */ const SyncResults *lastResults() const; /*! \brief Gets the synchronization log associated with this profile. * * \return The sync log. NULL if no log is set. */ SyncLog *log() const; /*! \brief Sets the synchronization log for this profile. * * The ownership of the given log object is transferred to this object. * If a log is already set, the old log object is deleted first. * \param aLog The log. */ void setLog(SyncLog *aLog); /*! \brief Adds synchronization results to the log. * * If a log does not exist yet, an empty log is created first. * \param aResults Results to add. */ void addResults(const SyncResults &aResults); /*! \brief Gets the sync type of this profile. * * \return The sync type. */ SyncType syncType() const; /*! \brief Sets the sync type of this profile (manual/scheduled). * * \param aType The new sync type. */ void setSyncType(SyncType aType); /*! \brief Gets the names of storage backends used by this profile. * * \return List of storage backend names. */ QStringList storageBackendNames() const; /*! \brief Gets sync schedule settings. * * \return Sync schedule. */ SyncSchedule syncSchedule() const; /*! \brief Sets sync schedule settings. * * \param aSchedule New schedule. */ void setSyncSchedule(const SyncSchedule &aSchedule); /*! \brief Gets allowed connection types. * * \return List of allowed connection types. */ QList internetConnectionTypes() const; /*! \brief Sets the internet connection types on which this profile can be synced. * * If empty, the default settings are used. * * \param aTypes The allowed internet connection types. */ void setInternetConnectionTypes(const QList &aTypes); /*! \brief Get the first service sub-profile. * * \return Service profile. NULL if not found. */ //const Profile *serviceProfile() const; /*! \brief Get the first service sub-profile. * * \return Service profile. NULL if not found. */ //Profile *serviceProfile(); /*! \brief Get the first client sub-profile. * * \return Client profile. NULL if not found. */ const Profile *clientProfile() const; /*! \brief Get the first client sub-profile. * * \return Client profile. NULL if not found. */ Profile *clientProfile(); /*! \brief Get the first server sub-profile. * * \return Server profile. NULL if not found. */ const Profile *serverProfile() const; /*! \brief Get the first server sub-profile. * * \return Server profile. NULL if not found. */ Profile *serverProfile(); /*! \brief Get the storage sub-profiles. * * \return Storage profiles. */ QList storageProfiles() const; /*! \brief Get the storage sub-profiles. * * \return Storage profiles. */ QList storageProfilesNonConst(); /*! \brief Gets sync destination type (device or online). * * \return Destination type. */ DestinationType destinationType() const; /*! \brief Gets sync direction (two way, to destination, from destination). * * \return Sync direction. */ SyncDirection syncDirection() const; /*! \brief Sets sync direction. * * \return New sync direction. */ void setSyncDirection(SyncDirection aDirection); /*! \brief Gets conflict resolution policy. * * \return Conflict resolution policy. */ ConflictResolutionPolicy conflictResolutionPolicy() const; /*! \brief Set conflict resolution policy. * * \return Conflict resolution policy. */ void setConflictResolutionPolicy(ConflictResolutionPolicy aPolicy); /*! \brief Get the service name of profile. * * \return Service name associated with profile. */ QString serviceName() const; /*! \brief If a profiles is interested in SOC, this * gets the the SOC after time from that profile. * The time should be in seconds and a value of 0 means * sync immediately afer change * * @return SOC after time or -1 if none is specified */ quint32 syncOnChangeAfter() const; /*! \brief checks if a profile has SOC enabled * * @return true if SOC enabled for this profile, false otherwise */ bool isSOCProfile() const; bool hasRetries() const; QList retryIntervals() const; /*! \brief Gives the current status of the sync as an enum value * If the current status of ongoing syncs is required, check the * d-bus API "runningSyncs" which returns the list of currently running * sync sessions. The current sync sessions cannot be part of a profiel, * * @return CurrentSyncStatus */ CurrentSyncStatus currentSyncStatus() const; private: SyncProfile &operator=(const SyncProfile &aRhs); SyncProfilePrivate *d_ptr; }; } #endif // SYNCPROFILE_H buteo-syncfw-0.11.10/libbuteosyncfw/profile/SyncResults.cpp000066400000000000000000000131701477124122200240040ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "SyncResults.h" #include "LogMacros.h" #include #include "ProfileEngineDefs.h" namespace Buteo { //! Private implementation class for SyncResults. class SyncResultsPrivate { public: //! Default Constructors SyncResultsPrivate(); //! Copy Constructor SyncResultsPrivate(const SyncResultsPrivate &aSource); //! List of target results. QList iTargetResults; //! Sync time. QDateTime iTime; SyncResults::MajorCode iMajorCode; SyncResults::MinorCode iMinorCode; //! Sync target id QString iTargetId; //! Are results for Scheduled Sync bool iScheduled; }; SyncResultsPrivate::SyncResultsPrivate() : iTime(QDateTime::currentDateTime()), iMajorCode(SyncResults::SYNC_RESULT_SUCCESS), iMinorCode(SyncResults::NO_ERROR), iScheduled(false) { } SyncResultsPrivate::SyncResultsPrivate(const SyncResultsPrivate &aSource) : iTargetResults(aSource.iTargetResults), iTime(aSource.iTime), iMajorCode(aSource.iMajorCode), iMinorCode(aSource.iMinorCode), iTargetId(aSource.iTargetId), iScheduled(aSource.iScheduled) { } } using namespace Buteo; SyncResults::SyncResults() : d_ptr(new SyncResultsPrivate()) { } SyncResults::SyncResults(const SyncResults &aSource) : d_ptr(new SyncResultsPrivate(*aSource.d_ptr)) { } SyncResults::SyncResults(QDateTime aTime, SyncResults::MajorCode aMajorCode, SyncResults::MinorCode aMinorCode) : d_ptr(new SyncResultsPrivate()) { d_ptr->iTime = aTime; d_ptr->iMajorCode = aMajorCode; d_ptr->iMinorCode = aMinorCode; } SyncResults::SyncResults(const QDomElement &aRoot) : d_ptr(new SyncResultsPrivate()) { d_ptr->iTime = QDateTime::fromString(aRoot.attribute(ATTR_TIME), Qt::ISODate); d_ptr->iMajorCode = static_cast(aRoot.attribute(ATTR_MAJOR_CODE).toInt()); d_ptr->iMinorCode = static_cast(aRoot.attribute(ATTR_MINOR_CODE).toInt()); d_ptr->iScheduled = (aRoot.attribute(KEY_SYNC_SCHEDULED) == BOOLEAN_TRUE); QDomElement target = aRoot.firstChildElement(TAG_TARGET_RESULTS); for (; !target.isNull(); target = target.nextSiblingElement(TAG_TARGET_RESULTS)) { d_ptr->iTargetResults.append(TargetResults(target)); } } SyncResults::~SyncResults() { } SyncResults &SyncResults::operator=(const SyncResults &aRhs) { if (&aRhs != this) { d_ptr = aRhs.d_ptr; } return *this; } QDomElement SyncResults::toXml(QDomDocument &aDoc) const { QDomElement root = aDoc.createElement(TAG_SYNC_RESULTS); root.setAttribute(ATTR_TIME, d_ptr->iTime.toString(Qt::ISODate)); root.setAttribute(ATTR_MAJOR_CODE, QString::number(d_ptr->iMajorCode)); root.setAttribute(ATTR_MINOR_CODE, QString::number(d_ptr->iMinorCode)); root.setAttribute(KEY_SYNC_SCHEDULED, d_ptr->iScheduled ? BOOLEAN_TRUE : BOOLEAN_FALSE); foreach (TargetResults tr, d_ptr->iTargetResults) { root.appendChild(tr.toXml(aDoc)); } return root; } QString SyncResults::toString() const { QDomDocument doc; QDomProcessingInstruction xmlHeading = doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""); doc.appendChild(xmlHeading); QDomElement root = toXml(doc); doc.appendChild(root); return doc.toString(PROFILE_INDENT); } QList SyncResults::targetResults() const { return d_ptr->iTargetResults; } QVariantList SyncResults::variantTargetResults() const { QVariantList out; for (const TargetResults &result : d_ptr->iTargetResults) { out << QVariant::fromValue(result); } return out; } void SyncResults::addTargetResults(const TargetResults &aResults) { d_ptr->iTargetResults.append(aResults); } QDateTime SyncResults::syncTime() const { return d_ptr->iTime; } SyncResults::MajorCode SyncResults::majorCode() const { return d_ptr->iMajorCode; } void SyncResults::setMajorCode(SyncResults::MajorCode aMajorCode) { FUNCTION_CALL_TRACE(lcButeoTrace); d_ptr->iMajorCode = aMajorCode; } SyncResults::MinorCode SyncResults::minorCode() const { return d_ptr->iMinorCode; } void SyncResults::setMinorCode(SyncResults::MinorCode aMinorCode) { FUNCTION_CALL_TRACE(lcButeoTrace); d_ptr->iMinorCode = aMinorCode; } void SyncResults::setTargetId(const QString &aTargetId) { d_ptr->iTargetId = aTargetId; } QString SyncResults::getTargetId() const { return d_ptr->iTargetId; } bool SyncResults::operator<(const SyncResults &aOther) const { return (d_ptr->iTime < aOther.d_ptr->iTime); } void SyncResults::setScheduled(bool aScheduled) { d_ptr->iScheduled = aScheduled; } bool SyncResults::isScheduled() const { return d_ptr->iScheduled; } buteo-syncfw-0.11.10/libbuteosyncfw/profile/SyncResults.h000066400000000000000000000157151477124122200234600ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCRESULTS_H_2 #define SYNCRESULTS_H_2 #include #include #include #include #include #include "TargetResults.h" class QDomDocument; class QDomElement; namespace Buteo { struct DatabaseResults { int iLocalItemsAdded; /*! targetResults() const; /*! \brief Adds target results to this object. * * \param aResults The target results to add. */ void addTargetResults(const TargetResults &aResults); /*! \brief Gets the sync time. * * \return Sync time. */ QDateTime syncTime() const; /*! \brief Gets the result code. * * \return major code. */ MajorCode majorCode() const; /*! \brief Sets the result code. * * \param aMajorCode The result code. */ void setMajorCode(MajorCode aMajorCode); /*! \brief Gets the failed reason. * * \return failed Reason. */ MinorCode minorCode() const; /*! \brief Sets the failed Reason. * * \param aMinorCode - minor code or the reason */ void setMinorCode(MinorCode aMinorCode); /*! \brief Sets the remote target Id. * * \param aTargetId The remote device Id. */ void setTargetId(const QString &aTargetId); /*! \brief Gets the remote target Id. */ QString getTargetId() const; /*! \brief Compares two results objects by sync time. * * The object with earlier sync time is smaller. * \param aOther Point of comparison. */ bool operator<(const SyncResults &aOther) const; /*! \brief Sets if the results are from a scheduled sync. * * \param aScheduled True if this is a scheduled sync. */ void setScheduled(bool aScheduled); /*! \brief Checks if the results are from a scheduled sync. * * \return True if scheduled. */ bool isScheduled() const; private: QVariantList variantTargetResults() const; QSharedPointer d_ptr; #ifdef SYNCFW_UNIT_TESTS friend class ClientThreadTest; #endif }; } Q_DECLARE_METATYPE(Buteo::SyncResults) #endif // SYNCRESULTS_H_2 buteo-syncfw-0.11.10/libbuteosyncfw/profile/SyncSchedule.cpp000066400000000000000000000577371477124122200241200ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2014-2019 Jolla Ltd. * Copyright (C) 2020 Open Mobile Platform LLC. * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "SyncSchedule.h" #include "SyncSchedule_p.h" #include "ProfileEngineDefs.h" #include "SyncCommonDefs.h" #include "LogMacros.h" #include #include #include using namespace Buteo; static const QString DAY_SEPARATOR = ","; SyncSchedulePrivate::SyncSchedulePrivate() : iDays(SyncSchedule::NoDays) , iInterval(0) , iEnabled(false) , iRushDays(SyncSchedule::NoDays) , iRushInterval(0) , iRushEnabled(false) , iExternalRushEnabled(false) { } SyncSchedulePrivate::SyncSchedulePrivate(const SyncSchedulePrivate &aSource) : iDays(aSource.iDays) , iTime(aSource.iTime) , iScheduleConfiguredTime(aSource.iScheduleConfiguredTime) , iInterval(aSource.iInterval) , iEnabled(aSource.iEnabled) , iRushDays(aSource.iRushDays) , iRushBegin(aSource.iRushBegin) , iRushEnd(aSource.iRushEnd) , iRushInterval(aSource.iRushInterval) , iRushEnabled(aSource.iRushEnabled) , iExternalRushEnabled(aSource.iExternalRushEnabled) { } SyncSchedule::SyncSchedule() : d_ptr(new SyncSchedulePrivate()) { } SyncSchedule::SyncSchedule(const SyncSchedule &aSource) : d_ptr(new SyncSchedulePrivate(*aSource.d_ptr)) { } SyncSchedule::SyncSchedule(const QDomElement &aRoot) : d_ptr(new SyncSchedulePrivate()) { d_ptr->iTime = QTime::fromString(aRoot.attribute(ATTR_TIME), Qt::ISODate); d_ptr->iInterval = aRoot.attribute(ATTR_INTERVAL).toUInt(); d_ptr->iEnabled = (aRoot.attribute(ATTR_ENABLED) == BOOLEAN_TRUE); d_ptr->iDays = d_ptr->parseDays(aRoot.attribute(ATTR_DAYS)); d_ptr->iScheduleConfiguredTime = QDateTime::fromString(aRoot.attribute(ATTR_SYNC_CONFIGURE), Qt::ISODate); QDomElement rush = aRoot.firstChildElement(TAG_RUSH); if (!rush.isNull()) { d_ptr->iRushEnabled = (rush.attribute(ATTR_ENABLED) == BOOLEAN_TRUE); d_ptr->iExternalRushEnabled = (rush.attribute(ATTR_EXTERNAL_SYNC) == BOOLEAN_TRUE); d_ptr->iRushInterval = rush.attribute(ATTR_INTERVAL).toUInt(); d_ptr->iRushBegin = QTime::fromString(rush.attribute(ATTR_BEGIN), Qt::ISODate); d_ptr->iRushEnd = QTime::fromString(rush.attribute(ATTR_END), Qt::ISODate); d_ptr->iRushDays = d_ptr->parseDays(rush.attribute(ATTR_DAYS)); } else { d_ptr->iRushEnabled = false; d_ptr->iExternalRushEnabled = false; d_ptr->iRushInterval = 0; } } SyncSchedule::~SyncSchedule() { delete d_ptr; d_ptr = 0; } SyncSchedule &SyncSchedule::operator=(const SyncSchedule &aRhs) { if (&aRhs != this) { delete d_ptr; d_ptr = new SyncSchedulePrivate(*aRhs.d_ptr); } return *this; } bool SyncSchedule::operator==(const SyncSchedule &aRhs) const { if (&aRhs == this) return true; if (d_ptr->iRushDays != aRhs.d_ptr->iRushDays) return false; else if (d_ptr->iRushBegin != aRhs.d_ptr->iRushBegin) return false; else if (d_ptr->iRushEnd != aRhs.d_ptr->iRushEnd) return false; else if (d_ptr->iRushInterval != aRhs.d_ptr->iRushInterval) return false; else if (d_ptr->iInterval != aRhs.d_ptr->iInterval) return false; else if (d_ptr->iEnabled != aRhs.d_ptr->iEnabled) return false; else if (d_ptr->iRushEnabled != aRhs.d_ptr->iRushEnabled) return false; else if (d_ptr->iExternalRushEnabled != aRhs.d_ptr->iExternalRushEnabled) return false; return true; } QDomElement SyncSchedule::toXml(QDomDocument &aDoc) const { QDomElement root = aDoc.createElement(TAG_SCHEDULE); root.setAttribute(ATTR_ENABLED, d_ptr->iEnabled ? BOOLEAN_TRUE : BOOLEAN_FALSE); root.setAttribute(ATTR_TIME, d_ptr->iTime.toString(Qt::ISODate)); root.setAttribute(ATTR_INTERVAL, QString::number(d_ptr->iInterval)); root.setAttribute(ATTR_DAYS, d_ptr->createDays(d_ptr->iDays)); root.setAttribute(ATTR_SYNC_CONFIGURE, d_ptr->iScheduleConfiguredTime.toString(Qt::ISODate)); QDomElement rush = aDoc.createElement(TAG_RUSH); rush.setAttribute(ATTR_ENABLED, d_ptr->iRushEnabled ? BOOLEAN_TRUE : BOOLEAN_FALSE); rush.setAttribute(ATTR_EXTERNAL_SYNC, d_ptr->iExternalRushEnabled ? BOOLEAN_TRUE : BOOLEAN_FALSE); rush.setAttribute(ATTR_INTERVAL, QString::number(d_ptr->iRushInterval)); rush.setAttribute(ATTR_BEGIN, d_ptr->iRushBegin.toString(Qt::ISODate)); rush.setAttribute(ATTR_END, d_ptr->iRushEnd.toString(Qt::ISODate)); rush.setAttribute(ATTR_DAYS, d_ptr->createDays(d_ptr->iRushDays)); root.appendChild(rush); return root; } QString SyncSchedule::toString() const { QDomDocument doc; QDomProcessingInstruction xmlHeading = doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""); doc.appendChild(xmlHeading); QDomElement root = toXml(doc); doc.appendChild(root); return doc.toString(PROFILE_INDENT); } SyncSchedule::Days SyncSchedule::days() const { return d_ptr->iDays; } void SyncSchedule::setDays(SyncSchedule::Days aDays) { d_ptr->iDays = aDays; } void SyncSchedule::setScheduleConfiguredTime(const QDateTime &aDateTime) { d_ptr->iScheduleConfiguredTime = aDateTime; } QDateTime SyncSchedule::scheduleConfiguredTime() { return d_ptr->iScheduleConfiguredTime; } QTime SyncSchedule::time() const { return d_ptr->iTime; } void SyncSchedule::setTime(const QTime &aTime) { d_ptr->iTime = aTime; } unsigned SyncSchedule::interval() const { return d_ptr->iInterval; } void SyncSchedule::setInterval(unsigned aInterval) { d_ptr->iInterval = aInterval; } bool SyncSchedule::scheduleEnabled() const { return d_ptr->iEnabled; } void SyncSchedule::setScheduleEnabled(bool aEnabled) { d_ptr->iEnabled = aEnabled; } bool SyncSchedule::rushEnabled() const { return d_ptr->iRushEnabled; } void SyncSchedule::setRushEnabled(bool aEnabled) { d_ptr->iRushEnabled = aEnabled; } bool SyncSchedule::syncExternallyDuringRush() const { return d_ptr->iExternalRushEnabled; } void SyncSchedule::setSyncExternallyDuringRush(bool aEnabled) { d_ptr->iExternalRushEnabled = aEnabled; } SyncSchedule::Days SyncSchedule::rushDays() const { return d_ptr->iRushDays; } void SyncSchedule::setRushDays(SyncSchedule::Days aDays) { d_ptr->iRushDays = aDays; } QTime SyncSchedule::rushBegin() const { return d_ptr->iRushBegin; } QTime SyncSchedule::rushEnd() const { return d_ptr->iRushEnd; } void SyncSchedule::setRushTime(const QTime &aBegin, const QTime &aEnd) { d_ptr->iRushBegin = aBegin; d_ptr->iRushEnd = aEnd; } unsigned SyncSchedule::rushInterval() const { return d_ptr->iRushInterval; } void SyncSchedule::setRushInterval(unsigned aInterval) { d_ptr->iRushInterval = aInterval; } bool SyncSchedule::inExternalSyncRushPeriod(const QDateTime &aDateTime) const { if (d_ptr->iEnabled && d_ptr->iRushEnabled && d_ptr->iExternalRushEnabled) { if (d_ptr->isRush(aDateTime)) { return true; } } return false; } QDateTime SyncSchedule::nextSyncTime(const QDateTime &aPrevSync) const { FUNCTION_CALL_TRACE(lcButeoTrace); QDateTime nextSync; QDateTime scheduleConfiguredTime = d_ptr->iScheduleConfiguredTime; QDateTime now = QDateTime::currentDateTime(); qCDebug(lcButeoCore) << "aPrevSync" << aPrevSync.toString() << "Last Configured Time " << scheduleConfiguredTime.toString() << "CurrentDateTime" << now; if (d_ptr->iTime.isValid() && d_ptr->iDays != SyncSchedule::NoDays) { // The sync time is defined explicitly (for ex. every Mon, Wed, at // 5:30PM). So choose the next applicable day from now. qCDebug(lcButeoCore) << "Explicit sync time defined."; nextSync.setTime(d_ptr->iTime); nextSync.setDate(now.date()); if (now.time() > d_ptr->iTime) { nextSync = nextSync.addDays(1); } d_ptr->adjustDate(nextSync, d_ptr->iDays); } else if (d_ptr->iInterval > 0 && d_ptr->iEnabled) { // Sync time is defined in terms of interval (for ex. every 15 minutes) qCDebug(lcButeoCore) << "Sync interval defined as" << d_ptr->iInterval; // Last sync time is not available/valid (Could happen if the device // is shut down for an extended period before the first sync can be // performed). Hence use the time the // sync was last scheduled as the reference // Figure out the number of intervals passed QDateTime reference; reference = (aPrevSync.isValid()) ? aPrevSync : scheduleConfiguredTime; if (reference > now) { // If the clock was rolled back... qCDebug(lcButeoCore) << "Setting reference to now"; reference = now; } else if (!reference.isValid()) { //It means configuring first time account. Need to sync now only. qCDebug(lcButeoCore) << "Reference is not valid returning current date time"; return QDateTime::currentDateTime(); } if (d_ptr->iInterval == Sync::SYNC_INTERVAL_MONTHLY) { nextSync.setDate(reference.date().addMonths(1)); nextSync.setTime(d_ptr->iTime); if (QDateTime::currentDateTime().secsTo(nextSync) < 0) { qCDebug(lcButeoCore) << "Named interval is in the past, so sync now"; // The next sync time has passed, so schedule it to happen shortly. return QDateTime::currentDateTime(); } } else if (d_ptr->iInterval == Sync::SYNC_INTERVAL_FIRST_DAY_OF_MONTH || d_ptr->iInterval == Sync::SYNC_INTERVAL_LAST_DAY_OF_MONTH) { QDate date = reference.date(); date.setDate(date.year(), date.month(), d_ptr->iInterval == Sync::SYNC_INTERVAL_FIRST_DAY_OF_MONTH ? 1 : date.daysInMonth()); nextSync.setDate(date); nextSync.setTime(d_ptr->iTime); if (now.secsTo(nextSync) < 0) { qCDebug(lcButeoCore) << "Named interval to" << nextSync << "is in the past, so use date for next month instead"; // The next sync time has passed, so schedule it in the month following the // current date. while (nextSync < now) { nextSync = nextSync.addMonths(1); } } } else { const int secs = reference.secsTo(now) + 1; const int numberOfIntervals = (secs / (d_ptr->iInterval * 60)) + (((secs % (d_ptr->iInterval * 60)) != 0) ? 1 : 0); qCDebug(lcButeoCore) << "numberOfInterval:" << numberOfIntervals << "interval time" << d_ptr->iInterval; nextSync = reference.addSecs(numberOfIntervals * d_ptr->iInterval * 60); } } qCDebug(lcButeoCore) << "next non rush hour sync is at:: " << nextSync; // Rush is controlled by a external process(e.g always-up-to-date), buteo controls the switch between rush and offRush if (d_ptr->iRushEnabled && d_ptr->iExternalRushEnabled) { qCDebug(lcButeoCore) << "Rush Interval is controlled by a external process."; // Set next sync to rush end const bool isRush(d_ptr->isRush(now)); QDateTime nextSyncRush; nextSyncRush.setTime(isRush ? d_ptr->iRushEnd : d_ptr->iRushBegin); nextSyncRush.setDate(now.date()); if (now.time() > d_ptr->iRushEnd) { nextSyncRush = nextSyncRush.addDays(1); } d_ptr->adjustDate(nextSyncRush, d_ptr->iRushDays); qCDebug(lcButeoCore) << "Rush controlled by external process, next scheduled sync at rush " << (isRush ? "end" : "begin") << nextSyncRush.toString(); // Use next sync time calculated with rush settings if necessary. if (nextSyncRush.isValid()) { // check to see if we should use it, or instead use the next non-rush sync time. if (nextSync.isValid() && nextSync > now && nextSync < nextSyncRush && (!d_ptr->isRush(nextSync))) { // the next non-rush sync time occurs after now // but before the next rush sync time, and either it // doesn't fall within the rush period itself or the // next rush sync time is in the next rush period. // we should use the non-rush schedule. qCDebug(lcButeoCore) << "Using non-rush time as the next sync time"; } else { nextSync = nextSyncRush; } } } else if (d_ptr->iRushEnabled && d_ptr->iRushInterval > 0 && !d_ptr->iExternalRushEnabled) { qCDebug(lcButeoCore) << "Calculating next sync time with rush settings. Rush Interval is " << d_ptr->iRushInterval; // Calculate next sync time with rush settings. QDateTime nextSyncRush; bool nextSyncRushInNextRushPeriod = false; if (d_ptr->isRush(now)) { qCDebug(lcButeoCore) << "Current time is in rush"; // We are in rush hour if (aPrevSync.isValid()) { qCDebug(lcButeoCore) << "PrevSync is valid and isRush true.. "; nextSyncRush = aPrevSync.addSecs(d_ptr->iRushInterval * 60); if ((nextSyncRush < now) || (aPrevSync > now)) { // Use current time if the previous sync time is too old, or // the clock has been rolled back nextSyncRush = now.addSecs(d_ptr->iRushInterval * 60); qCDebug(lcButeoCore) << "nextsyncRush based on aPrevSync" << nextSyncRush; } } else { nextSyncRush = now.addSecs(d_ptr->iRushInterval * 60); } if (!d_ptr->isRush(nextSyncRush)) { // If the calculated rush time does not lie in the rush // interval, choose the next available rush time as the begin // time for the rush interval qCDebug(lcButeoCore) << "isRush False"; nextSyncRushInNextRushPeriod = true; nextSyncRush.setTime(d_ptr->iRushBegin); if (nextSyncRush < now) { nextSyncRush = nextSyncRush.addDays(1); } d_ptr->adjustDate(nextSyncRush, d_ptr->iRushDays); } } else { qCDebug(lcButeoCore) << "Current Time is Not Rush"; nextSyncRush.setTime(d_ptr->iRushBegin); nextSyncRush.setDate(now.date()); if (now.time() > d_ptr->iRushBegin) { nextSyncRush = nextSyncRush.addDays(1); } d_ptr->adjustDate(nextSyncRush, d_ptr->iRushDays); } qCDebug(lcButeoCore) << "nextSyncRush" << nextSyncRush.toString(); // Use next sync time calculated with rush settings if necessary. if (nextSyncRush.isValid()) { // check to see if we should use it, or instead use the next non-rush sync time. if (nextSync.isValid() && nextSync > now && nextSync < nextSyncRush && (!d_ptr->isRush(nextSync) || nextSyncRushInNextRushPeriod)) { // the next non-rush sync time occurs after now // but before the next rush sync time, and either it // doesn't fall within the rush period itself or the // next rush sync time is in the next rush period. // we should use the non-rush schedule. qCDebug(lcButeoCore) << "Using non-rush time as the next sync time"; } else { // we should use the rush schedule. qCDebug(lcButeoCore) << "Using rush time as the next sync time"; nextSync = nextSyncRush; } } } //For safer side checking nextSyncTime should not be behind currentDateTime. if (QDateTime::currentDateTime().secsTo(nextSync) < 0) { //If it is the case making it to currentTime. qCWarning(lcButeoCore) << "Something went wrong in nextSyncTime calculation resetting to current time"; nextSync = QDateTime::currentDateTime(); } qCDebug(lcButeoCore) << "nextSync" << nextSync.toString(); return nextSync; } QDateTime SyncSchedule::nextRushSwitchTime(const QDateTime &aFromTime) const { if (rushEnabled() && scheduleEnabled()) { if (d_ptr->iRushInterval == d_ptr->iInterval && !d_ptr->iExternalRushEnabled) { qCDebug(lcButeoCore) << "Rush interval is the same as normal interval no need to switch"; return QDateTime(); } if (d_ptr->isRush(aFromTime)) { return QDateTime(aFromTime.date(), d_ptr->iRushEnd); } else { // If rush day and before rush end next switch is at rush begin if (SyncSchedulePrivate::daysMatch(d_ptr->iRushDays, aFromTime.date().dayOfWeek()) && aFromTime.time() < d_ptr->iRushBegin) { return QDateTime(aFromTime.date(), d_ptr->iRushBegin); } else { // Not a rush day or the rush period has ended, attemp switch at next day rush begin, // we can only schedule for 24h return QDateTime(aFromTime.date().addDays(1), d_ptr->iRushBegin); } } } else { return QDateTime(); } } bool SyncSchedule::isSyncScheduled(const QDateTime &aActualDateTime, const QDateTime &aPreviousSyncTime) const { qCDebug(lcButeoCore) << "Check if sync is scheduled against" << aActualDateTime.toString(); // Simple case, aDateTime is the defined sync time. if (d_ptr->iTime.isValid() && d_ptr->iDays != SyncSchedule::NoDays) { /* Todo: this is to simple implementation for the case where sync time is close to midnight and the day has changed already when fired. */ if (!SyncSchedulePrivate::daysMatch(d_ptr->iDays, aActualDateTime.date().dayOfWeek())) { return false; } /* Keep a 10 minutes margin to ensure that delayed syncs by more prioritary sync in progress are still considered as valid sync times. */ return (aActualDateTime.time() < d_ptr->iTime.addSecs(5 * 60) && aActualDateTime.time() > d_ptr->iTime.addSecs(-5 * 60)); } // If sync schedule is defined by rush, check that rush is enabled for aActualDateTime if (rushEnabled() && d_ptr->iRushInterval > 0 && d_ptr->isRush(aActualDateTime)) { return true; } if (!scheduleEnabled() || !d_ptr->iInterval) { qCDebug(lcButeoCore) << "Scheduled by interval: schedule is disabled or not syncing by interval"; return false; } QDateTime reference = (aPreviousSyncTime.isValid()) ? aPreviousSyncTime : d_ptr->iScheduleConfiguredTime; if (!reference.isValid()) { qCDebug(lcButeoCore) << "Schedule has no reference past date, sync now"; return true; } qint64 longInterval = 0; if (d_ptr->iInterval == Sync::SYNC_INTERVAL_MONTHLY) { QDateTime nextSync = reference.addMonths(1); nextSync.setTime(d_ptr->iTime); longInterval = reference.secsTo(nextSync) / 60; } else if (d_ptr->iInterval == Sync::SYNC_INTERVAL_FIRST_DAY_OF_MONTH) { // Calculate interval to the first day of the month after the previous sync. QDate date = reference.date().addMonths(1); date.setDate(date.year(), date.month(), 1); QDateTime nextSync(date, d_ptr->iTime); longInterval = reference.secsTo(nextSync) / 60; } else if (d_ptr->iInterval == Sync::SYNC_INTERVAL_LAST_DAY_OF_MONTH) { // Calculate interval to the last day of the month after the previous sync. QDate date = reference.date(); if (reference.date() == aActualDateTime.date()) { // Already synced on the last day, so calculate to the next month instead. date = date.addMonths(1); } QDateTime nextSync(date, d_ptr->iTime); longInterval = reference.secsTo(nextSync) / 60; } const unsigned int interval = longInterval > 0 ? (longInterval > UINT_MAX ? UINT_MAX : static_cast(longInterval)) : d_ptr->iInterval; // avoid wrap-around: don't subtract from unsigned interval return reference.secsTo(aActualDateTime) > (interval * 60); } SyncSchedule::Days SyncSchedulePrivate::parseDays(const QString &aDays) const { SyncSchedule::Days days = 0; const SyncSchedule::Day fromStrDays[] = {SyncSchedule::Monday, SyncSchedule::Tuesday, SyncSchedule::Wednesday, SyncSchedule::Thursday, SyncSchedule::Friday, SyncSchedule::Saturday, SyncSchedule::Sunday}; if (!aDays.isNull()) { QStringList dayList = aDays.split(DAY_SEPARATOR, QString::SkipEmptyParts); foreach (QString dayStr, dayList) { bool ok; int dayNum = dayStr.toInt(&ok); if (ok && dayNum > 0 && dayNum <= 7) { days |= fromStrDays[dayNum - 1]; } } } return days; } QString SyncSchedulePrivate::createDays(SyncSchedule::Days aDays) const { QStringList dayList; if (aDays & SyncSchedule::Monday) { dayList.append(QString::fromLatin1("1")); } if (aDays & SyncSchedule::Tuesday) { dayList.append(QString::fromLatin1("2")); } if (aDays & SyncSchedule::Wednesday) { dayList.append(QString::fromLatin1("3")); } if (aDays & SyncSchedule::Thursday) { dayList.append(QString::fromLatin1("4")); } if (aDays & SyncSchedule::Friday) { dayList.append(QString::fromLatin1("5")); } if (aDays & SyncSchedule::Saturday) { dayList.append(QString::fromLatin1("6")); } if (aDays & SyncSchedule::Sunday) { dayList.append(QString::fromLatin1("7")); } return dayList.join(DAY_SEPARATOR); } bool SyncSchedulePrivate::daysMatch(SyncSchedule::Days aDays, int aDay) { const SyncSchedule::Day fromQtDays[] = {SyncSchedule::Monday, SyncSchedule::Tuesday, SyncSchedule::Wednesday, SyncSchedule::Thursday, SyncSchedule::Friday, SyncSchedule::Saturday, SyncSchedule::Sunday}; return aDay >= Qt::Monday && aDay <= Qt::Sunday && (aDays & fromQtDays[aDay - 1]); } bool SyncSchedulePrivate::adjustDate(QDateTime &aTime, SyncSchedule::Days aDays) const { if (aDays == SyncSchedule::NoDays) { aTime = QDateTime(); return false; } bool newValidDay = false; int startDay = aTime.date().dayOfWeek(); while (!daysMatch(aDays, aTime.date().dayOfWeek())) { newValidDay = true; aTime = aTime.addDays(1); // Safety check, avoid infinite loop if date set contains // only invalid values. if (aTime.date().dayOfWeek() == startDay) { // Clear next sync time. newValidDay = false; aTime = QDateTime(); break; } } return newValidDay; } bool SyncSchedulePrivate::isRush(const QDateTime &aTime) const { return (daysMatch(iRushDays, aTime.date().dayOfWeek()) && aTime.time() >= iRushBegin && aTime.time() < iRushEnd); } buteo-syncfw-0.11.10/libbuteosyncfw/profile/SyncSchedule.h000066400000000000000000000221641477124122200235470ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2014-2019 Jolla Ltd. * Copyright (C) 2020 Open Mobile Platform LLC. * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCSCHEDULE_H #define SYNCSCHEDULE_H #include #include #include class QDomDocument; class QDomElement; namespace Buteo { class SyncSchedulePrivate; class SyncScheduleTest; const QString SYNC_SCHEDULE_ENABLED_KEY_BOOL("scheduler/schedule_enabled"); const QString SYNC_SCHEDULE_PEAK_ENABLED_KEY_BOOL("scheduler/schedule_peak_enabled"); const QString SYNC_SCHEDULE_OFFPEAK_ENABLED_KEY_BOOL("scheduler/schedule_offpeak_enabled"); const QString SYNC_SCHEDULE_PEAK_DAYS_KEY_INT ("scheduler/schedule_peak_days"); const QString SYNC_SCHEDULE_PEAK_START_TIME_KEY_INT ("scheduler/schedule_peak_start_time"); const QString SYNC_SCHEDULE_PEAK_END_TIME_KEY_INT ("scheduler/schedule_peak_end_time"); const QString SYNC_SCHEDULE_PEAK_SCHEDULE_KEY_INT ("scheduler/schedule_peak"); const QString SYNC_SCHEDULE_OFFPEAK_SCHEDULE_KEY_INT ("scheduler/schedule_off_peak"); /*! \brief Class for handling sync schedule settings. * */ class SyncSchedule { Q_GADGET Q_PROPERTY(QTime time READ time) Q_PROPERTY(Days days READ days) Q_PROPERTY(unsigned interval READ interval) Q_PROPERTY(bool enabled READ scheduleEnabled) Q_PROPERTY(QTime rushBegin READ rushBegin) Q_PROPERTY(QTime rushEnd READ rushEnd) Q_PROPERTY(Days rushDays READ rushDays) Q_PROPERTY(unsigned rushInterval READ rushInterval) Q_PROPERTY(bool rushEnabled READ rushEnabled) public: enum Day { NoDays = 0x00, Monday = 0x01, Tuesday = 0x02, Wednesday = 0x04, Thursday = 0x08, Friday = 0x10, Saturday = 0x20, Sunday = 0x40 }; Q_DECLARE_FLAGS(Days, Day) Q_FLAG(Days) /*! \brief Constructs an empty schedule. * */ SyncSchedule(); /*! \brief Copy constructor. * * \param aSource Copy source. */ SyncSchedule(const SyncSchedule &aSource); /*! \brief Constructs sync schedule from XML. * * \param aRoot Root element of the XML representation. */ explicit SyncSchedule(const QDomElement &aRoot); /*! \brief Destructor. */ ~SyncSchedule(); /*! \brief Assignment operator. * * \param aRhs Source */ SyncSchedule &operator=(const SyncSchedule &aRhs); /*! \brief Equal to operator. * * \param aRhs Source */ bool operator==(const SyncSchedule &aRhs) const; /*! \brief Exports the sync schedule to XML. * * \param aDoc Parent document for the created XML elements. * \return Root element of the created XML. */ QDomElement toXml(QDomDocument &aDoc) const; /*! \brief Exports the sync schedule to QString. * * \return return the Schedule as xml formatted string */ QString toString() const; /*! \brief Gets the enabled week days of the sync schedule. * * Enabled week days are provided as a bitwise OR of SyncSchedule::Day. * \return Set of week days. */ Days days() const; /*! \brief Sets the enabled week days. * * \param aDays Set of enabled week days. */ void setDays(Days aDays); /*! \brief Gets the exact time set in sync schedule. * * \return Sync time. Null object if time has not been defined. */ QTime time() const; /*! \brief Sets the exact time for sync. * * Set to null object QTime() to disable syncing at exact time. * \param aTime Sync time. */ void setTime(const QTime &aTime); /*! \brief Sets scheduled config time. * * \param QDateTime Sync time. */ void setScheduleConfiguredTime(const QDateTime &aDateTime); /*! \brief To get the scheduled config time. * * \return QDateTime Sync time. */ QDateTime scheduleConfiguredTime(); /*! \brief Gets sync interval in minutes. * * \return Interval. */ unsigned interval() const; /*! \brief Sets sync interval in minutes. * * Set to zero to disable syncing with intervals. * \param aInterval Interval. */ void setInterval(unsigned aInterval); /*! \brief Checks if normal schedule is obeyed. * * \return Normal schedule is obeyed. Return value false corresponds to "manual" mode. */ bool scheduleEnabled() const; /*! \brief Sets if normal schedule is to be obeyed. * * \param aEnabled Specify if normal scheduling hours enabled. If set to false, corresponds to "manual" mode. */ void setScheduleEnabled(bool aEnabled); // ============== RUSH HOUR SETTINGS ============================ /*! \brief Checks if rush hour schedule is to be obeyed. * * \return True if rush hour schedule is to be used. False, if rush hour scheduling is off (i.e. manual mode). */ bool rushEnabled() const; /*! \brief Sets rush hour schedule is to be obeyed. * * \param aEnabled If set to false, corresponds to rush hour scheduling off, i.e. "manual" sync. */ void setRushEnabled(bool aEnabled); /*! \brief Checks if rush schedule is controlled by a external process. * * \return True if rush hour schedule is to be used by a external process, The external process will control the sync, buteo will just * controll the schedule outside rush hours and will be responsible to switch from rush to no-rush(and vice-versa) modes. * False, if rush hour scheduling is controlled by this process or if rush hour scheduling is off (i.e. manual mode). */ bool syncExternallyDuringRush() const; /*! \brief Sets if rush schedule is controlled by a external process. * * \param aEnabled If set to true, corresponds to external rush hour scheduling on, i.e. sync controlled by a external process. */ void setSyncExternallyDuringRush(bool aEnabled); /*! \brief Gets days enabled for rush hours. * * \return Set of days enabled for rush hours. */ Days rushDays() const; /*! \brief Sets days enabled for rush hours. * * \param aDays Enabled days for rush hours. */ void setRushDays(Days aDays); /*! \brief Gets begin time of rush hours. * * \return Begin time. */ QTime rushBegin() const; /*! \brief Gets end time of rush hours. * * \return End time. */ QTime rushEnd() const; /*! \brief Sets begin and end times of rush hours. * * \param aBegin Begin time. * \param aEnd End time. */ void setRushTime(const QTime &aBegin, const QTime &aEnd); /*! \brief Gets sync interval for rush hours. * * \return Interval in minutes. */ unsigned rushInterval() const; /*! \brief Sets sync interval for rush hours. * * \param aInterval Interval. */ void setRushInterval(unsigned aInterval); /*! \brief Checks if a given time is inside rush hour and if the sync is controlled by a external process. * * \param aDateTime DateTime to check. */ bool inExternalSyncRushPeriod(const QDateTime &aDateTime) const; /*! \brief Gets next sync time based on the sync schedule settings. * * \param aPrevSync Previous sync time. * \return Next sync time. Null object if schedule is not defined. */ QDateTime nextSyncTime(const QDateTime &aPrevSync) const; /*! \brief Gets next time to switch rush/off-rush schedule intervals. * * \param aFromTime From time to calculate next switch, usually current time. * \return Next time to switch rush/off-rush schedule intervals. Null object if schedule is not defined for rush/off-rush * or if the rush and off-rush intervals are the same. */ QDateTime nextRushSwitchTime(const QDateTime &aFromTime) const; /*! \brief Returns true if aDateTime is within a scheduled period. * * \param aActualDateTime the actual sync date time to be tested. * \param aPreviousSyncTime the previous sync time, for calculation where a sync interval is used. * \return true if at aActualDateTime, the synchronization is possible. */ bool isSyncScheduled(const QDateTime &aActualDateTime, const QDateTime &aPreviousSyncTime = QDateTime()) const; private: SyncSchedulePrivate *d_ptr; #ifdef SYNCFW_UNIT_TESTS friend class SyncScheduleTest; #endif }; } Q_DECLARE_METATYPE(Buteo::SyncSchedule) #endif // SYNCSCHEDULE_H buteo-syncfw-0.11.10/libbuteosyncfw/profile/SyncSchedule_p.h000066400000000000000000000071211477124122200240620ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2014-2015 Jolla Ltd. * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCSCHEDULE_P_H #define SYNCSCHEDULE_P_H #include #include #include "SyncSchedule.h" namespace Buteo { //! Private implementation class for SyncSchedule. class SyncSchedulePrivate { public: /*! \brief Constructor * */ SyncSchedulePrivate(); /*! \brief Copy Constructor * */ SyncSchedulePrivate(const SyncSchedulePrivate &aSource); /*! \brief Tell if aDays contains aDay. * * \param aDays A set of days. * \param aDay A given day in Qt convention of Qt::DayOfWeek. * \return true if aDay is within aDays. */ static bool daysMatch(SyncSchedule::Days aDays, int aDay); /*! \brief Parses week day numbers from a string. * * \param aDays String containing the week day numbers. * \return Set of week days. */ SyncSchedule::Days parseDays(const QString &aDays) const; /*! \brief Creates a string from a set of week day numbers. * * \param aDays Set of week days. * \return String of week day represented by their numbers (Monday = 1). */ QString createDays(SyncSchedule::Days aDays) const; /*! \brief Adjusts given date to be in the set of given week days. * * Day is increased until the week day is contained in the given set of * week day numbers. * \param aTime Date/time to adjust. * \param aDays Set of enabled week days. * \return Was day adjusted to a valid day. If the week day was already in * the set of given week days, this function returns false. If the day * set does not contain any valid days, this function sets aTime to null * object and returns false. */ bool adjustDate(QDateTime &aTime, SyncSchedule::Days aDays) const; /*! \brief Checks if the given date/time is inside rush hours. * * \param aTime Date/time to check. * \return True if in rush hours. */ bool isRush(const QDateTime &aTime) const; //! Days for the sync SyncSchedule::Days iDays; //! Sync Time QTime iTime; //! sync schedule configure time for intial update QDateTime iScheduleConfiguredTime; //! Time interval unsigned int iInterval; bool iEnabled; // ============ RUSH HOUR SETTINGS =========== //! indicates the schedule for rush hour - days SyncSchedule::Days iRushDays; //! indicates the schedule for rush hour start QTime iRushBegin; //! indicates the schedule for rush hour end QTime iRushEnd; //! Rush Hour Time interval unsigned int iRushInterval; //! Indicates if Rush Hour is Enabled bool iRushEnabled; //! Indicates if External Rush Hour schedule is Enabled bool iExternalRushEnabled; }; } #endif // SYNCSCHEDULE_P_H buteo-syncfw-0.11.10/libbuteosyncfw/profile/TargetResults.cpp000066400000000000000000000306541477124122200243240ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "TargetResults.h" #include "ProfileEngineDefs.h" #include "LogMacros.h" #include namespace Buteo { struct ItemDetails { QString uid; TargetResults::ItemOperationStatus status; QString message; ItemDetails(): status(TargetResults::ITEM_OPERATION_SUCCEEDED) {} ItemDetails(const QString &aUid, TargetResults::ItemOperationStatus aStatus, const QString &aMessage) : uid(aUid), status(aStatus), message(aMessage) {} ItemDetails(const QDomElement &aRoot) : uid(aRoot.attribute(ATTR_UID)) , status(aRoot.attribute(ATTR_STATUS).compare(QLatin1String("failed"), Qt::CaseInsensitive) ? TargetResults::ITEM_OPERATION_SUCCEEDED : TargetResults::ITEM_OPERATION_FAILED) , message(aRoot.text()) { } QDomElement toXml(QDomDocument &aDoc, const QString &aTag) const { QDomElement item = aDoc.createElement(aTag); item.setAttribute(ATTR_UID, uid); if (status == TargetResults::ITEM_OPERATION_FAILED) { item.setAttribute(ATTR_STATUS, QLatin1String("failed")); } if (!message.isEmpty()) { item.appendChild(aDoc.createCDATASection(message)); } return item; }; static QList fromXml(const QDomElement &aRoot, const QString &aTag) { QList out; QDomElement item = aRoot.firstChildElement(aTag); while (!item.isNull()) { ItemDetails details(item); if (!details.uid.isEmpty()) { out.append(details); } item = item.nextSiblingElement(aTag); }; return out; }; static QList filterStatus(const QList &aList, TargetResults::ItemOperationStatus aStatus) { QList out; for (const ItemDetails &details : aList) { if (details.status == aStatus) { out.append(details.uid); } } return out; }; static bool find(const QList &aList, const QString &aUid, QList::ConstIterator &it) { for (it = aList.constBegin(); it != aList.constEnd(); ++it) { if (it->uid == aUid) { return true; } } return false; } }; // Private implementation class for TargetResults class TargetResultsPrivate { public: TargetResultsPrivate(); TargetResultsPrivate(const TargetResultsPrivate &aSource); // Target name. QString iTargetName; ItemCounts iLocalItems; QList iLocalAdditions; QList iLocalDeletions; QList iLocalModifications; ItemCounts iRemoteItems; QList iRemoteAdditions; QList iRemoteDeletions; QList iRemoteModifications; }; } using namespace Buteo; TargetResultsPrivate::TargetResultsPrivate() { } TargetResultsPrivate::TargetResultsPrivate(const TargetResultsPrivate &aSource) : iTargetName(aSource.iTargetName), iLocalItems(aSource.iLocalItems), iLocalAdditions(aSource.iLocalAdditions), iLocalDeletions(aSource.iLocalDeletions), iLocalModifications(aSource.iLocalModifications), iRemoteItems(aSource.iRemoteItems), iRemoteAdditions(aSource.iRemoteAdditions), iRemoteDeletions(aSource.iRemoteDeletions), iRemoteModifications(aSource.iRemoteModifications) { } TargetResults::TargetResults() : d_ptr(new TargetResultsPrivate()) { } TargetResults::TargetResults(const TargetResults &aSource) : d_ptr(new TargetResultsPrivate(*aSource.d_ptr)) { } TargetResults::TargetResults(const QString &aTargetName, ItemCounts aLocalItems, ItemCounts aRemoteItems) : d_ptr(new TargetResultsPrivate()) { d_ptr->iTargetName = aTargetName; d_ptr->iLocalItems = aLocalItems; d_ptr->iRemoteItems = aRemoteItems; } TargetResults::TargetResults(const QDomElement &aRoot) : d_ptr(new TargetResultsPrivate()) { d_ptr->iTargetName = aRoot.attribute(ATTR_NAME); QDomElement local = aRoot.firstChildElement(TAG_LOCAL); if (!local.isNull()) { d_ptr->iLocalItems.added = local.attribute(ATTR_ADDED).toUInt(); d_ptr->iLocalItems.deleted = local.attribute(ATTR_DELETED).toUInt(); d_ptr->iLocalItems.modified = local.attribute(ATTR_MODIFIED).toUInt(); d_ptr->iLocalAdditions = ItemDetails::fromXml(local, TAG_ADDED_ITEM); d_ptr->iLocalDeletions = ItemDetails::fromXml(local, TAG_DELETED_ITEM); d_ptr->iLocalModifications = ItemDetails::fromXml(local, TAG_MODIFIED_ITEM); } QDomElement remote = aRoot.firstChildElement(TAG_REMOTE); if (!remote.isNull()) { d_ptr->iRemoteItems.added = remote.attribute(ATTR_ADDED).toUInt(); d_ptr->iRemoteItems.deleted = remote.attribute(ATTR_DELETED).toUInt(); d_ptr->iRemoteItems.modified = remote.attribute(ATTR_MODIFIED).toUInt(); d_ptr->iRemoteAdditions = ItemDetails::fromXml(remote, TAG_ADDED_ITEM); d_ptr->iRemoteDeletions = ItemDetails::fromXml(remote, TAG_DELETED_ITEM); d_ptr->iRemoteModifications = ItemDetails::fromXml(remote, TAG_MODIFIED_ITEM); } } TargetResults::~TargetResults() { delete d_ptr; d_ptr = 0; } TargetResults &TargetResults::operator=(const TargetResults &aRhs) { if (&aRhs != this) { delete d_ptr; d_ptr = new TargetResultsPrivate(*aRhs.d_ptr); } return *this; } QDomElement TargetResults::toXml(QDomDocument &aDoc) const { QDomElement root = aDoc.createElement(TAG_TARGET_RESULTS); root.setAttribute(ATTR_NAME, d_ptr->iTargetName); QDomElement local = aDoc.createElement(TAG_LOCAL); local.setAttribute(ATTR_ADDED, d_ptr->iLocalItems.added); local.setAttribute(ATTR_DELETED, d_ptr->iLocalItems.deleted); local.setAttribute(ATTR_MODIFIED, d_ptr->iLocalItems.modified); for (const ItemDetails &details : d_ptr->iLocalAdditions) { local.appendChild(details.toXml(aDoc, TAG_ADDED_ITEM)); } for (const ItemDetails &details : d_ptr->iLocalDeletions) { local.appendChild(details.toXml(aDoc, TAG_DELETED_ITEM)); } for (const ItemDetails &details : d_ptr->iLocalModifications) { local.appendChild(details.toXml(aDoc, TAG_MODIFIED_ITEM)); } root.appendChild(local); QDomElement remote = aDoc.createElement(TAG_REMOTE); remote.setAttribute(ATTR_ADDED, d_ptr->iRemoteItems.added); remote.setAttribute(ATTR_DELETED, d_ptr->iRemoteItems.deleted); remote.setAttribute(ATTR_MODIFIED, d_ptr->iRemoteItems.modified); for (const ItemDetails &details : d_ptr->iRemoteAdditions) { remote.appendChild(details.toXml(aDoc, TAG_ADDED_ITEM)); } for (const ItemDetails &details : d_ptr->iRemoteDeletions) { remote.appendChild(details.toXml(aDoc, TAG_DELETED_ITEM)); } for (const ItemDetails &details : d_ptr->iRemoteModifications) { remote.appendChild(details.toXml(aDoc, TAG_MODIFIED_ITEM)); } root.appendChild(remote); return root; } QString TargetResults::targetName() const { return d_ptr->iTargetName; } ItemCounts TargetResults::localItems() const { return d_ptr->iLocalItems; } ItemCounts TargetResults::remoteItems() const { return d_ptr->iRemoteItems; } void TargetResults::addLocalDetails(const QString &aUid, Buteo::TargetResults::ItemOperation aOperation, Buteo::TargetResults::ItemOperationStatus aStatus, const QString &aMessage) { if (aUid.isEmpty()) { qCWarning(lcButeoCore) << "Cannot add details with empty uid."; return; } switch (aOperation) { case ITEM_ADDED: if (aStatus == Buteo::TargetResults::ITEM_OPERATION_SUCCEEDED) d_ptr->iLocalItems.added += 1; d_ptr->iLocalAdditions.append(ItemDetails(aUid, aStatus, aMessage)); break; case ITEM_DELETED: if (aStatus == Buteo::TargetResults::ITEM_OPERATION_SUCCEEDED) d_ptr->iLocalItems.deleted += 1; d_ptr->iLocalDeletions.append(ItemDetails(aUid, aStatus, aMessage)); break; case ITEM_MODIFIED: if (aStatus == Buteo::TargetResults::ITEM_OPERATION_SUCCEEDED) d_ptr->iLocalItems.modified += 1; d_ptr->iLocalModifications.append(ItemDetails(aUid, aStatus, aMessage)); break; }; } void TargetResults::addRemoteDetails(const QString &aUid, Buteo::TargetResults::ItemOperation aOperation, Buteo::TargetResults::ItemOperationStatus aStatus, const QString &aMessage) { if (aUid.isEmpty()) { qCWarning(lcButeoCore) << "Cannot add details with empty uid."; return; } switch (aOperation) { case ITEM_ADDED: if (aStatus == Buteo::TargetResults::ITEM_OPERATION_SUCCEEDED) d_ptr->iRemoteItems.added += 1; d_ptr->iRemoteAdditions.append(ItemDetails(aUid, aStatus, aMessage)); break; case ITEM_DELETED: if (aStatus == Buteo::TargetResults::ITEM_OPERATION_SUCCEEDED) d_ptr->iRemoteItems.deleted += 1; d_ptr->iRemoteDeletions.append(ItemDetails(aUid, aStatus, aMessage)); break; case ITEM_MODIFIED: if (aStatus == Buteo::TargetResults::ITEM_OPERATION_SUCCEEDED) d_ptr->iRemoteItems.modified += 1; d_ptr->iRemoteModifications.append(ItemDetails(aUid, aStatus, aMessage)); break; }; } QList TargetResults::localDetails(Buteo::TargetResults::ItemOperation aOperation, Buteo::TargetResults::ItemOperationStatus aStatus) const { switch (aOperation) { case ITEM_ADDED: return ItemDetails::filterStatus(d_ptr->iLocalAdditions, aStatus); case ITEM_DELETED: return ItemDetails::filterStatus(d_ptr->iLocalDeletions, aStatus); case ITEM_MODIFIED: return ItemDetails::filterStatus(d_ptr->iLocalModifications, aStatus); } return QList(); } QString TargetResults::localMessage(const QString &aUid) const { QList::ConstIterator it; if (ItemDetails::find(d_ptr->iLocalAdditions, aUid, it)) { return it->message; } else if (ItemDetails::find(d_ptr->iLocalDeletions, aUid, it)) { return it->message; } else if (ItemDetails::find(d_ptr->iLocalModifications, aUid, it)) { return it->message; } return QString(); } QList TargetResults::remoteDetails(Buteo::TargetResults::ItemOperation aOperation, Buteo::TargetResults::ItemOperationStatus aStatus) const { switch (aOperation) { case ITEM_ADDED: return ItemDetails::filterStatus(d_ptr->iRemoteAdditions, aStatus); case ITEM_DELETED: return ItemDetails::filterStatus(d_ptr->iRemoteDeletions, aStatus); case ITEM_MODIFIED: return ItemDetails::filterStatus(d_ptr->iRemoteModifications, aStatus); }; return QList(); } QString TargetResults::remoteMessage(const QString &aUid) const { QList::ConstIterator it; if (ItemDetails::find(d_ptr->iRemoteAdditions, aUid, it)) { return it->message; } else if (ItemDetails::find(d_ptr->iRemoteDeletions, aUid, it)) { return it->message; } else if (ItemDetails::find(d_ptr->iRemoteModifications, aUid, it)) { return it->message; } return QString(); } buteo-syncfw-0.11.10/libbuteosyncfw/profile/TargetResults.h000066400000000000000000000220211477124122200237560ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef TARGETRESULTS_H #define TARGETRESULTS_H #include #include #include class QDomDocument; class QDomElement; namespace Buteo { class TargetResultsPrivate; //! \brief Container for number of items added, deleted and modified. struct ItemCounts { Q_GADGET Q_PROPERTY(unsigned added MEMBER added) Q_PROPERTY(unsigned deleted MEMBER deleted) Q_PROPERTY(unsigned modified MEMBER modified) public: //! No. of Items added unsigned added; //! No. of Items deleted unsigned deleted; //! No. of Items modified unsigned modified; //! Default Constructor ItemCounts() : added(0), deleted(0), modified(0) { }; //! Constructor with 3 parameters ItemCounts(unsigned aAdded, unsigned aDeleted, unsigned aModified) : added(aAdded), deleted(aDeleted), modified(aModified) {} }; /*! \brief Sync results for one target. * * TargetResults contains information about how many items were added, deleted * and modified in a specific sync target during the sync session. */ class TargetResults { Q_GADGET Q_PROPERTY(QString target READ targetName) Q_PROPERTY(Buteo::ItemCounts local READ localItems) Q_PROPERTY(Buteo::ItemCounts remote READ remoteItems) Q_PROPERTY(QStringList localAdditions READ localAdditions) Q_PROPERTY(QStringList localDeletions READ localDeletions) Q_PROPERTY(QStringList localModifications READ localModifications) Q_PROPERTY(QStringList localFailures READ localFailures) Q_PROPERTY(QStringList remoteAdditions READ remoteAdditions) Q_PROPERTY(QStringList remoteDeletions READ remoteDeletions) Q_PROPERTY(QStringList remoteModifications READ remoteModifications) Q_PROPERTY(QStringList remoteFailures READ remoteFailures) public: enum ItemOperation { ITEM_ADDED, ITEM_DELETED, ITEM_MODIFIED }; enum ItemOperationStatus { ITEM_OPERATION_SUCCEEDED, ITEM_OPERATION_FAILED }; TargetResults(); /*! \brief Copy constructor. * * \param aSource Copy source. */ TargetResults(const TargetResults &aSource); /*! \brief Constructor. * * \param aTargetName Name of the target. * \param aLocalItems Counts of local items. * \param aRemoteItems Counts of remote items. */ TargetResults(const QString &aTargetName, ItemCounts aLocalItems = ItemCounts(), ItemCounts aRemoteItems = ItemCounts()); /*! \brief Constructs TargetResults from XML. * * \param aRoot Root element of the XML representation. */ explicit TargetResults(const QDomElement &aRoot); /*! \brief Destructor. */ ~TargetResults(); /*! \brief Assignment operator. * * \param aRhs Source. */ TargetResults &operator=(const TargetResults &aRhs); /*! \brief Exports the target results to XML. * * \param aDoc Parent document for the created XML elements. The created * elements are not inserted to the document by this function, but the * document is still required for creating the elements. * \return Root element of the created XML. */ QDomElement toXml(QDomDocument &aDoc) const; /*! \brief Gets the target name. * * \return Target name. */ QString targetName() const; /*! \brief Gets the counts of items added, deleted and modified locally. * * \return Item counts. */ ItemCounts localItems() const; /*! \brief Gets the counts of items added, deleted and modified at remote. * * \return Item counts. */ ItemCounts remoteItems() const; /*! \brief Add some details on the local changes done during the sync process. * * Provide additional information per item basis on the local changes * done during the sync process. If the operation succeeded, the associated * item count is increase by one. * * \param aUid A way for a sync plugin to identify the changed item. * \param aOperation The operation done on the item (addition, deletion * or modification). * \param aStatus Particular status for this item sync operation. * \param aMessage Additional information related to this item operation. */ void addLocalDetails(const QString &aUid, ItemOperation aOperation, ItemOperationStatus aStatus = ITEM_OPERATION_SUCCEEDED, const QString &aMessage = QString()); /*! \brief Add some details on the remote changes done during the sync process. * * Provide additional information per item basis on the remote changes * done during the sync process. If the operation succeeded, the associated * item count is increase by one. * * \param aUid A way for a sync plugin to identify the changed item. * \param aOperation The operation done on the item (addition, deletion * or modification). * \param aStatus Particular status for this item sync operation. * \param aMessage Additional information related to this item operation. */ void addRemoteDetails(const QString &aUid, ItemOperation aOperation, ItemOperationStatus aStatus = ITEM_OPERATION_SUCCEEDED, const QString &aMessage = QString()); /*! \brief Gets the details, if any for changes done local during a sync process. * * \param aOperation The operation the details are related to (addition, * deletion or modification). * \param aStatus The kind of status for the operation (success or failure). * \return A list of UIDs that correspond to this operation and status. * The meaning of these UIDs is defined by the SyncPlugin * which generated the log. */ QList localDetails(ItemOperation aOperation, ItemOperationStatus aStatus) const; /*! \brief Gets a possible message related to the a given item. * * \param aUid A UID as returned by localDetails(). * \return A message stored in the log related to this particular item. */ Q_INVOKABLE QString localMessage(const QString &aUid) const; /*! \brief Gets the details, if any for changes done remote during a sync process. * * \param aOperation The operation the details are related to (addition, * deletion or modification). * \param aStatus The kind of status for the operation (success or failure). * \return A list of UIDs that correspond to this operation and status. * The meaning of these UIDs is defined by the SyncPlugin * which generated the log. */ QList remoteDetails(ItemOperation aOperation, ItemOperationStatus aStatus) const; /*! \brief Gets a possible message related to the a given item. * * \param aUid A UID as returned by remoteDetails(). * \return A message stored in the log related to this particular item. */ Q_INVOKABLE QString remoteMessage(const QString &aUid) const; private: QStringList localAdditions() const { return localDetails(ITEM_ADDED, ITEM_OPERATION_SUCCEEDED); } QStringList localDeletions() const { return localDetails(ITEM_DELETED, ITEM_OPERATION_SUCCEEDED); } QStringList localModifications() const { return localDetails(ITEM_MODIFIED, ITEM_OPERATION_SUCCEEDED); } QStringList localFailures() const { return localDetails(ITEM_ADDED, ITEM_OPERATION_FAILED) + localDetails(ITEM_MODIFIED, ITEM_OPERATION_FAILED) + localDetails(ITEM_DELETED, ITEM_OPERATION_FAILED); } QStringList remoteAdditions() const { return remoteDetails(ITEM_ADDED, ITEM_OPERATION_SUCCEEDED); } QStringList remoteDeletions() const { return remoteDetails(ITEM_DELETED, ITEM_OPERATION_SUCCEEDED); } QStringList remoteModifications() const { return remoteDetails(ITEM_MODIFIED, ITEM_OPERATION_SUCCEEDED); } QStringList remoteFailures() const { return remoteDetails(ITEM_ADDED, ITEM_OPERATION_FAILED) + remoteDetails(ITEM_MODIFIED, ITEM_OPERATION_FAILED) + remoteDetails(ITEM_DELETED, ITEM_OPERATION_FAILED); } TargetResultsPrivate *d_ptr; }; } Q_DECLARE_METATYPE(Buteo::ItemCounts) Q_DECLARE_METATYPE(Buteo::TargetResults) #endif // TARGETRESULTS_H buteo-syncfw-0.11.10/msyncd/000077500000000000000000000000001477124122200155765ustar00rootroot00000000000000buteo-syncfw-0.11.10/msyncd/AccountsHelper.cpp000066400000000000000000000355701477124122200212330ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "AccountsHelper.h" #include "LogMacros.h" #include "ProfileManager.h" #include "Profile.h" #include "ProfileEngineDefs.h" #include static const QString ACCOUNTS_GLOBAL_SERVICE("global"); static const QString REMOTE_SERVICE_NAME("remote_service_name"); using namespace Buteo; AccountsHelper::AccountsHelper(ProfileManager &aProfileManager, QObject *aParent) : QObject(aParent) , iProfileManager(aProfileManager) { iAccountManager = new Accounts::Manager(this); // Connect to signal for account creation and deletion. QObject::connect(iAccountManager, SIGNAL(accountCreated(Accounts::AccountId)), this, SLOT(createProfileForAccount(Accounts::AccountId))); QObject::connect(iAccountManager, SIGNAL(accountRemoved(Accounts::AccountId)), this, SLOT(slotAccountRemoved(Accounts::AccountId))); // load accounts after return from contructor, to allow connection with class signals // that can be fired by 'registerAccountListeners' function // Handle account modifications with a listener per account. QTimer::singleShot(0, this, SLOT(registerAccountListeners())); } AccountsHelper::~AccountsHelper() { iAcctWatchMap.clear(); delete iAccountManager; iAccountManager = 0; // There is no need to delete the accounts objects as they get deleted by // their parent, aka, the manager } void AccountsHelper::createProfileForAccount(Accounts::AccountId id) { FUNCTION_CALL_TRACE(lcButeoTrace); Accounts::Account *newAccount = iAccountManager->account(id); if (0 != newAccount) { registerAccountListener(id); bool profileFoundAndCreated = false; const Accounts::ServiceList serviceList = newAccount->services(); for (const Accounts::Service &service : serviceList) { // Look for a sync profile that matches the service name (template) qCDebug(lcButeoMsyncd) << "Looking for sync profile::" << service.name(); bool serviceEnabled = newAccount->enabledServices().contains(service); profileFoundAndCreated = addProfileForAccount(newAccount, service.name(), serviceEnabled) || profileFoundAndCreated; } // Fetch the key "remote_service_name" from the account settings and // use it to create a profile QString serviceName = newAccount->valueAsString(REMOTE_SERVICE_NAME); if (profileFoundAndCreated == false && !serviceName.isEmpty()) { qCDebug(lcButeoMsyncd) << "Profile name from account setting:" << serviceName; const QString profileName = serviceName + "-" + QString::number(newAccount->id()); if (addProfileForAccount(newAccount, serviceName, true, profileName)) { newAccount->setValue(KEY_PROFILE_ID, profileName); newAccount->syncAndBlock(); } } } else { qCDebug(lcButeoMsyncd) << "Account not found:" << id; } } void AccountsHelper::slotAccountRemoved(Accounts::AccountId id) { FUNCTION_CALL_TRACE(lcButeoTrace); // Delete the profile(s) with account ID QList syncProfiles = getProfilesByAccountId(id); foreach (SyncProfile *profile, syncProfiles) { qCDebug(lcButeoMsyncd) << "Removing profile" << profile->name(); emit removeProfile(profile->name()); delete profile; } #ifdef USE_ACCOUNTSHELPER_SCHEDULER_WATCHER // remove corresponding watch from the Map QMap::iterator i = iAcctWatchMap.begin(); while (i != iAcctWatchMap.end()) { if (i.value() == id) { i = iAcctWatchMap.erase(i); break; } else ++i; } #endif } static Accounts::Service serviceForProfile(Accounts::Account *account, const SyncProfile *profile) { const Accounts::ServiceList services = account->services(); for (const Accounts::Service &service : services) { account->selectService(service); // Look for profile naming using a template defined in the account service. const QStringList names = account->value(QStringLiteral("sync_profile_templates")).toStringList(); for (const QString &name : names) { if (profile->name() == name + "-" + QString::number(account->id())) { return service; } } // Earlier possible matching was using service name as profile name. // This was abandonned later to avoid profile name clashing with // identical services for different accounts. if (profile->name() == service.name()) { return service; } // Add here other possible matcher schemes. } return Accounts::Service(); } void AccountsHelper::syncEnableWithAccount(Accounts::Account *account) { account->selectService(); // Always use the current enabled value since signals may be emitted with delays. bool enabled = account->enabled(); const QList profiles = getProfilesByAccountId(account->id()); for (SyncProfile *profile : profiles) { qCDebug(lcButeoMsyncd) << "Changing profile enabled" << profile->name() << enabled; if (enabled) { // Global is enabled, checking by service if any. bool serviceEnabled = true; Accounts::Service service = serviceForProfile(account, profile); if (service.isValid()) { account->selectService(service); serviceEnabled = account->enabled(); } qCDebug(lcButeoMsyncd) << "Enabled status for service ::" << profile->name() << serviceEnabled; if (profile->isEnabled() != serviceEnabled) { profile->setEnabled(serviceEnabled); iProfileManager.updateProfile(*profile); emit scheduleUpdated(profile->name()); } } else if (profile->isEnabled()) { // Global is false, unconditionally disable profile->setEnabled(false); iProfileManager.updateProfile(*profile); emit removeScheduledSync(profile->name()); } delete profile; } account->selectService(); } void AccountsHelper::setSyncSchedule(SyncProfile *syncProfile, Accounts::AccountId id, bool aCreateNew) { FUNCTION_CALL_TRACE(lcButeoTrace); Q_UNUSED (aCreateNew); Accounts::Account *account = iAccountManager->account(id); if (0 != account) { //Sync schedule settings should be global account->selectService(); account->beginGroup("scheduler"); SyncSchedule syncSchedule; int peakStart = account->valueAsInt(Buteo::SYNC_SCHEDULE_PEAK_START_TIME_KEY_INT); QTime startTime(peakStart / 60, peakStart % 60); int peakEnd = account->valueAsInt(Buteo::SYNC_SCHEDULE_PEAK_END_TIME_KEY_INT); QTime endTime(peakEnd / 60, peakEnd % 60); qCDebug(lcButeoMsyncd) << "Start time:" << startTime << "End Time :" << endTime; syncSchedule.setRushTime(startTime, endTime); SyncProfile::SyncType syncType = account->valueAsBool (Buteo::SYNC_SCHEDULE_ENABLED_KEY_BOOL) ? SyncProfile::SYNC_SCHEDULED : SyncProfile::SYNC_MANUAL ; syncProfile->setSyncType (syncType); qCDebug(lcButeoMsyncd) << "Sync Type :" << syncType; syncSchedule.setRushEnabled(account->valueAsBool(Buteo::SYNC_SCHEDULE_PEAK_ENABLED_KEY_BOOL)); qCDebug(lcButeoMsyncd) << "Sync PEAK :" << account->valueAsBool(Buteo::SYNC_SCHEDULE_PEAK_ENABLED_KEY_BOOL); syncSchedule.setScheduleEnabled(account->valueAsBool(Buteo::SYNC_SCHEDULE_OFFPEAK_ENABLED_KEY_BOOL)); qCDebug(lcButeoMsyncd) << "Sync OFFPEAK :" << account->valueAsBool(Buteo::SYNC_SCHEDULE_OFFPEAK_ENABLED_KEY_BOOL); int scheduleInterval = 60; if (syncSchedule.scheduleEnabled()) scheduleInterval = account->valueAsInt(Buteo::SYNC_SCHEDULE_OFFPEAK_SCHEDULE_KEY_INT); syncSchedule.setInterval(scheduleInterval); qCDebug(lcButeoMsyncd) << "Sync Interval :" << scheduleInterval; scheduleInterval = 60; if (syncSchedule.rushEnabled()) scheduleInterval = account->valueAsInt (Buteo::SYNC_SCHEDULE_PEAK_SCHEDULE_KEY_INT); syncSchedule.setRushInterval(scheduleInterval); qCDebug(lcButeoMsyncd) << "Sync Rush Interval :" << scheduleInterval; syncSchedule.setDays(Buteo::SyncSchedule::Days() | Buteo::SyncSchedule::Monday | Buteo::SyncSchedule::Tuesday | Buteo::SyncSchedule::Wednesday | Buteo::SyncSchedule::Thursday | Buteo::SyncSchedule::Friday | Buteo::SyncSchedule::Saturday | Buteo::SyncSchedule::Sunday); syncSchedule.setRushDays(Buteo::SyncSchedule::Days(account->valueAsInt(Buteo::SYNC_SCHEDULE_PEAK_DAYS_KEY_INT))); qCDebug(lcButeoMsyncd) << "Sync Days :" << syncSchedule.rushDays(); account->endGroup(); syncProfile->setSyncSchedule (syncSchedule); } } QList AccountsHelper::getProfilesByAccountId(Accounts::AccountId id) { FUNCTION_CALL_TRACE(lcButeoTrace); QList filters; ProfileManager::SearchCriteria filter; filter.iType = ProfileManager::SearchCriteria::EQUAL; filter.iKey = KEY_ACCOUNT_ID; filter.iValue = QString::number(id); filters.append(filter); return iProfileManager.getSyncProfilesByData(filters); } bool AccountsHelper::addProfileForAccount(Accounts::Account *account, const QString &serviceName, bool serviceEnabled, const QString &label) { FUNCTION_CALL_TRACE(lcButeoTrace); SyncProfile *serviceProfile = iProfileManager.syncProfile(serviceName); if (!serviceProfile || (false == serviceProfile->boolKey(KEY_USE_ACCOUNTS, false))) { qCDebug(lcButeoMsyncd) << "!!!! Service not supported !!!!"; return false; } QString profileName = label; if (profileName.isEmpty()) { QStringList keys; keys << QString::number(account->id()) << serviceName; serviceProfile->setName(keys); profileName = serviceProfile->name(); } SyncProfile *profile = iProfileManager.syncProfile(profileName); qCDebug(lcButeoMsyncd) << "profileName:" << profileName; if (0 == profile) { qCDebug(lcButeoMsyncd) << "Creating new profile by cloning base profile"; // Create a new sync profile with username profile = serviceProfile->clone(); profile->setName(profileName); profile->setKey(KEY_DISPLAY_NAME, account->displayName()); profile->setKey(KEY_ACCOUNT_ID, QString::number(account->id())); setSyncSchedule(profile, account->id(), true); } if (profile && (true == profile->boolKey(KEY_USE_ACCOUNTS, false))) { profile->setEnabled(account->enabled() && serviceEnabled); iProfileManager.updateProfile(*profile); emit scheduleUpdated(profile->name()); if (profile->isSOCProfile()) { emit enableSOC(profile->name()); } } delete profile; delete serviceProfile; return true; } void AccountsHelper::registerAccountListeners() { FUNCTION_CALL_TRACE(lcButeoTrace); // Populate all enabled accounts list (so that we can listen to changes) QList accountIds = iAccountManager->accountList(); foreach (Accounts::AccountId id, accountIds) { registerAccountListener(id); } } void AccountsHelper::slotSchedulerSettingsChanged(const char *aKey) { FUNCTION_CALL_TRACE(lcButeoTrace); qCDebug(lcButeoMsyncd) << "Key Changed" << QString(aKey); Accounts::Watch *watch = qobject_cast(this->sender()); if (watch && iAcctWatchMap.contains(watch)) { Accounts::AccountId id = iAcctWatchMap.value(watch); QList syncProfiles = getProfilesByAccountId(id); foreach (SyncProfile *syncProfile, syncProfiles) { if (syncProfile) { setSyncSchedule(syncProfile, id); iProfileManager.updateProfile(*syncProfile); emit scheduleUpdated(syncProfile->name()); delete syncProfile; } } } } void AccountsHelper::registerAccountListener(Accounts::AccountId id) { FUNCTION_CALL_TRACE(lcButeoTrace); Accounts::Account *account = iAccountManager->account(id); if (iAccountList.contains(account)) { return; } iAccountList.append(account); // Initialisation and callback for account enabled/disabled syncEnableWithAccount(account); QObject::connect(account, &Accounts::Account::enabledChanged, [this, account] (const QString & serviceName, bool enabled) { qCDebug(lcButeoMsyncd) << "Received account enabled changed signal" << serviceName << enabled << account->displayName(); syncEnableWithAccount(account); }); #ifndef USE_ACCOUNTSHELPER_SCHEDULER_WATCHER qCDebug(lcButeoMsyncd) << "AccountsHelper::registerAccountListener() is disabled! Not listening to scheduler change signals for account:" << id; #else // Account SyncOnChange QList profiles = getProfilesByAccountId(id); foreach (SyncProfile *profile, profiles) { if (profile->isSOCProfile()) { emit enableSOC(profile->name()); } } account->selectService(); account->beginGroup("scheduler"); qCDebug(lcButeoMsyncd) << "Watching Group :" << account->group(); Accounts::Watch *watch = account->watchKey(); account->endGroup(); if (!watch) { qCDebug(lcButeoMsyncd) << "Failed to add watch for acct with id:" << id; return; } iAcctWatchMap[watch] = id; QObject::connect(watch, SIGNAL(notify(const char *)), SLOT(slotSchedulerSettingsChanged(const char *)), Qt::UniqueConnection); #endif } buteo-syncfw-0.11.10/msyncd/AccountsHelper.h000066400000000000000000000063331477124122200206730ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef ACCOUNTSHELPER_H #define ACCOUNTSHELPER_H #include #include #include namespace Buteo { class Profile; class AccountsHelperTest; class ProfileManager; class SyncProfile; /*! \brief Helper Class towards Accounts::Manager and various SSO related * operations. */ class AccountsHelper : public QObject { Q_OBJECT public: /*! \brief Constructor * * \param aProfileManager - reference to Profile Manager Object * \param aParent - Parent object */ AccountsHelper(ProfileManager &aProfileManager, QObject *aParent = NULL); /*! \brief Destructor * */ virtual ~AccountsHelper(); /*! \brief Returns sync profiles that correspond to a given account ID * * \param id - The account ID. * \return A list of sync profiles. The caller should delete the profiles * after use. */ QList getProfilesByAccountId(Accounts::AccountId id); public Q_SLOTS: /*! \brief This method is used to create profiles for a specified * account * \param id Accounts Id */ void createProfileForAccount(Accounts::AccountId id); /*! \brief slot for Accounts::Manager accountRemoved signal * * \param id of the accounts */ void slotAccountRemoved(Accounts::AccountId id); void slotSchedulerSettingsChanged(const char *aKey); Q_SIGNALS: void enableSOC(const QString &aProfileName); void scheduleUpdated(const QString &aProfileName); void removeProfile(QString profileId); void removeScheduledSync(const QString &profileId); private Q_SLOTS: void registerAccountListeners(); private: void syncEnableWithAccount(Accounts::Account *account); bool addProfileForAccount(Accounts::Account *account, const QString &serviceName, bool serviceEnabled, const QString &label = QString()); void setSyncSchedule(SyncProfile *syncProfile, Accounts::AccountId id, bool aCreateNew = false); void registerAccountListener(Accounts::AccountId id); Accounts::Manager *iAccountManager; ProfileManager &iProfileManager; QList iAccountList; QMap iAcctWatchMap; #ifdef SYNCFW_UNIT_TESTS friend class AccountsHelperTest; #endif }; } #endif // ACCOUNTSHELPER_H buteo-syncfw-0.11.10/msyncd/BackgroundSync.cpp000066400000000000000000000244711477124122200212260ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2014 Jolla Ltd. * * Contact: Valerio Valerio * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "LogMacros.h" #include "BackgroundSync.h" #include #include // 24 hours const int MAX_FREQUENCY = 1440; BackgroundSync::BackgroundSync(QObject *aParent) : QObject(aParent) { FUNCTION_CALL_TRACE(lcButeoTrace); } BackgroundSync::~BackgroundSync() { FUNCTION_CALL_TRACE(lcButeoTrace); removeAll(); } void BackgroundSync::removeAll() { FUNCTION_CALL_TRACE(lcButeoTrace); QStringList profNames; QMapIterator iter(iScheduledSyncs); while (iter.hasNext()) { iter.next(); profNames.append(iter.key()); } for (int i = 0; i < profNames.size(); i++) { remove(profNames[i]); } removeAllSwitches(); } bool BackgroundSync::remove(const QString &aProfName) { FUNCTION_CALL_TRACE(lcButeoTrace); removeSwitch(aProfName); if (iScheduledSyncs.contains(aProfName) == false) return false; BActivityStruct &tmp = iScheduledSyncs[aProfName]; tmp.backgroundActivity->stop(); delete tmp.backgroundActivity; iScheduledSyncs.remove(aProfName); return true; } bool BackgroundSync::set(const QString &aProfName, int seconds) { FUNCTION_CALL_TRACE(lcButeoTrace); if (aProfName.isEmpty()) return false; if (iScheduledSyncs.contains(aProfName) == true) { // Can't schedule sync for such long interval removing existent profile if it exists, // new background activity will be added below if ((seconds / 60 > MAX_FREQUENCY)) { remove(aProfName); } else { BActivityStruct &newAct = iScheduledSyncs[aProfName]; BackgroundActivity::Frequency frequency = frequencyFromSeconds(seconds); if (newAct.frequency != frequency) { newAct.backgroundActivity->stop(); newAct.frequency = frequency; newAct.backgroundActivity->setWakeupFrequency(newAct.frequency); newAct.backgroundActivity->wait(); qCDebug(lcButeoMsyncd) << "BackgroundSync::set() Rescheduling for" << aProfName << "with frequency" << (seconds / 60) << "minutes, waiting."; return true; } else { newAct.backgroundActivity->wait(); qCDebug(lcButeoMsyncd) << "BackgroundSync::set() Frequency unchanged for" << aProfName << ", waiting."; return true; //returning 'true' - no immediate sync request to be sent. } } } BActivityStruct &newAct = iScheduledSyncs[aProfName]; newAct.backgroundActivity = new BackgroundActivity(this); newAct.id = newAct.backgroundActivity->id(); connect(newAct.backgroundActivity, SIGNAL(running()), this, SLOT(onBackgroundSyncStarted())); if (seconds / 60 > MAX_FREQUENCY) { newAct.frequency = BackgroundActivity::Range; // 0 newAct.backgroundActivity->wait(seconds); qCDebug(lcButeoMsyncd) << "BackgroundSync::set() profile name =" << aProfName << "without a valid frequency, waiting for" << seconds << "seconds."; } else { newAct.frequency = frequencyFromSeconds(seconds); newAct.backgroundActivity->setWakeupFrequency(newAct.frequency); newAct.backgroundActivity->wait(); qCDebug(lcButeoMsyncd) << "BackgroundSync::set() profile name =" << aProfName << "with frequency " << (seconds / 60) << "minutes, waiting."; } return true; } void BackgroundSync::onBackgroundSyncStarted() { FUNCTION_CALL_TRACE(lcButeoTrace); BackgroundActivity *tempAct = static_cast(sender()); QString profName = getProfNameFromId(tempAct->id()); if (!profName.isEmpty()) { qCDebug(lcButeoMsyncd) << "BackgroundSync started, for profile = " << profName; emit onBackgroundSyncRunning(profName); } else { qCWarning(lcButeoMsyncd) << "BackgroundSync: Error: profile for background activity not found! Stopping background activity."; tempAct->stop(); // but don't delete tempAct to avoid possible crash in later profile cleanup. } } void BackgroundSync::onBackgroundSyncCompleted(QString aProfName) { FUNCTION_CALL_TRACE(lcButeoTrace); qCDebug(lcButeoMsyncd) << "BackgroundSync completed, removing activity, profile name = " << aProfName; remove(aProfName); } QString BackgroundSync::getProfNameFromId(const QString activityId) const { FUNCTION_CALL_TRACE(lcButeoTrace); QMapIterator iter(iScheduledSyncs); while (iter.hasNext()) { iter.next(); const BActivityStruct &tmp = iter.value(); if (tmp.id == activityId) { return iter.key(); break; } } return QString(); } BackgroundActivity::Frequency BackgroundSync::frequencyFromSeconds(int seconds) { // Don't allow frequencies smaller than 5 mins. // In rare cases is possible that seconds is 0 int minutes = seconds / 60; if (minutes <= 5) return BackgroundActivity::FiveMinutes; else if (minutes <= 10) return BackgroundActivity::TenMinutes; else if (minutes <= 15) return BackgroundActivity::FifteenMinutes; else if (minutes <= 30) return BackgroundActivity::ThirtyMinutes; else if (minutes <= 60) return BackgroundActivity::OneHour; else if (minutes <= 2 * 60) return BackgroundActivity::TwoHours; else if (minutes <= 4 * 60) return BackgroundActivity::FourHours; else if (minutes <= 8 * 60) return BackgroundActivity::EightHours; else if (minutes <= 10 * 60) return BackgroundActivity::TenHours; else if (minutes <= 12 * 60) return BackgroundActivity::TwelveHours; else return BackgroundActivity::TwentyFourHours; } // sync switches void BackgroundSync::removeAllSwitches() { FUNCTION_CALL_TRACE(lcButeoTrace); QStringList profNames; QMapIterator iter(iScheduledSwitch); while (iter.hasNext()) { iter.next(); profNames.append(iter.key()); } for (int i = 0; i < profNames.size(); i++) { removeSwitch(profNames[i]); } } bool BackgroundSync::removeSwitch(const QString &aProfName) { FUNCTION_CALL_TRACE(lcButeoTrace); if (iScheduledSwitch.contains(aProfName) == false) return false; BActivitySwitchStruct &tmp = iScheduledSwitch[aProfName]; tmp.backgroundActivity->stop(); delete tmp.backgroundActivity; iScheduledSwitch.remove(aProfName); return true; } bool BackgroundSync::setSwitch(const QString &aProfName, const QDateTime &aSwitchTime) { FUNCTION_CALL_TRACE(lcButeoTrace); if (aProfName.isEmpty()) return false; int switchSecs = QDateTime::currentDateTime().secsTo(aSwitchTime); if (iScheduledSwitch.contains(aProfName) == true) { BActivitySwitchStruct &newSwitch = iScheduledSwitch[aProfName]; if (newSwitch.nextSwitch != aSwitchTime) { // If activity's state was already Waiting, the state doesn't change, nothing happens and // the existing background activity keeps running until the previously set time expires, so we have to stop it. newSwitch.backgroundActivity->stop(); newSwitch.nextSwitch = aSwitchTime; newSwitch.backgroundActivity->wait(switchSecs); qCDebug(lcButeoMsyncd) << "BackgroundSync::setSwitch() Rescheduling switch for" << aProfName << "at" << aSwitchTime.toString() << "(" << switchSecs << "secs ) waiting."; } else { newSwitch.backgroundActivity->wait(switchSecs); qCDebug(lcButeoMsyncd) << "BackgroundSync::setSwitch() Profile" << aProfName << "already with the same switch timer, at" << aSwitchTime.toString() << "(" << switchSecs << "secs ) waiting."; } } else { BActivitySwitchStruct &newSwitch = iScheduledSwitch[aProfName]; newSwitch.backgroundActivity = new BackgroundActivity(this); newSwitch.id = newSwitch.backgroundActivity->id(); connect(newSwitch.backgroundActivity, SIGNAL(running()), this, SLOT(onBackgroundSwitchStarted())); newSwitch.nextSwitch = aSwitchTime; newSwitch.backgroundActivity->wait(switchSecs); qCDebug(lcButeoMsyncd) << "BackgroundSync::setSwitch() Set switch for profile name =" << aProfName << "at" << aSwitchTime.toString() << "(" << switchSecs << "secs ) waiting."; } return true; } void BackgroundSync::onBackgroundSwitchStarted() { FUNCTION_CALL_TRACE(lcButeoTrace); BackgroundActivity *tempAct = static_cast(sender()); QString profName = getProfNameFromSwitchId(tempAct->id()); if (!profName.isEmpty()) { qCDebug(lcButeoMsyncd) << "BackgroundSync: switch timer started, for profile = " << profName; emit onBackgroundSwitchRunning(profName); } else { qCWarning(lcButeoMsyncd) << "BackgroundSync: Error: profile for switch timer not found! Stopping background activity."; tempAct->stop(); // but don't delete tempAct to avoid possible crash in later profile cleanup. } } QString BackgroundSync::getProfNameFromSwitchId(const QString activityId) const { FUNCTION_CALL_TRACE(lcButeoTrace); QMapIterator iter(iScheduledSwitch); while (iter.hasNext()) { iter.next(); const BActivitySwitchStruct &tmp = iter.value(); if (tmp.id == activityId) { return iter.key(); break; } } return QString(); } buteo-syncfw-0.11.10/msyncd/BackgroundSync.h000066400000000000000000000120441477124122200206640ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2014 Jolla Ltd. * * Contact: Valerio Valerio * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef BACKGROUNDSYNC_H #define BACKGROUNDSYNC_H #include #include #include #include class BackgroundActivity; /// \brief BackgroundSync implementation. /// /// This class manages background syncs for different profiles. class BackgroundSync : public QObject { Q_OBJECT /// \brief Internal structure to hold profile-background activity stucture-notifier map struct BActivityStruct { QString id; BackgroundActivity *backgroundActivity; BackgroundActivity::Frequency frequency; }; /// \brief Internal structure to hold profile-background activity switch stucture-notifier map struct BActivitySwitchStruct { QString id; BackgroundActivity *backgroundActivity; QDateTime nextSwitch; }; public: /*! \brief Constructor. * \param aParent Parent object. */ BackgroundSync(QObject *aParent); /** * \brief Destructor */ virtual ~BackgroundSync(); /*! \brief Schedules a background sync for this profile. * * The beat will be generated between minWaitTime and maxWaitTime seconds * \param aProfName Name of the profile. * \param seconds Sync frequency in seconds * \return Success indicator. */ bool set(const QString &aProfName, int seconds); /*! \brief Removes background sync for a profile. * * \param aProfName Name of the profile. */ bool remove(const QString &aProfName); /*! \brief Removes all background syncs for all profiles. */ void removeAll(); // Sync switch /*! \brief Schedules a switch(rush/off-rush) for a background sync running for this profile, the switch should be * added after the background activity. * * \param aProfName Name of the profile. * \param aSwitchTime when the switch will occurs * \return Success indicator. */ bool setSwitch(const QString &aProfName, const QDateTime &aSwitchTime); /*! \brief Removes a switch(rush/off-rush) for a profile. * * \param aProfName Name of the profile. */ bool removeSwitch(const QString &aProfName); signals: /*! \brief This signal will be emitted when a background sync timer for particular profile is triggered. * * \param aProfName Name of the profile for which background sync timer is triggered. */ void onBackgroundSyncRunning(QString aProfName); /*! \brief This signal will be emitted when a switch timer for particular profile is triggered. * * \param aProfName Name of the profile for which switch timer is triggered. */ void onBackgroundSwitchRunning(const QString &aProfName); public slots: /*! \brief Called when background sync is completed * * \param aProfName Name of the profile for which background sync is completed. */ void onBackgroundSyncCompleted(QString aProfName); private slots: /*! \brief Called when background sync timer starts running */ void onBackgroundSyncStarted(); /*! \brief Called when a switch timer starts running */ void onBackgroundSwitchStarted(); private: /*! \brief Finds the name of the profile which uses particular background activity * * \param activityId Id of the background activity * \return name of the profile. */ QString getProfNameFromId(const QString activityId) const; /*! \brief Returns a valid BackgroundActivity frequency * * \param seconds Amounth of time for the frequency * \return BackgroundActivity frequency */ BackgroundActivity::Frequency frequencyFromSeconds(int seconds); // Sync switch /*! \brief Removes all scheduled switches(rush/off-rush) for all profiles. */ void removeAllSwitches(); /*! \brief Finds the name of the profile which uses particular switch activity id * * \param activityId Id of the switch activity * \return name of the profile. */ QString getProfNameFromSwitchId(const QString activityId) const; private: ///Map of structures waiting for background sync QMap iScheduledSyncs; ///Map of switch timer structures QMap iScheduledSwitch; }; #endif // BACKGROUNDSYNC_H buteo-syncfw-0.11.10/msyncd/ClientPluginRunner.cpp000066400000000000000000000146471477124122200221050ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "ClientPluginRunner.h" #include "ClientThread.h" #include "ClientPlugin.h" #include "LogMacros.h" #include "PluginManager.h" using namespace Buteo; // Maximum time in milliseconds for a plugin to finish sync static const unsigned long long MAX_PLUGIN_SYNC_TIME = 1800000; //30 mins ClientPluginRunner::ClientPluginRunner(const QString &aPluginName, SyncProfile *aProfile, PluginManager *aPluginMgr, PluginCbInterface *aPluginCbIf, QObject *aParent) : PluginRunner(PLUGIN_CLIENT, aPluginName, aPluginMgr, aPluginCbIf, aParent), iProfile(aProfile), iPlugin(0), iThread(0) { FUNCTION_CALL_TRACE(lcButeoTrace); } ClientPluginRunner::~ClientPluginRunner() { FUNCTION_CALL_TRACE(lcButeoTrace); stop(); disconnect(); if (iPlugin != 0 && iPluginMgr != 0) { iPluginMgr->destroyClient(iPlugin); iPlugin = 0; } delete iThread; iThread = 0; } bool ClientPluginRunner::init() { FUNCTION_CALL_TRACE(lcButeoTrace); if (iInitialized) return true; if (iPluginMgr == 0 || iPluginCbIf == 0 || iProfile == 0) { qCWarning(lcButeoMsyncd) << "Invalid members, failed to initialize"; return false; } iPlugin = iPluginMgr->createClient(iPluginName, *iProfile, iPluginCbIf); if (iPlugin == 0) { qCWarning(lcButeoMsyncd) << "Failed to create client plug-in:" << iPluginName; return false; } iThread = new ClientThread(); // Pass connectivity state change signal to the plug-in. connect(this, SIGNAL(connectivityStateChanged(Sync::ConnectivityType, bool)), iPlugin, SLOT(connectivityStateChanged(Sync::ConnectivityType, bool))); // Connect signals from the plug-in. connect(iPlugin, SIGNAL(transferProgress(const QString &, Sync::TransferDatabase, Sync::TransferType, const QString &, int)), this, SLOT(onTransferProgress(const QString &, Sync::TransferDatabase, Sync::TransferType, const QString &, int))); connect(iPlugin, &ClientPlugin::error, this, &ClientPluginRunner::onError); connect(iPlugin, SIGNAL(success(const QString &, const QString &)), this, SLOT(onSuccess(const QString &, const QString &))); connect(iPlugin, SIGNAL(accquiredStorage(const QString &)), this, SLOT(onStorageAccquired(const QString &))); connect(iPlugin, SIGNAL(syncProgressDetail(const QString &, int)), this, SLOT(onSyncProgressDetail(const QString &, int))); // Connect signals from the thread. connect(iThread, &ClientThread::initError, this, &ClientPluginRunner::onError); connect(iThread, SIGNAL(finished()), this, SLOT(onThreadExit())); iInitialized = true; return true; } bool ClientPluginRunner::start() { FUNCTION_CALL_TRACE(lcButeoTrace); bool rv = false; if (iInitialized && iThread != 0) { // Set a timer after which the sync session should stop QTimer::singleShot(MAX_PLUGIN_SYNC_TIME, this, SLOT(pluginTimeout())); rv = iThread->startThread(iPlugin); qCDebug(lcButeoMsyncd) << "ClientPluginRunner started thread for plugin:" << iPlugin->getProfileName() << ", returning:" << rv; } return rv; } void ClientPluginRunner::stop() { FUNCTION_CALL_TRACE(lcButeoTrace); if (iThread != 0) { iThread->stopThread(); iThread->wait(); } } void ClientPluginRunner::abort(Sync::SyncStatus aStatus) { FUNCTION_CALL_TRACE(lcButeoTrace); if (iPlugin != 0) { iPlugin->abortSync(aStatus); } } SyncPluginBase *ClientPluginRunner::plugin() { FUNCTION_CALL_TRACE(lcButeoTrace); return iPlugin; } SyncResults ClientPluginRunner::syncResults() { FUNCTION_CALL_TRACE(lcButeoTrace); if (iPlugin != 0) { return iPlugin->getSyncResults(); } else { return SyncResults(); } } bool ClientPluginRunner::cleanUp() { FUNCTION_CALL_TRACE(lcButeoTrace); bool retval = false; if (iPlugin != 0) { retval = iPlugin->cleanUp(); } return retval; } void ClientPluginRunner::onTransferProgress(const QString &aProfileName, Sync::TransferDatabase aDatabase, Sync::TransferType aType, const QString &aMimeType, int aCommittedItems) { FUNCTION_CALL_TRACE(lcButeoTrace); emit transferProgress(aProfileName, aDatabase, aType, aMimeType, aCommittedItems); } void ClientPluginRunner::onError(const QString &aProfileName, const QString &aMessage, SyncResults::MinorCode aErrorCode) { FUNCTION_CALL_TRACE(lcButeoTrace); emit error(aProfileName, aMessage, aErrorCode); stop(); } void ClientPluginRunner::onSuccess(const QString &aProfileName, const QString &aMessage) { FUNCTION_CALL_TRACE(lcButeoTrace); emit success(aProfileName, aMessage); stop(); } void ClientPluginRunner::onStorageAccquired(const QString &aMimeType) { FUNCTION_CALL_TRACE(lcButeoTrace); emit storageAccquired(aMimeType); } void ClientPluginRunner::onSyncProgressDetail(const QString &aProfileName, int aProgressDetail) { FUNCTION_CALL_TRACE(lcButeoTrace); emit syncProgressDetail(aProfileName, aProgressDetail); } void ClientPluginRunner::onThreadExit() { FUNCTION_CALL_TRACE(lcButeoTrace); emit done(); } void ClientPluginRunner::pluginTimeout() { FUNCTION_CALL_TRACE(lcButeoTrace); emit error(iProfile->name(), "Plugin timeout occurred", SyncResults::PLUGIN_TIMEOUT); stop(); } buteo-syncfw-0.11.10/msyncd/ClientPluginRunner.h000066400000000000000000000062431477124122200215430ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef CLIENTPLUGINRUNNER_H #define CLIENTPLUGINRUNNER_H #include "PluginRunner.h" #include namespace Buteo { class ClientPlugin; class ClientThread; class SyncProfile; /*! \brief Class for running client sync plug-ins */ class ClientPluginRunner : public PluginRunner { Q_OBJECT public: /*! \brief Constructor * * @param aPluginName Name of the plug-in to run * @param aProfile Sync profile for the client plug-in. Ownership is NOT * transferred. * @param aPluginMgr PluginManager instance for creating and destroying * plug-ins by name * @param aPluginCbIf Callback interface that the created plug-in can use * @param aParent Parent object */ ClientPluginRunner(const QString &aPluginName, SyncProfile *aProfile, PluginManager *aPluginMgr, PluginCbInterface *aPluginCbIf, QObject *aParent = 0); //! \brief Destructor virtual ~ClientPluginRunner(); //! @see PluginRunner::init virtual bool init(); //! @see PluginRunner::start virtual bool start(); //! @see PluginRunner::stop virtual void stop(); //! @see PluginRunner::abort virtual void abort(Sync::SyncStatus aStatus = Sync::SYNC_ABORTED); //! @see PluginRunner::syncResults virtual SyncResults syncResults(); //! @see PluginRunner::plugin virtual SyncPluginBase *plugin(); //! @see PluginRunner::plugin virtual bool cleanUp(); private slots: // Slots for catching plug-in signals. void onTransferProgress(const QString &aProfileName, Sync::TransferDatabase aDatabase, Sync::TransferType aType, const QString &aMimeType, int aCommittedItems); void onError(const QString &aProfileName, const QString &aMessage, SyncResults::MinorCode aErrorCode); void onSuccess(const QString &aProfileName, const QString &aMessage); void onStorageAccquired(const QString &aMimeType); void onSyncProgressDetail(const QString &aProfileName, int aProgressDetail); // Slot for observing thread exit void onThreadExit(); void pluginTimeout(); private: SyncProfile *iProfile; ClientPlugin *iPlugin; ClientThread *iThread; #ifdef SYNCFW_UNIT_TESTS friend class ClientPluginRunnerTest; #endif }; } #endif // CLIENTPLUGINRUNNER_H buteo-syncfw-0.11.10/msyncd/ClientThread.cpp000066400000000000000000000134231477124122200206530ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "ClientThread.h" #include "ClientPlugin.h" #include "LogMacros.h" #include using namespace Buteo; ClientThread::ClientThread() : iClientPlugin(0), iIdentity(nullptr), iService(nullptr), iSession(nullptr), iRunning(false) { FUNCTION_CALL_TRACE(lcButeoTrace); } ClientThread::~ClientThread() { FUNCTION_CALL_TRACE(lcButeoTrace); if (iSession) { iIdentity->destroySession(iSession); } delete iIdentity; } QString ClientThread::getProfileName() const { FUNCTION_CALL_TRACE(lcButeoTrace); QString profileName; if (iClientPlugin != 0) { profileName = iClientPlugin->getProfileName(); } return profileName; } ClientPlugin *ClientThread::getPlugin() const { FUNCTION_CALL_TRACE(lcButeoTrace); return iClientPlugin; } bool ClientThread::startThread(ClientPlugin *aClientPlugin) { FUNCTION_CALL_TRACE(lcButeoTrace); if (aClientPlugin == 0) return false; { QMutexLocker locker(&iMutex); if (iRunning) { return false; } else { iRunning = true; } } iClientPlugin = aClientPlugin; if (iClientPlugin == 0) { qCCritical(lcButeoMsyncd) << "Client plugin is NULL"; return false; } SyncProfile &profile = iClientPlugin->profile(); const QString prefix("sso-provider="); QString username = profile.key("Username"); if (username.startsWith(prefix)) { // Look up real username/password in SSO first, // before starting sync. This is better done // in the application thread, because this is where // this instance lives. iProvider = username.mid(prefix.size()); qCDebug(lcButeoMsyncd) << "SSO provider::" << iProvider; iService = new SignOn::AuthService(this); connect(iService, SIGNAL(identities(const QList &)), this, SLOT(identities(const QList &))); iService->queryIdentities(); } else { // Move to client thread iClientPlugin->moveToThread(this); start(); } return true; } void ClientThread::stopThread() { FUNCTION_CALL_TRACE(lcButeoTrace); exit(); } void ClientThread::run() { FUNCTION_CALL_TRACE(lcButeoTrace); if (!iClientPlugin->init()) { qCWarning(lcButeoMsyncd) << "Could not initialize client plugin:" << iClientPlugin->getPluginName(); emit initError(getProfileName(), "", SyncResults::PLUGIN_ERROR); return; } if (!iClientPlugin->startSync()) { qCWarning(lcButeoMsyncd) << "Could not start client plugin:" << iClientPlugin->getPluginName(); emit initError(getProfileName(), "", SyncResults::PLUGIN_ERROR); return; } exec(); iSyncResults = iClientPlugin->getSyncResults(); iClientPlugin->uninit(); // Move back to application thread iClientPlugin->moveToThread(QCoreApplication::instance()->thread()); { QMutexLocker locker(&iMutex); iRunning = false; } } SyncResults ClientThread::getSyncResults() { FUNCTION_CALL_TRACE(lcButeoTrace); return iSyncResults; } void ClientThread::identities(const QList &identityList) { FUNCTION_CALL_TRACE(lcButeoTrace); for (int i = 0; i < identityList.size(); ++i) { const SignOn::IdentityInfo &info = identityList.at(i); qCDebug(lcButeoMsyncd) << "Signon identity::" << info.caption(); if (info.caption() == iProvider) { iIdentity = SignOn::Identity::existingIdentity(info.id(), this); // Setup an authentication session using the "password" method iSession = iIdentity->createSession(QLatin1String("password")); connect(iSession, SIGNAL(response(const SignOn::SessionData &)), this, SLOT(identityResponse(const SignOn::SessionData &))); connect(iSession, SIGNAL(error(SignOn::Error)), this, SLOT(identityError(SignOn::Error))); // Get the password! iSession->process(SignOn::SessionData(), QLatin1String("password")); return; } } emit initError(getProfileName(), "credentials not found in SSO", SyncResults::AUTHENTICATION_FAILURE); } void ClientThread::identityResponse(const SignOn::SessionData &sessionData) { FUNCTION_CALL_TRACE(lcButeoTrace); // temporarily set real username/password, then invoke client SyncProfile &profile = iClientPlugin->profile(); qCDebug(lcButeoMsyncd) << "Username::" << sessionData.UserName(); profile.setKey("Username", sessionData.UserName()); profile.setKey("Password", sessionData.Secret()); // delayed starting of thread iClientPlugin->moveToThread(this); start(); } void ClientThread::identityError(SignOn::Error err) { FUNCTION_CALL_TRACE(lcButeoTrace); emit initError(getProfileName(), err.message(), SyncResults::AUTHENTICATION_FAILURE); } buteo-syncfw-0.11.10/msyncd/ClientThread.h000066400000000000000000000071701477124122200203220ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef CLIENTTHREAD_H #define CLIENTTHREAD_H #include #include #include #include "SignOn/AuthService" #include "SignOn/Identity" namespace Buteo { class ClientPlugin; /*! \brief Thread for client plugins * */ class ClientThread : public QThread { Q_OBJECT public: /*! \brief Constructor * */ ClientThread(); /*! \brief Destructor * */ virtual ~ClientThread(); /*! \brief Returns profile that this thread is running * * @return Profile name */ QString getProfileName() const; /*! \brief Returns plugin that this thread is running * * @return Plugin */ ClientPlugin *getPlugin() const; /*! \brief Starts client thread * * @param aClientPlugin Client plug-in to run. Plug-in is owned by the caller, * and must not be deleted while the thread is running. * @return True on success, otherwise false */ bool startThread(ClientPlugin *aClientPlugin); /*! \brief Stops client thread * */ void stopThread(); /*! \brief Returns the results for this particular thread * */ SyncResults getSyncResults(); signals: /*! \brief Emitted when synchronization cannot be started due to an * error in plugin initialization * * @param aProfileName Name of the profile being synchronized * @param aMessage Message data related to error event * @param aErrorCode Error code */ void initError(const QString &aProfileName, const QString &aMessage, SyncResults::MinorCode aErrorCode); protected: /*! \brief overriding method for QThread::run */ virtual void run(); private: ClientPlugin *iClientPlugin; SyncResults iSyncResults; SignOn::Identity *iIdentity; SignOn::AuthService *iService; SignOn::AuthSession *iSession; QString iProvider; bool iRunning; mutable QMutex iMutex; #ifdef SYNCFW_UNIT_TESTS friend class ClientThreadTest; #endif /*! * \brief invokes iClientPlugin->startSync() * * It should be called when profile is ready for use, with * credentials set in the Username/Password keys. It is called * either in run() or, if the Username key starts with the * "sso-provider=" prefix, after retrieving the credentials from * SSO (queryIdentities() -> identities() -> session -> * identityResponse() -> startSync()). * * @return true for success (run thread), else failure (running * thread is no longer necessary) */ bool startSync(); private slots: void identities(const QList &identityList); void identityResponse(const SignOn::SessionData &session); void identityError(SignOn::Error error); }; } #endif // CLIENTTHREAD_H buteo-syncfw-0.11.10/msyncd/IPHeartBeat.cpp000066400000000000000000000100741477124122200203740ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "IPHeartBeat.h" #include #include "LogMacros.h" using namespace Buteo; IPHeartBeat::IPHeartBeat(QObject *aParent) : QObject(aParent) { FUNCTION_CALL_TRACE(lcButeoTrace); } IPHeartBeat::~IPHeartBeat() { FUNCTION_CALL_TRACE(lcButeoTrace); removeAllWaits(); } void IPHeartBeat::removeAllWaits() { FUNCTION_CALL_TRACE(lcButeoTrace); QStringList profNames; QMapIterator iter(iBeatsWaiting); while (iter.hasNext()) { iter.next(); profNames.append(iter.key()); } for (int i = 0; i < profNames.size(); i++) { removeWait(profNames[i]); } } void IPHeartBeat::removeWait(const QString &aProfName) { FUNCTION_CALL_TRACE(lcButeoTrace); if (iBeatsWaiting.contains(aProfName) == false) return; BeatStruct &tmp = iBeatsWaiting[aProfName]; delete tmp.sockNotifier; iphb_discard_wakeups(tmp.iphbHandle); iphb_close(tmp.iphbHandle); iBeatsWaiting.remove(aProfName); } bool IPHeartBeat::getProfNameFromFd(int aSockFd, QString &aProfName) { FUNCTION_CALL_TRACE(lcButeoTrace); bool ret = false; QMapIterator iter(iBeatsWaiting); while (iter.hasNext()) { iter.next(); const BeatStruct &tmp = iter.value(); if (tmp.sockfd == aSockFd) { aProfName = iter.key(); ret = true; break; } } return ret; } bool IPHeartBeat::setHeartBeat(const QString &aProfName, ushort aMinWaitTime, ushort aMaxWaitTime) { FUNCTION_CALL_TRACE(lcButeoTrace); if ((aMinWaitTime > aMaxWaitTime) || aProfName.isEmpty()) return false; qCDebug(lcButeoMsyncd) << "setHeartBeat(), profile name = " << aProfName; if (iBeatsWaiting.contains(aProfName) == true) { qCDebug(lcButeoMsyncd) << "Profile already in waiting... No new beat"; return true; //returing 'true' - no immediate sync request to be sent. } iphb_t iphbHandle = iphb_open(nullptr); if (iphbHandle == 0) { qCDebug(lcButeoMsyncd) << "iphb_open() failed.... No IP heartbeat available"; return false; } int sockfd = iphb_get_fd(iphbHandle); if (sockfd < 0) { qCDebug(lcButeoMsyncd) << "iphb_get_fd() failed.... No IP heartbeat"; iphb_close(iphbHandle); return false; } BeatStruct &newBeat = iBeatsWaiting[aProfName]; newBeat.iphbHandle = iphbHandle; newBeat.sockfd = sockfd; newBeat.sockNotifier = new QSocketNotifier(sockfd, QSocketNotifier::Read, 0); if (iphb_wait(iphbHandle, aMinWaitTime, aMaxWaitTime, 0) == -1) { qCDebug(lcButeoMsyncd) << "iphb_wait() failed .... No IP heartbeat"; removeWait(aProfName); return false; } connect(newBeat.sockNotifier, SIGNAL(activated(int)), this, SLOT(internalBeatTriggered(int))); qCDebug(lcButeoMsyncd) << "IP Heartbeat set for profile"; return true; } void IPHeartBeat::internalBeatTriggered(int aSockFd) { FUNCTION_CALL_TRACE(lcButeoTrace); QString profName; if (getProfNameFromFd(aSockFd, profName) == true) { removeWait(profName); qCDebug(lcButeoMsyncd) << "Emitting IP Heartbeat, profile name = " << profName; emit onHeartBeat(profName); } } buteo-syncfw-0.11.10/msyncd/IPHeartBeat.h000066400000000000000000000063001477124122200200360ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef IPHEARTBEAT_H #define IPHEARTBEAT_H #include #include #include extern "C" { #include "iphbd/libiphb.h" } namespace Buteo { /// \brief IPHeartBeat implementation. /// /// This class manages heart beats for different profiles. class IPHeartBeat : public QObject { Q_OBJECT /// \brief Internal structure to hold profile-iphb stucture-notifier map struct BeatStruct { int sockfd; QSocketNotifier *sockNotifier; iphb_t iphbHandle; }; public: /*! \brief Constructor. * \param aParent Parent object. */ IPHeartBeat(QObject *aParent); /** * \brief Destructor */ virtual ~IPHeartBeat(); /*! \brief Schedules a heartbeat for this profile between minWaitTime and maxWaitTime. * * The beat will be generated between minWaitTime and maxWaitTime seconds * \param aProfName Name of the profile. * \param aMinWaitTime Minimum wait time in seconds. * \param aMaxWaitTime Minimum wait time in seconds. * \return Success indicator. */ bool setHeartBeat(const QString &aProfName, ushort aMinWaitTime, ushort aMaxWaitTime); /*! \brief Removes heart beat waiting for a profile. * * \param aProfName Name of the profile. */ void removeWait(const QString &aProfName); /*! \brief Removes heart beat waiting for all profiles. */ void removeAllWaits(); signals: /*! \brief This signal will be emitted when a heartbeat for particular profile is triggered. * * \param aProfName Name of the profile for which heart beat is triggered. */ void onHeartBeat(QString aProfName); private slots: /*! \brief This signal will be emitted when a socket descriptor gets an event notification. * * \param aSockFd Socket descriptor who got the event. */ void internalBeatTriggered(int aSockFd); private: /*! \brief Finds the name of the profile which uses particular file descriptor * * \param aSockFd Socket descriptor. * \param aProfName name of the profile. * \return true if profile name found, otherwise false */ bool getProfNameFromFd(int aSockFd, QString &aProfName); private: ///Map of structures waiting for heart beat QMap iBeatsWaiting; #ifdef SYNCFW_UNIT_TESTS friend class IPHeartBeatTest; #endif }; } #endif buteo-syncfw-0.11.10/msyncd/PluginRunner.cpp000066400000000000000000000041421477124122200207330ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "PluginRunner.h" #include "LogMacros.h" #include "SyncResults.h" using namespace Buteo; PluginRunner::PluginRunner(PluginType aPluginType, const QString &aPluginName, PluginManager *aPluginMgr, PluginCbInterface *aPluginCbIf, QObject *aParent) : QObject(aParent), iInitialized(false), iPluginMgr(aPluginMgr), iPluginCbIf(aPluginCbIf), iType(aPluginType), iPluginName(aPluginName) { FUNCTION_CALL_TRACE(lcButeoTrace); // register various metatypes used in DBus arguments qRegisterMetaType("SyncResults::MinorCode"); qRegisterMetaType("Sync::SyncStatus"); qRegisterMetaType("Sync::TransferDatabase"); qRegisterMetaType("Sync::TransferType"); qRegisterMetaType("Sync::ConnectivityType"); qRegisterMetaType("QProcess::ProcessError"); qRegisterMetaType("QProcess::ExitStatus"); } PluginRunner::PluginType PluginRunner::pluginType() const { FUNCTION_CALL_TRACE(lcButeoTrace); return iType; } QString PluginRunner::pluginName() const { FUNCTION_CALL_TRACE(lcButeoTrace); return iPluginName; } buteo-syncfw-0.11.10/msyncd/PluginRunner.h000066400000000000000000000121571477124122200204050ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef PLUGINRUNNER_H #define PLUGINRUNNER_H #include #include "SyncCommonDefs.h" #include "SyncPluginBase.h" #include #include namespace Buteo { class PluginManager; class PluginCbInterface; /*! \brief Base class for running sync plug-ins. * * This class hides the details of thread/process handling when sync client * and server plug-ins are run. Specific client and server plug-in runner * classes are derived from this class. */ class PluginRunner : public QObject { Q_OBJECT public: //! Plug-in type: client or server enum PluginType { PLUGIN_CLIENT, PLUGIN_SERVER }; /*! \brief Constructor * * @param aPluginType Type of the plug-in to run * @param aPluginName Name of the plug-in to run * @param aPluginMgr PluginManager instance for creating and destroying * plug-ins by name * @param aPluginCbIf Callback interface that the created plug-in can use * @param aParent Parent object */ PluginRunner(PluginType aPluginType, const QString &aPluginName, PluginManager *aPluginMgr, PluginCbInterface *aPluginCbIf, QObject *aParent = 0); /*! \brief Initializes the plug-in runner * * Creates the plug-in that will be run and a thread or process * for running it. * @return Success indicator */ virtual bool init() = 0; /*! \brief Starts running the plug-in * * @return Success indicator. */ virtual bool start() = 0; /*! \brief Stops running the plug-in * * Returns when the plug-in is stopped. */ virtual void stop() = 0; /*! \brief Aborts running the plug-in * \param Status error. * The plug-in is requested to abort. This function will return when the * abort request is sent, but the plug-in will continue running until * it has gracefully aborted. */ virtual void abort(Sync::SyncStatus aStatus = Sync::SYNC_ABORTED) = 0; /*! \brief Gets the sync results from the plug-in. * * Should be called only after success or error signal is received from * this class. * @return Sync results */ virtual SyncResults syncResults() = 0; /*! \brief Calls the cleanup for the plugin * * The plug-in is requested to clean up. */ virtual bool cleanUp() = 0; /*! \brief Gets the plug-in type * * @return Plug-in type */ PluginType pluginType() const; /*! \brief Gets the plug-in name * * @return Plug-in name */ QString pluginName() const; /*! \brief Gets the plug-in associated with this plug-in runner * * @return Plug-in instance */ virtual SyncPluginBase *plugin() = 0; signals: //! @see SyncPluginBase::transferProgress void transferProgress(const QString &aProfileName, Sync::TransferDatabase aDatabase, Sync::TransferType aType, const QString &aMimeType, int aCommittedItems); //! @see SyncPluginBase::error void error(const QString &aProfileName, const QString &aMessage, SyncResults::MinorCode aErrorCode); //! @see SyncPluginBase::success void success(const QString &aProfileName, const QString &aMessage); //! @see SyncPluginBase::storageAcquired void storageAccquired (const QString &aMimeType); //! @see SyncPluginBase::syncProgressDetail void syncProgressDetail(const QString &aProfileName, int aProgressDetail); /*! \brief Signal sent when the plug-in runner has finished * * Sent when the thread or process running the plug-in has exited */ void done(); //! @see SyncPluginBase::newSession void newSession(const QString &aDestination); //! @see SyncPluginBase::connectivityStateChanged void connectivityStateChanged(Sync::ConnectivityType aType, bool aState); protected: //! Initialization status of the plugin bool iInitialized; //! pointer to an instance of plugin manager PluginManager *iPluginMgr; //! pointer to an instance of synchronizer PluginCbInterface *iPluginCbIf; //! type of the plugin PluginType iType; //! name of the plugin QString iPluginName; private: #ifdef SYNCFW_UNIT_TESTS friend class PluginRunnerTest; #endif }; } #endif // PLUGINRUNNER_H buteo-syncfw-0.11.10/msyncd/ServerActivator.cpp000066400000000000000000000132741477124122200214340ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "ServerActivator.h" #include "ProfileEngineDefs.h" #include "LogMacros.h" using namespace Buteo; ServerActivator::ServerActivator(ProfileManager &aProfileManager, TransportTracker &aTransportTracker, QObject *aParent) : QObject(aParent) , iProfileManager(aProfileManager) , iTransportTracker(aTransportTracker) { FUNCTION_CALL_TRACE(lcButeoTrace); // Get server profiles and transports used by them. QStringList serverProfileNames = iProfileManager.profileNames( Profile::TYPE_SERVER); foreach (QString serverProfileName, serverProfileNames) { Profile *serverProfile = iProfileManager.profile(serverProfileName, Profile::TYPE_SERVER); if (serverProfile != 0) { if (serverProfile->isEnabled()) { ServerData data; data.iTransports = transportsFromProfile(serverProfile); // Normally server plug-in is loaded only when a transport // used by it is available. A server profile can force the // the plug-in to be always loaded be defining the following // key. if (serverProfile->boolKey(KEY_LOAD_WITHOUT_TRANSPORT)) { data.iRefCount++; } iServers.insert(serverProfileName, data); } delete serverProfile; serverProfile = 0; } else { qCWarning(lcButeoMsyncd) << "Failed to load server profile:" << serverProfileName; } } // Add one reference per matching and enabled transport. QList transports; transports.append(Sync::CONNECTIVITY_BT); transports.append(Sync::CONNECTIVITY_USB); transports.append(Sync::CONNECTIVITY_INTERNET); foreach (Sync::ConnectivityType transport, transports) { bool transportEnabled = iTransportTracker.isConnectivityAvailable(transport); foreach (QString serverName, iServers.keys()) { if (transportEnabled && iServers[serverName].iTransports.contains(transport)) { iServers[serverName].iRefCount++; } } } connect(&aTransportTracker, SIGNAL(connectivityStateChanged(Sync::ConnectivityType, bool)), this, SLOT(onConnectivityStateChanged(Sync::ConnectivityType, bool))); } ServerActivator::~ServerActivator() { FUNCTION_CALL_TRACE(lcButeoTrace); } int ServerActivator::addRef(const QString &aServerName, bool emitSignal /*= true*/) { FUNCTION_CALL_TRACE(lcButeoTrace); int refCount = 0; if (iServers.contains(aServerName)) { refCount = ++iServers[aServerName].iRefCount; if (emitSignal && (refCount == 1)) { emit serverEnabled(aServerName); } } else { qCWarning(lcButeoMsyncd) << "Unknown server:" << aServerName; } return refCount; } int ServerActivator::removeRef(const QString &aServerName, bool emitSignal /*= true*/) { FUNCTION_CALL_TRACE(lcButeoTrace); int refCount = 0; if (iServers.contains(aServerName)) { ServerData &data = iServers[aServerName]; if (data.iRefCount > 0) { data.iRefCount--; if (emitSignal && data.iRefCount == 0) { emit serverDisabled(aServerName); } } refCount = data.iRefCount; } else { qCWarning(lcButeoMsyncd) << "Unknown server:" << aServerName; } return refCount; } QStringList ServerActivator::enabledServers() const { FUNCTION_CALL_TRACE(lcButeoTrace); QStringList enabledServers; foreach (QString serverName, iServers.keys()) { if (iServers[serverName].iRefCount > 0) { enabledServers.append(serverName); } } return enabledServers; } void ServerActivator::onConnectivityStateChanged(Sync::ConnectivityType aType, bool aState) { FUNCTION_CALL_TRACE(lcButeoTrace); foreach (QString serverName, iServers.keys()) { if (iServers[serverName].iTransports.contains(aType)) { if (aState) { addRef(serverName); } else { removeRef(serverName); } } } } QList ServerActivator::transportsFromProfile( const Profile *aProfile) { FUNCTION_CALL_TRACE(lcButeoTrace); QList transports; if (aProfile != 0) { if (aProfile->boolKey(KEY_BT_TRANSPORT)) { transports.append(Sync::CONNECTIVITY_BT); } if (aProfile->boolKey(KEY_USB_TRANSPORT)) { transports.append(Sync::CONNECTIVITY_USB); } if (aProfile->boolKey(KEY_INTERNET_TRANSPORT)) { transports.append(Sync::CONNECTIVITY_INTERNET); } } return transports; } buteo-syncfw-0.11.10/msyncd/ServerActivator.h000066400000000000000000000104041477124122200210710ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SERVERACTIVATOR_H_ #define SERVERACTIVATOR_H_ #include "TransportTracker.h" #include "ProfileManager.h" #include #include #include namespace Buteo { /*! \brief Keeps track of which server plug-ins should be enabled * * Finds out from server profiles which transports each server plug-in uses and * concludes the enabled/disabled state based on the transport states. A server * is enabled if any of the transports it uses is enabled or if there are any * references left to the server. Each matching and enabled transport creates * one reference to the server. References can be added and removed also * externally, so that a server won't become disabled if it is still in use but * all its transports are disabled. A signal is emitted when a server is enabled * or disabled. This class does not control the actual server plug-ins, it only * gives information about when a server plug-in should be enabled or disabled. */ class ServerActivator : public QObject { Q_OBJECT public: /*! \brief Constructor * * @param aProfileManager Profile manager for accessing server profiles * @param aTransportTracker Transport tracker * @param aParent Parent object */ ServerActivator(ProfileManager &aProfileManager, TransportTracker &aTransportTracker, QObject *aParent = 0); //! \brief Destructor virtual ~ServerActivator(); /*! \brief Adds a reference to the given server * * If the reference count was zero, emits the serverEnabled signal, if the * emit signal paramater is true * @param aServerName Server profile name * @param emitSignal Controls the emission of the serverEnabled signal * @return Number of references after the addition */ int addRef(const QString &aServerName, bool emitSignal = true); /*! \brief Removes a reference from the given server * * If the reference count reaches zero, emits the serverDisabled signal, if the * emitSignal parameter is true * @param aServerName Server profile name * @param emitSignal Controls the emission of the serverDisabled signal * @return Number of references after the remove */ int removeRef(const QString &aServerName, bool emitSignal = true); /*! \brief Gets the list of enabled server * * @return Server profile names */ QStringList enabledServers() const; signals: /*! \brief Signal emitted when a server should be enabled * * @param aServerName Server profile name */ void serverEnabled(const QString &aServerName); /*! \brief Signal emitted when a server should be disabled * * @param aServerName Server profile name */ void serverDisabled(const QString &aServerName); public slots: /*! \brief Called when transport state changes * * @param aType Connectivity type * @param aState New state */ void onConnectivityStateChanged(Sync::ConnectivityType aType, bool aState); private: QList transportsFromProfile(const Profile *aProfile); ProfileManager &iProfileManager; TransportTracker &iTransportTracker; struct ServerData { ServerData() : iRefCount(0) { } QList iTransports; int iRefCount; }; QMap iServers; #ifdef SYNCFW_UNIT_TESTS friend class ServerActivatorTest; #endif }; } #endif /* SERVERACTIVATOR_H_ */ buteo-syncfw-0.11.10/msyncd/ServerPluginRunner.cpp000066400000000000000000000162371477124122200221320ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "ServerPluginRunner.h" #include "ServerThread.h" #include "ServerActivator.h" #include "ServerPlugin.h" #include "LogMacros.h" #include "PluginManager.h" using namespace Buteo; ServerPluginRunner::ServerPluginRunner(const QString &aPluginName, Profile *aProfile, PluginManager *aPluginMgr, PluginCbInterface *aPluginCbIf, ServerActivator *aServerActivator, QObject *aParent) : PluginRunner(PLUGIN_SERVER, aPluginName, aPluginMgr, aPluginCbIf, aParent) , iProfile(aProfile) , iPlugin(0) , iThread(0) , iServerActivator(aServerActivator) { FUNCTION_CALL_TRACE(lcButeoTrace); } ServerPluginRunner::~ServerPluginRunner() { FUNCTION_CALL_TRACE(lcButeoTrace); stop(); disconnect(); if (iPlugin != 0 && iPluginMgr != 0) { iPluginMgr->destroyServer(iPlugin); iPlugin = 0; } delete iThread; iThread = 0; delete iProfile; iProfile = 0; } bool ServerPluginRunner::init() { FUNCTION_CALL_TRACE(lcButeoTrace); if (iInitialized) return true; if (iPluginMgr == 0 || iPluginCbIf == 0 || iProfile == 0) { qCWarning(lcButeoMsyncd) << "Invalid members, failed to initialize"; return false; } iPlugin = iPluginMgr->createServer(iPluginName, *iProfile, iPluginCbIf); if (iPlugin == 0) { qCWarning(lcButeoMsyncd) << "Failed to create server plug-in:" << iPluginName; return false; } iThread = new ServerThread(); // Pass connectivity state change signal to the plug-in. connect(this, SIGNAL(connectivityStateChanged(Sync::ConnectivityType, bool)), iPlugin, SLOT(connectivityStateChanged(Sync::ConnectivityType, bool))); connect(iPlugin, SIGNAL(newSession(const QString &)), this, SLOT(onNewSession(const QString &))); // Connect signals from the plug-in. connect(iPlugin, SIGNAL(transferProgress(const QString &, Sync::TransferDatabase, Sync::TransferType, const QString &, int)), this, SLOT(onTransferProgress(const QString &, Sync::TransferDatabase, Sync::TransferType, const QString &, int))); connect(iPlugin, &ServerPlugin::error, this, &ServerPluginRunner::onError); connect(iPlugin, SIGNAL(success(const QString &, const QString &)), this, SLOT(onSuccess(const QString &, const QString &))); connect(iPlugin, SIGNAL(accquiredStorage(const QString &)), this, SLOT(onStorageAccquired(const QString &))); connect(iPlugin, SIGNAL(syncProgressDetail(const QString &, int)), this, SIGNAL(syncProgressDetail(const QString &, int))); // Connect signals from the thread. connect(iThread, &ServerThread::initError, this, &ServerPluginRunner::onError); connect(iThread, SIGNAL(finished()), this, SLOT(onThreadExit())); iInitialized = true; return true; } bool ServerPluginRunner::start() { FUNCTION_CALL_TRACE(lcButeoTrace); bool rv = false; if (iInitialized && iThread != 0) { rv = iThread->startThread(iPlugin); qCDebug(lcButeoMsyncd) << "ServerPluginRunner started thread for plugin:" << iPlugin->getProfileName() << ", returning:" << rv; } return rv; } void ServerPluginRunner::stop() { FUNCTION_CALL_TRACE(lcButeoTrace); // Disconnect all signals from this object to the plug-in. disconnect(this, 0, iPlugin, 0); if (iThread != 0) { iThread->stopThread(); iThread->wait(); } } void ServerPluginRunner::abort(Sync::SyncStatus aStatus) { FUNCTION_CALL_TRACE(lcButeoTrace); if (iPlugin != 0) { iPlugin->abortSync(aStatus); } } void ServerPluginRunner::suspend() { FUNCTION_CALL_TRACE(lcButeoTrace); if (iPlugin != 0) { iPlugin->suspend(); } } void ServerPluginRunner::resume() { FUNCTION_CALL_TRACE(lcButeoTrace); if (iPlugin != 0) { iPlugin->resume(); } } SyncPluginBase *ServerPluginRunner::plugin() { FUNCTION_CALL_TRACE(lcButeoTrace); return iPlugin; } SyncResults ServerPluginRunner::syncResults() { FUNCTION_CALL_TRACE(lcButeoTrace); if (iPlugin != 0) { return iPlugin->getSyncResults(); } else { return SyncResults(); } } bool ServerPluginRunner::cleanUp() { FUNCTION_CALL_TRACE(lcButeoTrace); bool retval = false ; if (iPlugin != 0) { retval = iPlugin->cleanUp(); } return retval; } void ServerPluginRunner::onNewSession(const QString &aDestination) { // Add reference to the server plug-in, so that the plug-in // will not be stopped when there is a session running. if (iServerActivator != 0) { iServerActivator->addRef(plugin()->getProfileName()); } emit newSession(aDestination); } void ServerPluginRunner::onTransferProgress(const QString &aProfileName, Sync::TransferDatabase aDatabase, Sync::TransferType aType, const QString &aMimeType, int aCommittedItems) { FUNCTION_CALL_TRACE(lcButeoTrace); emit transferProgress(aProfileName, aDatabase, aType, aMimeType, aCommittedItems); } void ServerPluginRunner::onError(const QString &aProfileName, const QString &aMessage, SyncResults::MinorCode aErrorCode) { FUNCTION_CALL_TRACE(lcButeoTrace); emit error(aProfileName, aMessage, aErrorCode); onSessionDone(); } void ServerPluginRunner::onSuccess(const QString &aProfileName, const QString &aMessage) { FUNCTION_CALL_TRACE(lcButeoTrace); emit success(aProfileName, aMessage); onSessionDone(); } void ServerPluginRunner::onStorageAccquired(const QString &aMimeType) { FUNCTION_CALL_TRACE(lcButeoTrace); emit storageAccquired(aMimeType); } void ServerPluginRunner::onThreadExit() { FUNCTION_CALL_TRACE(lcButeoTrace); emit done(); } void ServerPluginRunner::onSessionDone() { FUNCTION_CALL_TRACE(lcButeoTrace); // Remove reference to the server plug-in. This may result in stopping // the server plug-in, if it doesn't need to be active any more. #if 0 // SyncML Server plugin should not die after one single session // Don't think removing the server reference is useful if (iServerActivator != 0) { iServerActivator->removeRef(plugin()->getProfileName()); } #endif } buteo-syncfw-0.11.10/msyncd/ServerPluginRunner.h000066400000000000000000000066241477124122200215760ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SERVERPLUGINRUNNER_H #define SERVERPLUGINRUNNER_H #include "PluginRunner.h" namespace Buteo { class ServerActivator; class ServerPlugin; class ServerThread; class Profile; /*! \brief Class for running server sync plug-ins */ class ServerPluginRunner : public PluginRunner { Q_OBJECT public: /*! \brief Constructor * * @param aPluginName Name of the plug-in to run * @param aProfile Profile for the server plug-in. Ownership is transferred. * @param aPluginMgr PluginManager instance for creating and destroying * plug-ins by name * @param aPluginCbIf Callback interface that the created plug-in can use * @param aServerActivator Server activator, controls enabled/disabled state * of the server plug-in * @param aParent Parent object */ ServerPluginRunner(const QString &aPluginName, Profile *aProfile, PluginManager *aPluginMgr, PluginCbInterface *aPluginCbIf, ServerActivator *aServerActivator, QObject *aParent = 0); //! \brief Destructor virtual ~ServerPluginRunner(); //! @see PluginRunner::init virtual bool init(); //! @see PluginRunner::start virtual bool start(); //! @see PluginRunner::stop virtual void stop(); //! @see PluginRunner::abort virtual void abort(Sync::SyncStatus aStatus = Sync::SYNC_ABORTED); //! @see PluginRunner::syncResults virtual SyncResults syncResults(); //! @see PluginRunner::plugin virtual SyncPluginBase *plugin(); //! @see PluginRunner::plugin virtual bool cleanUp(); // Suspend a server plug-in void suspend(); // Resume a suspended server plug-in void resume(); private slots: // Slots for catching plug-in signals. void onNewSession(const QString &aDestination); void onTransferProgress(const QString &aProfileName, Sync::TransferDatabase aDatabase, Sync::TransferType aType, const QString &aMimeType, int aCommittedItems); void onStorageAccquired(const QString &aMimeType ); void onError(const QString &aProfileName, const QString &aMessage, SyncResults::MinorCode aErrorCode); void onSuccess(const QString &aProfileName, const QString &aMessage); // Slot for observing thread exit void onThreadExit(); private: void onSessionDone(); Profile *iProfile; ServerPlugin *iPlugin; ServerThread *iThread; ServerActivator *iServerActivator; #ifdef SYNCFW_UNIT_TESTS friend class ServerPluginRunnerTest; #endif }; } #endif // SERVERPLUGINRUNNER_H buteo-syncfw-0.11.10/msyncd/ServerThread.cpp000066400000000000000000000057531477124122200207120ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "ServerThread.h" #include "ServerPlugin.h" #include "LogMacros.h" #include #include using namespace Buteo; ServerThread::ServerThread() : iServerPlugin(0) , iRunning(false) { FUNCTION_CALL_TRACE(lcButeoTrace); } ServerThread::~ServerThread() { FUNCTION_CALL_TRACE(lcButeoTrace); } QString ServerThread::getProfileName() const { FUNCTION_CALL_TRACE(lcButeoTrace); QString profileName; if (iServerPlugin != 0) { profileName = iServerPlugin->getProfileName(); } return profileName; } ServerPlugin *ServerThread::getPlugin() const { FUNCTION_CALL_TRACE(lcButeoTrace); return iServerPlugin; } bool ServerThread::startThread(ServerPlugin *aServerPlugin) { FUNCTION_CALL_TRACE(lcButeoTrace); if (aServerPlugin == 0) return false; { QMutexLocker locker(&iMutex); if (iRunning) { return false; } else { iRunning = true; } } iServerPlugin = aServerPlugin; // Move to server thread iServerPlugin->moveToThread(this); start(); return true; } void ServerThread::stopThread() { FUNCTION_CALL_TRACE(lcButeoTrace); exit(); } void ServerThread::run() { FUNCTION_CALL_TRACE(lcButeoTrace); if (iServerPlugin == 0) { qCCritical(lcButeoMsyncd) << "Server plug-in is NULL"; return; } if (!iServerPlugin->init()) { qCWarning(lcButeoMsyncd) << "Could not initialize server plugin:" << iServerPlugin->getPluginName(); emit initError(iServerPlugin->getProfileName(), "", SyncResults::PLUGIN_ERROR); return; } if (!iServerPlugin->startListen()) { qCWarning(lcButeoMsyncd) << "Could not start server plugin:" << iServerPlugin->getPluginName(); emit initError(iServerPlugin->getProfileName(), "", SyncResults::PLUGIN_ERROR); return; } exec(); iServerPlugin->stopListen(); iServerPlugin->uninit(); // Move back to application thread iServerPlugin->moveToThread(QCoreApplication::instance()->thread()); { QMutexLocker locker(&iMutex); iRunning = false; } } buteo-syncfw-0.11.10/msyncd/ServerThread.h000066400000000000000000000050401477124122200203440ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SERVERTHREAD_H #define SERVERTHREAD_H #include #include #include namespace Buteo { class ServerPlugin; /*! \brief Thread for server plugin * */ class ServerThread : public QThread { Q_OBJECT public: /*! \brief Constructor * */ ServerThread(); /*! \brief Destructor * */ virtual ~ServerThread(); /*! \brief Returns profile that this thread is running * * @return Profile name */ QString getProfileName() const; /*! \brief Returns plugin that this thread is running * * @return Plugin */ ServerPlugin *getPlugin() const; /*! \brief Starts server thread * * @param aServerPlugin Server plug-in to run. The plug-in is owned by the caller * and must not be deleted while the thread is running. * @return True on success, otherwise false */ bool startThread( ServerPlugin *aServerPlugin); /*! \brief Stops server thread * */ void stopThread(); signals: /*! \brief Emitted when synchronization cannot be started due to an * error in plugin initialization * * @param aProfileName Name of the profile being synchronized * @param aMessage Message data related to error event * @param aErrorCode Error code */ void initError(const QString &aProfileName, const QString &aMessage, SyncResults::MinorCode aErrorCode); protected: //! overriding method of QThread::run virtual void run(); private: ServerPlugin *iServerPlugin; bool iRunning; mutable QMutex iMutex; #ifdef SYNCFW_UNIT_TESTS friend class ServerThreadTest; #endif }; } #endif // SERVERTHREAD_H buteo-syncfw-0.11.10/msyncd/StorageBooker.cpp000066400000000000000000000077531477124122200210640ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "StorageBooker.h" #include #include "LogMacros.h" using namespace Buteo; StorageBooker::StorageBooker() : iMutex(QMutex::Recursive) { FUNCTION_CALL_TRACE(lcButeoTrace); } StorageBooker::~StorageBooker() { FUNCTION_CALL_TRACE(lcButeoTrace); } bool StorageBooker::reserveStorage(const QString &aStorageName, const QString &aClientId) { FUNCTION_CALL_TRACE(lcButeoTrace); QMutexLocker locker(&iMutex); bool success = false; if (iStorageMap.contains(aStorageName)) { StorageMapItem item = iStorageMap[aStorageName]; if (aClientId.isEmpty() || aClientId != item.iClientId) { // Already reserved for different client. success = false; } else { // Already reserved for the same client. Increase ref count. item.iRefCount++; iStorageMap[aStorageName] = item; success = true; } } else { // No reservations for the storage. Add a new entry to the storage // reservation map. iStorageMap.insert(aStorageName, aClientId); success = true; } return success; } bool StorageBooker::reserveStorages(const QStringList &aStorageNames, const QString &aClientId) { FUNCTION_CALL_TRACE(lcButeoTrace); QMutexLocker locker(&iMutex); bool success = false; if (storagesAvailable(aStorageNames, aClientId)) { foreach (QString storage, aStorageNames) { reserveStorage(storage, aClientId); } success = true; } else { success = false; } return success; } unsigned StorageBooker::releaseStorage(const QString &aStorageName) { FUNCTION_CALL_TRACE(lcButeoTrace); QMutexLocker locker(&iMutex); unsigned remainingRefCount = 0; if (iStorageMap.contains(aStorageName)) { StorageMapItem item = iStorageMap[aStorageName]; item.iRefCount--; remainingRefCount = item.iRefCount; if (remainingRefCount == 0) { iStorageMap.remove(aStorageName); } else { iStorageMap[aStorageName] = item; } } return remainingRefCount; } void StorageBooker::releaseStorages(const QStringList &aStorageNames) { FUNCTION_CALL_TRACE(lcButeoTrace); QMutexLocker locker(&iMutex); foreach (QString storage, aStorageNames) { releaseStorage(storage); } } bool StorageBooker::isStorageAvailable(const QString &aStorageName, const QString &aClientId) const { FUNCTION_CALL_TRACE(lcButeoTrace); QMutexLocker locker(&iMutex); return (!iStorageMap.contains(aStorageName) || (!aClientId.isEmpty() && (aClientId == iStorageMap[aStorageName].iClientId))); } bool StorageBooker::storagesAvailable(const QStringList &aStorageNames, const QString &aClientId) const { FUNCTION_CALL_TRACE(lcButeoTrace); QMutexLocker locker(&iMutex); foreach (QString storage, aStorageNames) { if (!isStorageAvailable(storage, aClientId)) return false; } return true; } buteo-syncfw-0.11.10/msyncd/StorageBooker.h000066400000000000000000000104431477124122200205170ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef STORAGEBOOKER_H #define STORAGEBOOKER_H #include #include #include #include namespace Buteo { /*! \brief A helper class for managing storage reservations. * */ class StorageBooker { public: //! \brief Constructor StorageBooker(); //! \brief Destructor ~StorageBooker(); /*! \brief Tries to reserve one storage for the given client. * * If the reserve is successfull, the caller must call release when * it does not need the storage anymore. * The same client can call reserve multiple times. Internal reference * counter is increased in that case. For each reserve there must be a * release call later. Other clients calling reserve for the same storage * will fail, while the storage is reserved to some other client. * \param aStorageName Name of the requested storage. * \param aClientId ID of the requesting client. * \return Success indicator. */ bool reserveStorage(const QString &aStorageName, const QString &aClientId = ""); /*! \brief Tries to reserve multiple storages for the given client. * * If the reserve is successfull, the caller must call release for each * storage when it does not need the storages anymore. * The reserve is successfull only if all given storages are available. * If the reserve fails, no storages are reserved. * \param aStorageNames Names of the storages to reserve. * \param aClientId ID of the requesting client. * \return Success indicator. */ bool reserveStorages(const QStringList &aStorageNames, const QString &aClientId = ""); /*! \brief Releases the given storage. * * \param aStorageName Name of the storage to release. * \return Number of remaining references to the storage. If this is zero, * other clients can now reserve the storage. */ unsigned releaseStorage(const QString &aStorageName); /*! \brief Releases the given storages. * * \param aStorageNames Names of the storages to release. */ void releaseStorages(const QStringList &aStorageNames); /*! \brief Checks if the given storage is available for the given client. * * The storage is available if there are no reservations for it or if the * storage is already reserved for the same client. If the storage is * available, it can be reserved for the client by calling reserve. * \param aStorageName Name of the requested storage. * \param aClientId ID of the requesting client. * \return Is the storage available. */ bool isStorageAvailable(const QString &aStorageName, const QString &aClientId = "") const; /*! \brief Checks if the given storages are available for the given client. * * \param aStorageNames Names of the requested storages. * \param aClientId ID of the requesting client. * \return Are the storages available. */ bool storagesAvailable(const QStringList &aStorageNames, const QString &aClientId = "") const; private: struct StorageMapItem { QString iClientId; unsigned iRefCount; StorageMapItem() : iRefCount(0) { }; StorageMapItem(const QString &aClientId) : iClientId(aClientId), iRefCount(1) { }; }; QMap iStorageMap; mutable QMutex iMutex; }; } #endif // STORAGEBOOKER_H buteo-syncfw-0.11.10/msyncd/StorageChangeNotifier.cpp000066400000000000000000000070761477124122200225260ustar00rootroot00000000000000#include "StorageChangeNotifier.h" #include "StorageChangeNotifierPlugin.h" #include "PluginManager.h" #include "LogMacros.h" #include using namespace Buteo; StorageChangeNotifier::StorageChangeNotifier() : iPluginManager(0) { FUNCTION_CALL_TRACE(lcButeoTrace); } StorageChangeNotifier::~StorageChangeNotifier() { FUNCTION_CALL_TRACE(lcButeoTrace); StorageChangeNotifierPlugin *plugin = 0; for (QHash::iterator storageNameItr = iNotifierMap.begin(); storageNameItr != iNotifierMap.end(); ++storageNameItr) { plugin = storageNameItr.value(); if (iPluginManager && plugin) { iPluginManager->destroyStorageChangeNotifier(plugin); } } } void StorageChangeNotifier::loadNotifiers(PluginManager *aPluginManager, const QStringList &aStorageNames) { FUNCTION_CALL_TRACE(lcButeoTrace); StorageChangeNotifierPlugin *plugin = 0; iPluginManager = aPluginManager; for (QStringList::const_iterator storageNameItr = aStorageNames.constBegin(); storageNameItr != aStorageNames.constEnd(); ++storageNameItr) { if (iPluginManager) { plugin = iPluginManager->createStorageChangeNotifier(*storageNameItr); if (plugin) { iNotifierMap[*storageNameItr] = plugin; } } } } bool StorageChangeNotifier::startListen(QStringList &aFailedStorages) { FUNCTION_CALL_TRACE(lcButeoTrace); bool success = true; StorageChangeNotifierPlugin *plugin = 0; if (!iNotifierMap.count()) { success = false; } for (QHash::iterator storageNameItr = iNotifierMap.begin(); storageNameItr != iNotifierMap.end(); ++storageNameItr) { plugin = storageNameItr.value(); if (plugin) { QObject::connect(plugin, SIGNAL(storageChange()), this, SLOT(storageChanged())); plugin->enable(); } else { aFailedStorages << storageNameItr.key(); success = false; } } return success; } void StorageChangeNotifier::stopListen(bool disableAfterNextChange) { FUNCTION_CALL_TRACE(lcButeoTrace); StorageChangeNotifierPlugin *plugin = 0; for (QHash::iterator storageNameItr = iNotifierMap.begin(); storageNameItr != iNotifierMap.end(); ++storageNameItr) { plugin = storageNameItr.value(); if (plugin) { QObject::disconnect(plugin, SIGNAL(storageChange()), this, SLOT(storageChanged())); plugin->disable(disableAfterNextChange); } } } void StorageChangeNotifier::storageChanged() { FUNCTION_CALL_TRACE(lcButeoTrace); StorageChangeNotifierPlugin *plugin = qobject_cast(sender()); if (plugin) { qCDebug(lcButeoMsyncd) << "Change in storage" << plugin->name(); plugin->changesReceived(); emit storageChange(plugin->name()); } } void StorageChangeNotifier::checkForChanges() { FUNCTION_CALL_TRACE(lcButeoTrace); StorageChangeNotifierPlugin *plugin = 0; for (QHash::iterator storageNameItr = iNotifierMap.begin(); storageNameItr != iNotifierMap.end(); ++storageNameItr) { plugin = storageNameItr.value(); if (plugin && plugin->hasChanges()) { plugin->changesReceived(); emit storageChange(plugin->name()); } } } buteo-syncfw-0.11.10/msyncd/StorageChangeNotifier.h000066400000000000000000000043231477124122200221630ustar00rootroot00000000000000#ifndef STORAGECHANGENOTIFIER_H #define STORAGECHANGENOTIFIER_H #include #include namespace Buteo { class StorageChangeNotifierPlugin; class PluginManager; /*! \brief Notifies about changes in storages * that it's asked to monitor */ class StorageChangeNotifier : public QObject { Q_OBJECT public: /*! \brief constructor */ StorageChangeNotifier(); /*! \brief destructor */ ~StorageChangeNotifier(); /*! \brief load all implemented storage change notifier plug-in's * * @param aPluginManager used to load SOC storage plugins * @param aStorageNames list of storages we wan't to monitor */ void loadNotifiers(PluginManager *aPluginManager, const QStringList &aStorageNames); /*! Call this to start monitoring changes in storages * * @param list of storage names which can't be monitored * @return true if we can monitor all storages requested for * false otherwise */ bool startListen(QStringList &aFailedStorages); /*! \brief call this to ignore taking action on * storage changes. Whether there was a change can * be determined by calling hasChanges() on the notifier plug-in * and startListen() can be called again * * @param disableAfterNextChange if set to true, we stop listening * to change notifiers after they've notified about the next change. * This is useful for eg to note that a change did occur during a d2d * sync (during which we disable SOC), but we don't want to get notified * for each batch change, the one notification lets itself be known to us * when we call checkForChanges() */ void stopListen(bool disableAfterNextChange = false); /*! Manually check and notify changes in storage */ void checkForChanges(); private Q_SLOTS: /*! \brief process a storage change notification */ void storageChanged(); Q_SIGNALS: /*! emit this signal if a storage changed * * @param storageName name of the storage that changed */ void storageChange(QString aStorageName); private: QHash iNotifierMap; PluginManager *iPluginManager; }; } #endif buteo-syncfw-0.11.10/msyncd/SyncAlarmInventory.cpp000066400000000000000000000176441477124122200221250ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "SyncAlarmInventory.h" #include "SyncCommonDefs.h" #include #include #include #include #include const QString ALARM_CONNECTION_NAME("alarms"); const int TRIGGER_COUNT = 1; SyncAlarmInventory::SyncAlarmInventory() : iTimer(0) , currentAlarm(0) , triggerCount(TRIGGER_COUNT) { // empty.explicitly call init } bool SyncAlarmInventory::init() { FUNCTION_CALL_TRACE(lcButeoTrace); static unsigned connectionNumber = 0; iConnectionName = ALARM_CONNECTION_NAME + QString::number(connectionNumber++); iDbHandle = QSqlDatabase::addDatabase("QSQLITE", iConnectionName); QString path(Sync::syncConfigDir()); path.append(QDir::separator()).append("alarms.db.sqlite"); path = QDir::toNativeSeparators(path); iDbHandle.setDatabaseName(path); if (!iDbHandle.open()) { qCCritical(lcButeoMsyncd) << "Failed to OPEN DB. SCHEDULING WILL NOT WORK"; return false; } else { qCDebug(lcButeoMsyncd) << "DB Opened Successfully"; } // Create the alarms table const QString createTableQuery("CREATE TABLE IF NOT EXISTS alarms(alarmid INTEGER PRIMARY KEY AUTOINCREMENT, synctime DATETIME)"); QSqlQuery query(createTableQuery, iDbHandle); qCDebug(lcButeoMsyncd) << "SQL Query::" << query.lastQuery(); if (!query.exec()) { qCWarning(lcButeoMsyncd) << "Failed to execute the createTableQuery"; return false; } // Clear any old alarms that may have lingered removeAllAlarms(); // Create the iTimer object iTimer = new QTimer(this); connect(iTimer, SIGNAL(timeout()), this, SLOT(timerTriggered())); currentAlarm = 0; return true; } SyncAlarmInventory::~SyncAlarmInventory() { FUNCTION_CALL_TRACE(lcButeoTrace); iDbHandle.close(); iDbHandle = QSqlDatabase(); QSqlDatabase::removeDatabase(iConnectionName); delete iTimer; iTimer = 0; } int SyncAlarmInventory::addAlarm(QDateTime alarmDate) { FUNCTION_CALL_TRACE(lcButeoTrace); // Check if alarmDate < QDateTime::currentDateTime() if (QDateTime::currentDateTime().secsTo(alarmDate) < 0) { qCWarning(lcButeoMsyncd) << "alarmDate < QDateTime::currentDateTime()"; //Setting with current date time. alarmDate = QDateTime::currentDateTime(); } // Store the alarm int alarmId = 0; if ((alarmId = addAlarmToDb(alarmDate)) == 0) { // Note: Even incase of an already existing profile, false is returned by the query // There is no way to detect a record insertion from an already existing alarm // If unable to add an alarm to db, set the iTimers // for already existing alarms qCWarning(lcButeoMsyncd) << "(alarmId = addAlarmToDb(alarmDate)) == 0"; } // Select all the alarms from the db sorted by alarm time QSqlQuery selectQuery(iDbHandle); if (selectQuery.exec("SELECT alarmid,synctime FROM alarms ORDER BY synctime ASC")) { qCDebug(lcButeoMsyncd) << "SQL Query::" << selectQuery.lastQuery(); if (selectQuery.first()) { int newAlarm = selectQuery.value(0).toInt(); QDateTime alarmTime = selectQuery.value(1).toDateTime(); // If the newAlarm != currentAlarm that is fetched from DB, stop the // previous iTimer if ((currentAlarm != 0) && (newAlarm != currentAlarm)) { if (!iTimer->isActive()) iTimer->stop(); } // This is a new alarm. Set the iTimer for the alarm currentAlarm = newAlarm; QDateTime now = QDateTime::currentDateTime(); int iTimerInterval = 0; if (now < alarmTime) { iTimerInterval = (now.secsTo(alarmTime) / TRIGGER_COUNT) * 1000; // time interval in millisec } qCDebug(lcButeoMsyncd) << "currentAlarm" << currentAlarm << "alarmTime" << alarmTime << "iTimerInterval" << iTimerInterval; triggerCount = TRIGGER_COUNT; iTimer->setInterval(iTimerInterval); iTimer->start(); } } else { qCWarning(lcButeoMsyncd) << "Select Query Execution Failed"; } return alarmId; } bool SyncAlarmInventory::removeAlarm(int alarmId) { FUNCTION_CALL_TRACE(lcButeoTrace); if (alarmId <= 0) return false; deleteAlarmFromDb(alarmId); return true; } void SyncAlarmInventory::removeAllAlarms() { FUNCTION_CALL_TRACE(lcButeoTrace); QSqlQuery deleteAllQuery(QString("DELETE FROM alarms"), iDbHandle); qCDebug(lcButeoMsyncd) << "SQL Query::" << deleteAllQuery.lastQuery(); if (!deleteAllQuery.exec()) { qCWarning(lcButeoMsyncd) << "Failed query to delete all alarms"; } } void SyncAlarmInventory::timerTriggered() { FUNCTION_CALL_TRACE(lcButeoTrace); // Decrement the alarm counter triggerCount--; // Alarm expired. Trigger the alarm and delete it from DB and set the alarm for the next one if (triggerCount == 0) { qCDebug(lcButeoMsyncd) << "Triggering the alarm " << currentAlarm; emit triggerAlarm(currentAlarm); // Delete the alarm from DB if (!deleteAlarmFromDb(currentAlarm)) { } iTimer->stop(); currentAlarm = 0; // Set the new alarm iTimer // Select all the alarms from the db sorted by alarm time QSqlQuery selectQuery(iDbHandle); if (selectQuery.exec("SELECT alarmid,synctime FROM alarms ORDER BY synctime ASC")) { qCDebug(lcButeoMsyncd) << "SQL Query::" << selectQuery.lastQuery(); if (selectQuery.first()) { currentAlarm = selectQuery.value(0).toInt(); QDateTime alarmTime = selectQuery.value(1).toDateTime(); // Set the iTimer for the alarm QDateTime now = QDateTime::currentDateTime(); int iTimerInterval = 0; if (now < alarmTime) { iTimerInterval = (now.secsTo(alarmTime) / TRIGGER_COUNT) * 1000; // time interval in millisec } triggerCount = TRIGGER_COUNT; qCDebug(lcButeoMsyncd) << "Starting timer with interval::" << iTimerInterval; iTimer->setInterval(iTimerInterval); iTimer->start(); } } } } bool SyncAlarmInventory::deleteAlarmFromDb(int alarmId) { FUNCTION_CALL_TRACE(lcButeoTrace); QSqlQuery removeQuery (iDbHandle); removeQuery.prepare("DELETE FROM alarms WHERE alarmid=:alarmid"); removeQuery.bindValue(":alarmid", alarmId); qCDebug(lcButeoMsyncd) << "SQL Query::" << removeQuery.lastQuery(); if (!removeQuery.exec()) return false; else return true; } int SyncAlarmInventory::addAlarmToDb(QDateTime timeStamp) { FUNCTION_CALL_TRACE(lcButeoTrace); QSqlQuery insertQuery(iDbHandle); insertQuery.prepare("INSERT INTO alarms(synctime) VALUES(:synctime)"); insertQuery.bindValue(":synctime", timeStamp); qCDebug(lcButeoMsyncd) << "SQL Query::" << insertQuery.lastQuery(); if (insertQuery.exec()) return insertQuery.lastInsertId().toInt(); else return 0; } buteo-syncfw-0.11.10/msyncd/SyncAlarmInventory.h000066400000000000000000000056651477124122200215720ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCALARMINVENTORY_H #define SYNCALARMINVENTORY_H #include #include #include /*! \brief Class for storing alarms * * This class stores alarms for scheduled synchronizations. The main elements * are the sync time and the alarm id */ class SyncAlarmInventory : public QObject { Q_OBJECT public: /*! The alarm inventory constructor * Always Call init() before using other methods of this class */ SyncAlarmInventory(); /*! The alarm inventory destructor */ ~SyncAlarmInventory(); /*! \brief Creates and Initialize the alarms database. also Creates the timers * Please call this function to make sure the database is initialised properly * @return - status of the initialisation */ bool init(); /*! \brief Method to add an alarm * * @param alarmTime - time of the alarm as QDateTime * @return id of the alarm if alarm was added successfully. else 0 */ int addAlarm(QDateTime alarmTime); /*! Method to remove an alarm * * @param alarmId - id of the alarm to remove * @return status of the remove */ bool removeAlarm(int alarmId); /*! Method to remove all alarms * */ void removeAllAlarms(); signals: /*! \brief Signal triggered when an alarm expired * @param alarmId - id of the alarm that got triggered. * */ void triggerAlarm(int alarmId); private: /* Deletes the alarm from DB */ bool deleteAlarmFromDb(int alarmName); /* Method to add an alarm to the database */ int addAlarmToDb(QDateTime timeStamp); /* Method to fetch the database handle */ QSqlDatabase *getDbHandle(); /* Timer object to keep tracke of alarm timers */ QTimer *iTimer; /* Current alarm that is under work */ int currentAlarm; /* Number of times that the alarm triggers */ int triggerCount; /* Database handle */ QSqlDatabase iDbHandle; /* Database connection name */ QString iConnectionName; private slots: /*! Slot used whenever the timer object expires */ void timerTriggered(); }; #endif buteo-syncfw-0.11.10/msyncd/SyncBackup.cpp000066400000000000000000000072641477124122200203550ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "SyncBackup.h" #include "SyncBackupAdaptor.h" #include "LogMacros.h" #include "UnitTest.h" #include using namespace Buteo; static const char *DBUS_BACKUP_OBJECT = "/backup"; SyncBackup::SyncBackup() : iBackupRestore(false) , iReply(0) , iAdaptor(0) { FUNCTION_CALL_TRACE(lcButeoTrace); iAdaptor = new SyncBackupAdaptor(this); QDBusConnection dbus = QDBusConnection::sessionBus(); if (dbus.registerObject(DBUS_BACKUP_OBJECT, this)) { qCDebug(lcButeoMsyncd) << "Registered sync backup to D-Bus"; } else { qCCritical(lcButeoMsyncd) << "Failed to register sync backup to D-Bus"; Q_ASSERT(false); } } SyncBackup::~SyncBackup() { FUNCTION_CALL_TRACE(lcButeoTrace); iBackupRestore = false; //Unregister from D-Bus. QDBusConnection dbus = QDBusConnection::sessionBus(); dbus.unregisterObject(DBUS_BACKUP_OBJECT); delete iAdaptor; iAdaptor = 0; qCDebug(lcButeoMsyncd) << "Unregistered backup from D-Bus"; } void SyncBackup::sendDelayReply (const QDBusMessage &message) { FUNCTION_CALL_TRACE(lcButeoTrace); if (SYNCFW_UNIT_TESTS_RUNTIME) return; // coverity[unreachable] //Suppressing false positives with code annotations message.setDelayedReply(true); if (!iReply) iReply = new QDBusMessage; *iReply = message.createReply(); } void SyncBackup::sendReply(uchar aResult) { FUNCTION_CALL_TRACE(lcButeoTrace); if (SYNCFW_UNIT_TESTS_RUNTIME) return ; // coverity[unreachable] //Suppressing false positives with code annotations if (iReply) { qCDebug(lcButeoMsyncd) << "Send Reply"; QList arguments; QVariant vt = QVariant::fromValue((uchar)aResult); arguments.append(vt); iReply->setArguments(arguments); QDBusConnection::sessionBus().send(*iReply); delete iReply; iReply = 0; } } uchar SyncBackup::backupStarts(const QDBusMessage &message) { FUNCTION_CALL_TRACE(lcButeoTrace); iBackupRestore = true; sendDelayReply(message); emit startBackup(); return 0; } uchar SyncBackup::backupFinished(const QDBusMessage &message) { FUNCTION_CALL_TRACE(lcButeoTrace); Q_UNUSED (message); iBackupRestore = false; sendDelayReply(message); emit backupDone(); return 0; } uchar SyncBackup::restoreStarts(const QDBusMessage &message) { FUNCTION_CALL_TRACE(lcButeoTrace); iBackupRestore = true; sendDelayReply(message); emit startRestore(); return 0; } uchar SyncBackup::restoreFinished(const QDBusMessage &message) { FUNCTION_CALL_TRACE(lcButeoTrace); Q_UNUSED (message); iBackupRestore = false; sendDelayReply(message); emit restoreDone(); return 0; } bool SyncBackup::getBackUpRestoreState() { FUNCTION_CALL_TRACE(lcButeoTrace); return iBackupRestore; } buteo-syncfw-0.11.10/msyncd/SyncBackup.h000066400000000000000000000051651477124122200200200ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCBACKUP_H #define SYNCBACKUP_H #include "SyncBackupProxy.h" #include "SyncBackupAdaptor.h" namespace Buteo { /*! \brief Handles Sync requirements towards Backup * * This class communicates with the Backup daemon for Backup state before * starting a sync. */ class SyncBackup : public SyncBackupProxy // Derived from QObject { Q_OBJECT public: /*! * \brief Default Constructor */ SyncBackup(); /*! * \brief Destructor */ ~SyncBackup(); /*! * \brief Requests the current state og backup/restore operation. */ bool getBackUpRestoreState(); /*! \brief Reply to backup framework, result of o/p * @param aResult - result */ void sendReply (uchar aResult); public slots: // From backup framework ... /*! \brief Called by backup framework when backup starts * @param message - result * @return uchar */ uchar backupStarts(const QDBusMessage &message); /*! Called by backup framework when backup is completed * @param message - result * @return uchar */ uchar backupFinished(const QDBusMessage &message); /*! Called by backup framework when it starts to restore a backup. * @param message - result * @return uchar */ uchar restoreStarts(const QDBusMessage &message); /*! Called by backup framework when backup ie restored * @param message - result * @return uchar */ uchar restoreFinished(const QDBusMessage &message); private: bool iBackupRestore; QDBusMessage *iReply; SyncBackupAdaptor *iAdaptor; // Reply to backup dbus framework that response will // be delayed void sendDelayReply(const QDBusMessage &message); #ifdef SYNCFW_UNIT_TESTS friend class SyncBackupTest; #endif }; } #endif // SYNCBACKUP_H buteo-syncfw-0.11.10/msyncd/SyncBackupAdaptor.cpp000066400000000000000000000060441477124122200216630ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ /* * This file was generated by qdbusxml2cpp version 0.7 * Command line was: qdbusxml2cpp -a SyncBackupAdaptor -c SyncBackupAdaptor com.nokia.syncbackup.xml * * qdbusxml2cpp is Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * This is an auto-generated file. * Do not edit! All changes made to it will be lost. */ #include "SyncBackupAdaptor.h" #include #include #include #include #include #include #include using namespace Buteo; /* * Implementation of adaptor class SyncBackupAdaptor */ SyncBackupAdaptor::SyncBackupAdaptor(QObject *parent) : QDBusAbstractAdaptor(parent) { // constructor setAutoRelaySignals(true); } SyncBackupAdaptor::~SyncBackupAdaptor() { // destructor } uchar SyncBackupAdaptor::backupFinished(const QDBusMessage &message) { // handle method call com.nokia.backupclient.backupFinished uchar out0; QMetaObject::invokeMethod(parent(), "backupFinished", Q_RETURN_ARG(uchar, out0), Q_ARG (QDBusMessage, message)); return out0; } uchar SyncBackupAdaptor::backupStarts(const QDBusMessage &message) { // handle method call com.nokia.backupclient.backupStarts uchar out0; QMetaObject::invokeMethod(parent(), "backupStarts", Q_RETURN_ARG(uchar, out0), Q_ARG (QDBusMessage, message)); return out0; } bool SyncBackupAdaptor::getBackUpRestoreState() { // handle method call com.nokia.backupclient.getBackUpRestoreState bool out0; QMetaObject::invokeMethod(parent(), "getBackUpRestoreState", Q_RETURN_ARG(bool, out0)); return out0; } uchar SyncBackupAdaptor::restoreFinished(const QDBusMessage &message) { // handle method call com.nokia.backupclient.restoreFinished uchar out0; QMetaObject::invokeMethod(parent(), "restoreFinished", Q_RETURN_ARG(uchar, out0), Q_ARG (QDBusMessage, message)); return out0; } uchar SyncBackupAdaptor::restoreStarts(const QDBusMessage &message) { // handle method call com.nokia.backupclient.restoreStarts uchar out0; QMetaObject::invokeMethod(parent(), "restoreStarts", Q_RETURN_ARG(uchar, out0), Q_ARG (QDBusMessage, message)); return out0; } buteo-syncfw-0.11.10/msyncd/SyncBackupAdaptor.h000066400000000000000000000074651477124122200213400ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ /* * This file was generated by qdbusxml2cpp version 0.7 * Command line was: qdbusxml2cpp -a SyncBackupAdaptor -c SyncBackupAdaptor com.nokia.syncbackup.xml * * qdbusxml2cpp is Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * This is an auto-generated file. * This file may have been hand-edited. Look for HAND-EDIT comments * before re-generating it. */ #ifndef SYNCBACKUPADAPTOR_H_1277973475 #define SYNCBACKUPADAPTOR_H_1277973475 #include #include class QByteArray; template class QList; template class QMap; class QString; class QStringList; class QVariant; namespace Buteo { /*! * \brief Adaptor class for interface com.nokia.backupclient */ class SyncBackupAdaptor: public QDBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "com.nokia.backupclient") Q_CLASSINFO("D-Bus Introspection", "" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" ""); public: //! \see SyncBackup::SyncBackup() SyncBackupAdaptor(QObject *parent); //! \see ~SyncBackup::SyncBackup() virtual ~SyncBackupAdaptor(); public: // PROPERTIES public Q_SLOTS: // METHODS //! \see ~SyncBackup::backupFinished() uchar backupFinished(const QDBusMessage &message); //! \see ~SyncBackup::backupStarts() uchar backupStarts(const QDBusMessage &message); //! \see ~SyncBackup::getBackUpRestoreState() bool getBackUpRestoreState(); //! \see ~SyncBackup::restoreFinished uchar restoreFinished(const QDBusMessage &message); //! \see ~SyncBackup::restoreStarts uchar restoreStarts(const QDBusMessage &message); Q_SIGNALS: // SIGNALS //! \see ~SyncBackup::backupDone void backupDone(); //! \see ~SyncBackup::backupInProgress void backupInProgress(); //! \see ~SyncBackup::restoreDone void restoreDone(); //! \see ~SyncBackup::restoreInProgress void restoreInProgress(); }; } #endif buteo-syncfw-0.11.10/msyncd/SyncBackupProxy.h000066400000000000000000000063711477124122200210620ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCBACKUPPROXY_H #define SYNCBACKUPPROXY_H #include #include #include namespace Buteo { /*! * \brief Defines a D-Bus backup proxy for the backupclient * * A XML file describing the interface can be generated from this class using * qdbuscpp2xml tool. This XML file can then be used to generate interface * adaptor and proxy classes using qdbusxml2cpp tool. */ class SyncBackupProxy : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "com.nokia.backupclient") public: signals: /*! \brief Notifies about completion of backup. * * This signal is sent when the backup is completed */ void backupDone(); /*! \brief Notifies about starting of backup. * * This signal is sent when the backup is started */ void startBackup(); /*! \brief Notifies about completion of restore opertaion. * * This signal is sent when the backup is completed */ void restoreDone(); /*! \brief Notifies about starting of restore operation. * * This signal is sent when the restore is started/in progress */ void startRestore(); public slots: /*! * \brief Sets the required params and stops the servers and any running sync * sessions. * * This function must be called when backup is initiated, * @param message Received dbus message */ virtual uchar backupStarts (const QDBusMessage &message) = 0; /*! * \brief Sets the required params and starts the servers. * * This function must be called when backup is completed. * @param message Received dbus message */ virtual uchar backupFinished (const QDBusMessage &message) = 0; /*! * \brief Sets the required params and stops the servers and any running sync * sessions. * * This function must be called when restore is initiated, * @param message Received dbus message */ virtual uchar restoreStarts (const QDBusMessage &message) = 0; /*! * \brief Sets the required params and starts the servers. * * This function must be called when restore is completed. * @param message Received dbus message */ virtual uchar restoreFinished (const QDBusMessage &message) = 0; /*! * \brief Requests the current state og backup/restore operation. */ virtual bool getBackUpRestoreState() = 0; }; } #endif // SYNCBACKUPPROXY_H buteo-syncfw-0.11.10/msyncd/SyncDBusAdaptor.cpp000066400000000000000000000171501477124122200213130ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2015 Jolla Ltd. * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "SyncDBusAdaptor.h" #include "synchronizer.h" #include #include #include #include #include #include #include using namespace Buteo; /* * Implementation of adaptor class SyncDBusAdaptor */ SyncDBusAdaptor::SyncDBusAdaptor(QObject *parent) : QDBusAbstractAdaptor(parent) { // constructor setAutoRelaySignals(true); } SyncDBusAdaptor::~SyncDBusAdaptor() { // destructor } void SyncDBusAdaptor::abortSync(const QString &aProfileId) { // handle method call com.meego.msyncd.abortSync QMetaObject::invokeMethod(parent(), "abortSync", Q_ARG(QString, aProfileId)); } QStringList SyncDBusAdaptor::allVisibleSyncProfiles() { // handle method call com.meego.msyncd.allVisibleSyncProfiles QStringList out0; QMetaObject::invokeMethod(parent(), "allVisibleSyncProfiles", Q_RETURN_ARG(QStringList, out0)); return out0; } bool SyncDBusAdaptor::getBackUpRestoreState() { // handle method call com.meego.msyncd.getBackUpRestoreState bool out0; QMetaObject::invokeMethod(parent(), "getBackUpRestoreState", Q_RETURN_ARG(bool, out0)); return out0; } QString SyncDBusAdaptor::getLastSyncResult(const QString &aProfileId) { // handle method call com.meego.msyncd.getLastSyncResult QString out0; QMetaObject::invokeMethod(parent(), "getLastSyncResult", Q_RETURN_ARG(QString, out0), Q_ARG(QString, aProfileId)); return out0; } bool SyncDBusAdaptor::isConnectivityAvailable(int connectivityType) { // handle method call com.meego.msyncd.isConnectivityAvailable bool out0; QMetaObject::invokeMethod(parent(), "isConnectivityAvailable", Q_RETURN_ARG(bool, out0), Q_ARG(int, connectivityType)); return out0; } void SyncDBusAdaptor::releaseStorages(const QStringList &aStorageNames) { // handle method call com.meego.msyncd.releaseStorages QMetaObject::invokeMethod(parent(), "releaseStorages", Q_ARG(QStringList, aStorageNames)); } bool SyncDBusAdaptor::removeProfile(const QString &aProfileId) { // handle method call com.meego.msyncd.removeProfile bool out0; QMetaObject::invokeMethod(parent(), "removeProfile", Q_RETURN_ARG(bool, out0), Q_ARG(QString, aProfileId)); return out0; } bool SyncDBusAdaptor::requestStorages(const QStringList &aStorageNames) { // handle method call com.meego.msyncd.requestStorages bool out0; QMetaObject::invokeMethod(parent(), "requestStorages", Q_RETURN_ARG(bool, out0), Q_ARG(QStringList, aStorageNames)); return out0; } QStringList SyncDBusAdaptor::runningSyncs() { // handle method call com.meego.msyncd.runningSyncs QStringList out0; QMetaObject::invokeMethod(parent(), "runningSyncs", Q_RETURN_ARG(QStringList, out0)); return out0; } bool SyncDBusAdaptor::saveSyncResults(const QString &aProfileId, const QString &aSyncResults) { // handle method call com.meego.msyncd.saveSyncResults bool out0; QMetaObject::invokeMethod(parent(), "saveSyncResults", Q_RETURN_ARG(bool, out0), Q_ARG(QString, aProfileId), Q_ARG(QString, aSyncResults)); return out0; } bool SyncDBusAdaptor::setSyncSchedule(const QString &aProfileId, const QString &aScheduleAsXml) { // handle method call com.meego.msyncd.setSyncSchedule bool out0; QMetaObject::invokeMethod(parent(), "setSyncSchedule", Q_RETURN_ARG(bool, out0), Q_ARG(QString, aProfileId), Q_ARG(QString, aScheduleAsXml)); return out0; } void SyncDBusAdaptor::start(uint aAccountId) { // handle method call com.meego.msyncd.start QMetaObject::invokeMethod(parent(), "start", Q_ARG(uint, aAccountId)); } bool SyncDBusAdaptor::startSync(const QString &aProfileId) { // handle method call com.meego.msyncd.startSync bool out0; QMetaObject::invokeMethod(parent(), "startSync", Q_RETURN_ARG(bool, out0), Q_ARG(QString, aProfileId)); return out0; } int SyncDBusAdaptor::status(uint aAccountId, int &aFailedReason, qlonglong &aPrevSyncTime, qlonglong &aNextSyncTime) { // handle method call com.meego.msyncd.status return static_cast(parent())->status(aAccountId, aFailedReason, aPrevSyncTime, aNextSyncTime); } void SyncDBusAdaptor::stop(uint aAccountId) { // handle method call com.meego.msyncd.stop QMetaObject::invokeMethod(parent(), "stop", Q_ARG(uint, aAccountId)); } QString SyncDBusAdaptor::syncProfile(const QString &aProfileId) { // handle method call com.meego.msyncd.syncProfile QString out0; QMetaObject::invokeMethod(parent(), "syncProfile", Q_RETURN_ARG(QString, out0), Q_ARG(QString, aProfileId)); return out0; } QStringList SyncDBusAdaptor::syncProfilesByKey(const QString &aKey, const QString &aValue) { // handle method call com.meego.msyncd.syncProfilesByKey QStringList out0; QMetaObject::invokeMethod(parent(), "syncProfilesByKey", Q_RETURN_ARG(QStringList, out0), Q_ARG(QString, aKey), Q_ARG(QString, aValue)); return out0; } QStringList SyncDBusAdaptor::syncProfilesByType(const QString &aType) { // handle method call com.meego.msyncd.syncProfilesByType QStringList out0; QMetaObject::invokeMethod(parent(), "syncProfilesByType", Q_RETURN_ARG(QStringList, out0), Q_ARG(QString, aType)); return out0; } QStringList SyncDBusAdaptor::profilesByType(const QString &aType) { // handle method call com.meego.msyncd.profilesByType QStringList out0; QMetaObject::invokeMethod(parent(), "profilesByType", Q_RETURN_ARG(QStringList, out0), Q_ARG(QString, aType)); return out0; } QList SyncDBusAdaptor::syncingAccounts() { // handle method call com.meego.msyncd.syncingAccounts QList out0; QMetaObject::invokeMethod(parent(), "syncingAccounts", Q_RETURN_ARG(QList, out0)); return out0; } bool SyncDBusAdaptor::updateProfile(const QString &aProfileAsXml) { // handle method call com.meego.msyncd.updateProfile bool out0; QMetaObject::invokeMethod(parent(), "updateProfile", Q_RETURN_ARG(bool, out0), Q_ARG(QString, aProfileAsXml)); return out0; } void SyncDBusAdaptor::isSyncedExternally(uint aAccountId, const QString aClientProfileName) { // handle method call com.meego.msyncd.isSyncedExternally QMetaObject::invokeMethod(parent(), "isSyncedExternally", Q_ARG(uint, aAccountId), Q_ARG(QString, aClientProfileName)); } QString SyncDBusAdaptor::createSyncProfileForAccount(uint aAccountId) { // handle method call com.meego.msyncd.createSyncProfileForAccount QString out0; QMetaObject::invokeMethod(parent(), "createSyncProfileForAccount", Q_RETURN_ARG(QString, out0), Q_ARG(uint, aAccountId)); return out0; } buteo-syncfw-0.11.10/msyncd/SyncDBusAdaptor.h000066400000000000000000000302061477124122200207550ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2015 Jolla Ltd. * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCDBUSADAPTOR_H_1383642656 #define SYNCDBUSADAPTOR_H_1383642656 #include #include class QByteArray; template class QList; template class QMap; class QString; class QStringList; class QVariant; /* * Adaptor class for interface com.meego.msyncd */ class SyncDBusAdaptor: public QDBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "com.meego.msyncd") Q_CLASSINFO("D-Bus Introspection", "" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \" name=\"com.trolltech.QtDBus.QtTypeName.Out0\"/>\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "") public: SyncDBusAdaptor(QObject *parent); virtual ~SyncDBusAdaptor(); public: // PROPERTIES public Q_SLOTS: // METHODS Q_NOREPLY void abortSync(const QString &aProfileId); QStringList allVisibleSyncProfiles(); bool getBackUpRestoreState(); QString getLastSyncResult(const QString &aProfileId); bool isConnectivityAvailable(int connectivityType); Q_NOREPLY void releaseStorages(const QStringList &aStorageNames); bool removeProfile(const QString &aProfileId); bool requestStorages(const QStringList &aStorageNames); QStringList runningSyncs(); bool saveSyncResults(const QString &aProfileId, const QString &aSyncResults); bool setSyncSchedule(const QString &aProfileId, const QString &aScheduleAsXml); Q_NOREPLY void start(uint aAccountId); bool startSync(const QString &aProfileId); int status(uint aAccountId, int &aFailedReason, qlonglong &aPrevSyncTime, qlonglong &aNextSyncTime); Q_NOREPLY void stop(uint aAccountId); QString syncProfile(const QString &aProfileId); QStringList syncProfilesByKey(const QString &aKey, const QString &aValue); QStringList syncProfilesByType(const QString &aType); QStringList profilesByType(const QString &aType); QList syncingAccounts(); bool updateProfile(const QString &aProfileAsXml); Q_NOREPLY void isSyncedExternally(uint aAccountId, const QString aClientProfileName); QString createSyncProfileForAccount(uint aAccountId); Q_SIGNALS: // SIGNALS void backupDone(); void backupInProgress(); void restoreDone(); void restoreInProgress(); void resultsAvailable(const QString &aProfileName, const QString &aResultsAsXml); void signalProfileChanged(const QString &aProfileName, int aChangeType, const QString &aProfileAsXml); void statusChanged(uint aAccountId, int aNewStatus, int aFailedReason, qlonglong aPrevSyncTime, qlonglong aNextSyncTime); void syncStatus(const QString &aProfileName, int aStatus, const QString &aMessage, int aMoreDetails); void transferProgress(const QString &aProfileName, int aTransferDatabase, int aTransferType, const QString &aMimeType, int aCommittedItems); void syncedExternallyStatus(uint aAccountId, const QString &aClientProfileName, bool aState); }; #endif buteo-syncfw-0.11.10/msyncd/SyncDBusInterface.h000066400000000000000000000370351477124122200212720ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2015 Jolla Ltd. * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCDBUSINTERFACE_H #define SYNCDBUSINTERFACE_H #include #include #include #include namespace Buteo { /*! * \brief Defines a D-Bus interface for the sync daemon. * * A XML file describing the interface can be generated from this class using * qdbuscpp2xml tool. This XML file can then be used to generate interface * adaptor and proxy classes using qdbusxml2cpp tool. */ class SyncDBusInterface : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "com.meego.msyncd") public: signals: /*! * \brief Notifies about a change in synchronization status. * * \param aProfileName Name of the profile used in the sync session whose * status has changed. * \param aStatus The new status. One of the following: * 0 (QUEUED): Sync request has been queued or was already in the * queue when sync start was requested. * 1 (STARTED): Sync session has been started. * 2 (PROGRESS): Sync session is progressing. * 3 (ERROR): Sync session has encountered an error and has been stopped, * or the session could not be started at all. * 4 (DONE): Sync session was successfully completed. * 5 (ABORTED): Sync session was aborted. * Statuses 3-5 are final, no more status changes will be sent from the * same sync session. * \param aMessage A message describing the status change in detail. This * can for example be shown to the user or written to a log * \param aMoreDetails * When aStatus is ERROR, this parameter contains a specific error code. * When aStatus is PROGRESS, this parameter contains more details about the progress */ void syncStatus(QString aProfileName, int aStatus, QString aMessage, int aMoreDetails); /*! \brief Notifies about progress in transferring items * * \param aProfileName Name of the profile where progress has occurred * \param aTransferDatabase Database to which transfer was made. One of the following: * 0 (LOCAL_DATABASE): Transfer was made from remote database to local database * 1 (REMOTE_DATABASE): Transfer was made from local database to remote database * \param aTransferType Type of transfer that was made. One of the following: * 0 (ADDITION): Addition was made to database * 1 (MODIFICATION): Modification was made to database * 2 (DELETION): Deletion was made to database * 3 (ERROR): Addition/Modification/Deletion was attempted, but it failed * \param aMimeType Mime type of the processed item * \param aCommittedItems No. of Items committed for this operation */ void transferProgress(QString aProfileName, int aTransferDatabase, int aTransferType, QString aMimeType, int aCommittedItems); /*! \brief Notifies about a change in profile. * * This signal is sent when the profile data is modified or when a profile * is added or deleted in msyncd. * \param aProfileName Name of the changed profile. * \param aChangeType * 0 (ADDITION): Profile was added. * 1 (MODIFICATION): Profile was modified. * 2 (DELETION): Profile was deleted. * \param aProfileAsXml Updated Profile Object is sent as xml * */ void signalProfileChanged(QString aProfileName, int aChangeType, QString aProfileAsXml); /*! \brief Notifies about Backup start. * * This signal is sent when the backup framework is backing the sync related * data */ void backupInProgress (); /*! \brief Notifies about Backup done. * * This signal is sent when the backup framework has completed backing the sync related * data. */ void backupDone(); /*! \brief Notifies about Restore start. * * This signal is sent when the backup framework is restoring the sync related * data */ void restoreInProgress(); /*! \brief Notifies about Restore Done. * * This signal is sent when the backup framework has restored the sync related * data */ void restoreDone(); /*! \brief Notifies about the availability of Results for a recent sync * * This signal is sent when the results are available for the last sync * only recent results ( SyncResults object) are sent as xml. * \param aProfileName Name of the profile for which results are available * \param aResultsAsXml results as an xml object */ void resultsAvailable(QString aProfileName, QString aResultsAsXml); /*! \brief Notifies sync status change for a set of account Ids * * This signal is sent when the status of a sync for a particular * account ID changes state Upon receiving this signal, the client is * expected to call the status method to check whether sync is * running/stopped for this account ID * * \param aAccountId The account IDs that changed state * \param aNewStatus The new status of sync for this account * \param aFailedReason This is an out parameter. In case the last sync has * failed, this will contain the code indicating the failure reason (TODO: * Define error codes). In case the last sync has not failed, this must be * ignored * \param aPrevSyncTime This is an out parameter. The previous sync time. * Invalid time is returned if there was no last sync. * \param aNextSyncTime This is an out parameter. The next sync time. */ void statusChanged(unsigned int aAccountId, int aNewStatus, int aFailedReason, qlonglong aPrevSyncTime, qlonglong aNextSyncTime); /*! \brief Returns the connectivity state of a specific medium like * bluetooth, USB or network. * \see SyncCommonDefs::ConnectivityType for arguments */ bool isConnectivityAvailable(int connectivityType); /*! \brief Notifies sync externally status for an account and client profile. * * This signal is sent when the sync externally status of a particular * account changes or a client queries for the state via 'isSyncedExternally' * * \param aAccountId The account IDs that changed state/was queried the state * \param aClientProfileName The name of the client profile resposible for the sync. * \param aState The current status of sync externally(on/off). */ void syncedExternallyStatus(uint aAccountId, const QString &aClientProfileName, bool aState); public slots: /*! * \brief Requests to starts synchronizing using a profile with the given * name. * * A status change signal (QUEUED, STARTED or ERROR) will be sent by the * daemon when the request is processed. If there is a sync already in * progress using the same resources that are needed by the given profile, * adds the sync request to a sync queue. Otherwise a sync session is * started immediately. * * \param aProfileId Id of the profile to use in sync. * \return True if a profile with the given id was found. Otherwise * false and no status change signals will follow from this request. */ virtual bool startSync(QString aProfileId) = 0; /*! * \brief Stops synchronizing the profile with the given name. * * If the sync request is still in queue and not yet started, the queue * entry is removed. * * \param aProfileId Name of the profile to stop syncing. */ virtual Q_NOREPLY void abortSync(QString aProfileId) = 0; /*! * \brief This function should be called when sync profile has to be deleted * * \param aProfileId Id of the profile to be deleted. * \return status of the remove operation */ virtual bool removeProfile(QString aProfileId) = 0; /*! * \brief This function should be called when sync profile information has * been changed by someone else than the sync daemon. * \note If profile does not exist prior to calling this function, a new profile file is created * * \param aProfileAsXml - Modified Profile Object as XML. * \return status of the update operation */ virtual bool updateProfile(QString aProfileAsXml) = 0; /*! * \brief Requests sync daemon to reserve storages for the caller. * * This function must be called if an external sync entity (like Active * Sync engine) wants to use the same storages that the sync daemon uses, * because concurrent access might lead to data corruption. If none of the * requested storages is currently used by the sync daemon, they are all * marked as reserved and can not be used by the daemon until the storages * are freed by calling releaseStorages. If one or more of the requested * storages is already in use, none of them is reserved. * * \param aStorageNames Names of the storages to reserve. * \return Success indicator. True if all requested storages were * successfully reserved. False if request failed and no storages were * reserved. */ virtual bool requestStorages(QStringList aStorageNames) = 0; /*! * \brief Releases the given storages so that sync daemon can again use * them freely. * * This function must be called after a successful requestStorages call, * when the reserved storages are not used by the caller any more. */ virtual Q_NOREPLY void releaseStorages(QStringList aStorageNames) = 0; /*! * \brief Gets the list of profile names of currently running syncs. * * \return Profile name list. */ virtual QStringList runningSyncs() = 0; /*! * \brief This function returns true if backup/restore in progress else * false. */ virtual bool getBackUpRestoreState() = 0; /*! * \brief sets the schedule for a profile * * This Function helps in setting a schedule to profile * this Function is to be used by the SyncInterface Client Library to * expose a user friendly API by abstracting the dbus mechanisms * involved with synchronizer * * \param aProfileId - Id of the profile for which schedule has to be set * \param aScheduleAsXml - Over the dbus the schedule object is transmitted as xml * * \return bool - status of the operation */ virtual bool setSyncSchedule(QString aProfileId, QString aScheduleAsXml) = 0; /*! * \brief Save SyncResults to log.xml file. * \param aProfileId to save result in corresponding file. * \param aSyncResults to save in the \code .log.xml. \endcode * \return status of the saveSyncResults */ virtual bool saveSyncResults(QString aProfileId, QString aSyncResults) = 0; /*! \brief To get lastSyncResult. * \param aProfileId * \return QString of syncResult. */ virtual QString getLastSyncResult(const QString &aProfileId) = 0; /*! \brief Gets all visible sync profiles. * * Returns all sync profiles that should be visible in sync ui. A profile * is visible if it has not been explicitly set as hidden. * \return The list of sync profiles. */ virtual QStringList allVisibleSyncProfiles() = 0; /*! \brief Gets a sync profile. * * Loads and merges also all sub-profiles that are referenced from the * main profile. Loads the log of finished synchronization sessions with * this profile. * \param aProfileId Name of the profile to get. * \return The sync profile as Xml string. */ virtual QString syncProfile(const QString &aProfileId) = 0; /*! \brief Gets a sync profiles matching the key-value. * * Loads and merges also all sub-profiles that are referenced from the * main profile. Loads the log of finished synchronization sessions with * this profile. * \param aKey Key to match for profile. * \param aValue Value to match for profile. * \return The sync profiles as Xml string list. */ virtual QStringList syncProfilesByKey(const QString &aKey, const QString &aValue) = 0; /*! \brief Gets all profiles matching the profile type. * * \param aType Type of the profile service/storage/sync. * \return The sync profiles as Xml string list. */ virtual QStringList profilesByType(const QString &aType) = 0; /*! \brief Gets a profiles matching the profile type. * * \param aType Type of the profile service/storage/sync. * \return The sync profile ids as string list. */ virtual QStringList syncProfilesByType(const QString &aType) = 0; /*! \brief Starts sync for all profiles matching the given account ID. * * \param aAccountId The account ID. */ virtual Q_NOREPLY void start(unsigned int aAccountId) = 0; /*! \brief Stops sync for all profiles matching the given account ID. * * \param aAccountId The account ID. */ virtual Q_NOREPLY void stop(unsigned int aAccountId) = 0; /*! \brief Returns the list of account IDs for which sync is ongoing * * \return The list of account IDs currectly syncing. */ virtual QList syncingAccounts() = 0; /*! \brief Returns the status of the sync for the given account Id * * \param aAccountId The account ID. * \param aFailedReason This is an out parameter. In case the last sync has * failed, this will contain the code indicating the failure reason (TODO: * Define error codes). In case the last sync has not failed, this must be * ignored * \param aPrevSyncTime This is an out parameter. The previous sync time. * Invalid time is returned if there was no last sync. * \param aNextSyncTime This is an out parameter. The next sync time. * \return The status of sync: 0 = Sync is running, * 1 = Last sync succeeded, 2 = last sync failed */ virtual int status(unsigned int aAccountId, int &aFailedReason, qlonglong &aPrevSyncTime, qlonglong &aNextSyncTime) = 0; /*! \brief Queries the sync externally status of a given account, * 'syncedExternallyStatus' signal is emitted with the reply is ready, clients should listen * to the later. * * \param aAccountId The account ID. * \param aClientProfileName The name of the client profile resposible for the sync, this is used to distinguish accounts * having several services enabled */ virtual Q_NOREPLY void isSyncedExternally(unsigned int aAccountId, const QString aClientProfileName) = 0; /*! \brief Create a sync profile for the account if it does not exists * * \param aAccountId The account ID. * \return The profile name if the profile was created successful or empty if it fails */ virtual QString createSyncProfileForAccount(uint aAccountId) = 0; }; } #endif // SYNCDBUSINTERFACE_H buteo-syncfw-0.11.10/msyncd/SyncOnChange.cpp000066400000000000000000000103711477124122200206230ustar00rootroot00000000000000#include "SyncOnChange.h" #include "SyncOnChangeScheduler.h" #include "SyncProfile.h" #include "StorageChangeNotifier.h" #include "LogMacros.h" using namespace Buteo; SyncOnChange::SyncOnChange() : iStorageChangeNotifier(new StorageChangeNotifier()) , iSOCScheduler(0) { FUNCTION_CALL_TRACE(lcButeoTrace); } SyncOnChange::~SyncOnChange() { FUNCTION_CALL_TRACE(lcButeoTrace); QStringList storages; disable(); storages = getSOCStorageNames(); for (QStringList::const_iterator storageItr = storages.constBegin(); storageItr != storages.constEnd(); ++storageItr) { cleanup(*storageItr); } delete iStorageChangeNotifier; } bool SyncOnChange::enable(const QHash > &aSOCStorageMap, SyncOnChangeScheduler *aSOCScheduler, PluginManager *aPluginManager, QStringList &aFailedStorages) { FUNCTION_CALL_TRACE(lcButeoTrace); bool enabled = false; QStringList storages; iSOCStorageMap = aSOCStorageMap; iSOCScheduler = aSOCScheduler; storages = getSOCStorageNames(); iStorageChangeNotifier->loadNotifiers(aPluginManager, storages); enabled = iStorageChangeNotifier->startListen(aFailedStorages); for (QStringList::const_iterator failedStorageItr = aFailedStorages.constBegin(); failedStorageItr != aFailedStorages.constEnd(); ++failedStorageItr) { cleanup(*failedStorageItr); } if (storages.count() > aFailedStorages.count()) { QObject::connect(iStorageChangeNotifier, SIGNAL(storageChange(QString)), this, SLOT(sync(QString))); } return enabled; } void SyncOnChange::enable() { FUNCTION_CALL_TRACE(lcButeoTrace); if (iStorageChangeNotifier) { QStringList aFailedStorages; bool enabled = iStorageChangeNotifier->startListen(aFailedStorages); Q_UNUSED(enabled); for (QStringList::const_iterator failedStorageItr = aFailedStorages.constBegin(); failedStorageItr != aFailedStorages.constEnd(); ++failedStorageItr) { cleanup(*failedStorageItr); } iStorageChangeNotifier->checkForChanges(); } } void SyncOnChange::disable() { FUNCTION_CALL_TRACE(lcButeoTrace); iStorageChangeNotifier->stopListen(); } void SyncOnChange::disableNext() { FUNCTION_CALL_TRACE(lcButeoTrace); iStorageChangeNotifier->stopListen(true); } void SyncOnChange::cleanup(const QString &aStorageName) { FUNCTION_CALL_TRACE(lcButeoTrace); QList profilesList; if (iSOCStorageMap.contains(aStorageName)) { profilesList = iSOCStorageMap.value(aStorageName); } for (QList::iterator profileItr = profilesList.begin(); profileItr != profilesList.end(); ++profileItr) { delete (*profileItr); } iSOCStorageMap.remove(aStorageName); } QStringList SyncOnChange::getSOCStorageNames() { FUNCTION_CALL_TRACE(lcButeoTrace); QStringList storages; for (QHash >::const_iterator storageNameItr = iSOCStorageMap.constBegin(); storageNameItr != iSOCStorageMap.constEnd(); ++storageNameItr) { storages << storageNameItr.key(); } return storages; } void SyncOnChange::sync(QString aStorageName) { FUNCTION_CALL_TRACE(lcButeoTrace); QList profilesList; if (iSOCStorageMap.contains(aStorageName)) { profilesList = iSOCStorageMap.value(aStorageName); } for (QList::iterator profileItr = profilesList.begin(); profileItr != profilesList.end(); ++profileItr) { iSOCScheduler->addProfile(*profileItr); } } void SyncOnChange::addProfile(const QString &aStorageName, SyncProfile *aProfile) { FUNCTION_CALL_TRACE(lcButeoTrace); bool found = false; QList profilesList = iSOCStorageMap.value(aStorageName); for (QList::iterator profileItr = profilesList.begin(); profileItr != profilesList.end(); ++profileItr) { if (*profileItr && aProfile && (*profileItr)->name() == aProfile->name()) { found = true; break; } } if (!found) { iSOCStorageMap[aStorageName].append(aProfile); } } buteo-syncfw-0.11.10/msyncd/SyncOnChange.h000066400000000000000000000051211477124122200202650ustar00rootroot00000000000000#ifndef SYNCONCHANGE_H #define SYNCONCHANGE_H #include #include #include namespace Buteo { class SyncProfile; class StorageChangeNotifier; class PluginManager; class SyncOnChangeScheduler; /*! \brief this class initiates a sync if there are changes * in storage(s) it's asked to monitor */ class SyncOnChange : public QObject { Q_OBJECT public: /*! \brief constructor */ SyncOnChange(); /*! \brief destructor */ ~SyncOnChange(); /*! \brief enable sync on change for a list of storages * for the interested profiles * * Destroys the profile objects when they are no longer needed * * @param aPluginManager Used to load SOC storage plug-ins * @param aSOCScheduler used to schedule SOC * @param aSOCStorageMap map of well-known storage name * to list of sync profiles insterested in SOC for that * storage * @param list of storage names for which SOC couldn't be enabled * @return false if SOC can't be enabled for one or more * storages */ bool enable(const QHash > &aSOCStorageMap, SyncOnChangeScheduler *aSOCScheduler, PluginManager *aPluginManager, QStringList &aFailedStorages); /*! If the storage change notifier plug-in's have already been loaded, * call this to re-enable sync on change. Handy to call after a disable. * * This also checks if there were changes when SOC was disabled, and notifies * if there were any */ void enable(); /*! \brief disable sync on change immediately, i.e stop listening * to change notifiers */ void disable(); /*! \brief Note the next change, and disable SOC if that happens */ void disableNext(); /*! \brief adds a profile to the list of profiles interested in soc for a specific storage * * @param aStorageName storage name * @param aProfile sync profile */ void addProfile(const QString &aStorageName, SyncProfile *aProfile); public Q_SLOTS: /*! initiate sync for this storage */ void sync(QString aStorageName); private: /*! \brief destroys profile objects interested in SOC for this * storage */ void cleanup(const QString &aStorageName); /*! \brief Get the names for storages for which SOC is desired * * @return list of storage names */ QStringList getSOCStorageNames(); StorageChangeNotifier *iStorageChangeNotifier; QHash > iSOCStorageMap; SyncOnChangeScheduler *iSOCScheduler; }; } #endif buteo-syncfw-0.11.10/msyncd/SyncOnChangeScheduler.cpp000066400000000000000000000047731477124122200224730ustar00rootroot00000000000000#include #include "SyncOnChangeScheduler.h" #include "SyncProfile.h" #include "LogMacros.h" using namespace Buteo; SyncOnChangeScheduler::SyncOnChangeScheduler() { FUNCTION_CALL_TRACE(lcButeoTrace); } SyncOnChangeScheduler::~SyncOnChangeScheduler() { FUNCTION_CALL_TRACE(lcButeoTrace); foreach (QObject *o, iSOCTimers.values()) { delete o; } iSOCTimers.clear(); iSOCProfileNames.clear(); } bool SyncOnChangeScheduler::addProfile(const SyncProfile *aProfile) { FUNCTION_CALL_TRACE(lcButeoTrace); bool scheduled = false; if (aProfile && !iSOCProfileNames.contains(aProfile->name())) { qint32 time = aProfile->syncOnChangeAfter(); iSOCProfileNames << aProfile->name(); SyncOnChangeTimer *SOCtimer = new SyncOnChangeTimer(aProfile, time); QObject::connect(SOCtimer, SIGNAL(timeout(const SyncProfile *)), this, SLOT(sync(const SyncProfile *)), Qt::QueuedConnection); SOCtimer->fire(); scheduled = true; iSOCTimers.insert(aProfile->name(), SOCtimer); qCDebug(lcButeoMsyncd) << "Sync on change scheduled for profile" << aProfile->name(); } else if (aProfile) { qCDebug(lcButeoMsyncd) << "Sync on change already scheduled for profile" << aProfile->name(); } return scheduled; } void SyncOnChangeScheduler::removeProfile(const QString &aProfileName) { FUNCTION_CALL_TRACE(lcButeoTrace); iSOCProfileNames.removeAll(aProfileName); // cancel timer delete iSOCTimers.take(aProfileName); } void SyncOnChangeScheduler::sync(const SyncProfile *aProfile) { FUNCTION_CALL_TRACE(lcButeoTrace); iSOCProfileNames.removeAll(aProfile->name()); iSOCTimers.remove(aProfile->name()); SyncOnChangeTimer *SOCtimer = qobject_cast(sender()); if (SOCtimer) { qCDebug(lcButeoMsyncd) << "Sync on change for profile" << aProfile->name(); delete SOCtimer; emit syncNow(aProfile->name()); } } SyncOnChangeTimer::SyncOnChangeTimer(const SyncProfile *profile, const quint32 &aTimeout) : iSyncProfile(profile), iTimeout(aTimeout) { FUNCTION_CALL_TRACE(lcButeoTrace); } SyncOnChangeTimer::~SyncOnChangeTimer() { FUNCTION_CALL_TRACE(lcButeoTrace); } void SyncOnChangeTimer::fire() { FUNCTION_CALL_TRACE(lcButeoTrace); QTimer::singleShot(iTimeout * 1000, this, SLOT(onTimeout())); } void SyncOnChangeTimer::onTimeout() { FUNCTION_CALL_TRACE(lcButeoTrace); emit timeout(iSyncProfile); } buteo-syncfw-0.11.10/msyncd/SyncOnChangeScheduler.h000066400000000000000000000046171477124122200221350ustar00rootroot00000000000000#ifndef SYNCONCHANGESCHEDULER_H #define SYNCONCHANGESCHEDULER_H #include #include #include #include "SyncScheduler.h" namespace Buteo { class SyncProfile; class SyncOnChangeScheduler : public SyncScheduler { Q_OBJECT public: /*! \brief constructor */ SyncOnChangeScheduler(); /*! \brief destructor */ ~SyncOnChangeScheduler(); /*! \brief Call this method to schedule SOC for a profile * * There are 3 scheduling criteria - SOC after info from the * profile, default SOC after and sync now. * * The profile is first checked for sync on change after time, which * should be specified in seconds (0 means sync now). If none is specified * then we use a default of DEFAULT_SOC_AFTER_TIME * * If the profile has already been added and if it's SOC is scheduled, * calling this method again will just use the previous schedule, and in * this case the method will return false. * * Once the SOC is initiated (by sending a syncNow signal), the profile is * removed automatically * * @param aProfile pointer to sync profile * @return true if SOC could be scheduled, false otherwise */ bool addProfile(const SyncProfile *aProfile); /*! \brief call this method to disable SOC that has been scheduled * for a certain profile * * @param aProfileName name of the profile */ void removeProfile(const QString &aProfileName); private Q_SLOTS: /*! \brief slot to initiate sync when timeout criterion is being used * and the timeout occurs * * @param aProfile sync profile */ void sync(const SyncProfile *aProfile); private: QStringList iSOCProfileNames; QMap iSOCTimers; }; class SyncOnChangeTimer : public QObject { Q_OBJECT public: /*! \brief constructor */ SyncOnChangeTimer(const SyncProfile *aProfile, const quint32 &aTimeout); /*! \brief destructor */ ~SyncOnChangeTimer(); /*! \brief fire the timer */ void fire(); Q_SIGNALS: /*! \brief emit this signal when the timeout occurs * * @param aProfile sync profile */ void timeout(const SyncProfile *aProfile); private Q_SLOTS: /*! \brief slot corresponding to timeout */ void onTimeout(); private: const SyncProfile *iSyncProfile; quint32 iTimeout; }; } #endif buteo-syncfw-0.11.10/msyncd/SyncQueue.cpp000066400000000000000000000065521477124122200202330ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "SyncQueue.h" #include "SyncSession.h" #include "SyncProfile.h" #include "LogMacros.h" using namespace Buteo; void SyncQueue::enqueue(SyncSession *aSession) { FUNCTION_CALL_TRACE(lcButeoTrace); iItems.enqueue(aSession); sort(); } SyncSession *SyncQueue::dequeue() { FUNCTION_CALL_TRACE(lcButeoTrace); SyncSession *p = nullptr; if (!iItems.isEmpty()) { p = iItems.dequeue(); } return p; } SyncSession *SyncQueue::dequeue(const QString &aProfileName) { FUNCTION_CALL_TRACE(lcButeoTrace); SyncSession *ret = 0; QQueue::iterator i; for (i = iItems.begin(); i != iItems.end(); ++i) { if ((*i)->profileName() == aProfileName) { ret = *i; iItems.erase(i); break; } } return ret; } SyncSession *SyncQueue::head() { FUNCTION_CALL_TRACE(lcButeoTrace); SyncSession *p = nullptr; if (!iItems.isEmpty()) { p = iItems.head(); } return p; } bool SyncQueue::isEmpty() const { FUNCTION_CALL_TRACE(lcButeoTrace); return iItems.isEmpty(); } int SyncQueue::size() const { FUNCTION_CALL_TRACE(lcButeoTrace); return iItems.size(); } bool SyncQueue::contains(const QString &aProfileName) const { FUNCTION_CALL_TRACE(lcButeoTrace); QQueue::const_iterator i; for (i = iItems.begin(); i != iItems.end(); ++i) { if ((*i)->profileName() == aProfileName) return true; } return false; } bool syncSessionPointerLessThan(SyncSession *&aLhs, SyncSession *&aRhs) { if (aLhs && aRhs) { // Manual sync has higher priority than scheduled sync. if (aLhs->isScheduled() != aRhs->isScheduled()) return !aLhs->isScheduled(); SyncProfile *lhsProfile = aLhs->profile(); SyncProfile *rhsProfile = aRhs->profile(); if (lhsProfile == 0 || rhsProfile == 0) return false; // Device sync has higher priority than online sync. SyncProfile::DestinationType lhsDestType = lhsProfile->destinationType(); SyncProfile::DestinationType rhsDestType = rhsProfile->destinationType(); if (lhsDestType != rhsDestType) return (lhsDestType == SyncProfile::DESTINATION_TYPE_DEVICE); } return false; } void SyncQueue::sort() { FUNCTION_CALL_TRACE(lcButeoTrace); // @todo: Sort queued profiles using some criteria. } const QList &SyncQueue::getQueuedSyncSessions() const { FUNCTION_CALL_TRACE(lcButeoTrace); return iItems; } buteo-syncfw-0.11.10/msyncd/SyncQueue.h000066400000000000000000000052261477124122200176750ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCQUEUE_H #define SYNCQUEUE_H #include namespace Buteo { class SyncSession; /*! \brief Class for queuing sync sessions. * * The queue is sorted every time when new items are added to it, so that * the sync sessions with highest priority will be at the front of the queue. */ class SyncQueue { public: /*! \brief Adds a new profile to the queue. Queue is sorted automatically. * * \param aSession Session to add to queue */ void enqueue(SyncSession *aSession); /*! \brief Removes the sync session corresponding to the profile name and returns it. * * \return The removed item. NULL if the queue was empty. */ SyncSession *dequeue(const QString &aProfileName); /*! \brief Removes the first item from the queue and returns it. * * \return The removed item. NULL if the queue was empty. */ SyncSession *dequeue(); /*! \brief Returns the first item in the queue but does not remove it. * * \return First item of the queue. NULL if the queue is empty. */ SyncSession *head(); /*! \brief Checks if the queue is empty. * * \return Is the queue empty. */ bool isEmpty() const; /*! \brief Current size of the sync queue. * * \return Number of elements in the sync queue. */ int size() const; /*! \brief Checks if a profile with the given name is in the queue. * * \return Is the profile in the queue. */ bool contains(const QString &aProfileName) const; /*! \brief Returns as a const reference, the list of all SyncSessions * currently queued. * * \return Is the profile in the queue. */ const QList &getQueuedSyncSessions() const; private: void sort(); QQueue iItems; }; } #endif // SYNCQUEUE_H buteo-syncfw-0.11.10/msyncd/SyncScheduler.cpp000066400000000000000000000235521477124122200210640ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2014-2016 Jolla Ltd. * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #if defined(USE_KEEPALIVE) #include "BackgroundSync.h" #elif defined(USE_IPHB) #include "IPHeartBeat.h" #endif #include "SyncScheduler.h" #include "SyncProfile.h" #include "SyncCommonDefs.h" #include "LogMacros.h" #include using namespace Buteo; SyncScheduler::SyncScheduler(QObject *aParent) : QObject(aParent) { FUNCTION_CALL_TRACE(lcButeoTrace); #if defined(USE_KEEPALIVE) iBackgroundActivity = new BackgroundSync(this); connect(iBackgroundActivity, SIGNAL(onBackgroundSyncRunning(QString)), this, SLOT(doIPHeartbeatActions(QString))); connect(iBackgroundActivity, SIGNAL(onBackgroundSwitchRunning(QString)), this, SLOT(rescheduleBackgroundActivity(QString))); #elif defined(USE_IPHB) iIPHeartBeatMan = new IPHeartBeat(this); connect(iIPHeartBeatMan, SIGNAL(onHeartBeat(QString)), this, SLOT(doIPHeartbeatActions(QString))); // Create the alarm inventory object iAlarmInventory = new SyncAlarmInventory(); connect(iAlarmInventory, SIGNAL(triggerAlarm(int)), this, SLOT(doAlarmActions(int))); if (!iAlarmInventory->init()) { qCWarning(lcButeoMsyncd) << "AlarmInventory Init Failed"; } #endif } SyncScheduler::~SyncScheduler() { FUNCTION_CALL_TRACE(lcButeoTrace); #if defined(USE_KEEPALIVE) iBackgroundActivity->removeAll(); #elif defined(USE_IPHB) removeAllAlarms(); delete iAlarmInventory; iAlarmInventory = 0; #endif } void SyncScheduler::addProfileForSyncRetry(const SyncProfile *aProfile, QDateTime aNextSyncTime) { FUNCTION_CALL_TRACE(lcButeoTrace); if (aProfile && aProfile->isEnabled()) { #if defined(USE_KEEPALIVE) setNextAlarm(aProfile, aNextSyncTime); #elif defined(USE_IPHB) //remove alarm removeProfile(aProfile->name()); int alarmId = setNextAlarm(aProfile, aNextSyncTime); if (alarmId > 0) { iSyncScheduleProfiles.insert(aProfile->name(), alarmId); qCDebug(lcButeoMsyncd) << "syncretries : retry scheduled for profile" << aProfile->name(); } #endif } } bool SyncScheduler::addProfile(const SyncProfile *aProfile) { FUNCTION_CALL_TRACE(lcButeoTrace); if (!aProfile) return false; // In case Keepalive is used no need to remove // existent profile will be updated #if defined(USE_KEEPALIVE) if (aProfile->isEnabled() && aProfile->syncType() == SyncProfile::SYNC_SCHEDULED) { setNextAlarm(aProfile); return true; } else { removeProfile(aProfile->name()); return false; } #elif defined(USE_IPHB) bool profileAdded = false; // Remove possible old alarm first. removeProfile(aProfile->name()); if (aProfile->isEnabled() && aProfile->syncType() == SyncProfile::SYNC_SCHEDULED) { int alarmId = setNextAlarm(aProfile); if (alarmId > 0) { iSyncScheduleProfiles.insert(aProfile->name(), alarmId); profileAdded = true; qCDebug(lcButeoMsyncd) << "Sync scheduled: profile =" << aProfile->name() << "time =" << aProfile->nextSyncTime(); } } return profileAdded; #else return false; #endif } void SyncScheduler::removeProfile(const QString &aProfileName) { FUNCTION_CALL_TRACE(lcButeoTrace); #if defined(USE_KEEPALIVE) if (iBackgroundActivity->remove(aProfileName)) { qCDebug(lcButeoMsyncd) << "Scheduled sync removed: profile =" << aProfileName; } #elif defined(USE_IPHB) if (iSyncScheduleProfiles.contains(aProfileName)) { int alarmEventID = iSyncScheduleProfiles.value(aProfileName); removeAlarmEvent(alarmEventID); iSyncScheduleProfiles.remove(aProfileName); qCDebug(lcButeoMsyncd) << "Scheduled sync removed: profile =" << aProfileName; } #endif } void SyncScheduler::doIPHeartbeatActions(QString aProfileName) { FUNCTION_CALL_TRACE(lcButeoTrace); iActiveBackgroundSyncProfiles.insert(aProfileName); emit syncNow(aProfileName); } void SyncScheduler::syncStatusChanged(const QString &aProfileName, int aStatus, const QString &aMessage, int aMoreDetails) { if (iActiveBackgroundSyncProfiles.contains(aProfileName) && aStatus >= Sync::SYNC_ERROR) { // the background sync cycle is finished. // tell the scheduler that it can stop preventing device suspend. qCDebug(lcButeoMsyncd) << "Background sync" << aProfileName << "finished with status:" << aStatus << "and extra:" << aMessage << "," << aMoreDetails; iActiveBackgroundSyncProfiles.remove(aProfileName); #if defined(USE_KEEPALIVE) iBackgroundActivity->onBackgroundSyncCompleted(aProfileName); // and schedule the next background sync if necessary. SyncProfile *profile = iProfileManager.syncProfile(aProfileName); if (profile) { setNextAlarm(profile); delete profile; } #endif } } #if defined(USE_KEEPALIVE) void SyncScheduler::rescheduleBackgroundActivity(const QString &aProfileName) { FUNCTION_CALL_TRACE(lcButeoTrace); SyncProfile *profile = iProfileManager.syncProfile(aProfileName); if (profile) { if (profile->syncExternallyEnabled() || profile->syncExternallyDuringRush()) { emit externalSyncChanged(profile->name(), false); } setNextAlarm(profile); delete profile; } else { qCWarning(lcButeoMsyncd) << "Invalid profile, can't reschedule switch timer for " << aProfileName; } } #endif int SyncScheduler::setNextAlarm(const SyncProfile *aProfile, QDateTime aNextSyncTime) { FUNCTION_CALL_TRACE(lcButeoTrace); int alarmEventID = -1; if (aProfile == 0) { return alarmEventID; } QDateTime nextSyncTime; if (!aNextSyncTime.isValid()) { nextSyncTime = aProfile->nextSyncTime(aProfile->lastSyncTime()); } else { nextSyncTime = aNextSyncTime; } if (nextSyncTime.isValid()) { // The existing event object can be used by just updating the alarm time // and enqueuing it again. #if defined(USE_KEEPALIVE) alarmEventID = 1; iBackgroundActivity->set(aProfile->name(), QDateTime::currentDateTime().secsTo(nextSyncTime) + 1); if (aProfile->rushEnabled()) { QDateTime nextSyncSwitch = aProfile->nextRushSwitchTime(QDateTime::currentDateTime()); if (nextSyncSwitch.isValid()) { iBackgroundActivity->setSwitch(aProfile->name(), nextSyncSwitch); } else { iBackgroundActivity->removeSwitch(aProfile->name()); qCDebug(lcButeoMsyncd) << "Removing switch timer for" << aProfile->name() << " invalid switch timer"; } } else { iBackgroundActivity->removeSwitch(aProfile->name()); } #elif defined(USE_IPHB) iAlarmInventory->addAlarm(nextSyncTime); #endif if (alarmEventID == 0) { qCWarning(lcButeoMsyncd) << "Failed to add alarm for scheduled sync of profile" << aProfile->name(); } } else { #if defined(USE_KEEPALIVE) // no valid next scheduled sync time for background sync. // stop the background activity to allow device suspend. iBackgroundActivity->remove(aProfile->name()); if (aProfile->rushEnabled()) { QDateTime nextSyncSwitch = aProfile->nextRushSwitchTime(QDateTime::currentDateTime()); if (nextSyncSwitch.isValid()) { iBackgroundActivity->setSwitch(aProfile->name(), nextSyncSwitch); } else { iBackgroundActivity->removeSwitch(aProfile->name()); } } else { iBackgroundActivity->removeSwitch(aProfile->name()); } #endif qCWarning(lcButeoMsyncd) << "Next sync time is not valid, sync not scheduled for profile" << aProfile->name(); } return alarmEventID; } #if defined(USE_IPHB) void SyncScheduler::doAlarmActions(int aAlarmEventID) { FUNCTION_CALL_TRACE(lcButeoTrace); const QString syncProfileName = iSyncScheduleProfiles.key(aAlarmEventID); if (!syncProfileName.isEmpty()) { iSyncScheduleProfiles.remove(syncProfileName); // Use global slots (min time == max time) for scheduling heart beats. if (iIPHeartBeatMan->setHeartBeat(syncProfileName, IPHB_GS_WAIT_2_5_MINS, IPHB_GS_WAIT_2_5_MINS)) { //Do nothing, sync will be triggered on getting heart beat } else { emit syncNow(syncProfileName); } } // no else, in error cases simply ignore } void SyncScheduler::removeAlarmEvent(int aAlarmEventID) { FUNCTION_CALL_TRACE(lcButeoTrace); bool err = iAlarmInventory->removeAlarm(aAlarmEventID); if (err < false) { qCWarning(lcButeoMsyncd) << "No alarm found for ID " << aAlarmEventID; } else { qCDebug(lcButeoMsyncd) << "Removed alarm, ID =" << aAlarmEventID; } } void SyncScheduler::removeAllAlarms() { FUNCTION_CALL_TRACE(lcButeoTrace); iAlarmInventory->removeAllAlarms(); } #endif buteo-syncfw-0.11.10/msyncd/SyncScheduler.h000066400000000000000000000141051477124122200205230ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCSCHEDULER_H #define SYNCSCHEDULER_H #if defined(USE_KEEPALIVE) #include "BackgroundSync.h" #include "ProfileManager.h" #elif defined(USE_IPHB) #include "SyncAlarmInventory.h" #include "IPHeartBeat.h" #endif #include #include #include #include #include #include class QDateTime; #ifdef USE_KEEPALIVE class BackgroundSync; #elif defined(USE_IPHB) class IPHeartBeat; #endif namespace Buteo { class SyncSession; class SyncSchedulerTest; class SyncProfile; /*! \brief SyncScheduler Object to be used to set Schedule via the framework */ class SyncScheduler : public QObject { Q_OBJECT public: //! \brief Constructor. SyncScheduler(QObject *aParent = 0); /** * \brief Destructor */ virtual ~SyncScheduler(); /*! \brief Adds a profile to the scheduler. * * Verifies that the profile is enabled and it has schedule enabled * before adding. If the profile is already added, removes the old schedule * and then adds the profile again. * A syncNow signal is sent when sync should be started based on the * schedule settings of the profile. The signal is sent only once. Call * this function again after the sync has finished to continue scheduling * syncs for the profile. * \param aProfile Profile to add to scheduler. * \return Success indicator. */ bool addProfile(const SyncProfile *aProfile); /* Schedule a retry for a failed sync if the profile has retries enabled * * @param aProfile sync profile * @param aNExtSyncTime retry after this duration */ void addProfileForSyncRetry(const SyncProfile *aProfile, QDateTime aNextSyncTime); /*! \brief Removes the profile with the given name from the scheduler. * * No new syncNow signals will be sent for the profile. Note that an * already sent signal may still be waiting in the event queue of receiving * thread. * \param aProfileName Name of the profile to remove from the scheduler. */ void removeProfile(const QString &aProfileName); public slots: /*! \brief Handles the sync status change signal from the synchronizer * * This allows the SyncScheduler to appropriately wait() or stop() any background * activity which is preventing device suspend. * * @param aProfileName Name of the profile * @param aStatus Status of the sync * @param aMessage Status message as a string * @param aMoreDetails In case of failure, contains detailed reason */ void syncStatusChanged(const QString &aProfileName, int aStatus, const QString &aMessage, int aMoreDetails); private slots: #if defined(USE_IPHB) /** * \brief Performs needed actions when scheduled alarm is triggered * * @param aAlarmEventID an ID that identifies the triggered alarm event */ void doAlarmActions(int aAlarmEventID); #endif /** * \brief Performs needed actions when a IP heart beat is triggered * * @param aProfileName Name of the profile on which heart beat received */ void doIPHeartbeatActions(QString aProfileName); #if defined(USE_KEEPALIVE) /** * \brief Reschedule backgroundActivity for a profile * * @param aProfileName Name of the profile to reschedule */ void rescheduleBackgroundActivity(const QString &aProfileName); #endif signals: /*! \brief Signal emitted when a sync session should be launched based on * the sync schedule settings of the profile. * * \param aProfileName Name of the profile. */ void syncNow(QString aProfileName); /*! \brief Signal emitted when a sync session should be launched based on * the sync schedule settings of the profile. * * \param aProfileName Name of the profile. */ void externalSyncChanged(QString aProfileName, bool aQuery = false); private: // functions /** * \brief Programs next alarm event to alarmd. * * @param aProfile The profile for which the alarm is programmed * @param aNextSyncTime use if provided, otherwise fetch the info from the profile * @return Unique alarm event ID or 0 in failure case. */ int setNextAlarm(const SyncProfile *aProfile, QDateTime aNextSyncTime = QDateTime()); /** * \brief Creates a DBUS adaptor for the scheduler */ void setupDBusAdaptor(); #if defined(USE_IPHB) /** * \brief Removes an alarm from alarmd queue * @param aAlarmEventID ID of the alarm to be removed */ void removeAlarmEvent(int aAlarmEvent); /** * \brief A convenience method that removes all alarms from alarmd queue */ void removeAllAlarms(); #endif private: // data QSet iActiveBackgroundSyncProfiles; #if defined(USE_KEEPALIVE) /// BackgroundSync management object BackgroundSync *iBackgroundActivity; ProfileManager iProfileManager; #elif defined(USE_IPHB) /// A list of sync schedule profiles QMap iSyncScheduleProfiles; /// Alarm factory object SyncAlarmInventory *iAlarmInventory; /// IP Heartbeat management object IPHeartBeat *iIPHeartBeatMan; #endif #ifdef SYNCFW_UNIT_TESTS friend class SyncSchedulerTest; #endif }; } #endif // SYNCSCHEDULER_H buteo-syncfw-0.11.10/msyncd/SyncSession.cpp000066400000000000000000000320151477124122200205630ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "SyncSession.h" #include "PluginRunner.h" #include "StorageBooker.h" #include "SyncProfile.h" #include "NetworkManager.h" #include "LogMacros.h" using namespace Buteo; SyncSession::SyncSession(SyncProfile *aProfile, QObject *aParent) : QObject(aParent) , iProfile(aProfile) , iPluginRunner(0) , iStatus(Sync::SYNC_ERROR) , iErrorCode(SyncResults::NO_ERROR) , iPluginRunnerOwned(false) , iScheduled(false) , iAborted(false) , iStarted(false) , iFinished(false) , iCreateProfile(false) , iStorageBooker(0) , iNetworkManager(0) { FUNCTION_CALL_TRACE(lcButeoTrace); } SyncSession::~SyncSession() { FUNCTION_CALL_TRACE(lcButeoTrace); if (iPluginRunnerOwned) { PluginRunner *runner = iPluginRunner; iPluginRunner = 0; delete runner; } if (iNetworkManager) { // Disconnect all slots connected to the network manager disconnect(iNetworkManager, SIGNAL(connectionSuccess()), this, SLOT(onNetworkSessionOpened())); disconnect(iNetworkManager, SIGNAL(connectionError()), this, SLOT(onNetworkSessionError())); iNetworkManager->disconnectSession(); } releaseStorages(); delete iProfile; iProfile = 0; } void SyncSession::setPluginRunner(PluginRunner *aPluginRunner, bool aTransferOwnership) { FUNCTION_CALL_TRACE(lcButeoTrace); // Delete old owned plug-in runner, if any. if (iPluginRunnerOwned) { delete iPluginRunner; iPluginRunner = 0; } iPluginRunner = aPluginRunner; iPluginRunnerOwned = aTransferOwnership; if (iPluginRunner != 0) { // As we are setting a plugin runner, the pluginrunner should have // been started already iStarted = true; // Connect signals from plug-in runner. connect(iPluginRunner, SIGNAL(transferProgress(const QString &, Sync::TransferDatabase, Sync::TransferType, const QString &, int)), this, SLOT(onTransferProgress(const QString &, Sync::TransferDatabase, Sync::TransferType, const QString &, int))); connect(iPluginRunner, &PluginRunner::error, this, &SyncSession::onError); connect(iPluginRunner, SIGNAL(success(const QString &, const QString &)), this, SLOT(onSuccess(const QString &, const QString &))); connect(iPluginRunner, SIGNAL(storageAccquired(const QString &)), this, SLOT(onStorageAccquired(const QString &))); connect(iPluginRunner, SIGNAL(syncProgressDetail(const QString &, int)), this, SLOT(onSyncProgressDetail(const QString &, int))); connect(iPluginRunner, SIGNAL(done()), this, SLOT(onDone())); connect(iPluginRunner, SIGNAL(destroyed(QObject *)), this, SLOT(onDestroyed(QObject *))); } } PluginRunner *SyncSession::pluginRunner() { FUNCTION_CALL_TRACE(lcButeoTrace); return iPluginRunner; } bool SyncSession::start() { FUNCTION_CALL_TRACE(lcButeoTrace); bool rv = false; // If this is an online session, then we need to ensure that the network // session is opened before starting our plugin runner if ((iProfile->destinationType() == SyncProfile::DESTINATION_TYPE_ONLINE) && !iScheduled) { iNetworkManager = new NetworkManager(this); connect(iNetworkManager, SIGNAL(connectionSuccess()), SLOT(onNetworkSessionOpened()), Qt::QueuedConnection); connect(iNetworkManager, SIGNAL(connectionError()), SLOT(onNetworkSessionError()), Qt::QueuedConnection); // Return true here and wait for the session open status iNetworkManager->connectSession(iScheduled); rv = true; } else { rv = tryStart(); } return rv; } bool SyncSession::tryStart() { bool rv = false; if (iPluginRunner != 0) { iStarted = rv = iPluginRunner->start(); } if (!rv) { updateResults(SyncResults(QDateTime::currentDateTime(), SyncResults::SYNC_RESULT_FAILED, Buteo::SyncResults::INTERNAL_ERROR)); if (iPluginRunner != 0) { disconnect(iPluginRunner, 0, this, 0); } } return rv; } bool SyncSession::isFinished() { return iFinished; } bool SyncSession::isAborted() { return iAborted; } void SyncSession::abort(Sync::SyncStatus aStatus) { FUNCTION_CALL_TRACE(lcButeoTrace); if (!iStarted) { qCDebug(lcButeoMsyncd) << "Client plugin runner not started, ignore abort"; updateResults(SyncResults(QDateTime::currentDateTime(), SyncResults::SYNC_RESULT_FAILED, Buteo::SyncResults::ABORTED)); emit finished(profileName(), Sync::SYNC_ERROR, QString(), SyncResults::ABORTED); return; } else { iAborted = true; if (iPluginRunner != 0) { iPluginRunner->abort(aStatus); } } } QMap SyncSession::getStorageMap() { FUNCTION_CALL_TRACE(lcButeoTrace); return iStorageMap; } void SyncSession::setStorageMap(QMap &aStorageMap) { FUNCTION_CALL_TRACE(lcButeoTrace); iStorageMap = aStorageMap; } bool SyncSession::isProfileCreated() { FUNCTION_CALL_TRACE(lcButeoTrace); return iCreateProfile; } void SyncSession::setProfileCreated(bool aProfileCreated) { FUNCTION_CALL_TRACE(lcButeoTrace); iCreateProfile = aProfileCreated; } void SyncSession::stop() { FUNCTION_CALL_TRACE(lcButeoTrace); if (!iStarted) { qCDebug(lcButeoMsyncd) << "Plugin runner not yet started, ignoring stop."; } else if (iPluginRunner != 0) { iPluginRunner->stop(); } } SyncProfile *SyncSession::profile() const { FUNCTION_CALL_TRACE(lcButeoTrace); return iProfile; } QString SyncSession::profileName() const { FUNCTION_CALL_TRACE(lcButeoTrace); QString name; if (iProfile != 0) { name = iProfile->name(); } return name; } SyncResults SyncSession::results() const { FUNCTION_CALL_TRACE(lcButeoTrace); return iResults; } void SyncSession::setScheduled(bool aScheduled) { FUNCTION_CALL_TRACE(lcButeoTrace); iScheduled = aScheduled; iResults.setScheduled(iScheduled); } bool SyncSession::isScheduled() const { FUNCTION_CALL_TRACE(lcButeoTrace); return iScheduled; } void SyncSession::onSuccess(const QString &aProfileName, const QString &aMessage) { FUNCTION_CALL_TRACE(lcButeoTrace); iErrorCode = SyncResults::NO_ERROR; Q_UNUSED(aProfileName); iFinished = true; if (!iAborted) { iStatus = Sync::SYNC_DONE; } else { iStatus = Sync::SYNC_ABORTED; } iMessage = aMessage; if (iPluginRunner != 0) { updateResults(iPluginRunner->syncResults()); } emit finished(profileName(), iStatus, iMessage, iErrorCode); } void SyncSession::onError(const QString &aProfileName, const QString &aMessage, SyncResults::MinorCode aErrorCode) { FUNCTION_CALL_TRACE(lcButeoTrace); Q_UNUSED(aProfileName); iFinished = true; iStatus = mapToSyncStatusError(aErrorCode); iMessage = aMessage; iErrorCode = aErrorCode; if (iPluginRunner != 0) { updateResults(iPluginRunner->syncResults()); } emit finished(profileName(), iStatus, iMessage, iErrorCode); } Sync::SyncStatus SyncSession::mapToSyncStatusError(int aErrorCode) { Sync::SyncStatus status; SyncResults::MinorCode code = static_cast(aErrorCode); switch (code) { case SyncResults::UNSUPPORTED_SYNC_TYPE: { status = Sync::SYNC_NOTPOSSIBLE; break; } default: { status = Sync::SYNC_ERROR; break; } } return status; } void SyncSession::onTransferProgress(const QString &aProfileName, Sync::TransferDatabase aDatabase, Sync::TransferType aType, const QString &aMimeType, int aCommittedItems) { FUNCTION_CALL_TRACE(lcButeoTrace); emit transferProgress(aProfileName, aDatabase, aType, aMimeType, aCommittedItems); } void SyncSession::onStorageAccquired (const QString &aMimeType) { FUNCTION_CALL_TRACE(lcButeoTrace); emit storageAccquired (profileName(), aMimeType); } void SyncSession::onSyncProgressDetail(const QString &aProfileName, int aProgressDetail) { FUNCTION_CALL_TRACE(lcButeoTrace); Q_UNUSED(aProfileName); emit syncProgressDetail (profileName(), aProgressDetail); } void SyncSession::onDone() { FUNCTION_CALL_TRACE(lcButeoTrace); QString pluginName; if (iPluginRunner != 0) { disconnect(iPluginRunner, 0, this, 0); pluginName = iPluginRunner->pluginName(); } if (!iFinished) { qCWarning(lcButeoMsyncd) << "Plug-in terminated unexpectedly:" << pluginName; emit finished(profileName(), Sync::SYNC_ERROR, iMessage, SyncResults::NO_ERROR); } } void SyncSession::onDestroyed(QObject *aPluginRunner) { if (iPluginRunner == aPluginRunner) { qCWarning(lcButeoMsyncd) << "Plug-in runner destroyed before sync session"; iPluginRunner = 0; } } void SyncSession::updateResults(const SyncResults &aResults) { FUNCTION_CALL_TRACE(lcButeoTrace); iResults = aResults; iResults.setScheduled(iScheduled); iResults.setTargetId(aResults.getTargetId()); } void SyncSession::setFailureResult(SyncResults::MajorCode aMajorCode, SyncResults::MinorCode aMinorCode) { FUNCTION_CALL_TRACE(lcButeoTrace); iResults.setMajorCode(aMajorCode); iResults.setMinorCode(aMinorCode); } bool SyncSession::reserveStorages(StorageBooker *aStorageBooker) { FUNCTION_CALL_TRACE(lcButeoTrace); bool success = false; if (aStorageBooker != 0 && iProfile != 0 && aStorageBooker->reserveStorages(iProfile->storageBackendNames(), iProfile->name())) { success = true; iStorageBooker = aStorageBooker; } return success; } void SyncSession::releaseStorages() { // Release storages that were reserved earlier. if (iStorageBooker != 0 && iProfile != 0) { iStorageBooker->releaseStorages(iProfile->storageBackendNames()); } // Set storage booker to nullptr. This indicates that we don't hold any // storage reservations. iStorageBooker = 0; } void SyncSession::onNetworkSessionOpened() { // Start the plugin runner now FUNCTION_CALL_TRACE(lcButeoTrace); if (iNetworkManager) { // Disconnect all slots connected to the network manager disconnect(iNetworkManager, SIGNAL(connectionSuccess()), this, SLOT(onNetworkSessionOpened())); disconnect(iNetworkManager, SIGNAL(connectionError()), this, SLOT(onNetworkSessionError())); } if (false == tryStart()) { qCWarning(lcButeoMsyncd) << "attempt to start sync session due to network session opened failed!"; updateResults(SyncResults(QDateTime::currentDateTime(), SyncResults::SYNC_RESULT_FAILED, Buteo::SyncResults::INTERNAL_ERROR)); emit finished(profileName(), Sync::SYNC_ERROR, QString(), SyncResults::INTERNAL_ERROR); } else { qCDebug(lcButeoMsyncd) << "attempt to start sync session due to network session opened succeeded."; } } void SyncSession::onNetworkSessionError() { FUNCTION_CALL_TRACE(lcButeoTrace); if (iNetworkManager) { // Disconnect all slots connected to the network manager disconnect(iNetworkManager, SIGNAL(connectionSuccess()), this, SLOT(onNetworkSessionOpened())); disconnect(iNetworkManager, SIGNAL(connectionError()), this, SLOT(onNetworkSessionError())); iNetworkManager->disconnectSession(); } updateResults(SyncResults(QDateTime::currentDateTime(), SyncResults::SYNC_RESULT_FAILED, Buteo::SyncResults::CONNECTION_ERROR)); // Update the session with connection error emit finished(profileName(), Sync::SYNC_ERROR, QString(), SyncResults::CONNECTION_ERROR); } buteo-syncfw-0.11.10/msyncd/SyncSession.h000066400000000000000000000205521477124122200202330ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCSESSION_H #define SYNCSESSION_H #include "SyncCommonDefs.h" #include "SyncResults.h" #include #include namespace Buteo { class SyncProfile; class PluginRunner; class StorageBooker; class NetworkManager; /*! \brief Class representing a single sync session * * The session can be initiated by a client or server plug-in */ class SyncSession : public QObject { Q_OBJECT public: /*! \brief Constructor * * @param aProfile SyncProfile associated with the session. With server * plug-in initiated sessions this can be null, if there is no profile with * a matching destination address. * @param aParent Parent object */ explicit SyncSession(SyncProfile *aProfile, QObject *aParent = 0); //! \brief Destructor virtual ~SyncSession(); /*! \brief Associates a plug-in runner with this session * * @param aPluginRunner The plug-in runner to use for this session * @param aTransferOwnership Does this session become the owner of the plug-in * runner instance. If so, the plug-in runner is deleted when the session is * deleted. */ void setPluginRunner(PluginRunner *aPluginRunner, bool aTransferOwnership); /*! \brief Gets the plug-in runner associated with this session * * @return Plug-in runner */ PluginRunner *pluginRunner(); /*! \brief Returns if the sync session is finished or in process * * @return Finished indicator */ bool isFinished(); /*! \brief Returns if the sync session was aborted * * @return Aborted indicator */ bool isAborted(); /*! \brief Starts the session using the associated plug-in runner * * @return Success indicator */ bool start(); /*! \brief Aborts the session. Returns when the abort request is sent. * \param - Status. */ void abort(Sync::SyncStatus aStatus = Sync::SYNC_ABORTED); /*! \brief Stops the session. Returns when the session is stopped. */ void stop(); /*! \brief Gets the sync profile used by this session * * @return Sync profile */ SyncProfile *profile() const; /*! \brief Gets the name of the profile used by this session * * @return Profile name */ QString profileName() const; /*! \brief Gets the results of the finished session. * * This function should be called only after the finished signal is received * from the session. * @return Sync results */ SyncResults results() const; /*! \brief Sets if the session was started by the scheduler * * @param aScheduled True if scheduled, false otherwise */ void setScheduled(bool aScheduled); /*! \brief Checks if the session was started by the scheduler * * @return True if scheduled, false otherwise */ bool isScheduled() const; /*! \brief Sets the results for this session * * This function can be used in error situations to set the results to this * session, even if the session has failed to run. * @param aResults The results to set */ void updateResults(const SyncResults &aResults); /*! \brief Sets the results for this session using the provided error code * * This function can be used in error situations to set the results to this * session, even if the session has failed to run. Time stamp for the results * is set to current time. * \param aMajorCode Error code * \param aMinorCode failed reason */ void setFailureResult(SyncResults::MajorCode aMajorCode, SyncResults::MinorCode aMinorCode); /*! \brief Tries to reserve storages needed by the session * * Successfully reserved storages are automatically released when the session * is deleted. * @param aStorageBooker Storake booker to use for reserving the storages. * If reserving is successfull, this booker is saved internally and used * later to release the storages when the session is deleted. * @return Success indicator. True if all storages were successfully reserved. * When false, no storages were reserved, meaning one or more of the needed * storages were already in use. */ bool reserveStorages(StorageBooker *aStorageBooker); //! \brief Releases storages that were reserved earlier with reserveStorages void releaseStorages(); //! \brief returns the StorageMap used for this session QMap getStorageMap(); /*! \brief sets the storage map for this session * * @param aStorageMap - storage map to set */ void setStorageMap(QMap &aStorageMap); //! \brief returns the returns the status of the profile creation for this session bool isProfileCreated(); //! \brief sets Profile Created flag to true void setProfileCreated(bool aProfileCreated); //! \brief Maps sync failure error code from stack to SyncStatus Sync::SyncStatus mapToSyncStatusError(int aErrorCode); signals: //! @see SyncPluginBase::transferProgress void transferProgress(const QString &aProfileName, Sync::TransferDatabase aDatabase, Sync::TransferType aType, const QString &aMimeType, int aCommittedItems); /*! \brief Signal sent when a storage is accquired * * @param aProfileName Name of the profile used by the session * @param aMimeType Mimetype of the storage accquired. */ void storageAccquired(const QString &aProfileName, const QString &aMimeType); /*! \brief Signal sent when the session has finished * * @param aProfileName Name of the profile used by the session * @param aStatus Status of the finished session * @param aMessage Possible textual message * @param aErrorCode Error code, if the status is error */ void finished(const QString &aProfileName, Sync::SyncStatus aStatus, const QString &aMessage, SyncResults::MinorCode aErrorCode); /*! \brief Signal sent when the sync is in progress to indicate the detail of the progress * * @param aProfileName Name of the profile used by the session * @param aProgressDetail Detail of the progress. */ void syncProgressDetail(const QString &aProfileName, int aProgressDetail); private: bool tryStart(); private slots: // Slots for catching plug-in runner signals. void onSuccess(const QString &aProfileName, const QString &aMessage); void onError(const QString &aProfileName, const QString &aMessage, SyncResults::MinorCode aErrorCode); void onTransferProgress(const QString &aProfileName, Sync::TransferDatabase aDatabase, Sync::TransferType aType, const QString &aMimeType, int aCommittedItems); void onStorageAccquired (const QString &aMimeType); void onSyncProgressDetail(const QString &aProfileName, int aProgressDetail); void onDone(); void onDestroyed(QObject *aPluginRunner); void onNetworkSessionOpened(); void onNetworkSessionError(); private: SyncProfile *iProfile; PluginRunner *iPluginRunner; SyncResults iResults; Sync::SyncStatus iStatus; SyncResults::MinorCode iErrorCode; bool iPluginRunnerOwned; bool iScheduled; bool iAborted; bool iStarted; bool iFinished; bool iCreateProfile; QString iMessage; QString iRemoteId; StorageBooker *iStorageBooker; QMap iStorageMap; NetworkManager *iNetworkManager; #ifdef SYNCFW_UNIT_TESTS friend class SyncSessionTest; #endif }; } #endif // SYNCSESSION_H buteo-syncfw-0.11.10/msyncd/SyncSigHandler.cpp000066400000000000000000000055271477124122200211700ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include #include #include #include #include #include "SyncSigHandler.h" #include "LogMacros.h" int SyncSigHandler::iSigHupFd[2]; int SyncSigHandler::iSigTermFd[2]; SyncSigHandler::SyncSigHandler(QObject *aParent, const char */*aName*/) : QObject(aParent) { FUNCTION_CALL_TRACE(lcButeoTrace); //Adding signal hanlder for unix Signals signal(SIGTERM, termSignalHandler); signal(SIGINT, termSignalHandler); signal(SIGHUP, hupSignalHandler); //Adding socketpair to monitor those fd's. if (::socketpair(AF_UNIX, SOCK_STREAM, 0, iSigHupFd)) { qCCritical(lcButeoMsyncd) << "Couldn't create HUP socketpair"; } if (::socketpair(AF_UNIX, SOCK_STREAM, 0, iSigTermFd)) { qCCritical(lcButeoMsyncd) << "Couldn't create TERM socketpair"; } //SocketNotifier for read those fd's. iSigHup = new QSocketNotifier(iSigHupFd[1], QSocketNotifier::Read, this); connect(iSigHup, SIGNAL(activated(int)), this, SLOT(handleSigHup())); iSigTerm = new QSocketNotifier(iSigTermFd[1], QSocketNotifier::Read, this); connect(iSigTerm, SIGNAL(activated(int)), this, SLOT(handleSigTerm())); } SyncSigHandler::~SyncSigHandler() { FUNCTION_CALL_TRACE(lcButeoTrace); delete iSigHup; iSigHup = 0; delete iSigTerm; iSigTerm = 0; } // Linux signal handler. //This application is shutdown by sending a SIGTERM to it. void SyncSigHandler::termSignalHandler(int /*signal*/) { char a = 1; ::write(iSigTermFd[0], &a, sizeof(a)); } void SyncSigHandler::hupSignalHandler(int /*signal*/) { // Do nothing } //Qt Slot will eventually get called corresponding to Unix signal. void SyncSigHandler::handleSigTerm() { FUNCTION_CALL_TRACE(lcButeoTrace); iSigTerm->setEnabled(false); char tmp; ::read(iSigTermFd[1], &tmp, sizeof(tmp)); // Doing Qt stuff.Exiting application QCoreApplication::exit(0); } void SyncSigHandler::handleSigHup() { FUNCTION_CALL_TRACE(lcButeoTrace); } buteo-syncfw-0.11.10/msyncd/SyncSigHandler.h000066400000000000000000000060621477124122200206300ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCSIGHANDLER_H #define SYNCSIGHANDLER_H #include /*! \brief About this class. * We can't call Qt functions from Unix signal handlers.We can only call async-signal-safe functions from signal handlers. * So this provides a way to use Unix signal handlers with Qt. The strategy is to have our Unix signal handler will eventually * cause a Qt signal to be emitted, and then we simply return from our Unix signal handler. * Back in our Qt program, that Qt signal gets emitted and then received by our Qt slot function, where we are safely doing Qt stuff * which weren't allowed to do in the Unix signal handler. * One simple way to make this happen is declares a socket pair in our class for each Unix signal we want to handle. * The socket pairs are declared as static data members.We also created a QSocketNotifier to monitor the read end of each socket pair, * declare your Unix signal handlers to be static class methods, and declare a slot function corresponding to each of our * Unix signal handlers. In this class, we intend to handle both the SIGHUP and SIGTERM signals. */ class SyncSigHandler : public QObject { Q_OBJECT public: /*! \brief Constructor * * @param aParent object * @param aName const char */ SyncSigHandler(QObject *aParent = 0, const char *aName = 0); /*! \brief Destructor * */ ~SyncSigHandler(); // Unix signal handlers. static void hupSignalHandler(int unused); static void termSignalHandler(int unused); public slots: /*! \brief QT signal handler to handle SIG_HUP * * @return None */ void handleSigHup(); /*! \brief QT signal handler to handle SIG_TERM * * @return None */ void handleSigTerm(); private: //socket pair for each Unix signal to handle static int iSigHupFd[2]; static int iSigTermFd[2]; //QSocketNotifier to monitor the read end of each socket pair, // declare your Unix signal handlers to be static class methods QSocketNotifier *iSigHup; QSocketNotifier *iSigTerm; #ifdef SYNCFW_UNIT_TESTS friend class SyncSigHandlerTest; #endif }; #endif // SYNCSIGHANDLER_H buteo-syncfw-0.11.10/msyncd/UnitTest.cpp000066400000000000000000000017121477124122200200620ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2014 Jolla Ltd. * * Contact: Valerio Valerio * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "UnitTest.h" #ifdef SYNCFW_UNIT_TESTS bool __SYNCFW_UNIT_TESTS_RUNTIME = true; #else bool __SYNCFW_UNIT_TESTS_RUNTIME = false; #endif buteo-syncfw-0.11.10/msyncd/UnitTest.h000066400000000000000000000023421477124122200175270ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2014 Jolla Ltd. * * Contact: Valerio Valerio * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef UNITTEST_H #define UNITTEST_H #include /*! * \brief A dirty hack to allow modified behavior during unit test execution. * * If you are writing a unit test, \c include(msyncd/unittest.pri) in your * project file. */ #define SYNCFW_UNIT_TESTS_RUNTIME Q_UNLIKELY(__SYNCFW_UNIT_TESTS_RUNTIME) /*! \cond __false */ extern bool __SYNCFW_UNIT_TESTS_RUNTIME; /*! \endcond __false */ #endif // UNITTEST_H buteo-syncfw-0.11.10/msyncd/bin/000077500000000000000000000000001477124122200163465ustar00rootroot00000000000000buteo-syncfw-0.11.10/msyncd/bin/msyncd.service000066400000000000000000000005671477124122200212350ustar00rootroot00000000000000[Unit] Description=Sync FW daemon Requires=dbus.socket booster-qt5.service After=pre-user-session.target booster-qt5.service [Service] # -G (--global-syms) so that msyncd's plugins can find symbols in msyncd and # in the libraries msyncd is linked to. ExecStart=/usr/bin/invoker -G -o -s --type=qt5 /usr/bin/msyncd Restart=always [Install] WantedBy=user-session.target buteo-syncfw-0.11.10/msyncd/com.meego.msyncd000066400000000000000000000002141477124122200206630ustar00rootroot00000000000000 com.meego.msyncd com.meego.msyncd /synchronizer buteo-syncfw-0.11.10/msyncd/com.meego.msyncd.service000066400000000000000000000000731477124122200223250ustar00rootroot00000000000000[D-BUS Service] Name=com.meego.msyncd Exec=/usr/bin/msyncd buteo-syncfw-0.11.10/msyncd/com.meego.msyncd.xml000066400000000000000000000133121477124122200214650ustar00rootroot00000000000000 buteo-syncfw-0.11.10/msyncd/generate_dbus_adaptor.sh000077500000000000000000000017731477124122200224660ustar00rootroot00000000000000#/* # * This file is part of buteo-syncfw package # * # * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). # * # * Contact: Sateesh Kavuri # * # * This library is free software; you can redistribute it and/or # * modify it under the terms of the GNU Lesser General Public License # * version 2.1 as published by the Free Software Foundation. # * # * 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 Lesser General Public # * License along with this library; if not, write to the Free Software # * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA # * 02110-1301 USA # * # */ # qdbuscpp2xml -M -S SyncDBusInterface.h -o com.meego.msyncd.xml qdbusxml2cpp -a SyncDBusAdaptor -c SyncDBusAdaptor com.meego.msyncd.xml buteo-syncfw-0.11.10/msyncd/generate_dbus_proxy.sh000077500000000000000000000016771477124122200222200ustar00rootroot00000000000000#/* # * This file is part of buteo-syncfw package # * # * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). # * # * Contact: Sateesh Kavuri # * # * This library is free software; you can redistribute it and/or # * modify it under the terms of the GNU Lesser General Public License # * version 2.1 as published by the Free Software Foundation. # * # * 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 Lesser General Public # * License along with this library; if not, write to the Free Software # * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA # * 02110-1301 USA # * # */ # qdbusxml2cpp -p SyncDaemonProxy -N -c SyncDaemonProxy com.meego.msyncd.xml buteo-syncfw-0.11.10/msyncd/gschemas/000077500000000000000000000000001477124122200173705ustar00rootroot00000000000000buteo-syncfw-0.11.10/msyncd/gschemas/com.meego.msyncd.gschema.xml000066400000000000000000000005761477124122200246750ustar00rootroot00000000000000 Scheduled Sync over cellular Allow scheduled syncs to run over cellular connections. true buteo-syncfw-0.11.10/msyncd/main.cpp000066400000000000000000000040271477124122200172310ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include #include #include #include #include "Logger.h" #include "synchronizer.h" #include "SyncSigHandler.h" #include "SyncCommonDefs.h" Q_DECL_EXPORT int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); Buteo::Synchronizer *synchronizer = new Buteo::Synchronizer(&app); if (!synchronizer->initialize()) { delete synchronizer; synchronizer = 0; return -1; } QString msyncConfigSyncDir = Sync::syncConfigDir() + QDir::separator() + "sync"; // Make sure we have the msyncd/sync directory QDir syncDir; syncDir.mkpath(msyncConfigSyncDir); Buteo::configureLegacyLogging(); // Note:- Since we can't call Qt functions from Unix signal handlers. // This class provide handling unix signal. SyncSigHandler *sigHandler = new SyncSigHandler(); qCDebug(lcButeoMsyncd) << "Entering event loop"; int returnValue = app.exec(); qCDebug(lcButeoMsyncd) << "Exiting event loop"; synchronizer->close(); delete synchronizer; synchronizer = 0; delete sigHandler; sigHandler = 0; qDebug() << "Exiting program"; return returnValue; } buteo-syncfw-0.11.10/msyncd/msyncd-app.pro000066400000000000000000000020251477124122200203720ustar00rootroot00000000000000TEMPLATE = app TARGET = msyncd QT += xml \ dbus \ sql \ network QT -= gui CONFIG += \ link_pkgconfig \ link_prl DEPENDPATH += . INCLUDEPATH += . \ ../ \ ../libbuteosyncfw/pluginmgr \ ../libbuteosyncfw/common \ ../libbuteosyncfw/profile PRE_TARGETDEPS += libmsyncd.a PKGCONFIG += dbus-1 libsignon-qt5 accounts-qt5 LIBS += -lbuteosyncfw5 packagesExist(qt5-boostable) { DEFINES += HAS_BOOSTER PKGCONFIG += qt5-boostable } else { warning("qt5-boostable not available; startup times will be slower") } QMAKE_LIBDIR_QT += ../libsyncprofile/ LIBS += -L../libbuteosyncfw LIBS += -L. -lmsyncd # Input SOURCES += \ main.cpp \ UnitTest.cpp # install target.path = /usr/bin/ service.files = bin/msyncd.service service.path = /usr/lib/systemd/user/ syncwidget.path = /etc/syncwidget/ syncwidget.files = com.meego.msyncd gschemas.path = /usr/share/glib-2.0/schemas gschemas.files = gschemas/com.meego.msyncd.gschema.xml INSTALLS += target \ syncwidget \ service \ gschemas buteo-syncfw-0.11.10/msyncd/msyncd-lib.pro000066400000000000000000000040721477124122200203640ustar00rootroot00000000000000TEMPLATE = lib CONFIG += staticlib TARGET = msyncd QT += xml \ dbus \ sql \ network QT -= gui CONFIG += \ link_pkgconfig \ create_prl DEPENDPATH += . INCLUDEPATH += . \ ../ \ ../libbuteosyncfw/pluginmgr \ ../libbuteosyncfw/common \ ../libbuteosyncfw/profile PKGCONFIG += dbus-1 gio-2.0 libsignon-qt5 accounts-qt5 packagesExist(mce-qt5) { PKGCONFIG += mce-qt5 DEFINES += HAS_MCE } else { message("mce-qt5 not found, MCE support disabled") } LIBS += -lbuteosyncfw5 packagesExist(qt5-boostable) { DEFINES += HAS_BOOSTER PKGCONFIG += qt5-boostable } else { warning("qt5-boostable not available; startup times will be slower") } QMAKE_LIBDIR_QT += ../libsyncprofile/ LIBS += -L../libbuteosyncfw # Input HEADERS += ServerActivator.h \ synchronizer.h \ SyncDBusInterface.h \ SyncBackupProxy.h \ SyncDBusAdaptor.h \ SyncBackupAdaptor.h \ ClientThread.h \ ServerThread.h \ StorageBooker.h \ SyncQueue.h \ SyncScheduler.h \ SyncBackup.h \ AccountsHelper.h \ SyncSession.h \ PluginRunner.h \ ClientPluginRunner.h \ ServerPluginRunner.h \ SyncSigHandler.h \ StorageChangeNotifier.h \ SyncOnChange.h \ SyncOnChangeScheduler.h SOURCES += ServerActivator.cpp \ synchronizer.cpp \ SyncDBusAdaptor.cpp \ SyncBackupAdaptor.cpp \ ClientThread.cpp \ ServerThread.cpp \ StorageBooker.cpp \ SyncQueue.cpp \ SyncScheduler.cpp \ SyncBackup.cpp \ AccountsHelper.cpp \ SyncSession.cpp \ PluginRunner.cpp \ ClientPluginRunner.cpp \ ServerPluginRunner.cpp \ SyncSigHandler.cpp \ StorageChangeNotifier.cpp \ SyncOnChange.cpp \ SyncOnChangeScheduler.cpp contains(DEFINES, USE_KEEPALIVE) { PKGCONFIG += keepalive HEADERS += \ BackgroundSync.h SOURCES += \ BackgroundSync.cpp } else:contains(DEFINES, USE_IPHB) { PKGCONFIG += libiphb HEADERS += \ IPHeartBeat.h \ SyncAlarmInventory.h SOURCES += \ IPHeartBeat.cpp \ SyncAlarmInventory.cpp } buteo-syncfw-0.11.10/msyncd/msyncd.pro000066400000000000000000000001151477124122200176120ustar00rootroot00000000000000TEMPLATE = subdirs CONFIG += ordered SUBDIRS = msyncd-lib.pro msyncd-app.pro buteo-syncfw-0.11.10/msyncd/synchronizer.cpp000066400000000000000000002323331477124122200210450ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2014-2019 Jolla Ltd. * Copyright (C) 2020 Open Mobile Platform LLC. * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include #include "synchronizer.h" #include "SyncDBusAdaptor.h" #include "SyncSession.h" #include "ClientPluginRunner.h" #include "ServerPluginRunner.h" #include "AccountsHelper.h" #include "NetworkManager.h" #include "TransportTracker.h" #include "ServerActivator.h" #include "SyncCommonDefs.h" #include "StoragePlugin.h" #include "SyncLog.h" #include "ClientPlugin.h" #include "ServerPlugin.h" #include "ProfileFactory.h" #include "ProfileEngineDefs.h" #include "LogMacros.h" #include "BtHelper.h" #ifdef HAS_MCE #include #include #endif #include #include #include using namespace Buteo; static const QString SYNC_DBUS_OBJECT = "/synchronizer"; static const QString SYNC_DBUS_SERVICE = "com.meego.msyncd"; static const QString BT_PROPERTIES_NAME = "Name"; class Buteo::BatteryInfo { #ifdef HAS_MCE public: bool isLowPower() { return (iBatteryStatus.valid() && iBatteryStatus.status() <= QMceBatteryStatus::Low); } bool inPowerSaveMode() { return (iPowerSaveMode.valid() && iPowerSaveMode.active()); } private: QMcePowerSaveMode iPowerSaveMode; QMceBatteryStatus iBatteryStatus; #else public: bool isLowPower() { return false; } bool inPowerSaveMode() { return false; } #endif }; Synchronizer::Synchronizer(QCoreApplication *aApplication) : iNetworkManager(0) , iSyncScheduler(0) , iSyncBackup(0) , iTransportTracker(0) , iServerActivator(0) , iAccounts(0) , iClosing(false) , iSOCEnabled(false) , iSyncUIInterface(nullptr) , iBatteryInfo(new BatteryInfo) { iSettings = g_settings_new_with_path("com.meego.msyncd", "/com/meego/msyncd/"); FUNCTION_CALL_TRACE(lcButeoTrace); this->setParent(aApplication); iProfileChangeTriggerTimer.setSingleShot(true); connect(&iProfileChangeTriggerTimer, &QTimer::timeout, this, &Synchronizer::profileChangeTriggerTimeout); } Synchronizer::~Synchronizer() { FUNCTION_CALL_TRACE(lcButeoTrace); delete iSyncUIInterface; iSyncUIInterface = nullptr; g_object_unref(iSettings); delete iBatteryInfo; } bool Synchronizer::initialize() { FUNCTION_CALL_TRACE(lcButeoTrace); qCDebug(lcButeoMsyncd) << "Starting msyncd"; // Create a D-Bus adaptor. It will get deleted when the Synchronizer is // deleted. new SyncDBusAdaptor(this); // Register our object on the session bus and expose interface to others. QDBusConnection dbus = QDBusConnection::sessionBus(); if (!dbus.registerObject(SYNC_DBUS_OBJECT, this) || !dbus.registerService(SYNC_DBUS_SERVICE)) { qCCritical(lcButeoMsyncd) << "Failed to register to D-Bus (D-Bus not started or msyncd already running?), aborting start"; return false; } else { qCDebug(lcButeoMsyncd) << "Registered to D-Bus"; } // else ok connect(this, SIGNAL(syncStatus(QString, int, QString, int)), this, SLOT(slotSyncStatus(QString, int, QString, int)), Qt::QueuedConnection); // use queued connection because the profile will be stored after the signal connect(&iProfileManager, SIGNAL(signalProfileChanged(QString, int, QString)), this, SLOT(slotProfileChanged(QString, int, QString)), Qt::QueuedConnection); iNetworkManager = new NetworkManager(this); iTransportTracker = new TransportTracker(this); iServerActivator = new ServerActivator(iProfileManager, *iTransportTracker, this); connect(iTransportTracker, SIGNAL(networkStateChanged(bool, Sync::InternetConnectionType)), SLOT(onNetworkStateChanged(bool, Sync::InternetConnectionType))); // Initialize account manager. iAccounts = new AccountsHelper(iProfileManager, this); // Deleted with parent. connect(iAccounts, SIGNAL(enableSOC(QString)), this, SLOT(enableSOCSlot(QString)), Qt::QueuedConnection); connect(iAccounts, SIGNAL(scheduleUpdated(QString)), this, SLOT(reschedule(QString)), Qt::QueuedConnection); connect(iAccounts, SIGNAL(removeProfile(QString)), this, SLOT(removeProfile(QString)), Qt::QueuedConnection); connect(iAccounts, SIGNAL(removeScheduledSync(QString)), this, SLOT(removeScheduledSync(QString)), Qt::QueuedConnection); connect(this, SIGNAL(storageReleased()), this, SLOT(onStorageReleased()), Qt::QueuedConnection); startServers(); // For Backup/restore handling iSyncBackup = new SyncBackup(); // Initialize scheduler initializeScheduler(); // Connect backup signals after the scheduler has been initialized connect(iSyncBackup, SIGNAL(startBackup()), this, SLOT(backupStarts())); connect(iSyncBackup, SIGNAL(backupDone()), this, SLOT(backupFinished())); connect(iSyncBackup, SIGNAL(startRestore()), this, SLOT(restoreStarts())); connect(iSyncBackup, SIGNAL(restoreDone()), this, SLOT(restoreFinished())); //For Sync On Change // enable SOC for contacts only as of now QHash > aSOCStorageMap; //TODO can we do away with hard coding storage (plug-in) names, in other words do they //have to be well known this way QList SOCProfiles = iProfileManager.getSOCProfilesForStorage("./contacts"); if (SOCProfiles.count()) { // TODO Come up with a scheme to avoid hard-coding, this is not done just here // but also for storage plug-ins in onStorageAquired aSOCStorageMap["hcontacts"] = SOCProfiles; QStringList aFailedStorages; bool isSOCEnabled = iSyncOnChange.enable(aSOCStorageMap, &iSyncOnChangeScheduler, &iPluginManager, aFailedStorages); if (!isSOCEnabled) { foreach (const QString &aStorageName, aFailedStorages) { qCCritical(lcButeoMsyncd) << "Sync on change couldn't be enabled for storage" << aStorageName; } } else { QObject::connect(&iSyncOnChangeScheduler, SIGNAL(syncNow(QString)), this, SLOT(startScheduledSync(QString)), Qt::QueuedConnection); iSOCEnabled = true; } } else { qCDebug(lcButeoMsyncd) << "No profiles interested in SOC"; } return true; } void Synchronizer::enableSOCSlot(const QString &aProfileName) { FUNCTION_CALL_TRACE(lcButeoTrace); SyncProfile *profile = iProfileManager.syncProfile(aProfileName); if (profile && profile->isSOCProfile()) { if (iSOCEnabled) { iSyncOnChange.addProfile("hcontacts", profile); } else { QHash > aSOCStorageMap; QList SOCProfiles; SOCProfiles.append(profile); aSOCStorageMap["hcontacts"] = SOCProfiles; QStringList aFailedStorages; if (iSyncOnChange.enable(aSOCStorageMap, &iSyncOnChangeScheduler, &iPluginManager, aFailedStorages)) { QObject::connect(&iSyncOnChangeScheduler, SIGNAL(syncNow(const QString &)), this, SLOT(startScheduledSync(const QString &)), Qt::QueuedConnection); iSOCEnabled = true; qCDebug(lcButeoMsyncd) << "Sync on change enabled for profile" << aProfileName; } else { qCCritical(lcButeoMsyncd) << "Sync on change couldn't be enabled for profile" << aProfileName; } } } else { delete profile; } } void Synchronizer::close() { FUNCTION_CALL_TRACE(lcButeoTrace); qCDebug(lcButeoMsyncd) << "Stopping msyncd"; iClosing = true; // Stop running sessions if (iSOCEnabled) { iSyncOnChange.disable(); } QList sessions = iActiveSessions.values(); foreach (SyncSession *session, sessions) { if (session != 0) { session->stop(); } } qDeleteAll(sessions); iActiveSessions.clear(); stopServers(); delete iSyncScheduler; iSyncScheduler = 0; delete iSyncBackup; iSyncBackup = 0; // Unregister from D-Bus. QDBusConnection dbus = QDBusConnection::sessionBus(); dbus.unregisterObject(SYNC_DBUS_OBJECT); if (!dbus.unregisterService(SYNC_DBUS_SERVICE)) { qCWarning(lcButeoMsyncd) << "Failed to unregister from D-Bus"; } else { qCDebug(lcButeoMsyncd) << "Unregistered from D-Bus"; } } bool Synchronizer::startSync(QString aProfileName) { FUNCTION_CALL_TRACE(lcButeoTrace); // Manually triggered sync. return startSync(aProfileName, false); } bool Synchronizer::startScheduledSync(QString aProfileName) { FUNCTION_CALL_TRACE(lcButeoTrace); SyncProfile *profile = iProfileManager.syncProfile(aProfileName); // All scheduled syncs are online syncs // Add this to the waiting online syncs and it will be started when we // receive a session connection status from the NetworkManager bool accept = acceptScheduledSync(iNetworkManager->isOnline(), iNetworkManager->connectionType(), profile); if (accept) { /* Ensure that current time is compatible with sync schedule. The Background process may have started a sync in a period where sync is disabled due to delayed interval wake up. */ bool wrongTime = (profile && !profile->syncSchedule().isSyncScheduled(QDateTime::currentDateTime(), profile->lastSuccessfulSyncTime())); if (wrongTime) { qCDebug(lcButeoMsyncd) << "Woken up of" << aProfileName << "in a disabled period, not starting sync."; if (iSyncScheduler) { // can be null if in backup/restore state. iSyncScheduler->syncStatusChanged(aProfileName, Sync::SYNC_NOTPOSSIBLE, QLatin1String("Sync cancelled due to wrong wake up"), Buteo::SyncResults::ABORTED); } } else { qCDebug(lcButeoMsyncd) << "Scheduled sync of" << aProfileName << "accepted with current connection type" << iNetworkManager->connectionType(); startSync(aProfileName, true); } } else { qCInfo(lcButeoMsyncd) << "Wait for internet connection:" << aProfileName; if (iNetworkManager->isOnline()) { // see acceptScheduledSync() for the determination of whether the connection type is allowed for sync operations. qCInfo(lcButeoMsyncd) << "Connection" << iNetworkManager->connectionType() << "is of disallowed type. The sync will be postponed until an allowed connection is available."; } else { qCInfo(lcButeoMsyncd) << "Device offline. Wait for internet connection."; } if (!iWaitingOnlineSyncs.contains(aProfileName)) { iWaitingOnlineSyncs.append(aProfileName); } qCDebug(lcButeoMsyncd) << "Marking" << aProfileName << "sync as NOTPOSSIBLE due to connectivity status"; if (iSyncScheduler) { // can be null if in backup/restore state. iSyncScheduler->syncStatusChanged(aProfileName, Sync::SYNC_NOTPOSSIBLE, QLatin1String("No internet connectivity or connectivity type restricted for sync"), Buteo::SyncResults::OFFLINE_MODE); } } delete profile; return true; } bool Synchronizer::setSyncSchedule(QString aProfileId, QString aScheduleAsXml) { bool status = false; if (iProfileManager.setSyncSchedule(aProfileId, aScheduleAsXml)) { reschedule(aProfileId); status = true; } return status; } bool Synchronizer::saveSyncResults(QString aProfileId, QString aSyncResults) { QDomDocument doc; bool status = false; if (doc.setContent(aSyncResults, true)) { Buteo::SyncResults results(doc.documentElement()); status = iProfileManager.saveSyncResults(aProfileId, results); } else { qCCritical(lcButeoMsyncd) << "Invalid Profile Xml Received from msyncd"; } return status; } QString Synchronizer::createSyncProfileForAccount(uint aAccountId) { iAccounts->createProfileForAccount(aAccountId); // There could be profiles created for every service. There is no // point to give back a profile name. return QString(); } bool Synchronizer::startSync(const QString &aProfileName, bool aScheduled) { FUNCTION_CALL_TRACE(lcButeoTrace); bool success = false; if (isBackupRestoreInProgress()) { SyncResults syncResults(QDateTime::currentDateTime(), SyncResults::SYNC_RESULT_FAILED, Buteo::SyncResults::BACKUP_IN_PROGRESS); iProfileManager.saveSyncResults(aProfileName, syncResults); emit syncStatus(aProfileName, Sync::SYNC_NOTPOSSIBLE, "Backup in progress, cannot start sync", Buteo::SyncResults::BACKUP_IN_PROGRESS); return success; } qCDebug(lcButeoMsyncd) << "Start sync requested for profile:" << aProfileName; // This function can be called from a client app as manual sync: // If we receive a manual sync to a profile that is peding to sync due a // data change we can remove it from the iSyncOnChangeScheduler, to avoid a // second sync. iSyncOnChangeScheduler.removeProfile(aProfileName); // Do the same if the profile is pending sync due to a profile change. for (int i = iProfileChangeTriggerQueue.size() - 1; i >= 0; --i) { if (iProfileChangeTriggerQueue[i].first == aProfileName) { qCDebug(lcButeoMsyncd) << "Removing queued profile change sync due to sync trigger:" << aProfileName; iProfileChangeTriggerQueue.removeAt(i); } } if (iActiveSessions.contains(aProfileName)) { qCDebug(lcButeoMsyncd) << "Sync already in progress"; return true; } else if (iSyncQueue.contains(aProfileName)) { qCDebug(lcButeoMsyncd) << "Sync request already in queue"; emit syncStatus(aProfileName, Sync::SYNC_QUEUED, "", 0); return true; } else if (!aScheduled && iWaitingOnlineSyncs.contains(aProfileName)) { // Manual sync is allowed to happen in any kind of connection // if sync is not scheduled remove it from iWaitingOnlineSyncs to avoid // sync it twice later iWaitingOnlineSyncs.removeOne(aProfileName); qCDebug(lcButeoMsyncd) << "Removing" << aProfileName << "from online waiting list."; } SyncProfile *profile = iProfileManager.syncProfile(aProfileName); if (!profile) { qCWarning(lcButeoMsyncd) << "Profile not found"; SyncResults syncResults(QDateTime::currentDateTime(), SyncResults::SYNC_RESULT_FAILED, Buteo::SyncResults::INTERNAL_ERROR); iProfileManager.saveSyncResults(aProfileName, syncResults); emit syncStatus(aProfileName, Sync::SYNC_ERROR, "Internal Error", Buteo::SyncResults::INTERNAL_ERROR); return false; } else if (false == profile->isEnabled()) { qCWarning(lcButeoMsyncd) << "Profile is disabled, not starting sync"; SyncResults syncResults(QDateTime::currentDateTime(), SyncResults::SYNC_RESULT_FAILED, Buteo::SyncResults::INTERNAL_ERROR); iProfileManager.saveSyncResults(aProfileName, syncResults); emit syncStatus(aProfileName, Sync::SYNC_ERROR, "Internal Error", Buteo::SyncResults::INTERNAL_ERROR); delete profile; return false; } SyncSession *session = new SyncSession(profile, this); session->setScheduled(aScheduled); if (profile->clientProfile() && clientProfileActive(profile->clientProfile()->name())) { qCDebug(lcButeoMsyncd) << "Sync request of the same type in progress, adding request to the sync queue"; iSyncQueue.enqueue(session); emit syncStatus(aProfileName, Sync::SYNC_QUEUED, "", 0); return false; } // @todo: Complete profile with data from account manager. //iAccounts->addAccountData(*profile); if (!profile->isValid()) { qCWarning(lcButeoMsyncd) << "Profile is not valid"; session->setFailureResult(SyncResults::SYNC_RESULT_FAILED, Buteo::SyncResults::INTERNAL_ERROR); emit syncStatus(aProfileName, Sync::SYNC_ERROR, "Internal Error", Buteo::SyncResults::INTERNAL_ERROR); } else if (aScheduled && iBatteryInfo->isLowPower()) { qCWarning(lcButeoMsyncd) << "Low power, scheduled sync for profile" << aProfileName << "aborted"; session->setFailureResult(SyncResults::SYNC_RESULT_FAILED, Buteo::SyncResults::LOW_BATTERY_POWER); emit syncStatus(aProfileName, Sync::SYNC_ERROR, "Low battery", Buteo::SyncResults::LOW_BATTERY_POWER); } else if (aScheduled && iBatteryInfo->inPowerSaveMode()) { qCWarning(lcButeoMsyncd) << "Power save mode active, scheduled sync for profile" << aProfileName << "aborted"; session->setFailureResult(SyncResults::SYNC_RESULT_FAILED, Buteo::SyncResults::POWER_SAVING_MODE); emit syncStatus(aProfileName, Sync::SYNC_ERROR, "Power Save Mode active", Buteo::SyncResults::POWER_SAVING_MODE); } else if (!session->reserveStorages(&iStorageBooker)) { qCDebug(lcButeoMsyncd) << "Needed storage(s) already in use, queuing sync request"; iSyncQueue.enqueue(session); emit syncStatus(aProfileName, Sync::SYNC_QUEUED, "", 0); success = true; } else { // Sync can be started now. success = startSyncNow(session); if (success) { emit syncStatus(aProfileName, Sync::SYNC_STARTED, "", 0); } else { qCWarning(lcButeoMsyncd) << "Internal error, unable to start sync session for profile:" << aProfileName; session->setFailureResult(SyncResults::SYNC_RESULT_FAILED, Buteo::SyncResults::INTERNAL_ERROR); emit syncStatus(aProfileName, Sync::SYNC_ERROR, "Internal Error", Buteo::SyncResults::INTERNAL_ERROR); } } if (!success) { cleanupSession(session, Sync::SYNC_ERROR); } return success; } bool Synchronizer::startSyncNow(SyncSession *aSession) { FUNCTION_CALL_TRACE(lcButeoTrace); if (!aSession || isBackupRestoreInProgress()) { qCWarning(lcButeoMsyncd) << "Session is null || backup in progress"; return false; } SyncProfile *profile = aSession->profile(); if (!profile) { qCWarning(lcButeoMsyncd) << "Profile in session is null"; return false; } qCDebug(lcButeoMsyncd) << "Starting sync with profile" << aSession->profileName(); Profile *clientProfile = profile->clientProfile(); if (clientProfile == 0) { qCWarning(lcButeoMsyncd) << "Could not find client sub-profile"; return false; } qCDebug(lcButeoMsyncd) << "Disable sync on change:" << iSOCEnabled << profile->isSOCProfile(); //As sync is ongoing, disable sync on change for now, we can query later if //there are changes. if (iSOCEnabled) { if (profile->isSOCProfile()) { iSyncOnChange.disable(); } else { iSyncOnChange.disableNext(); } } iProfileManager.addRetriesInfo(profile); PluginRunner *pluginRunner = new ClientPluginRunner( clientProfile->name(), aSession->profile(), &iPluginManager, this, this); aSession->setPluginRunner(pluginRunner, true); if (!pluginRunner->init()) { qCWarning(lcButeoMsyncd) << "Failed to initialize client plug-in runner"; return false; } // Relay connectivity state change signal to plug-in runner. connect(iTransportTracker, SIGNAL(connectivityStateChanged(Sync::ConnectivityType, bool)), pluginRunner, SIGNAL(connectivityStateChanged(Sync::ConnectivityType, bool))); qCDebug(lcButeoMsyncd) << "Client plug-in runner initialized"; // Connect signals from sync session. connect(aSession, SIGNAL(transferProgress(const QString &, Sync::TransferDatabase, Sync::TransferType, const QString &, int)), this, SLOT(onTransferProgress(const QString &, Sync::TransferDatabase, Sync::TransferType, const QString &, int))); connect(aSession, SIGNAL(storageAccquired(const QString &, const QString &)), this, SLOT(onStorageAccquired(const QString &, const QString &))); connect(aSession, SIGNAL(syncProgressDetail(const QString &, int)), this, SLOT(onSyncProgressDetail(const QString &, int))); connect(aSession, &SyncSession::finished, this, &Synchronizer::onSessionFinished); if (aSession->start()) { // Get the DBUS interface for sync-UI. qCDebug(lcButeoMsyncd) << "sync-ui dbus interface is getting called"; if (aSession->isScheduled() && !profile->isHidden()) { if (iSyncUIInterface == nullptr) { qCDebug(lcButeoMsyncd) << "iSyncUIInterface is Null"; iSyncUIInterface = new QDBusInterface("com.nokia.syncui", "/org/maemo/m", "com.nokia.MApplicationIf", QDBusConnection::sessionBus()); } else if (!iSyncUIInterface->isValid()) { qCDebug(lcButeoMsyncd) << "iSyncUIInterface is not valid"; delete iSyncUIInterface; iSyncUIInterface = nullptr; iSyncUIInterface = new QDBusInterface("com.nokia.syncui", "/org/maemo/m", "com.nokia.MApplicationIf", QDBusConnection::sessionBus()); } //calling launch with argument list QStringList list; list.append("launching"); QList argumentList; argumentList << qVariantFromValue(list); iSyncUIInterface->asyncCallWithArgumentList(QLatin1String("launch"), argumentList); } qCDebug(lcButeoMsyncd) << "Sync session started"; iActiveSessions.insert(aSession->profileName(), aSession); } else { qCWarning(lcButeoMsyncd) << "Failed to start sync session"; return false; } return true; } void Synchronizer::onSessionFinished(const QString &aProfileName, Sync::SyncStatus aStatus, const QString &aMessage, SyncResults::MinorCode aErrorCode) { FUNCTION_CALL_TRACE(lcButeoTrace); qCDebug(lcButeoMsyncd) << "Session finished:" << aProfileName << ", status:" << aStatus; if (iActiveSessions.contains(aProfileName)) { SyncSession *session = iActiveSessions[aProfileName]; if (session) { switch (aStatus) { case Sync::SYNC_DONE: { bool enabledUpdated = false, visibleUpdated = false; QMap storageMap = session->getStorageMap(); SyncProfile *sessionProf = session->profile(); iProfileManager.enableStorages(*sessionProf, storageMap, &enabledUpdated); // If caps have not been modified, i.e. fetched from the remote device yet, set // enabled storages also visible. If caps have been modified, we must not touch visibility anymore if (sessionProf->boolKey(KEY_CAPS_MODIFIED) == false) { iProfileManager.setStoragesVisible(*sessionProf, storageMap, &visibleUpdated); } if (enabledUpdated || visibleUpdated) { iProfileManager.updateProfile(*sessionProf); } iProfileManager.retriesDone(sessionProf->name()); break; } case Sync::SYNC_ABORTED: case Sync::SYNC_CANCELLED: { session->setFailureResult(SyncResults::SYNC_RESULT_CANCELLED, Buteo::SyncResults::ABORTED); if (session->isProfileCreated()) { iProfileManager.removeProfile(session->profileName()); } break; } case Sync::SYNC_ERROR: { session->setFailureResult(SyncResults::SYNC_RESULT_FAILED, aErrorCode); if (session->isProfileCreated()) { iProfileManager.removeProfile(session->profileName()); } QDateTime nextRetryInterval = iProfileManager.getNextRetryInterval(session->profile()); if (nextRetryInterval.isValid()) { if (iSyncScheduler) { // can be null if in backup/restore state. iSyncScheduler->addProfileForSyncRetry(session->profile(), nextRetryInterval); } } else { iProfileManager.retriesDone(session->profile()->name()); } break; } default: qCWarning(lcButeoMsyncd) << "Unhandled Status in onSessionFinished" << aStatus; break; } iActiveSessions.remove(aProfileName); if (session->isScheduled()) { // Calling this multiple times has no effect, even if the // session was not actually opened iNetworkManager->disconnectSession(); } cleanupSession(session, aStatus); if (iProfilesToRemove.contains(aProfileName)) { cleanupProfile(aProfileName); iProfilesToRemove.removeAll(aProfileName); } if (session->isAborted() && (iActiveSessions.size() == 0) && isBackupRestoreInProgress()) { stopServers(); iSyncBackup->sendReply(0); } } else { qCWarning(lcButeoMsyncd) << "Session found in active sessions, but is NULL"; } } else { qCWarning(lcButeoMsyncd) << "Session not found from active sessions"; } emit syncStatus(aProfileName, aStatus, aMessage, aErrorCode); emit syncDone(aProfileName); //Re-enable sync on change if (iSOCEnabled) { iSyncOnChange.enable(); } // Try starting new sync sessions waiting in the queue. while (startNextSync()) { //intentionally empty } } void Synchronizer::onSyncProgressDetail(const QString &aProfileName, int aProgressDetail) { FUNCTION_CALL_TRACE(lcButeoTrace); qCDebug(lcButeoMsyncd) << "aProfileName" << aProfileName; emit syncStatus(aProfileName, Sync::SYNC_PROGRESS, "Sync Progress", aProgressDetail); } bool Synchronizer::startNextSync() { FUNCTION_CALL_TRACE(lcButeoTrace); if (iSyncQueue.isEmpty() || isBackupRestoreInProgress()) { return false; } bool tryNext = true; SyncSession *session = iSyncQueue.head(); if (session == 0) { qCWarning(lcButeoMsyncd) << "Null session found from queue"; iSyncQueue.dequeue(); return true; } SyncProfile *profile = session->profile(); if (profile == 0) { qCWarning(lcButeoMsyncd) << "Null profile found from queued session"; cleanupSession(session, Sync::SYNC_ERROR); iSyncQueue.dequeue(); return true; } QString profileName = session->profileName(); qCDebug(lcButeoMsyncd) << "Trying to start next sync in queue. Profile:" << profileName << session->isScheduled(); if (session->isScheduled() && iBatteryInfo->isLowPower()) { qCWarning(lcButeoMsyncd) << "Low power, scheduled sync aborted"; iSyncQueue.dequeue(); session->setFailureResult(SyncResults::SYNC_RESULT_FAILED, Buteo::SyncResults::LOW_BATTERY_POWER); cleanupSession(session, Sync::SYNC_ERROR); emit syncStatus(profileName, Sync::SYNC_ERROR, "Low Battery", Buteo::SyncResults::LOW_BATTERY_POWER); tryNext = true; } else if (!session->reserveStorages(&iStorageBooker)) { qCDebug(lcButeoMsyncd) << "Needed storage(s) already in use"; tryNext = false; } else if (clientProfileActive(profile->clientProfile()->name())) { qCDebug(lcButeoMsyncd) << "Client profile active, wait for finish"; return false; } else { // Sync can be started now. iSyncQueue.dequeue(); if (startSyncNow(session)) { emit syncStatus(profileName, Sync::SYNC_STARTED, "", 0); } else { qCWarning(lcButeoMsyncd) << "unable to start sync with session:" << session->profileName(); session->setFailureResult(SyncResults::SYNC_RESULT_FAILED, Buteo::SyncResults::INTERNAL_ERROR); cleanupSession(session, Sync::SYNC_ERROR); emit syncStatus(profileName, Sync::SYNC_ERROR, "Internal Error", Buteo::SyncResults::INTERNAL_ERROR); } tryNext = true; } return tryNext; } void Synchronizer::cleanupSession(SyncSession *aSession, Sync::SyncStatus aStatus) { FUNCTION_CALL_TRACE(lcButeoTrace); if (aSession != 0) { QString profileName = aSession->profileName(); if (!profileName.isEmpty()) { qCDebug(lcButeoMsyncd) << "Clean up session for profile" << profileName; SyncProfile *profile = aSession->profile(); if ((profile->lastResults() == 0) && (aStatus == Sync::SYNC_DONE)) { iProfileManager.saveRemoteTargetId(*profile, aSession->results().getTargetId()); } iProfileManager.saveSyncResults(profileName, aSession->results()); // UI needs to know that Sync Log has been updated. emit resultsAvailable(profileName, aSession->results().toString()); if (aSession->isScheduled()) { reschedule(profileName); emit signalProfileChanged(profileName, 1, QString()); } } aSession->setProfileCreated(false); aSession->releaseStorages(); aSession->deleteLater(); aSession = 0; } } void Synchronizer::abortSync(QString aProfileName) { FUNCTION_CALL_TRACE(lcButeoTrace); qCDebug(lcButeoMsyncd) << "Abort sync requested for profile: " << aProfileName; if (iActiveSessions.contains(aProfileName)) { iActiveSessions[aProfileName]->abort(); } else { qCWarning(lcButeoMsyncd) << "No sync in progress with the given profile"; // Check if sync was queued, in which case, remove it from the queue SyncSession *queuedSession = iSyncQueue.dequeue(aProfileName); if (queuedSession) { qCDebug(lcButeoMsyncd) << "Removed queued sync" << aProfileName; delete queuedSession; } SyncResults syncResults(QDateTime::currentDateTime(), SyncResults::SYNC_RESULT_CANCELLED, Buteo::SyncResults::ABORTED); iProfileManager.saveSyncResults(aProfileName, syncResults); emit syncStatus(aProfileName, Sync::SYNC_CANCELLED, "", Buteo::SyncResults::ABORTED); } } bool Synchronizer::cleanupProfile(const QString &aProfileId) { FUNCTION_CALL_TRACE(lcButeoTrace); // We assume this call is made on a Sync Profile SyncProfile *profile = iProfileManager.syncProfile (aProfileId); bool status = false; if (!aProfileId.isEmpty() && profile) { bool client = true ; Profile *subProfile = profile->clientProfile(); // client or server if (!subProfile) { qCWarning(lcButeoMsyncd) << "Could not find client sub-profile"; subProfile = profile->serverProfile (); client = false; if (!subProfile) { qCWarning(lcButeoMsyncd) << "Could not find server sub-profile"; return status; } } if (profile->syncType() == SyncProfile::SYNC_SCHEDULED && iSyncScheduler) { iSyncScheduler->removeProfile(aProfileId); } // Remove external sync status if it exist removeExternalSyncStatus(profile); PluginRunner *pluginRunner; if (client) { pluginRunner = new ClientPluginRunner(subProfile->name(), profile, &iPluginManager, this, this); } else { pluginRunner = new ServerPluginRunner(subProfile->name(), profile, &iPluginManager, this, iServerActivator, this); } if (!pluginRunner->init()) { qCWarning(lcButeoMsyncd) << "Failed to initialize client plug-in runner"; delete profile; return status; } const SyncResults *syncResults = profile->lastResults(); if (!pluginRunner->cleanUp() && syncResults) { qCCritical(lcButeoMsyncd) << "Error in removing anchors, sync session "; } else { qCDebug(lcButeoMsyncd) << "Removing the profile"; iProfileManager.removeProfile(aProfileId); status = true; } delete profile; delete pluginRunner; } return status; } bool Synchronizer::clientProfileActive(const QString &clientProfileName) { QList activeSessions = iActiveSessions.values(); foreach (SyncSession *session, activeSessions) { if (session->profile()) { SyncProfile *profile = session->profile(); if (profile->clientProfile()->name() == clientProfileName) { return true; } } } return false; } bool Synchronizer::removeProfile(QString aProfileId) { FUNCTION_CALL_TRACE(lcButeoTrace); bool status = true; // Check if a sync session is ongoing for this profile. if (iActiveSessions.contains(aProfileId)) { // If yes, abort that sync session first qCDebug(lcButeoMsyncd) << "Sync still ongoing for profile" << aProfileId; qCDebug(lcButeoMsyncd) << "Aborting sync for profile" << aProfileId; abortSync(aProfileId); iProfilesToRemove.append(aProfileId); } else { status = cleanupProfile(aProfileId); } return status; } bool Synchronizer::updateProfile(QString aProfileAsXml) { FUNCTION_CALL_TRACE(lcButeoTrace); bool status = false; QString address; if (!aProfileAsXml.isEmpty()) { // save the changes to persistent storage Profile *profile = iProfileManager.profileFromXml(aProfileAsXml); if (profile) { // Update storage info before giving it to profileManager /* Profile* service = 0; foreach(Profile* p, profile->allSubProfiles()) { if (p->type() == Profile::TYPE_SERVICE) { service = p; break; } } */ if (!profile->boolKey(Buteo::KEY_STORAGE_UPDATED)) { address = profile->key(Buteo::KEY_BT_ADDRESS); if (!address.isNull()) { if (profile->key(Buteo::KEY_UUID).isEmpty()) { QString uuid = QUuid::createUuid().toString(); uuid = uuid.remove(QRegExp("[{}]")); profile->setKey(Buteo::KEY_UUID, uuid); } if (profile->key(Buteo::KEY_REMOTE_NAME).isEmpty()) { profile->setKey(Buteo::KEY_REMOTE_NAME, profile->displayname()); } profile->setBoolKey(Buteo::KEY_STORAGE_UPDATED, true); } } QString profileId = iProfileManager.updateProfile(*profile); // if the profile changes are for schedule sync we need to reschedule if (!profileId.isEmpty()) { reschedule(profileId); status = true; } delete profile; } } return status; } bool Synchronizer::requestStorages(QStringList aStorageNames) { FUNCTION_CALL_TRACE(lcButeoTrace); return iStorageBooker.reserveStorages(aStorageNames, ""); } void Synchronizer::releaseStorages(QStringList aStorageNames) { FUNCTION_CALL_TRACE(lcButeoTrace); iStorageBooker.releaseStorages(aStorageNames); emit storageReleased(); } QStringList Synchronizer::runningSyncs() { FUNCTION_CALL_TRACE(lcButeoTrace); return iActiveSessions.keys(); } void Synchronizer::onStorageReleased() { FUNCTION_CALL_TRACE(lcButeoTrace); qCDebug(lcButeoMsyncd) << "Storage released"; while (startNextSync()) { // Intentionally empty. } } void Synchronizer::onTransferProgress(const QString &aProfileName, Sync::TransferDatabase aDatabase, Sync::TransferType aType, const QString &aMimeType, int aCommittedItems) { FUNCTION_CALL_TRACE(lcButeoTrace); qCDebug(lcButeoMsyncd) << "Sync session progress"; qCDebug(lcButeoMsyncd) << "Profile:" << aProfileName; qCDebug(lcButeoMsyncd) << "Database:" << aDatabase; qCDebug(lcButeoMsyncd) << "Transfer type:" << aType; qCDebug(lcButeoMsyncd) << "Mime type:" << aMimeType; emit transferProgress(aProfileName, aDatabase, aType, aMimeType, aCommittedItems); } void Synchronizer::onStorageAccquired(const QString &aProfileName, const QString &aMimeType) { FUNCTION_CALL_TRACE(lcButeoTrace); qCDebug(lcButeoMsyncd) << "Mime type:" << aMimeType; qCDebug(lcButeoMsyncd) << "Profile:" << aProfileName; if (!aProfileName.isEmpty() && !aMimeType.isEmpty()) { SyncSession *session = qobject_cast(QObject::sender()); if (session) { QMap storageMap = session->getStorageMap(); if (aMimeType.compare(QString("text/x-vcard"), Qt::CaseInsensitive) == 0) storageMap["hcontacts"] = true; else if (aMimeType.compare(QString("text/x-vcalendar"), Qt::CaseInsensitive) == 0) storageMap["hcalendar"] = true; else if (aMimeType.compare(QString("text/plain"), Qt::CaseInsensitive) == 0) storageMap["hnotes"] = true; #ifdef BM_SYNC else if (aMimeType.compare(QString("text/x-vbookmark"), Qt::CaseInsensitive) == 0) storageMap["hbookmarks"] = true; #endif #ifdef SMS_SYNC else if (aMimeType.compare(QString("text/x-vmsg"), Qt::CaseInsensitive) == 0) storageMap["hsms"] = true; #endif else qCDebug(lcButeoMsyncd) << "Unsupported mime type" << aMimeType; session->setStorageMap(storageMap); } } } bool Synchronizer::requestStorage(const QString &aStorageName, const SyncPluginBase *aCaller) { FUNCTION_CALL_TRACE(lcButeoTrace); return iStorageBooker.reserveStorage(aStorageName, aCaller->getProfileName()); } void Synchronizer::releaseStorage(const QString &aStorageName, const SyncPluginBase */*aCaller*/) { FUNCTION_CALL_TRACE(lcButeoTrace); iStorageBooker.releaseStorage(aStorageName); emit storageReleased(); } StoragePlugin *Synchronizer::createStorage(const QString &aPluginName) { FUNCTION_CALL_TRACE(lcButeoTrace); StoragePlugin *plugin = nullptr; if (!aPluginName.isEmpty()) { plugin = iPluginManager.createStorage(aPluginName); } return plugin; } void Synchronizer::initializeScheduler() { FUNCTION_CALL_TRACE(lcButeoTrace); if (!iSyncScheduler) { iSyncScheduler = new SyncScheduler(this); connect(iSyncScheduler, SIGNAL(syncNow(QString)), this, SLOT(startScheduledSync(QString)), Qt::QueuedConnection); connect(iSyncScheduler, SIGNAL(externalSyncChanged(QString, bool)), this, SLOT(reportExternalSyncStatus(QString, bool)), Qt::QueuedConnection); QList profiles = iProfileManager.allSyncProfiles(); foreach (SyncProfile *profile, profiles) { if (profile->syncType() == SyncProfile::SYNC_SCHEDULED) { iSyncScheduler->addProfile(profile); } // Emit external sync status for all profiles // on startup, in case of a crash/abort of this // process, we should update pontential listeners with // the correct status reportExternalSyncStatus(profile, true); } qDeleteAll(profiles); } } void Synchronizer::destroyStorage(StoragePlugin *aStorage) { FUNCTION_CALL_TRACE(lcButeoTrace); iPluginManager.destroyStorage(aStorage); } bool Synchronizer::isConnectivityAvailable(Sync::ConnectivityType aType) { FUNCTION_CALL_TRACE(lcButeoTrace); if (iTransportTracker != 0) { return iTransportTracker->isConnectivityAvailable(aType); } else { return false; } } void Synchronizer::startServers(bool resume) { FUNCTION_CALL_TRACE(lcButeoTrace); qCDebug(lcButeoMsyncd) << "Starting/Resuming server plug-ins"; if (iServerActivator != 0) { if (false == resume) { connect(iServerActivator, SIGNAL(serverEnabled(const QString &)), this, SLOT(startServer(const QString &)), Qt::QueuedConnection); connect(iServerActivator, SIGNAL(serverDisabled(const QString &)), this, SLOT(stopServer(const QString &)), Qt::QueuedConnection); } QStringList enabledServers = iServerActivator->enabledServers(); foreach (QString server, enabledServers) { if (false == resume) { startServer(server); } else { ServerPluginRunner *pluginRunner = iServers[server]; if (pluginRunner) { pluginRunner->resume(); } } } } else { qCCritical(lcButeoMsyncd) << "No server plug-in activator"; } } void Synchronizer::stopServers(bool suspend) { FUNCTION_CALL_TRACE(lcButeoTrace); qCDebug(lcButeoMsyncd) << "Stopping/Suspending all server plug-ins"; if (false == suspend) { iServerActivator->disconnect(); } QStringList activeServers = iServers.keys(); foreach (QString server, activeServers) { if (false == suspend) { stopServer(server); } else { ServerPluginRunner *pluginRunner = iServers[server]; if (pluginRunner) { pluginRunner->suspend(); } } } } void Synchronizer::startServer(const QString &aProfileName) { FUNCTION_CALL_TRACE(lcButeoTrace); qCDebug(lcButeoMsyncd) << "Starting server plug-in:" << aProfileName; if (iServers.contains(aProfileName)) { qCWarning(lcButeoMsyncd) << "Server thread already running for profile:" << aProfileName; // Remove reference from the activator iServerActivator->removeRef(aProfileName, false); return; } Profile *serverProfile = iProfileManager.profile( aProfileName, Profile::TYPE_SERVER); if (!serverProfile) { // @todo: for now, do not enforce server plug-ins to have an XML profile qCWarning(lcButeoMsyncd) << "Profile not found, creating an empty one"; ProfileFactory pf; serverProfile = pf.createProfile(aProfileName, Profile::TYPE_SERVER); } else { iProfileManager.expand(*serverProfile); } if (!serverProfile || !serverProfile->isValid()) { qCWarning(lcButeoMsyncd) << "Profile not found or not valid:" << aProfileName; delete serverProfile; serverProfile = 0; return; } ServerPluginRunner *pluginRunner = new ServerPluginRunner(aProfileName, serverProfile, &iPluginManager, this, iServerActivator, this); // Relay connectivity state change signal to plug-in runner. connect(iTransportTracker, SIGNAL(connectivityStateChanged(Sync::ConnectivityType, bool)), pluginRunner, SIGNAL(connectivityStateChanged(Sync::ConnectivityType, bool))); connect(pluginRunner, SIGNAL(done()), this, SLOT(onServerDone())); connect(pluginRunner, SIGNAL(newSession(const QString &)), this, SLOT(onNewSession(const QString &))); if (!pluginRunner->init() || !pluginRunner->start()) { qCCritical(lcButeoMsyncd) << "Failed to start plug-in"; delete pluginRunner; pluginRunner = 0; return; } iServers.insert(aProfileName, pluginRunner); } void Synchronizer::stopServer(const QString &aProfileName) { FUNCTION_CALL_TRACE(lcButeoTrace); qCDebug(lcButeoMsyncd) << "Stopping server:" << aProfileName; if (iServers.contains(aProfileName)) { ServerPluginRunner *pluginRunner = iServers[aProfileName]; if (pluginRunner) { pluginRunner->stop(); } qCDebug(lcButeoMsyncd) << "Deleting server"; if (!iClosing) { // This function may have been invoked from a signal. The plugin runner // will only be deleted when the server thread returns. qCWarning(lcButeoMsyncd) << "The server thread for profile: " << aProfileName << "is still running. Server will be deleted later"; } else { iServers.remove(aProfileName); // Synchronizer is closing, this function is not invoked by a signal. // Delete plug-in runner immediately. delete pluginRunner; } pluginRunner = 0; } else { qCWarning(lcButeoMsyncd) << "Server not found"; } } void Synchronizer::onServerDone() { FUNCTION_CALL_TRACE(lcButeoTrace); ServerPluginRunner *pluginRunner = qobject_cast(QObject::sender()); QString serverName = "unknown"; if (pluginRunner != 0) { serverName = pluginRunner->pluginName(); } qCDebug(lcButeoMsyncd) << "Server stopped:" << serverName; if (iServers.values().contains(pluginRunner)) { qCDebug(lcButeoMsyncd) << "Deleting server"; iServers.remove(iServers.key(pluginRunner)); pluginRunner->deleteLater(); pluginRunner = 0; } } bool syncProfilePointerLessThan(SyncProfile *&aLhs, SyncProfile *&aRhs) { if (aLhs && aRhs) { if (aLhs->isHidden() != aRhs->isHidden()) return !aLhs->isHidden(); if (aLhs->isEnabled() != aRhs->isEnabled()) return aLhs->isEnabled(); } return false; } void Synchronizer::onNewSession(const QString &aDestination) { FUNCTION_CALL_TRACE(lcButeoTrace); qCDebug(lcButeoMsyncd) << "New session from" << aDestination; bool createNewProfile = false; ServerPluginRunner *pluginRunner = qobject_cast(QObject::sender()); if (pluginRunner != 0) { SyncProfile *profile = 0; QList syncProfiles; if (aDestination.contains("USB")) { syncProfiles = iProfileManager.getSyncProfilesByData( QString::null, QString::null, KEY_DISPLAY_NAME, PC_SYNC); } else { syncProfiles = iProfileManager.getSyncProfilesByData( QString::null, Profile::TYPE_SYNC, KEY_BT_ADDRESS, aDestination); } if (syncProfiles.isEmpty()) { qCDebug(lcButeoMsyncd) << "No sync profiles found with a matching destination address"; // destination a bt address profile = iProfileManager.createTempSyncProfile(aDestination, createNewProfile); profile->setKey(Buteo::KEY_UUID, iUUID); profile->setKey(Buteo::KEY_REMOTE_NAME, iRemoteName); if (createNewProfile) { iProfileManager.updateProfile(*profile); } } else { // Sort profiles to preference order. Visible and enabled are preferred. std::sort(syncProfiles.begin(), syncProfiles.end(), syncProfilePointerLessThan); profile = syncProfiles.first(); qCDebug(lcButeoMsyncd) << "Found" << syncProfiles.count() << "sync profiles with a " "matching destination address. Selecting" << profile->name(); syncProfiles.removeFirst(); qDeleteAll(syncProfiles); } // If the profile is not hidden, UI must be informed. if (!profile->isHidden()) { // Get the DBUS interface for sync-UI. qCDebug(lcButeoMsyncd) << "sync-ui dbus interface is getting called"; if (iSyncUIInterface == nullptr) { qCDebug(lcButeoMsyncd) << "iSyncUIInterface is nullptr"; iSyncUIInterface = new QDBusInterface("com.nokia.syncui", "/org/maemo/m", "com.nokia.MApplicationIf", QDBusConnection::sessionBus()); } else if (!iSyncUIInterface->isValid()) { qCDebug(lcButeoMsyncd) << "iSyncUIInterface is not Valid()"; delete iSyncUIInterface; iSyncUIInterface = nullptr; iSyncUIInterface = new QDBusInterface("com.nokia.syncui", "/org/maemo/m", "com.nokia.MApplicationIf", QDBusConnection::sessionBus()); } //calling launch with argument list QStringList list; list.append("launching"); QList argumentList; argumentList << qVariantFromValue(list); iSyncUIInterface->asyncCallWithArgumentList(QLatin1String("launch"), argumentList); } SyncSession *session = new SyncSession(profile, this); qCDebug(lcButeoMsyncd) << "Disable sync on change"; //As sync is ongoing, disable sync on change for now, we can query later if //there are changes. if (iSOCEnabled) { iSyncOnChange.disableNext(); } session->setProfileCreated(createNewProfile); // disable all storages // @todo : Can we remove hardcoding of the storageNames ??? QMap storageMap; storageMap["hcontacts"] = 0; storageMap["hcalendar"] = 0; storageMap["hnotes"] = 0; #ifdef BM_SYNC storageMap["hbookmarks"] = 0; #endif #ifdef SMS_SYNC storageMap["hsms"] = 0; #endif session->setStorageMap(storageMap); iActiveSessions.insert(profile->name(), session); // Connect signals from sync session. connect(session, SIGNAL(transferProgress(const QString &, Sync::TransferDatabase, Sync::TransferType, const QString &, int)), this, SLOT(onTransferProgress(const QString &, Sync::TransferDatabase, Sync::TransferType, const QString &, int))); connect(session, SIGNAL(storageAccquired(const QString &, const QString &)), this, SLOT(onStorageAccquired(const QString &, const QString &))); connect(session, SIGNAL(finished(const QString &, Sync::SyncStatus, const QString &, int)), this, SLOT(onSessionFinished(const QString &, Sync::SyncStatus, const QString &, int))); connect(session, SIGNAL(syncProgressDetail(const QString &, int)), this, SLOT(onSyncProgressDetail(const QString &, int))); // Associate plug-in runner with the new session. session->setPluginRunner(pluginRunner, false); emit syncStatus(profile->name(), Sync::SYNC_STARTED, "", 0); } else { qCWarning(lcButeoMsyncd) << "Could not resolve server, session object not created"; } } void Synchronizer::slotProfileChanged(QString aProfileName, int aChangeType, QString aProfileAsXml) { // queue up a sync when a new profile is added or an existing profile is modified. // we coalesce changes to profiles so that we do not trigger syncs immediately // on change, to avoid thrash during backup/restore and races if the client wishes // to trigger manually. Temporary until we can improve Buteo's SyncOnChange handler. switch (aChangeType) { case ProfileManager::PROFILE_ADDED: { iProfileChangeTriggerQueue.append(qMakePair(aProfileName, ProfileManager::PROFILE_ADDED)); iProfileChangeTriggerTimer.start(30000); // 30 seconds. } break; case ProfileManager::PROFILE_REMOVED: iSyncOnChangeScheduler.removeProfile(aProfileName); iWaitingOnlineSyncs.removeAll(aProfileName); for (int i = iProfileChangeTriggerQueue.size() - 1; i >= 0; --i) { if (iProfileChangeTriggerQueue[i].first == aProfileName) { qCDebug(lcButeoMsyncd) << "Removing queued profile change sync due to profile removal:" << aProfileName; iProfileChangeTriggerQueue.removeAt(i); } } break; case ProfileManager::PROFILE_MODIFIED: { bool alreadyQueued = false; for (int i = 0; i < iProfileChangeTriggerQueue.size(); ++i) { if (iProfileChangeTriggerQueue.at(i).first == aProfileName) { alreadyQueued = true; break; } } if (!alreadyQueued) { iProfileChangeTriggerQueue.append(qMakePair(aProfileName, ProfileManager::PROFILE_MODIFIED)); } iProfileChangeTriggerTimer.start(30000); // 30 seconds. } break; } emit signalProfileChanged(aProfileName, aChangeType, aProfileAsXml); } void Synchronizer::profileChangeTriggerTimeout() { if (iProfileChangeTriggerQueue.isEmpty()) { return; } QPair queuedChange = iProfileChangeTriggerQueue.takeFirst(); SyncProfile *profile = iProfileManager.syncProfile(queuedChange.first); if (profile) { if (queuedChange.second == ProfileManager::PROFILE_ADDED) { enableSOCSlot(queuedChange.first); if (profile->isEnabled()) { qCDebug(lcButeoMsyncd) << "Triggering queued profile addition sync for:" << queuedChange.first; startSync(queuedChange.first); } } else if (profile->isEnabled()) { qCDebug(lcButeoMsyncd) << "Triggering queued profile modification sync for:" << queuedChange.first; startScheduledSync(queuedChange.first); } delete profile; } // continue triggering profiles until we have emptied the queue. QMetaObject::invokeMethod(this, "profileChangeTriggerTimeout", Qt::QueuedConnection); } void Synchronizer::reschedule(const QString &aProfileName) { FUNCTION_CALL_TRACE(lcButeoTrace); if (iSyncScheduler == 0) return; SyncProfile *profile = iProfileManager.syncProfile(aProfileName); if (profile && profile->syncType() == SyncProfile::SYNC_SCHEDULED && profile->isEnabled()) { iSyncScheduler->addProfile(profile); } else { qCDebug(lcButeoMsyncd) << "Scheduled sync got disabled for" << aProfileName; iSyncScheduler->removeProfile(aProfileName); } if (profile) { reportExternalSyncStatus(profile); qCDebug(lcButeoMsyncd) << "Reschdule profile" << aProfileName << profile->syncType() << profile->isEnabled(); delete profile; profile = nullptr; } } void Synchronizer::slotSyncStatus(QString aProfileName, int aStatus, QString aMessage, int aMoreDetails) { FUNCTION_CALL_TRACE(lcButeoTrace); SyncProfile *profile = iProfileManager.syncProfile(aProfileName); if (profile) { QString accountId = profile->key(KEY_ACCOUNT_ID); if (!accountId.isNull()) { switch (aStatus) { case Sync::SYNC_QUEUED: case Sync::SYNC_STARTED: case Sync::SYNC_ERROR: case Sync::SYNC_DONE: case Sync::SYNC_ABORTED: case Sync::SYNC_CANCELLED: case Sync::SYNC_NOTPOSSIBLE: { qCDebug(lcButeoMsyncd) << "Sync status changed for account" << accountId; qlonglong aPrevSyncTime; qlonglong aNextSyncTime; int aFailedReason; int aNewStatus = status(accountId.toUInt(), aFailedReason, aPrevSyncTime, aNextSyncTime); emit statusChanged(accountId.toUInt(), aNewStatus, aFailedReason, aPrevSyncTime, aNextSyncTime); } break; case Sync::SYNC_STOPPING: case Sync::SYNC_PROGRESS: default: break; } } if (iSyncScheduler) { // can be null if in backup/restore state. iSyncScheduler->syncStatusChanged(aProfileName, aStatus, aMessage, aMoreDetails); } delete profile; } } void Synchronizer::removeScheduledSync(const QString &aProfileName) { FUNCTION_CALL_TRACE(lcButeoTrace); if (iSyncScheduler == 0) return; SyncProfile *profile = iProfileManager.syncProfile(aProfileName); if (profile) { if (!profile->isEnabled()) { qCDebug(lcButeoMsyncd) << "Sync got disabled for" << aProfileName; iSyncScheduler->removeProfile(aProfileName); } // Check if external sync status changed, profile might be turned // to sync externally and thus buteo sync set to disable reportExternalSyncStatus(profile); delete profile; } } bool Synchronizer::isBackupRestoreInProgress() { FUNCTION_CALL_TRACE(lcButeoTrace); bool retVal = getBackUpRestoreState(); if (retVal) { qCDebug(lcButeoMsyncd) << "Backup-Restore o/p in progress - Failed to start manual sync"; } return retVal; } void Synchronizer::backupRestoreStarts() { qCDebug(lcButeoMsyncd) << "Synchronizer:backupRestoreStarts:"; iClosing = true; // No active sessions currently !! if (iActiveSessions.size() == 0) { qCDebug(lcButeoMsyncd) << "No active sync sessions "; stopServers(true); iSyncBackup->sendReply(0); } else { // Stop running sessions QList sessions = iActiveSessions.values(); foreach (SyncSession *session, sessions) { if (session) { session->abort(); } } } delete iSyncScheduler; iSyncScheduler = 0; // Request all external syncs to stop, relying on reportExternalSyncStatus() to // act appropriately because (getBackUpRestoreState() == true) QMap::iterator syncStatus; for (syncStatus = iExternalSyncProfileStatus.begin(); syncStatus != iExternalSyncProfileStatus.end(); ++syncStatus) { const QString &profileName = syncStatus.key(); reportExternalSyncStatus(profileName, false); } } void Synchronizer::backupRestoreFinished() { qCDebug(lcButeoMsyncd) << "Synchronizer::backupFinished"; iClosing = false; startServers(true); initializeScheduler(); iSyncBackup->sendReply(0); } void Synchronizer::backupStarts() { qCDebug(lcButeoMsyncd) << "Synchronizer::backupStarts"; emit backupInProgress(); backupRestoreStarts (); } void Synchronizer::backupFinished() { qCDebug(lcButeoMsyncd) << "Synchronizer::backupFinished"; backupRestoreFinished(); emit backupDone(); } void Synchronizer::restoreStarts() { qCDebug(lcButeoMsyncd) << "Synchronizer::restoreStarts"; emit restoreInProgress(); backupRestoreStarts(); } void Synchronizer::restoreFinished() { qCDebug(lcButeoMsyncd) << "Synchronizer::restoreFinished"; backupRestoreFinished(); emit restoreDone(); } bool Synchronizer::getBackUpRestoreState() { qCDebug(lcButeoMsyncd) << "Synchronizer::getBackUpRestoreState"; return iSyncBackup->getBackUpRestoreState(); } void Synchronizer::start(unsigned int aAccountId) { FUNCTION_CALL_TRACE(lcButeoTrace); qCDebug(lcButeoMsyncd) << "Start sync requested for account" << aAccountId; QList profileList = iAccounts->getProfilesByAccountId(aAccountId); foreach (SyncProfile *profile, profileList) { startSync(profile->name()); delete profile; } } void Synchronizer::stop(unsigned int aAccountId) { FUNCTION_CALL_TRACE(lcButeoTrace); qCDebug(lcButeoMsyncd) << "Stop sync requested for account" << aAccountId; QList profileList = iAccounts->getProfilesByAccountId(aAccountId); foreach (SyncProfile *profile, profileList) { abortSync(profile->name()); delete profile; } } int Synchronizer::status(unsigned int aAccountId, int &aFailedReason, qlonglong &aPrevSyncTime, qlonglong &aNextSyncTime) { FUNCTION_CALL_TRACE(lcButeoTrace); int status = 1; // Initialize to Done QDateTime prevSyncTime; // Initialize to invalid QDateTime nextSyncTime; QList profileList = iAccounts->getProfilesByAccountId(aAccountId); foreach (SyncProfile *profile, profileList) { // First check if sync is going on for any profile corresponding to this // account ID if (iActiveSessions.contains(profile->name()) || iSyncQueue.contains(profile->name())) { qCDebug(lcButeoMsyncd) << "Sync running for" << aAccountId; status = 0; break; } else { // Check if the last sync resulted in an error for any of the // profiles const SyncResults *lastResults = profile->lastResults(); if (lastResults && SyncResults::SYNC_RESULT_FAILED == lastResults->majorCode()) { status = 2; // TODO: Determine the set of failure enums needed here aFailedReason = lastResults->minorCode(); break; } } } if (status != 0) { // Need to return the next and last sync times foreach (SyncProfile *profile, profileList) { if (!prevSyncTime.isValid()) { prevSyncTime = profile->lastSyncTime(); } else { (prevSyncTime > profile->lastSyncTime()) ? prevSyncTime : profile->lastSyncTime(); } } if (prevSyncTime.isValid()) { // Doesn't really matter which profile we do this for, as all of // them have the same schedule SyncProfile *profile = profileList.first(); nextSyncTime = profile->nextSyncTime(prevSyncTime); } } aPrevSyncTime = prevSyncTime.toMSecsSinceEpoch(); aNextSyncTime = nextSyncTime.toMSecsSinceEpoch(); qDeleteAll(profileList); return status; } QList Synchronizer::syncingAccounts() { FUNCTION_CALL_TRACE(lcButeoTrace); QList syncingAccountsList; // Check active sessions QList activeSessions = iActiveSessions.values(); foreach (SyncSession *session, activeSessions) { if (session->profile()) { SyncProfile *profile = session->profile(); QString accountId = profile->key(KEY_ACCOUNT_ID); if (!accountId.isNull()) { syncingAccountsList.append(accountId.toUInt()); } } } // Check queued syncs const QList queuedSessions = iSyncQueue.getQueuedSyncSessions(); foreach (const SyncSession *session, queuedSessions) { if (session->profile()) { SyncProfile *profile = session->profile(); QString accountId = profile->key(KEY_ACCOUNT_ID); if (!accountId.isNull()) { syncingAccountsList.append(accountId.toUInt()); } } } return syncingAccountsList; } QString Synchronizer::getLastSyncResult(const QString &aProfileId) { FUNCTION_CALL_TRACE(lcButeoTrace); QString lastSyncResult; if (!aProfileId.isEmpty()) { SyncProfile *profile = iProfileManager.syncProfile (aProfileId); if (profile) { const SyncResults *syncResults = profile->lastResults(); if (syncResults) { lastSyncResult = syncResults->toString(); qCDebug(lcButeoMsyncd) << "SyncResults found:" << lastSyncResult; } else { qCDebug(lcButeoMsyncd) << "SyncResults not Found!!!"; } delete profile; } else { qCDebug(lcButeoMsyncd) << "No profile found with aProfileId" << aProfileId; } } return lastSyncResult; } QStringList Synchronizer::allVisibleSyncProfiles() { FUNCTION_CALL_TRACE(lcButeoTrace); QStringList profilesAsXml; QList profiles = iProfileManager.allVisibleSyncProfiles(); if (!profiles.isEmpty()) { foreach (Buteo::SyncProfile *profile, profiles) { if (profile) { profilesAsXml.append(profile->toString()); delete profile; profile = nullptr; } } } qCDebug(lcButeoMsyncd) << "allVisibleSyncProfiles profilesAsXml" << profilesAsXml; return profilesAsXml; } QString Synchronizer::syncProfile(const QString &aProfileId) { FUNCTION_CALL_TRACE(lcButeoTrace); QString profileAsXml; if (!aProfileId.isEmpty()) { SyncProfile *profile = iProfileManager.syncProfile (aProfileId); if (profile) { profileAsXml.append(profile->toString()); delete profile; profile = nullptr; } else { qCDebug(lcButeoMsyncd) << "No profile found with aProfileId" << aProfileId; } } qCDebug(lcButeoMsyncd) << "syncProfile profileAsXml" << profileAsXml << "aProfileId" << aProfileId; return profileAsXml; } QStringList Synchronizer::syncProfilesByKey(const QString &aKey, const QString &aValue) { FUNCTION_CALL_TRACE(lcButeoTrace); qCDebug(lcButeoMsyncd) << "syncProfile key : " << aKey << "Value :" << aValue; QStringList profilesAsXml; if (!aKey.isEmpty() && !aValue.isEmpty()) { QList filters; ProfileManager::SearchCriteria filter; filter.iType = ProfileManager::SearchCriteria::EQUAL; filter.iKey = aKey; filter.iValue = aValue; filters.append(filter); QList profiles = iProfileManager.getSyncProfilesByData(filters); if (profiles.size() > 0) { qCDebug(lcButeoMsyncd) << "Found matching profiles :" << profiles.size(); foreach (SyncProfile *profile, profiles) { profilesAsXml.append(profile->toString()); } qDeleteAll(profiles); } else { qCDebug(lcButeoMsyncd) << "No profile found with key :" << aKey << "Value : " << aValue; } } return profilesAsXml; } QStringList Synchronizer::syncProfilesByType(const QString &aType) { FUNCTION_CALL_TRACE(lcButeoTrace); qCDebug(lcButeoMsyncd) << "Profile Type : " << aType; return iProfileManager.profileNames(aType); } QStringList Synchronizer::profilesByType(const QString &aType) { FUNCTION_CALL_TRACE(lcButeoTrace); QStringList profilesAsXml; qCDebug(lcButeoMsyncd) << "Profile Type : " << aType; for (const QString &profileId : iProfileManager.profileNames(aType)) { SyncProfile *profile = iProfileManager.syncProfile(profileId); if (profile) { profilesAsXml.append(profile->toString()); } delete profile; } qCDebug(lcButeoMsyncd) << "profilesByType profilesAsXml" << profilesAsXml; return profilesAsXml; } void Synchronizer::onNetworkStateChanged(bool aState, Sync::InternetConnectionType type) { FUNCTION_CALL_TRACE(lcButeoTrace); qCDebug(lcButeoMsyncd) << "Network state changed: OnLine:" << aState << " connection type:" << type; if (aState) { qCDebug(lcButeoMsyncd) << "Restart sync for profiles that need network, checking profiles:" << iWaitingOnlineSyncs; QStringList profiles(iWaitingOnlineSyncs); foreach (QString profileName, profiles) { SyncProfile *profile = iProfileManager.syncProfile(profileName); if (acceptScheduledSync(aState, type, profile)) { // start sync now, we do not need to call 'startScheduledSync' since that function // only checks for internet connection iWaitingOnlineSyncs.removeOne(profileName); startSync(profileName, true); } delete profile; } } else if (!aState) { QList profiles = iActiveSessions.keys(); foreach (QString profileId, profiles) { //Getting profile SyncProfile *profile = iProfileManager.syncProfile (profileId); if (profile) { if (profile->destinationType() == Buteo::SyncProfile::DESTINATION_TYPE_ONLINE) { iActiveSessions[profileId]->abort(Sync::SYNC_ERROR); } delete profile; } else { qCDebug(lcButeoMsyncd) << "No profile found with aProfileId" << profileId; } } } } Profile *Synchronizer::getSyncProfileByRemoteAddress(const QString &aAddress) { FUNCTION_CALL_TRACE(lcButeoTrace); Profile *profile = 0; QList profiles; if ("USB" == aAddress) { profiles = iProfileManager.getSyncProfilesByData( QString::null, QString::null, KEY_DISPLAY_NAME, PC_SYNC); } else { profiles = iProfileManager.getSyncProfilesByData("", Buteo::Profile::TYPE_SYNC, Buteo::KEY_BT_ADDRESS, aAddress); } if (!profiles.isEmpty()) { profile = profiles.first(); } return profile; } QString Synchronizer::getValue(const QString &aAddress, const QString &aKey) { FUNCTION_CALL_TRACE(lcButeoTrace); QString value; if (Buteo::KEY_UUID == aKey) { iUUID = QUuid::createUuid().toString(); iUUID = iUUID.remove(QRegExp("[{}]")); value = iUUID; } if (Buteo::KEY_REMOTE_NAME == aKey) { if ("USB" == aAddress) { iRemoteName = PC_SYNC; } else { BtHelper btHelper(aAddress); iRemoteName = btHelper.getDeviceProperties().value(BT_PROPERTIES_NAME).toString(); } value = iRemoteName; } return value; } void Synchronizer::reportExternalSyncStatus(const QString &aProfileName, bool force) { SyncProfile *profile = iProfileManager.syncProfile(aProfileName); if (profile) { reportExternalSyncStatus(profile, force); delete profile; } } // Here we store profile names since they are unique, but can be anything, and we emit signals // containing the client profile name, since those are always associated with a // specific plugin, this way potential listeners of these signals can distinguish the signals // based on the accountId and client profile name. void Synchronizer::reportExternalSyncStatus(const SyncProfile *aProfile, bool force) { int accountId = aProfile->key(KEY_ACCOUNT_ID).toInt(); if (accountId) { const QString &profileName = aProfile->name(); const QString &clientProfile = aProfile->clientProfile()->name(); // All external syncs are stopped while a backup or restore is running if (getBackUpRestoreState()) { if (iExternalSyncProfileStatus.value(profileName) || force) { qCDebug(lcButeoMsyncd) << "Sync externally status suspended during backup for profile:" << profileName; iExternalSyncProfileStatus.insert(profileName, false); emit syncedExternallyStatus(accountId, clientProfile, false); } // Account in set to sync externally, buteo will let external process handle the syncs in this case } else if (aProfile->syncExternallyEnabled()) { if (!iExternalSyncProfileStatus.value(profileName)) { qCDebug(lcButeoMsyncd) << "Sync externally status changed from false to true for profile:" << profileName; iExternalSyncProfileStatus.insert(profileName, true); emit syncedExternallyStatus(accountId, clientProfile, true); } else if (force) { qCDebug(lcButeoMsyncd) << "Account is in set to sync externally for profile:" << profileName; emit syncedExternallyStatus(accountId, clientProfile, true); } // Account set to sync externally in rush mode } else if (aProfile->syncExternallyDuringRush()) { // Check if we are currently inside rush bool isSyncExternally = aProfile->inExternalSyncRushPeriod(); if (iExternalSyncProfileStatus.contains(profileName)) { qCDebug(lcButeoMsyncd) << "We already have this profile, lets check the status for profile:" << profileName; bool prevSyncExtState = iExternalSyncProfileStatus.value(profileName); if (prevSyncExtState != isSyncExternally) { iExternalSyncProfileStatus.insert(profileName, isSyncExternally); qCDebug(lcButeoMsyncd) << "Sync externally status changed to " << isSyncExternally << "for profile:" << profileName; emit syncedExternallyStatus(accountId, clientProfile, isSyncExternally); } else if (force) { qCDebug(lcButeoMsyncd) << "Sync externally status did not change, current state is: " << prevSyncExtState << "for profile:" << profileName; emit syncedExternallyStatus(accountId, clientProfile, prevSyncExtState); } } else { iExternalSyncProfileStatus.insert(profileName, isSyncExternally); qCDebug(lcButeoMsyncd) << "Inserting sync externally status:" << isSyncExternally << "for profile:" << profileName; emit syncedExternallyStatus(accountId, clientProfile, isSyncExternally); } } else { if (iExternalSyncProfileStatus.contains(profileName)) { iExternalSyncProfileStatus.remove(profileName); emit syncedExternallyStatus(accountId, clientProfile, false); qCDebug(lcButeoMsyncd) << "Removing sync externally status for profile:" << profileName; } else if (force) { qCDebug(lcButeoMsyncd) << "Sync externally is off for profile:" << profileName; emit syncedExternallyStatus(accountId, clientProfile, false); } } } } void Synchronizer::removeExternalSyncStatus(const SyncProfile *aProfile) { int accountId = aProfile->key(KEY_ACCOUNT_ID).toInt(); if (accountId) { const QString &profileName = aProfile->name(); if (iExternalSyncProfileStatus.contains(profileName)) { // if profile was set to sync externally emit the change state signal if (iExternalSyncProfileStatus.value(profileName)) { emit syncedExternallyStatus(accountId, aProfile->clientProfile()->name(), false); } iExternalSyncProfileStatus.remove(profileName); qCDebug(lcButeoMsyncd) << "Removing sync externally status for profile:" << profileName; } } } bool Synchronizer::acceptScheduledSync(bool aConnected, Sync::InternetConnectionType aType, SyncProfile *aSyncProfile) const { if (!aConnected) { qCWarning(lcButeoMsyncd) << "Scheduled sync refused, not connected"; return false; } if (!aSyncProfile) { qCWarning(lcButeoMsyncd) << "Scheduled sync refused, invalid sync profile"; return false; } QList allowedTypes = aSyncProfile->internetConnectionTypes(); if (aType != Sync::INTERNET_CONNECTION_UNKNOWN && !allowedTypes.isEmpty()) { return allowedTypes.contains(aType); } // If no allowed types are specified, fallback to the old default settings. if (aType == Sync::INTERNET_CONNECTION_WLAN || aType == Sync::INTERNET_CONNECTION_ETHERNET) { return true; } if (g_settings_get_boolean(iSettings, "allow-scheduled-sync-over-cellular")) { qCInfo(lcButeoMsyncd) << "Allowing sync for cellular/other connection type:" << aType; return true; } qCWarning(lcButeoMsyncd) << "Scheduled sync refused, profile disallows current connection type:" << aType; return false; } void Synchronizer::isSyncedExternally(unsigned int aAccountId, const QString aClientProfileName) { qCDebug(lcButeoMsyncd) << "Received isSyncedExternally request for account:" << aAccountId; bool profileFound = false; QList syncProfiles = iAccounts->getProfilesByAccountId(aAccountId); if (!syncProfiles.isEmpty()) { foreach (SyncProfile *profile, syncProfiles) { if (profile->clientProfile()->name() == aClientProfileName) { reportExternalSyncStatus(profile, true); profileFound = true; break; } } } if (!profileFound) { qCDebug(lcButeoMsyncd) << "We don't have a profile for account:" << aAccountId << "emitting sync external status false"; emit syncedExternallyStatus(aAccountId, QString(), false); } qDeleteAll(syncProfiles); } buteo-syncfw-0.11.10/msyncd/synchronizer.h000066400000000000000000000370001477124122200205040ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2014-2019 Jolla Ltd. * Copyright (C) 2020 Open Mobile Platform LLC. * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCHRONIZER_H #define SYNCHRONIZER_H #include "SyncDBusInterface.h" #include "SyncQueue.h" #include "StorageBooker.h" #include "SyncScheduler.h" #include "SyncBackup.h" #include "SyncOnChange.h" #include "SyncOnChangeScheduler.h" #include "SyncCommonDefs.h" #include "ProfileManager.h" #include "PluginManager.h" #include "PluginCbInterface.h" #include "ClientPlugin.h" #include #include #include #include #include #include #include #include struct _GSettings; namespace Buteo { class PluginManager; class ServerPluginRunner; class NetworkManager; class TransportTracker; class ServerActivator; class AccountsHelper; class BatteryInfo; /// \brief The main entry point to the synchronization framework. /// /// This class manages other components and connects them to provide /// the fully functioning synchronization framework. class Synchronizer : public SyncDBusInterface, // Derived from QObject public PluginCbInterface { Q_OBJECT public: /// \brief The contructor. Synchronizer(QCoreApplication *aApplication); /// \brief Destructor virtual ~Synchronizer(); /// \brief registers the dbus service and creates handlers for various /// tasks of the synchronizer bool initialize(); /// \brief stops the daemon and unregisters the dbus object void close(); // From PluginCbInterface // --------------------------------------------------------------------------- /// \see PluginCbInterface::requestStorage virtual bool requestStorage(const QString &aStorageName, const SyncPluginBase *aCaller); /// \see PluginCbInterface::releaseStorage virtual void releaseStorage(const QString &aStorageName, const SyncPluginBase *aCaller); /// \see PluginCbInterface::createStorage virtual StoragePlugin *createStorage(const QString &aPluginName); /// \see PluginCbInterface::destroyStorage virtual void destroyStorage(StoragePlugin *aStorage); /// \see PluginCbInterface::isConnectivityAvailable virtual bool isConnectivityAvailable(Sync::ConnectivityType aType); /// \see PluginCbInterface::getSyncProfileByRemoteAddress virtual Profile *getSyncProfileByRemoteAddress(const QString &aAddress); /// \see PluginCbInterface::getValue virtual QString getValue(const QString &aAddress, const QString &aKey); // From SyncDBusInterface // -------------------------------------------------------------------------- public slots: //! \see SyncDBusInterface::startSync virtual bool startSync(QString aProfileName); //! \see SyncDBusInterface::abortSync virtual void abortSync(QString aProfileName); //! \see SyncDBusInterface::removeProfile virtual bool removeProfile(QString aProfileAsXml); //! \see SyncDBusInterface::updateProfile virtual bool updateProfile(QString aProfileAsXml); //! \see SyncDBusInterface::requestStorages virtual bool requestStorages(QStringList aStorageNames); //! \see SyncDBusInterface::releaseStorages virtual void releaseStorages(QStringList aStorageNames); //! \see SyncDBusInterface::runningSyncs virtual QStringList runningSyncs(); //! \see SyncDBusInterface::setSyncSchedule virtual bool setSyncSchedule(QString aProfileId, QString aScheduleAsXml); //! \see SyncDBusInterface::saveSyncResults virtual bool saveSyncResults(QString aProfileId, QString aSyncResults); //! \see SyncDBusInterface::createSyncProfileForAccount virtual QString createSyncProfileForAccount(uint aAccountId); /*! \brief To get lastSyncResult. * \param aProfileId * \return QString of syncResult. */ virtual QString getLastSyncResult(const QString &aProfileId); /*! \brief Gets all visible sync profiles. * * Returns all sync profiles that should be visible in sync ui. A profile * is visible if it has not been explicitly set as hidden. * \return The list of sync profiles. */ virtual QStringList allVisibleSyncProfiles(); /*! \brief Gets a sync profile. * * Loads and merges also all sub-profiles that are referenced from the * main profile. Loads the log of finished synchronization sessions with * this profile. * \param aProfileId Name of the profile to get. * \return The sync profile as Xml string. */ virtual QString syncProfile(const QString &aProfileId); virtual QStringList syncProfilesByKey(const QString &aKey, const QString &aValue); virtual QStringList syncProfilesByType(const QString &aType); virtual QStringList profilesByType(const QString &aType) override; // -------------------------------------------------------------------------- //! Called starts a schedule sync. bool startScheduledSync(QString aProfileName); //! Called when backup starts void backupStarts(); //! Called when backup is completed void backupFinished(); //! Called when starting to restore a backup. void restoreStarts(); //! Called when backup is restored void restoreFinished(); //! Called to get the current backup/restore state virtual bool getBackUpRestoreState(); void start(unsigned int aAccountId); /*! \brief Stops sync for all profiles matching the given account ID. * * \param aAccountId The account ID. */ void stop(unsigned int aAccountId); /*! \brief Returns the list of account IDs for which sync is ongoing * * \return The list of account IDs currectly syncing. */ QList syncingAccounts(); /*! \brief Returns the status of the sync for the given account Id * * \param aAccountId The account ID. * \param aFailedReason This is an out parameter. In case the last sync has * failed, this will contain the code indicating the failure reason (TODO: * Define error codes). In case the last sync has not failed, this must be * ignored * \param aPrevSyncTime This is an out parameter. The previous sync time. * Invalid time is returned if there was no last sync. * \param aNextSyncTime This is an out parameter. The next sync time. * \return The status of sync: 0 = Sync is running, * 1 = Last sync succeeded, 2 = last sync failed */ int status(unsigned int aAccountId, int &aFailedReason, qlonglong &aPrevSyncTime, qlonglong &aNextSyncTime); /*! \brief Queries the sync externally status of a given account, * 'syncedExternallyStatus' signal is emitted with the reply is ready, clients should listen * to the later. * * \param aAccountId The account ID. * \param aClientProfileName The name of the client profile resposible for the sync, this is used to distinguish accounts * having several services enabled. */ void isSyncedExternally(unsigned int aAccountId, const QString aClientProfileName); signals: //! emitted by releaseStorages call void storageReleased(); /*! \brief emit this signal when the sync session is completed, * this is useful when the session status is not important. * * @param aProfileName */ void syncDone(const QString &aProfileName); private slots: /*! \brief Handler for storage released signal. * * Tries to start the next sync in queue, which may have been blocked * earlier by storage reservations. */ void onStorageReleased(); void onTransferProgress(const QString &aProfileName, Sync::TransferDatabase aDatabase, Sync::TransferType aType, const QString &aMimeType, int aCommittedItems); void onSessionFinished(const QString &aProfileName, Sync::SyncStatus aStatus, const QString &aMessage, SyncResults::MinorCode aErrorCode); void onStorageAccquired(const QString &aProfileName, const QString &aMimeType); void onSyncProgressDetail(const QString &aProfileName, int aProgressDetail); void onServerDone(); void onNewSession(const QString &aDestination); void slotProfileChanged(QString aProfileName, int aChangeType, QString aProfileAsXml); /*! \brief Starts a server plug-in * * @param aProfileName Server profile name */ void startServer(const QString &aProfileName); /*! \brief Stops a server plug-in * * @param aProfileName Server profile name */ void stopServer(const QString &aProfileName); void onNetworkStateChanged(bool aState, Sync::InternetConnectionType type); /*! \brief call this to request the sync daemon to enable soc * for a profile. The sync daemon decides as of now for which storages * soc should be enabled * * @param aProfileName profile name */ void enableSOCSlot(const QString &aProfileName); /*! \brief Adds a profile to sync scheduler * * @param aProfileName Name of the profile to schedule. */ void reschedule(const QString &aProfileName); /*! \brief Handles the sync status signal * * @param aProfileName Name of the profile * @param aStatus Status of the sync * @param aMessage Status message as a string * @param aMoreDetails In case of failure, contains detailed reason */ void slotSyncStatus(QString aProfileName, int aStatus, QString aMessage, int aMoreDetails); /*! \brief Handles the removed scheduled sync signal * * @param aProfileName Name of the profile */ void removeScheduledSync(const QString &aProfileName); /*! \brief Handles externalSyncChanged signal * * @param aProfileName Name of the profile * @param force When true 'syncedExternallyStatus' dbus signal will be emitted even if the state did not change. */ void reportExternalSyncStatus(const QString &aProfileName, bool force); /*! \brief Triggers sync for profiles which were queued for sync due to profile changes. */ void profileChangeTriggerTimeout(); private: bool startSync(const QString &aProfileName, bool aScheduled); /*! \brief Starts a sync with the given profile. * * \param aProfile Profile to use in sync. Ownership is transferred. * The profile is automatically deleted when the sync finishes. */ bool startSyncNow(SyncSession *aSession); /*! \brief Tries to starts next sync request from the sync queue. * * \return Is it possible to try starting more syncs by calling this * function again. Will be true if the first sync request in the queue * is not blocked by already reserved storages. */ bool startNextSync(); /*! \brief To clean up session * \param aSession * \param aStatus of sync * \return None */ void cleanupSession(SyncSession *aSession, Sync::SyncStatus aStatus); /*! \brief Start all server plug-ins * * @param resume, if true resume servers instead of starting them */ void startServers(bool resume = false); /*! \brief Stop all server plug-ins * * @param suspend, if true suspend servers instead of stopping them */ void stopServers(bool suspend = false); /*! \brief Helper function when backup/restore starts. * */ void backupRestoreStarts(); /*! \brief Helper function when backup/restore is done. * */ void backupRestoreFinished(); /*! \brief Initializes sync scheduler * */ void initializeScheduler(); bool isBackupRestoreInProgress(); /*! \brief Requests for a cleanup from the plugin for the given profileId * * @param aProfileId Name/Id of the profile * @return True or False to indicate success or failure */ bool cleanupProfile(const QString &profileId); bool clientProfileActive(const QString &clientProfileName); /*! \brief Removes the external sync status for a given profile, if status changes * 'syncedExternallyStatus' dbus signal will be emitted to notify possible clients. * * @param aProfile the profile that the status should be removed. */ void removeExternalSyncStatus(const SyncProfile *aProfile); /*! \brief Check if sheduled sync is allowed for this type of connection. * * @param aType the connection type; */ bool acceptScheduledSync(bool aConnected, Sync::InternetConnectionType aType, SyncProfile *profile) const; /*! \brief Checks the status of external sync for a given profile, when the status * changes(or aQuery param is set to true) or the profile is added for the first time 'syncedExternallyStatus' dbus signal * will be emitted to notify possible clients. * * @param aProfile the profile that the state will be checked * @param force When true 'syncedExternallyStatus' dbus signal will be emitted even if the state did not change. */ void reportExternalSyncStatus(const SyncProfile *aProfile, bool force = false); QMap iActiveSessions; QMap iExternalSyncProfileStatus; QList iProfilesToRemove; QMap iServers; QList iWaitingOnlineSyncs; NetworkManager *iNetworkManager; QMap iCountersStorage; PluginManager iPluginManager; ProfileManager iProfileManager; SyncQueue iSyncQueue; StorageBooker iStorageBooker; SyncScheduler *iSyncScheduler; SyncBackup *iSyncBackup; TransportTracker *iTransportTracker; ServerActivator *iServerActivator; AccountsHelper *iAccounts; bool iClosing; SyncOnChange iSyncOnChange; SyncOnChangeScheduler iSyncOnChangeScheduler; /*! \brief Save the counter for given profile * * @param aProfile profile to save counter */ void saveProfileCounter(const SyncProfile *aProfile); /*! \brief Restore the counter for given profile * * @param aProfile profile to restore counter */ void restoreProfileCounter(SyncProfile *aProfile); bool iSOCEnabled; QString iUUID; QString iRemoteName; /* * Temporary, until we can clean up Buteo and properly implement the SyncOnChange * queue to handle all of the required changes (account + profile + connectivity) * in a sane manner (also taking into account BackupRestore status). * However, that change will be far more invasive, so for now this is much simpler. */ QList > iProfileChangeTriggerQueue; QTimer iProfileChangeTriggerTimer; #ifdef SYNCFW_UNIT_TESTS friend class SynchronizerTest; #endif QDBusInterface *iSyncUIInterface; _GSettings *iSettings; BatteryInfo *iBatteryInfo; }; } #endif // SYNCHRONIZER_H buteo-syncfw-0.11.10/msyncd/unittest.pri000066400000000000000000000003641477124122200201740ustar00rootroot00000000000000CONFIG += link_prl msyncd_out_pwd = $${OUT_PWD}/$$relative_path($${PWD}, $$dirname(_PRO_FILE_)) LIBS += -L$${msyncd_out_pwd} -lmsyncd SOURCES += $$PWD/UnitTest.cpp # Already present in ../unittests/tests/tests.pro #DEFINES += SYNCFW_UNIT_TESTS buteo-syncfw-0.11.10/oopp-runner/000077500000000000000000000000001477124122200165655ustar00rootroot00000000000000buteo-syncfw-0.11.10/oopp-runner/ButeoPluginIfaceAdaptor.cpp000066400000000000000000000062031477124122200237720ustar00rootroot00000000000000/* * This file was generated by qdbusxml2cpp version 0.8 * Command line was: qdbusxml2cpp -c ButeoPluginIfaceAdaptor -a ButeoPluginIfaceAdaptor.h:ButeoPluginIfaceAdaptor.cpp com.buteo.msyncd.baseplugin.xml * * qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). * * This is an auto-generated file. * Do not edit! All changes made to it will be lost. */ #include "ButeoPluginIfaceAdaptor.h" #include #include #include #include #include #include #include /* * Implementation of adaptor class ButeoPluginIfaceAdaptor */ ButeoPluginIfaceAdaptor::ButeoPluginIfaceAdaptor(QObject *parent) : QDBusAbstractAdaptor(parent) { // constructor setAutoRelaySignals(true); } ButeoPluginIfaceAdaptor::~ButeoPluginIfaceAdaptor() { // destructor } void ButeoPluginIfaceAdaptor::abortSync(uchar aStatus) { // handle method call com.buteo.msyncd.baseplugin.abortSync QMetaObject::invokeMethod(parent(), "abortSync", Q_ARG(uchar, aStatus)); } bool ButeoPluginIfaceAdaptor::cleanUp() { // handle method call com.buteo.msyncd.baseplugin.cleanUp bool out0; QMetaObject::invokeMethod(parent(), "cleanUp", Q_RETURN_ARG(bool, out0)); return out0; } void ButeoPluginIfaceAdaptor::connectivityStateChanged(int aType, bool aState) { // handle method call com.buteo.msyncd.baseplugin.connectivityStateChanged QMetaObject::invokeMethod(parent(), "connectivityStateChanged", Q_ARG(int, aType), Q_ARG(bool, aState)); } QString ButeoPluginIfaceAdaptor::getSyncResults() { // handle method call com.buteo.msyncd.baseplugin.getSyncResults QString out0; QMetaObject::invokeMethod(parent(), "getSyncResults", Q_RETURN_ARG(QString, out0)); return out0; } bool ButeoPluginIfaceAdaptor::init() { // handle method call com.buteo.msyncd.baseplugin.init bool out0; QMetaObject::invokeMethod(parent(), "init", Q_RETURN_ARG(bool, out0)); return out0; } void ButeoPluginIfaceAdaptor::resume() { // handle method call com.buteo.msyncd.baseplugin.resume QMetaObject::invokeMethod(parent(), "resume"); } bool ButeoPluginIfaceAdaptor::startListen() { // handle method call com.buteo.msyncd.baseplugin.startListen bool out0; QMetaObject::invokeMethod(parent(), "startListen", Q_RETURN_ARG(bool, out0)); return out0; } bool ButeoPluginIfaceAdaptor::startSync() { // handle method call com.buteo.msyncd.baseplugin.startSync bool out0; QMetaObject::invokeMethod(parent(), "startSync", Q_RETURN_ARG(bool, out0)); return out0; } void ButeoPluginIfaceAdaptor::stopListen() { // handle method call com.buteo.msyncd.baseplugin.stopListen QMetaObject::invokeMethod(parent(), "stopListen"); } void ButeoPluginIfaceAdaptor::suspend() { // handle method call com.buteo.msyncd.baseplugin.suspend QMetaObject::invokeMethod(parent(), "suspend"); } bool ButeoPluginIfaceAdaptor::uninit() { // handle method call com.buteo.msyncd.baseplugin.uninit bool out0; QMetaObject::invokeMethod(parent(), "uninit", Q_RETURN_ARG(bool, out0)); return out0; } buteo-syncfw-0.11.10/oopp-runner/ButeoPluginIfaceAdaptor.h000066400000000000000000000122201477124122200234330ustar00rootroot00000000000000/* * This file was generated by qdbusxml2cpp version 0.8 * Command line was: qdbusxml2cpp -c ButeoPluginIfaceAdaptor -a ButeoPluginIfaceAdaptor.h:ButeoPluginIfaceAdaptor.cpp com.buteo.msyncd.baseplugin.xml * * qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). * * This is an auto-generated file. * This file may have been hand-edited. Look for HAND-EDIT comments * before re-generating it. */ #ifndef BUTEOPLUGINIFACEADAPTOR_H_1391669724 #define BUTEOPLUGINIFACEADAPTOR_H_1391669724 #include #include QT_BEGIN_NAMESPACE class QByteArray; template class QList; template class QMap; class QString; class QStringList; class QVariant; QT_END_NAMESPACE /* * Adaptor class for interface com.buteo.msyncd.baseplugin */ class ButeoPluginIfaceAdaptor: public QDBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "com.buteo.msyncd.baseplugin") Q_CLASSINFO("D-Bus Introspection", "" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "") public: ButeoPluginIfaceAdaptor(QObject *parent); virtual ~ButeoPluginIfaceAdaptor(); public: // PROPERTIES public Q_SLOTS: // METHODS void abortSync(uchar aStatus); bool cleanUp(); void connectivityStateChanged(int aType, bool aState); QString getSyncResults(); bool init(); void resume(); bool startListen(); bool startSync(); void stopListen(); void suspend(); bool uninit(); Q_SIGNALS: // SIGNALS void accquiredStorage(const QString &aMimeType); void error(const QString &aProfileName, const QString &aMessage, int aErrorCode); void newSession(const QString &aDestination); void success(const QString &aProfileName, const QString &aMessage); void syncProgressDetail(const QString &aProfileName, int aProgressDetail); void transferProgress(const QString &aProfileName, int aTransferDatabase, int aTransferType, const QString &aMimeType, int aCommittedItems); }; #endif buteo-syncfw-0.11.10/oopp-runner/PluginCbImpl.cpp000066400000000000000000000063571477124122200216310ustar00rootroot00000000000000/* * This file is part of buteo-sync-plugins package * * Copyright (C) 2013 Jolla Ltd. * * Author: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "PluginCbImpl.h" #include "SyncPluginBase.h" #include "StoragePlugin.h" #include "SyncCommonDefs.h" #include "Profile.h" #include "LogMacros.h" using namespace Buteo; PluginCbImpl::PluginCbImpl() { FUNCTION_CALL_TRACE(lcButeoTrace); imsyncIface = new SyncDaemonProxy("com.meego.msyncd", "/synchronizer", QDBusConnection::sessionBus()); } PluginCbImpl::~PluginCbImpl() { FUNCTION_CALL_TRACE(lcButeoTrace); delete imsyncIface; imsyncIface = 0; } bool PluginCbImpl::requestStorage(const QString &aStorageName, const SyncPluginBase */*aCaller*/) { FUNCTION_CALL_TRACE(lcButeoTrace); bool requestResult = false; if (imsyncIface) { QStringList storages; storages << aStorageName; QDBusReply gotStorages = imsyncIface->requestStorages(storages); if (!gotStorages.isValid()) qCWarning(lcButeoPlugin) << "Request for storage " << aStorageName << " failed"; else requestResult = gotStorages.value(); } else { qCWarning(lcButeoPlugin) << "msyncd dbus interface is NULL"; } return requestResult; } void PluginCbImpl::releaseStorage(const QString &aStorageName, const SyncPluginBase */*aCaller*/) { FUNCTION_CALL_TRACE(lcButeoTrace); if (imsyncIface) { QStringList storages; storages << aStorageName; imsyncIface->releaseStorages(storages); } else { qCWarning(lcButeoPlugin) << "msyncd dbus interface is NULL"; } } StoragePlugin *PluginCbImpl::createStorage(const QString &aPluginName) { FUNCTION_CALL_TRACE(lcButeoTrace); StoragePlugin *plugin = nullptr; if (!aPluginName.isEmpty()) { plugin = iPluginManager.createStorage(aPluginName); } return plugin; } void PluginCbImpl::destroyStorage(StoragePlugin *aStorage) { FUNCTION_CALL_TRACE(lcButeoTrace); iPluginManager.destroyStorage(aStorage); } bool PluginCbImpl::isConnectivityAvailable(Sync::ConnectivityType aType) { FUNCTION_CALL_TRACE(lcButeoTrace); return iTransportTracker.isConnectivityAvailable(aType); } Profile *PluginCbImpl::getSyncProfileByRemoteAddress(const QString &aAddress) { Q_UNUSED(aAddress); return nullptr; } QString PluginCbImpl::getValue(const QString &aAddress, const QString &aKey) { Q_UNUSED(aAddress); Q_UNUSED(aKey); return ""; } buteo-syncfw-0.11.10/oopp-runner/PluginCbImpl.h000066400000000000000000000043421477124122200212660ustar00rootroot00000000000000/* * This file is part of buteo-sync-plugins package * * Copyright (C) 2013 Jolla Ltd. * * Author: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef PLUGINCBIMPL_H #define PLUGINCBIMPL_H #include "PluginCbInterface.h" #include "PluginManager.h" #include "SyncDaemonProxy.h" #include "TransportTracker.h" namespace Buteo { class PluginCbImpl : public PluginCbInterface { public: PluginCbImpl(); ~PluginCbImpl(); /// \see PluginCbInterface::requestStorage virtual bool requestStorage(const QString &aStorageName, const SyncPluginBase *aCaller); /// \see PluginCbInterface::releaseStorage virtual void releaseStorage(const QString &aStorageName, const SyncPluginBase *aCaller); /// \see PluginCbInterface::createStorage virtual StoragePlugin *createStorage(const QString &aPluginName); /// \see PluginCbInterface::destroyStorage virtual void destroyStorage(StoragePlugin *aStorage); /// \see PluginCbInterface::isConnectivityAvailable virtual bool isConnectivityAvailable(Sync::ConnectivityType aType); /// \see PluginCbInterface::getSyncProfileByRemoteAddress virtual Profile *getSyncProfileByRemoteAddress(const QString &aAddress); /// \see PluginCbInterface::getValue virtual QString getValue(const QString &aAddress, const QString &aKey); signals: //! emitted by releaseStorages call void storageReleased(); private: SyncDaemonProxy *imsyncIface; PluginManager iPluginManager; TransportTracker iTransportTracker; }; } #endif // PLUGINCBIMPL_H buteo-syncfw-0.11.10/oopp-runner/PluginServiceObj.cpp000066400000000000000000000201471477124122200225070ustar00rootroot00000000000000/* * This file is part of buteo-sync-plugins package * * Copyright (C) 2013 - 2021 Jolla Ltd. * * Author: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "PluginServiceObj.h" #include #include #include #include #include #include #include #include using namespace Buteo; PluginServiceObj::PluginServiceObj(const QString &aPluginName, const QString &aProfileName, const QString &aPluginFilePath, QObject *parent) : QObject(parent) , iPluginCb(new Buteo::PluginCbImpl) , iPluginName(aPluginName) , iProfileName(aProfileName) , iPluginFilePath(aPluginFilePath) { } PluginServiceObj::~PluginServiceObj() { delete iPlugin; iPlugin = nullptr; delete iPluginCb; } SyncPluginBase *PluginServiceObj::initializePlugin() { if (!iPluginLoader) { iPluginLoader = new QPluginLoader(iPluginFilePath, this); } iSyncPluginLoader = qobject_cast(iPluginLoader->instance()); if (!iSyncPluginLoader) { qCWarning(lcButeoPlugin) << "Unable to load SyncPluginLoader" << iPluginName << "from path" << iPluginFilePath << "Error:" << iPluginLoader->errorString(); return nullptr; } ProfileManager pm; const QString pluginBaseName = QFileInfo(iPluginFilePath).baseName(); if (pluginBaseName.endsWith(QStringLiteral("-client"))) { SyncProfile *syncProfile = pm.syncProfile(iProfileName); if (!syncProfile) { qCWarning(lcButeoPlugin) << "Profile " << iProfileName << " does not exist"; return nullptr; } // Create the plugin (client) return iSyncPluginLoader->createClientPlugin(iPluginName, *syncProfile, iPluginCb); } else if (pluginBaseName.endsWith(QStringLiteral("-server"))) { Profile *profile = pm.profile(iProfileName, Profile::TYPE_SERVER); if (!profile || !profile->isValid()) { qCWarning(lcButeoPlugin) << "Profile " << iProfileName << " does not exist"; return nullptr; } else { pm.expand(*profile); } // Create the plugin (server) return iSyncPluginLoader->createServerPlugin(iPluginName, *profile, iPluginCb); } else { qCWarning(lcButeoPlugin) << "Plugin is neither -client nor -server type:" << iPluginName; return nullptr; } } bool PluginServiceObj::init() { FUNCTION_CALL_TRACE(lcButeoTrace); iPlugin = initializePlugin(); if (!iPlugin) { qCWarning(lcButeoPlugin) << "PluginServiceObj::init(): unable to initialize plugin" ; return false; } if (ServerPlugin *serverPlugin = qobject_cast(iPlugin)) { // Server signals connect(serverPlugin, &ServerPlugin::newSession, this, &PluginServiceObj::newSession); } // Chain the signals connect(iPlugin, &SyncPluginBase::transferProgress, this, &PluginServiceObj::transferProgress); connect(iPlugin, &SyncPluginBase::error, this, &PluginServiceObj::error); connect(iPlugin, &SyncPluginBase::success, this, &PluginServiceObj::success); connect(iPlugin, &SyncPluginBase::accquiredStorage, this, &PluginServiceObj::accquiredStorage); connect(iPlugin, &SyncPluginBase::syncProgressDetail, this, &PluginServiceObj::syncProgressDetail); return iPlugin->init(); } bool PluginServiceObj::uninit() { FUNCTION_CALL_TRACE(lcButeoTrace); if (!iPlugin) { qCWarning(lcButeoPlugin) << "PluginServiceObj::uninit(): called on uninitialized plugin" ; return true; } if (iPlugin->uninit()) { delete iPlugin; iPlugin = nullptr; iPluginLoader->unload(); return true; } return false; } void PluginServiceObj::abortSync(uchar aStatus) { FUNCTION_CALL_TRACE(lcButeoTrace); if (!iPlugin) { qCWarning(lcButeoPlugin) << "PluginServiceObj::abortSync(): called on uninitialized plugin" ; return; } iPlugin->abortSync(static_cast(aStatus)); } bool PluginServiceObj::cleanUp() { FUNCTION_CALL_TRACE(lcButeoTrace); if (!iPlugin) { iPlugin = initializePlugin(); if (!iPlugin) { qCWarning(lcButeoPlugin) << "PluginServiceObj::cleanUp(): unable to initialize plugin" ; return false; } } return iPlugin->cleanUp(); } void PluginServiceObj::connectivityStateChanged(int aType, bool aState) { FUNCTION_CALL_TRACE(lcButeoTrace); if (!iPlugin) { qCWarning(lcButeoPlugin) << "PluginServiceObj::connectivityStateChanged(): called on uninitialized plugin" ; return; } iPlugin->connectivityStateChanged(static_cast(aType), aState); } QString PluginServiceObj::getSyncResults() { FUNCTION_CALL_TRACE(lcButeoTrace); if (!iPlugin) { qCWarning(lcButeoPlugin) << "PluginServiceObj::getSyncResults(): called on uninitialized plugin" ; return QString(); } return iPlugin->getSyncResults().toString(); } bool PluginServiceObj::startSync() { FUNCTION_CALL_TRACE(lcButeoTrace); if (!iPlugin) { qCWarning(lcButeoPlugin) << "PluginServiceObj::startSync(): called on uninitialized plugin" ; return false; } if (ClientPlugin *clientPlugin = qobject_cast(iPlugin)) { return clientPlugin->startSync(); } else { qCWarning(lcButeoPlugin) << "PluginServiceObj::startSync(): client plugin unavailable" ; return false; } } void PluginServiceObj::resume() { FUNCTION_CALL_TRACE(lcButeoTrace); if (!iPlugin) { qCWarning(lcButeoPlugin) << "PluginServiceObj::resume(): called on uninitialized plugin" ; return; } if (ServerPlugin *serverPlugin = qobject_cast(iPlugin)) { serverPlugin->resume(); } else { qCWarning(lcButeoPlugin) << "PluginServiceObj::resume(): server plugin unavailable" ; } } bool PluginServiceObj::startListen() { FUNCTION_CALL_TRACE(lcButeoTrace); if (!iPlugin) { qCWarning(lcButeoPlugin) << "PluginServiceObj::startListen(): called on uninitialized plugin" ; return false; } if (ServerPlugin *serverPlugin = qobject_cast(iPlugin)) { return serverPlugin->startListen(); } else { qCWarning(lcButeoPlugin) << "PluginServiceObj::startListen(): server plugin unavailable" ; return false; } } void PluginServiceObj::stopListen() { FUNCTION_CALL_TRACE(lcButeoTrace); if (!iPlugin) { qCWarning(lcButeoPlugin) << "PluginServiceObj::stopListen(): called on uninitialized plugin" ; return; } if (ServerPlugin *serverPlugin = qobject_cast(iPlugin)) { serverPlugin->stopListen(); } else { qCWarning(lcButeoPlugin) << "PluginServiceObj::stopListen(): server plugin unavailable" ; } } void PluginServiceObj::suspend() { FUNCTION_CALL_TRACE(lcButeoTrace); if (!iPlugin) { qCWarning(lcButeoPlugin) << "PluginServiceObj::suspend(): called on uninitialized plugin" ; return; } if (ServerPlugin *serverPlugin = qobject_cast(iPlugin)) { serverPlugin->suspend(); } else { qCWarning(lcButeoPlugin) << "PluginServiceObj::suspend(): server plugin unavailable" ; } } buteo-syncfw-0.11.10/oopp-runner/PluginServiceObj.h000066400000000000000000000050371477124122200221550ustar00rootroot00000000000000/* * This file is part of buteo-sync-plugins package * * Copyright (C) 2013 - 2021 Jolla Ltd. * * Author: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef PLUGINSERVICEOBJ_H #define PLUGINSERVICEOBJ_H #include "PluginCbImpl.h" #include #include #include #include class QPluginLoader; using namespace Buteo; class PluginServiceObj : public QObject { Q_OBJECT public: PluginServiceObj(const QString &aPluginName, const QString &aProfileName, const QString &aPluginFilePath, QObject *parent = nullptr); virtual ~PluginServiceObj(); public Q_SLOTS: void abortSync(uchar aStatus); bool cleanUp(); void connectivityStateChanged(int aType, bool aState); QString getSyncResults(); bool init(); bool uninit(); // client functions bool startSync(); // server functions void resume(); bool startListen(); void stopListen(); void suspend(); Q_SIGNALS: void accquiredStorage(const QString &aMimeType); void error(const QString &aProfileName, const QString &aMessage, int aErrorCode); void newSession(const QString &aDestination); void success(const QString &aProfileName, const QString &aMessage); void syncProgressDetail(const QString &aProfileName, int aProgressDetail); void transferProgress(const QString &aProfileName, Sync::TransferDatabase aDatabase, Sync::TransferType aType, const QString &aMimeType, int aCommittedItems); private: SyncPluginBase *initializePlugin(); QPluginLoader *iPluginLoader = nullptr; SyncPluginLoader *iSyncPluginLoader = nullptr; QPointer iPlugin = nullptr; Buteo::PluginCbImpl *iPluginCb = nullptr; QString iPluginName; QString iProfileName; QString iPluginFilePath; }; #endif // PLUGINSERVICEOBJ_H buteo-syncfw-0.11.10/oopp-runner/main.cpp000066400000000000000000000071621477124122200202230ustar00rootroot00000000000000/* * This file is part of buteo-sync-plugins package * * Copyright (C) 2013 - 2021 Jolla Ltd. * * Author: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #include #include #include "PluginServiceObj.h" #include "ButeoPluginIfaceAdaptor.h" #include "Logger.h" #define DBUS_SERVICE_NAME_PREFIX "com.buteo.msyncd.plugin." #define DBUS_SERVICE_OBJ_PATH "/" int main(int argc, char **argv) { QCoreApplication app(argc, argv); Buteo::configureLegacyLogging(); // We obtain the plugin name and the profile name from cmdline // One way to pass the arguments is via cmdline, the other way is // to use the method setPluginParams() dbus method. But setting // cmdline arguments is probably cleaner QStringList args = app.arguments(); if (args.length() < 4) { qCCritical(lcButeoPlugin) << "Plugin name, profile name and plugin path not obtained from cmdline" ; } const QString pluginName = args.value(1); const QString profileName = args.value(2); const QString pluginFilePath = args.value(3); PluginServiceObj *serviceObj = new PluginServiceObj(pluginName, profileName, pluginFilePath); new ButeoPluginIfaceAdaptor(serviceObj); // randomly-generated profile names cannot be registered // as dbus service paths due to being purely numeric. int numericIdx = profileName.indexOf(QRegExp("[0123456789]")); QString servicePath = numericIdx == 0 ? QString(QLatin1String("%1%2%3")) .arg(DBUS_SERVICE_NAME_PREFIX) .arg("profile-") .arg(profileName) : QString(QLatin1String("%1%2")) .arg(DBUS_SERVICE_NAME_PREFIX) .arg(profileName); int retn; qCDebug(lcButeoPlugin) << "attempting to register dbus service:" << servicePath ; QDBusConnection connection = QDBusConnection::sessionBus(); if (connection.registerObject(DBUS_SERVICE_OBJ_PATH, serviceObj)) { if (connection.registerService(servicePath)) { qCDebug(lcButeoPlugin) << "Plugin " << pluginName << " with profile " << profileName << " registered at dbus " << DBUS_SERVICE_NAME_PREFIX + profileName << " and path " << DBUS_SERVICE_OBJ_PATH; // TODO: Should any unix signals be handled? retn = app.exec(); connection.unregisterService(servicePath); } else { qCWarning(lcButeoPlugin) << "Unable to register dbus service" << servicePath << ", terminating."; retn = -1; } connection.unregisterObject(DBUS_SERVICE_OBJ_PATH); } else { qCWarning(lcButeoPlugin) << "Unable to register dbus object" << DBUS_SERVICE_OBJ_PATH << "for service" << servicePath << ", terminating."; retn = -2; } delete serviceObj; return retn; } buteo-syncfw-0.11.10/oopp-runner/oopp-runner.pro000066400000000000000000000010341477124122200215710ustar00rootroot00000000000000TEMPLATE = app TARGET = buteo-oopp-runner QT += dbus QT -= gui INCLUDEPATH += $$PWD \ ../libbuteosyncfw/pluginmgr \ ../libbuteosyncfw/clientfw \ ../libbuteosyncfw/common \ ../libbuteosyncfw/profile LIBS += -lbuteosyncfw5 LIBS += -L../libbuteosyncfw HEADERS += ButeoPluginIfaceAdaptor.h \ PluginCbImpl.h \ PluginServiceObj.h SOURCES += ButeoPluginIfaceAdaptor.cpp \ PluginCbImpl.cpp \ PluginServiceObj.cpp \ main.cpp target.path = /usr/libexec/ INSTALLS += target buteo-syncfw-0.11.10/rpm/000077500000000000000000000000001477124122200150775ustar00rootroot00000000000000buteo-syncfw-0.11.10/rpm/buteo-syncfw-qt5.privileges000066400000000000000000000000321477124122200223210ustar00rootroot00000000000000/usr/bin/msyncd,aceimnpsu buteo-syncfw-0.11.10/rpm/buteo-syncfw-qt5.spec000066400000000000000000000064661477124122200211230ustar00rootroot00000000000000Name: buteo-syncfw-qt5 Version: 0.11.0 Release: 1 Summary: Synchronization backend URL: https://github.com/sailfishos/buteo-syncfw/ License: LGPLv2 Source0: %{name}-%{version}.tar.gz Source1: %{name}.privileges Source2: move-buteo-config.sh BuildRequires: doxygen BuildRequires: fdupes BuildRequires: pkgconfig(Qt5Core) BuildRequires: pkgconfig(Qt5Network) BuildRequires: pkgconfig(Qt5DBus) BuildRequires: pkgconfig(Qt5Sql) BuildRequires: pkgconfig(Qt5Test) BuildRequires: pkgconfig(Qt5Qml) BuildRequires: pkgconfig(dbus-1) BuildRequires: pkgconfig(accounts-qt5) >= 1.13 BuildRequires: pkgconfig(libsignon-qt5) BuildRequires: pkgconfig(qt5-boostable) BuildRequires: pkgconfig(keepalive) BuildRequires: pkgconfig(gio-2.0) BuildRequires: pkgconfig(mce-qt5) >= 1.1.0 BuildRequires: pkgconfig(systemd) BuildRequires: oneshot Requires: oneshot %{_oneshot_requires_post} %description %{summary}. %package devel Summary: Development files for %{name} Requires: %{name} = %{version}-%{release} %description devel %{summary}. %package msyncd Summary: Buteo sync daemon Requires: %{name} = %{version}-%{release} Requires: systemd Requires: systemd-user-session-targets Requires: mapplauncherd-qt5 Provides: buteo-syncfw-msyncd = %{version} Obsoletes: buteo-syncfw-msyncd < %{version} %description msyncd %{summary}. %package doc Summary: Documentation for %{name} %description doc %{summary}. %package tests Summary: Tests for %{name} %description tests %{summary}. %package qml-plugin Summary: QML plugin for %{name} %description qml-plugin %{summary}. %prep %setup -q %build %qmake5 -recursive "VERSION=%{version}" DEFINES+=USE_KEEPALIVE %make_build make doc %{_smp_mflags} %install %qmake5_install %fdupes %{buildroot}/opt/tests/buteo-syncfw/ mkdir -p %{buildroot}%{_userunitdir}/user-session.target.wants ln -s ../msyncd.service %{buildroot}%{_userunitdir}/user-session.target.wants/ mkdir -p %{buildroot}%{_datadir}/mapplauncherd/privileges.d install -m 644 -p %{SOURCE1} %{buildroot}%{_datadir}/mapplauncherd/privileges.d/ mkdir -p %{buildroot}%{_oneshotdir} install -m 755 -p %{SOURCE2} %{buildroot}%{_oneshotdir} mkdir -p %{buildroot}%{_libdir}/buteo-plugins-qt5/oopp %post /sbin/ldconfig %{_bindir}/add-oneshot --all-users --privileged --now move-buteo-config.sh if [ "$1" -ge 1 ]; then systemctl-user daemon-reload || true systemctl-user try-restart msyncd.service || true fi %post msyncd glib-compile-schemas %{_datadir}/glib-2.0/schemas %postun /sbin/ldconfig if [ "$1" -eq 0 ]; then systemctl-user stop msyncd.service || true systemctl-user daemon-reload || true fi %files %license COPYING %{_libdir}/libbuteosyncfw5.so.* %{_libexecdir}/buteo-oopp-runner %{_oneshotdir}/move-buteo-config.sh %files devel %{_includedir}/* %{_libdir}/*.so %{_libdir}/*.prl %{_libdir}/pkgconfig/*.pc %files msyncd %{_userunitdir}/*.service %{_userunitdir}/user-session.target.wants/*.service %{_sysconfdir}/syncwidget %{_bindir}/msyncd %{_datadir}/mapplauncherd/privileges.d/* %{_datadir}/glib-2.0/schemas/* %dir %{_libdir}/buteo-plugins-qt5 %dir %{_libdir}/buteo-plugins-qt5/oopp %files doc %{_docdir}/buteo-syncfw-doc %files tests /opt/tests/buteo-syncfw %{_datadir}/accounts/services/*.service %files qml-plugin %dir %{_libdir}/qt5/qml/Buteo/Profiles %{_libdir}/qt5/qml/Buteo/Profiles/libbuteoprofiles.so %{_libdir}/qt5/qml/Buteo/Profiles/qmldir buteo-syncfw-0.11.10/rpm/move-buteo-config.sh000066400000000000000000000014751477124122200207670ustar00rootroot00000000000000#/bin/sh # move the msyncs configs, logs etc from .cache to .local if [ ! -f $HOME/.local/share/system/privileged/msyncd/cache_dir_migrated ]; then if [ -d $HOME/.cache/msyncd ]; then if [ -d $HOME/.local/share/system/privileged/msyncd/sync ]; then # msyncd probably restarted before this oneshot got executed. just move basic known content mkdir -p $HOME/.local/share/system/privileged/msyncd/sync/logs mv $HOME/.cache/msyncd/sync/*.xml $HOME/.local/share/system/privileged/msyncd/sync mv $HOME/.cache/msyncd/sync/logs/*.xml $HOME/.local/share/system/privileged/msyncd/sync/logs/ else mv $HOME/.cache/msyncd $HOME/.local/share/system/privileged fi fi mkdir -p $HOME/.local/share/system/privileged/msyncd touch $HOME/.local/share/system/privileged/msyncd/cache_dir_migrated fi buteo-syncfw-0.11.10/unittests/000077500000000000000000000000001477124122200163435ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/dummyplugins/000077500000000000000000000000001477124122200211005ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/dummyplugins/dummyclient/000077500000000000000000000000001477124122200234325ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/dummyplugins/dummyclient/DummyClient.cpp000066400000000000000000000037531477124122200264000ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2013 - 2021 Jolla Ltd. * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "DummyClient.h" using namespace Buteo; DummyClient::DummyClient( const QString &aPluginName, const SyncProfile &aProfile, PluginCbInterface *aCbInterface ) : ClientPlugin( aPluginName, aProfile, aCbInterface ) { } DummyClient::~DummyClient() { } bool DummyClient::init() { return true; } bool DummyClient::uninit() { return true; } bool DummyClient::startSync() { return true; } void DummyClient::abortSync() { } bool DummyClient::cleanUp() { return false; } SyncResults DummyClient::getSyncResults() { SyncResults results; return results; } void DummyClient::connectivityStateChanged( Sync::ConnectivityType /*aType*/, bool /*aState*/ ) { } ClientPlugin *DummyClientLoader::createClientPlugin( const QString &aPluginName, const Buteo::SyncProfile &aProfile, Buteo::PluginCbInterface *aCbInterface ) { return new DummyClient( aPluginName, aProfile, aCbInterface ); } buteo-syncfw-0.11.10/unittests/dummyplugins/dummyclient/DummyClient.h000066400000000000000000000037731477124122200260470ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2013 - 2021 Jolla Ltd. * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef DUMMYCLIENT_H #define DUMMYCLIENT_H #include "ClientPlugin.h" #include "SyncPluginLoader.h" namespace Buteo { class DummyClient : public ClientPlugin { Q_OBJECT public: DummyClient( const QString &aPluginName, const SyncProfile &aProfile, PluginCbInterface *aCbInterface ); ~DummyClient(); virtual bool init(); virtual bool uninit(); virtual bool startSync(); virtual void abortSync(); virtual bool cleanUp(); virtual SyncResults getSyncResults(); public slots: virtual void connectivityStateChanged( Sync::ConnectivityType aType, bool aState ); }; class DummyClientLoader : public Buteo::SyncPluginLoader { Q_OBJECT Q_PLUGIN_METADATA(IID "com.buteo.msyncd.test.DummyClientLoader") Q_INTERFACES(Buteo::SyncPluginLoader) public: ClientPlugin *createClientPlugin( const QString &aPluginName, const Buteo::SyncProfile &aProfile, Buteo::PluginCbInterface *aCbInterface ) override; }; } #endif // DUMMYCLIENT_H buteo-syncfw-0.11.10/unittests/dummyplugins/dummyclient/dummyclient.pro000066400000000000000000000006731477124122200265140ustar00rootroot00000000000000TEMPLATE = lib TARGET = hdummy-client DEPENDPATH += . INCLUDEPATH += . \ ../../.. \ ../../../libbuteosyncfw/common \ ../../../libbuteosyncfw/pluginmgr \ ../../../libbuteosyncfw/profile QT -= gui CONFIG += plugin HEADERS += DummyClient.h SOURCES += DummyClient.cpp #clean QMAKE_CLEAN += $(TARGET) $(TARGET0) $(TARGET1) $(TARGET2) QMAKE_CLEAN += $(OBJECTS_DIR)/moc_* target.path = /opt/tests/buteo-syncfw INSTALLS += target buteo-syncfw-0.11.10/unittests/dummyplugins/dummyplugins.pro000066400000000000000000000001361477124122200243570ustar00rootroot00000000000000TEMPLATE = subdirs SUBDIRS += dummyclient \ dummyserver \ dummystorage buteo-syncfw-0.11.10/unittests/dummyplugins/dummyserver/000077500000000000000000000000001477124122200234625ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/dummyplugins/dummyserver/DummyServer.cpp000066400000000000000000000037101477124122200264510ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2013 - 2021 Jolla Ltd. * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "DummyServer.h" using namespace Buteo; DummyServer::DummyServer( const QString &aPluginName, const Profile &aProfile, PluginCbInterface *aCbInterface ) : ServerPlugin( aPluginName, aProfile, aCbInterface ) { } DummyServer::~DummyServer() { } bool DummyServer::init() { return true; } bool DummyServer::uninit() { return true; } bool DummyServer::startListen() { return true; } bool DummyServer::cleanUp() { return true; } void DummyServer::stopListen() { } void DummyServer::suspend() { } void DummyServer::resume() { } void DummyServer::connectivityStateChanged( Sync::ConnectivityType /*aType*/, bool /*aState*/ ) { } ServerPlugin *DummyServerLoader::createServerPlugin( const QString &aPluginName, const Buteo::Profile &aProfile, Buteo::PluginCbInterface *aCbInterface ) { return new DummyServer( aPluginName, aProfile, aCbInterface ); } buteo-syncfw-0.11.10/unittests/dummyplugins/dummyserver/DummyServer.h000066400000000000000000000040131477124122200261130ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2013 - 2021 Jolla Ltd. * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef DUMMYSERVER_H #define DUMMYSERVER_H #include "ServerPlugin.h" #include "SyncPluginLoader.h" namespace Buteo { class DummyServer : public ServerPlugin { Q_OBJECT public: DummyServer( const QString &aPluginName, const Profile &aProfile, PluginCbInterface *aCbInterface ); virtual ~DummyServer(); virtual bool init(); virtual bool uninit(); virtual bool startListen(); virtual void stopListen(); virtual void suspend(); virtual void resume(); virtual bool cleanUp(); public slots: virtual void connectivityStateChanged( Sync::ConnectivityType aType, bool aState ); }; class DummyServerLoader : public Buteo::SyncPluginLoader { Q_OBJECT Q_PLUGIN_METADATA(IID "com.buteo.msyncd.test.DummyServerLoader") Q_INTERFACES(Buteo::SyncPluginLoader) public: ServerPlugin *createServerPlugin( const QString &aPluginName, const Buteo::Profile &aProfile, Buteo::PluginCbInterface *aCbInterface ) override; }; } #endif // DUMMYSERVER_H buteo-syncfw-0.11.10/unittests/dummyplugins/dummyserver/dummyserver.pro000066400000000000000000000007031477124122200265660ustar00rootroot00000000000000TEMPLATE = lib TARGET = hdummy-server DEPENDPATH += . INCLUDEPATH += . \ ../../.. \ ../../../libbuteosyncfw/common \ ../../../libbuteosyncfw/pluginmgr \ ../../../libbuteosyncfw/profile CONFIG += plugin QT -= gui #input HEADERS += DummyServer.h SOURCES += DummyServer.cpp #clean QMAKE_CLEAN += $(TARGET) $(TARGET0) $(TARGET1) $(TARGET2) QMAKE_CLEAN += $(OBJECTS_DIR)/moc_* target.path = /opt/tests/buteo-syncfw/ INSTALLS += target buteo-syncfw-0.11.10/unittests/dummyplugins/dummystorage/000077500000000000000000000000001477124122200236205ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/dummyplugins/dummystorage/DummyStorage.cpp000066400000000000000000000071471477124122200267550ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2013 - 2021 Jolla Ltd. * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "DummyStorage.h" using namespace Buteo; DummyStorage::DummyStorage( const QString &aPluginName ) : StoragePlugin( aPluginName ) { } DummyStorage::~DummyStorage() { } bool DummyStorage::init( const QMap & /*aProperties*/ ) { return true; } bool DummyStorage::uninit() { return true; } bool DummyStorage::getAllItems( QList & /*aItems*/ ) { return true; } bool DummyStorage::getNewItems( QList & /*aNewItems*/, const QDateTime & /*aTime*/ ) { return true; } bool DummyStorage::getModifiedItems( QList & /*aModifiedItems*/, const QDateTime & /*aTime*/ ) { return true; } bool DummyStorage::getDeletedItems( QList & /*aDeletedItems*/, const QDateTime & /*aTime*/ ) { return true; } bool DummyStorage::getAllItemIds( QList & /*aItems*/ ) { return true; } bool DummyStorage::getNewItemIds( QList & /*aNewItems*/, const QDateTime & /*aTime*/ ) { return true; } bool DummyStorage::getModifiedItemIds( QList & /*aModifiedItems*/, const QDateTime & /*aTime*/ ) { return true; } bool DummyStorage::getDeletedItemIds( QList & /*aDeletedItems*/, const QDateTime & /*aTime*/ ) { return true; } StorageItem *DummyStorage::newItem() { return nullptr; } StorageItem *DummyStorage::getItem( const QString & /*aItemId*/ ) { return nullptr; } QList DummyStorage::getItems(const QStringList & /*aItemIdList*/ ) { QList items; return items; } StoragePlugin::OperationStatus DummyStorage::addItem( StorageItem & /*aItem*/ ) { return STATUS_OK; } QList DummyStorage::addItems( const QList &aItems ) { QList statuses; for ( int i = 0; i < aItems.count(); ++i ) { statuses.append( STATUS_OK ); } return statuses; } StoragePlugin::OperationStatus DummyStorage::modifyItem( StorageItem & /*aItem*/ ) { return STATUS_OK; } QList DummyStorage::modifyItems( const QList &aItems ) { QList statuses; for ( int i = 0; i < aItems.count(); ++i ) { statuses.append( STATUS_OK ); } return statuses; } StoragePlugin::OperationStatus DummyStorage::deleteItem( const QString & /*aItemId*/ ) { return STATUS_OK; } QList DummyStorage::deleteItems( const QList &aItemIds ) { QList statuses; for ( int i = 0; i < aItemIds.count(); ++i ) { statuses.append( STATUS_OK ); } return statuses; } StoragePlugin *DummyStorageLoader::createPlugin( const QString &aPluginName ) { return new DummyStorage( aPluginName ); } buteo-syncfw-0.11.10/unittests/dummyplugins/dummystorage/DummyStorage.h000066400000000000000000000054561477124122200264230ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2013 - 2021 Jolla Ltd. * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef DUMMYSTORAGE_H #define DUMMYSTORAGE_H #include "StoragePlugin.h" #include "StoragePluginLoader.h" namespace Buteo { class DummyStorage : public StoragePlugin { public: DummyStorage( const QString &aPluginName ); virtual ~DummyStorage(); virtual bool init( const QMap &aProperties ); virtual bool uninit(); virtual bool getAllItems( QList &aItems ); virtual bool getNewItems( QList &aNewItems, const QDateTime &aTime ); virtual bool getModifiedItems( QList &aModifiedItems, const QDateTime &aTime ); virtual bool getDeletedItems( QList &aDeletedItems, const QDateTime &aTime ); virtual bool getAllItemIds( QList &aItems ); virtual bool getNewItemIds( QList &aNewItems, const QDateTime &aTime ); virtual bool getModifiedItemIds( QList &aModifiedItems, const QDateTime &aTime ); virtual bool getDeletedItemIds( QList &aDeletedItems, const QDateTime &aTime ); virtual StorageItem *newItem(); virtual StorageItem *getItem( const QString &aItemId ); virtual QList getItems(const QStringList &aItemIdList ); virtual OperationStatus addItem( StorageItem &aItem ); virtual QList addItems( const QList &aItems ); virtual OperationStatus modifyItem( StorageItem &aItem ); virtual QList modifyItems( const QList &aItems ); virtual OperationStatus deleteItem( const QString &aItemId ); virtual QList deleteItems( const QList &aItemIds ); }; class DummyStorageLoader : public Buteo::StoragePluginLoader { Q_OBJECT Q_PLUGIN_METADATA(IID "com.buteo.msyncd.test.DummyStorageLoader") Q_INTERFACES(Buteo::StoragePluginLoader) public: StoragePlugin *createPlugin( const QString &aPluginName ); }; } #endif // DUMMYSTORAGE_H buteo-syncfw-0.11.10/unittests/dummyplugins/dummystorage/dummystorage.pro000066400000000000000000000007061477124122200270650ustar00rootroot00000000000000TEMPLATE = lib TARGET = hdummy-storage DEPENDPATH += . INCLUDEPATH += . \ ../../.. \ ../../../libbuteosyncfw/common \ ../../../libbuteosyncfw/pluginmgr \ ../../../libbuteosyncfw/profile CONFIG += plugin QT -= gui #input HEADERS += DummyStorage.h SOURCES += DummyStorage.cpp #clean QMAKE_CLEAN += $(TARGET) $(TARGET0) $(TARGET1) $(TARGET2) QMAKE_CLEAN += $(OBJECTS_DIR)/moc_* target.path = /opt/tests/buteo-syncfw/ INSTALLS += target buteo-syncfw-0.11.10/unittests/tests/000077500000000000000000000000001477124122200175055ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/msyncdtests/000077500000000000000000000000001477124122200220655ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/msyncdtests/AccountsHelperTest/000077500000000000000000000000001477124122200256445ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/msyncdtests/AccountsHelperTest/AccountsHelperTest.cpp000066400000000000000000000053361477124122200321360ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "AccountsHelperTest.h" #include #include using namespace Buteo; static const QString PROFILE_XML = "" "" "" "" ""; static const QString OVI_PROVIDER = "ovi"; static const QString DUMMY_USER = "dummy"; static const QString USERPROFILE_DIR = "syncprofiletests/testprofiles/user"; static const QString SYSTEMPROFILE_DIR = "syncprofiletests/testprofiles/system"; static const QString SERVICE_SYNC = "Sync"; static const QString SERVICE_NAME = "testsync-ovi"; AccountsHelperTest::AccountsHelperTest() : QObject(nullptr), iManager(SERVICE_SYNC, this) { iProfileManager.setPaths(USERPROFILE_DIR, SYSTEMPROFILE_DIR); iAccountsHelper = new AccountsHelper(iProfileManager, 0); } void AccountsHelperTest::initTestCase() { iAccount = iManager.createAccount(OVI_PROVIDER); iAccount->setDisplayName(DUMMY_USER); iAccount->setEnabled(true); Accounts::Service service = iManager.service(SERVICE_NAME); iAccount->selectService(service); iAccount->setEnabled(true); iAccount->selectService(); QVERIFY(iAccount != nullptr); iAccount->sync(); } void AccountsHelperTest::cleanupTestCase() { if (iAccount != nullptr) { iAccount->remove(); iAccount->sync(); delete iAccount; iAccount = nullptr; } // no else } void AccountsHelperTest::testProfileAdded() { // Ensure that the profile with the username was added correctly SyncProfile *syncProfile = iProfileManager.syncProfile(SERVICE_NAME + "-" + iAccount->displayName()); //QVERIFY(syncProfile != 0); Q_UNUSED(syncProfile); } void AccountsHelperTest::testAddAccountData() { } QTEST_MAIN(Buteo::AccountsHelperTest) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/AccountsHelperTest/AccountsHelperTest.h000066400000000000000000000026231477124122200315770ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef ACCOUNTSHELPERTEST_H #define ACCOUNTSHELPERTEST_H #include #include "AccountsHelper.h" #include namespace Buteo { class AccountsHelperTest: public QObject { Q_OBJECT public: AccountsHelperTest(); private slots: void initTestCase(); void cleanupTestCase(); void testProfileAdded(); void testAddAccountData(); private: Accounts::Manager iManager; Accounts::Account *iAccount; ProfileManager iProfileManager; AccountsHelper *iAccountsHelper; }; } #endif // ACCOUNTSHELPERTEST_H buteo-syncfw-0.11.10/unittests/tests/msyncdtests/AccountsHelperTest/AccountsHelperTest.pro000066400000000000000000000000461477124122200321450ustar00rootroot00000000000000include(../msyncdtestapplication.pri) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/ClientPluginRunnerTest/000077500000000000000000000000001477124122200265145ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/msyncdtests/ClientPluginRunnerTest/ClientPluginRunnerTest.cpp000066400000000000000000000114271477124122200336540ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "ClientPluginRunnerTest.h" #include "PluginManager.h" #include "synchronizer.h" #include "ClientThread.h" using namespace Buteo; const QString PROFILE = "Profile"; const QString PLUGIN = "plugin"; void ClientPluginRunnerTest::initTestCase() { iSprofile = new SyncProfile(PROFILE); iPluginMgr = new PluginManager(); iPluginCbIf = new Synchronizer(nullptr); iClientPrunner = new ClientPluginRunner(PLUGIN, iSprofile, iPluginMgr, iPluginCbIf, nullptr); } void ClientPluginRunnerTest::cleanupTestCase() { QVERIFY(iSprofile); delete iSprofile; QVERIFY(iClientPrunner); delete iClientPrunner; QVERIFY(iPluginMgr); delete iPluginMgr; QVERIFY(iPluginCbIf); delete iPluginCbIf; iSprofile = 0; iPluginMgr = 0; iPluginCbIf = 0; iClientPrunner = 0; } void ClientPluginRunnerTest::testCpluginRunnerConstructor() { SyncResults syncRes; bool dateTime; QDateTime current; QVERIFY(iClientPrunner->plugin() == 0); QVERIFY(iClientPrunner->iThread == 0); QCOMPARE(iClientPrunner->iProfile, iSprofile); QCOMPARE(iClientPrunner->pluginType(), PluginRunner::PLUGIN_CLIENT); QCOMPARE(iClientPrunner->pluginName(), PLUGIN); QCOMPARE(iClientPrunner->iInitialized, false); QVERIFY(iClientPrunner->iPluginMgr != 0); QVERIFY(iClientPrunner->iPluginCbIf != 0); //test syncResults() current = QDateTime::currentDateTime(); syncRes = iClientPrunner->syncResults(); QCOMPARE(syncRes.majorCode(), SyncResults::SYNC_RESULT_SUCCESS); QCOMPARE(syncRes.isScheduled(), false); dateTime = syncRes.syncTime() >= current; QVERIFY(dateTime); } void ClientPluginRunnerTest::testInit() { SyncResults syncRes; iClientPrunner->iInitialized = true; QCOMPARE(iClientPrunner->init(), true); iClientPrunner->iInitialized = false; QVERIFY(iClientPrunner->iPluginMgr != 0); QVERIFY(iClientPrunner->iPluginCbIf != 0); QVERIFY(iClientPrunner->iProfile != 0); QCOMPARE(iClientPrunner->init(), false); QVERIFY(iClientPrunner->plugin() == 0); QVERIFY(iClientPrunner->iThread == 0); iClientPrunner->iInitialized = true; QCOMPARE(iClientPrunner->iInitialized, true); } void ClientPluginRunnerTest::testStart() { //test start() QCOMPARE(iClientPrunner->iInitialized, true); QVERIFY(iClientPrunner->iThread == 0); QCOMPARE(iClientPrunner->start(), false); iClientPrunner->iThread = new ClientThread(); QVERIFY(iClientPrunner->iThread != 0); QCOMPARE(iClientPrunner->start(), false); } void ClientPluginRunnerTest::testSignals() { QSignalSpy doneSignal(iClientPrunner, SIGNAL(done())); iClientPrunner->onThreadExit(); QCOMPARE(doneSignal.count(), 1); QSignalSpy successSignal(iClientPrunner, SIGNAL(success(const QString &, const QString &))); iClientPrunner->onSuccess(PROFILE, "Message"); QCOMPARE(successSignal.count(), 1); QSignalSpy errorSignal(iClientPrunner, SIGNAL(error(const QString &, const QString &, SyncResults::MinorCode))); iClientPrunner->onError(PROFILE, "Message", SyncResults::PLUGIN_ERROR); QCOMPARE(errorSignal.count(), 1); QSignalSpy transferSignal(iClientPrunner, SIGNAL(transferProgress(const QString &, Sync::TransferDatabase, Sync::TransferType, const QString &, int))); qRegisterMetaType("Sync::TransferDatabase"); qRegisterMetaType("Sync::TransferType"); iClientPrunner->onTransferProgress(PROFILE, Sync::LOCAL_DATABASE, Sync::ITEM_ADDED, "Mime", 1); QCOMPARE(transferSignal.count(), 1); } QTEST_MAIN(Buteo::ClientPluginRunnerTest) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/ClientPluginRunnerTest/ClientPluginRunnerTest.h000066400000000000000000000027041477124122200333170ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef CLIENTPLUGINRUNNERTEST_H_ #define CLIENTPLUGINRUNNERTEST_H_ #include #include #include "ClientPluginRunner.h" #include "SyncProfile.h" namespace Buteo { class ClientPluginRunnerTest: public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testCpluginRunnerConstructor(); void testInit(); void testStart(); void testSignals(); private: ClientPluginRunner *iClientPrunner; SyncProfile *iSprofile; PluginManager *iPluginMgr; PluginCbInterface *iPluginCbIf; }; } #endif /*CLIENTPLUGINRUNNERTEST_H_*/ buteo-syncfw-0.11.10/unittests/tests/msyncdtests/ClientPluginRunnerTest/ClientPluginRunnerTest.pro000066400000000000000000000000461477124122200336650ustar00rootroot00000000000000include(../msyncdtestapplication.pri) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/ClientThreadTest/000077500000000000000000000000001477124122200252735ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/msyncdtests/ClientThreadTest/ClientThreadTest.cpp000066400000000000000000000101711477124122200312050ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "ClientThreadTest.h" #include "SyncResults.h" using namespace Buteo; const QString PROFILE = "profile"; const QString PLUGIN = "plugin"; //QString TYPE = Profile::TYPE_CLIENT; //Defining pure virtual functions of base classes bool ClientPluginDerived::startSync() { return true; } bool ClientPluginDerived::init() { if (iTestClSignal == true) { return false; } return true; } bool ClientPluginDerived::uninit() { return true; } void ClientPluginDerived::connectivityStateChanged(Sync::ConnectivityType, bool) { } bool ClientPluginDerived::cleanUp() { return true; } //Constructor of the derived class ClientPluginDerived::ClientPluginDerived(const QString &aPluginName, const SyncProfile &aProfile, PluginCbInterface *aCbInterface) : ClientPlugin(aPluginName, aProfile, aCbInterface), iTestClSignal(false) { } void ClientThreadTest::initTestCase() { iClientThread = new ClientThread(); iSyncProfile = new SyncProfile(PROFILE); iPluginDerived = new ClientPluginDerived(PLUGIN, *iSyncProfile, nullptr); iPlugin = iPluginDerived; iClientThreadRet = false; } void ClientThreadTest::cleanupTestCase() { QVERIFY(iSyncProfile != 0); delete iSyncProfile; QVERIFY(iPluginDerived != 0); delete iPluginDerived; QVERIFY(iClientThread != 0); delete iClientThread; iSyncProfile = 0; iPluginDerived = 0; iClientThread = 0; iPlugin = 0; } void ClientThreadTest::testClientThreadConstructor() { QVERIFY(iClientThread->getPlugin() == nullptr); QCOMPARE(iClientThread->iRunning, false); } void ClientThreadTest::testGetPlugin() { iClientThreadRet = iClientThread->startThread(iPlugin); QTest::qWait(20); QCOMPARE(iClientThreadRet, true); QCOMPARE(iClientThread->iRunning, true); QCOMPARE(iClientThread->getPlugin(), iPlugin); } void ClientThreadTest::testGetProfileName() { QCOMPARE(iClientThreadRet, true); QCOMPARE(iClientThread->getProfileName(), PROFILE); } void ClientThreadTest::testClientThread() { //The thread is already running QCOMPARE(iClientThread->startThread(iPlugin), false); QCOMPARE(iClientThread->iRunning, true); } void ClientThreadTest::testGetSyncResults() { SyncResults syncRes; bool dateTime; QDateTime current; QVERIFY(iClientThread != 0); current = QDateTime::currentDateTime(); iClientThread->stopThread(); iClientThread->wait(9000); QCOMPARE(iClientThread->iRunning, false); syncRes = iClientThread->getSyncResults(); QCOMPARE(syncRes.majorCode(), SyncResults::SYNC_RESULT_SUCCESS); QCOMPARE(syncRes.isScheduled(), false); dateTime = syncRes.syncTime() >= current; QVERIFY(dateTime); //QCOMPARE(syncRes.syncTime().date(), QDateTime::currentDateTime().date()); } void ClientThreadTest::testInitError() { iPluginDerived->iTestClSignal = true; //Test for the signal QSignalSpy spy(iClientThread, SIGNAL(initError(const QString &, const QString &, SyncResults::MinorCode))); QCOMPARE(iClientThread->startThread(iPlugin), true); QTest::qWait(20); QCOMPARE(spy.count(), 1); } QTEST_MAIN(Buteo::ClientThreadTest) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/ClientThreadTest/ClientThreadTest.h000066400000000000000000000037701477124122200306610ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef CLIENTTHREADTEST_H_ #define CLIENTTHREADTEST_H_ #include #include #include #include "ClientThread.h" #include "ClientPlugin.h" #include "SyncProfile.h" namespace Buteo { class ClientPluginDerived: public ClientPlugin { Q_OBJECT public: ClientPluginDerived(const QString &aPluginName, const SyncProfile &aProfile, PluginCbInterface *aCbInterface); bool startSync(); bool init(); bool uninit(); virtual bool cleanUp(); bool iTestClSignal; public slots: void connectivityStateChanged(Sync::ConnectivityType aType, bool aState); }; class ClientThreadTest: public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testClientThreadConstructor(); void testGetPlugin(); void testGetProfileName(); void testClientThread(); void testGetSyncResults(); void testInitError(); private: ClientThread *iClientThread; ClientPluginDerived *iPluginDerived; ClientPlugin *iPlugin; SyncProfile *iSyncProfile; bool iClientThreadRet; }; } #endif /*CLIENTTHREADTEST_H_*/ buteo-syncfw-0.11.10/unittests/tests/msyncdtests/ClientThreadTest/ClientThreadTest.pro000066400000000000000000000000461477124122200312230ustar00rootroot00000000000000include(../msyncdtestapplication.pri) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/IPHeartBeatTest/000077500000000000000000000000001477124122200250155ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/msyncdtests/IPHeartBeatTest/IPHeartBeatTest.cpp000066400000000000000000000043111477124122200304500ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "IPHeartBeatTest.h" #include "IPHeartBeat.h" using namespace Buteo; void IPHeartBeatTest::initTestCase() { iHbeat = new IPHeartBeat(this); connect(iHbeat, SIGNAL(onHeartBeat(QString)), this, SLOT(onBeatTriggered(QString))); } void IPHeartBeatTest::cleanupTestCase() { delete iHbeat; } void IPHeartBeatTest::testSetHeartBeat() { iBeatReceived = false; if (iHbeat->setHeartBeat("someprofile", 0, 2) == true) { QTest::qWait(1000 * 3); QVERIFY(iBeatReceived == true); } else { // If iphbd service is not running setHeartBeat() will fail // Not letting that to make the test to fail qDebug() << "******* Start the iphbd service and run this test *******"; } } void IPHeartBeatTest::testRemoveHeartBeat() { iHbeat->setHeartBeat("someprofile", 0, 20); iHbeat->removeWait("someprofile"); QVERIFY(iHbeat->iBeatsWaiting.contains("someprofile") == false); } void IPHeartBeatTest::testRemoveAllHeartBeats() { iHbeat->setHeartBeat("someprofile", 0, 20); iHbeat->setHeartBeat("anotherprofile", 50, 100); iHbeat->removeAllWaits(); QVERIFY(iHbeat->iBeatsWaiting.size() == 0); } void IPHeartBeatTest::testInternalBeat() { iHbeat->internalBeatTriggered(-1); } void IPHeartBeatTest::onBeatTriggered(QString /*aProfName*/) { iBeatReceived = true; } QTEST_MAIN(Buteo::IPHeartBeatTest) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/IPHeartBeatTest/IPHeartBeatTest.h000066400000000000000000000025261477124122200301230ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef IPHEARTBEAT_TEST_H #define IPHEARTBEAT_TEST_H #include #include namespace Buteo { class IPHeartBeat; class IPHeartBeatTest: public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testSetHeartBeat(); void testRemoveHeartBeat(); void testRemoveAllHeartBeats(); void testInternalBeat(); void onBeatTriggered(QString aProfName); private: IPHeartBeat *iHbeat; bool iBeatReceived; }; } #endif buteo-syncfw-0.11.10/unittests/tests/msyncdtests/IPHeartBeatTest/IPHeartBeatTest.pro000066400000000000000000000000461477124122200304670ustar00rootroot00000000000000include(../msyncdtestapplication.pri) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/PluginRunnerTest/000077500000000000000000000000001477124122200253555ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/msyncdtests/PluginRunnerTest/PluginRunnerTest.cpp000066400000000000000000000036061477124122200313560ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "PluginRunnerTest.h" using namespace Buteo; const QString PLUGIN = "plugin"; const QString PROFILE = "profile"; void PluginRunnerTest::initTestCase() { iSProfile = new SyncProfile(PROFILE); iPManager = new PluginManager(); iClientPRunner = new ClientPluginRunner(PLUGIN, iSProfile, iPManager, nullptr, nullptr); iPRunner = iClientPRunner; } void PluginRunnerTest::testPluginRunnerConstructor() { QCOMPARE(iPRunner->iInitialized, false); QCOMPARE(iPRunner->iPluginMgr, iPManager); QVERIFY(iPRunner->iPluginCbIf == nullptr); QCOMPARE(iPRunner->iType, PluginRunner::PLUGIN_CLIENT); QCOMPARE(iPRunner->pluginType(), PluginRunner::PLUGIN_CLIENT); QCOMPARE(iPRunner->pluginName(), PLUGIN); } void PluginRunnerTest::cleanupTestCase() { QVERIFY(iPManager != 0); delete iPManager; QVERIFY(iSProfile != 0); delete iSProfile; QVERIFY(iClientPRunner != 0); delete iClientPRunner; iPManager = 0; iSProfile = 0; iClientPRunner = 0; iPRunner = 0; } QTEST_MAIN(Buteo::PluginRunnerTest) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/PluginRunnerTest/PluginRunnerTest.h000066400000000000000000000026241477124122200310220ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef PLUGINRUNNERTEST_H_ #define PLUGINRUNNERTEST_H_ #include #include #include "PluginRunner.h" #include "PluginManager.h" #include "SyncProfile.h" #include "ClientPluginRunner.h" namespace Buteo { class PluginRunnerTest : public QObject { Q_OBJECT private slots: void initTestCase(); void testPluginRunnerConstructor(); void cleanupTestCase(); private: PluginRunner *iPRunner; PluginManager *iPManager; SyncProfile *iSProfile; ClientPluginRunner *iClientPRunner; }; } #endif /*PLUGINRUNNERTEST_H_*/ buteo-syncfw-0.11.10/unittests/tests/msyncdtests/PluginRunnerTest/PluginRunnerTest.pro000066400000000000000000000000461477124122200313670ustar00rootroot00000000000000include(../msyncdtestapplication.pri) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/ServerActivatorTest/000077500000000000000000000000001477124122200260505ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/msyncdtests/ServerActivatorTest/ServerActivatorTest.cpp000066400000000000000000000157341477124122200325510ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "ServerActivatorTest.h" #include #include #include #include "Profile.h" #include #include using namespace Buteo; void ServerActivatorTest::initTestCase() { iTransportTracker = new TransportTracker(this); iProfileManager = new ProfileManager; iProfileManager->setPaths("profile1", "profile2"); // failure in next step would result in crash upon cleanupTestCase() iServerActivator = 0; // add server profiles through Profile class Profile myProfile("sampleServerProfile", Profile::TYPE_SERVER); QVERIFY(!iProfileManager->updateProfile(myProfile).isEmpty()); Profile myProfile2("sampleServerProfile2", Profile::TYPE_SERVER); iProfileManager->updateProfile(myProfile2); iServerActivator = new ServerActivator(*iProfileManager, *iTransportTracker, this); // iServers.keys() does the same QVERIFY(iServerActivator->iServers.count()); } void ServerActivatorTest::cleanupTestCase() { // deallocate the memory delete iServerActivator; delete iProfileManager; delete iTransportTracker; } void ServerActivatorTest :: testRef() { // dummy server name which fails const QString SERVERNAME = "dummy"; // saved server profile name const QString ANOTHERSERVER = "sampleServerProfile"; QSignalSpy enabledSpy(iServerActivator, SIGNAL(serverEnabled(QString))); QSignalSpy disabledSpy(iServerActivator, SIGNAL(serverDisabled(QString))); int returnedVal = iServerActivator->addRef(SERVERNAME); // this call should fail as there is no "dummy" profile saved. so compare with expected value '0' QCOMPARE(returnedVal, 0); QCOMPARE(enabledSpy.count(), 0); returnedVal = iServerActivator->addRef(ANOTHERSERVER); // when the server name is found, ref count will be incremented and returned to the calling function QCOMPARE(returnedVal, 1); QCOMPARE(enabledSpy.count(), 1); returnedVal = iServerActivator->removeRef(SERVERNAME); QCOMPARE(returnedVal, 0); returnedVal = iServerActivator->removeRef(ANOTHERSERVER); QCOMPARE(returnedVal, 0); QCOMPARE(disabledSpy.count(), 1); // signals will be emitted only if reference count is 1 for addRef and 0 for removeRef } void ServerActivatorTest :: testEnabledServers() { // test enableServers() without adding server name. expected result is empty stringlist QStringList serverData = iServerActivator->enabledServers(); QVERIFY(serverData.empty()); // add a servername and test the function again(). expected result is added // server name as stringlist returned iServerActivator->addRef("sampleServerProfile2"); serverData = iServerActivator->enabledServers(); QVERIFY(serverData.count() && serverData.contains("sampleServerProfile2")); } void ServerActivatorTest :: testConnectivityStateChanged() { // adding a server profile and use USB transport medium const QString SERVER_XML = " " " " " "; QDomDocument doc; QVERIFY(doc.setContent(SERVER_XML, false)); Profile sampleServerProfile(doc.documentElement()); sampleServerProfile.setName("sampleProfile"); const QString PROFILE_PATH("syncprofiletests/testprofiles/user"); ProfileManager myProfileManager; myProfileManager.setPaths(PROFILE_PATH, PROFILE_PATH); myProfileManager.updateProfile(sampleServerProfile); TransportTracker myTrasportTracker(this); ServerActivator sampleServerActivator(myProfileManager, myTrasportTracker, this); QSignalSpy enabledSpy(&sampleServerActivator, SIGNAL(serverEnabled(QString))); QSignalSpy disabledSpy(&sampleServerActivator, SIGNAL(serverDisabled(QString))); // make sure that the present count is '0' QCOMPARE(enabledSpy.count(), 0); sampleServerActivator.onConnectivityStateChanged(Sync::CONNECTIVITY_USB, true); // after calling onConnectivityStateChanged(), total signal emissions will be total number // of server profiles available. QCOMPARE(enabledSpy.count(), 1); // make sure that the present count is '0' QCOMPARE(disabledSpy.count(), 0); sampleServerActivator.onConnectivityStateChanged(Sync::CONNECTIVITY_USB, false); QCOMPARE(disabledSpy.count(), 1); } void ServerActivatorTest :: testTransportsFromProfile() { Profile profileToSetKey("profile", Profile::TYPE_SERVER); profileToSetKey.setKey("profilekey", "keyval"); QList typeReceived = iServerActivator->transportsFromProfile(&profileToSetKey); QVERIFY(typeReceived.empty() != 0); // XML profiles creation const QString USB_XML = " " " " " "; const QString BT_XML = " " " " " "; const QString INTERNET_XML = " " " " " "; // test for USB transport QDomDocument doc; QVERIFY(doc.setContent(USB_XML, false)); Profile usbServerProfile(doc.documentElement()); typeReceived = iServerActivator->transportsFromProfile(&usbServerProfile); QCOMPARE(typeReceived.count(), 1); QCOMPARE(typeReceived.takeFirst(), Sync::CONNECTIVITY_USB); // test for BT transport QVERIFY(doc.setContent(BT_XML, false)); Profile btServerProfile(doc.documentElement()); typeReceived = iServerActivator->transportsFromProfile(&btServerProfile); QCOMPARE(typeReceived.count(), 1); QCOMPARE(typeReceived.takeFirst(), Sync::CONNECTIVITY_BT); // test for internet transport QVERIFY(doc.setContent(INTERNET_XML, false)); Profile internetServerProfile(doc.documentElement()); typeReceived = iServerActivator->transportsFromProfile(&internetServerProfile); QCOMPARE(typeReceived.count(), 1); QCOMPARE(typeReceived.takeFirst(), Sync::CONNECTIVITY_INTERNET); } QTEST_MAIN(Buteo::ServerActivatorTest) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/ServerActivatorTest/ServerActivatorTest.h000066400000000000000000000026411477124122200322070ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SERVERACTIVATOR_H #define SERVERACTIVATOR_H #include "ServerActivator.h" #include "TransportTracker.h" #include namespace Buteo { class ServerActivatorTest : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testRef(); void testEnabledServers(); void testConnectivityStateChanged(); void testTransportsFromProfile(); private: ServerActivator *iServerActivator; TransportTracker *iTransportTracker; ProfileManager *iProfileManager; }; } #endif // SERVERACTIVATOR_H buteo-syncfw-0.11.10/unittests/tests/msyncdtests/ServerActivatorTest/ServerActivatorTest.pro000066400000000000000000000000461477124122200325550ustar00rootroot00000000000000include(../msyncdtestapplication.pri) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/ServerPluginRunnerTest/000077500000000000000000000000001477124122200265445ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/msyncdtests/ServerPluginRunnerTest/ServerPluginRunnerTest.cpp000066400000000000000000000107771477124122200337430ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "ServerPluginRunnerTest.h" #include #include #include #define TEST_PLUGIN_PATH "/opt/tests/buteo-syncfw" using namespace Buteo; void ServerPluginRunnerTest::initTestCase() { iTransportTracker = new TransportTracker(this); iProfileManager = new ProfileManager; iProfileManager->setPaths("profile1", "profile2"); iProfile = new Profile("dummyprofile", Profile::TYPE_SERVER); iProfileManager->updateProfile(*iProfile); iServerActivator = new ServerActivator(*iProfileManager, *iTransportTracker); iPluginManager = new PluginManager(TEST_PLUGIN_PATH) ; // TODO: need to update with valid PluginCbInterface pointer iServerPluginRunner = new ServerPluginRunner("hdummy", iProfile, iPluginManager, reinterpret_cast(this), iServerActivator); /* test init() */ bool initResult = iServerPluginRunner->init(); // after returning from init(), iPlugin should have the pointer to server thread and // iThread memory allocation should be success QVERIFY(iServerPluginRunner->plugin()); QVERIFY(iServerPluginRunner->iThread); QVERIFY(initResult); } void ServerPluginRunnerTest::cleanupTestCase() { // test ThreadStop() QVERIFY(iServerPluginRunner->iThread); iServerPluginRunner->stop(); // deallocate allocated memory delete iServerPluginRunner; delete iPluginManager; delete iServerActivator; delete iProfileManager; delete iTransportTracker; /* don't deallocate memory for iProfile, as this will be deallocated by * ServerPluginRunner class. */ } void ServerPluginRunnerTest::testStartAbort() { /* testing start() */ QVERIFY(iServerPluginRunner->iInitialized); bool isStarted = iServerPluginRunner->start(); QVERIFY(isStarted); /* test abort() */ // test plugin pointer for nullptr QVERIFY(iServerPluginRunner->plugin()); iServerPluginRunner->abort(); } void ServerPluginRunnerTest::testSyncResults() { // test plugin pointer for nullptr QVERIFY(iServerPluginRunner->plugin()); // we can't set values from ServerPluginRunner. Hence, comparing the default return value QCOMPARE(iServerPluginRunner->syncResults().majorCode(), SyncResults::SYNC_RESULT_SUCCESS); } void ServerPluginRunnerTest::testSignals() { QSignalSpy sessionSpy(iServerPluginRunner, SIGNAL(newSession(QString))); QSignalSpy errorSpy(iServerPluginRunner, SIGNAL(error(QString, QString, SyncResults::MinorCode))); QSignalSpy successSpy(iServerPluginRunner, SIGNAL(success(QString, QString))); QSignalSpy doneSpy(iServerPluginRunner, SIGNAL(done())); // registering metatypes that are not known qRegisterMetaType("Sync::TransferDatabase"); qRegisterMetaType("Sync::TransferType"); QSignalSpy transferSpy(iServerPluginRunner, SIGNAL(transferProgress(QString, Sync::TransferDatabase, Sync::TransferType, QString, int))); QVERIFY(iServerPluginRunner->iServerActivator); iServerPluginRunner->onNewSession("toDevice"); QCOMPARE(sessionSpy.count(), 1); iServerPluginRunner->onTransferProgress("profile", Sync::LOCAL_DATABASE, Sync::ITEM_ADDED, "text", 1); QCOMPARE(transferSpy.count(), 1); iServerPluginRunner->onError("profile", "message", SyncResults::PLUGIN_ERROR); QCOMPARE(errorSpy.count(), 1); iServerPluginRunner->onSuccess("profile", "message"); QCOMPARE(successSpy.count(), 1); iServerPluginRunner->onThreadExit(); QCOMPARE(doneSpy.count(), 1); } QTEST_MAIN(Buteo::ServerPluginRunnerTest) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/ServerPluginRunnerTest/ServerPluginRunnerTest.h000066400000000000000000000030751477124122200334010ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SERVERPLUGINRUNNERTEST_H #define SERVERPLUGINRUNNERTEST_H #include "ServerPluginRunner.h" #include "TransportTracker.h" #include "Profile.h" #include "ProfileManager.h" #include "PluginManager.h" #include "ServerActivator.h" namespace Buteo { class ServerPluginRunnerTest : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testStartAbort(); void testSyncResults(); void testSignals(); private: ServerPluginRunner *iServerPluginRunner; TransportTracker *iTransportTracker; ProfileManager *iProfileManager; ServerActivator *iServerActivator; PluginManager *iPluginManager; Profile *iProfile; }; } #endif // SERVERPLUGINRUNNERTEST_H buteo-syncfw-0.11.10/unittests/tests/msyncdtests/ServerPluginRunnerTest/ServerPluginRunnerTest.pro000066400000000000000000000000461477124122200337450ustar00rootroot00000000000000include(../msyncdtestapplication.pri) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/ServerThreadTest/000077500000000000000000000000001477124122200253235ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/msyncdtests/ServerThreadTest/ServerThreadTest.cpp000066400000000000000000000073701477124122200312740ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "ServerThreadTest.h" #include "PluginManager.h" using namespace Buteo; const QString PGNAME = "Plugin"; const QString PFNAME = "Profile"; QString TYPE = Profile::TYPE_SERVER; /* Defining the pure virtual functions of the base classes */ bool ServerPluginDerived::startListen() { return true; } void ServerPluginDerived::stopListen() { } void ServerPluginDerived::suspend() { } void ServerPluginDerived::resume() { } bool ServerPluginDerived::cleanUp() { return true; } bool ServerPluginDerived::init() { if (iTestSignal == true) { return false; } return true; } bool ServerPluginDerived::uninit() { return true; } void ServerPluginDerived::connectivityStateChanged(Sync::ConnectivityType, bool) { } /* Constructor of the serverPluginDerived Class */ ServerPluginDerived::ServerPluginDerived(const QString &aPluginName, const Profile &aProfile, PluginCbInterface *aCbInterface) : ServerPlugin(aPluginName, aProfile, aCbInterface), iTestSignal(false) { } void ServerThreadTest::initTestCase() { iThreadreturn = false; iProfile = new Profile(PFNAME, TYPE); iServerThread = new ServerThread(); iPluginDerived = new ServerPluginDerived(PGNAME, *iProfile, nullptr); iThreadTestSp = iPluginDerived; } void ServerThreadTest::cleanupTestCase() { QVERIFY(iProfile != 0); delete iProfile; QVERIFY(iPluginDerived != 0); delete iPluginDerived; QVERIFY(iServerThread != 0); delete iServerThread; iProfile = 0; iPluginDerived = 0; iServerThread = 0; iThreadTestSp = 0; } void ServerThreadTest::testServerThreadConstructor() { QVERIFY(iServerThread->getPlugin() == nullptr); QCOMPARE(iServerThread->iRunning, false); } void ServerThreadTest::testGetPlugin() { iThreadreturn = iServerThread->startThread(iThreadTestSp); QTest::qWait(20); QCOMPARE(iThreadreturn, true); QCOMPARE(iServerThread->getPlugin(), iThreadTestSp); } void ServerThreadTest::testGetProfileName() { QCOMPARE(iThreadreturn, true); QCOMPARE(iServerThread->getProfileName(), PFNAME); } void ServerThreadTest::testThread() { //The Thread is already started in testGetPlugin() QCOMPARE(iServerThread->startThread(iThreadTestSp), false); QCOMPARE(iServerThread->iRunning, true); QCOMPARE(iServerThread->getPlugin(), iThreadTestSp); } void ServerThreadTest::testStopThErrorSignal() { QVERIFY(iServerThread != 0); iServerThread->stopThread(); iServerThread->wait(9000); iPluginDerived->iTestSignal = true; //Test for the signal QSignalSpy spy(iServerThread, SIGNAL(initError(const QString &, const QString &, SyncResults::MinorCode))); QCOMPARE(iServerThread->startThread(iThreadTestSp), true); QTest::qWait(20); QCOMPARE(spy.count(), 1); } QTEST_MAIN(Buteo::ServerThreadTest) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/ServerThreadTest/ServerThreadTest.h000066400000000000000000000040621477124122200307340ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SERVERTHREADTEST_H_ #define SERVERTHREADTEST_H_ #include #include #include #include #include "ServerThread.h" #include "ServerPlugin.h" #include "SyncCommonDefs.h" #include "Profile.h" namespace Buteo { class ServerPluginDerived: public ServerPlugin { Q_OBJECT public: ServerPluginDerived(const QString &aPluginName, const Profile &aProfile, PluginCbInterface *aCbInterface); bool startListen(); void stopListen(); bool init(); bool uninit(); void suspend(); void resume(); virtual bool cleanUp(); bool iTestSignal; public slots: void connectivityStateChanged( Sync::ConnectivityType, bool); }; class ServerThreadTest: public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testServerThreadConstructor(); void testGetPlugin(); void testGetProfileName(); void testThread(); void testStopThErrorSignal(); private: ServerThread *iServerThread; ServerPluginDerived *iPluginDerived; ServerPlugin *iThreadTestSp; const Profile *iProfile; bool iThreadreturn; }; } #endif /*SERVERTHREADTEST_H_*/ buteo-syncfw-0.11.10/unittests/tests/msyncdtests/ServerThreadTest/ServerThreadTest.pro000066400000000000000000000000461477124122200313030ustar00rootroot00000000000000include(../msyncdtestapplication.pri) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/StorageBookerTest/000077500000000000000000000000001477124122200254735ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/msyncdtests/StorageBookerTest/StorageBookerTest.cpp000066400000000000000000000102721477124122200316070ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "StorageBookerTest.h" #include "StorageBooker.h" using namespace Buteo; void StorageBookerTest::testBooking() { const QString STORAGE1 = "Storage1"; const QString STORAGE2 = "Storage2"; const QString CLIENT1 = "Client1"; const QString CLIENT2 = "Client2"; const QString EMPTY_CLIENT = ""; StorageBooker booker; QStringList allStorages; allStorages << STORAGE1 << STORAGE2; // No reservations yet, storages are available. QCOMPARE(booker.isStorageAvailable(STORAGE1, CLIENT1), true); QCOMPARE(booker.isStorageAvailable(STORAGE1, EMPTY_CLIENT), true); QCOMPARE(booker.storagesAvailable(allStorages, CLIENT2), true); // Reserve storage1 for client1. Other clients are not able to reserve. QCOMPARE(booker.reserveStorage(STORAGE1, CLIENT1), true); QCOMPARE(booker.reserveStorage(STORAGE1, CLIENT2), false); QCOMPARE(booker.reserveStorage(STORAGE1, EMPTY_CLIENT), false); QCOMPARE(booker.isStorageAvailable(STORAGE1, CLIENT1), true); QCOMPARE(booker.isStorageAvailable(STORAGE1, CLIENT2), false); QCOMPARE(booker.storagesAvailable(allStorages, CLIENT1), true); QCOMPARE(booker.storagesAvailable(allStorages, CLIENT2), false); // Reserve more again, release gives correct remaingin ref count. QCOMPARE(booker.reserveStorage(STORAGE1, CLIENT1), true); QCOMPARE(booker.releaseStorage(STORAGE1), (unsigned)1); QCOMPARE(booker.reserveStorage(STORAGE1, CLIENT2), false); QCOMPARE(booker.releaseStorage(STORAGE1), (unsigned)0); // Another client is able to reserve after all references are released. QCOMPARE(booker.reserveStorage(STORAGE1, CLIENT2), true); QCOMPARE(booker.releaseStorage(STORAGE1), (unsigned)0); // Reserve multiple storages at a time. QCOMPARE(booker.reserveStorages(allStorages, CLIENT2), true); QCOMPARE(booker.reserveStorages(allStorages, CLIENT1), false); QCOMPARE(booker.storagesAvailable(allStorages, CLIENT2), true); QCOMPARE(booker.storagesAvailable(allStorages, CLIENT1), false); booker.releaseStorages(allStorages); QCOMPARE(booker.storagesAvailable(allStorages, CLIENT1), true); QCOMPARE(booker.reserveStorages(allStorages, EMPTY_CLIENT), true); QCOMPARE(booker.storagesAvailable(allStorages, CLIENT1), false); QCOMPARE(booker.storagesAvailable(allStorages, CLIENT2), false); QCOMPARE(booker.storagesAvailable(allStorages, EMPTY_CLIENT), false); QCOMPARE(booker.releaseStorage(STORAGE1), (unsigned)0); QCOMPARE(booker.storagesAvailable(allStorages, CLIENT1), false); QCOMPARE(booker.releaseStorage(STORAGE2), (unsigned)0); QCOMPARE(booker.storagesAvailable(allStorages, CLIENT1), true); // Reserve all, reserve individual, release individual, release all. QCOMPARE(booker.reserveStorages(allStorages, CLIENT1), true); QCOMPARE(booker.reserveStorage(STORAGE1, CLIENT1), true); QCOMPARE(booker.reserveStorage(STORAGE2, CLIENT1), true); QCOMPARE(booker.reserveStorage(STORAGE1, CLIENT2), false); QCOMPARE(booker.reserveStorage(STORAGE2, CLIENT2), false); QCOMPARE(booker.releaseStorage(STORAGE1), (unsigned)1); QCOMPARE(booker.releaseStorage(STORAGE2), (unsigned)1); QCOMPARE(booker.storagesAvailable(allStorages, CLIENT2), false); booker.releaseStorages(allStorages); QCOMPARE(booker.storagesAvailable(allStorages, CLIENT2), true); } QTEST_MAIN(Buteo::StorageBookerTest) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/StorageBookerTest/StorageBookerTest.h000066400000000000000000000021061477124122200312510ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef STORAGEBOOKERTEST_H #define STORAGEBOOKERTEST_H #include namespace Buteo { class StorageBookerTest: public QObject { Q_OBJECT private slots: void testBooking(); }; } #endif // STORAGEBOOKERTEST_H buteo-syncfw-0.11.10/unittests/tests/msyncdtests/StorageBookerTest/StorageBookerTest.pro000066400000000000000000000000461477124122200316230ustar00rootroot00000000000000include(../msyncdtestapplication.pri) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/SyncBackupTest/000077500000000000000000000000001477124122200247675ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/msyncdtests/SyncBackupTest/SyncBackupTest.cpp000066400000000000000000000042201477124122200303730ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "SyncBackupTest.h" #include using namespace Buteo; void SyncBackupTest::initTestCase() { iBackup = new SyncBackup(); } void SyncBackupTest::cleanupTestCase() { QVERIFY(iBackup != 0); delete iBackup; iBackup = 0; } void SyncBackupTest::testInitialize() { QVERIFY(iBackup->iReply == 0); QVERIFY(iBackup->iBackupRestore == 0); QVERIFY(iBackup->iAdaptor != 0); } void SyncBackupTest::testBackup() { QDBusMessage msg; QSignalSpy sigStatus1(iBackup, SIGNAL(startBackup())); QCOMPARE(iBackup->backupStarts(msg), uchar(0)); QCOMPARE(sigStatus1.count(), 1); QCOMPARE(iBackup->getBackUpRestoreState(), true); QSignalSpy sigStatus2(iBackup, SIGNAL(backupDone())); QCOMPARE(iBackup->backupFinished(msg), uchar(0)); QCOMPARE(sigStatus2.count(), 1); QCOMPARE(iBackup->getBackUpRestoreState(), false); QSignalSpy sigStatus3(iBackup, SIGNAL(startRestore())); QCOMPARE(iBackup->restoreStarts(msg), uchar(0)); QCOMPARE(sigStatus3.count(), 1); QCOMPARE(iBackup->getBackUpRestoreState(), true); QSignalSpy sigStatus4(iBackup, SIGNAL(restoreDone())); QCOMPARE(iBackup->restoreFinished(msg), uchar(0)); QCOMPARE(sigStatus4.count(), 1); QCOMPARE(iBackup->getBackUpRestoreState(), false); } QTEST_MAIN(Buteo::SyncBackupTest) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/SyncBackupTest/SyncBackupTest.h000066400000000000000000000023601477124122200300430ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCBACKUPTEST_H_ #define SYNCBACKUPTEST_H_ #include #include #include #include "SyncBackup.h" namespace Buteo { class SyncBackupTest: public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testInitialize(); void testBackup(); private: SyncBackup *iBackup; }; } #endif /*SYNCBACKUPTEST_H_*/ buteo-syncfw-0.11.10/unittests/tests/msyncdtests/SyncBackupTest/SyncBackupTest.pro000066400000000000000000000000461477124122200304130ustar00rootroot00000000000000include(../msyncdtestapplication.pri) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/SyncQueueTest/000077500000000000000000000000001477124122200246465ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/msyncdtests/SyncQueueTest/SyncQueueTest.cpp000066400000000000000000000036401477124122200301360ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "SyncQueueTest.h" #include "SyncQueue.h" #include "SyncSession.h" #include using namespace Buteo; void SyncQueueTest::testQueue() { const QString NAME1 = "Name1"; const QString NAME2 = "Name2"; SyncSession s1(new SyncProfile(NAME1)); SyncSession s2(new SyncProfile(NAME2)); SyncQueue q; // Empty queue. QCOMPARE(q.isEmpty(), true); QVERIFY(q.head() == nullptr); QVERIFY(q.dequeue() == nullptr); QCOMPARE(q.contains(NAME1), false); // Add items. q.enqueue(&s1); q.enqueue(&s2); QCOMPARE(q.isEmpty(), false); QCOMPARE(q.head(), &s1); QCOMPARE(q.contains(NAME1), true); QCOMPARE(q.contains(NAME2), true); QCOMPARE(q.dequeue(), &s1); QCOMPARE(q.contains(NAME1), false); QCOMPARE(q.contains(NAME2), true); QCOMPARE(q.isEmpty(), false); q.enqueue(&s1); QCOMPARE(q.head(), &s2); QCOMPARE(q.dequeue(), &s2); QCOMPARE(q.head(), &s1); QCOMPARE(q.dequeue(), &s1); QCOMPARE(q.isEmpty(), true); QVERIFY(q.dequeue() == nullptr); } QTEST_MAIN(Buteo::SyncQueueTest) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/SyncQueueTest/SyncQueueTest.h000066400000000000000000000020641477124122200276020ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCQUEUETEST_H #define SYNCQUEUETEST_H #include namespace Buteo { class SyncQueueTest: public QObject { Q_OBJECT private slots: void testQueue(); }; } #endif // SYNCQUEUETEST_H buteo-syncfw-0.11.10/unittests/tests/msyncdtests/SyncQueueTest/SyncQueueTest.pro000066400000000000000000000000461477124122200301510ustar00rootroot00000000000000include(../msyncdtestapplication.pri) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/SyncSchedulerTest/000077500000000000000000000000001477124122200255005ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/msyncdtests/SyncSchedulerTest/SyncSchedulerTest.cpp000066400000000000000000000045701477124122200316250ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "SyncSchedulerTest.h" #include "SyncScheduler.h" #include "SyncProfile.h" #include using namespace Buteo; void SyncSchedulerTest::init() { iSyncScheduler = new SyncScheduler(); iSyncProfileName.clear(); } void SyncSchedulerTest::cleanup() { if (iSyncScheduler) { delete iSyncScheduler; iSyncScheduler = nullptr; } } void SyncSchedulerTest::syncTriggered(QString aProfileName) { iSyncProfileName = aProfileName; } void SyncSchedulerTest::testAddRemoveProfile() { SyncProfileStub profile("foo"); profile.setEnabled(true); profile.setSyncType(SyncProfile::SYNC_SCHEDULED); bool profileAdded = iSyncScheduler->addProfile(&profile); QVERIFY(profileAdded); QVERIFY(iSyncScheduler->iSyncScheduleProfiles.contains(profile.name())); SyncProfileStub invalidProfile("bar"); profile.setEnabled(false); profileAdded = iSyncScheduler->addProfile(&invalidProfile); QVERIFY(!profileAdded); iSyncScheduler->removeProfile("foo"); QVERIFY(iSyncScheduler->iSyncScheduleProfiles.isEmpty()); } void SyncSchedulerTest::testSetNextAlarm() { QString profileName = "foo"; SyncProfileStub profile(profileName); profile.setEnabled(true); profile.setSyncType(SyncProfile::SYNC_SCHEDULED); //int sanity = alarm_event_is_sane(iSyncScheduler->iAlarmEventParameters); //QVERIFY(sanity == 1); int alarmId = iSyncScheduler->setNextAlarm(&profile); QVERIFY(alarmId > 0); iSyncScheduler->removeAlarmEvent(alarmId); } QTEST_MAIN(Buteo::SyncSchedulerTest) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/SyncSchedulerTest/SyncSchedulerTest.h000066400000000000000000000032731477124122200312710ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCSCHEDULERTEST_H_ #define SYNCSCHEDULERTEST_H_ #include #include #include "SyncProfile.h" namespace Buteo { class SyncScheduler; class SyncSchedulerTest: public QObject { Q_OBJECT private slots: void init(); void cleanup(); void syncTriggered(QString aProfileName); void testAddRemoveProfile(); void testSetNextAlarm(); private: SyncScheduler *iSyncScheduler; QString iSyncProfileName; }; class SyncProfileStub : public SyncProfile { public: SyncProfileStub(const QString &aName) : SyncProfile(aName) {} ~SyncProfileStub() {} QDateTime nextSyncTime() const { const int HOUR = 3600; int syncTime_t = QDateTime::currentDateTime().toTime_t() + HOUR; return QDateTime::fromTime_t(syncTime_t); } }; } #endif // SYNCSCHEDULERTEST_H_ buteo-syncfw-0.11.10/unittests/tests/msyncdtests/SyncSchedulerTest/SyncSchedulerTest.pro000066400000000000000000000000461477124122200316350ustar00rootroot00000000000000include(../msyncdtestapplication.pri) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/SyncSessionTest/000077500000000000000000000000001477124122200252055ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/msyncdtests/SyncSessionTest/SyncSessionTest.cpp000066400000000000000000000274021477124122200310360ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "SyncSessionTest.h" #include #include #include #include "SyncSession.h" #include "PluginManager.h" #include "PluginCbInterface.h" #include "SyncResults.h" #include using namespace Buteo; bool SyncSessionTest :: isValuePassedTrue; int SyncSessionPluginRunnerTest :: testValue; void SyncSessionTest :: init() { iNullPluginRunner = 0; // initialize SyncSession iSyncProfile = new SyncProfile("foo"); iSyncProfile->setEnabled(true); iSyncProfile->setSyncType(SyncProfile::SYNC_SCHEDULED); // TODO: need to include profile creation using xml // SyncSession pointer without iPluginRunner initialization iSyncSession = new SyncSession(iSyncProfile); // check whether the given profile is assigned to SyncSession->iProfile or not QCOMPARE(iSyncSession->profile(), iSyncProfile); // initialize iPluginRunner PluginManager samplePluginManager; iSyncSessionPluginRunnerTest = new SyncSessionPluginRunnerTest("testPlugin", &samplePluginManager, 0 ); } void SyncSessionTest :: cleanup() { // release the allocated memory // profile name will be deallocated in SyncSession. So, not deallocating delete iSyncSession ; iSyncSession = nullptr; delete iSyncSessionPluginRunnerTest; } void SyncSessionTest :: testPluginRunner() { /* testing setPluginRunner() & pluginRunner() */ // set iPluginRunner to a value and check whether the same exists in iPluginRunner /* by passing a valid PluginRunner pointer to SyncSession, the class should be able to * register with the signals successfully. If it not a valid PluginRunner pointer, * if should throw warnings */ iSyncSession->setPluginRunner(iSyncSessionPluginRunnerTest, true); QCOMPARE(iSyncSession->iPluginRunnerOwned, true); QCOMPARE(iSyncSession->pluginRunner(), iSyncSessionPluginRunnerTest.data()); // Check whether the same value is set to iPluginRunner or not QCOMPARE(iSyncSession->iPluginRunner, iSyncSession->pluginRunner()); iSyncSession->onDestroyed(iSyncSessionPluginRunnerTest); QCOMPARE(iSyncSession->pluginRunner(), iNullPluginRunner); /* set nullptr to iPluginRunner and test */ iSyncSession->setPluginRunner(iNullPluginRunner, false); QCOMPARE(iSyncSession->iPluginRunnerOwned, false); QCOMPARE(iSyncSession->iPluginRunner, iNullPluginRunner); // Check whether the same value is set to iPluginRunner or not QCOMPARE(iSyncSession->iPluginRunner, iSyncSession->pluginRunner()); iSyncSession->onDestroyed(iSyncSessionPluginRunnerTest); QCOMPARE(iSyncSession->pluginRunner(), iNullPluginRunner); } void SyncSessionTest :: testProfile() { /* testing profile() and profileName() */ const QString PROFILENAME = "Profile"; iSyncSession->iProfile->setName(PROFILENAME); SyncProfile *sampleProfile = iSyncSession->profile(); QCOMPARE(PROFILENAME, sampleProfile->name()); QString profileName = iSyncSession->profileName(); QVERIFY(profileName != 0); QCOMPARE(iSyncSession->profileName(), iSyncSession->iProfile->name()); } void SyncSessionTest :: testStartAbortStop() { bool isStarted; // testing with iNullPluginRunner iSyncSession->setPluginRunner(iNullPluginRunner, true); QCOMPARE(iNullPluginRunner, iSyncSession->pluginRunner()); /* testing start() */ isStarted = iSyncSession->start(); QCOMPARE(isStarted, false); /* testing abort() */ iSyncSession->abort(); QVERIFY(!iSyncSession->iAborted); // stop() neither updates nor calls other functions for iPluginRunner nullptr value. so, omitting this function for nullptr value // testing with iSyncSessionPluginRunnerTest iSyncSession->setPluginRunner(iSyncSessionPluginRunnerTest, true); QCOMPARE(iSyncSessionPluginRunnerTest.data(), iSyncSession->iPluginRunner); /* testing start() */ // test start() with both possible values isValuePassedTrue = false; isStarted = iSyncSession->start(); QCOMPARE(isStarted, isValuePassedTrue); isValuePassedTrue = true; isStarted = iSyncSession->start(); QCOMPARE(isStarted, isValuePassedTrue); /* testing abort() */ iSyncSession->abort(); QCOMPARE(SyncSessionPluginRunnerTest::testValue, 2); iSyncSession->stop(); QCOMPARE(SyncSessionPluginRunnerTest::testValue, 3); } void SyncSessionTest :: testResults() { SyncResults results1; results1.setMajorCode(Buteo::SyncResults::SYNC_RESULT_CANCELLED); iSyncSession->updateResults(results1); SyncResults results2 = iSyncSession->results(); QCOMPARE(results1.majorCode(), results2.majorCode()); // testing setFailureResult() iSyncSession->setFailureResult(Buteo::SyncResults::SYNC_RESULT_FAILED, Buteo::SyncResults::INTERNAL_ERROR); SyncResults givenResult = iSyncSession->results(); SyncResults receivedtResult = iSyncSession->results(); QCOMPARE(givenResult.majorCode(), receivedtResult.majorCode()); } void SyncSessionTest :: testScheduled() { /* testing both setScheduled() & isScheduled() */ bool givenScheduledData = false; iSyncSession->setScheduled(givenScheduledData); QCOMPARE(iSyncSession->isScheduled(), givenScheduledData); givenScheduledData = true; iSyncSession->setScheduled(givenScheduledData); QCOMPARE(iSyncSession->isScheduled(), givenScheduledData); } void SyncSessionTest :: testStorages() { // TODO: need to check before and after calling reserveStorage() // testing reserveStorages() StorageBooker *myTestStorageBooker = new StorageBooker ; QString oneStorage; foreach (QString storage, iSyncProfile->storageBackendNames()) { // take one name of storage to check the storage oneStorage = storage; break; } QVERIFY(myTestStorageBooker->isStorageAvailable(oneStorage, iSyncProfile->name())); bool isSuccess = iSyncSession->reserveStorages(myTestStorageBooker); QCOMPARE(iSyncSession->iStorageBooker, myTestStorageBooker); QVERIFY(isSuccess); QVERIFY(myTestStorageBooker->isStorageAvailable(oneStorage, iSyncProfile->name())); //testing releaseStorages() // releaseStorages() can be verified iSyncSession->releaseStorages(); QVERIFY(myTestStorageBooker->isStorageAvailable(oneStorage, iSyncProfile->name())); if (myTestStorageBooker) { delete myTestStorageBooker; myTestStorageBooker = nullptr; } } void SyncSessionTest :: testOnSuccess() { const QString PROFILE = "sampleProfile"; const QString MESSAGE = "testMessage"; iSyncSession->setPluginRunner(iSyncSessionPluginRunnerTest, true); iSyncSession->onSuccess(PROFILE, MESSAGE); QCOMPARE(iSyncSession->iMessage, MESSAGE); QVERIFY(iSyncSession->iFinished); // testing iStatus when onSuccess() is called iSyncSession->iAborted = false; iSyncSession->onSuccess(PROFILE, MESSAGE); QCOMPARE(iSyncSession->iStatus, Sync::SYNC_DONE); iSyncSession->iAborted = true; iSyncSession->onSuccess(PROFILE, MESSAGE); QCOMPARE(iSyncSession->iStatus, Sync::SYNC_ABORTED); isValuePassedTrue = iSyncSession->isScheduled(); QCOMPARE(iSyncSession->iPluginRunner->syncResults().majorCode(), iSyncSession->iResults.majorCode()); QCOMPARE(iSyncSession->isScheduled(), iSyncSession->iResults.isScheduled()); // finished() signal is checked in testOnDone() } void SyncSessionTest :: testOnError() { const QString MESSAGE = "testMessage"; SyncResults::MinorCode errorCode = SyncResults::PLUGIN_ERROR; iSyncSession->setPluginRunner(iSyncSessionPluginRunnerTest, true); iSyncSession->onError("sampleProfile", MESSAGE, errorCode); QCOMPARE(iSyncSession->iMessage, MESSAGE); QCOMPARE(iSyncSession->iErrorCode, errorCode); QCOMPARE(iSyncSession->iStatus, Sync::SYNC_ERROR); QVERIFY(iSyncSession->iFinished); isValuePassedTrue = iSyncSession->iScheduled; QCOMPARE(iSyncSession->iPluginRunner->syncResults().majorCode(), iSyncSession->iResults.majorCode()); QCOMPARE(iSyncSession->results().isScheduled(), iSyncSession->iResults.isScheduled()); } void SyncSessionTest :: testOnTransferProgress() { // registering metatypes that are not known qRegisterMetaType("Sync::TransferDatabase"); qRegisterMetaType("Sync::TransferType"); QSignalSpy sampleSpy(iSyncSession, SIGNAL(transferProgress(QString, Sync::TransferDatabase, Sync::TransferType, QString, int))); QVERIFY(iSyncSessionPluginRunnerTest); // call functions that emit transferProgress() signal iSyncSession->onTransferProgress("profile", Sync::LOCAL_DATABASE, Sync::ITEM_ADDED, "text", 1); QCOMPARE(sampleSpy.count(), 1); } void SyncSessionTest :: testOnDone() { // registering unknown metatype qRegisterMetaType("Sync::SyncStatus"); QSignalSpy sampleSpy(iSyncSession, SIGNAL(finished(QString, Sync::SyncStatus, QString, SyncResults::MinorCode))); // call functions that emit finished() signal iSyncSession->onDone(); QCOMPARE(sampleSpy.count(), 1); iSyncSession->onSuccess("testProfile", "testMessage"); QCOMPARE(sampleSpy.count(), 2); iSyncSession->onError("testProfile", "testMessage", SyncResults::PLUGIN_ERROR); QCOMPARE(sampleSpy.count(), 3); } // ############################################ /* * Starting SyncSessionPluginRunnerTest class */ // ############################################ SyncSessionPluginRunnerTest :: SyncSessionPluginRunnerTest(const QString &aPluginName, PluginManager *aPluginMgr, PluginCbInterface *aPluginCbIf) : PluginRunner(PLUGIN_CLIENT, aPluginName, aPluginMgr, aPluginCbIf) { } bool SyncSessionPluginRunnerTest :: init() { /* Checking whether control is coming to PluginRuneer derived class or not */ if (SyncSessionTest::isValuePassedTrue ) return true; else return false; } bool SyncSessionPluginRunnerTest :: start() { /* Checking whether control is coming to PluginRuneer derived class or not */ if (SyncSessionTest::isValuePassedTrue ) return true; else return false; } void SyncSessionPluginRunnerTest ::stop () { // check the value after returning to the calling function testValue = 3; } void SyncSessionPluginRunnerTest :: abort(Sync::SyncStatus /*aStatus*/) { // check the value after returning to the calling function testValue = 2; } SyncResults SyncSessionPluginRunnerTest :: syncResults() { SyncResults results; results.setScheduled(SyncSessionTest::isValuePassedTrue); return results; } SyncPluginBase *SyncSessionPluginRunnerTest :: plugin() { // This is not being used by SyncSession. returning nullptr to supress compile warning return (SyncPluginBase *)0; } bool SyncSessionPluginRunnerTest :: cleanUp() { // check the value after returning to the calling function return true; } QTEST_MAIN(Buteo::SyncSessionTest) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/SyncSessionTest/SyncSessionTest.h000066400000000000000000000044371477124122200305060ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCSESSIONTEST_H #define SYNCSESSIONTEST_H #include #include "SyncResults.h" #include "StorageBooker.h" #include "SyncProfile.h" #include "PluginRunner.h" namespace Buteo { class SyncSession; class SyncSessionPluginRunnerTest; class SyncSessionTest : public QObject { Q_OBJECT private slots: void init(); void cleanup(); void testPluginRunner(); void testStartAbortStop(); void testProfile(); void testScheduled(); void testResults(); void testStorages(); void testOnSuccess(); void testOnError(); void testOnTransferProgress(); void testOnDone(); private: SyncSession *iSyncSession; SyncProfile *iSyncProfile; PluginRunner *iNullPluginRunner; QPointer iSyncSessionPluginRunnerTest; public: static bool isValuePassedTrue; }; class SyncSessionPluginRunnerTest : public PluginRunner { Q_OBJECT public slots: bool init(); bool start(); void stop(); void abort(Sync::SyncStatus aStatus = Sync::SYNC_ABORTED); bool cleanUp(); SyncResults syncResults(); SyncPluginBase *plugin(); public: SyncSessionPluginRunnerTest(const QString &aPluginName, PluginManager *aPluginMgr, PluginCbInterface *aPluginCbIf); private: PluginRunner *iPluginRunner; public: static int testValue; // to cross-check the value while calling stop() / abort() }; } #endif // SYNCSESSIONTEST_H buteo-syncfw-0.11.10/unittests/tests/msyncdtests/SyncSessionTest/SyncSessionTest.pro000066400000000000000000000000461477124122200310470ustar00rootroot00000000000000include(../msyncdtestapplication.pri) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/SyncSigHandlerTest/000077500000000000000000000000001477124122200256025ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/msyncdtests/SyncSigHandlerTest/SyncSigHandlerTest.cpp000066400000000000000000000025431477124122200320270ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "SyncSigHandlerTest.h" #include #include using namespace Buteo; void SyncSigHandlerTest :: init() { // initiate sighandler iSigHandler = new SyncSigHandler(); } void SyncSigHandlerTest :: cleanup() { delete iSigHandler ; iSigHandler = nullptr; } void SyncSigHandlerTest :: testSigTerm() { //kill("TERM", 'pidof msyncd'); qDebug() << "Check there should not be any core dump in /home/user/Mydocs/core-dumps"; } QTEST_MAIN(Buteo::SyncSigHandlerTest) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/SyncSigHandlerTest/SyncSigHandlerTest.h000066400000000000000000000022671477124122200314770ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCSIGHANDLERTEST_H #define SYNCSIGHANDLERTEST_H #include "SyncSigHandler.h" #include "LogMacros.h" namespace Buteo { class SyncSigHandlerTest : public QObject { Q_OBJECT private slots: void init(); void cleanup(); void testSigTerm(); private: SyncSigHandler *iSigHandler; }; } #endif // SYNCSESSIONTEST_H buteo-syncfw-0.11.10/unittests/tests/msyncdtests/SyncSigHandlerTest/SyncSigHandlerTest.pro000066400000000000000000000000461477124122200320410ustar00rootroot00000000000000include(../msyncdtestapplication.pri) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/SynchronizerTest/000077500000000000000000000000001477124122200254225ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/msyncdtests/SynchronizerTest/SynchronizerTest.cpp000066400000000000000000000222461477124122200314710ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "SynchronizerTest.h" #include "TransportTracker.h" #include "ServerActivator.h" #include "ServerPluginRunner.h" using namespace Buteo; void SynchronizerTest::initTestCase() { iSync = new Synchronizer(nullptr); iProfile = new SyncProfile("Profile"); iProfileQ = new SyncProfile("Profile1"); iSyncSession = new SyncSession(iProfile, nullptr); iSyncSessionQ = new SyncSession(iProfileQ, nullptr); } void SynchronizerTest::cleanupTestCase() { iSync->close(); QVERIFY(iSyncSession != 0); delete iSyncSession; QVERIFY(iSyncSessionQ != 0); delete iSyncSessionQ; QVERIFY(iSync != 0); iSync->close(); delete iSync; iSync = 0; iSyncSession = 0; iSyncSessionQ = 0; } void SynchronizerTest::testSyncConstructor() { QVERIFY(iSync->iSyncScheduler == 0); QVERIFY(iSync->iTransportTracker == 0); QVERIFY(iSync->iServerActivator == 0); QVERIFY(iSync->iAccounts == 0); QVERIFY(iSync->iSyncBackup == 0); QCOMPARE(iSync->iClosing, false); } void SynchronizerTest::testInitialize() { QCOMPARE(iSync->isConnectivityAvailable(Sync::CONNECTIVITY_USB), false); QCOMPARE(iSync->initialize(), true); QVERIFY(iSync->iSyncScheduler != 0); QVERIFY(iSync->iTransportTracker != 0); QVERIFY(iSync->iServerActivator != 0); QVERIFY(iSync->iAccounts != 0); QVERIFY(iSync->iSyncBackup != 0); } void SynchronizerTest::testSync() { QEXPECT_FAIL("", "This test case is so broken so that I do not find it worth to fix", Abort); // For some notes see comments starting with XXX QVERIFY(false); QString name; QStringList alist; alist << "storage"; SyncSession *aSync = 0; QVERIFY(aSync == 0); QVERIFY(iSync->iSyncBackup != 0); QCOMPARE(iSync->startSyncNow(aSync), false); QVERIFY(iSyncSession != 0); QCOMPARE(iSync->startSyncNow(iSyncSession), false); QCOMPARE(iSync->startSync("Profile"), false); // XXX broken since 7678242 (Check network connectivity before attempting online sync) QCOMPARE(iSync->startScheduledSync("Profile"), false); iSync->iActiveSessions.insert(iSyncSession->profileName(), iSyncSession); QCOMPARE(iSync->startSync("Profile"), true); QCOMPARE(iSync->startScheduledSync("Profile"), true); QCOMPARE(iSync->runningSyncs().takeFirst(), iSyncSession->profileName()); QCOMPARE(iSync->startSync("Profile1"), false); QCOMPARE(iSync->startScheduledSync("Profile1"), false); iSync->iSyncQueue.enqueue(iSyncSessionQ); QSignalSpy sigStatus(iSync, SIGNAL(syncStatus(QString, int, QString, int))); QCOMPARE(iSync->startSync("Profile1"), true); QCOMPARE(sigStatus.count(), 1); QSignalSpy sigStatus1(iSync, SIGNAL(syncStatus(QString, int, QString, int))); QCOMPARE(iSync->startScheduledSync("Profile1"), true); QTRY_COMPARE(sigStatus1.count(), 1); QCOMPARE(iSync->startSync("S40"), false); QCOMPARE(iSync->requestStorages(alist), true); //test startNextSync() QCOMPARE(iSync->iSyncQueue.isEmpty(), false); QVERIFY(iSync->iSyncQueue.head() != 0); // XXX this->iSyncSession->deleteLater() is be called upon this but iSyncSession is still accessed // later and expected to be valid at cleanupTestCase() QCOMPARE(iSync->startNextSync(), true); QCOMPARE(iSync->iSyncQueue.isEmpty(), true); QCOMPARE(iSync->startNextSync(), false); //test abortSync() iSync->iActiveSessions.insert(iSyncSession->profileName(), iSyncSession); QCOMPARE(iSync->iActiveSessions.contains("Profile"), true); iSync->abortSync("Profile"); //test createStorage(const QString) QCOMPARE(QString("Plugin").isEmpty(), false); QVERIFY(iSync->createStorage("Plugin") == nullptr); // XXX This leads to memory corruption QCOMPARE(iSync->requestStorage("Storage", reinterpret_cast(this)), true); //Test startServer(const QString &aProfileName) QVERIFY(iSync->iServerActivator != 0); // XXX Try to use QTRY_COMPARE() here to see the effects mentioned in the above XXX comments QCOMPARE(iSync->iServers.isEmpty(), true); QVERIFY(iSync->iProfileManager.profile("profile", Profile::TYPE_SERVER) == 0); iSync->startServer("profile"); QCOMPARE(iSync->iServers.isEmpty(), true); QVERIFY(iSync->iProfileManager.profile("syncml", Profile::TYPE_SERVER) != 0); iSync->startServer("syncml"); QCOMPARE(iSync->iServers.isEmpty(), false); QTest::qSleep( 100 ); // Fake that synchronizer is closing, so that server plug-in is deleted // immediately. Otherwise plug-in will get deleted only after synchronizer // destructor when plug-in manager is already destroyed. This will cause a // a crash because plug-in runner uses the plug-in manager to destroy the // plug-in. In the real application this situation does not happen, because // event loop is running and deleteLater works normally. iSync->iClosing = true; iSync->stopServer("syncml"); iSync->iClosing = false; QCOMPARE(iSync->iServers.isEmpty(), true); QCOMPARE(iSync->iServerActivator->enabledServers().isEmpty(), true); QVERIFY(iSync->iTransportTracker != 0); iSync->iTransportTracker->updateState(Sync::CONNECTIVITY_USB, true); //test startServers() QVERIFY(iSync->iServerActivator != 0); QCOMPARE(iSync->iServerActivator->enabledServers().isEmpty(), false); iSync->startServers(); QTest::qSleep( 100 ); // Fake that synchronizer is closing, so that server plug-in is deleted // immediately. iSync->iClosing = true; iSync->stopServers(); iSync->iClosing = false; //test onServerDone() //This is not called in a slot activated by the signal. sender() returns 0 QVERIFY(iSync->iProfileManager.profile("syncml", Profile::TYPE_SERVER) != 0); iSync->startServer("syncml"); QTest::qSleep( 100 ); QCOMPARE(iSync->iServers.isEmpty(), false); QCOMPARE(iSync->iServers.size(), 1); iSync->onServerDone(); QCOMPARE(iSync->iServers.size(), 1); //test onNewSession(const QString &aDestination). //This is not called in a slot activated by the signal. sender() returns 0 QCOMPARE(iSync->iActiveSessions.isEmpty(), false); QCOMPARE(iSync->iActiveSessions.size(), 1); iSync->onNewSession("USB"); QCOMPARE(iSync->iActiveSessions.size(), 1); /* ServerPluginRunner *plugin = new ServerPluginRunner("Plugin", nullptr, nullptr, nullptr, nullptr); QSignalSpy spy(plugin, SIGNAL(newSession(const QString ))); plugin->onNewSession("USB"); QCOMPARE(spy.count(), 1); QTest::qWait(20); QCOMPARE(iSync->iActiveSessions.size(), 1);*/ QVERIFY(iSync->iSyncScheduler != 0); iSync->reschedule("Profile"); //test is TransportAvailable(const SyncSession *aSession) QVERIFY(iSyncSession != 0); QVERIFY(iSync->iTransportTracker != 0); QVERIFY(iSyncSession->profile() != 0); } void SynchronizerTest::testSignals() { QStringList alist; alist << "storage"; QSignalSpy sigStorage(iSync, SIGNAL(storageReleased())); iSync->releaseStorages(alist); QCOMPARE(sigStorage.count(), 1); qRegisterMetaType("Sync::TransferDatabase"); qRegisterMetaType("Sync::TransferType"); QSignalSpy sigTransfer(iSync, SIGNAL(transferProgress(QString, int, int, QString, int))); iSync->onTransferProgress("Profile", Sync::LOCAL_DATABASE, Sync::ITEM_ADDED, "Mime", 1); QCOMPARE(sigTransfer.count(), 1); QSignalSpy sigStorage1(iSync, SIGNAL(storageReleased())); iSync->releaseStorage("Storage", nullptr); QCOMPARE(sigStorage1.count(), 1); QSignalSpy sigbackupRestoreStarts(iSync, SIGNAL(backupInProgress())); iSync->backupStarts(); QCOMPARE(sigbackupRestoreStarts.count(), 1); QSignalSpy sigbackupDone(iSync, SIGNAL(backupDone())); iSync->backupFinished(); QCOMPARE(sigbackupDone.count(), 1); QSignalSpy sigrestoreStarts(iSync, SIGNAL(restoreInProgress())); iSync->restoreStarts(); QCOMPARE(sigrestoreStarts.count(), 1); QSignalSpy sigrestoreFinished(iSync, SIGNAL(restoreDone())); iSync->restoreFinished(); QCOMPARE(sigrestoreFinished.count(), 1); QSignalSpy sessionStatus(iSync, SIGNAL(syncStatus(QString, int, QString, int))); iSync->onSessionFinished("Profile", Sync::SYNC_DONE, "Msg", SyncResults::NO_ERROR); QCOMPARE(sessionStatus.count(), 1); } QTEST_MAIN(Buteo::SynchronizerTest) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/SynchronizerTest/SynchronizerTest.h000066400000000000000000000026771477124122200311440ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCHRONIZERTEST_H_ #define SYNCHRONIZERTEST_H_ #include #include #include #include "synchronizer.h" #include "SyncSession.h" namespace Buteo { class SynchronizerTest: public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testSyncConstructor(); void testInitialize(); void testSync(); void testSignals(); private: Synchronizer *iSync; SyncSession *iSyncSession; SyncProfile *iProfile; SyncSession *iSyncSessionQ; SyncProfile *iProfileQ; }; } #endif /*SYNCHRONIZERTEST_H_*/ buteo-syncfw-0.11.10/unittests/tests/msyncdtests/SynchronizerTest/SynchronizerTest.pro000066400000000000000000000000461477124122200315010ustar00rootroot00000000000000include(../msyncdtestapplication.pri) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/TransportTrackerTest/000077500000000000000000000000001477124122200262355ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/msyncdtests/TransportTrackerTest/TransportTrackerTest.cpp000066400000000000000000000133601477124122200331140ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * 2019 Updated to use bluez5 by deloptes@gmail.com * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "TransportTrackerTest.h" #include #include #include #include using namespace Buteo; void TransportTrackerTest :: initTestCase() { // instantiating TransportTrackerTest iTransportTracker = new TransportTracker(this); /* TODO : For the following test cases, conditional compilation flag need to be added, as these * fail under scractch box */ /* // check memory allocation for HalProxy QVERIFY(iTransportTracker->iHalProxy); // check memory allocation for USB // check for valid pointer of iInternet QVERIFY(iTransportTracker->iInternet); */ } void TransportTrackerTest :: cleanupTestCase() { // deallocate the memory delete iTransportTracker; } void TransportTrackerTest :: testConnectivityAvailable() { // set the connectivity status for each type and compare it with the value returned // first set value as false iTransportTracker->updateState(Sync::CONNECTIVITY_USB, false); #ifdef HAVE_BLUEZ_5 iTransportTracker->updateState(Sync::CONNECTIVITY_BT, false); #endif iTransportTracker->updateState(Sync::CONNECTIVITY_INTERNET, false); bool usbTransportStatus = iTransportTracker->isConnectivityAvailable(Sync::CONNECTIVITY_USB); QCOMPARE(usbTransportStatus, false); #ifdef HAVE_BLUEZ_5 bool btTransportStatus = iTransportTracker->isConnectivityAvailable(Sync::CONNECTIVITY_BT); QCOMPARE(btTransportStatus, false); #endif bool internetTransportStatus = iTransportTracker->isConnectivityAvailable(Sync::CONNECTIVITY_INTERNET); QCOMPARE(internetTransportStatus, false); // next set the value true iTransportTracker->updateState(Sync::CONNECTIVITY_USB, true); #ifdef HAVE_BLUEZ_5 iTransportTracker->updateState(Sync::CONNECTIVITY_BT, true); #endif iTransportTracker->updateState(Sync::CONNECTIVITY_INTERNET, true); usbTransportStatus = iTransportTracker->isConnectivityAvailable(Sync::CONNECTIVITY_USB); QCOMPARE(usbTransportStatus, true); #ifdef HAVE_BLUEZ_5 btTransportStatus = iTransportTracker->isConnectivityAvailable(Sync::CONNECTIVITY_BT); QCOMPARE(btTransportStatus, true); #endif internetTransportStatus = iTransportTracker->isConnectivityAvailable(Sync::CONNECTIVITY_INTERNET); QCOMPARE(internetTransportStatus, true); } void TransportTrackerTest :: testStateChanged() { qRegisterMetaType("Sync::ConnectivityType"); QSignalSpy connectivityStateSpy(iTransportTracker, SIGNAL(connectivityStateChanged(Sync::ConnectivityType, bool))); QSignalSpy networkStateSpy(iTransportTracker, SIGNAL(networkStateChanged(bool, Sync::InternetConnectionType))); // change USB state and verify bool usbCurrentState = iTransportTracker->isConnectivityAvailable(Sync::CONNECTIVITY_USB); iTransportTracker->onUsbStateChanged(!usbCurrentState); QCOMPARE(iTransportTracker->isConnectivityAvailable(Sync::CONNECTIVITY_USB), !usbCurrentState); QCOMPARE(connectivityStateSpy.count(), 1); QCOMPARE(connectivityStateSpy.first().at(0).value(), Sync::CONNECTIVITY_USB); QCOMPARE(connectivityStateSpy.first().at(1).value(), !usbCurrentState); connectivityStateSpy.clear(); #ifdef HAVE_BLUEZ_5 // change BT state and verify bool btCurrentState = iTransportTracker->isConnectivityAvailable(Sync::CONNECTIVITY_BT); QVariantMap map; QStringList list; map["Powered"] = QVariant(!btCurrentState); list << "Powered"; iTransportTracker->onBtStateChanged(BT::BLUEZ_ADAPTER_INTERFACE, map, list); QCOMPARE(iTransportTracker->isConnectivityAvailable(Sync::CONNECTIVITY_BT), !btCurrentState); QCOMPARE(connectivityStateSpy.count(), 1); QCOMPARE(connectivityStateSpy.first().at(0).value(), Sync::CONNECTIVITY_BT); QCOMPARE(connectivityStateSpy.first().at(1).value(), !btCurrentState); connectivityStateSpy.clear(); #endif // change internet state and verify bool internetCurrentState = iTransportTracker->isConnectivityAvailable(Sync::CONNECTIVITY_INTERNET); iTransportTracker->onInternetStateChanged(!internetCurrentState, Sync::INTERNET_CONNECTION_UNKNOWN); QCOMPARE(iTransportTracker->isConnectivityAvailable(Sync::CONNECTIVITY_INTERNET), !internetCurrentState); QEXPECT_FAIL("", "IMO connectivityStateChanged() should be emitted also for CONNECTIVITY_INTERNET", Continue); QCOMPARE(connectivityStateSpy.count(), 1); //QCOMPARE(connectivityStateSpy.first().at(0).value(), Sync::CONNECTIVITY_INTERNET); //QCOMPARE(connectivityStateSpy.first().at(1).value(), !internetCurrentState); //connectivityStateSpy.clear(); QCOMPARE(networkStateSpy.count(), 1); QCOMPARE(networkStateSpy.first().at(0).value(), !internetCurrentState); networkStateSpy.clear(); } QTEST_MAIN(Buteo::TransportTrackerTest) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/TransportTrackerTest/TransportTrackerTest.h000066400000000000000000000023541477124122200325620ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef TRANSPORTTRACKERTEST_H #define TRANSPORTTRACKERTEST_H #include "TransportTracker.h" namespace Buteo { class TransportTrackerTest: public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testConnectivityAvailable(); void testStateChanged(); private: TransportTracker *iTransportTracker; }; } #endif // TRANSPORTTRACKERTEST_H buteo-syncfw-0.11.10/unittests/tests/msyncdtests/TransportTrackerTest/TransportTrackerTest.pro000066400000000000000000000000461477124122200331270ustar00rootroot00000000000000include(../msyncdtestapplication.pri) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/msyncdtestapplication.pri000066400000000000000000000001061477124122200272170ustar00rootroot00000000000000include(../testapplication.pri) include(../../../msyncd/unittest.pri) buteo-syncfw-0.11.10/unittests/tests/msyncdtests/msyncdtests.pro000066400000000000000000000010611477124122200251650ustar00rootroot00000000000000TEMPLATE = subdirs SUBDIRS = \ AccountsHelperTest \ ClientPluginRunnerTest \ ClientThreadTest \ PluginRunnerTest \ ServerActivatorTest \ ServerPluginRunnerTest \ ServerThreadTest \ StorageBookerTest \ SyncBackupTest \ SyncQueueTest \ SyncSessionTest \ SyncSigHandlerTest \ SynchronizerTest \ TransportTrackerTest \ !contains(DEFINES, USE_KEEPALIVE):contains(DEFINES, USE_IPHB) { SUBDIRS += \ IPHeartBeatTest \ SyncSchedulerTest \ } buteo-syncfw-0.11.10/unittests/tests/pluginmanagertests/000077500000000000000000000000001477124122200234215ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/pluginmanagertests/ClientPluginTest/000077500000000000000000000000001477124122200266565ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/pluginmanagertests/ClientPluginTest/ClientPluginTest.cpp000066400000000000000000000033121477124122200326160ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "ClientPluginTest.h" #include "PluginManager.h" #include "SyncProfile.h" #define TEST_PLUGIN_PATH "/opt/tests/buteo-syncfw" using namespace Buteo; void ClientPluginTest::testCreateDestroy() { PluginManager pluginManager( TEST_PLUGIN_PATH ); SyncProfile profile( "dummyprofile" ); ClientPlugin *client1 = pluginManager.createClient( "hdummy", profile, this ); QVERIFY( client1 ); QVERIFY( pluginManager.iLoadedDlls.count() == 1 ); ClientPlugin *client2 = pluginManager.createClient( "hdummy", profile, this ); QVERIFY( client2 ); QVERIFY( pluginManager.iLoadedDlls.count() == 1 ); pluginManager.destroyClient( client1 ); QVERIFY( pluginManager.iLoadedDlls.count() == 1 ); pluginManager.destroyClient( client2 ); QVERIFY( pluginManager.iLoadedDlls.count() == 0 ); } QTEST_GUILESS_MAIN(Buteo::ClientPluginTest) buteo-syncfw-0.11.10/unittests/tests/pluginmanagertests/ClientPluginTest/ClientPluginTest.h000066400000000000000000000041231477124122200322640ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef CLIENTPLUGINTEST_H #define CLIENTPLUGINTEST_H #include #include "PluginCbInterface.h" namespace Buteo { class ClientPluginTest : public QObject, public PluginCbInterface { Q_OBJECT public: virtual bool requestStorage(const QString &/*aStorageName*/, const SyncPluginBase */*aCaller*/) { return false; } virtual void releaseStorage(const QString &/*aStorageName*/, const SyncPluginBase */*aCaller*/) { } virtual StoragePlugin *createStorage(const QString &/*aPluginName*/) { return NULL; } virtual void destroyStorage(StoragePlugin */*aStorage*/) { } virtual QString getDeviceIMEI() { return QString( "000000000000000" ); } virtual bool isConnectivityAvailable( Sync::ConnectivityType /*aType*/ ) { return false; } virtual Profile *getSyncProfileByRemoteAddress(const QString &aAddress) { Q_UNUSED(aAddress); return 0; } virtual QString getValue(const QString &aAddress, const QString &aKey) { Q_UNUSED(aAddress); Q_UNUSED(aKey); return ""; } private slots: void testCreateDestroy(); private: }; } #endif buteo-syncfw-0.11.10/unittests/tests/pluginmanagertests/ClientPluginTest/ClientPluginTest.pro000066400000000000000000000000431477124122200326320ustar00rootroot00000000000000include(../../testapplication.pri) buteo-syncfw-0.11.10/unittests/tests/pluginmanagertests/DeletedItemsIdStorageTest/000077500000000000000000000000001477124122200304335ustar00rootroot00000000000000DeletedItemsIdStorageTest.cpp000066400000000000000000000061741477124122200361020ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/pluginmanagertests/DeletedItemsIdStorageTest/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "DeletedItemsIdStorageTest.h" #include "DeletedItemsIdStorage.h" #include #include using namespace Buteo; const QString DBFILE("/tmp/deleteditemsidstoragetest.db"); /*! \fn DeletedItemsIdStorageTest::init() */ void DeletedItemsIdStorageTest::init() { iDeletedItems = new DeletedItemsIdStorage; } /*! \fn DeletedItemsIdStorageTest::cleanup() */ void DeletedItemsIdStorageTest::cleanup() { delete iDeletedItems; iDeletedItems = nullptr; } /*! \fn DeletedItemsIdStorageTest::testInit() */ void DeletedItemsIdStorageTest::testInit() { bool initResult; bool uninitResult; initResult = iDeletedItems->init(DBFILE); QVERIFY(initResult == true); uninitResult = iDeletedItems->uninit(); QVERIFY(uninitResult == true); } /*! \fn DeletedItemsIdStorageTest::testItemIdStoring() */ void DeletedItemsIdStorageTest::testItemIdStoring() { iDeletedItems->init(DBFILE); QDateTime creationTime = QDateTime::fromTime_t(100000); QDateTime deletionTime = QDateTime::fromTime_t(20000000); QString itemId = "foo1"; iDeletedItems->addDeletedItem(itemId, creationTime, deletionTime); QDateTime fetchTime = QDateTime::fromTime_t(5000000); QList itemIdList; bool success = iDeletedItems->getDeletedItems(itemIdList, fetchTime); QVERIFY(success); QVERIFY(itemIdList.size() > 0); QVERIFY(itemIdList.first() == itemId); iDeletedItems->uninit(); } /*! \fn DeletedItemsIdStorageTest::testSnapshot() */ void DeletedItemsIdStorageTest::testSnapshot() { iDeletedItems->init(DBFILE); QDateTime now = QDateTime::currentDateTime(); // Zero milliseconds. now.setTime(now.time().addMSecs(-now.time().msec())); QList setItemIdList; setItemIdList << "foo" << "bar" << "zed"; QList setCreationTimes; setCreationTimes << now << now << now; iDeletedItems->setSnapshot(setItemIdList, setCreationTimes); QList receivedItemIdList; QList receivedCreationTimes; iDeletedItems->getSnapshot(receivedItemIdList, receivedCreationTimes); QVERIFY(setItemIdList == receivedItemIdList); QVERIFY(setCreationTimes == receivedCreationTimes); iDeletedItems->uninit(); } QTEST_GUILESS_MAIN(Buteo::DeletedItemsIdStorageTest) DeletedItemsIdStorageTest.h000066400000000000000000000024071477124122200355420ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/pluginmanagertests/DeletedItemsIdStorageTest/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef DELETEDITEMSIDSTORAGETEST_H #define DELETEDITEMSIDSTORAGETEST_H #include #include namespace Buteo { class DeletedItemsIdStorage; class DeletedItemsIdStorageTest : public QObject { Q_OBJECT private slots: void init(); void cleanup(); void testInit(); void testItemIdStoring(); void testSnapshot(); private: DeletedItemsIdStorage *iDeletedItems; }; } #endif DeletedItemsIdStorageTest.pro000066400000000000000000000000431477124122200361050ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/pluginmanagertests/DeletedItemsIdStorageTestinclude(../../testapplication.pri) buteo-syncfw-0.11.10/unittests/tests/pluginmanagertests/ServerPluginTest/000077500000000000000000000000001477124122200267065ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/pluginmanagertests/ServerPluginTest/ServerPluginTest.cpp000066400000000000000000000033101477124122200326740ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "ServerPluginTest.h" #include "PluginManager.h" #include "SyncProfile.h" #define TEST_PLUGIN_PATH "/opt/tests/buteo-syncfw" using namespace Buteo; void ServerPluginTest::testCreateDestroy() { PluginManager pluginManager( TEST_PLUGIN_PATH ); SyncProfile profile( "dummyprofile" ); ServerPlugin *server1 = pluginManager.createServer( "hdummy", profile, this ); QVERIFY( server1 ); QVERIFY( pluginManager.iLoadedDlls.count() == 1 ); ServerPlugin *server2 = pluginManager.createServer( "hdummy", profile, this ); QVERIFY( server2 ); QVERIFY( pluginManager.iLoadedDlls.count() == 1 ); pluginManager.destroyServer( server1 ); QVERIFY( pluginManager.iLoadedDlls.count() == 1 ); pluginManager.destroyServer( server2 ); QVERIFY( pluginManager.iLoadedDlls.count() == 0 ); } QTEST_GUILESS_MAIN(Buteo::ServerPluginTest) buteo-syncfw-0.11.10/unittests/tests/pluginmanagertests/ServerPluginTest/ServerPluginTest.h000066400000000000000000000041221477124122200323430ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SERVERPLUGINTEST_H #define SERVERPLUGINTEST_H #include #include "PluginCbInterface.h" namespace Buteo { class ServerPluginTest : public QObject, public PluginCbInterface { Q_OBJECT public: virtual bool requestStorage(const QString &/*aStorageName*/, const SyncPluginBase */*aCaller*/) { return false; } virtual void releaseStorage(const QString &/*aStorageName*/, const SyncPluginBase */*aCaller*/) { } virtual StoragePlugin *createStorage(const QString &/*aPluginName*/) { return NULL; } virtual void destroyStorage(StoragePlugin */*aStorage*/) { } virtual QString getDeviceIMEI() { return QString( "000000000000000" ); } virtual bool isConnectivityAvailable( Sync::ConnectivityType /*aType*/ ) { return false; } virtual Profile *getSyncProfileByRemoteAddress(const QString &aAddress) { Q_UNUSED(aAddress); return 0; } virtual QString getValue(const QString &aAddress, const QString &aKey) { Q_UNUSED(aAddress); Q_UNUSED(aKey); return ""; } private slots: void testCreateDestroy(); private: }; } #endif buteo-syncfw-0.11.10/unittests/tests/pluginmanagertests/ServerPluginTest/ServerPluginTest.pro000066400000000000000000000000431477124122200327120ustar00rootroot00000000000000include(../../testapplication.pri) buteo-syncfw-0.11.10/unittests/tests/pluginmanagertests/StoragePluginTest/000077500000000000000000000000001477124122200270445ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/pluginmanagertests/StoragePluginTest/StoragePluginTest.cpp000066400000000000000000000031641477124122200331770ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "StoragePluginTest.h" #include "PluginManager.h" #define TEST_PLUGIN_PATH "/opt/tests/buteo-syncfw" using namespace Buteo; void StoragePluginTest::testCreateDestroy() { PluginManager pluginManager( TEST_PLUGIN_PATH ); StoragePlugin *storage1 = pluginManager.createStorage( "hdummy" ); QVERIFY( storage1 ); QVERIFY( pluginManager.iLoadedDlls.count() == 1 ); StoragePlugin *storage2 = pluginManager.createStorage( "hdummy" ); QVERIFY( storage2 ); QVERIFY( pluginManager.iLoadedDlls.count() == 1 ); pluginManager.destroyStorage( storage1 ); QVERIFY( pluginManager.iLoadedDlls.count() == 1 ); pluginManager.destroyStorage(storage2 ); QVERIFY( pluginManager.iLoadedDlls.count() == 0 ); } QTEST_GUILESS_MAIN(Buteo::StoragePluginTest) buteo-syncfw-0.11.10/unittests/tests/pluginmanagertests/StoragePluginTest/StoragePluginTest.h000066400000000000000000000021001477124122200326310ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef STORAGEPLUGINTEST_H #define STORAGEPLUGINTEST_H #include namespace Buteo { class StoragePluginTest : public QObject { Q_OBJECT private slots: void testCreateDestroy(); private: }; } #endif buteo-syncfw-0.11.10/unittests/tests/pluginmanagertests/StoragePluginTest/StoragePluginTest.pro000066400000000000000000000000431477124122200332060ustar00rootroot00000000000000include(../../testapplication.pri) buteo-syncfw-0.11.10/unittests/tests/pluginmanagertests/pluginmanagertests.pro000066400000000000000000000002251477124122200300560ustar00rootroot00000000000000TEMPLATE = subdirs SUBDIRS = \ ClientPluginTest \ DeletedItemsIdStorageTest \ ServerPluginTest \ StoragePluginTest \ buteo-syncfw-0.11.10/unittests/tests/runstarget.sh000077500000000000000000000013221477124122200222400ustar00rootroot00000000000000#!/bin/sh -e if [ $# -lt 1 ]; then echo "You need to pass test executable as an argument!" exit 1 fi TESTS_DIR="$(dirname "${0}")" TMP_DIR="/tmp/$(basename "${TESTS_DIR}")" rm -rf "${TMP_DIR}" mkdir -p "${TMP_DIR}" # Copy test data into tmp dir (read/write enabled location) mkdir -p "${TMP_DIR}/syncprofiletests" cp -a "${TESTS_DIR}/syncprofiletests/testprofiles" "${TMP_DIR}/syncprofiletests/" # Test data are searched with paths relative to CWD cd "${TMP_DIR}" export LD_LIBRARY_PATH="${TESTS_DIR}:${LD_LIBRARY_PATH}" # Accept both absolute and relative path to test executable TESTDIR=$(cd "${TESTS_DIR}"; readlink -f "${1}") TEST=$(basename ${1}) shift exec "${TESTDIR}/${TEST}" "${@}" -maxwarnings 0 buteo-syncfw-0.11.10/unittests/tests/sync-fw-tests.ref000066400000000000000000000011421477124122200227270ustar00rootroot00000000000000Totals: 3 passed, 0 failed, 0 skipped Totals: 8 passed, 0 failed, 0 skipped Totals: 3 passed, 0 failed, 0 skipped Totals: 3 passed, 0 failed, 0 skipped Totals: 3 passed, 0 failed, 0 skipped Totals: 5 passed, 0 failed, 0 skipped Totals: 7 passed, 0 failed, 0 skipped Totals: 4 passed, 0 failed, 0 skipped Totals: 3 passed, 0 failed, 0 skipped Totals: 10 passed, 0 failed, 0 skipped Totals: 3 passed, 0 failed, 0 skipped Totals: 4 passed, 0 failed, 0 skipped Totals: 10 passed, 0 failed, 0 skipped Totals: 3 passed, 0 failed, 0 skipped Totals: 3 passed, 0 failed, 0 skipped Totals: 3 passed, 0 failed, 0 skipped buteo-syncfw-0.11.10/unittests/tests/syncfwclienttests/000077500000000000000000000000001477124122200233005ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/syncfwclienttests/SyncClientInterfaceTest/000077500000000000000000000000001477124122200300345ustar00rootroot00000000000000SyncClientInterfaceTest.cpp000066400000000000000000000066761477124122200352340ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/syncfwclienttests/SyncClientInterfaceTest/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include #include "SyncClientInterfaceTest.h" #include "synchronizer.h" #include "Profile.h" #include "SyncSchedule.h" #include "SyncProfile.h" #include "SyncClientInterfacePrivate.h" #include using namespace Buteo; static const QString USERPROFILE_DIR = "syncprofiletests/testprofiles/user"; static const QString SYSTEMPROFILE_DIR = "syncprofiletests/testprofiles/system"; // FIXME: This test case tries to execute real sync scenario but with invalid // profiles - this simply cannot work. void SyncClientInterfaceTest::initTestCase() { iSync = new Synchronizer(nullptr); iInterface = new SyncClientInterface(); qRegisterMetaType("Buteo::Profile"); qRegisterMetaType("Buteo::SyncResults"); } void SyncClientInterfaceTest::cleanupTestCase() { QVERIFY(iInterface != 0); delete iInterface; iInterface = 0; QVERIFY(iSync != 0); delete iSync; iSync = 0; } void SyncClientInterfaceTest::testIsValid() { QCOMPARE(iInterface->isValid(), false); } void SyncClientInterfaceTest::testStartSync() { QString empty(""); QCOMPARE(iInterface->startSync(empty), false); QSignalSpy sigStatus(iInterface, SIGNAL(resultsAvailable(QString, Buteo::SyncResults))); QString profile("testsync-ovi"); QCOMPARE(iInterface->startSync(profile), true); /* QCOMPARE(sigStatus.count(),1);*/ } void SyncClientInterfaceTest::testAbortSync() { QString profile("testsync-ovi"); iInterface->abortSync(profile); QStringList list = iInterface->getRunningSyncList(); sleep(1); QCOMPARE(list.size(), 1); } void SyncClientInterfaceTest::testGetRunningSyncList() { QString profile("testsync-ovi"); iInterface->startSync(profile); QStringList list = iInterface->getRunningSyncList(); QCOMPARE(list.size(), 1); } void SyncClientInterfaceTest::testSetSyncSchedule() { QString profile("testsync-ovi"); Buteo::SyncSchedule schedule; QSignalSpy sigProfile(iInterface, SIGNAL(profileChanged(QString, int, QString))); QCOMPARE(iInterface->setSyncSchedule(profile, schedule), true); } void SyncClientInterfaceTest::testAddProfile() { Buteo::SyncProfile profileToAdd("testsync-ovi"); QVERIFY(iInterface->updateProfile(profileToAdd)); } void SyncClientInterfaceTest::testUpdateProfile() { Buteo::SyncProfile profileToChange("testsync-ovi"); QVERIFY(iInterface->updateProfile(profileToChange)); } void SyncClientInterfaceTest::testRemoveProfile() { QString profileToChange("testsync-ovi"); QVERIFY(iInterface->removeProfile(profileToChange)); } QTEST_MAIN(Buteo::SyncClientInterfaceTest) SyncClientInterfaceTest.h000066400000000000000000000030301477124122200346560ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/syncfwclienttests/SyncClientInterfaceTest/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SyncClientInterfaceTEST_H_ #define SyncClientInterfaceTEST_H_ #include #include "SyncClientInterface.h" namespace Buteo { class Synchronizer; class SyncProfile; class SyncClientInterfaceTest: public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testIsValid(); void testAddProfile(); void testStartSync(); void testAbortSync(); void testGetRunningSyncList(); void testSetSyncSchedule(); void testUpdateProfile(); void testRemoveProfile(); private: Buteo::SyncClientInterface *iInterface; Synchronizer *iSync; SyncProfile *iProfile; }; } #endif /*SyncClientInterfaceTEST_H_*/ SyncClientInterfaceTest.pro000066400000000000000000000000541477124122200352320ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/syncfwclienttests/SyncClientInterfaceTestinclude(../syncfwclienttestapplication.pri) buteo-syncfw-0.11.10/unittests/tests/syncfwclienttests/syncfwclienttestapplication.pri000066400000000000000000000001061477124122200316450ustar00rootroot00000000000000include(../testapplication.pri) include(../../../msyncd/unittest.pri) buteo-syncfw-0.11.10/unittests/tests/syncfwclienttests/syncfwclienttests.pro000077500000000000000000000001011477124122200276100ustar00rootroot00000000000000TEMPLATE = subdirs SUBDIRS = \ SyncClientInterfaceTest \ buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/000077500000000000000000000000001477124122200231255ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/ProfileFactoryTest/000077500000000000000000000000001477124122200267155ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/ProfileFactoryTest/ProfileFactoryTest.cpp000066400000000000000000000061201477124122200332100ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "ProfileFactoryTest.h" #include #include #include "ProfileFactory.h" #include "SyncProfile.h" #include "StorageProfile.h" using namespace Buteo; void ProfileFactoryTest::testCreateDirect() { const QString NAME = "name"; ProfileFactory pf; { QString type = Profile::TYPE_CLIENT; QScopedPointer p(pf.createProfile(NAME, type)); QVERIFY(p != 0); QCOMPARE(p->name(), NAME); QCOMPARE(p->type(), type); } { QString type = Profile::TYPE_SYNC; QScopedPointer p(pf.createProfile(NAME, type)); QVERIFY(p != 0); QCOMPARE(p->name(), NAME); QCOMPARE(p->type(), type); QVERIFY(dynamic_cast(p.data()) != 0); } { QString type = Profile::TYPE_STORAGE; QScopedPointer p(pf.createProfile(NAME, type)); QVERIFY(p != 0); QCOMPARE(p->name(), NAME); QCOMPARE(p->type(), type); QVERIFY(dynamic_cast(p.data()) != 0); } } void ProfileFactoryTest::testCreateFromXml() { const QString NAME = "name"; ProfileFactory pf; QDomDocument doc; { QString type = Profile::TYPE_CLIENT; Profile clientProfile(NAME, type); QDomElement root = clientProfile.toXml(doc); QScopedPointer p(pf.createProfile(root)); QVERIFY(p != 0); QCOMPARE(p->name(), NAME); QCOMPARE(p->type(), type); } { QString type = Profile::TYPE_SYNC; SyncProfile syncProfile(NAME); QDomElement root = syncProfile.toXml(doc); QScopedPointer p(pf.createProfile(root)); QVERIFY(p != 0); QCOMPARE(p->name(), NAME); QCOMPARE(p->type(), type); QVERIFY(dynamic_cast(p.data()) != 0); } { QString type = Profile::TYPE_STORAGE; StorageProfile storageProfile(NAME); QDomElement root = storageProfile.toXml(doc); QScopedPointer p(pf.createProfile(root)); QVERIFY(p != 0); QCOMPARE(p->name(), NAME); QCOMPARE(p->type(), type); QVERIFY(dynamic_cast(p.data()) != 0); } } QTEST_GUILESS_MAIN(Buteo::ProfileFactoryTest) buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/ProfileFactoryTest/ProfileFactoryTest.h000066400000000000000000000021551477124122200326610ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef PROFILEFACTORYTEST_H #define PROFILEFACTORYTEST_H #include namespace Buteo { class ProfileFactoryTest: public QObject { Q_OBJECT private slots: void testCreateDirect(); void testCreateFromXml(); }; } #endif // PROFILEFACTORYTEST_H buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/ProfileFactoryTest/ProfileFactoryTest.pro000066400000000000000000000000431477124122200332240ustar00rootroot00000000000000include(../../testapplication.pri) buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/ProfileFieldTest/000077500000000000000000000000001477124122200263315ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/ProfileFieldTest/ProfileFieldTest.cpp000066400000000000000000000045031477124122200322430ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "ProfileFieldTest.h" #include #include "ProfileField.h" using namespace Buteo; static const QString FIELD_XML = "" "" "" ""; void ProfileFieldTest::testField() { QDomDocument doc; QVERIFY(doc.setContent(FIELD_XML, false)); // Create from XML. ProfileField pf_original(doc.documentElement()); // Copy construction. ProfileField pf(pf_original); // Verify properties. QCOMPARE(pf.name(), QString("Notebook Name")); QCOMPARE(pf.type(), QString("combo")); QCOMPARE(pf.defaultValue(), QString("myNotebook")); QCOMPARE(pf.label(), QString("Notebook")); QCOMPARE(pf.visible(), ProfileField::VISIBLE_USER); QCOMPARE(pf.isReadOnly(), true); QStringList options = pf.options(); QCOMPARE(options.size(), 2); QCOMPARE(options[0], QString("myNotebook")); QCOMPARE(options[1], QString("otherNotebook")); // Test value validation. QCOMPARE(pf.validate("myNotebook"), true); QCOMPARE(pf.validate("otherNotebook"), true); QCOMPARE(pf.validate("something else"), false); // XML output. QDomDocument doc2; QDomElement root = pf.toXml(doc2); QVERIFY(!root.isNull()); doc2.appendChild(root); QCOMPARE(doc.toString(), doc2.toString()); } QTEST_GUILESS_MAIN(Buteo::ProfileFieldTest) buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/ProfileFieldTest/ProfileFieldTest.h000066400000000000000000000021011477124122200317000ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef PROFILEFIELDTEST_H #define PROFILEFIELDTEST_H #include namespace Buteo { class ProfileFieldTest: public QObject { Q_OBJECT private slots: void testField(); }; } #endif // PROFILEFIELDTEST_H buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/ProfileFieldTest/ProfileFieldTest.pro000066400000000000000000000000431477124122200322540ustar00rootroot00000000000000include(../../testapplication.pri) buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/ProfileManagerTest/000077500000000000000000000000001477124122200266605ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/ProfileManagerTest/ProfileManagerTest.cpp000066400000000000000000000565171477124122200331350ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "ProfileManagerTest.h" #include "ProfileManager.h" #include "Profile_p.h" #include "ProfileEngineDefs.h" #include "StorageProfile.h" #include "SyncResults.h" #include #include using namespace Buteo; static const QString HCALENDAR = "hcalendar"; static const QString OVI_CALENDAR = "ovi-calendar"; static const QString CORRUPTED = "corrupted"; static const QString SYNCML = "syncml"; static const QString USERPROFILE_DIR = "syncprofiletests/testprofiles/user"; static const QString SYSTEMPROFILE_DIR = "syncprofiletests/testprofiles/system"; void ProfileManagerTest::initTestCase() { } void ProfileManagerTest::cleanupTestCase() { } void ProfileManagerTest::testGetProfile() { ProfileManager pm; pm.setPaths(USERPROFILE_DIR, USERPROFILE_DIR); // Storage profiles exist. QStringList names = pm.profileNames(Profile::TYPE_STORAGE); QVERIFY(!names.isEmpty()); QVERIFY(names.contains(HCALENDAR)); // No profiles for unknown type. QVERIFY(pm.profileNames("unknown").isEmpty()); // Getting unknown profile returns 0. QVERIFY(pm.profile("unknown", Profile::TYPE_SYNC) == 0); // Get a storage profile. QScopedPointer p(pm.profile(HCALENDAR, Profile::TYPE_STORAGE)); QVERIFY(p != 0); QCOMPARE(p->name(), HCALENDAR); QCOMPARE(p->type(), Profile::TYPE_STORAGE); QVERIFY(dynamic_cast(p.data()) != 0); // Getting unknown sync profile returns 0. QVERIFY(pm.syncProfile("unknown") == 0); // Get a sync profile. QScopedPointer sp(pm.profile(OVI_CALENDAR, Profile::TYPE_SYNC)); QVERIFY(sp != 0); QCOMPARE(sp->name(), OVI_CALENDAR); QCOMPARE(sp->type(), Profile::TYPE_SYNC); // No merged keys & fields before expanding. QCOMPARE(sp->isLoaded(), false); Profile *sub = sp->subProfile(HCALENDAR, Profile::TYPE_STORAGE); QVERIFY(sub != 0); QCOMPARE(sub->isLoaded(), false); QCOMPARE(sub->name(), HCALENDAR); QCOMPARE(sub->type(), Profile::TYPE_STORAGE); QCOMPARE(sub->key("Local URI"), QString()); // Merged keys & fields available after expanding. pm.expand(*sp); QCOMPARE(sp->isLoaded(), true); QCOMPARE(sub->isLoaded(), true); QCOMPARE(sub->key("Local URI"), QString("./Calendar")); } void ProfileManagerTest::testGetSyncProfile() { ProfileManager pm; pm.setPaths(USERPROFILE_DIR + '/', SYSTEMPROFILE_DIR + '/'); // Getting unknown sync profile returns 0. QVERIFY(pm.syncProfile("unknown") == 0); // Get a sync profile. QScopedPointer sp(pm.syncProfile(OVI_CALENDAR)); QVERIFY(sp != 0); QCOMPARE(sp->name(), OVI_CALENDAR); QCOMPARE(sp->type(), Profile::TYPE_SYNC); // Sync profile is expanded automatically. QCOMPARE(sp->isLoaded(), true); Profile *sub = sp->subProfile(SYNCML, Profile::TYPE_CLIENT); QVERIFY(sub != 0); sub = sp->subProfile(HCALENDAR, Profile::TYPE_STORAGE); QVERIFY(sub != 0); QCOMPARE(sub->key("Local URI"), QString("./Calendar")); // Get all sync profiles. QList allProfiles = pm.allSyncProfiles(); QVERIFY(!allProfiles.isEmpty()); QCOMPARE(allProfiles.first()->name(), OVI_CALENDAR); QCOMPARE(allProfiles.first()->type(), Profile::TYPE_SYNC); foreach (SyncProfile *p, allProfiles) { delete p; } allProfiles.clear(); } void ProfileManagerTest::testGetByData() { ProfileManager pm; pm.setPaths(USERPROFILE_DIR, USERPROFILE_DIR); QList profiles; // Get profiles with non-existent key. profiles = pm.getSyncProfilesByData("", "", "unknown"); QVERIFY(profiles.isEmpty()); // Get profiles with existing key, defined value, no match. profiles = pm.getSyncProfilesByData("", "", "enabled", "false"); QVERIFY(profiles.isEmpty()); // Get profiles with existing key, undefined value. profiles = pm.getSyncProfilesByData("", "", "enabled"); QVERIFY(!profiles.isEmpty()); QVERIFY(profiles[0] != 0); foreach (SyncProfile *p, profiles) { delete p; } profiles.clear(); // Get profiles with existing key, defined value, match. profiles = pm.getSyncProfilesByData("", "", "enabled", "true"); QVERIFY(!profiles.isEmpty()); QVERIFY(profiles[0] != 0); QCOMPARE(profiles[0]->key("enabled"), QString("true")); foreach (SyncProfile *p, profiles) { delete p; } profiles.clear(); // Get profiles by sub-profile information, no match. profiles = pm.getSyncProfilesByData("unknown", Profile::TYPE_STORAGE); QVERIFY(profiles.isEmpty()); // Get profiles by sub-profile information, key and value defined, no match. profiles = pm.getSyncProfilesByData(HCALENDAR, Profile::TYPE_STORAGE, "Target URI", "unknown"); QVERIFY(profiles.isEmpty()); // Get profiles by sub-profile information, sub-profile name not defined, // key and value defined, no match. profiles = pm.getSyncProfilesByData("", Profile::TYPE_STORAGE, "Target URI", "unknown"); QVERIFY(profiles.isEmpty()); // Get profiles by sub-profile information, no key defined. profiles = pm.getSyncProfilesByData(HCALENDAR, Profile::TYPE_STORAGE); QVERIFY(!profiles.isEmpty()); QVERIFY(profiles[0] != 0); QCOMPARE(profiles[0]->name(), OVI_CALENDAR); foreach (SyncProfile *p, profiles) { delete p; } profiles.clear(); // Get profiles by sub-profile information, key and value defined. profiles = pm.getSyncProfilesByData(HCALENDAR, Profile::TYPE_STORAGE, "Target URI", "./EventTask/Tasks"); QVERIFY(!profiles.isEmpty()); QVERIFY(profiles[0] != 0); QCOMPARE(profiles[0]->name(), OVI_CALENDAR); foreach (SyncProfile *p, profiles) { delete p; } profiles.clear(); // Get profiles by sub-profile information, sub-profile name not defined, // key and value defined. profiles = pm.getSyncProfilesByData("", Profile::TYPE_STORAGE, "Target URI", "./EventTask/Tasks"); QVERIFY(!profiles.isEmpty()); QVERIFY(profiles[0] != 0); QCOMPARE(profiles[0]->name(), OVI_CALENDAR); foreach (SyncProfile *p, profiles) { delete p; } profiles.clear(); } void ProfileManagerTest::testGetBySingleCriteria() { ProfileManager pm; pm.setPaths(USERPROFILE_DIR, USERPROFILE_DIR); QList profiles; // Get profiles with non-existent key. ProfileManager::SearchCriteria criteria; QList criteriaList; criteria.iType = ProfileManager::SearchCriteria::EXISTS; criteria.iKey = "unknown"; criteriaList.append(criteria); profiles = pm.getSyncProfilesByData(criteriaList); QVERIFY(profiles.isEmpty()); // Get profiles with existing key, defined value, no match. criteria.iType = ProfileManager::SearchCriteria::EQUAL; criteria.iKey = "enabled"; criteria.iValue = "false"; criteriaList.clear(); criteriaList.append(criteria); profiles = pm.getSyncProfilesByData(criteriaList); QVERIFY(profiles.isEmpty()); // Get profiles with existing key, undefined value. criteria.iType = ProfileManager::SearchCriteria::EXISTS; criteria.iKey = "enabled"; criteria.iValue = QString::null; criteriaList.clear(); criteriaList.append(criteria); profiles = pm.getSyncProfilesByData(criteriaList); QVERIFY(!profiles.isEmpty()); QVERIFY(profiles[0] != 0); foreach (SyncProfile *p, profiles) { delete p; } profiles.clear(); // Get profiles with existing key, defined value, match. criteria.iType = ProfileManager::SearchCriteria::EQUAL; criteria.iKey = "enabled"; criteria.iValue = "true"; criteriaList.clear(); criteriaList.append(criteria); profiles = pm.getSyncProfilesByData(criteriaList); QVERIFY(!profiles.isEmpty()); QVERIFY(profiles[0] != 0); QCOMPARE(profiles[0]->key("enabled"), QString("true")); foreach (SyncProfile *p, profiles) { delete p; } profiles.clear(); // Get profiles by sub-profile information, no match. criteria.iType = ProfileManager::SearchCriteria::EXISTS; criteria.iSubProfileName = "unknown"; criteria.iSubProfileType = Profile::TYPE_STORAGE; criteria.iKey = QString::null; criteria.iValue = QString::null; criteriaList.clear(); criteriaList.append(criteria); profiles = pm.getSyncProfilesByData(criteriaList); QVERIFY(profiles.isEmpty()); // Get profiles by sub-profile. criteria.iType = ProfileManager::SearchCriteria::EXISTS; criteria.iSubProfileName = HCALENDAR; criteria.iSubProfileType = Profile::TYPE_STORAGE; criteria.iKey = QString::null; criteria.iValue = QString::null; criteriaList.clear(); criteriaList.append(criteria); profiles = pm.getSyncProfilesByData(criteriaList); QVERIFY(!profiles.isEmpty()); QVERIFY(profiles[0] != 0); QCOMPARE(profiles[0]->name(), OVI_CALENDAR); foreach (SyncProfile *p, profiles) { delete p; } profiles.clear(); // Get profiles by sub-profile, no match. criteria.iType = ProfileManager::SearchCriteria::NOT_EXISTS; criteria.iSubProfileName = HCALENDAR; criteria.iSubProfileType = Profile::TYPE_STORAGE; criteria.iKey = QString::null; criteria.iValue = QString::null; criteriaList.clear(); criteriaList.append(criteria); profiles = pm.getSyncProfilesByData(criteriaList); QVERIFY(profiles.isEmpty()); // Get profiles by sub-profile. criteria.iType = ProfileManager::SearchCriteria::NOT_EXISTS; criteria.iSubProfileName = "unknown"; criteria.iSubProfileType = Profile::TYPE_STORAGE; criteria.iKey = QString::null; criteria.iValue = QString::null; criteriaList.clear(); criteriaList.append(criteria); profiles = pm.getSyncProfilesByData(criteriaList); QVERIFY(!profiles.isEmpty()); QVERIFY(profiles[0] != 0); foreach (SyncProfile *p, profiles) { delete p; } profiles.clear(); // Get profiles by sub-profile information, key and value defined, no match. criteria.iType = ProfileManager::SearchCriteria::EQUAL; criteria.iSubProfileName = HCALENDAR; criteria.iSubProfileType = Profile::TYPE_STORAGE; criteria.iKey = "Target URI"; criteria.iValue = "unknown"; criteriaList.clear(); criteriaList.append(criteria); profiles = pm.getSyncProfilesByData(criteriaList); QVERIFY(profiles.isEmpty()); // Get profiles by sub-profile information, sub-profile name not defined, // key and value defined, no match. criteria.iType = ProfileManager::SearchCriteria::EQUAL; criteria.iSubProfileName = QString::null; criteria.iSubProfileType = Profile::TYPE_STORAGE; criteria.iKey = "Target URI"; criteria.iValue = "unknown"; criteriaList.clear(); criteriaList.append(criteria); profiles = pm.getSyncProfilesByData(criteriaList); QVERIFY(profiles.isEmpty()); // Get profiles by sub-profile information, no key defined. criteria.iType = ProfileManager::SearchCriteria::EXISTS; criteria.iSubProfileName = HCALENDAR; criteria.iSubProfileType = Profile::TYPE_STORAGE; criteria.iKey = QString::null; criteria.iValue = QString::null; criteriaList.clear(); criteriaList.append(criteria); profiles = pm.getSyncProfilesByData(criteriaList); QVERIFY(!profiles.isEmpty()); QVERIFY(profiles[0] != 0); QCOMPARE(profiles[0]->name(), OVI_CALENDAR); foreach (SyncProfile *p, profiles) { delete p; } profiles.clear(); // Get profiles by sub-profile information, key and value defined. criteria.iType = ProfileManager::SearchCriteria::EQUAL; criteria.iSubProfileName = HCALENDAR; criteria.iSubProfileType = Profile::TYPE_STORAGE; criteria.iKey = "Target URI"; criteria.iValue = "./EventTask/Tasks"; criteriaList.clear(); criteriaList.append(criteria); profiles = pm.getSyncProfilesByData(criteriaList); QVERIFY(!profiles.isEmpty()); QVERIFY(profiles[0] != 0); QCOMPARE(profiles[0]->name(), OVI_CALENDAR); foreach (SyncProfile *p, profiles) { delete p; } profiles.clear(); // Get profiles by sub-profile information, key and value defined, no match. criteria.iType = ProfileManager::SearchCriteria::NOT_EQUAL; criteria.iSubProfileName = HCALENDAR; criteria.iSubProfileType = Profile::TYPE_STORAGE; criteria.iKey = "Target URI"; criteria.iValue = "./EventTask/Tasks"; criteriaList.clear(); criteriaList.append(criteria); profiles = pm.getSyncProfilesByData(criteriaList); QVERIFY(profiles.isEmpty()); // Get profiles by sub-profile information, key and value defined. criteria.iType = ProfileManager::SearchCriteria::NOT_EQUAL; criteria.iSubProfileName = HCALENDAR; criteria.iSubProfileType = Profile::TYPE_STORAGE; criteria.iKey = "Target URI"; criteria.iValue = "foobar"; criteriaList.clear(); criteriaList.append(criteria); profiles = pm.getSyncProfilesByData(criteriaList); QVERIFY(!profiles.isEmpty()); QVERIFY(profiles[0] != 0); foreach (SyncProfile *p, profiles) { delete p; } profiles.clear(); // Get profiles by sub-profile information, sub-profile name not defined, // key and value defined. criteria.iType = ProfileManager::SearchCriteria::EQUAL; criteria.iSubProfileName = QString::null; criteria.iSubProfileType = Profile::TYPE_STORAGE; criteria.iKey = "Target URI"; criteria.iValue = "./EventTask/Tasks"; criteriaList.clear(); criteriaList.append(criteria); profiles = pm.getSyncProfilesByData(criteriaList); QVERIFY(!profiles.isEmpty()); QVERIFY(profiles[0] != 0); QCOMPARE(profiles[0]->name(), OVI_CALENDAR); foreach (SyncProfile *p, profiles) { delete p; } profiles.clear(); } void ProfileManagerTest::testGetByMultipleCriteria() { ProfileManager pm; pm.setPaths(USERPROFILE_DIR, USERPROFILE_DIR); QList profiles; QList criteriaList; // Profile must be enabled. ProfileManager::SearchCriteria criteria1; criteria1.iType = ProfileManager::SearchCriteria::EQUAL; criteria1.iKey = KEY_ENABLED; criteria1.iValue = BOOLEAN_TRUE; criteriaList.append(criteria1); // Profile must not be hidden. ProfileManager::SearchCriteria criteria2; criteria2.iType = ProfileManager::SearchCriteria::NOT_EQUAL; criteria2.iKey = KEY_HIDDEN; criteria2.iValue = BOOLEAN_TRUE; criteriaList.append(criteria2); // Profile must have a hcalendar storage. ProfileManager::SearchCriteria criteria3; criteria3.iType = ProfileManager::SearchCriteria::EXISTS; criteria3.iSubProfileName = "hcalendar"; criteria3.iSubProfileType = Profile::TYPE_STORAGE; criteriaList.append(criteria3); profiles = pm.getSyncProfilesByData(criteriaList); QVERIFY(!profiles.isEmpty()); QVERIFY(profiles[0] != 0); QCOMPARE(profiles[0]->name(), OVI_CALENDAR); foreach (SyncProfile *p, profiles) { delete p; } profiles.clear(); // Add a fourth criteria, that doesn't match to any profile. ProfileManager::SearchCriteria criteria4; criteria4.iType = ProfileManager::SearchCriteria::EQUAL; criteria4.iSubProfileName = "hcalendar"; criteria4.iSubProfileType = Profile::TYPE_STORAGE; criteria4.iKey = "Notebook Name"; criteria4.iValue = "Personal"; criteriaList.append(criteria4); profiles = pm.getSyncProfilesByData(criteriaList); QVERIFY(profiles.isEmpty()); // Replace the fourth criteria with a criteria that matches. criteria4.iValue = "myNotebook"; criteriaList[3] = criteria4; profiles = pm.getSyncProfilesByData(criteriaList); QVERIFY(!profiles.isEmpty()); QVERIFY(profiles[0] != 0); QCOMPARE(profiles[0]->name(), OVI_CALENDAR); foreach (SyncProfile *p, profiles) { delete p; } profiles.clear(); } void ProfileManagerTest::testGetByStorage() { ProfileManager pm; pm.setPaths(USERPROFILE_DIR, USERPROFILE_DIR); QList profiles; // Get profiles by storage. profiles = pm.getSyncProfilesByStorage("hcalendar", true); QVERIFY(!profiles.isEmpty()); QVERIFY(profiles[0] != 0); QCOMPARE(profiles[0]->name(), OVI_CALENDAR); foreach (SyncProfile *p, profiles) { delete p; } profiles.clear(); } void ProfileManagerTest::testLog() { ProfileManager pm; pm.setPaths(USERPROFILE_DIR, USERPROFILE_DIR); { QScopedPointer p(pm.syncProfile(OVI_CALENDAR)); QVERIFY(p != 0); // Create log with some content. p->setLog(new SyncLog(OVI_CALENDAR)); QDateTime now = QDateTime::currentDateTime(); SyncResults syncResults(now, Buteo::SyncResults::SYNC_RESULT_FAILED, Buteo::SyncResults::INTERNAL_ERROR); syncResults.addTargetResults(TargetResults("hcalendar", ItemCounts(1, 2, 3), ItemCounts(4, 5, 6))); p->addResults(syncResults); // Save log. pm.saveLog(*p->log()); } // Load profile. Log is loaded also. { QScopedPointer p(pm.syncProfile(OVI_CALENDAR)); QVERIFY(p != 0); const SyncLog *loadedLog = p->log(); QVERIFY(loadedLog != 0); QCOMPARE(loadedLog->profileName(), OVI_CALENDAR); QVERIFY(p->lastResults() != 0); QCOMPARE(p->lastResults()->majorCode(), SyncResults::SYNC_RESULT_FAILED); QList targetResults = p->lastResults()->targetResults(); QCOMPARE(targetResults.size(), 1); QCOMPARE(targetResults[0].targetName(), QString("hcalendar")); QCOMPARE(targetResults[0].localItems().added, (unsigned)1); QCOMPARE(targetResults[0].localItems().deleted, (unsigned)2); QCOMPARE(targetResults[0].localItems().modified, (unsigned)3); QCOMPARE(targetResults[0].remoteItems().added, (unsigned)4); QCOMPARE(targetResults[0].remoteItems().deleted, (unsigned)5); QCOMPARE(targetResults[0].remoteItems().modified, (unsigned)6); } // Save results through ProfileManager. { QCOMPARE(QFile::remove( USERPROFILE_DIR + "/sync/logs/" + OVI_CALENDAR + ".log.xml"), true); SyncResults syncResults(QDateTime::currentDateTime(), Buteo::SyncResults::SYNC_RESULT_FAILED, Buteo::SyncResults::INTERNAL_ERROR); syncResults.setMajorCode(Buteo::SyncResults::SYNC_RESULT_SUCCESS); pm.saveSyncResults(OVI_CALENDAR, syncResults); QScopedPointer p(pm.syncProfile(OVI_CALENDAR)); QVERIFY(p != 0); const SyncLog *log = p->log(); QVERIFY(log != 0); QVERIFY(log->lastResults() != 0); QVERIFY(log->lastResults()->majorCode() == Buteo::SyncResults::SYNC_RESULT_SUCCESS); } } void ProfileManagerTest::testSave() { ProfileManager pm; pm.setPaths(USERPROFILE_DIR + "/primary", USERPROFILE_DIR); { QScopedPointer p(pm.syncProfile(OVI_CALENDAR)); QVERIFY(p != 0); QCOMPARE(p->isEnabled(), true); p->setEnabled(false); pm.updateProfile(*p); } { QScopedPointer p(pm.syncProfile(OVI_CALENDAR)); QVERIFY(p != 0); QCOMPARE(p->isEnabled(), false); // Profile file in secondary directory is not affected. { ProfileManager pm2; pm2.setPaths(USERPROFILE_DIR, USERPROFILE_DIR); QScopedPointer p2(pm2.syncProfile(OVI_CALENDAR)); QVERIFY(p2 != 0); QCOMPARE(p2->isEnabled(), true); } p->setEnabled(true); pm.updateProfile(*p); } } void ProfileManagerTest::testHiddenProfiles() { ProfileManager pm; pm.setPaths(USERPROFILE_DIR, USERPROFILE_DIR); // Get number of visible sync profiles. QList profiles = pm.allVisibleSyncProfiles(); int num_profiles = profiles.size(); qDeleteAll(profiles); profiles.clear(); // Make one of the profiles hidden. QScopedPointer p(pm.syncProfile(OVI_CALENDAR)); QVERIFY(p != 0); p->setBoolKey(KEY_HIDDEN, true); pm.updateProfile(*p); // Verify that number of visible profiles is reduced. profiles = pm.allVisibleSyncProfiles(); QCOMPARE(profiles.size(), num_profiles - 1); qDeleteAll(profiles); profiles.clear(); // Make profile visible again. p->removeKey(KEY_HIDDEN); pm.updateProfile(*p); } void ProfileManagerTest::testRemovingProfiles() { ProfileManager pm; pm.setPaths(USERPROFILE_DIR, USERPROFILE_DIR); // Create a temporary profile that can be deleted. const QString TEMP_NAME = "TempProfile"; QScopedPointer p(pm.syncProfile(OVI_CALENDAR)); QVERIFY(p != 0); p->setName(TEMP_NAME); pm.updateProfile(*p); // Try removing protected profile. p->setBoolKey(KEY_PROTECTED, true); pm.updateProfile(*p); QCOMPARE(pm.removeProfile(TEMP_NAME), false); // Disable protectiong and remove profile. p->removeKey(KEY_PROTECTED); pm.updateProfile(*p); QCOMPARE(pm.removeProfile(TEMP_NAME), true); } void ProfileManagerTest::testOverrideKey() { ProfileManager pm; pm.setPaths(USERPROFILE_DIR, USERPROFILE_DIR); QScopedPointer p(static_cast( pm.profile(OVI_CALENDAR, Profile::TYPE_SYNC))); QVERIFY(p != 0); Profile *storage = p->subProfile(HCALENDAR, Profile::TYPE_STORAGE); QVERIFY(storage != 0); // Set target URI to the main profile (storage section). const QString URI_KEY = "Target URI"; const QString URI = "./new/uri"; storage->setKey(URI_KEY, URI); // Service sub-profile file contains a target uri also, but the key defined // in the main profile overrides it. pm.expand(*p); QCOMPARE(storage->key(URI_KEY), URI); } void ProfileManagerTest::testBackup() { ProfileManager pm; pm.setPaths(USERPROFILE_DIR + '/', SYSTEMPROFILE_DIR + '/'); // Copy to backup. QString fileName = USERPROFILE_DIR + '/' + Profile::TYPE_SYNC + '/' + OVI_CALENDAR + ".xml"; QFile file(fileName); QVERIFY(file.copy(fileName + ".bak")); // Get a sync profile. Profile is restored from a backup. QScopedPointer sp(pm.syncProfile(OVI_CALENDAR)); QVERIFY(sp != 0); QCOMPARE(sp->name(), OVI_CALENDAR); QCOMPARE(sp->type(), Profile::TYPE_SYNC); // Backup is removed after successful load. QVERIFY(!QFile::exists(fileName + ".bak")); } QTEST_GUILESS_MAIN(Buteo::ProfileManagerTest) buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/ProfileManagerTest/ProfileManagerTest.h000066400000000000000000000026761477124122200325770ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef PROFILEMANAGERTEST_H #define PROFILEMANAGERTEST_H #include namespace Buteo { class ProfileManagerTest: public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testGetProfile(); void testGetSyncProfile(); void testGetByData(); void testGetBySingleCriteria(); void testGetByMultipleCriteria(); void testGetByStorage(); void testLog(); void testSave(); void testHiddenProfiles(); void testRemovingProfiles(); void testOverrideKey(); void testBackup(); }; } #endif // PROFILEMANAGERTEST_H buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/ProfileManagerTest/ProfileManagerTest.pro000066400000000000000000000000431477124122200331320ustar00rootroot00000000000000include(../../testapplication.pri) buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/ProfileTest/000077500000000000000000000000001477124122200253655ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/ProfileTest/ProfileTest.cpp000066400000000000000000000343551477124122200303430ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "ProfileTest.h" #include "Profile_p.h" #include #include #include "ProfileEngineDefs.h" using namespace Buteo; static const QString PROFILE_DIR = "syncprofiletests/testprofiles/user"; static const QString TMP_PROFILE_DIR = "syncprofiletests/testprofiles/tmp"; static const QString EXPECTED_PROFILE_DIR = "syncprofiletests/testprofiles/expected"; void ProfileTest::initTestCase() { } void ProfileTest::cleanupTestCase() { } void ProfileTest::testConstruction() { QString name = "ovi-calendar"; QString type = Profile::TYPE_SYNC; // Construction from scratch. { Profile p(name, type); QCOMPARE(p.name(), name); QCOMPARE(p.type(), type); } // Construction from XML. { QScopedPointer p(loadFromXmlFile(name, Profile::TYPE_SYNC)); QVERIFY(p != 0); QCOMPARE(p->name(), name); QCOMPARE(p->type(), type); QCOMPARE(p->isEnabled(), true); QStringList subProfileNames = p->subProfileNames(); QCOMPARE(subProfileNames.size(), 3); Profile *sp = p->subProfile("hcalendar", Profile::TYPE_STORAGE); QVERIFY(sp != 0); QCOMPARE(sp->key("Notebook Name"), QString("myNotebook")); } // Copy constructor. { QScopedPointer p(loadFromXmlFile("hcalendar", Profile::TYPE_STORAGE)); QVERIFY(p != 0); QScopedPointer p2(loadFromXmlFile("testsync-ovi", Profile::TYPE_SYNC)); QVERIFY(p2 != 0); p2->merge(*p); Profile p3(*p2); QCOMPARE(p3.toString(), p2->toString()); } } void ProfileTest::testProperties() { QString name = "ovi-calendar"; QString newName = "ovi-new"; QString type = Profile::TYPE_SYNC; Profile p(name, type); // Change name. QCOMPARE(p.name(), name); p.setName(newName); QCOMPARE(p.name(), newName); // Change enabled status. QCOMPARE(p.isEnabled(), true); p.setEnabled(false); QCOMPARE(p.isEnabled(), false); // Change loaded status. QCOMPARE(p.isLoaded(), false); p.setLoaded(true); QCOMPARE(p.isLoaded(), true); } void ProfileTest::testKeys() { const QString NAME = "ovi-calendar"; const QString TYPE = Profile::TYPE_SYNC; const QString KEY1 = "key1"; const QString VALUE1 = "value1"; const QString KEY2 = "key2"; const QString VALUE2 = "value2"; const QString VALUE3 = "value3"; const QString BOOLKEY = "boolkey"; const QString DEFAULT = "default"; Profile p(NAME, TYPE); // No keys. QVERIFY(p.key(KEY1).isNull()); QCOMPARE(p.key(KEY1, DEFAULT), DEFAULT); QVERIFY(p.allKeys().isEmpty()); QCOMPARE(p.boolKey(BOOLKEY), false); // Default = false QCOMPARE(p.boolKey(BOOLKEY, true), true); // Default = true QVERIFY(p.keyValues(KEY1).isEmpty()); QVERIFY(p.keyNames().isEmpty()); // Add keys. p.setKey(KEY1, VALUE1); QStringList values; values << VALUE2 << VALUE3; QCOMPARE(values.size(), 2); p.setKeyValues(KEY2, values); p.setBoolKey(BOOLKEY, true); QCOMPARE(p.key(KEY1), VALUE1); QCOMPARE(p.key(KEY2), VALUE2); QCOMPARE(p.boolKey(BOOLKEY), true); QMap allKeys = p.allKeys(); QCOMPARE(allKeys.size(), 4); // Note: two values for KEY2. QCOMPARE(allKeys.value(KEY1), VALUE1); QCOMPARE(allKeys.value(KEY2), VALUE2); QCOMPARE(allKeys.value(BOOLKEY), QString("true")); QStringList key2Values = p.keyValues(KEY2); QCOMPARE(key2Values.size(), 2); QCOMPARE(key2Values[0], VALUE2); QCOMPARE(key2Values[1], VALUE3); QStringList keyNames = p.keyNames(); QCOMPARE(keyNames.size(), 3); QCOMPARE(keyNames[0], BOOLKEY); QCOMPARE(keyNames[1], KEY1); QCOMPARE(keyNames[2], KEY2); // Modify keys. p.setKey(KEY1, VALUE2); p.setKey(KEY2, VALUE1); p.setKey(BOOLKEY, QString("false")); QCOMPARE(p.key(KEY1), VALUE2); QCOMPARE(p.keyValues(KEY1).size(), 1); QCOMPARE(p.key(KEY2), VALUE1); QCOMPARE(p.boolKey(BOOLKEY), false); key2Values = p.keyValues(KEY2); QCOMPARE(key2Values.size(), 2); QCOMPARE(key2Values[0], VALUE1); QCOMPARE(key2Values[1], VALUE3); values.clear(); p.setKeyValues(KEY2, values); QCOMPARE(p.keyValues(KEY2).size(), 0); values << VALUE2 << VALUE1; p.setKeyValues(KEY2, values); key2Values = p.keyValues(KEY2); QCOMPARE(key2Values.size(), 2); QCOMPARE(key2Values[0], VALUE2); QCOMPARE(key2Values[1], VALUE1); // Remove key. p.removeKey(KEY2); key2Values = p.keyValues(KEY2); QCOMPARE(key2Values.size(), 0); } void ProfileTest::testFields() { // Load a profile that has fields: calendar storage profile. const QString NAME = "hcalendar"; const QString TYPE = Profile::TYPE_STORAGE; QScopedPointer p(loadFromXmlFile(NAME, TYPE)); QVERIFY(p != 0); QCOMPARE(p->name(), NAME); QCOMPARE(p->type(), TYPE); // There should be 3 fields. QList allFields = p->allFields(); QCOMPARE(allFields.size(), 3); // The last field should be about notebook name. const ProfileField *field = p->field("Notebook Name"); QCOMPARE(field, allFields.last()); // Field that does not exist. QVERIFY(p->field("unknown") == 0); // Compare field properties. QCOMPARE(field->name(), QString("Notebook Name")); QCOMPARE(field->type(), QString("combo")); QCOMPARE(field->defaultValue(), QString("myNotebook")); QStringList options = field->options(); QCOMPARE(options.size(), 2); QCOMPARE(options[0], QString("myNotebook")); QCOMPARE(options[1], QString("otherNotebook")); QCOMPARE(field->validate("myNotebook"), true); QCOMPARE(field->validate("otherNotebook"), true); QCOMPARE(field->validate("invalidNotebook"), false); // All fields are visible. QCOMPARE(p->visibleFields().size(), 3); } void ProfileTest::testSubProfiles() { // Load a profile that has sub-profiles. const QString NAME = "ovi-calendar"; const QString TYPE = Profile::TYPE_SYNC; QScopedPointer p(loadFromXmlFile(NAME, TYPE)); QVERIFY(p != 0); QCOMPARE(p->name(), NAME); QCOMPARE(p->type(), TYPE); QStringList subProfileNames = p->subProfileNames(); QCOMPARE(subProfileNames[0], QString("syncml")); QCOMPARE(subProfileNames[1], QString("hcalendar")); QCOMPARE(subProfileNames[2], QString("hcontacts")); QCOMPARE(p->subProfileNames(Profile::TYPE_CLIENT).size(), 1); QCOMPARE(p->subProfileNames(Profile::TYPE_SYNC).size(), 0); QCOMPARE(p->subProfileNames(Profile::TYPE_STORAGE).size(), 2); // Sub-profile that does not exist. QVERIFY(p->subProfile("unknown", Profile::TYPE_CLIENT) == 0); const Profile *const_p = p.data(); const Profile *sub = const_p->subProfile("hcalendar", Profile::TYPE_STORAGE); QVERIFY(sub != 0); QCOMPARE(sub, p->subProfile("hcalendar")); QCOMPARE(sub->isEnabled(), true); QCOMPARE(sub->key("Notebook Name"), QString("myNotebook")); QList allSubProfiles = p->allSubProfiles(); QCOMPARE(allSubProfiles.size(), 3); // Sub-profile by key value. sub = p->subProfileByKeyValue("Notebook Name", "myNotebook", Profile::TYPE_STORAGE, true); QVERIFY(sub != 0); QCOMPARE(sub, p->subProfile("hcalendar")); } void ProfileTest::testMerge() { QScopedPointer p(loadFromXmlFile("testsync-ovi", Profile::TYPE_SYNC)); QScopedPointer p2(loadFromXmlFile("hcalendar", Profile::TYPE_STORAGE)); QVERIFY(p != 0); QVERIFY(p2 != 0); Profile *sub = p->subProfile("hcalendar"); QVERIFY(sub != 0); QVERIFY(sub->key("Local URI").isNull()); QVERIFY(sub->allFields().isEmpty()); p->merge(*p2); QCOMPARE(sub->key("Local URI"), QString("./Calendar")); QCOMPARE(sub->allFields().size(), 3); QCOMPARE(sub->d_ptr->iLocalKeys.size(), 4); QCOMPARE(sub->d_ptr->iMergedKeys.size(), 1); QCOMPARE(sub->d_ptr->iLocalFields.size(), 0); QCOMPARE(sub->d_ptr->iMergedFields.size(), 3); // Merge service to sync profile. QScopedPointer p3(loadFromXmlFile("ovi-calendar", Profile::TYPE_SYNC)); QVERIFY(p3 != 0); p3->merge(*p); QVERIFY(p3->subProfile("syncml", Profile::TYPE_CLIENT) != 0); } void ProfileTest::testValidate() { QScopedPointer p(loadFromXmlFile("hcalendar", Profile::TYPE_STORAGE)); QVERIFY(p != 0); // Invalid, because field values are not set. QCOMPARE(p->isValid(), false); // Valid after setting field values. p->setKey("Target URI", "cal"); p->setKey("Calendar Format", "vcalendar"); p->setKey("Notebook Name", "myNotebook"); QCOMPARE(p->isValid(), true); // Invalid when name or type is empty. p->setName(""); QCOMPARE(p->isValid(), false); p->setName("hcalendar"); p->d_ptr->iType = ""; QCOMPARE(p->isValid(), false); p->d_ptr->iType = Profile::TYPE_STORAGE; QCOMPARE(p->isValid(), true); // Invalid when field value is invalid. p->setKey("Notebook Name", "invalid"); QCOMPARE(p->isValid(), false); p->setKey("Notebook Name", "myNotebook"); // Validate profile that has sub-profiles. QScopedPointer p2(loadFromXmlFile("testsync-ovi", Profile::TYPE_SYNC)); QVERIFY(p2 != 0); p->setEnabled(true); p2->merge(*p); QCOMPARE(p2->isValid(), true); // Removing required key makes the profile invalid. Profile *sub = p2->subProfile("hcalendar"); QVERIFY(sub != 0); sub->setKey("Target URI", QString::null); QEXPECT_FAIL("", "It seems all keys are dispensable with the current implementation", Continue); QCOMPARE(p2->isValid(), false); // Disabling the profile that requires the removed key makes the profile // valid again. sub->setEnabled(false); QCOMPARE(p2->isValid(), true); } void ProfileTest::testXmlConversion() { // TODO: Compare XML documents in a clever way, then fix the *-expected.xml // files which need to be updated after "service" profile-type removal // (left empty now) QEXPECT_FAIL("", "The order in which QDomDocument::toString() writes out XML attributes is undefined - cannot simply compare XML strings", Abort); QVERIFY(false); QScopedPointer p(loadFromXmlFile("hcalendar", Profile::TYPE_STORAGE)); QVERIFY(p != 0); QVERIFY(saveToXmlFile(*p, "hcalendar-output", true, TMP_PROFILE_DIR)); QCOMPARE(profileFileToString("hcalendar-output", Profile::TYPE_STORAGE, TMP_PROFILE_DIR), profileFileToString("hcalendar-expected", Profile::TYPE_STORAGE, EXPECTED_PROFILE_DIR)); // Merge storage profile to service profile. QScopedPointer p2(loadFromXmlFile("ovi-calendar", Profile::TYPE_SYNC)); QVERIFY(p2 != 0); p2->merge(*p); // Output local profile data only, no merged sub-profile data. QVERIFY(saveToXmlFile(*p2, "ovi-calendar-output", true, TMP_PROFILE_DIR)); QCOMPARE(profileFileToString("ovi-calendar-output", Profile::TYPE_SYNC, TMP_PROFILE_DIR), profileFileToString("ovi-calendar-expected", Profile::TYPE_SYNC, EXPECTED_PROFILE_DIR)); // Output merged sub-profiles also. QVERIFY(saveToXmlFile(*p2, "ovi-calendar-output", false, TMP_PROFILE_DIR)); QCOMPARE(profileFileToString("ovi-calendar-output", Profile::TYPE_SYNC, TMP_PROFILE_DIR), profileFileToString("ovi-calendar-merged-expected", Profile::TYPE_SYNC, EXPECTED_PROFILE_DIR)); } Profile *ProfileTest::loadFromXmlFile(const QString &aName, const QString &aType, const QString &aProfileDir) { QString profileDir = aProfileDir.isEmpty() ? PROFILE_DIR : aProfileDir; QFile file(profileDir + "/" + aType + "/" + aName + ".xml"); if (!file.open(QIODevice::ReadOnly)) { return 0; } // no else QDomDocument doc; if (!doc.setContent(&file)) { file.close(); return 0; } // no else file.close(); return new Profile(doc.documentElement()); } bool ProfileTest::saveToXmlFile(const Profile &aProfile, const QString &aName, bool aLocalOnly, const QString &aProfileDir) { QString profileDir = aProfileDir.isEmpty() ? PROFILE_DIR : aProfileDir; QDir dir; dir.mkpath(profileDir + "/" + aProfile.type()); QFile file(profileDir + "/" + aProfile.type() + "/" + aName + ".xml"); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { return false; } // no else QDomDocument doc; QDomProcessingInstruction xmlHeading = doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""); doc.appendChild(xmlHeading); QDomElement root = aProfile.toXml(doc, aLocalOnly); if (root.isNull()) { return false; } // no else doc.appendChild(root); QTextStream outputStream(&file); outputStream << doc.toString(PROFILE_INDENT); file.close(); return true; } QString ProfileTest::profileFileToString(const QString &aName, const QString &aType, const QString &aProfileDir) { QString profileDir = aProfileDir.isEmpty() ? PROFILE_DIR : aProfileDir; QString output; QFile file(profileDir + "/" + aType + "/" + aName + ".xml"); if (file.open(QIODevice::ReadOnly)) { QTextStream outputStream(&file); output = outputStream.readAll(); } // no else return output; } QTEST_GUILESS_MAIN(Buteo::ProfileTest) buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/ProfileTest/ProfileTest.h000066400000000000000000000034251477124122200300020ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef PROFILETEST_H #define PROFILETEST_H #include "Profile.h" #include namespace Buteo { class ProfileTest: public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testConstruction(); void testProperties(); void testKeys(); void testFields(); void testSubProfiles(); void testValidate(); void testMerge(); void testXmlConversion(); private: Profile *loadFromXmlFile(const QString &aName, const QString &aType, const QString &aProfileDir = QString()); bool saveToXmlFile(const Profile &aProfile, const QString &aName, bool aLocalOnly = true, const QString &aProfileDir = QString()); QString profileFileToString(const QString &aName, const QString &aType, const QString &aProfileDir = QString()); }; } #endif // PROFILETEST_H buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/ProfileTest/ProfileTest.pro000066400000000000000000000000431477124122200303440ustar00rootroot00000000000000include(../../testapplication.pri) buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/StorageProfileTest/000077500000000000000000000000001477124122200267125ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/StorageProfileTest/StorageProfileTest.cpp000066400000000000000000000040211477124122200332000ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "StorageProfileTest.h" #include #include "StorageProfile.h" using namespace Buteo; static const QString NAME = "hcalendar"; static const QString TYPE = Profile::TYPE_STORAGE; static const QString PROFILE_XML = "" "" "" "" "" "" ""; void StorageProfileTest::testStorageProfile() { // Create from scratch. StorageProfile p1(NAME); QCOMPARE(p1.name(), NAME); QCOMPARE(p1.type(), TYPE); // Create from XML. QDomDocument doc; QVERIFY(doc.setContent(PROFILE_XML, false)); StorageProfile p2(doc.documentElement()); QCOMPARE(p2.name(), NAME); QCOMPARE(p2.type(), TYPE); // Copy constructor. StorageProfile p3(p2); QCOMPARE(p3.toString(), p2.toString()); // Storage profile is disabled by default. Profile *p = &p1; QCOMPARE(p->isEnabled(), false); p->setEnabled(true); QCOMPARE(p->isEnabled(), true); } QTEST_GUILESS_MAIN(Buteo::StorageProfileTest) buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/StorageProfileTest/StorageProfileTest.h000066400000000000000000000021221477124122200326450ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef STORAGEPROFILETEST_H #define STORAGEPROFILETEST_H #include namespace Buteo { class StorageProfileTest: public QObject { Q_OBJECT private slots: void testStorageProfile(); }; } #endif // STORAGEPROFILETEST_H buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/StorageProfileTest/StorageProfileTest.pro000066400000000000000000000000431477124122200332160ustar00rootroot00000000000000include(../../testapplication.pri) buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/SyncLogTest/000077500000000000000000000000001477124122200253435ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/SyncLogTest/SyncLogTest.cpp000066400000000000000000000333051477124122200302710ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "SyncLogTest.h" #include #include "SyncLog.h" using namespace Buteo; static const QString NAME = "ovi-calendar"; static const QString LOG_XML = "" "" "" "" "" "" "" ""; void SyncLogTest::testLog() { // Create from scratch. SyncLog log1(NAME); QCOMPARE(log1.profileName(), NAME); QVERIFY(log1.lastResults() == 0); QCOMPARE(log1.allResults().size(), 0); QVERIFY(log1.lastSuccessfulResults() == 0); // Create from XML. QDomDocument doc; QVERIFY(doc.setContent(LOG_XML, false)); SyncLog log2(doc.documentElement()); QCOMPARE(log2.profileName(), NAME); QVERIFY(log2.lastResults() != 0); QVERIFY(log2.lastSuccessfulResults() == 0); QCOMPARE(log2.allResults().size(), 1); TargetResults tr2 = log2.lastResults()->targetResults().at(0); QCOMPARE(tr2.targetName(), QString("hcalendar")); QCOMPARE(tr2.localItems().added, (unsigned)2); QCOMPARE(tr2.localItems().deleted, (unsigned)3); QCOMPARE(tr2.localItems().modified, (unsigned)4); QCOMPARE(tr2.remoteItems().added, (unsigned)5); QCOMPARE(tr2.remoteItems().deleted, (unsigned)6); QCOMPARE(tr2.remoteItems().modified, (unsigned)7); QCOMPARE(log2.lastResults()->isScheduled(), true); // Copy constructor. SyncLog log3(log2); QDomDocument doc2; doc2.appendChild(log2.toXml(doc2)); QDomDocument doc3; doc3.appendChild(log3.toXml(doc3)); QVERIFY(doc2.toString().size() >= LOG_XML.size()); QCOMPARE(doc2.toString(), doc3.toString()); // Add a successful results. QDateTime successTime = QDateTime::fromString("2017-08-30T11:53:27", "yyyy-MM-ddThh:mm:ss"); log2.addResults(SyncResults(successTime, Buteo::SyncResults::SYNC_RESULT_SUCCESS, Buteo::SyncResults::NO_ERROR)); QVERIFY(log2.lastSuccessfulResults() != 0); QCOMPARE(log2.lastSuccessfulResults()->syncTime(), successTime); // Add new results. SyncResults newResults; newResults.setMajorCode(Buteo::SyncResults::SYNC_RESULT_CANCELLED); QCOMPARE(newResults.majorCode(), SyncResults::SYNC_RESULT_CANCELLED); QCOMPARE(newResults < *log2.lastResults(), false); newResults.addTargetResults(TargetResults("hcontacts", ItemCounts(2, 3, 4), ItemCounts(5, 6, 7))); log2.addResults(newResults); QVERIFY(log2.lastResults() != 0); QCOMPARE(log2.lastResults()->majorCode(), SyncResults::SYNC_RESULT_CANCELLED); QCOMPARE(log2.allResults().size(), 3); QCOMPARE(log2.allResults().at(0)->majorCode(), SyncResults::SYNC_RESULT_FAILED); QVERIFY(log2.lastSuccessfulResults() != 0); QCOMPARE(log2.lastSuccessfulResults()->syncTime(), successTime); // Verify target results contents. QCOMPARE(log2.lastResults()->targetResults().size(), 1); TargetResults tr = log2.lastResults()->targetResults().at(0); QCOMPARE(tr.targetName(), QString("hcontacts")); QCOMPARE(tr.localItems().added, (unsigned)2); QCOMPARE(tr.localItems().deleted, (unsigned)3); QCOMPARE(tr.localItems().modified, (unsigned)4); QCOMPARE(tr.remoteItems().added, (unsigned)5); QCOMPARE(tr.remoteItems().deleted, (unsigned)6); QCOMPARE(tr.remoteItems().modified, (unsigned)7); // Update last successful results. successTime = QDateTime::fromString("2017-08-30T12:00:00", "yyyy-MM-ddThh:mm:ss"); log2.addResults(SyncResults(successTime, Buteo::SyncResults::SYNC_RESULT_SUCCESS, Buteo::SyncResults::NO_ERROR)); QVERIFY(log2.lastSuccessfulResults() != 0); QCOMPARE(log2.lastSuccessfulResults()->syncTime(), successTime); // Push out last successful results from allResults. SyncResults failed; failed.setMajorCode(Buteo::SyncResults::SYNC_RESULT_FAILED); log2.addResults(failed); log2.addResults(failed); log2.addResults(failed); log2.addResults(failed); log2.addResults(failed); log2.addResults(failed); QCOMPARE(log2.allResults().size(), 5); QVERIFY(log2.lastSuccessfulResults() != 0); QCOMPARE(log2.lastSuccessfulResults()->syncTime(), successTime); QDomDocument docFailed; docFailed.appendChild(log2.toXml(docFailed)); SyncLog logFailed(docFailed.documentElement()); QCOMPARE(logFailed.allResults().size(), 5); QVERIFY(logFailed.lastSuccessfulResults() != 0); QCOMPARE(logFailed.lastSuccessfulResults()->syncTime(), successTime); QDomDocument docFailed2; docFailed2.appendChild(logFailed.toXml(docFailed2)); QCOMPARE(docFailed.toString(), docFailed2.toString()); } void SyncLogTest::testAddResults() { const int MAXLOGENTRIES = 5; SyncLog log(NAME); SyncResults newResults; newResults.setMajorCode(Buteo::SyncResults::SYNC_RESULT_CANCELLED); newResults.addTargetResults(TargetResults("hcontacts", ItemCounts(2, 3, 4), ItemCounts(5, 6, 7))); for (int i = 0; i < 7; ++i) { log.addResults(newResults); QVERIFY(log.allResults().size() <= MAXLOGENTRIES); } } #define FAILURE_MESSAGE "Database error: UID not unique" #define FAILURE_SERVER "No resource at URI" static const QString DETAILS_XML = "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""; void SyncLogTest::testDetailsFromXML() { QDomDocument doc; QVERIFY(doc.setContent(DETAILS_XML, false)); SyncLog log(doc.documentElement()); QCOMPARE(log.allResults().length(), 1); const SyncResults *result = log.lastResults(); QVERIFY(result); QCOMPARE(result->targetResults().length(), 1); TargetResults target = result->targetResults().first(); QCOMPARE(target.localDetails(TargetResults::ITEM_ADDED, TargetResults::ITEM_OPERATION_SUCCEEDED), QList() << QLatin1String("123-4") << QLatin1String("123-5")); QCOMPARE(target.localDetails(TargetResults::ITEM_ADDED, TargetResults::ITEM_OPERATION_FAILED), QList() << QLatin1String("123-6")); QCOMPARE(target.localMessage(QLatin1String("123-6")), QLatin1String(FAILURE_MESSAGE)); QCOMPARE(target.localDetails(TargetResults::ITEM_DELETED, TargetResults::ITEM_OPERATION_SUCCEEDED), QList() << QLatin1String("123-7")); QVERIFY(target.localDetails(TargetResults::ITEM_DELETED, TargetResults::ITEM_OPERATION_FAILED).isEmpty()); QCOMPARE(target.localDetails(TargetResults::ITEM_MODIFIED, TargetResults::ITEM_OPERATION_SUCCEEDED), QList() << QLatin1String("123-8") << QLatin1String("123-9")); QVERIFY(target.localDetails(TargetResults::ITEM_MODIFIED, TargetResults::ITEM_OPERATION_FAILED).isEmpty()); QCOMPARE(target.remoteDetails(TargetResults::ITEM_ADDED, TargetResults::ITEM_OPERATION_SUCCEEDED), QList() << QLatin1String("456-1")); QVERIFY(target.remoteDetails(TargetResults::ITEM_ADDED, TargetResults::ITEM_OPERATION_FAILED).isEmpty()); QCOMPARE(target.remoteDetails(TargetResults::ITEM_DELETED, TargetResults::ITEM_OPERATION_SUCCEEDED), QList() << QLatin1String("456-2")); QVERIFY(target.remoteDetails(TargetResults::ITEM_DELETED, TargetResults::ITEM_OPERATION_FAILED).isEmpty()); QCOMPARE(target.remoteDetails(TargetResults::ITEM_MODIFIED, TargetResults::ITEM_OPERATION_SUCCEEDED), QList() << QLatin1String("456-3")); QCOMPARE(target.remoteDetails(TargetResults::ITEM_MODIFIED, TargetResults::ITEM_OPERATION_FAILED), QList() << QLatin1String("456-7")); QCOMPARE(target.remoteMessage(QLatin1String("456-7")), QLatin1String(FAILURE_SERVER)); } void SyncLogTest::testAddDetails() { TargetResults result(QLatin1String("Test target")); result.addLocalDetails(QLatin1String("123456-7"), TargetResults::ITEM_ADDED); result.addLocalDetails(QLatin1String("123456-8"), TargetResults::ITEM_ADDED); result.addLocalDetails(QLatin1String("123456-9"), TargetResults::ITEM_MODIFIED); result.addLocalDetails(QLatin1String("123456-10"), TargetResults::ITEM_DELETED); result.addLocalDetails(QLatin1String("123456-11"), TargetResults::ITEM_ADDED, TargetResults::ITEM_OPERATION_FAILED, QLatin1String(FAILURE_MESSAGE)); QCOMPARE(result.localItems().added, (unsigned)2); QCOMPARE(result.localItems().deleted, (unsigned)1); QCOMPARE(result.localItems().modified, (unsigned)1); QCOMPARE(result.localDetails(TargetResults::ITEM_ADDED, TargetResults::ITEM_OPERATION_SUCCEEDED), QList() << QLatin1String("123456-7") << QLatin1String("123456-8")); QCOMPARE(result.localDetails(TargetResults::ITEM_ADDED, TargetResults::ITEM_OPERATION_FAILED), QList() << QLatin1String("123456-11")); QCOMPARE(result.localMessage(QLatin1String("123456-11")), QLatin1String(FAILURE_MESSAGE)); QCOMPARE(result.localDetails(TargetResults::ITEM_DELETED, TargetResults::ITEM_OPERATION_SUCCEEDED), QList() << QLatin1String("123456-10")); QVERIFY(result.localDetails(TargetResults::ITEM_DELETED, TargetResults::ITEM_OPERATION_FAILED).isEmpty()); QCOMPARE(result.localDetails(TargetResults::ITEM_MODIFIED, TargetResults::ITEM_OPERATION_SUCCEEDED), QList() << QLatin1String("123456-9")); QVERIFY(result.localDetails(TargetResults::ITEM_MODIFIED, TargetResults::ITEM_OPERATION_FAILED).isEmpty()); result.addRemoteDetails(QLatin1String("147258-7"), TargetResults::ITEM_ADDED); result.addRemoteDetails(QLatin1String("147258-8"), TargetResults::ITEM_ADDED); result.addRemoteDetails(QLatin1String("147258-9"), TargetResults::ITEM_MODIFIED); result.addRemoteDetails(QLatin1String("147258-10"), TargetResults::ITEM_DELETED); result.addRemoteDetails(QLatin1String("147258-11"), TargetResults::ITEM_ADDED, TargetResults::ITEM_OPERATION_FAILED, QLatin1String(FAILURE_SERVER)); QCOMPARE(result.remoteItems().added, (unsigned)2); QCOMPARE(result.remoteItems().deleted, (unsigned)1); QCOMPARE(result.remoteItems().modified, (unsigned)1); QCOMPARE(result.remoteDetails(TargetResults::ITEM_ADDED, TargetResults::ITEM_OPERATION_SUCCEEDED), QList() << QLatin1String("147258-7") << QLatin1String("147258-8")); QCOMPARE(result.remoteDetails(TargetResults::ITEM_ADDED, TargetResults::ITEM_OPERATION_FAILED), QList() << QLatin1String("147258-11")); QCOMPARE(result.remoteMessage(QLatin1String("147258-11")), QLatin1String(FAILURE_SERVER)); QCOMPARE(result.remoteDetails(TargetResults::ITEM_DELETED, TargetResults::ITEM_OPERATION_SUCCEEDED), QList() << QLatin1String("147258-10")); QVERIFY(result.remoteDetails(TargetResults::ITEM_DELETED, TargetResults::ITEM_OPERATION_FAILED).isEmpty()); QCOMPARE(result.remoteDetails(TargetResults::ITEM_MODIFIED, TargetResults::ITEM_OPERATION_SUCCEEDED), QList() << QLatin1String("147258-9")); QVERIFY(result.remoteDetails(TargetResults::ITEM_MODIFIED, TargetResults::ITEM_OPERATION_FAILED).isEmpty()); } QTEST_GUILESS_MAIN(Buteo::SyncLogTest) buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/SyncLogTest/SyncLogTest.h000066400000000000000000000022001477124122200277240ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCLOGTEST_H #define SYNCLOGTEST_H #include namespace Buteo { class SyncLogTest: public QObject { Q_OBJECT private slots: void testLog(); void testAddResults(); void testAddDetails(); void testDetailsFromXML(); }; } #endif // SYNCLOGTEST_H buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/SyncLogTest/SyncLogTest.pro000066400000000000000000000000431477124122200303000ustar00rootroot00000000000000include(../../testapplication.pri) buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/SyncProfileTest/000077500000000000000000000000001477124122200262225ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/SyncProfileTest/SyncProfileTest.cpp000066400000000000000000000204711477124122200320270ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "SyncProfileTest.h" #include #include #include "SyncProfile.h" #include "ProfileEngineDefs.h" using namespace Buteo; static const QString NAME = "ovi-calendar"; static const QString TYPE = Profile::TYPE_SYNC; static const QString PROFILE_XML = "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""; void SyncProfileTest::testConstruction() { // Create from scratch. SyncProfile p1(NAME); QCOMPARE(p1.name(), NAME); QCOMPARE(p1.type(), TYPE); // Create from XML. QDomDocument doc; QVERIFY(doc.setContent(PROFILE_XML, false)); SyncProfile p2(doc.documentElement()); QCOMPARE(p2.name(), NAME); QCOMPARE(p2.type(), TYPE); QCOMPARE(p2.syncSchedule().interval(), (unsigned)30); // Copy constructor. QScopedPointer p3(p2.clone()); QVERIFY(p3 != 0); QCOMPARE(p3->toString(), p2.toString()); } void SyncProfileTest::testProperties() { // Create from XML. QDomDocument doc; QVERIFY(doc.setContent(PROFILE_XML, false)); SyncProfile p(doc.documentElement()); // Get non-storage keys. QMap keys = p.allNonStorageKeys(); QCOMPARE(keys.size(), 2); QCOMPARE(keys.value("username"), QString("me")); QCOMPARE(keys.value("enabled"), QString("true")); // Get storage backend names. QStringList storages = p.storageBackendNames(); QCOMPARE(storages.size(), 1); QCOMPARE(storages[0], QString("cal-backend")); // Change sync type. QEXPECT_FAIL("", "Broken since d6d974e (Added functions to enable/disable normal scheduling). " "Not sure how to fix this.test Maybe setSyncType should be just removed from the API", Continue); QCOMPARE(p.syncType(), SyncProfile::SYNC_MANUAL); p.setSyncType(SyncProfile::SYNC_SCHEDULED); QCOMPARE(p.syncType(), SyncProfile::SYNC_SCHEDULED); // Destination type. QCOMPARE(p.destinationType(), SyncProfile::DESTINATION_TYPE_UNDEFINED); //Profile *service = p.serviceProfile(); //QVERIFY(service != 0); p.setKey(KEY_DESTINATION_TYPE, VALUE_ONLINE); QCOMPARE(p.destinationType(), SyncProfile::DESTINATION_TYPE_ONLINE); p.setKey(KEY_DESTINATION_TYPE, VALUE_DEVICE); QCOMPARE(p.destinationType(), SyncProfile::DESTINATION_TYPE_DEVICE); // Sync direction. QCOMPARE(p.syncDirection(), SyncProfile::SYNC_DIRECTION_UNDEFINED); Profile *client = p.clientProfile(); QVERIFY(client != 0); client->setKey(KEY_SYNC_DIRECTION, VALUE_TWO_WAY); QCOMPARE(p.syncDirection(), SyncProfile::SYNC_DIRECTION_TWO_WAY); client->setKey(KEY_SYNC_DIRECTION, VALUE_FROM_REMOTE); QCOMPARE(p.syncDirection(), SyncProfile::SYNC_DIRECTION_FROM_REMOTE); client->setKey(KEY_SYNC_DIRECTION, VALUE_TO_REMOTE); QCOMPARE(p.syncDirection(), SyncProfile::SYNC_DIRECTION_TO_REMOTE); p.setSyncDirection(SyncProfile::SYNC_DIRECTION_TWO_WAY); QCOMPARE(client->key(KEY_SYNC_DIRECTION), VALUE_TWO_WAY); p.setSyncDirection(SyncProfile::SYNC_DIRECTION_FROM_REMOTE); QCOMPARE(client->key(KEY_SYNC_DIRECTION), VALUE_FROM_REMOTE); p.setSyncDirection(SyncProfile::SYNC_DIRECTION_TO_REMOTE); QCOMPARE(client->key(KEY_SYNC_DIRECTION), VALUE_TO_REMOTE); SyncProfile emptyProfile("empty"); emptyProfile.setSyncDirection(SyncProfile::SYNC_DIRECTION_TWO_WAY); // Conflict resolution policy. QCOMPARE(p.conflictResolutionPolicy(), SyncProfile::CR_POLICY_UNDEFINED); QVERIFY(client != 0); client->setKey(KEY_CONFLICT_RESOLUTION_POLICY, VALUE_PREFER_REMOTE); QCOMPARE(p.conflictResolutionPolicy(), SyncProfile::CR_POLICY_PREFER_REMOTE_CHANGES); client->setKey(KEY_CONFLICT_RESOLUTION_POLICY, VALUE_PREFER_LOCAL); QCOMPARE(p.conflictResolutionPolicy(), SyncProfile::CR_POLICY_PREFER_LOCAL_CHANGES); p.setConflictResolutionPolicy(SyncProfile::CR_POLICY_PREFER_REMOTE_CHANGES); QCOMPARE(client->key(KEY_CONFLICT_RESOLUTION_POLICY), VALUE_PREFER_REMOTE); p.setConflictResolutionPolicy(SyncProfile::CR_POLICY_PREFER_LOCAL_CHANGES); QCOMPARE(client->key(KEY_CONFLICT_RESOLUTION_POLICY), VALUE_PREFER_LOCAL); emptyProfile.setConflictResolutionPolicy(SyncProfile::CR_POLICY_PREFER_REMOTE_CHANGES); } void SyncProfileTest::testResults() { SyncProfile p(NAME); // Create log with some content. p.setLog(new SyncLog(NAME)); QVERIFY(p.lastSyncTime().isNull()); QDateTime now = QDateTime::currentDateTime(); SyncResults syncResults(now, SyncResults::SYNC_RESULT_FAILED, SyncResults::NO_ERROR); syncResults.addTargetResults(TargetResults("hcalendar", ItemCounts(1, 2, 3), ItemCounts())); p.addResults(syncResults); QCOMPARE(p.lastSyncTime(), now); const SyncResults *sr = p.lastResults(); QVERIFY(sr != 0); QCOMPARE(sr->syncTime(), now); QCOMPARE(sr->majorCode(), SyncResults::SYNC_RESULT_FAILED); QCOMPARE(sr->targetResults().size(), 1); // 0 log. p.setLog(0); QVERIFY(p.lastResults() == 0); QVERIFY(p.lastSyncTime().isNull()); p.addResults(syncResults); QCOMPARE(p.lastSyncTime(), now); } void SyncProfileTest::testNextSyncTime() { // No next sync time in manual mode. SyncProfile p(NAME); QVERIFY(p.nextSyncTime(QDateTime::currentDateTime()).isNull()); // Scheduled sync. p.setSyncType(SyncProfile::SYNC_SCHEDULED); QDateTime lastSync(QDateTime::currentDateTime()); SyncResults r(lastSync, Buteo::SyncResults::SYNC_RESULT_SUCCESS, Buteo::SyncResults::NO_ERROR); p.addResults(r); SyncSchedule s; const unsigned INTERVAL = 15; s.setInterval(INTERVAL); s.setDays(Buteo::SyncSchedule::Days() | Buteo::SyncSchedule::Monday | Buteo::SyncSchedule::Tuesday | Buteo::SyncSchedule::Wednesday | Buteo::SyncSchedule::Thursday | Buteo::SyncSchedule::Friday | Buteo::SyncSchedule::Saturday | Buteo::SyncSchedule::Sunday); s.setScheduleEnabled(true); p.setSyncSchedule(s); QDateTime nextSync = p.nextSyncTime(p.lastSyncTime()); QCOMPARE(nextSync, lastSync.addSecs(INTERVAL * 60)); } void SyncProfileTest::testSubProfiles() { // Create from XML. QDomDocument doc; QVERIFY(doc.setContent(PROFILE_XML, false)); SyncProfile p(doc.documentElement()); const Profile *client = p.clientProfile(); QVERIFY(client != 0); QVERIFY(client->name() == "syncml"); QList storages = p.storageProfiles(); QCOMPARE(storages.size(), 2); QList storages2 = p.storageProfilesNonConst(); QCOMPARE(storages2.size(), 2); const Profile *server = p.serverProfile(); QVERIFY(server != 0); QVERIFY(server->name() == "syncmlserver"); } QTEST_GUILESS_MAIN(Buteo::SyncProfileTest) buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/SyncProfileTest/SyncProfileTest.h000066400000000000000000000022631477124122200314730ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCPROFILETEST_H #define SYNCPROFILETEST_H #include namespace Buteo { class SyncProfileTest: public QObject { Q_OBJECT private slots: void testConstruction(); void testProperties(); void testResults(); void testNextSyncTime(); void testSubProfiles(); }; } #endif // SYNCPROFILETEST_H buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/SyncProfileTest/SyncProfileTest.pro000066400000000000000000000000431477124122200320360ustar00rootroot00000000000000include(../../testapplication.pri) buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/SyncScheduleTest/000077500000000000000000000000001477124122200263565ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/SyncScheduleTest/SyncScheduleTest.cpp000066400000000000000000000226661477124122200323270ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "SyncScheduleTest.h" #include "SyncSchedule.h" #include "SyncSchedule_p.h" #include using namespace Buteo; static const QString SCHEDULE_XML = "" "" ""; void SyncScheduleTest::testConstruction() { // Create from scratch. SyncSchedule s; QCOMPARE(s.interval(), (unsigned)0); QCOMPARE(s.time().isNull(), true); QCOMPARE(s.days(), SyncSchedule::NoDays); QCOMPARE(s.rushEnabled(), false); QCOMPARE(s.rushInterval(), (unsigned)0); QCOMPARE(s.rushDays(), SyncSchedule::NoDays); // Create from XML. QDomDocument doc; QVERIFY(doc.setContent(SCHEDULE_XML, false)); SyncSchedule s2(doc.documentElement()); QCOMPARE(s2.interval(), (unsigned)30); QVERIFY(s2.time() == QTime(12, 34, 56, 0)); SyncSchedule::Days days = s2.days(); QVERIFY(days.testFlag(SyncSchedule::Monday)); QVERIFY(days.testFlag(SyncSchedule::Tuesday)); QVERIFY(days.testFlag(SyncSchedule::Wednesday)); QVERIFY(days.testFlag(SyncSchedule::Thursday)); QVERIFY(days.testFlag(SyncSchedule::Friday)); QVERIFY(days.testFlag(SyncSchedule::Saturday)); QVERIFY(!days.testFlag(SyncSchedule::Sunday)); QCOMPARE(s2.rushEnabled(), true); QCOMPARE(s2.rushInterval(), (unsigned)15); QVERIFY(s2.rushBegin() == QTime(8, 0, 0, 0)); QVERIFY(s2.rushEnd() == QTime(16, 0, 0, 0)); SyncSchedule::Days rushDays = s2.rushDays(); QVERIFY(rushDays.testFlag(SyncSchedule::Monday)); QVERIFY(!rushDays.testFlag(SyncSchedule::Tuesday)); QVERIFY(!rushDays.testFlag(SyncSchedule::Wednesday)); QVERIFY(rushDays.testFlag(SyncSchedule::Thursday)); QVERIFY(rushDays.testFlag(SyncSchedule::Friday)); QVERIFY(!rushDays.testFlag(SyncSchedule::Saturday)); QVERIFY(!rushDays.testFlag(SyncSchedule::Sunday)); // Copy constructor. SyncSchedule s3(s2); QDomDocument doc2; QDomDocument doc3; doc2.appendChild(s2.toXml(doc2)); doc3.appendChild(s3.toXml(doc3)); QVERIFY(doc2.toString().size() >= SCHEDULE_XML.size()); QCOMPARE(doc2.toString(), doc3.toString()); } void SyncScheduleTest::testProperties() { SyncSchedule s; SyncSchedule::Days d; d |= SyncSchedule::Tuesday; d |= SyncSchedule::Saturday; s.setDays(d); QVERIFY(s.days() == d); QTime t(1, 2, 3, 0); s.setTime(t); QVERIFY(s.time() == t); unsigned interval = 20; s.setInterval(interval); QVERIFY(s.interval() == interval); s.setRushEnabled(true); QVERIFY(s.rushEnabled() == true); d |= SyncSchedule::Wednesday; s.setRushDays(d); QVERIFY(s.rushDays() == d); QTime rushBegin(8, 0, 0, 0); QTime rushEnd(16, 0, 0, 0); s.setRushTime(rushBegin, rushEnd); QVERIFY(s.rushBegin() == rushBegin); QVERIFY(s.rushEnd() == rushEnd); unsigned rushInterval = 5; s.setRushInterval(rushInterval); QVERIFY(s.rushInterval() == rushInterval); } void SyncScheduleTest::testNextSyncTime() { QEXPECT_FAIL("", "Implementation of SyncSchedule has changed so that it _currently_ does not " "allow to use the approach originally used by this test case", Abort); QVERIFY(false); const unsigned INTERVAL = 30; const unsigned RUSH_INTERVAL = 10; SyncSchedule s; QDateTime previous(QDate(2009, 10, 7), QTime(11, 0, 0, 0)); QDateTime now(QDate(2009, 10, 7), QTime(12, 0, 0, 0)); // No schedule settings. QVERIFY(s.nextSyncTime(previous).isNull()); // Exact time. QTime exact(15, 0, 0, 0); s.setTime(exact); QDateTime next = s.nextSyncTime(previous); QVERIFY(next.isNull()); SyncSchedule::Days days; days |= SyncSchedule::Wednesday; days |= SyncSchedule::Monday; s.setDays(days); next = s.nextSyncTime(previous); QVERIFY(!next.isNull()); QVERIFY(next.date() == now.date()); QVERIFY(next.time() == exact); now.setTime(QTime(15, 0, 1, 0)); next = s.nextSyncTime(previous); QVERIFY(next.date() == now.date().addDays(5)); QVERIFY(next.time() == exact); // Interval. s.setTime(QTime()); s.setInterval(INTERVAL); next = s.nextSyncTime(previous); QVERIFY(next == previous.addSecs(INTERVAL * 60)); // Interval, no previous sync. next = s.nextSyncTime(QDateTime()); QVERIFY(next == now); // Interval, sync missed. next = s.nextSyncTime(previous); // now = previous + 1h QVERIFY(next == now); // Interval, across day boundary, disabled days in the middle. previous.setTime(QTime(23, 50, 0, 0)); next = s.nextSyncTime(previous); QVERIFY(next.date() == previous.addDays(5).date()); // Disabled days are skipped. QVERIFY(next.time() == QTime(0, 0, 0, 0)); // Sync as soon as day starts. // Rush enabled, no effect. SyncSchedule::Days rushDays; rushDays |= SyncSchedule::Monday; rushDays |= SyncSchedule::Friday; s.setRushDays(rushDays); s.setRushEnabled(true); s.setRushTime(QTime(8, 0, 0, 0), QTime(16, 0, 0, 0)); s.setRushInterval(RUSH_INTERVAL); previous.setTime(QTime(11, 50, 0, 0)); next = s.nextSyncTime(previous); QVERIFY(next == previous.addSecs(INTERVAL * 60)); // No previous sync. s.setInterval(0); next = s.nextSyncTime(QDateTime()); QVERIFY(next == now); s.setInterval(INTERVAL); // Currently in rush, sync missed. now.setDate(QDate(2009, 10, 9)); next = s.nextSyncTime(previous); QVERIFY(next == now); // Currently in rush, previous in rush. now.setTime(QTime(12, 0, 0, 0)); QVERIFY(s.d_ptr->isRush(now)); next = s.nextSyncTime(now); QCOMPARE(next, now.addSecs(RUSH_INTERVAL * 60)); // Currently in rush, next out of rush. now.setTime(QTime(15, 55, 0, 0)); next = s.nextSyncTime(now); QCOMPARE(next.date().dayOfWeek(), (int)Qt::Monday); QCOMPARE(next.time(), QTime(0, 0, 0, 0)); s.setInterval(0); next = s.nextSyncTime(now); QCOMPARE(next.date().dayOfWeek(), (int)Qt::Monday); QCOMPARE(next.time(), s.rushBegin()); // Not currently in rush. now.setTime(QTime(6, 0, 0, 0)); next = s.nextSyncTime(now); QCOMPARE(next.date(), now.date()); QCOMPARE(next.time(), s.rushBegin()); now.setTime(QTime(17, 0, 0, 0)); next = s.nextSyncTime(now); QCOMPARE(next.date().dayOfWeek(), (int)Qt::Monday); QCOMPARE(next.time(), s.rushBegin()); } void SyncScheduleTest::testIsSyncScheduled() { SyncSchedule s; // Exact time. QTime exact(15, 0, 0, 0); s.setTime(exact); QVERIFY(!s.isSyncScheduled(QDateTime(QDate(2019, 8, 27), QTime(15, 0, 0, 0)))); SyncSchedule::Days days; days |= SyncSchedule::Wednesday; days |= SyncSchedule::Monday; s.setDays(days); // This is a Tuesday. QVERIFY(!s.isSyncScheduled(QDateTime(QDate(2019, 8, 27), QTime(15, 0)))); // These are valid within 10 minutes margin QVERIFY(s.isSyncScheduled(QDateTime(QDate(2019, 8, 28), QTime(15, 0)))); QVERIFY(s.isSyncScheduled(QDateTime(QDate(2019, 8, 28), QTime(15, 4, 59)))); QVERIFY(s.isSyncScheduled(QDateTime(QDate(2019, 8, 28), QTime(14, 55, 1)))); // These are invalid QVERIFY(!s.isSyncScheduled(QDateTime(QDate(2019, 8, 28), QTime(15, 5)))); QVERIFY(!s.isSyncScheduled(QDateTime(QDate(2019, 8, 28), QTime(14, 55)))); // Simply enabled, no rush. s.setTime(QTime()); s.setInterval(3600); s.setScheduleEnabled(true); // Any time, any day should match QVERIFY(s.isSyncScheduled(QDateTime(QDate(2019, 8, 25), QTime(15, 0, 0, 0)))); QVERIFY(s.isSyncScheduled(QDateTime(QDate(2019, 8, 28), QTime(5, 0, 0, 0)))); QVERIFY(s.isSyncScheduled(QDateTime(QDate(2019, 8, 29), QTime(21, 0, 0, 0)))); // Rush enabled, in rush time. SyncSchedule::Days rushDays; rushDays |= SyncSchedule::Tuesday; rushDays |= SyncSchedule::Wednesday; s.setRushDays(rushDays); s.setRushEnabled(true); s.setRushTime(QTime(8, 0, 0, 0), QTime(16, 0, 0, 0)); s.setRushInterval(300); QVERIFY(s.isSyncScheduled(QDateTime(QDate(2019, 8, 27), QTime(15, 0, 0, 0)))); QVERIFY(s.isSyncScheduled(QDateTime(QDate(2019, 8, 28), QTime(9, 0, 0, 0)))); // Rush enabled, not in rush time. QVERIFY(s.isSyncScheduled(QDateTime(QDate(2019, 8, 27), QTime(17, 0, 0, 0)))); QVERIFY(s.isSyncScheduled(QDateTime(QDate(2019, 8, 29), QTime(9, 0, 0, 0)))); // Rush enabled, not in rush time, schedule disabled. s.setScheduleEnabled(false); QVERIFY(!s.isSyncScheduled(QDateTime(QDate(2019, 8, 27), QTime(17, 0, 0, 0)))); QVERIFY(!s.isSyncScheduled(QDateTime(QDate(2019, 8, 29), QTime(9, 0, 0, 0)))); } QTEST_MAIN(Buteo::SyncScheduleTest) buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/SyncScheduleTest/SyncScheduleTest.h000066400000000000000000000022421477124122200317600ustar00rootroot00000000000000/* * This file is part of buteo-syncfw package * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * Contact: Sateesh Kavuri * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * 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 Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #ifndef SYNCSCHEDULETEST_H #define SYNCSCHEDULETEST_H #include namespace Buteo { class SyncScheduleTest: public QObject { Q_OBJECT private slots: void testConstruction(); void testProperties(); void testNextSyncTime(); void testIsSyncScheduled(); }; } #endif // SYNCSCHEDULETEST_H buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/SyncScheduleTest/SyncScheduleTest.pro000066400000000000000000000000431477124122200323260ustar00rootroot00000000000000include(../../testapplication.pri) buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/syncprofiletests.pro000066400000000000000000000027231477124122200272730ustar00rootroot00000000000000include(../tests_common.pri) TEMPLATE = subdirs SUBDIRS = \ ProfileFactoryTest \ ProfileFieldTest \ ProfileManagerTest \ ProfileTest \ StorageProfileTest \ SyncLogTest \ SyncProfileTest \ SyncScheduleTest \ testprofiles_client.files = testprofiles/user/client/* testprofiles_client.path = $${INSTALL_TESTDIR}/syncprofiletests/testprofiles/user/client/ testprofiles_storage.files = testprofiles/user/storage/* testprofiles_storage.path = $${INSTALL_TESTDIR}/syncprofiletests/testprofiles/user/storage/ testprofiles_sync.files = testprofiles/user/sync/* testprofiles_sync.path = $${INSTALL_TESTDIR}/syncprofiletests/testprofiles/user/sync/ testprofiles_syssync.files = testprofiles/system/sync/* testprofiles_syssync.path = $${INSTALL_TESTDIR}/syncprofiletests/testprofiles/system/sync/ testprofiles_expstorage.files = testprofiles/expected/storage/* testprofiles_expstorage.path = $${INSTALL_TESTDIR}/syncprofiletests/testprofiles/expected/storage/ testprofiles_expsync.files = testprofiles/expected/sync/* testprofiles_expsync.path = $${INSTALL_TESTDIR}/syncprofiletests/testprofiles/expected/sync/ testaccount_service.files = testprofiles/testsync-ovi.service testaccount_service.path = /usr/share/accounts/services/ INSTALLS += \ testaccount_service \ testprofiles_client \ testprofiles_storage \ testprofiles_sync \ testprofiles_syssync \ testprofiles_expstorage \ testprofiles_expsync \ buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/testprofiles/000077500000000000000000000000001477124122200256505ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/testprofiles/expected/000077500000000000000000000000001477124122200274515ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/testprofiles/expected/storage/000077500000000000000000000000001477124122200311155ustar00rootroot00000000000000hcalendar-expected.xml000066400000000000000000000006621477124122200353040ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/testprofiles/expected/storage buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/testprofiles/expected/sync/000077500000000000000000000000001477124122200304255ustar00rootroot00000000000000ovi-calendar-expected.xml000066400000000000000000000004241477124122200352330ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/testprofiles/expected/sync ovi-calendar-merged-expected.xml000066400000000000000000000004241477124122200364740ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/testprofiles/expected/sync buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/testprofiles/system/000077500000000000000000000000001477124122200271745ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/testprofiles/system/sync/000077500000000000000000000000001477124122200301505ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/testprofiles/system/sync/corrupted.xml000066400000000000000000000007771477124122200327140ustar00rootroot00000000000000 buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/testprofiles/testsync-ovi.service000066400000000000000000000007341477124122200317050ustar00rootroot00000000000000 Sync ovi OviSync icon_syncl buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/testprofiles/user/000077500000000000000000000000001477124122200266265ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/testprofiles/user/client/000077500000000000000000000000001477124122200301045ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/testprofiles/user/client/syncml.xml000066400000000000000000000003271477124122200321350ustar00rootroot00000000000000 buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/testprofiles/user/storage/000077500000000000000000000000001477124122200302725ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/testprofiles/user/storage/hcalendar.xml000066400000000000000000000005731477124122200327420ustar00rootroot00000000000000 buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/testprofiles/user/sync/000077500000000000000000000000001477124122200276025ustar00rootroot00000000000000buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/testprofiles/user/sync/ovi-calendar.xml000066400000000000000000000021301477124122200326640ustar00rootroot00000000000000 buteo-syncfw-0.11.10/unittests/tests/syncprofiletests/testprofiles/user/sync/testsync-ovi.xml000066400000000000000000000020161477124122200327720ustar00rootroot00000000000000 buteo-syncfw-0.11.10/unittests/tests/testapplication.pri000066400000000000000000000010261477124122200234230ustar00rootroot00000000000000include(tests_common.pri) pro_file_basename = $$basename(_PRO_FILE_) pro_file_basename ~= s/\\.pro$// TEMPLATE = app TARGET = $${pro_file_basename} HEADERS = $${pro_file_basename}.h SOURCES = $${pro_file_basename}.cpp target.path = $${INSTALL_TESTDIR}/$${tests_subdir} INSTALLS += target #check.depends = all #check.commands = '\ # cd "$${PWD}" \ # && export LD_LIBRARY_PATH="$${OUT_PWD}/$${tests_subdir_r}/../src:\$\${LD_LIBRARY_PATH}" \ # && $${OUT_PWD}/$${TARGET}' #check.CONFIG = phony #QMAKE_EXTRA_TARGETS += check buteo-syncfw-0.11.10/unittests/tests/tests.pro000066400000000000000000000005661477124122200214000ustar00rootroot00000000000000include(tests_common.pri) TEMPLATE = subdirs SUBDIRS = \ msyncdtests \ pluginmanagertests \ syncfwclienttests \ syncprofiletests \ # install testwrapper.files = runstarget.sh testwrapper.path = $${INSTALL_TESTDIR} INSTALLS += testwrapper testdefinition.files = tests.xml testdefinition.path = $${INSTALL_TESTDIR} INSTALLS += testdefinition buteo-syncfw-0.11.10/unittests/tests/tests.xml000066400000000000000000000127131477124122200213750ustar00rootroot00000000000000 systemctl --user stop msyncd systemctl --user start msyncd /opt/tests/buteo-syncfw/runstarget.sh msyncdtests/AccountsHelperTest /opt/tests/buteo-syncfw/runstarget.sh msyncdtests/ClientPluginRunnerTest /opt/tests/buteo-syncfw/runstarget.sh msyncdtests/ClientThreadTest /opt/tests/buteo-syncfw/runstarget.sh msyncdtests/PluginRunnerTest /opt/tests/buteo-syncfw/runstarget.sh msyncdtests/ServerActivatorTest /opt/tests/buteo-syncfw/runstarget.sh msyncdtests/ServerPluginRunnerTest /opt/tests/buteo-syncfw/runstarget.sh msyncdtests/ServerThreadTest /opt/tests/buteo-syncfw/runstarget.sh msyncdtests/StorageBookerTest /opt/tests/buteo-syncfw/runstarget.sh msyncdtests/SyncBackupTest /opt/tests/buteo-syncfw/runstarget.sh msyncdtests/SyncQueueTest /opt/tests/buteo-syncfw/runstarget.sh msyncdtests/SyncSessionTest /opt/tests/buteo-syncfw/runstarget.sh msyncdtests/SyncSigHandlerTest /opt/tests/buteo-syncfw/runstarget.sh msyncdtests/SynchronizerTest /opt/tests/buteo-syncfw/runstarget.sh msyncdtests/TransportTrackerTest /opt/tests/buteo-syncfw/runstarget.sh pluginmanagertests/ClientPluginTest /opt/tests/buteo-syncfw/runstarget.sh pluginmanagertests/DeletedItemsIdStorageTest /opt/tests/buteo-syncfw/runstarget.sh pluginmanagertests/ServerPluginTest /opt/tests/buteo-syncfw/runstarget.sh pluginmanagertests/StoragePluginTest /opt/tests/buteo-syncfw/runstarget.sh syncprofiletests/ProfileFactoryTest /opt/tests/buteo-syncfw/runstarget.sh syncprofiletests/ProfileFieldTest /opt/tests/buteo-syncfw/runstarget.sh syncprofiletests/ProfileManagerTest /opt/tests/buteo-syncfw/runstarget.sh syncprofiletests/ProfileTest /opt/tests/buteo-syncfw/runstarget.sh syncprofiletests/StorageProfileTest /opt/tests/buteo-syncfw/runstarget.sh syncprofiletests/SyncLogTest /opt/tests/buteo-syncfw/runstarget.sh syncprofiletests/SyncProfileTest /opt/tests/buteo-syncfw/runstarget.sh syncprofiletests/SyncScheduleTest buteo-syncfw-0.11.10/unittests/tests/tests_common.pri000066400000000000000000000023771477124122200227440ustar00rootroot00000000000000isEmpty(TESTS_COMMON_PRI_INCLUDED) { TESTS_COMMON_PRI_INCLUDED = 1 tests_subdir = $$relative_path($$dirname(_PRO_FILE_), $${PWD}) tests_subdir_r = $$relative_path($${PWD}, $$dirname(_PRO_FILE_)) QT += testlib \ core \ dbus \ xml \ dbus \ network \ sql QT -= gui CONFIG += link_pkgconfig link_prl PKGCONFIG += dbus-1 LIBS += -L$${OUT_PWD}/$${tests_subdir_r}/../../libbuteosyncfw PKGCONFIG += libsignon-qt5 accounts-qt5 LIBS += -lbuteosyncfw5 # This is needed to avoid adding the /usr/lib link directory before the # newer version in the present directories QMAKE_LIBDIR_QT = $${OUT_PWD}/$${tests_subdir_r}/../../libbuteosyncfw DEFINES += SYNCFW_UNIT_TESTS INCLUDEPATH = \ $${PWD} \ $${PWD}/../.. \ $${PWD}/../../libbuteosyncfw/clientfw \ $${PWD}/../../libbuteosyncfw/common \ $${PWD}/../../libbuteosyncfw/pluginmgr \ $${PWD}/../../libbuteosyncfw/profile \ $${PWD}/../../msyncd \ CONFIG -= depend_includepath DEPENDPATH = \ $${PWD}/../../libbuteosyncfw/clientfw \ $${PWD}/../../libbuteosyncfw/common \ $${PWD}/../../libbuteosyncfw/pluginmgr \ $${PWD}/../../libbuteosyncfw/profile \ $${PWD}/../../msyncd \ INSTALL_TESTDIR = /opt/tests/buteo-syncfw INSTALL_TESTDATADIR = $${INSTALL_TESTDIR}/data } buteo-syncfw-0.11.10/unittests/unittests.pro000066400000000000000000000000761477124122200211320ustar00rootroot00000000000000TEMPLATE = subdirs SUBDIRS += dummyplugins \ tests