From dddc200f9ba591b48139f9986b0d1c1963454cc1 Mon Sep 17 00:00:00 2001 From: Thomas Dohmke Date: Thu, 3 Nov 2011 20:32:15 +0100 Subject: [PATCH] Initial revision with new CNSHockeyManager. --- Classes/.DS_Store | Bin 0 -> 6148 bytes Classes/BWApp.h | 56 + Classes/BWApp.m | 186 +++ Classes/BWGlobal.h | 116 ++ Classes/BWGlobal.m | 53 + Classes/BWHockeyManager.h | 227 ++++ Classes/BWHockeyManager.m | 1152 +++++++++++++++++ Classes/BWHockeySettingsViewController.h | 22 + Classes/BWHockeySettingsViewController.m | 294 +++++ Classes/BWHockeyViewController.h | 65 + Classes/BWHockeyViewController.m | 626 +++++++++ Classes/BWQuincyManager.h | 221 ++++ Classes/BWQuincyManager.m | 772 +++++++++++ Classes/CNSHockeyManager.h | 29 + Classes/CNSHockeyManager.m | 111 ++ Classes/NSString+HockeyAdditions.h | 33 + Classes/NSString+HockeyAdditions.m | 50 + Classes/PSAppStoreHeader.h | 42 + Classes/PSAppStoreHeader.m | 173 +++ Classes/PSStoreButton.h | 81 ++ Classes/PSStoreButton.m | 295 +++++ Classes/PSWebTableViewCell.h | 44 + Classes/PSWebTableViewCell.m | 189 +++ Classes/UIImage+HockeyAdditions.h | 38 + Classes/UIImage+HockeyAdditions.m | 346 +++++ LICENSE.txt | 81 ++ Resources/.DS_Store | Bin 0 -> 6148 bytes Resources/Hockey.bundle/IconGradient.png | Bin 0 -> 2790 bytes Resources/Hockey.bundle/IconGradient@2x.png | Bin 0 -> 3628 bytes Resources/Hockey.bundle/authorize_denied.png | Bin 0 -> 2340 bytes .../Hockey.bundle/authorize_denied@2x.png | Bin 0 -> 4384 bytes Resources/Hockey.bundle/authorize_request.png | Bin 0 -> 2448 bytes .../Hockey.bundle/authorize_request@2x.png | Bin 0 -> 4793 bytes Resources/Hockey.bundle/bg.png | Bin 0 -> 1576 bytes Resources/Hockey.bundle/buttonHighlight.png | Bin 0 -> 1021 bytes .../Hockey.bundle/buttonHighlight@2x.png | Bin 0 -> 1101 bytes .../Hockey.bundle/de.lproj/Hockey.strings | 86 ++ .../Hockey.bundle/en.lproj/Hockey.strings | 86 ++ Resources/Hockey.bundle/gear.png | Bin 0 -> 521 bytes Resources/Hockey.bundle/gear@2x.png | Bin 0 -> 911 bytes .../Hockey.bundle/it.lproj/Hockey.strings | 86 ++ .../Hockey.bundle/sv.lproj/Hockey.strings | 87 ++ .../Quincy.bundle/de.lproj/Quincy.strings | Bin 0 -> 4416 bytes .../de.lproj/QuincyAlternate.strings | Bin 0 -> 4196 bytes .../Quincy.bundle/en.lproj/Quincy.strings | Bin 0 -> 4138 bytes .../en.lproj/QuincyAlternate.strings | Bin 0 -> 3818 bytes .../Quincy.bundle/es.lproj/Quincy.strings | Bin 0 -> 4280 bytes .../es.lproj/QuincyAlternate.strings | Bin 0 -> 4278 bytes .../Quincy.bundle/fr.lproj/Quincy.strings | Bin 0 -> 4528 bytes .../fr.lproj/QuincyAlternate.strings | Bin 0 -> 4518 bytes .../Quincy.bundle/it.lproj/Quincy.strings | Bin 0 -> 4338 bytes .../it.lproj/QuincyAlternate.strings | Bin 0 -> 4322 bytes .../Quincy.bundle/ja.lproj/Quincy.strings | Bin 0 -> 3700 bytes .../ja.lproj/QuincyAlternate.strings | Bin 0 -> 3684 bytes .../Quincy.bundle/nl.lproj/Quincy.strings | Bin 0 -> 4222 bytes .../nl.lproj/QuincyAlternate.strings | Bin 0 -> 4206 bytes .../Quincy.bundle/pt-PT.lproj/Quincy.strings | Bin 0 -> 4228 bytes .../pt-PT.lproj/QuincyAlternate.strings | Bin 0 -> 4212 bytes .../Quincy.bundle/pt.lproj/Quincy.strings | Bin 0 -> 4188 bytes .../pt.lproj/QuincyAlternate.strings | Bin 0 -> 4172 bytes .../Quincy.bundle/ru.lproj/Quincy.strings | Bin 0 -> 4056 bytes .../ru.lproj/QuincyAlternate.strings | Bin 0 -> 4078 bytes .../Quincy.bundle/tr.lproj/Quincy.strings | Bin 0 -> 4242 bytes .../tr.lproj/QuincyAlternate.strings | Bin 0 -> 4240 bytes .../Quincy.bundle/zh_CN.lproj/Quincy.strings | Bin 0 -> 3486 bytes .../zh_CN.lproj/QuincyAlternate.strings | Bin 0 -> 3506 bytes .../Quincy.bundle/zh_TW.lproj/Quincy.strings | Bin 0 -> 3508 bytes .../zh_TW.lproj/QuincyAlternate.strings | Bin 0 -> 3504 bytes Vendor/CrashReporter.framework/CrashReporter | 1 + Vendor/CrashReporter.framework/Headers | 1 + Vendor/CrashReporter.framework/Resources | 1 + .../Versions/A/CrashReporter | Bin 0 -> 424964 bytes .../Versions/A/Headers/CrashReporter.h | 229 ++++ .../Versions/A/Headers/PLCrashReport.h | 169 +++ .../A/Headers/PLCrashReportApplicationInfo.h | 53 + .../A/Headers/PLCrashReportBinaryImageInfo.h | 90 ++ .../A/Headers/PLCrashReportExceptionInfo.h | 65 + .../A/Headers/PLCrashReportFormatter.h | 51 + .../A/Headers/PLCrashReportMachineInfo.h | 73 ++ .../A/Headers/PLCrashReportProcessInfo.h | 92 ++ .../A/Headers/PLCrashReportProcessorInfo.h | 74 ++ .../A/Headers/PLCrashReportSignalInfo.h | 60 + .../A/Headers/PLCrashReportSystemInfo.h | 142 ++ .../A/Headers/PLCrashReportTextFormatter.h | 62 + .../A/Headers/PLCrashReportThreadInfo.h | 114 ++ .../Versions/A/Headers/PLCrashReporter.h | 97 ++ .../Versions/A/Resources/Info.plist | 38 + .../CrashReporter.framework/Versions/Current | 1 + 88 files changed, 7060 insertions(+) create mode 100644 Classes/.DS_Store create mode 100644 Classes/BWApp.h create mode 100644 Classes/BWApp.m create mode 100644 Classes/BWGlobal.h create mode 100644 Classes/BWGlobal.m create mode 100644 Classes/BWHockeyManager.h create mode 100644 Classes/BWHockeyManager.m create mode 100644 Classes/BWHockeySettingsViewController.h create mode 100644 Classes/BWHockeySettingsViewController.m create mode 100644 Classes/BWHockeyViewController.h create mode 100644 Classes/BWHockeyViewController.m create mode 100755 Classes/BWQuincyManager.h create mode 100755 Classes/BWQuincyManager.m create mode 100644 Classes/CNSHockeyManager.h create mode 100644 Classes/CNSHockeyManager.m create mode 100644 Classes/NSString+HockeyAdditions.h create mode 100644 Classes/NSString+HockeyAdditions.m create mode 100644 Classes/PSAppStoreHeader.h create mode 100644 Classes/PSAppStoreHeader.m create mode 100644 Classes/PSStoreButton.h create mode 100644 Classes/PSStoreButton.m create mode 100644 Classes/PSWebTableViewCell.h create mode 100644 Classes/PSWebTableViewCell.m create mode 100644 Classes/UIImage+HockeyAdditions.h create mode 100644 Classes/UIImage+HockeyAdditions.m create mode 100755 LICENSE.txt create mode 100644 Resources/.DS_Store create mode 100644 Resources/Hockey.bundle/IconGradient.png create mode 100644 Resources/Hockey.bundle/IconGradient@2x.png create mode 100644 Resources/Hockey.bundle/authorize_denied.png create mode 100644 Resources/Hockey.bundle/authorize_denied@2x.png create mode 100644 Resources/Hockey.bundle/authorize_request.png create mode 100644 Resources/Hockey.bundle/authorize_request@2x.png create mode 100644 Resources/Hockey.bundle/bg.png create mode 100644 Resources/Hockey.bundle/buttonHighlight.png create mode 100644 Resources/Hockey.bundle/buttonHighlight@2x.png create mode 100644 Resources/Hockey.bundle/de.lproj/Hockey.strings create mode 100644 Resources/Hockey.bundle/en.lproj/Hockey.strings create mode 100644 Resources/Hockey.bundle/gear.png create mode 100644 Resources/Hockey.bundle/gear@2x.png create mode 100755 Resources/Hockey.bundle/it.lproj/Hockey.strings create mode 100644 Resources/Hockey.bundle/sv.lproj/Hockey.strings create mode 100755 Resources/Quincy.bundle/de.lproj/Quincy.strings create mode 100755 Resources/Quincy.bundle/de.lproj/QuincyAlternate.strings create mode 100755 Resources/Quincy.bundle/en.lproj/Quincy.strings create mode 100755 Resources/Quincy.bundle/en.lproj/QuincyAlternate.strings create mode 100755 Resources/Quincy.bundle/es.lproj/Quincy.strings create mode 100755 Resources/Quincy.bundle/es.lproj/QuincyAlternate.strings create mode 100755 Resources/Quincy.bundle/fr.lproj/Quincy.strings create mode 100755 Resources/Quincy.bundle/fr.lproj/QuincyAlternate.strings create mode 100755 Resources/Quincy.bundle/it.lproj/Quincy.strings create mode 100755 Resources/Quincy.bundle/it.lproj/QuincyAlternate.strings create mode 100755 Resources/Quincy.bundle/ja.lproj/Quincy.strings create mode 100755 Resources/Quincy.bundle/ja.lproj/QuincyAlternate.strings create mode 100755 Resources/Quincy.bundle/nl.lproj/Quincy.strings create mode 100755 Resources/Quincy.bundle/nl.lproj/QuincyAlternate.strings create mode 100755 Resources/Quincy.bundle/pt-PT.lproj/Quincy.strings create mode 100755 Resources/Quincy.bundle/pt-PT.lproj/QuincyAlternate.strings create mode 100755 Resources/Quincy.bundle/pt.lproj/Quincy.strings create mode 100755 Resources/Quincy.bundle/pt.lproj/QuincyAlternate.strings create mode 100755 Resources/Quincy.bundle/ru.lproj/Quincy.strings create mode 100755 Resources/Quincy.bundle/ru.lproj/QuincyAlternate.strings create mode 100644 Resources/Quincy.bundle/tr.lproj/Quincy.strings create mode 100644 Resources/Quincy.bundle/tr.lproj/QuincyAlternate.strings create mode 100644 Resources/Quincy.bundle/zh_CN.lproj/Quincy.strings create mode 100644 Resources/Quincy.bundle/zh_CN.lproj/QuincyAlternate.strings create mode 100644 Resources/Quincy.bundle/zh_TW.lproj/Quincy.strings create mode 100644 Resources/Quincy.bundle/zh_TW.lproj/QuincyAlternate.strings create mode 120000 Vendor/CrashReporter.framework/CrashReporter create mode 120000 Vendor/CrashReporter.framework/Headers create mode 120000 Vendor/CrashReporter.framework/Resources create mode 100644 Vendor/CrashReporter.framework/Versions/A/CrashReporter create mode 100644 Vendor/CrashReporter.framework/Versions/A/Headers/CrashReporter.h create mode 100644 Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReport.h create mode 100644 Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportApplicationInfo.h create mode 100644 Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportBinaryImageInfo.h create mode 100644 Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportExceptionInfo.h create mode 100644 Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportFormatter.h create mode 100644 Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportMachineInfo.h create mode 100644 Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportProcessInfo.h create mode 100644 Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportProcessorInfo.h create mode 100644 Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportSignalInfo.h create mode 100644 Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportSystemInfo.h create mode 100644 Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportTextFormatter.h create mode 100644 Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportThreadInfo.h create mode 100644 Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReporter.h create mode 100644 Vendor/CrashReporter.framework/Versions/A/Resources/Info.plist create mode 120000 Vendor/CrashReporter.framework/Versions/Current diff --git a/Classes/.DS_Store b/Classes/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 + +@interface BWApp : NSObject { + NSString *name_; + NSString *version_; + NSString *shortVersion_; + NSString *notes_; + NSDate *date_; + NSNumber *size_; + NSNumber *mandatory_; +} +@property (nonatomic, copy) NSString *name; +@property (nonatomic, copy) NSString *version; +@property (nonatomic, copy) NSString *shortVersion; +@property (nonatomic, copy) NSString *notes; +@property (nonatomic, copy) NSDate *date; +@property (nonatomic, copy) NSNumber *size; +@property (nonatomic, copy) NSNumber *mandatory; + +- (NSString *)nameAndVersionString; +- (NSString *)versionString; +- (NSString *)dateString; +- (NSString *)sizeInMB; +- (NSString *)notesOrEmptyString; +- (void)setDateWithTimestamp:(NSTimeInterval)timestamp; +- (BOOL)isValid; +- (BOOL)isEqualToBWApp:(BWApp *)anApp; + ++ (BWApp *)appFromDict:(NSDictionary *)dict; + +@end diff --git a/Classes/BWApp.m b/Classes/BWApp.m new file mode 100644 index 0000000000..598a370d7c --- /dev/null +++ b/Classes/BWApp.m @@ -0,0 +1,186 @@ +// +// BWApp.m +// HockeyDemo +// +// Created by Peter Steinberger on 04.02.11. +// Copyright 2011 Buzzworks. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "BWApp.h" +#import "BWGlobal.h" + +@implementation BWApp + +@synthesize name = name_; +@synthesize version = version_; +@synthesize shortVersion = shortVersion_; +@synthesize notes = notes_; +@synthesize date = date_; +@synthesize size = size_; +@synthesize mandatory = mandatory_; + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark static + ++ (BWApp *)appFromDict:(NSDictionary *)dict { + BWApp *app = [[[[self class] alloc] init] autorelease]; + + // NSParameterAssert([dict isKindOfClass:[NSDictionary class]]); + if ([dict isKindOfClass:[NSDictionary class]]) { + app.name = [dict objectForKey:@"title"]; + app.version = [dict objectForKey:@"version"]; + app.shortVersion = [dict objectForKey:@"shortversion"]; + [app setDateWithTimestamp:[[dict objectForKey:@"timestamp"] doubleValue]]; + app.size = [dict objectForKey:@"appsize"]; + app.notes = [dict objectForKey:@"notes"]; + app.mandatory = [dict objectForKey:@"mandatory"]; + } + + return app; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark NSObject + +- (void)dealloc { + [name_ release]; + [version_ release]; + [shortVersion_ release]; + [notes_ release]; + [date_ release]; + [size_ release]; + [mandatory_ release]; + + [super dealloc]; +} + +- (BOOL)isEqual:(id)other { + if (other == self) + return YES; + if (!other || ![other isKindOfClass:[self class]]) + return NO; + return [self isEqualToBWApp:other]; +} + +- (BOOL)isEqualToBWApp:(BWApp *)anApp { + if (self == anApp) + return YES; + if (self.name != anApp.name && ![self.name isEqualToString:anApp.name]) + return NO; + if (self.version != anApp.version && ![self.version isEqualToString:anApp.version]) + return NO; + if (self.shortVersion != anApp.shortVersion && ![self.shortVersion isEqualToString:anApp.shortVersion]) + return NO; + if (self.notes != anApp.notes && ![self.notes isEqualToString:anApp.notes]) + return NO; + if (self.date != anApp.date && ![self.date isEqualToDate:anApp.date]) + return NO; + if (self.size != anApp.size && ![self.size isEqualToNumber:anApp.size]) + return NO; + if (self.mandatory != anApp.mandatory && ![self.mandatory isEqualToNumber:anApp.mandatory]) + return NO; + return YES; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark NSCoder + +- (void)encodeWithCoder:(NSCoder *)encoder { + [encoder encodeObject:self.name forKey:@"name"]; + [encoder encodeObject:self.version forKey:@"version"]; + [encoder encodeObject:self.shortVersion forKey:@"shortVersion"]; + [encoder encodeObject:self.notes forKey:@"notes"]; + [encoder encodeObject:self.date forKey:@"date"]; + [encoder encodeObject:self.size forKey:@"size"]; + [encoder encodeObject:self.mandatory forKey:@"mandatory"]; +} + +- (id)initWithCoder:(NSCoder *)decoder { + if ((self = [super init])) { + self.name = [decoder decodeObjectForKey:@"name"]; + self.version = [decoder decodeObjectForKey:@"version"]; + self.shortVersion = [decoder decodeObjectForKey:@"shortVersion"]; + self.notes = [decoder decodeObjectForKey:@"notes"]; + self.date = [decoder decodeObjectForKey:@"date"]; + self.size = [decoder decodeObjectForKey:@"size"]; + self.mandatory = [decoder decodeObjectForKey:@"mandatory"]; + } + return self; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark Properties + +- (NSString *)nameAndVersionString { + NSString *appNameAndVersion = [NSString stringWithFormat:@"%@ %@", self.name, [self versionString]]; + return appNameAndVersion; +} + +- (NSString *)versionString { + NSString *shortString = ([self.shortVersion respondsToSelector:@selector(length)] && [self.shortVersion length]) ? [NSString stringWithFormat:@"%@", self.shortVersion] : @""; + NSString *versionString = [shortString length] ? [NSString stringWithFormat:@" (%@)", self.version] : self.version; + return [NSString stringWithFormat:@"%@ %@%@", BWHockeyLocalize(@"HockeyVersion"), shortString, versionString]; +} + +- (NSString *)dateString { + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setDateStyle:NSDateFormatterMediumStyle]; + + return [formatter stringFromDate:self.date]; +} + +- (NSString *)sizeInMB { + if ([[size_ class] isKindOfClass: [NSNumber class]] && [size_ doubleValue] > 0) { + double appSizeInMB = [size_ doubleValue]/(1024*1024); + NSString *appSizeString = [NSString stringWithFormat:@"%.1f MB", appSizeInMB]; + return appSizeString; + } + + return @"0 MB"; +} + +- (void)setDateWithTimestamp:(NSTimeInterval)timestamp { + if (timestamp) { + NSDate *appDate = [NSDate dateWithTimeIntervalSince1970:timestamp]; + self.date = appDate; + } else { + self.date = nil; + } +} + +- (NSString *)notesOrEmptyString { + if (self.notes) { + return self.notes; + }else { + return [NSString string]; + } +} + +// a valid app needs at least following properties: name, version, date +- (BOOL)isValid { + BOOL valid = [self.name length] && [self.version length] && self.date; + return valid; +} + +@end diff --git a/Classes/BWGlobal.h b/Classes/BWGlobal.h new file mode 100644 index 0000000000..5316ff352c --- /dev/null +++ b/Classes/BWGlobal.h @@ -0,0 +1,116 @@ +// +// BWGlobal.h +// +// Created by Andreas Linde on 08/17/10. +// Copyright 2010-2011 Andreas Linde, Peter Steinberger. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "BWHockeyManager.h" +#import "BWApp.h" + +#define HOCKEYKIT_VERSION_MAJOR 2 +#define HOCKEYKIT_VERSION_MINOR 0 + +// uncomment this line to enable NSLog-debugging output +//#define kHockeyDebugEnabled + +#define kArrayOfLastHockeyCheck @"ArrayOfLastHockeyCheck" +#define kDateOfLastHockeyCheck @"DateOfLastHockeyCheck" +#define kDateOfVersionInstallation @"DateOfVersionInstallation" +#define kUsageTimeOfCurrentVersion @"UsageTimeOfCurrentVersion" +#define kUsageTimeForVersionString @"kUsageTimeForVersionString" +#define kHockeyAutoUpdateSetting @"HockeyAutoUpdateSetting" +#define kHockeyAllowUserSetting @"HockeyAllowUserSetting" +#define kHockeyAllowUsageSetting @"HockeyAllowUsageSetting" +#define kHockeyAutoUpdateSetting @"HockeyAutoUpdateSetting" +#define kHockeyAuthorizedVersion @"HockeyAuthorizedVersion" +#define kHockeyAuthorizedToken @"HockeyAuthorizedToken" + +#define kHockeyBundleName @"Hockey.bundle" + +// Notification message which HockeyManager is listening to, to retry requesting updated from the server +#define BWHockeyNetworkBecomeReachable @"NetworkDidBecomeReachable" + +#define BWHockeyLog(fmt, ...) do { if([BWHockeyManager sharedHockeyManager].isLoggingEnabled) { NSLog((@"[HockeyLib] %s/%d " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__); }} while(0) + +NSBundle *hockeyBundle(void); +NSString *BWmd5(NSString *str); + +#define BWHockeyLocalize(StringToken) NSLocalizedStringFromTableInBundle(StringToken, @"Hockey", hockeyBundle(), @"") + + +// compatibility helper +#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_3_2 +#define kCFCoreFoundationVersionNumber_iPhoneOS_3_2 478.61 +#endif +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 32000 +#define BW_IF_3_2_OR_GREATER(...) \ +if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_3_2) \ +{ \ +__VA_ARGS__ \ +} +#else +#define BW_IF_3_2_OR_GREATER(...) +#endif +#define BW_IF_PRE_3_2(...) \ +if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iPhoneOS_3_2) \ +{ \ +__VA_ARGS__ \ +} + +#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_4_0 +#define kCFCoreFoundationVersionNumber_iPhoneOS_4_0 550.32 +#endif +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000 +#define BW_IF_IOS4_OR_GREATER(...) \ +if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_4_0) \ +{ \ +__VA_ARGS__ \ +} +#else +#define BW_IF_IOS4_OR_GREATER(...) +#endif + +#define BW_IF_PRE_IOS4(...) \ +if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iPhoneOS_4_0) \ +{ \ +__VA_ARGS__ \ +} + + + +#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_5_0 +#define kCFCoreFoundationVersionNumber_iPhoneOS_5_0 674.0 +#endif +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 +#define BW_IF_IOS5_OR_GREATER(...) \ +if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_5_0) \ +{ \ +__VA_ARGS__ \ +} +#else +#define BW_IF_IOS5_OR_GREATER(...) +#endif + +#define BW_IF_PRE_IOS5(...) \ +if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iPhoneOS_5_0) \ +{ \ +__VA_ARGS__ \ +} diff --git a/Classes/BWGlobal.m b/Classes/BWGlobal.m new file mode 100644 index 0000000000..566bc1a4ec --- /dev/null +++ b/Classes/BWGlobal.m @@ -0,0 +1,53 @@ +// +// BWGlobal.h +// +// Created by Andreas Linde on 08/17/10. +// Copyright 2010-2011 Andreas Linde, Peter Steinberger. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "BWGlobal.h" +#include + +NSBundle *hockeyBundle(void) { + static NSBundle* bundle = nil; + if (!bundle) { + NSString* path = [[[NSBundle mainBundle] resourcePath] + stringByAppendingPathComponent:kHockeyBundleName]; + bundle = [[NSBundle bundleWithPath:path] retain]; + } + return bundle; +} + +NSString *BWmd5(NSString *str) { + const char *cStr = [str UTF8String]; + unsigned char result[CC_MD5_DIGEST_LENGTH]; + CC_MD5( cStr, strlen(cStr), result ); + return [NSString + stringWithFormat: @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", + result[0], result[1], + result[2], result[3], + result[4], result[5], + result[6], result[7], + result[8], result[9], + result[10], result[11], + result[12], result[13], + result[14], result[15] + ]; +} diff --git a/Classes/BWHockeyManager.h b/Classes/BWHockeyManager.h new file mode 100644 index 0000000000..ce76e4b31c --- /dev/null +++ b/Classes/BWHockeyManager.h @@ -0,0 +1,227 @@ +// +// BWHockeyManager.h +// +// Created by Andreas Linde on 8/17/10. +// Copyright 2010-2011 Andreas Linde. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + + +#import +#import "BWHockeyViewController.h" + +typedef enum { + HockeyComparisonResultDifferent, + HockeyComparisonResultGreater +} HockeyComparisonResult; + +typedef enum { + HockeyAuthorizationDenied, + HockeyAuthorizationAllowed, + HockeyAuthorizationPending +} HockeyAuthorizationState; + +typedef enum { + HockeyUpdateCheckStartup = 0, + HockeyUpdateCheckDaily = 1, + HockeyUpdateCheckManually = 2 +} HockeyUpdateSetting; + +@protocol BWHockeyManagerDelegate; + +@class BWApp; + +@interface BWHockeyManager : NSObject { + id delegate_; + NSArray *apps_; + + NSString *updateURL_; + NSString *appIdentifier_; + NSString *currentAppVersion_; + + UINavigationController *navController_; + BWHockeyViewController *currentHockeyViewController_; + UIView *authorizeView_; + + NSMutableData *receivedData_; + + BOOL loggingEnabled_; + BOOL checkInProgress_; + BOOL dataFound; + BOOL updateAvailable_; + BOOL showFeedback_; + BOOL updateURLOffline_; + BOOL updateAlertShowing_; + BOOL lastCheckFailed_; + + NSURLConnection *urlConnection_; + NSDate *lastCheck_; + NSDate *usageStartTimestamp_; + + BOOL sendUserData_; + BOOL sendUsageTime_; + BOOL allowUserToDisableSendData_; + BOOL userAllowsSendUserData_; + BOOL userAllowsSendUsageTime_; + BOOL showUpdateReminder_; + BOOL checkForUpdateOnLaunch_; + HockeyComparisonResult compareVersionType_; + HockeyUpdateSetting updateSetting_; + BOOL showUserSettings_; + BOOL showDirectInstallOption_; + + BOOL requireAuthorization_; + NSString *authenticationSecret_; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// this is a singleton ++ (BWHockeyManager *)sharedHockeyManager; + +// update url needs to be set +@property (nonatomic, retain) NSString *updateURL; + +// private app identifier (optional) +@property (nonatomic, retain) NSString *appIdentifier; + +// delegate is optional +@property (nonatomic, assign) id delegate; + +// hockey secret is required if authentication is used +@property (nonatomic, retain) NSString *authenticationSecret; + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// settings + +// if YES, the API will return an existing JMC config +// if NO, the API will return only version information +@property (nonatomic, assign) BOOL checkForTracker; + +@property (nonatomic, retain, readonly) NSDictionary *trackerConfig; + +// if YES, states will be logged using NSLog. Only enable this for debugging! +// if NO, nothing will be logged. (default) +@property (nonatomic, assign, getter=isLoggingEnabled) BOOL loggingEnabled; + +// if YES, the current user data is send: device type, iOS version, app version, UDID (default) +// if NO, no such data is send to the server +@property (nonatomic, assign, getter=shouldSendUserData) BOOL sendUserData; + +// if YES, the the users usage time of the app to the service, only in 1 minute granularity! (default) +// if NO, no such data is send to the server +@property (nonatomic, assign, getter=shouldSendUsageTime) BOOL sendUsageTime; + +// if YES, the user agrees to send the usage data, user can change it if the developer shows the settings (default) +// if NO, the user overwrites the developer setting and no such data is sent +@property (nonatomic, assign, getter=isAllowUserToDisableSendData) BOOL allowUserToDisableSendData; + +// if YES, the user allowed to send user data (default) +// if NO, the user denied to send user data +@property (nonatomic, assign, getter=doesUserAllowsSendUserData) BOOL userAllowsSendUserData; + +// if YES, the user allowed to send usage data (default) +// if NO, the user denied to send usage data +@property (nonatomic, assign, getter=doesUserAllowsSendUsageTime) BOOL userAllowsSendUsageTime; + +// if YES, the new version alert will be displayed always if the current version is outdated (default) +// if NO, the alert will be displayed only once for each new update +@property (nonatomic, assign) BOOL alwaysShowUpdateReminder; + +// if YES, the user can change the HockeyUpdateSetting value (default) +// if NO, the user can not change it, and the default or developer defined value will be used +@property (nonatomic, assign, getter=shouldShowUserSettings) BOOL showUserSettings; + +// if YES, then an update check will be performed after the application becomes active (default) +// if NO, then the update check will not happen unless invoked explicitly +@property (nonatomic, assign, getter=isCheckForUpdateOnLaunch) BOOL checkForUpdateOnLaunch; + +// if YES, the alert notifying about an new update also shows a button to install the update directly +// if NO, the alert notifying about an new update only shows ignore and show update button +@property (nonatomic, assign, getter=ishowingDirectInstallOption) BOOL showDirectInstallOption; + +// if YES, each app version needs to be authorized by the server to run on this device +// if NO, each app version does not need to be authorized (default) +@property (nonatomic, assign, getter=isRequireAuthorization) BOOL requireAuthorization; + +// HockeyComparisonResultDifferent: alerts if the version on the server is different (default) +// HockeyComparisonResultGreater: alerts if the version on the server is greater +@property (nonatomic, assign) HockeyComparisonResult compareVersionType; + +// see HockeyUpdateSetting-enum. Will be saved in user defaults. +// default value: HockeyUpdateCheckStartup +@property (nonatomic, assign) HockeyUpdateSetting updateSetting; + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// is an update available? +- (BOOL)isUpdateAvailable; + +// are we currently checking for updates? +- (BOOL)isCheckInProgress; + +// open update info view +- (void)showUpdateView; + +// manually start an update check +- (void)checkForUpdate; + +// checks for update, informs the user (error, no update found, etc) +- (void)checkForUpdateShowFeedback:(BOOL)feedback; + +// initiates app-download call. displays an system UIAlertView +- (BOOL)initiateAppDownload; + +// checks wether this app version is authorized +- (BOOL)appVersionIsAuthorized; + +// start checking for an authorization key +- (void)checkForAuthorization; + +// convenience methode to create hockey view controller +- (BWHockeyViewController *)hockeyViewController:(BOOL)modal; + +// get/set current active hockey view controller +@property (nonatomic, retain) BWHockeyViewController *currentHockeyViewController; + +// convenience method to get current running version string +- (NSString *)currentAppVersion; + +// get newest app or array of all available versions +- (BWApp *)app; + +- (NSArray *)apps; + +@end + +/////////////////////////////////////////////////////////////////////////////////////////////////// +@protocol BWHockeyManagerDelegate +@optional + +// Invoked when the internet connection is started, to let the app enable the activity indicator +- (void)connectionOpened; + +// Invoked when the internet connection is closed, to let the app disable the activity indicator +- (void)connectionClosed; + +// optional parent view controller for the update screen when invoked via the alert view, default is the root UIWindow instance +- (UIViewController *)viewControllerForHockeyController:(BWHockeyManager *)hockeyController; + +@end diff --git a/Classes/BWHockeyManager.m b/Classes/BWHockeyManager.m new file mode 100644 index 0000000000..8c791454ec --- /dev/null +++ b/Classes/BWHockeyManager.m @@ -0,0 +1,1152 @@ +// +// BWHockeyManager.m +// +// Created by Andreas Linde on 8/17/10. +// Copyright 2010-2011 Andreas Linde. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "BWHockeyManager.h" +#import "BWGlobal.h" +#import "BWApp.h" +#import "NSString+HockeyAdditions.h" +#import "UIImage+HockeyAdditions.h" +#import +#import + +// API defines - do not change +#define BETA_DOWNLOAD_TYPE_PROFILE @"profile" +#define BETA_UPDATE_RESULT @"result" +#define BETA_UPDATE_TITLE @"title" +#define BETA_UPDATE_SUBTITLE @"subtitle" +#define BETA_UPDATE_NOTES @"notes" +#define BETA_UPDATE_VERSION @"version" +#define BETA_UPDATE_TIMESTAMP @"timestamp" +#define BETA_UPDATE_APPSIZE @"appsize" + +@interface BWHockeyManager () +- (NSString *)getDevicePlatform_; +- (id)parseJSONResultString:(NSString *)jsonString; +- (void)connectionOpened_; +- (void)connectionClosed_; +- (BOOL)shouldCheckForUpdates; +- (void)startUsage; +- (void)stopUsage; +- (void)startManager; +- (void)wentOnline; +- (void)showAuthorizationScreen:(NSString *)message image:(NSString *)image; +- (BOOL)canSendUserData; +- (BOOL)canSendUsageTime; +- (NSString *)currentUsageString; +- (NSString *)installationDateString; +- (NSString *)authenticationToken; +- (HockeyAuthorizationState)authorizationState; + +@property (nonatomic, assign, getter=isUpdateAvailable) BOOL updateAvailable; +@property (nonatomic, assign, getter=isCheckInProgress) BOOL checkInProgress; +@property (nonatomic, retain) NSMutableData *receivedData; +@property (nonatomic, copy) NSDate *lastCheck; +@property (nonatomic, copy) NSArray *apps; +@property (nonatomic, retain) NSURLConnection *urlConnection; +@property (nonatomic, copy) NSDate *usageStartTimestamp; +@property (nonatomic, retain) UIView *authorizeView; +@property (nonatomic, retain) NSDictionary *trackerConfig; +@end + +// hockey api error domain +typedef enum { + HockeyErrorUnknown, + HockeyAPIServerReturnedInvalidStatus, + HockeyAPIServerReturnedInvalidData, + HockeyAPIServerReturnedEmptyResponse, + HockeyAPIClientAuthorizationMissingSecret, + HockeyAPIClientCannotCreateConnection +} HockeyErrorReason; +static NSString *kHockeyErrorDomain = @"HockeyErrorDomain"; + +@implementation BWHockeyManager + +@synthesize checkForTracker; +@synthesize trackerConfig; +@synthesize delegate = delegate_; +@synthesize updateURL = updateURL_; +@synthesize appIdentifier = appIdentifier_; +@synthesize urlConnection = urlConnection_; +@synthesize loggingEnabled = loggingEnabled_; +@synthesize checkInProgress = checkInProgress_; +@synthesize receivedData = receivedData_; +@synthesize sendUserData = sendUserData_; +@synthesize sendUsageTime = sendUsageTime_; +@synthesize allowUserToDisableSendData = allowUserToDisableSendData_; +@synthesize userAllowsSendUserData = userAllowsSendUserData_; +@synthesize userAllowsSendUsageTime = userAllowsSendUsageTime_; +@synthesize alwaysShowUpdateReminder = showUpdateReminder_; +@synthesize checkForUpdateOnLaunch = checkForUpdateOnLaunch_; +@synthesize compareVersionType = compareVersionType_; +@synthesize lastCheck = lastCheck_; +@synthesize showUserSettings = showUserSettings_; +@synthesize updateSetting = updateSetting_; +@synthesize apps = apps_; +@synthesize updateAvailable = updateAvailable_; +@synthesize usageStartTimestamp = usageStartTimestamp_; +@synthesize currentHockeyViewController = currentHockeyViewController_; +@synthesize showDirectInstallOption = showDirectInstallOption_; +@synthesize requireAuthorization = requireAuthorization_; +@synthesize authenticationSecret = authenticationSecret_; +@synthesize authorizeView = authorizeView_; + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark static + +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 40000 ++(BWHockeyManager *)sharedHockeyManager +{ + static BWHockeyManager *sharedInstance = nil; + static dispatch_once_t pred; + + if (sharedInstance) return sharedInstance; + + dispatch_once(&pred, ^{ + sharedInstance = [BWHockeyManager alloc]; + sharedInstance = [sharedInstance init]; + }); + + return sharedInstance; +} +#else ++ (BWHockeyManager *)sharedHockeyManager { + static BWHockeyManager *hockeyManager = nil; + + if (hockeyManager == nil) { + hockeyManager = [[BWHockeyManager alloc] init]; + } + + return hockeyManager; +} +#endif + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark private + +- (void)reportError_:(NSError *)error { + BWHockeyLog(@"Error: %@", [error localizedDescription]); + lastCheckFailed_ = YES; + + // only show error if we enable that + if (showFeedback_) { + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:BWHockeyLocalize(@"HockeyError") + message:[error localizedDescription] + delegate:nil + cancelButtonTitle:BWHockeyLocalize(@"OK") otherButtonTitles:nil]; + [alert show]; + [alert release]; + showFeedback_ = NO; + } +} + +- (NSString *)encodedAppIdentifier_ { + return (self.appIdentifier ? [self.appIdentifier stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] : [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]); +} + +- (NSString *)getDevicePlatform_ { + size_t size; + sysctlbyname("hw.machine", NULL, &size, NULL, 0); + char *answer = (char*)malloc(size); + sysctlbyname("hw.machine", answer, &size, NULL, 0); + NSString *platform = [NSString stringWithCString:answer encoding: NSUTF8StringEncoding]; + free(answer); + return platform; +} + +- (void)connectionOpened_ { + if ([self.delegate respondsToSelector:@selector(connectionOpened)]) + [(id)self.delegate connectionOpened]; +} + +- (void)connectionClosed_ { + if ([self.delegate respondsToSelector:@selector(connectionClosed)]) + [(id)self.delegate connectionClosed]; +} + +- (void)startUsage { + self.usageStartTimestamp = [NSDate date]; + BOOL newVersion = NO; + + if (![[NSUserDefaults standardUserDefaults] valueForKey:kUsageTimeForVersionString]) { + newVersion = YES; + } else { + if ([(NSString *)[[NSUserDefaults standardUserDefaults] valueForKey:kUsageTimeForVersionString] compare:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]] != NSOrderedSame) { + newVersion = YES; + } + } + + if (newVersion) { + [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithDouble:[[NSDate date] timeIntervalSinceReferenceDate]] forKey:kDateOfVersionInstallation]; + [[NSUserDefaults standardUserDefaults] setObject:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] forKey:kUsageTimeForVersionString]; + [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithDouble:0] forKey:kUsageTimeOfCurrentVersion]; + [[NSUserDefaults standardUserDefaults] synchronize]; + } +} + +- (void)stopUsage { + double timeDifference = [[NSDate date] timeIntervalSinceReferenceDate] - [usageStartTimestamp_ timeIntervalSinceReferenceDate]; + double previousTimeDifference = [(NSNumber *)[[NSUserDefaults standardUserDefaults] valueForKey:kUsageTimeOfCurrentVersion] doubleValue]; + + [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithDouble:previousTimeDifference + timeDifference] forKey:kUsageTimeOfCurrentVersion]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} + +- (NSString *)currentUsageString { + double currentUsageTime = [(NSNumber *)[[NSUserDefaults standardUserDefaults] valueForKey:kUsageTimeOfCurrentVersion] doubleValue]; + + if (currentUsageTime > 0) { + // round (up) to 1 minute + return [NSString stringWithFormat:@"%.0f", ceil(currentUsageTime / 60.0)*60]; + } else { + return @"0"; + } +} + +- (NSString *)installationDateString { + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setDateFormat:@"MM/dd/yyyy"]; + return [formatter stringFromDate:[NSDate dateWithTimeIntervalSinceReferenceDate:[(NSNumber *)[[NSUserDefaults standardUserDefaults] valueForKey:kDateOfVersionInstallation] doubleValue]]]; +} + +- (NSString *)deviceIdentifier { + if ([[UIDevice currentDevice] respondsToSelector:@selector(uniqueIdentifier)]) { + return [[UIDevice currentDevice] performSelector:@selector(uniqueIdentifier)]; + } + else { + return @"invalid"; + } +} + +- (NSString *)authenticationToken { + return [BWmd5([NSString stringWithFormat:@"%@%@%@%@", + authenticationSecret_, + [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"], + [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"], + [self deviceIdentifier] + ] + ) lowercaseString]; +} + +- (HockeyAuthorizationState)authorizationState { + NSString *version = [[NSUserDefaults standardUserDefaults] objectForKey:kHockeyAuthorizedVersion]; + NSString *token = [[NSUserDefaults standardUserDefaults] objectForKey:kHockeyAuthorizedToken]; + + if (version != nil && token != nil) { + if ([version compare:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]] == NSOrderedSame) { + // if it is denied, block the screen permanently + if ([token compare:[self authenticationToken]] != NSOrderedSame) { + return HockeyAuthorizationDenied; + } else { + return HockeyAuthorizationAllowed; + } + } + } + return HockeyAuthorizationPending; +} + +- (void)checkUpdateAvailable_ { + // check if there is an update available + if (self.compareVersionType == HockeyComparisonResultGreater) { + self.updateAvailable = ([self.app.version compare:self.currentAppVersion options:NSNumericSearch] == NSOrderedDescending); + } else { + self.updateAvailable = ([self.app.version compare:self.currentAppVersion] != NSOrderedSame); + } +} + +- (void)loadAppCache_ { + NSData *savedHockeyData = [[NSUserDefaults standardUserDefaults] objectForKey:kArrayOfLastHockeyCheck]; + NSArray *savedHockeyCheck = nil; + if (savedHockeyData) { + savedHockeyCheck = [NSKeyedUnarchiver unarchiveObjectWithData:savedHockeyData]; + } + if (savedHockeyCheck) { + self.apps = [NSArray arrayWithArray:savedHockeyCheck]; + [self checkUpdateAvailable_]; + } else { + self.apps = nil; + } +} + +- (void)saveAppCache_ { + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.apps]; + [[NSUserDefaults standardUserDefaults] setObject:data forKey:kArrayOfLastHockeyCheck]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} + +- (UIWindow *)findVisibleWindow { + UIWindow *visibleWindow = nil; + + // if the rootViewController property (available >= iOS 4.0) of the main window is set, we present the modal view controller on top of the rootViewController + NSArray *windows = [[UIApplication sharedApplication] windows]; + for (UIWindow *window in windows) { + if (!window.hidden && !visibleWindow) { + visibleWindow = window; + } + if ([UIWindow instancesRespondToSelector:@selector(rootViewController)]) { + if ([window rootViewController]) { + visibleWindow = window; + BWHockeyLog(@"UIWindow with rootViewController found: %@", visibleWindow); + break; + } + } + } + + return visibleWindow; +} + +- (BOOL)canSendUserData { + if (self.shouldSendUserData) { + if (self.allowUserToDisableSendData) { + return self.userAllowsSendUserData; + } + + return YES; + } + + return NO; +} + +- (BOOL)canSendUsageTime { + if (self.shouldSendUsageTime) { + if (self.allowUserToDisableSendData) { + return self.userAllowsSendUsageTime; + } + + return YES; + } + + return NO; +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark NSObject + +- (id)init { + if ((self = [super init])) { + updateURL_ = nil; + appIdentifier_ = nil; + checkInProgress_ = NO; + dataFound = NO; + updateAvailable_ = NO; + lastCheckFailed_ = NO; + currentAppVersion_ = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; + navController_ = nil; + authorizeView_ = nil; + requireAuthorization_ = NO; + authenticationSecret_= nil; + loggingEnabled_ = NO; + + // set defaults + self.showDirectInstallOption = NO; + self.requireAuthorization = NO; + self.sendUserData = YES; + self.sendUsageTime = YES; + self.allowUserToDisableSendData = YES; + self.alwaysShowUpdateReminder = YES; + self.checkForUpdateOnLaunch = YES; + self.showUserSettings = YES; + self.compareVersionType = HockeyComparisonResultDifferent; + + // load update setting from user defaults and check value + if ([[NSUserDefaults standardUserDefaults] objectForKey:kHockeyAutoUpdateSetting]) { + self.updateSetting = (HockeyUpdateSetting)[[NSUserDefaults standardUserDefaults] integerForKey:kHockeyAutoUpdateSetting]; + } else { + self.updateSetting = HockeyUpdateCheckStartup; + } + + if ([[NSUserDefaults standardUserDefaults] objectForKey:kHockeyAllowUserSetting]) { + self.userAllowsSendUserData = [[NSUserDefaults standardUserDefaults] boolForKey:kHockeyAllowUserSetting]; + } else { + self.userAllowsSendUserData = YES; + } + + if ([[NSUserDefaults standardUserDefaults] objectForKey:kHockeyAllowUsageSetting]) { + self.userAllowsSendUsageTime = [[NSUserDefaults standardUserDefaults] boolForKey:kHockeyAllowUsageSetting]; + } else { + self.userAllowsSendUsageTime = YES; + } + + [self loadAppCache_]; + + [self startUsage]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(startManager) + name:BWHockeyNetworkBecomeReachable + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(stopUsage) + name:UIApplicationWillTerminateNotification + object:nil]; + } + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self name:BWHockeyNetworkBecomeReachable object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil]; + + BW_IF_IOS4_OR_GREATER( + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil]; + ) + self.delegate = nil; + + [urlConnection_ cancel]; + self.urlConnection = nil; + + [navController_ release]; + [authorizeView_ release]; + [currentHockeyViewController_ release]; + [updateURL_ release]; + [apps_ release]; + [receivedData_ release]; + [lastCheck_ release]; + [usageStartTimestamp_ release]; + [authenticationSecret_ release]; + + [super dealloc]; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark BetaUpdateUI + +- (BWHockeyViewController *)hockeyViewController:(BOOL)modal { + return [[[BWHockeyViewController alloc] init:self modal:modal] autorelease]; +} + +- (void)showUpdateView { + if (currentHockeyViewController_) { + BWHockeyLog(@"update view already visible, aborting"); + return; + } + + UIViewController *parentViewController = nil; + + if ([[self delegate] respondsToSelector:@selector(viewControllerForHockeyController:)]) { + parentViewController = [[self delegate] viewControllerForHockeyController:self]; + } + + UIWindow *visibleWindow = [self findVisibleWindow]; + + if (parentViewController == nil && [UIWindow instancesRespondToSelector:@selector(rootViewController)]) { + parentViewController = [visibleWindow rootViewController]; + } + + // use topmost modal view + while (parentViewController.modalViewController) { + parentViewController = parentViewController.modalViewController; + } + + // special addition to get rootViewController from three20 which has it's own controller handling + if (NSClassFromString(@"TTNavigator")) { + parentViewController = [[NSClassFromString(@"TTNavigator") performSelector:(NSSelectorFromString(@"navigator"))] visibleViewController]; + } + + if (navController_ != nil) [navController_ release]; + + BWHockeyViewController *hockeyViewController = [self hockeyViewController:YES]; + navController_ = [[UINavigationController alloc] initWithRootViewController:hockeyViewController]; + + if (parentViewController) { + if ([navController_ respondsToSelector:@selector(setModalTransitionStyle:)]) { + navController_.modalTransitionStyle = UIModalTransitionStyleCoverVertical; + } + + // page sheet for the iPad + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad && [navController_ respondsToSelector:@selector(setModalPresentationStyle:)]) { + navController_.modalPresentationStyle = UIModalPresentationFormSheet; + } + + [parentViewController presentModalViewController:navController_ animated:YES]; + } else { + // if not, we add a subview to the window. A bit hacky but should work in most circumstances. + // Also, we don't get a nice animation for free, but hey, this is for beta not production users ;) + BWHockeyLog(@"No rootViewController found, using UIWindow-approach: %@", visibleWindow); + [visibleWindow addSubview:navController_.view]; + } +} + + +- (void)showCheckForUpdateAlert_ { + if (!updateAlertShowing_) { + if ([self.app.mandatory boolValue] ) { + UIAlertView *alertView = [[[UIAlertView alloc] initWithTitle:BWHockeyLocalize(@"HockeyUpdateAvailable") + message:[NSString stringWithFormat:BWHockeyLocalize(@"HockeyUpdateAlertMandatoryTextWithAppVersion"), [self.app nameAndVersionString]] + delegate:self + cancelButtonTitle:BWHockeyLocalize(@"HockeyInstallUpdate") + otherButtonTitles:nil + ] autorelease]; + [alertView setTag:2]; + [alertView show]; + updateAlertShowing_ = YES; + } else { + UIAlertView *alertView = [[[UIAlertView alloc] initWithTitle:BWHockeyLocalize(@"HockeyUpdateAvailable") + message:[NSString stringWithFormat:BWHockeyLocalize(@"HockeyUpdateAlertTextWithAppVersion"), [self.app nameAndVersionString]] + delegate:self + cancelButtonTitle:BWHockeyLocalize(@"HockeyIgnore") + otherButtonTitles:BWHockeyLocalize(@"HockeyShowUpdate"), nil + ] autorelease]; + BW_IF_IOS4_OR_GREATER( + if (self.ishowingDirectInstallOption) { + [alertView addButtonWithTitle:BWHockeyLocalize(@"HockeyInstallUpdate")]; + } + ) + [alertView setTag:0]; + [alertView show]; + updateAlertShowing_ = YES; + } + } +} + + +// nag the user with neverending alerts if we cannot find out the window for presenting the covering sheet +- (void)alertFallback:(NSString *)message { + UIAlertView *alertView = [[[UIAlertView alloc] initWithTitle:nil + message:message + delegate:self + cancelButtonTitle:@"Ok" + otherButtonTitles:nil + ] autorelease]; + [alertView setTag:1]; + [alertView show]; +} + + +// open an authorization screen +- (void)showAuthorizationScreen:(NSString *)message image:(NSString *)image { + self.authorizeView = nil; + + UIWindow *visibleWindow = [self findVisibleWindow]; + if (visibleWindow == nil) { + [self alertFallback:message]; + return; + } + + CGRect frame = [visibleWindow frame]; + + self.authorizeView = [[[UIView alloc] initWithFrame:frame] autorelease]; + UIImageView *backgroundView = [[[UIImageView alloc] initWithImage:[UIImage bw_imageNamed:@"bg.png" bundle:kHockeyBundleName]] autorelease]; + backgroundView.contentMode = UIViewContentModeScaleAspectFill; + backgroundView.frame = frame; + [self.authorizeView addSubview:backgroundView]; + + if (image != nil) { + UIImageView *imageView = [[[UIImageView alloc] initWithImage:[UIImage bw_imageNamed:image bundle:kHockeyBundleName]] autorelease]; + imageView.contentMode = UIViewContentModeCenter; + imageView.frame = frame; + [self.authorizeView addSubview:imageView]; + } + + if (message != nil) { + frame.origin.x = 20; + frame.origin.y = frame.size.height - 140; + frame.size.width -= 40; + frame.size.height = 40; + + UILabel *label = [[[UILabel alloc] initWithFrame:frame] autorelease]; + label.text = message; + label.textAlignment = UITextAlignmentCenter; + label.numberOfLines = 2; + label.backgroundColor = [UIColor clearColor]; + + [self.authorizeView addSubview:label]; + } + + [visibleWindow addSubview:self.authorizeView]; +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark JSONParsing + +- (id)parseJSONResultString:(NSString *)jsonString { + NSError *error = nil; + id feedResult = nil; + + SEL sbJSONSelector = NSSelectorFromString(@"JSONValue"); + SEL jsonKitSelector = NSSelectorFromString(@"objectFromJSONStringWithParseOptions:error:"); + + if (jsonKitSelector && [jsonString respondsToSelector:jsonKitSelector]) { + // first try JSONkit + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[jsonString methodSignatureForSelector:jsonKitSelector]]; + invocation.target = jsonString; + invocation.selector = jsonKitSelector; + int parseOptions = 0; + [invocation setArgument:&parseOptions atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation + [invocation setArgument:&error atIndex:3]; + [invocation invoke]; + [invocation getReturnValue:&feedResult]; + } else if (sbJSONSelector && [jsonString respondsToSelector:sbJSONSelector]) { + // now try SBJson + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[jsonString methodSignatureForSelector:sbJSONSelector]]; + invocation.target = jsonString; + invocation.selector = sbJSONSelector; + [invocation invoke]; + [invocation getReturnValue:&feedResult]; + } else { + BWHockeyLog(@"Error: You need a JSON Framework in your runtime!"); + [self doesNotRecognizeSelector:_cmd]; + } + if (error) { + BWHockeyLog(@"Error while parsing response feed: %@", [error localizedDescription]); + [self reportError_:error]; + return nil; + } + + return feedResult; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark RequestComments + +- (BOOL)shouldCheckForUpdates { + BOOL checkForUpdate = NO; + switch (self.updateSetting) { + case HockeyUpdateCheckStartup: + checkForUpdate = YES; + break; + case HockeyUpdateCheckDaily: + checkForUpdate = [[[self.lastCheck description] substringToIndex:10] compare:[[[NSDate date] description] substringToIndex:10]] != NSOrderedSame; + break; + case HockeyUpdateCheckManually: + checkForUpdate = NO; + break; + default: + break; + } + return checkForUpdate; +} + +- (void)checkForAuthorization { + NSMutableString *parameter = [NSMutableString stringWithFormat:@"api/2/apps/%@", [[self encodedAppIdentifier_] bw_URLEncodedString]]; + + [parameter appendFormat:@"?format=json&authorize=yes&app_version=%@&udid=%@", + [[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] bw_URLEncodedString], + [[self deviceIdentifier] bw_URLEncodedString] + ]; + + // build request & send + NSString *url = [NSString stringWithFormat:@"%@%@", self.updateURL, parameter]; + BWHockeyLog(@"sending api request to %@", url); + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:1 timeoutInterval:10.0]; + [request setHTTPMethod:@"GET"]; + [request setValue:@"Hockey/iOS" forHTTPHeaderField:@"User-Agent"]; + + NSURLResponse *response = nil; + NSError *error = NULL; + BOOL failed = YES; + + NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; + + if ([responseData length]) { + NSString *responseString = [[[NSString alloc] initWithBytes:[responseData bytes] length:[responseData length] encoding: NSUTF8StringEncoding] autorelease]; + + NSDictionary *feedDict = (NSDictionary *)[self parseJSONResultString:responseString]; + + // server returned empty response? + if (![feedDict count]) { + [self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIServerReturnedEmptyResponse userInfo: + [NSDictionary dictionaryWithObjectsAndKeys:@"Server returned empty response.", NSLocalizedDescriptionKey, nil]]]; + return; + } else { + BWHockeyLog(@"Received API response: %@", responseString); + NSString *token = [[feedDict objectForKey:@"authcode"] lowercaseString]; + failed = NO; + if ([[self authenticationToken] compare:token] == NSOrderedSame) { + // identical token, activate this version + + // store the new data + [[NSUserDefaults standardUserDefaults] setObject:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] forKey:kHockeyAuthorizedVersion]; + [[NSUserDefaults standardUserDefaults] setObject:token forKey:kHockeyAuthorizedToken]; + [[NSUserDefaults standardUserDefaults] synchronize]; + + self.requireAuthorization = NO; + self.authorizeView = nil; + + // now continue with an update check right away + if (self.checkForUpdateOnLaunch) { + [self checkForUpdate]; + } + } else { + // different token, block this version + BWHockeyLog(@"AUTH FAILURE: %@", [self authenticationToken]); + + // store the new data + [[NSUserDefaults standardUserDefaults] setObject:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] forKey:kHockeyAuthorizedVersion]; + [[NSUserDefaults standardUserDefaults] setObject:token forKey:kHockeyAuthorizedToken]; + [[NSUserDefaults standardUserDefaults] synchronize]; + + [self showAuthorizationScreen:BWHockeyLocalize(@"HockeyAuthorizationDenied") image:@"authorize_denied.png"]; + } + } + + } + + if (failed) { + [self showAuthorizationScreen:BWHockeyLocalize(@"HockeyAuthorizationOffline") image:@"authorize_request.png"]; + } +} + +- (void)checkForUpdate { + if (self.requireAuthorization) return; + if (self.isUpdateAvailable && [self.app.mandatory boolValue]) { + [self showCheckForUpdateAlert_]; + return; + } + [self checkForUpdateShowFeedback:NO]; +} + +- (void)checkForUpdateShowFeedback:(BOOL)feedback { + if (self.isCheckInProgress) return; + + showFeedback_ = feedback; + self.checkInProgress = YES; + + // do we need to update? + if (![self shouldCheckForUpdates] && !currentHockeyViewController_) { + BWHockeyLog(@"update not needed right now"); + self.checkInProgress = NO; + return; + } + + NSMutableString *parameter = [NSMutableString stringWithFormat:@"api/2/apps/%@?format=json&udid=%@", + [[self encodedAppIdentifier_] bw_URLEncodedString], + [[self deviceIdentifier] bw_URLEncodedString]]; + + // add additional statistics if user didn't disable flag + if ([self canSendUserData]) { + [parameter appendFormat:@"&app_version=%@&os=iOS&os_version=%@&device=%@&lang=%@&first_start_at=%@", + [[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] bw_URLEncodedString], + [[[UIDevice currentDevice] systemVersion] bw_URLEncodedString], + [[self getDevicePlatform_] bw_URLEncodedString], + [[[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0] bw_URLEncodedString], + [[[self installationDateString] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] bw_URLEncodedString] + ]; + if ([self canSendUsageTime]) { + [parameter appendFormat:@"&usage_time=%@", + [[[self currentUsageString] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] bw_URLEncodedString] + ]; + } + } + + if ([self checkForTracker]) { + [parameter appendFormat:@"&jmc=yes"]; + } + + // build request & send + NSString *url = [NSString stringWithFormat:@"%@%@", self.updateURL, parameter]; + BWHockeyLog(@"sending api request to %@", url); + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:1 timeoutInterval:10.0]; + [request setHTTPMethod:@"GET"]; + [request setValue:@"Hockey/iOS" forHTTPHeaderField:@"User-Agent"]; + [request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"]; + + self.urlConnection = [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease]; + if (!urlConnection_) { + self.checkInProgress = NO; + [self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIClientCannotCreateConnection userInfo: + [NSDictionary dictionaryWithObjectsAndKeys:@"Url Connection could not be created.", NSLocalizedDescriptionKey, nil]]]; + } +} + +- (BOOL)initiateAppDownload { + if (!self.isUpdateAvailable) { + BWHockeyLog(@"Warning: No update available. Aborting."); + return NO; + } + + BW_IF_PRE_IOS4 + ( + NSString *message = [NSString stringWithFormat:BWHockeyLocalize(@"HockeyiOS3Message"), self.updateURL]; + UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:BWHockeyLocalize(@"HockeyWarning") message:message delegate:nil cancelButtonTitle:BWHockeyLocalize(@"HockeyOK") otherButtonTitles:nil] autorelease]; + [alert show]; + return NO; + ) + +#if TARGET_IPHONE_SIMULATOR + UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:BWHockeyLocalize(@"HockeyWarning") message:BWHockeyLocalize(@"HockeySimulatorMessage") delegate:nil cancelButtonTitle:BWHockeyLocalize(@"HockeyOK") otherButtonTitles:nil] autorelease]; + [alert show]; + return NO; +#endif + + NSString *extraParameter = [NSString string]; + if ([self canSendUserData]) { + extraParameter = [NSString stringWithFormat:@"&udid=%@", [self deviceIdentifier]]; + } + + NSString *hockeyAPIURL = [NSString stringWithFormat:@"%@api/2/apps/%@?format=plist%@", self.updateURL, [self encodedAppIdentifier_], extraParameter]; + NSString *iOSUpdateURL = [NSString stringWithFormat:@"itms-services://?action=download-manifest&url=%@", [hockeyAPIURL bw_URLEncodedString]]; + + BWHockeyLog(@"API Server Call: %@, calling iOS with %@", hockeyAPIURL, iOSUpdateURL); + BOOL success = [[UIApplication sharedApplication] openURL:[NSURL URLWithString:iOSUpdateURL]]; + BWHockeyLog(@"System returned: %d", success); + return success; +} + + +// checks wether this app version is authorized +- (BOOL)appVersionIsAuthorized { + if (self.requireAuthorization && !authenticationSecret_) { + [self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIClientAuthorizationMissingSecret userInfo: + [NSDictionary dictionaryWithObjectsAndKeys:@"Authentication secret is not set but required.", NSLocalizedDescriptionKey, nil]]]; + + return NO; + } + + if (!self.requireAuthorization) { + self.authorizeView = nil; + return YES; + } + + HockeyAuthorizationState state = [self authorizationState]; + if (state == HockeyAuthorizationDenied) { + [self showAuthorizationScreen:BWHockeyLocalize(@"HockeyAuthorizationDenied") image:@"authorize_denied.png"]; + } else if (state == HockeyAuthorizationAllowed) { + self.requireAuthorization = NO; + return YES; + } + + return NO; +} + + +// begin the startup process +- (void)startManager { + if (![self appVersionIsAuthorized]) { + if ([self authorizationState] == HockeyAuthorizationPending) { + [self showAuthorizationScreen:BWHockeyLocalize(@"HockeyAuthorizationProgress") image:@"authorize_request.png"]; + + [self performSelector:@selector(checkForAuthorization) withObject:nil afterDelay:0.0f]; + } + } else { + if ([self shouldCheckForUpdates]) { + [self performSelector:@selector(checkForUpdate) withObject:nil afterDelay:0.0f]; + } + } +} + + +- (void)wentOnline { + if (![self appVersionIsAuthorized]) { + if ([self authorizationState] == HockeyAuthorizationPending) { + [self checkForAuthorization]; + } + } else { + if (lastCheckFailed_) { + [self checkForUpdate]; + } + } +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark NSURLRequest + +- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse { + [self connectionOpened_]; + NSURLRequest *newRequest = request; + if (redirectResponse) { + newRequest = nil; + } + return newRequest; +} + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { + if ([response respondsToSelector:@selector(statusCode)]) { + int statusCode = [((NSHTTPURLResponse *)response) statusCode]; + if (statusCode == 404) { + [connection cancel]; // stop connecting; no more delegate messages + NSString *errorStr = [NSString stringWithFormat:@"Hockey API received HTTP Status Code %d", statusCode]; + [self connectionClosed_]; + [self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIServerReturnedInvalidStatus userInfo: + [NSDictionary dictionaryWithObjectsAndKeys:errorStr, NSLocalizedDescriptionKey, nil]]]; + return; + } + } + + self.receivedData = [NSMutableData data]; + [receivedData_ setLength:0]; +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { + [receivedData_ appendData:data]; +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { + [self connectionClosed_]; + self.receivedData = nil; + self.urlConnection = nil; + self.checkInProgress = NO; + [self reportError_:error]; +} + +// api call returned, parsing +- (void)connectionDidFinishLoading:(NSURLConnection *)connection { + [self connectionClosed_]; + self.checkInProgress = NO; + + if ([self.receivedData length]) { + NSString *responseString = [[[NSString alloc] initWithBytes:[receivedData_ bytes] length:[receivedData_ length] encoding: NSUTF8StringEncoding] autorelease]; + BWHockeyLog(@"Received API response: %@", responseString); + + id json = [self parseJSONResultString:responseString]; + NSArray *feedArray = (NSArray *)([self checkForTracker] ? [json valueForKey:@"versions"] : json); + self.trackerConfig = ([self checkForTracker] ? [json valueForKey:@"tracker"] : nil); + + self.receivedData = nil; + self.urlConnection = nil; + + // remember that we just checked the server + self.lastCheck = [NSDate date]; + + // server returned empty response? + if (![feedArray count]) { + [self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIServerReturnedEmptyResponse userInfo: + [NSDictionary dictionaryWithObjectsAndKeys:@"Server returned empty response.", NSLocalizedDescriptionKey, nil]]]; + return; + } else { + lastCheckFailed_ = NO; + } + + + NSString *currentAppCacheVersion = [[[self app].version copy] autorelease]; + + // clear cache and reload with new data + NSMutableArray *tmpApps = [NSMutableArray arrayWithCapacity:[feedArray count]]; + for (NSDictionary *dict in feedArray) { + BWApp *app = [BWApp appFromDict:dict]; + if ([app isValid]) { + [tmpApps addObject:app]; + } else { + [self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIServerReturnedInvalidData userInfo: + [NSDictionary dictionaryWithObjectsAndKeys:@"Invalid data received from server.", NSLocalizedDescriptionKey, nil]]]; + } + } + // only set if different! + if (![self.apps isEqualToArray:tmpApps]) { + self.apps = [[tmpApps copy] autorelease]; + } + [self saveAppCache_]; + + [self checkUpdateAvailable_]; + BOOL newVersionDiffersFromCachedVersion = ![self.app.version isEqualToString:currentAppCacheVersion]; + + // show alert if we are on the latest & greatest + if (showFeedback_ && !self.isUpdateAvailable) { + // use currentVersionString, as version still may differ (e.g. server: 1.2, client: 1.3) + NSString *versionString = [self currentAppVersion]; + NSString *shortVersionString = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; + shortVersionString = shortVersionString ? [NSString stringWithFormat:@"%@ ", shortVersionString] : @""; + versionString = [shortVersionString length] ? [NSString stringWithFormat:@"(%@)", versionString] : versionString; + NSString *currentVersionString = [NSString stringWithFormat:@"%@ %@ %@%@", self.app.name, BWHockeyLocalize(@"HockeyVersion"), shortVersionString, versionString]; + NSString *alertMsg = [NSString stringWithFormat:BWHockeyLocalize(@"HockeyNoUpdateNeededMessage"), currentVersionString]; + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:BWHockeyLocalize(@"HockeyNoUpdateNeededTitle") message:alertMsg delegate:nil cancelButtonTitle:BWHockeyLocalize(@"HockeyOK") otherButtonTitles:nil]; + [alert show]; + [alert release]; + } + + if (self.isUpdateAvailable && (self.alwaysShowUpdateReminder || newVersionDiffersFromCachedVersion || [self.app.mandatory boolValue])) { + if (updateAvailable_ && !currentHockeyViewController_) { + [self showCheckForUpdateAlert_]; + } + } + showFeedback_ = NO; + }else { + [self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIServerReturnedEmptyResponse userInfo: + [NSDictionary dictionaryWithObjectsAndKeys:@"Server returned an empty response.", NSLocalizedDescriptionKey, nil]]]; + } +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark Properties + +- (void)setCurrentHockeyViewController:(BWHockeyViewController *)aCurrentHockeyViewController { + if (currentHockeyViewController_ != aCurrentHockeyViewController) { + [currentHockeyViewController_ release]; + currentHockeyViewController_ = [aCurrentHockeyViewController retain]; + //BWHockeyLog(@"active hockey view controller: %@", aCurrentHockeyViewController); + } +} + +- (void)setUpdateURL:(NSString *)anUpdateURL { + // ensure url ends with a trailing slash + if (![anUpdateURL hasSuffix:@"/"]) { + anUpdateURL = [NSString stringWithFormat:@"%@/", anUpdateURL]; + } + + BW_IF_IOS4_OR_GREATER( + // register/deregister logic + NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter]; + if (!updateURL_ && anUpdateURL) { + [dnc addObserver:self selector:@selector(startUsage) name:UIApplicationDidBecomeActiveNotification object:nil]; + [dnc addObserver:self selector:@selector(stopUsage) name:UIApplicationWillResignActiveNotification object:nil]; + } else if (updateURL_ && !anUpdateURL) { + [dnc removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; + [dnc removeObserver:self name:UIApplicationWillResignActiveNotification object:nil]; + } + ) + + if (updateURL_ != anUpdateURL) { + [updateURL_ release]; + updateURL_ = [anUpdateURL copy]; + } + + [self performSelector:@selector(startManager) withObject:nil afterDelay:0.0f]; +} + +- (void)setAppIdentifier:(NSString *)anAppIdentifier { + if (appIdentifier_ != anAppIdentifier) { + [appIdentifier_ release]; + appIdentifier_ = [anAppIdentifier copy]; + } + + [self setUpdateURL:@"https://rink.hockeyapp.net/"]; +} + +- (void)setCheckForUpdateOnLaunch:(BOOL)flag { + if (checkForUpdateOnLaunch_ != flag) { + checkForUpdateOnLaunch_ = flag; + BW_IF_IOS4_OR_GREATER( + NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter]; + if (flag) { + [dnc addObserver:self selector:@selector(checkForUpdate) name:UIApplicationDidBecomeActiveNotification object:nil]; + } else { + [dnc removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; + } + ) + } +} + +- (void)setUserAllowsSendUserData:(BOOL)flag { + userAllowsSendUserData_ = flag; + + [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInteger:userAllowsSendUserData_] forKey:kHockeyAllowUserSetting]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} + +- (void)setUserAllowsSendUsageTime:(BOOL)flag { + userAllowsSendUsageTime_ = flag; + + [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInteger:userAllowsSendUsageTime_] forKey:kHockeyAllowUsageSetting]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} + +- (NSString *)currentAppVersion { + return currentAppVersion_; +} + + +- (void)setUpdateSetting:(HockeyUpdateSetting)anUpdateSetting { + if (anUpdateSetting > HockeyUpdateCheckManually) { + updateSetting_ = HockeyUpdateCheckStartup; + } + + updateSetting_ = anUpdateSetting; + [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInteger:updateSetting_] forKey:kHockeyAutoUpdateSetting]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} + +- (void)setLastCheck:(NSDate *)aLastCheck { + if (lastCheck_ != aLastCheck) { + [lastCheck_ release]; + lastCheck_ = [aLastCheck copy]; + + [[NSUserDefaults standardUserDefaults] setObject:[[lastCheck_ description] substringToIndex:10] forKey:kDateOfLastHockeyCheck]; + [[NSUserDefaults standardUserDefaults] synchronize]; + } +} + +- (void)setApps:(NSArray *)anApps { + if (apps_ != anApps || !apps_) { + [apps_ release]; + [self willChangeValueForKey:@"apps"]; + + // populate with default values (if empty) + if (![anApps count]) { + BWApp *defaultApp = [[[BWApp alloc] init] autorelease]; + defaultApp.name = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"]; + defaultApp.version = currentAppVersion_; + defaultApp.shortVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; + apps_ = [[NSArray arrayWithObject:defaultApp] retain]; + }else { + apps_ = [anApps copy]; + } + [self didChangeValueForKey:@"apps"]; + } +} + +- (BWApp *)app { + BWApp *app = [apps_ objectAtIndex:0]; + return app; +} + +- (void)setAuthorizeView:(UIView *)anAuthorizeView { + if (authorizeView_ != anAuthorizeView) { + [authorizeView_ removeFromSuperview]; + [authorizeView_ release]; + authorizeView_ = [anAuthorizeView retain]; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark UIAlertViewDelegate + +// invoke the selected action from the actionsheet for a location element +- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex { + if ([alertView tag] == 2) { + [self initiateAppDownload]; + updateAlertShowing_ = NO; + return; + } else if ([alertView tag] == 1) { + [self alertFallback:[alertView message]]; + return; + } + + updateAlertShowing_ = NO; + if (buttonIndex == [alertView firstOtherButtonIndex]) { + // YES button has been clicked + [self showUpdateView]; + } else if (buttonIndex == [alertView firstOtherButtonIndex] + 1) { + // YES button has been clicked + [self initiateAppDownload]; + } +} + +@end diff --git a/Classes/BWHockeySettingsViewController.h b/Classes/BWHockeySettingsViewController.h new file mode 100644 index 0000000000..7e318f417a --- /dev/null +++ b/Classes/BWHockeySettingsViewController.h @@ -0,0 +1,22 @@ +// +// BWHockeySettingsViewController.h +// HockeyDemo +// +// Created by Andreas Linde on 3/8/11. +// Copyright 2011 Andreas Linde. All rights reserved. +// + +#import +@class BWHockeyManager; + + +@interface BWHockeySettingsViewController : UIViewController { + BWHockeyManager *hockeyManager_; +} + +@property (nonatomic, retain) BWHockeyManager *hockeyManager; + +- (id)init:(BWHockeyManager *)newHockeyManager; +- (id)init; + +@end diff --git a/Classes/BWHockeySettingsViewController.m b/Classes/BWHockeySettingsViewController.m new file mode 100644 index 0000000000..4615b240e0 --- /dev/null +++ b/Classes/BWHockeySettingsViewController.m @@ -0,0 +1,294 @@ +// +// BWHockeySettingsViewController.m +// HockeyDemo +// +// Created by Andreas Linde on 3/8/11. +// Copyright 2011 Andreas Linde. All rights reserved. +// + +#import "BWHockeySettingsViewController.h" +#import "BWHockeyManager.h" +#import "BWGlobal.h" + +#define BW_RGBCOLOR(r,g,b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1] + +@implementation BWHockeySettingsViewController + +@synthesize hockeyManager = hockeyManager_; + +- (void)dismissSettings { + [self.navigationController dismissModalViewControllerAnimated:YES]; +} + +#pragma mark - +#pragma mark Initialization + +- (id)init:(BWHockeyManager *)newHockeyManager { + if ((self = [super init])) { + self.hockeyManager = newHockeyManager; + self.title = BWHockeyLocalize(@"HockeySettingsTitle"); + + CGRect frame = self.view.frame; + frame.origin = CGPointZero; + + UITableView *tableView_ = [[[UITableView alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height - 260, self.view.frame.size.width, 260) style:UITableViewStyleGrouped] autorelease]; + tableView_.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth; + + BW_IF_3_2_OR_GREATER( + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + self.view.backgroundColor = BW_RGBCOLOR(200, 202, 204); + tableView_.backgroundColor = BW_RGBCOLOR(200, 202, 204); + } else { + tableView_.frame = frame; + tableView_.autoresizingMask = tableView_.autoresizingMask | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin; + } + ) + BW_IF_PRE_3_2( + self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone + target:self + action:@selector(dismissSettings)] autorelease]; + tableView_.frame = frame; + tableView_.autoresizingMask = tableView_.autoresizingMask | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin; + ) + + tableView_.delegate = self; + tableView_.dataSource = self; + tableView_.clipsToBounds = NO; + + [self.view addSubview:tableView_]; + + } + return self; +} + +- (id)init { + return [self init:[BWHockeyManager sharedHockeyManager]]; +} + +#pragma mark - +#pragma mark Table view data source + +- (int)numberOfSections { + int numberOfSections = 1; + + if ([self.hockeyManager isAllowUserToDisableSendData]) { + if ([self.hockeyManager shouldSendUserData]) numberOfSections++; + if ([self.hockeyManager shouldSendUsageTime]) numberOfSections++; + } + + return numberOfSections; +} + + +- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { + if (section == [self numberOfSections] - 1) { + return BWHockeyLocalize(@"HockeySectionCheckTitle"); + } else { + return nil; + } +} + + +- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { + if (section < [self numberOfSections] - 1) { + return 66; + } else return 0; +} + + +- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { + if ([self numberOfSections] > 1 && section < [self numberOfSections] - 1) { + UILabel *footer = [[[UILabel alloc] initWithFrame:CGRectMake(0, 0, 285, 66)] autorelease]; + footer.backgroundColor = [UIColor clearColor]; + footer.numberOfLines = 3; + footer.textAlignment = UITextAlignmentCenter; + footer.adjustsFontSizeToFitWidth = YES; + footer.textColor = [UIColor grayColor]; + footer.font = [UIFont systemFontOfSize:13]; + + if (section == 0 && [self.hockeyManager isAllowUserToDisableSendData] && [self.hockeyManager shouldSendUserData]) { + footer.text = BWHockeyLocalize(@"HockeySettingsUserDataDescription"); + } else if ([self.hockeyManager isAllowUserToDisableSendData] && section < [self numberOfSections]) { + footer.text = BWHockeyLocalize(@"HockeySettingsUsageDataDescription"); + } + + UIView* view = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 285, footer.frame.size.height + 6 + 11)] autorelease]; + [view setBackgroundColor:[UIColor clearColor]]; + + CGRect frame = footer.frame; + frame.origin.y = 8; + frame.origin.x = 16; + frame.size.width = 285; + footer.frame = frame; + + [view addSubview:footer]; + [view sizeToFit]; + + return view; + } + + return nil; +} + + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + // Return the number of sections. + return [self numberOfSections]; +} + + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + // Return the number of rows in the section. + if (section == [self numberOfSections] - 1) + return 3; + else + return 1; +} + + +- (void)sendUserData:(UISwitch *)switcher { + [self.hockeyManager setUserAllowsSendUserData:switcher.on]; +} + +- (void)sendUsageData:(UISwitch *)switcher { + [self.hockeyManager setUserAllowsSendUsageTime:switcher.on]; +} + + +// Customize the appearance of table view cells. +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + static NSString *CheckmarkCellIdentifier = @"CheckmarkCell"; + static NSString *SwitchCellIdentifier = @"SwitchCell"; + + NSString *requiredIdentifier = nil; + UITableViewCellStyle cellStyle = UITableViewCellStyleSubtitle; + + if (indexPath.section == (NSUInteger)[self numberOfSections] - 1) { + cellStyle = UITableViewCellStyleDefault; + requiredIdentifier = CheckmarkCellIdentifier; + } else { + cellStyle = UITableViewCellStyleValue1; + requiredIdentifier = SwitchCellIdentifier; + } + + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:requiredIdentifier]; + if (cell == nil) { + cell = [[[UITableViewCell alloc] initWithStyle:cellStyle reuseIdentifier:requiredIdentifier] autorelease]; + } + + cell.accessoryType = UITableViewCellAccessoryNone; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + + // Configure the cell... + if (indexPath.section == (NSUInteger)[self numberOfSections] - 1) { + cell.selectionStyle = UITableViewCellSelectionStyleBlue; + + // update check selection + HockeyUpdateSetting hockeyAutoUpdateSetting = [[BWHockeyManager sharedHockeyManager] updateSetting]; + if (indexPath.row == 0) { + // on startup + cell.textLabel.text = BWHockeyLocalize(@"HockeySectionCheckStartup"); + if (hockeyAutoUpdateSetting == HockeyUpdateCheckStartup) { + cell.accessoryType = UITableViewCellAccessoryCheckmark; + } + } else if (indexPath.row == 1) { + // daily + cell.textLabel.text = BWHockeyLocalize(@"HockeySectionCheckDaily"); + if (hockeyAutoUpdateSetting == HockeyUpdateCheckDaily) { + cell.accessoryType = UITableViewCellAccessoryCheckmark; + } + } else { + // manually + cell.textLabel.text = BWHockeyLocalize(@"HockeySectionCheckManually"); + if (hockeyAutoUpdateSetting == HockeyUpdateCheckManually) { + cell.accessoryType = UITableViewCellAccessoryCheckmark; + } + } + } else { + UISwitch *toggleSwitch = [[[UISwitch alloc] initWithFrame:CGRectZero] autorelease]; + + if (indexPath.section == 0 && [self.hockeyManager shouldSendUserData] && [self.hockeyManager isAllowUserToDisableSendData]) { + // send user data + cell.textLabel.text = BWHockeyLocalize(@"HockeySettingsUserData"); + [toggleSwitch addTarget:self action:@selector(sendUserData:) + forControlEvents:UIControlEventValueChanged]; + [toggleSwitch setOn:[self.hockeyManager doesUserAllowsSendUserData]]; + + } else if ([self.hockeyManager shouldSendUsageTime] && [self.hockeyManager isAllowUserToDisableSendData]) { + // send usage time + cell.textLabel.text = BWHockeyLocalize(@"HockeySettingsUsageData"); + [toggleSwitch addTarget:self action:@selector(sendUsageData:) + forControlEvents:UIControlEventValueChanged]; + [toggleSwitch setOn:[self.hockeyManager doesUserAllowsSendUsageTime]]; + } + + cell.accessoryView = toggleSwitch; + + } + + return cell; +} + + +#pragma mark - +#pragma mark Table view delegate + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + + // update check interval selection + if (indexPath.row == 0) { + // on startup + [BWHockeyManager sharedHockeyManager].updateSetting = HockeyUpdateCheckStartup; + } else if (indexPath.row == 1) { + // daily + [BWHockeyManager sharedHockeyManager].updateSetting = HockeyUpdateCheckDaily; + } else { + // manually + [BWHockeyManager sharedHockeyManager].updateSetting = HockeyUpdateCheckManually; + } + + [tableView reloadData]; +} + + +#pragma mark - +#pragma mark Memory management + +- (void)didReceiveMemoryWarning { + // Releases the view if it doesn't have a superview. + [super didReceiveMemoryWarning]; + + // Relinquish ownership any cached data, images, etc. that aren't in use. +} + +- (void)viewDidUnload { + // Relinquish ownership of anything that can be recreated in viewDidLoad or on demand. + // For example: self.myOutlet = nil; +} + + +- (void)dealloc { + [super dealloc]; +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark Rotation + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + BOOL shouldAutorotate; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + shouldAutorotate = (interfaceOrientation == UIInterfaceOrientationPortrait); + } else { + shouldAutorotate = YES; + } + + return shouldAutorotate; +} + +@end + diff --git a/Classes/BWHockeyViewController.h b/Classes/BWHockeyViewController.h new file mode 100644 index 0000000000..7b50d9162c --- /dev/null +++ b/Classes/BWHockeyViewController.h @@ -0,0 +1,65 @@ +// +// BWHockeyViewController.h +// +// Created by Andreas Linde on 8/17/10. +// Copyright 2010-2011 Andreas Linde. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import "PSStoreButton.h" +#import "PSAppStoreHeader.h" + +typedef enum { + AppStoreButtonStateOffline, + AppStoreButtonStateCheck, + AppStoreButtonStateSearching, + AppStoreButtonStateUpdate, + AppStoreButtonStateInstalling +} AppStoreButtonState; + + +@class BWHockeyManager; + +@interface BWHockeyViewController : UITableViewController { + BWHockeyManager *hockeyManager_; + + NSDictionary *cellLayout; + + BOOL modal_; + BOOL kvoRegistered_; + BOOL showAllVersions_; + UIStatusBarStyle statusBarStyle_; + PSAppStoreHeader *appStoreHeader_; + PSStoreButton *appStoreButton_; + + id popOverController_; + + AppStoreButtonState appStoreButtonState_; + + NSMutableArray *cells_; +} + +@property (nonatomic, retain) BWHockeyManager *hockeyManager; +@property (nonatomic, readwrite) BOOL modal; + +- (id)init:(BWHockeyManager *)newHockeyManager modal:(BOOL)newModal; +- (id)init; + +@end diff --git a/Classes/BWHockeyViewController.m b/Classes/BWHockeyViewController.m new file mode 100644 index 0000000000..42e5ed6e60 --- /dev/null +++ b/Classes/BWHockeyViewController.m @@ -0,0 +1,626 @@ +// +// BWHockeyViewController.m +// +// Created by Andreas Linde on 8/17/10. +// Copyright 2010-2011 Andreas Linde, Peter Steinberger. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import "NSString+HockeyAdditions.h" +#import "BWHockeyViewController.h" +#import "BWHockeyManager.h" +#import "BWGlobal.h" +#import "UIImage+HockeyAdditions.h" +#import "PSWebTableViewCell.h" +#import "BWHockeySettingsViewController.h" + +#define BW_RGBCOLOR(r,g,b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1] +#define kWebCellIdentifier @"PSWebTableViewCell" +#define kAppStoreViewHeight 90 + +@interface BWHockeyViewController () +// updates the whole view +- (void)showPreviousVersionAction; +- (void)redrawTableView; +@property (nonatomic, assign) AppStoreButtonState appStoreButtonState; +- (void)setAppStoreButtonState:(AppStoreButtonState)anAppStoreButtonState animated:(BOOL)animated; +@end + + +@implementation BWHockeyViewController + +@synthesize appStoreButtonState = appStoreButtonState_; +@synthesize hockeyManager = hockeyManager_; +@synthesize modal = modal_; + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark private + +- (void)restoreStoreButtonStateAnimated_:(BOOL)animated { + if ([self.hockeyManager isUpdateAvailable]) { + [self setAppStoreButtonState:AppStoreButtonStateUpdate animated:animated]; + } else { + [self setAppStoreButtonState:AppStoreButtonStateCheck animated:animated]; + } +} + +- (void)updateAppStoreHeader_ { + BWApp *app = self.hockeyManager.app; + appStoreHeader_.headerLabel = app.name; + appStoreHeader_.middleHeaderLabel = [app versionString]; + NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; + [formatter setDateStyle:NSDateFormatterMediumStyle]; + NSMutableString *subHeaderString = [NSMutableString string]; + if (app.date) { + [subHeaderString appendString:[formatter stringFromDate:app.date]]; + } + if (app.size) { + if ([subHeaderString length]) { + [subHeaderString appendString:@" - "]; + } + [subHeaderString appendString:app.sizeInMB]; + } + appStoreHeader_.subHeaderLabel = subHeaderString; +} + +- (void)appDidBecomeActive_ { + if (self.appStoreButtonState == AppStoreButtonStateInstalling) { + [self setAppStoreButtonState:AppStoreButtonStateUpdate animated:YES]; + } else if (![self.hockeyManager isCheckInProgress]) { + [self restoreStoreButtonStateAnimated_:YES]; + } +} + +- (void)openSettings:(id)sender { + BWHockeySettingsViewController *settings = [[[BWHockeySettingsViewController alloc] init] autorelease]; + + Class popoverControllerClass = NSClassFromString(@"UIPopoverController"); + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad && popoverControllerClass) { + if (popOverController_ == nil) { + popOverController_ = [[popoverControllerClass alloc] initWithContentViewController:settings]; + } + if ([popOverController_ contentViewController].view.window) { + [popOverController_ dismissPopoverAnimated:YES]; + }else { + [popOverController_ setPopoverContentSize: CGSizeMake(320, 440)]; + [popOverController_ presentPopoverFromBarButtonItem:self.navigationItem.rightBarButtonItem + permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES]; + } + } else { + + BW_IF_3_2_OR_GREATER( + settings.modalTransitionStyle = UIModalTransitionStylePartialCurl; + [self presentModalViewController:settings animated:YES]; + ) + BW_IF_PRE_3_2( + UINavigationController *navController = [[[UINavigationController alloc] initWithRootViewController:settings] autorelease]; + navController.modalTransitionStyle = UIModalTransitionStyleCoverVertical; + [self presentModalViewController:navController animated:YES]; + ) + } +} + +- (UIImage *)addGlossToImage_:(UIImage *)image { + BW_IF_IOS4_OR_GREATER(UIGraphicsBeginImageContextWithOptions(image.size, NO, 0.0);) + BW_IF_PRE_IOS4(UIGraphicsBeginImageContext(image.size);) + + [image drawAtPoint:CGPointZero]; + UIImage *iconGradient = [UIImage bw_imageNamed:@"IconGradient.png" bundle:kHockeyBundleName]; + [iconGradient drawInRect:CGRectMake(0, 0, image.size.width, image.size.height) blendMode:kCGBlendModeNormal alpha:0.5]; + + UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return result; +} + +#define kMinPreviousVersionButtonHeight 50 +- (void)realignPreviousVersionButton { + + // manually collect actual table height size + NSUInteger tableViewContentHeight = 0; + for (int i=0; i < [self tableView:self.tableView numberOfRowsInSection:0]; i++) { + tableViewContentHeight += [self tableView:self.tableView heightForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]]; + } + tableViewContentHeight += self.tableView.tableHeaderView.frame.size.height; + + NSUInteger footerViewSize = kMinPreviousVersionButtonHeight; + NSUInteger frameHeight = self.view.frame.size.height; + if(tableViewContentHeight < frameHeight && (frameHeight - tableViewContentHeight > 100)) { + footerViewSize = frameHeight - tableViewContentHeight; + } + + // update footer view + if(self.tableView.tableFooterView) { + CGRect frame = self.tableView.tableFooterView.frame; + frame.size.height = footerViewSize; + self.tableView.tableFooterView.frame = frame; + } +} + +- (void)changePreviousVersionButtonBackground:(id)sender { + [(UIButton *)sender setBackgroundColor:BW_RGBCOLOR(183,183,183)]; +} + +- (void)changePreviousVersionButtonBackgroundHighlighted:(id)sender { + [(UIButton *)sender setBackgroundColor:BW_RGBCOLOR(183,183,183)]; +} + +- (void)showHidePreviousVersionsButton { + BOOL multipleVersionButtonNeeded = [self.hockeyManager.apps count] > 1 && !showAllVersions_; + + if(multipleVersionButtonNeeded) { + // align at the bottom if tableview is small + UIView *footerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, kMinPreviousVersionButtonHeight)]; + footerView.autoresizingMask = UIViewAutoresizingFlexibleWidth; + footerView.backgroundColor = BW_RGBCOLOR(200, 202, 204); + UIButton *footerButton = [UIButton buttonWithType:UIButtonTypeCustom]; + BW_IF_IOS4_OR_GREATER( + //footerButton.layer.shadowOffset = CGSizeMake(-2, 2); + footerButton.layer.shadowColor = [[UIColor blackColor] CGColor]; + footerButton.layer.shadowRadius = 2.0f; + ) + footerButton.titleLabel.font = [UIFont boldSystemFontOfSize:14]; + [footerButton setTitle:BWHockeyLocalize(@"HockeyShowPreviousVersions") forState:UIControlStateNormal]; + [footerButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; + [footerButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted]; + [footerButton setBackgroundImage:[UIImage bw_imageNamed:@"buttonHighlight.png" bundle:kHockeyBundleName] forState:UIControlStateHighlighted]; + footerButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin; + [footerButton addTarget:self action:@selector(showPreviousVersionAction) forControlEvents:UIControlEventTouchUpInside]; + footerButton.frame = CGRectMake(0, kMinPreviousVersionButtonHeight-44, self.view.frame.size.width, 44); + footerButton.backgroundColor = BW_RGBCOLOR(183,183,183); + [footerView addSubview:footerButton]; + self.tableView.tableFooterView = footerView; + [self realignPreviousVersionButton]; + [footerView release]; + } else { + self.tableView.tableFooterView = nil; + self.tableView.backgroundColor = BW_RGBCOLOR(200, 202, 204); + } +} + +- (void)configureWebCell:(PSWebTableViewCell *)cell forApp_:(BWApp *)app { + // create web view for a version + NSString *installed = @""; + if ([app.version isEqualToString:[self.hockeyManager currentAppVersion]]) { + installed = [NSString stringWithFormat:@"%@", [app isEqual:self.hockeyManager.app] ? @"left" : @"right", BWHockeyLocalize(@"HockeyInstalled")]; + } + + if ([app isEqual:self.hockeyManager.app]) { + if ([app.notes length] > 0) { + installed = [NSString stringWithFormat:@"

 %@

", installed]; + cell.webViewContent = [NSString stringWithFormat:@"%@%@", installed, app.notes]; + } else { + cell.webViewContent = [NSString stringWithFormat:@"
%@
", BWHockeyLocalize(@"HockeyNoReleaseNotesAvailable")]; + } + } else { + cell.webViewContent = [NSString stringWithFormat:@"

%@%@
%@

%@

", [app versionString], installed, [app dateString], [app notesOrEmptyString]]; + } + cell.cellBackgroundColor = BW_RGBCOLOR(200, 202, 204); + + [cell addWebView]; + // hack + cell.textLabel.text = @""; + + [cell addObserver:self forKeyPath:@"webViewSize" options:0 context:nil]; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark NSObject + +- (id)init:(BWHockeyManager *)newHockeyManager modal:(BOOL)newModal { + if ((self = [super initWithStyle:UITableViewStylePlain])) { + self.hockeyManager = newHockeyManager; + self.modal = newModal; + self.title = BWHockeyLocalize(@"HockeyUpdateScreenTitle"); + + if ([self.hockeyManager shouldShowUserSettings]) { + self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithImage:[UIImage bw_imageNamed:@"gear.png" bundle:kHockeyBundleName] + style:UIBarButtonItemStyleBordered + target:self + action:@selector(openSettings:)] autorelease]; + } + + cells_ = [[NSMutableArray alloc] initWithCapacity:5]; + popOverController_ = nil; + } + return self; +} + +- (id)init { + return [self init:[BWHockeyManager sharedHockeyManager] modal:NO]; +} + +- (void)dealloc { + [self viewDidUnload]; + for (UITableViewCell *cell in cells_) { + [cell removeObserver:self forKeyPath:@"webViewSize"]; + } + [cells_ release]; + [super dealloc]; +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark View lifecycle + +- (void)onAction:(id)sender { + if (self.modal) { + + // Note that as of 5.0, parentViewController will no longer return the presenting view controller + UIViewController *presentingViewController = nil; + + BW_IF_IOS5_OR_GREATER(presentingViewController = self.navigationController.presentingViewController;); + BW_IF_PRE_IOS5(presentingViewController = self.navigationController.parentViewController;) + + if (presentingViewController) { + [self.navigationController dismissModalViewControllerAnimated:YES]; + } else { + [self.navigationController.view removeFromSuperview]; + } + } + else + [self.navigationController popViewControllerAnimated:YES]; + + [[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle_]; +} + +- (CAGradientLayer *)backgroundLayer { + UIColor *colorOne = [UIColor colorWithWhite:0.9 alpha:1.0]; + UIColor *colorTwo = [UIColor colorWithHue:0.625 saturation:0.0 brightness:0.85 alpha:1.0]; + UIColor *colorThree = [UIColor colorWithHue:0.625 saturation:0.0 brightness:0.7 alpha:1.0]; + UIColor *colorFour = [UIColor colorWithHue:0.625 saturation:0.0 brightness:0.4 alpha:1.0]; + + NSArray *colors = [NSArray arrayWithObjects:(id)colorOne.CGColor, colorTwo.CGColor, colorThree.CGColor, colorFour.CGColor, nil]; + + NSNumber *stopOne = [NSNumber numberWithFloat:0.0]; + NSNumber *stopTwo = [NSNumber numberWithFloat:0.02]; + NSNumber *stopThree = [NSNumber numberWithFloat:0.99]; + NSNumber *stopFour = [NSNumber numberWithFloat:1.0]; + + NSArray *locations = [NSArray arrayWithObjects:stopOne, stopTwo, stopThree, stopFour, nil]; + + CAGradientLayer *headerLayer = [CAGradientLayer layer]; + //headerLayer.frame = CGRectMake(0.0, 0.0, 320.0, 77.0); + headerLayer.colors = colors; + headerLayer.locations = locations; + + return headerLayer; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + // add notifications only to loaded view + NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter]; + [dnc addObserver:self selector:@selector(appDidBecomeActive_) name:UIApplicationDidBecomeActiveNotification object:nil]; + + // hook into manager with kvo! + [self.hockeyManager addObserver:self forKeyPath:@"checkInProgress" options:0 context:nil]; + [self.hockeyManager addObserver:self forKeyPath:@"isUpdateURLOffline" options:0 context:nil]; + [self.hockeyManager addObserver:self forKeyPath:@"updateAvailable" options:0 context:nil]; + [self.hockeyManager addObserver:self forKeyPath:@"apps" options:0 context:nil]; + kvoRegistered_ = YES; + + self.tableView.backgroundColor = BW_RGBCOLOR(200, 202, 204); + self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; + + UIView *topView = [[[UIView alloc] initWithFrame:CGRectMake(0, -(600-kAppStoreViewHeight), self.view.frame.size.width, 600)] autorelease]; + topView.autoresizingMask = UIViewAutoresizingFlexibleWidth; + topView.backgroundColor = BW_RGBCOLOR(140, 141, 142); + [self.tableView addSubview:topView]; + + appStoreHeader_ = [[PSAppStoreHeader alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, kAppStoreViewHeight)]; + [self updateAppStoreHeader_]; + + NSString *iconString = nil; + NSArray *icons = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIconFiles"]; + if (!icons) { + iconString = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIconFile"]; + if (!iconString) { + iconString = @"Icon.png"; + } + } else { + BOOL useHighResIcon = NO; + BW_IF_IOS4_OR_GREATER(if ([UIScreen mainScreen].scale == 2.0f) useHighResIcon = YES;) + + for(NSString *icon in icons) { + iconString = icon; + UIImage *iconImage = [UIImage imageNamed:icon]; + + if (iconImage.size.height == 57 && !useHighResIcon) { + // found! + break; + } + if (iconImage.size.height == 114 && useHighResIcon) { + // found! + break; + } + } + } + + BOOL addGloss = YES; + NSNumber *prerendered = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIPrerenderedIcon"]; + if (prerendered) { + addGloss = ![prerendered boolValue]; + } + + if (addGloss) { + appStoreHeader_.iconImage = [self addGlossToImage_:[UIImage imageNamed:iconString]]; + } else { + appStoreHeader_.iconImage = [UIImage imageNamed:iconString]; + } + + self.tableView.tableHeaderView = appStoreHeader_; + + if (self.modal) { + self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone + target:self + action:@selector(onAction:)] autorelease]; + } + + PSStoreButton *storeButton = [[[PSStoreButton alloc] initWithPadding:CGPointMake(5, 40)] autorelease]; + storeButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; + storeButton.buttonDelegate = self; + [self.tableView.tableHeaderView addSubview:storeButton]; + storeButton.buttonData = [PSStoreButtonData dataWithLabel:@"" colors:[PSStoreButton appStoreGrayColor] enabled:NO]; + self.appStoreButtonState = AppStoreButtonStateCheck; + [storeButton alignToSuperview]; + appStoreButton_ = [storeButton retain]; +} + +- (void)viewWillAppear:(BOOL)animated { + self.hockeyManager.currentHockeyViewController = self; + [super viewWillAppear:animated]; + statusBarStyle_ = [[UIApplication sharedApplication] statusBarStyle]; + [[UIApplication sharedApplication] setStatusBarStyle:(self.navigationController.navigationBar.barStyle == UIBarStyleDefault) ? UIStatusBarStyleDefault : UIStatusBarStyleBlackOpaque]; + [self redrawTableView]; +} + +- (void)viewWillDisappear:(BOOL)animated { + self.hockeyManager.currentHockeyViewController = nil; + //if the popover is still visible, dismiss it + [popOverController_ dismissPopoverAnimated:YES]; + [super viewWillDisappear:animated]; + [[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle_]; +} + +- (void)redrawTableView { + [self restoreStoreButtonStateAnimated_:NO]; + [self updateAppStoreHeader_]; + + // clean up and remove any pending overservers + for (UITableViewCell *cell in cells_) { + [cell removeObserver:self forKeyPath:@"webViewSize"]; + } + [cells_ removeAllObjects]; + + int i = 0; + BOOL breakAfterThisApp = NO; + for (BWApp *app in self.hockeyManager.apps) { + i++; + + // only show the newer version of the app by default, if we don't show all versions + if (!showAllVersions_) { + if ([app.version isEqualToString:[self.hockeyManager currentAppVersion]]) { + if (i == 1) { + breakAfterThisApp = YES; + } else { + break; + } + } + } + + PSWebTableViewCell *cell = [[[PSWebTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kWebCellIdentifier] autorelease]; + [self configureWebCell:cell forApp_:app]; + [cells_ addObject:cell]; + + if (breakAfterThisApp) break; + } + + [self.tableView reloadData]; + [self showHidePreviousVersionsButton]; +} + +- (void)showPreviousVersionAction { + showAllVersions_ = YES; + BOOL showAllPending = NO; + + for (BWApp *app in self.hockeyManager.apps) { + if (!showAllPending) { + if ([app.version isEqualToString:[self.hockeyManager currentAppVersion]]) { + showAllPending = YES; + if (app == self.hockeyManager.app) { + continue; // skip this version already if it the latest version is the installed one + } + } else { + continue; // skip already shown + } + } + + PSWebTableViewCell *cell = [[[PSWebTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kWebCellIdentifier] autorelease]; + [self configureWebCell:cell forApp_:app]; + [cells_ addObject:cell]; + } + [self.tableView reloadData]; + [self showHidePreviousVersionsButton]; +} + +- (void)viewDidUnload { + [appStoreHeader_ release]; appStoreHeader_ = nil; + [popOverController_ release], popOverController_ = nil; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + // test if KVO's are registered. if class is destroyed before it was shown(viewDidLoad) no KVOs are registered. + if (kvoRegistered_) { + [self.hockeyManager removeObserver:self forKeyPath:@"checkInProgress"]; + [self.hockeyManager removeObserver:self forKeyPath:@"isUpdateURLOffline"]; + [self.hockeyManager removeObserver:self forKeyPath:@"updateAvailable"]; + [self.hockeyManager removeObserver:self forKeyPath:@"apps"]; + kvoRegistered_ = NO; + } + + [super viewDidUnload]; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark Table view data source + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + CGFloat rowHeight = 0; + + if ([cells_ count] > indexPath.row) { + PSWebTableViewCell *cell = [cells_ objectAtIndex:indexPath.row]; + rowHeight = cell.webViewSize.height; + } + + if ([self.hockeyManager.apps count] > 1 && !showAllVersions_) { + self.tableView.backgroundColor = BW_RGBCOLOR(183, 183, 183); + } + + if (rowHeight == 0) { + rowHeight = indexPath.row == 0 ? 250 : 44; // fill screen on startup + self.tableView.backgroundColor = BW_RGBCOLOR(200, 202, 204); + } + + return rowHeight; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + NSInteger cellCount = [cells_ count]; + return cellCount; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark KVO + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + // only make changes if we are visible + if(self.view.window) { + if ([keyPath isEqualToString:@"webViewSize"]) { + [self.tableView reloadData]; + [self realignPreviousVersionButton]; + } else if ([keyPath isEqualToString:@"checkInProgress"]) { + if (self.hockeyManager.isCheckInProgress) { + [self setAppStoreButtonState:AppStoreButtonStateSearching animated:YES]; + }else { + [self restoreStoreButtonStateAnimated_:YES]; + } + } else if ([keyPath isEqualToString:@"isUpdateURLOffline"]) { + [self restoreStoreButtonStateAnimated_:YES]; + } else if ([keyPath isEqualToString:@"updateAvailable"]) { + [self restoreStoreButtonStateAnimated_:YES]; + } else if ([keyPath isEqualToString:@"apps"]) { + [self redrawTableView]; + } + } +} + +// Customize the appearance of table view cells. +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + if ([cells_ count] > indexPath.row) { + return [cells_ objectAtIndex:indexPath.row]; + } else { + BWHockeyLog(@"Warning: cells_ and indexPath do not match? forgot calling redrawTableView?"); + } + return nil; +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark Rotation + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + BOOL shouldAutorotate; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { + shouldAutorotate = (interfaceOrientation == UIInterfaceOrientationLandscapeLeft || + interfaceOrientation == UIInterfaceOrientationLandscapeRight || + interfaceOrientation == UIInterfaceOrientationPortrait); + } else { + shouldAutorotate = YES; + } + + return shouldAutorotate; +} + +- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration { + // update all cells + [cells_ makeObjectsPerformSelector:@selector(addWebView)]; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark PSAppStoreHeaderDelegate + +- (void)setAppStoreButtonState:(AppStoreButtonState)anAppStoreButtonState { + [self setAppStoreButtonState:anAppStoreButtonState animated:NO]; +} + +- (void)setAppStoreButtonState:(AppStoreButtonState)anAppStoreButtonState animated:(BOOL)animated { + appStoreButtonState_ = anAppStoreButtonState; + + switch (anAppStoreButtonState) { + case AppStoreButtonStateOffline: + [appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonOffline") colors:[PSStoreButton appStoreGrayColor] enabled:NO] animated:animated]; + break; + case AppStoreButtonStateCheck: + [appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonCheck") colors:[PSStoreButton appStoreGreenColor] enabled:YES] animated:animated]; + break; + case AppStoreButtonStateSearching: + [appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonSearching") colors:[PSStoreButton appStoreGrayColor] enabled:NO] animated:animated]; + break; + case AppStoreButtonStateUpdate: + [appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonUpdate") colors:[PSStoreButton appStoreBlueColor] enabled:YES] animated:animated]; + break; + case AppStoreButtonStateInstalling: + [appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonInstalling") colors:[PSStoreButton appStoreGrayColor] enabled:NO] animated:animated]; + break; + default: + break; + } +} + +- (void)storeButtonFired:(PSStoreButton *)button { + switch (appStoreButtonState_) { + case AppStoreButtonStateCheck: + [self.hockeyManager checkForUpdateShowFeedback:YES]; + break; + case AppStoreButtonStateUpdate: + if ([self.hockeyManager initiateAppDownload]) { + [self setAppStoreButtonState:AppStoreButtonStateInstalling animated:YES]; + }; + break; + default: + break; + } +} + +@end diff --git a/Classes/BWQuincyManager.h b/Classes/BWQuincyManager.h new file mode 100755 index 0000000000..3efa41d36c --- /dev/null +++ b/Classes/BWQuincyManager.h @@ -0,0 +1,221 @@ +/* + * Author: Andreas Linde + * Kent Sutherland + * + * Copyright (c) 2011 Andreas Linde & Kent Sutherland. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#import +#import + +#define kQuincyBundleName @"Quincy.bundle" + +NSBundle *quincyBundle(void); +NSString *BWQuincyLocalize(NSString *stringToken); + +//#define BWQuincyLocalize(StringToken) NSLocalizedStringFromTableInBundle(StringToken, @"Quincy", quincyBundle(), @"") + +// flags if the crashlog analyzer is started. since this may theoretically crash we need to track it +#define kQuincyKitAnalyzerStarted @"QuincyKitAnalyzerStarted" + +// flags if the QuincyKit is activated at all +#define kQuincyKitActivated @"QuincyKitActivated" + +// flags if the crashreporter should automatically send crashes without asking the user again +#define kAutomaticallySendCrashReports @"AutomaticallySendCrashReports" + +// stores the set of crashreports that have been approved but aren't sent yet +#define kApprovedCrashReports @"ApprovedCrashReports" + +// Notification message which QuincyManager is listening to, to retry sending pending crash reports to the server +#define BWQuincyNetworkBecomeReachable @"NetworkDidBecomeReachable" + +typedef enum QuincyKitAlertType { + QuincyKitAlertTypeSend = 0, + QuincyKitAlertTypeFeedback = 1, +} CrashAlertType; + +typedef enum CrashReportStatus { + // The status of the crash is queued, need to check later (HockeyApp) + CrashReportStatusQueued = -80, + + // This app version is set to discontinued, no new crash reports accepted by the server + CrashReportStatusFailureVersionDiscontinued = -30, + + // XML: Sender version string contains not allowed characters, only alphanumberical including space and . are allowed + CrashReportStatusFailureXMLSenderVersionNotAllowed = -21, + + // XML: Version string contains not allowed characters, only alphanumberical including space and . are allowed + CrashReportStatusFailureXMLVersionNotAllowed = -20, + + // SQL for adding a symoblicate todo entry in the database failed + CrashReportStatusFailureSQLAddSymbolicateTodo = -18, + + // SQL for adding crash log in the database failed + CrashReportStatusFailureSQLAddCrashlog = -17, + + // SQL for adding a new version in the database failed + CrashReportStatusFailureSQLAddVersion = -16, + + // SQL for checking if the version is already added in the database failed + CrashReportStatusFailureSQLCheckVersionExists = -15, + + // SQL for creating a new pattern for this bug and set amount of occurrances to 1 in the database failed + CrashReportStatusFailureSQLAddPattern = -14, + + // SQL for checking the status of the bugfix version in the database failed + CrashReportStatusFailureSQLCheckBugfixStatus = -13, + + // SQL for updating the occurances of this pattern in the database failed + CrashReportStatusFailureSQLUpdatePatternOccurances = -12, + + // SQL for getting all the known bug patterns for the current app version in the database failed + CrashReportStatusFailureSQLFindKnownPatterns = -11, + + // SQL for finding the bundle identifier in the database failed + CrashReportStatusFailureSQLSearchAppName = -10, + + // the post request didn't contain valid data + CrashReportStatusFailureInvalidPostData = -3, + + // incoming data may not be added, because e.g. bundle identifier wasn't found + CrashReportStatusFailureInvalidIncomingData = -2, + + // database cannot be accessed, check hostname, username, password and database name settings in config.php + CrashReportStatusFailureDatabaseNotAvailable = -1, + + CrashReportStatusUnknown = 0, + + CrashReportStatusAssigned = 1, + + CrashReportStatusSubmitted = 2, + + CrashReportStatusAvailable = 3, + + CrashReportStatusDiscontinued = 4, + + CrashReportStatusMoreInfo = 5, + +} CrashReportStatus; + +// This protocol is used to send the image updates +@protocol BWQuincyManagerDelegate + +@optional + +// Return the userid the crashreport should contain, empty by default +-(NSString *) crashReportUserID; + +// Return the contact value (e.g. email) the crashreport should contain, empty by default +-(NSString *) crashReportContact; + +// Return the description the crashreport should contain, empty by default. The string will automatically be wrapped into <[DATA[ ]]>, so make sure you don't do that in your string. +-(NSString *) crashReportDescription; + +// Invoked when the internet connection is started, to let the app enable the activity indicator +-(void) connectionOpened; + +// Invoked when the internet connection is closed, to let the app disable the activity indicator +-(void) connectionClosed; + +-(void) askForCrashInfo:(NSString*)messageBody; + +// Invoked before the user is asked to send a crash report, so you can do additional actions. E.g. to make sure not to ask the user for an app rating :) +-(void) willShowSubmitCrashReportAlert; + +@end + +@interface BWQuincyManager : NSObject { + NSString *_submissionURL; + + id _delegate; + + BOOL _showAlwaysButton; + BOOL _feedbackActivated; + BOOL _autoSubmitCrashReport; + BOOL _autoSubmitDeviceUDID; + + NSString *_appIdentifier; + + NSString *_feedbackRequestID; + float _feedbackDelayInterval; + + NSMutableString *_contentOfProperty; + CrashReportStatus _serverResult; + + int _analyzerStarted; + NSString *_crashesDir; + + BOOL _crashIdenticalCurrentVersion; + BOOL _crashReportActivated; + + NSMutableArray *_crashFiles; + + NSMutableData *_responseData; + NSInteger _statusCode; + + NSURLConnection *_urlConnection; + + NSData *_crashData; + + NSString *_languageStyle; + BOOL _sendingInProgress; +} + ++ (BWQuincyManager *)sharedQuincyManager; + +// submission URL defines where to send the crash reports to (required) +@property (nonatomic, retain) NSString *submissionURL; + +// delegate is optional +@property (nonatomic, assign) id delegate; + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// settings + +// nil, using the default localization files (Default) +// set to another string which will be appended to the Quincy localization file name, "Alternate" is another provided text set +@property (nonatomic, retain) NSString *languageStyle; + +// if YES, the user will get the option to choose "Always" for sending crash reports. This will cause the dialog not to show the alert description text landscape mode! (default) +// if NO, the dialog will not show a "Always" button +@property (nonatomic, assign, getter=isShowingAlwaysButton) BOOL showAlwaysButton; + +// if YES, the user will be presented with a status of the crash, if known +// if NO, the user will not see any feedback information (default) +@property (nonatomic, assign, getter=isFeedbackActivated) BOOL feedbackActivated; + +// if YES, the crash report will be submitted without asking the user +// if NO, the user will be asked if the crash report can be submitted (default) +@property (nonatomic, assign, getter=isAutoSubmitCrashReport) BOOL autoSubmitCrashReport; + +// if YES, the device UDID will be submitted as the user id, without the need to define it in the crashReportUserID delegate (meant for beta versions!) +// if NO, the crashReportUserID delegate defines what to be sent as user id (default) +@property (nonatomic, assign, getter=isAutoSubmitDeviceUDID) BOOL autoSubmitDeviceUDID; + +// If you want to use HockeyApp instead of your own server, this is required +@property (nonatomic, retain) NSString *appIdentifier; + +@end diff --git a/Classes/BWQuincyManager.m b/Classes/BWQuincyManager.m new file mode 100755 index 0000000000..6fa8a1d20e --- /dev/null +++ b/Classes/BWQuincyManager.m @@ -0,0 +1,772 @@ +/* + * Author: Andreas Linde + * Kent Sutherland + * + * Copyright (c) 2011 Andreas Linde & Kent Sutherland. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#import +#import +#import +#import "BWQuincyManager.h" + +#include +#include //needed for PRIx64 macro + +NSBundle *quincyBundle(void) { + static NSBundle* bundle = nil; + if (!bundle) { + NSString* path = [[[NSBundle mainBundle] resourcePath] + stringByAppendingPathComponent:kQuincyBundleName]; + bundle = [[NSBundle bundleWithPath:path] retain]; + } + return bundle; +} + +NSString *BWQuincyLocalize(NSString *stringToken) { + if ([BWQuincyManager sharedQuincyManager].languageStyle == nil) + return NSLocalizedStringFromTableInBundle(stringToken, @"Quincy", quincyBundle(), @""); + else { + NSString *alternate = [NSString stringWithFormat:@"Quincy%@", [BWQuincyManager sharedQuincyManager].languageStyle]; + return NSLocalizedStringFromTableInBundle(stringToken, alternate, quincyBundle(), @""); + } +} + + +@interface BWQuincyManager () + +- (void)startManager; + +- (void)showCrashStatusMessage; + +- (void)handleCrashReport; +- (void)_cleanCrashReports; + +- (void)_checkForFeedbackStatus; + +- (void)_performSendingCrashReports; +- (void)_sendCrashReports; + +- (void)_postXML:(NSString*)xml toURL:(NSURL*)url; +- (NSString *)_getDevicePlatform; + +- (BOOL)hasNonApprovedCrashReports; +- (BOOL)hasPendingCrashReport; + +@end + +@implementation BWQuincyManager + +@synthesize delegate = _delegate; +@synthesize submissionURL = _submissionURL; +@synthesize showAlwaysButton = _showAlwaysButton; +@synthesize feedbackActivated = _feedbackActivated; +@synthesize autoSubmitCrashReport = _autoSubmitCrashReport; +@synthesize autoSubmitDeviceUDID = _autoSubmitDeviceUDID; +@synthesize languageStyle = _languageStyle; + +@synthesize appIdentifier = _appIdentifier; + +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 40000 ++(BWQuincyManager *)sharedQuincyManager { + static BWQuincyManager *sharedInstance = nil; + static dispatch_once_t pred; + + dispatch_once(&pred, ^{ + sharedInstance = [BWQuincyManager alloc]; + sharedInstance = [sharedInstance init]; + }); + + return sharedInstance; +} +#else ++ (BWQuincyManager *)sharedQuincyManager { + static BWQuincyManager *quincyManager = nil; + + if (quincyManager == nil) { + quincyManager = [[BWQuincyManager alloc] init]; + } + + return quincyManager; +} +#endif + +- (id) init { + if ((self = [super init])) { + _serverResult = CrashReportStatusUnknown; + _crashIdenticalCurrentVersion = YES; + _crashData = nil; + _urlConnection = nil; + _submissionURL = nil; + _responseData = nil; + _appIdentifier = nil; + _sendingInProgress = NO; + _languageStyle = nil; + + self.delegate = nil; + self.feedbackActivated = NO; + self.showAlwaysButton = NO; + self.autoSubmitCrashReport = NO; + self.autoSubmitDeviceUDID = NO; + + NSString *testValue = [[NSUserDefaults standardUserDefaults] stringForKey:kQuincyKitAnalyzerStarted]; + if (testValue) { + _analyzerStarted = [[NSUserDefaults standardUserDefaults] integerForKey:kQuincyKitAnalyzerStarted]; + } else { + _analyzerStarted = 0; + } + + testValue = nil; + testValue = [[NSUserDefaults standardUserDefaults] stringForKey:kQuincyKitActivated]; + if (testValue) { + _crashReportActivated = [[NSUserDefaults standardUserDefaults] boolForKey:kQuincyKitActivated]; + } else { + _crashReportActivated = YES; + [[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithBool:YES] forKey:kQuincyKitActivated]; + } + + if ([[NSUserDefaults standardUserDefaults] stringForKey:kAutomaticallySendCrashReports]) { + self.autoSubmitCrashReport = [[NSUserDefaults standardUserDefaults] boolForKey: kAutomaticallySendCrashReports]; + } + + if (_crashReportActivated) { + _crashFiles = [[NSMutableArray alloc] init]; + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + _crashesDir = [[NSString stringWithFormat:@"%@", [[paths objectAtIndex:0] stringByAppendingPathComponent:@"/crashes/"]] retain]; + + NSFileManager *fm = [NSFileManager defaultManager]; + + if (![fm fileExistsAtPath:_crashesDir]) { + NSDictionary *attributes = [NSDictionary dictionaryWithObject: [NSNumber numberWithUnsignedLong: 0755] forKey: NSFilePosixPermissions]; + NSError *theError = NULL; + + [fm createDirectoryAtPath:_crashesDir withIntermediateDirectories: YES attributes: attributes error: &theError]; + } + + PLCrashReporter *crashReporter = [PLCrashReporter sharedReporter]; + NSError *error = NULL; + + // Check if we previously crashed + if ([crashReporter hasPendingCrashReport]) { + [self handleCrashReport]; + } + + // Enable the Crash Reporter + if (![crashReporter enableCrashReporterAndReturnError: &error]) + NSLog(@"Warning: Could not enable crash reporter: %@", error); + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(startManager) name:BWQuincyNetworkBecomeReachable object:nil]; + } + } + return self; +} + + +- (void) dealloc { + self.delegate = nil; + [[NSNotificationCenter defaultCenter] removeObserver:self name:BWQuincyNetworkBecomeReachable object:nil]; + + [_languageStyle release]; + + [_submissionURL release]; + _submissionURL = nil; + + [_appIdentifier release]; + _appIdentifier = nil; + + [_urlConnection cancel]; + [_urlConnection release]; + _urlConnection = nil; + + [_crashData release]; + + [_crashesDir release]; + [_crashFiles release]; + + [super dealloc]; +} + + +#pragma mark - +#pragma mark setter +- (void)setSubmissionURL:(NSString *)anSubmissionURL { + if (_submissionURL != anSubmissionURL) { + [_submissionURL release]; + _submissionURL = [anSubmissionURL copy]; + } + + [self performSelector:@selector(startManager) withObject:nil afterDelay:1.0f]; +} + +- (void)setAppIdentifier:(NSString *)anAppIdentifier { + if (_appIdentifier != anAppIdentifier) { + [_appIdentifier release]; + _appIdentifier = [anAppIdentifier copy]; + } + + [self setSubmissionURL:@"https://rink.hockeyapp.net/"]; +} + +#pragma mark - +#pragma mark private methods + +// begin the startup process +- (void)startManager { + if (!_sendingInProgress && [self hasPendingCrashReport]) { + _sendingInProgress = YES; + if (!self.autoSubmitCrashReport && [self hasNonApprovedCrashReports]) { + if (self.delegate != nil && [self.delegate respondsToSelector:@selector(willShowSubmitCrashReportAlert)]) { + [self.delegate willShowSubmitCrashReportAlert]; + } + + NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]; + + UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:[NSString stringWithFormat:BWQuincyLocalize(@"CrashDataFoundTitle"), appName] + message:[NSString stringWithFormat:BWQuincyLocalize(@"CrashDataFoundDescription"), appName] + delegate:self + cancelButtonTitle:BWQuincyLocalize(@"CrashDontSendReport") + otherButtonTitles:BWQuincyLocalize(@"CrashSendReport"), nil]; + + if ([self isShowingAlwaysButton]) { + [alertView addButtonWithTitle:BWQuincyLocalize(@"CrashSendReportAlways")]; + } + + [alertView setTag: QuincyKitAlertTypeSend]; + [alertView show]; + [alertView release]; + } else { + [self _sendCrashReports]; + } + } +} + +- (BOOL)hasNonApprovedCrashReports { + NSDictionary *approvedCrashReports = [[NSUserDefaults standardUserDefaults] dictionaryForKey: kApprovedCrashReports]; + + if (!approvedCrashReports || [approvedCrashReports count] == 0) return YES; + + for (NSUInteger i=0; i < [_crashFiles count]; i++) { + NSString *filename = [_crashFiles objectAtIndex:i]; + + if (![approvedCrashReports objectForKey:filename]) return YES; + } + + return NO; +} + +- (BOOL)hasPendingCrashReport { + if (_crashReportActivated) { + NSFileManager *fm = [NSFileManager defaultManager]; + + if ([_crashFiles count] == 0 && [fm fileExistsAtPath:_crashesDir]) { + NSString *file = nil; + NSError *error = NULL; + + NSDirectoryEnumerator *dirEnum = [fm enumeratorAtPath: _crashesDir]; + + while ((file = [dirEnum nextObject])) { + NSDictionary *fileAttributes = [fm attributesOfItemAtPath:[_crashesDir stringByAppendingPathComponent:file] error:&error]; + if ([[fileAttributes objectForKey:NSFileSize] intValue] > 0) { + [_crashFiles addObject:file]; + } + } + } + + if ([_crashFiles count] > 0) { + return YES; + } else + return NO; + } else + return NO; +} + + +- (void) showCrashStatusMessage { + UIAlertView *alertView = nil; + + if (_serverResult >= CrashReportStatusAssigned && + _crashIdenticalCurrentVersion) { + // show some feedback to the user about the crash status + NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]; + switch (_serverResult) { + case CrashReportStatusAssigned: + alertView = [[UIAlertView alloc] initWithTitle: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseTitle"), appName ] + message: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseNextRelease"), appName] + delegate: self + cancelButtonTitle: BWQuincyLocalize(@"CrashResponseTitleOK") + otherButtonTitles: nil]; + break; + case CrashReportStatusSubmitted: + alertView = [[UIAlertView alloc] initWithTitle: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseTitle"), appName ] + message: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseWaitingApple"), appName] + delegate: self + cancelButtonTitle: BWQuincyLocalize(@"CrashResponseTitleOK") + otherButtonTitles: nil]; + break; + case CrashReportStatusAvailable: + alertView = [[UIAlertView alloc] initWithTitle: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseTitle"), appName ] + message: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseAvailable"), appName] + delegate: self + cancelButtonTitle: BWQuincyLocalize(@"CrashResponseTitleOK") + otherButtonTitles: nil]; + break; + case CrashReportStatusMoreInfo: + if ([MFMailComposeViewController canSendMail]) { + alertView = [[UIAlertView alloc] initWithTitle: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseTitle"), appName ] + message: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseMoreInfo"), appName] + delegate: self + cancelButtonTitle: BWQuincyLocalize(@"Skip") + otherButtonTitles: BWQuincyLocalize(@"Send Email"), nil]; + } + break; + default: + alertView = nil; + break; + } + + if (alertView) { + [alertView setTag: QuincyKitAlertTypeFeedback]; + [alertView show]; + [alertView release]; + } + } +} + + +#pragma mark - +#pragma mark UIAlertView Delegate + +- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex { + + switch ([alertView tag]) { + case QuincyKitAlertTypeSend: + switch (buttonIndex) { + case 0: + _sendingInProgress = NO; + [self _cleanCrashReports]; + break; + case 1: + [self _sendCrashReports]; + break; + case 2: + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kAutomaticallySendCrashReports]; + + [self _sendCrashReports]; + break; + } + break; + case QuincyKitAlertTypeFeedback: + switch (buttonIndex) { + case 0: + break; + case 1: + [self.delegate askForCrashInfo:@"Please describe what you were doing when the crash occured:\n\n"]; + } + break; + + default: + break; + } +} + + +#pragma mark - +#pragma mark NSXMLParser Delegate + +#pragma mark NSXMLParser + +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict { + if (qName) { + elementName = qName; + } + + if ([elementName isEqualToString:@"result"]) { + _contentOfProperty = [NSMutableString string]; + } +} + +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { + if (qName) { + elementName = qName; + } + + // open source implementation + if ([elementName isEqualToString: @"result"]) { + if ([_contentOfProperty intValue] > _serverResult) { + _serverResult = (CrashReportStatus)[_contentOfProperty intValue]; + } + CrashReportStatus errorcode = (CrashReportStatus)[_contentOfProperty intValue]; + NSLog(@"CrashReporter ended in error code: %i", errorcode); + } +} + + +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { + if (_contentOfProperty) { + // If the current element is one whose content we care about, append 'string' + // to the property that holds the content of the current element. + if (string != nil) { + [_contentOfProperty appendString:string]; + } + } +} + +#pragma mark - +#pragma mark Private + + +- (NSString *)_getOSVersionBuild { + size_t size = 0; + NSString *osBuildVersion = nil; + + sysctlbyname("kern.osversion", NULL, &size, NULL, 0); + char *answer = (char*)malloc(size); + int result = sysctlbyname("kern.osversion", answer, &size, NULL, 0); + if (result >= 0) { + osBuildVersion = [NSString stringWithCString:answer encoding: NSUTF8StringEncoding]; + } + + return osBuildVersion; +} + +- (NSString *)_getDevicePlatform { + size_t size = 0; + sysctlbyname("hw.machine", NULL, &size, NULL, 0); + char *answer = (char*)malloc(size); + sysctlbyname("hw.machine", answer, &size, NULL, 0); + NSString *platform = [NSString stringWithCString:answer encoding: NSUTF8StringEncoding]; + free(answer); + return platform; +} + +- (NSString *)deviceIdentifier { + if ([[UIDevice currentDevice] respondsToSelector:@selector(uniqueIdentifier)]) { + return [[UIDevice currentDevice] performSelector:@selector(uniqueIdentifier)]; + } + else { + return @"invalid"; + } +} + +- (void)_performSendingCrashReports { + NSMutableDictionary *approvedCrashReports = [NSMutableDictionary dictionaryWithDictionary:[[NSUserDefaults standardUserDefaults] dictionaryForKey: kApprovedCrashReports]]; + + NSFileManager *fm = [NSFileManager defaultManager]; + NSError *error = NULL; + + NSString *userid = @""; + NSString *contact = @""; + NSString *description = @""; + + if (self.autoSubmitDeviceUDID) { + userid = [self deviceIdentifier]; + } else if (self.delegate != nil && [self.delegate respondsToSelector:@selector(crashReportUserID)]) { + userid = [self.delegate crashReportUserID] ?: @""; + } + + if (self.delegate != nil && [self.delegate respondsToSelector:@selector(crashReportContact)]) { + contact = [self.delegate crashReportContact] ?: @""; + } + + if (self.delegate != nil && [self.delegate respondsToSelector:@selector(crashReportDescription)]) { + description = [self.delegate crashReportDescription] ?: @""; + } + + NSMutableString *crashes = nil; + _crashIdenticalCurrentVersion = NO; + + for (NSUInteger i=0; i < [_crashFiles count]; i++) { + NSString *filename = [_crashesDir stringByAppendingPathComponent:[_crashFiles objectAtIndex:i]]; + NSData *crashData = [NSData dataWithContentsOfFile:filename]; + + if ([crashData length] > 0) { + PLCrashReport *report = [[[PLCrashReport alloc] initWithData:crashData error:&error] autorelease]; + + if (report == nil) { + NSLog(@"Could not parse crash report"); + continue; + } + + NSString *crashLogString = [PLCrashReportTextFormatter stringValueForCrashReport:report withTextFormat:PLCrashReportTextFormatiOS]; + + if ([report.applicationInfo.applicationVersion compare:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]] == NSOrderedSame) { + _crashIdenticalCurrentVersion = YES; + } + + if (crashes == nil) { + crashes = [NSMutableString string]; + } + + [crashes appendFormat:@"%s%@%@%@%@%@%@%@", + [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleExecutable"] UTF8String], + report.applicationInfo.applicationIdentifier, + report.systemInfo.operatingSystemVersion, + [self _getDevicePlatform], + [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"], + report.applicationInfo.applicationVersion, + crashLogString, + userid, + contact, + description]; + + // store this crash report as user approved, so if it fails it will retry automatically + [approvedCrashReports setObject:[NSNumber numberWithBool:YES] forKey:[_crashFiles objectAtIndex:i]]; + } else { + // we cannot do anything with this report, so delete it + [fm removeItemAtPath:filename error:&error]; + } + } + + [[NSUserDefaults standardUserDefaults] setObject:approvedCrashReports forKey:kApprovedCrashReports]; + [[NSUserDefaults standardUserDefaults] synchronize]; + + if (crashes != nil) { + [self _postXML:[NSString stringWithFormat:@"%@", crashes] + toURL:[NSURL URLWithString:self.submissionURL]]; + + } +} + +- (void)_cleanCrashReports { + NSError *error = NULL; + + NSFileManager *fm = [NSFileManager defaultManager]; + + for (NSUInteger i=0; i < [_crashFiles count]; i++) { + [fm removeItemAtPath:[_crashesDir stringByAppendingPathComponent:[_crashFiles objectAtIndex:i]] error:&error]; + } + [_crashFiles removeAllObjects]; + + [[NSUserDefaults standardUserDefaults] setObject:nil forKey:kApprovedCrashReports]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} + +- (void)_sendCrashReports { + // send it to the next runloop + [self performSelector:@selector(_performSendingCrashReports) withObject:nil afterDelay:0.0f]; +} + +- (void)_checkForFeedbackStatus { + NSMutableURLRequest *request = nil; + + request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@api/2/apps/%@/crashes/%@", + self.submissionURL, + [self.appIdentifier stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding], + _feedbackRequestID + ] + ]]; + + [request setCachePolicy: NSURLRequestReloadIgnoringLocalCacheData]; + [request setValue:@"Quincy/iOS" forHTTPHeaderField:@"User-Agent"]; + [request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"]; + [request setTimeoutInterval: 15]; + [request setHTTPMethod:@"GET"]; + + _serverResult = CrashReportStatusUnknown; + _statusCode = 200; + + // Release when done in the delegate method + _responseData = [[NSMutableData alloc] init]; + + if (self.delegate != nil && [self.delegate respondsToSelector:@selector(connectionOpened)]) { + [self.delegate connectionOpened]; + } + + _urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; +} + +- (void)_postXML:(NSString*)xml toURL:(NSURL*)url { + NSMutableURLRequest *request = nil; + NSString *boundary = @"----FOO"; + + if (self.appIdentifier) { + request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@api/2/apps/%@/crashes", + self.submissionURL, + [self.appIdentifier stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] + ] + ]]; + } else { + request = [NSMutableURLRequest requestWithURL:url]; + } + + [request setCachePolicy: NSURLRequestReloadIgnoringLocalCacheData]; + [request setValue:@"Quincy/iOS" forHTTPHeaderField:@"User-Agent"]; + [request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"]; + [request setTimeoutInterval: 15]; + [request setHTTPMethod:@"POST"]; + NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]; + [request setValue:contentType forHTTPHeaderField:@"Content-type"]; + + NSMutableData *postBody = [NSMutableData data]; + [postBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; + if (self.appIdentifier) { + [postBody appendData:[@"Content-Disposition: form-data; name=\"xml\"; filename=\"crash.xml\"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; + [postBody appendData:[[NSString stringWithFormat:@"Content-Type: text/xml\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; + } else { + [postBody appendData:[@"Content-Disposition: form-data; name=\"xmlstring\"\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; + } + [postBody appendData:[xml dataUsingEncoding:NSUTF8StringEncoding]]; + [postBody appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; + + [request setHTTPBody:postBody]; + + _serverResult = CrashReportStatusUnknown; + _statusCode = 200; + + //Release when done in the delegate method + _responseData = [[NSMutableData alloc] init]; + + if (self.delegate != nil && [self.delegate respondsToSelector:@selector(connectionOpened)]) { + [self.delegate connectionOpened]; + } + + _urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; + + if (!_urlConnection) { + _sendingInProgress = NO; + } +} + +#pragma mark NSURLConnection Delegate + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { + if ([response isKindOfClass:[NSHTTPURLResponse class]]) { + _statusCode = [(NSHTTPURLResponse *)response statusCode]; + } +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { + [_responseData appendData:data]; +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { + [_responseData release]; + _responseData = nil; + _urlConnection = nil; + + if (self.delegate != nil && [self.delegate respondsToSelector:@selector(connectionClosed)]) { + [self.delegate connectionClosed]; + } + + _sendingInProgress = NO; +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection { + if (_statusCode >= 200 && _statusCode < 400) { + [self _cleanCrashReports]; + + if (self.appIdentifier) { + // HockeyApp uses PList XML format + NSMutableDictionary *response = [NSPropertyListSerialization propertyListFromData:_responseData + mutabilityOption:NSPropertyListMutableContainersAndLeaves + format:nil + errorDescription:NULL]; + _serverResult = (CrashReportStatus)[[response objectForKey:@"status"] intValue]; + _feedbackRequestID = [[NSString alloc] initWithString:[response objectForKey:@"id"]]; + _feedbackDelayInterval = [[response objectForKey:@"delay"] floatValue]; + if (_feedbackDelayInterval > 0) + _feedbackDelayInterval *= 0.01; + } else { + NSXMLParser *parser = [[NSXMLParser alloc] initWithData:_responseData]; + // Set self as the delegate of the parser so that it will receive the parser delegate methods callbacks. + [parser setDelegate:self]; + // Depending on the XML document you're parsing, you may want to enable these features of NSXMLParser. + [parser setShouldProcessNamespaces:NO]; + [parser setShouldReportNamespacePrefixes:NO]; + [parser setShouldResolveExternalEntities:NO]; + + [parser parse]; + + [parser release]; + } + + if ([self isFeedbackActivated]) { + if (self.appIdentifier) { + // only proceed if the server did not report any problem + if (_serverResult == CrashReportStatusQueued) { + // the report is still in the queue + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_checkForFeedbackStatus) object:nil]; + [self performSelector:@selector(_checkForFeedbackStatus) withObject:nil afterDelay:_feedbackDelayInterval]; + } else { + // we do have a status, show it if needed + [self showCrashStatusMessage]; + } + } else { + [self showCrashStatusMessage]; + } + } + } + + [_responseData release]; + _responseData = nil; + _urlConnection = nil; + + if (self.delegate != nil && [self.delegate respondsToSelector:@selector(connectionClosed)]) { + [self.delegate connectionClosed]; + } + + _sendingInProgress = NO; +} + +#pragma mark PLCrashReporter + +// +// Called to handle a pending crash report. +// +- (void) handleCrashReport { + PLCrashReporter *crashReporter = [PLCrashReporter sharedReporter]; + NSError *error = NULL; + + // check if the next call ran successfully the last time + if (_analyzerStarted == 0) { + // mark the start of the routine + _analyzerStarted = 1; + [[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithInt:_analyzerStarted] forKey:kQuincyKitAnalyzerStarted]; + [[NSUserDefaults standardUserDefaults] synchronize]; + + // Try loading the crash report + _crashData = [[NSData alloc] initWithData:[crashReporter loadPendingCrashReportDataAndReturnError: &error]]; + + NSString *cacheFilename = [NSString stringWithFormat: @"%.0f", [NSDate timeIntervalSinceReferenceDate]]; + + if (_crashData == nil) { + NSLog(@"Could not load crash report: %@", error); + } else { + [_crashData writeToFile:[_crashesDir stringByAppendingPathComponent: cacheFilename] atomically:YES]; + } + } + + // Purge the report + // mark the end of the routine + _analyzerStarted = 0; + [[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithInt:_analyzerStarted] forKey:kQuincyKitAnalyzerStarted]; + [[NSUserDefaults standardUserDefaults] synchronize]; + + [crashReporter purgePendingCrashReport]; + return; +} + + +@end diff --git a/Classes/CNSHockeyManager.h b/Classes/CNSHockeyManager.h new file mode 100644 index 0000000000..c99bdb7dc3 --- /dev/null +++ b/Classes/CNSHockeyManager.h @@ -0,0 +1,29 @@ +// Copyright 2011 Codenauts UG (haftungsbeschränkt). All rights reserved. +// See LICENSE.txt for author information. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + + +@interface CNSHockeyManager : NSObject + ++ (CNSHockeyManager *)sharedHockeyManager; + +- (void)configureWithIdentifier:(NSString *)appIdentifier delegate:(id)delegate; + +@end diff --git a/Classes/CNSHockeyManager.m b/Classes/CNSHockeyManager.m new file mode 100644 index 0000000000..e9c601c16c --- /dev/null +++ b/Classes/CNSHockeyManager.m @@ -0,0 +1,111 @@ +// Copyright 2011 Codenauts UG (haftungsbeschränkt). All rights reserved. +// See LICENSE.txt for author information. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "CNSHockeyManager.h" +#import "BWQuincyManager.h" +#import "BWHockeyManager.h" + +#ifdef JMC_PRESENT +#import "JMC.h" +#endif + +@interface CNSHockeyManager () + +#ifdef JMC_PRESENT +- (void)configureJMC; +#endif + +@end + +@implementation CNSHockeyManager + +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 40000 ++ (CNSHockeyManager *)sharedHockeyManager { + static CNSHockeyManager *sharedInstance = nil; + static dispatch_once_t pred; + + if (sharedInstance) { + return sharedInstance; + } + + dispatch_once(&pred, ^{ + sharedInstance = [CNSHockeyManager alloc]; + sharedInstance = [sharedInstance init]; + }); + + return sharedInstance; +} +#else ++ (CNSHockeyManager *)sharedHockeyManager { + static CNSHockeyManager *hockeyManager = nil; + + if (hockeyManager == nil) { + hockeyManager = [[CNSHockeyManager alloc] init]; + } + + return hockeyManager; +} +#endif + +- (void)configureWithIdentifier:(NSString *)appIdentifier delegate:(id)delegate { + // Crash Reporting + [[BWQuincyManager sharedQuincyManager] setAppIdentifier:appIdentifier]; + + // Distribution + [[BWHockeyManager sharedHockeyManager] setAppIdentifier:appIdentifier]; + [[BWHockeyManager sharedHockeyManager] setUpdateURL:@"http://192.168.178.53:3000"]; + [[BWHockeyManager sharedHockeyManager] setCheckForTracker:YES]; + +#ifdef JMC_PRESENT + // JMC + [[[JMC instance] options] setCrashReportingEnabled:NO]; + [[BWHockeyManager sharedHockeyManager] addObserver:self forKeyPath:@"trackerConfig" options:0 context:nil]; + [self performSelector:@selector(configureJMC) withObject:nil afterDelay:0]; +#endif +} + +#ifdef JMC_PRESENT +- (void)configureJMC { + // Return if JMC is already configured + if ([[JMC instance] url]) { + return; + } + + // Configure JMC from user defaults + NSDictionary *config = [[NSUserDefaults standardUserDefaults] valueForKey:@"CNSTrackerConfig"]; + if (([[config valueForKey:@"enabled"] boolValue]) && + ([[config valueForKey:@"url"] length] > 0) && + ([[config valueForKey:@"key"] length] > 0) && + ([[config valueForKey:@"project"] length] > 0)) { + [[JMC instance] configureJiraConnect:[config valueForKey:@"url"] projectKey:[config valueForKey:@"project"] apiKey:[config valueForKey:@"key"]]; + } +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if ([object trackerConfig]) { + [[NSUserDefaults standardUserDefaults] setValue:[object trackerConfig] forKey:@"CNSTrackerConfig"]; + [[NSUserDefaults standardUserDefaults] synchronize]; + [self configureJMC]; + } +} +#endif + +@end diff --git a/Classes/NSString+HockeyAdditions.h b/Classes/NSString+HockeyAdditions.h new file mode 100644 index 0000000000..d32e13d4a6 --- /dev/null +++ b/Classes/NSString+HockeyAdditions.h @@ -0,0 +1,33 @@ +// +// NSString+HockeyAdditions.h +// +// Created by Jon Crosby on 10/19/07. +// Copyright 2007 Kaboomerang LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + + +#import + +@interface NSString (HockeyAdditions) + +- (NSString *)bw_URLEncodedString; +- (NSString *)bw_URLDecodedString; + +@end diff --git a/Classes/NSString+HockeyAdditions.m b/Classes/NSString+HockeyAdditions.m new file mode 100644 index 0000000000..5db3528dfe --- /dev/null +++ b/Classes/NSString+HockeyAdditions.m @@ -0,0 +1,50 @@ +// +// NSString+HockeyAdditions.m +// +// Created by Jon Crosby on 10/19/07. +// Copyright 2007 Kaboomerang LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + + +#import "NSString+HockeyAdditions.h" + + +@implementation NSString (HockeyAdditions) + +- (NSString *)bw_URLEncodedString { + NSString *result = (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, + (CFStringRef)self, + NULL, + CFSTR("!*'();:@&=+$,/?%#[]"), + kCFStringEncodingUTF8); + [result autorelease]; + return result; +} + +- (NSString*)bw_URLDecodedString { + NSString *result = (NSString *)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, + (CFStringRef)self, + CFSTR(""), + kCFStringEncodingUTF8); + [result autorelease]; + return result; +} + +@end diff --git a/Classes/PSAppStoreHeader.h b/Classes/PSAppStoreHeader.h new file mode 100644 index 0000000000..1d9a8f7a3e --- /dev/null +++ b/Classes/PSAppStoreHeader.h @@ -0,0 +1,42 @@ +// +// PSAppStoreHeader.h +// HockeyDemo +// +// Created by Peter Steinberger on 09.01.11. +// Copyright 2011 Peter Steinberger. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +@interface PSAppStoreHeader : UIView { + NSString *headerLabel_; + NSString *middleHeaderLabel_; + NSString *subHeaderLabel; + UIImage *iconImage_; + + UIImage *reflectedImage_; +} + +@property (nonatomic, copy) NSString *headerLabel; +@property (nonatomic, copy) NSString *middleHeaderLabel; +@property (nonatomic, copy) NSString *subHeaderLabel; +@property (nonatomic, retain) UIImage *iconImage; + +@end diff --git a/Classes/PSAppStoreHeader.m b/Classes/PSAppStoreHeader.m new file mode 100644 index 0000000000..c7898aeca0 --- /dev/null +++ b/Classes/PSAppStoreHeader.m @@ -0,0 +1,173 @@ +// +// PSAppStoreHeader.m +// HockeyDemo +// +// Created by Peter Steinberger on 09.01.11. +// Copyright 2011 Peter Steinberger. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "PSAppStoreHeader.h" +#import "UIImage+HockeyAdditions.h" +#import "BWGlobal.h" + +#define BW_RGBCOLOR(r,g,b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1] + +#define kLightGrayColor BW_RGBCOLOR(200, 202, 204) +#define kDarkGrayColor BW_RGBCOLOR(140, 141, 142) + +#define kImageHeight 57 +#define kReflectionHeight 20 +#define kImageBorderRadius 10 +#define kImageMargin 8 +#define kTextRow kImageMargin*2 + kImageHeight + +@implementation PSAppStoreHeader + +@synthesize headerLabel = headerLabel_; +@synthesize middleHeaderLabel = middleHeaderLabel_; +@synthesize subHeaderLabel; +@synthesize iconImage = iconImage_; + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark NSObject + +- (id)initWithFrame:(CGRect)frame { + if ((self = [super initWithFrame:frame])) { + self.autoresizingMask = UIViewAutoresizingFlexibleWidth; + self.backgroundColor = kLightGrayColor; + } + return self; +} + +- (void)dealloc { + [headerLabel_ release]; + [middleHeaderLabel_ release]; + [subHeaderLabel release]; + [iconImage_ release]; + + [super dealloc]; +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark UIView + +- (void)drawRect:(CGRect)rect { + CGRect bounds = self.bounds; + CGFloat globalWidth = self.frame.size.width; + CGContextRef context = UIGraphicsGetCurrentContext(); + + // draw the gradient + NSArray *colors = [NSArray arrayWithObjects:(id)kDarkGrayColor.CGColor, (id)kLightGrayColor.CGColor, nil]; + CGGradientRef gradient = CGGradientCreateWithColors(CGColorGetColorSpace((CGColorRef)[colors objectAtIndex:0]), (CFArrayRef)colors, (CGFloat[2]){0, 1}); + CGPoint top = CGPointMake(CGRectGetMidX(bounds), bounds.origin.y); + CGPoint bottom = CGPointMake(CGRectGetMidX(bounds), CGRectGetMaxY(bounds)-kReflectionHeight); + CGContextDrawLinearGradient(context, gradient, top, bottom, 0); + CGGradientRelease(gradient); + + // draw header name + UIColor *mainTextColor = BW_RGBCOLOR(0,0,0); + UIColor *secondaryTextColor = BW_RGBCOLOR(48,48,48); + UIFont *mainFont = [UIFont boldSystemFontOfSize:20]; + UIFont *secondaryFont = [UIFont boldSystemFontOfSize:12]; + UIFont *smallFont = [UIFont systemFontOfSize:12]; + + float myColorValues[] = {255, 255, 255, .6}; + CGColorSpaceRef myColorSpace = CGColorSpaceCreateDeviceRGB(); + CGColorRef myColor = CGColorCreate(myColorSpace, myColorValues); + + // icon + [iconImage_ drawAtPoint:CGPointMake(kImageMargin, kImageMargin)]; + [reflectedImage_ drawAtPoint:CGPointMake(kImageMargin, kImageMargin+kImageHeight)]; + + // shadows are a beast + NSInteger shadowOffset = 2; + BW_IF_IOS4_OR_GREATER(if([[UIScreen mainScreen] scale] == 2) shadowOffset = 1;) + BW_IF_IOS5_OR_GREATER(shadowOffset = 1;) // iOS5 changes this - again! + + BW_IF_3_2_OR_GREATER(CGContextSetShadowWithColor(context, CGSizeMake(shadowOffset, shadowOffset), 0, myColor);) + BW_IF_PRE_3_2(shadowOffset=1;CGContextSetShadowWithColor(context, CGSizeMake(shadowOffset, -shadowOffset), 0, myColor);) + + + [mainTextColor set]; + [headerLabel_ drawInRect:CGRectMake(kTextRow, kImageMargin, globalWidth-kTextRow, 20) withFont:mainFont lineBreakMode:UILineBreakModeTailTruncation]; + + // middle + [secondaryTextColor set]; + [middleHeaderLabel_ drawInRect:CGRectMake(kTextRow, kImageMargin + 25, globalWidth-kTextRow, 20) withFont:secondaryFont lineBreakMode:UILineBreakModeTailTruncation]; + CGContextSetShadowWithColor(context, CGSizeZero, 0, nil); + + // sub + [secondaryTextColor set]; + [subHeaderLabel drawAtPoint:CGPointMake(kTextRow, kImageMargin+kImageHeight-12) forWidth:globalWidth-kTextRow withFont:smallFont lineBreakMode:UILineBreakModeTailTruncation]; + + CGColorRelease(myColor); + CGColorSpaceRelease(myColorSpace); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark Properties + +- (void)setHeaderLabel:(NSString *)anHeaderLabel { + if (headerLabel_ != anHeaderLabel) { + [headerLabel_ release]; + headerLabel_ = [anHeaderLabel copy]; + [self setNeedsDisplay]; + } +} + +- (void)setMiddleHeaderLabel:(NSString *)aMiddleHeaderLabel { + if (middleHeaderLabel_ != aMiddleHeaderLabel) { + [middleHeaderLabel_ release]; + middleHeaderLabel_ = [aMiddleHeaderLabel copy]; + [self setNeedsDisplay]; + } +} + +- (void)setSubHeaderLabel:(NSString *)aSubHeaderLabel { + if (subHeaderLabel != aSubHeaderLabel) { + [subHeaderLabel release]; + subHeaderLabel = [aSubHeaderLabel copy]; + [self setNeedsDisplay]; + } +} + +- (void)setIconImage:(UIImage *)anIconImage { + if (iconImage_ != anIconImage) { + [iconImage_ release]; + + // scale, make borders and reflection + iconImage_ = [anIconImage bw_imageToFitSize:CGSizeMake(kImageHeight, kImageHeight) honorScaleFactor:YES]; + iconImage_ = [[iconImage_ bw_roundedCornerImage:kImageBorderRadius borderSize:0.0] retain]; + + // create reflected image + [reflectedImage_ release]; + reflectedImage_ = nil; + if (anIconImage) { + reflectedImage_ = [[iconImage_ bw_reflectedImageWithHeight:kReflectionHeight fromAlpha:0.5 toAlpha:0.0] retain]; + } + [self setNeedsDisplay]; + } +} + +@end diff --git a/Classes/PSStoreButton.h b/Classes/PSStoreButton.h new file mode 100644 index 0000000000..10356f0333 --- /dev/null +++ b/Classes/PSStoreButton.h @@ -0,0 +1,81 @@ +// +// PSStoreButton.h +// HockeyDemo +// +// Created by Peter Steinberger on 09.01.11. +// Copyright 2011 Peter Steinberger. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import + +// defines a button action set (data container) +@interface PSStoreButtonData : NSObject { + CGPoint customPadding_; + NSString *label_; + NSArray *colors_; + BOOL enabled_; +} + ++ (id)dataWithLabel:(NSString*)aLabel colors:(NSArray*)aColors enabled:(BOOL)flag; + +@property (nonatomic, copy) NSString *label; +@property (nonatomic, retain) NSArray *colors; +@property (nonatomic, assign, getter=isEnabled) BOOL enabled; + +@end + + +@class PSStoreButton; +@protocol PSStoreButtonDelegate +- (void)storeButtonFired:(PSStoreButton *)button; +@end + + +// Simulate the Paymeny-Button from the AppStore +// The interface is flexible, so there is now fixed order +@interface PSStoreButton : UIButton { + PSStoreButtonData *buttonData_; + id buttonDelegate_; + + CAGradientLayer *gradient_; + CGPoint customPadding_; +} + +- (id)initWithFrame:(CGRect)frame; +- (id)initWithPadding:(CGPoint)padding; + +// action delegate +@property (nonatomic, assign) id buttonDelegate; + +// change the button layer +@property (nonatomic, retain) PSStoreButtonData *buttonData; +- (void)setButtonData:(PSStoreButtonData *)aButtonData animated:(BOOL)animated; + +// align helper +@property (nonatomic, assign) CGPoint customPadding; +- (void)alignToSuperview; + +// helpers to mimic an AppStore button ++ (NSArray *)appStoreGreenColor; ++ (NSArray *)appStoreBlueColor; ++ (NSArray *)appStoreGrayColor; + +@end diff --git a/Classes/PSStoreButton.m b/Classes/PSStoreButton.m new file mode 100644 index 0000000000..4eb23997cb --- /dev/null +++ b/Classes/PSStoreButton.m @@ -0,0 +1,295 @@ +// +// PSStoreButton.m +// HockeyDemo +// +// Created by Peter Steinberger on 09.01.11. +// Copyright 2011 Peter Steinberger. All rights reserved. +// +// This code was inspired by https://github.com/dhmspector/ZIStoreButton +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "PSStoreButton.h" + +#ifdef DEBUG +#define PSLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__); +#else +#define PSLog(...) +#endif + +#define PS_RGBCOLOR(r,g,b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1] +#define PS_MIN_HEIGHT 25.0f +#define PS_MAX_WIDTH 120.0f +#define PS_PADDING 12.0f +#define kDefaultButtonAnimationTime 0.25f + +@implementation PSStoreButtonData + +@synthesize label = label_; +@synthesize colors = colors_; +@synthesize enabled = enabled_; + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark NSObject + +- (id)initWithLabel:(NSString*)aLabel colors:(NSArray*)aColors enabled:(BOOL)flag { + if ((self = [super init])) { + self.label = aLabel; + self.colors = aColors; + self.enabled = flag; + } + return self; +} + ++ (id)dataWithLabel:(NSString*)aLabel colors:(NSArray*)aColors enabled:(BOOL)flag { + return [[[[self class] alloc] initWithLabel:aLabel colors:aColors enabled:flag] autorelease]; +} + +- (void)dealloc { + [label_ release]; + [colors_ release]; + + [super dealloc]; +} +@end + + +@interface PSStoreButton () +// call when buttonData was updated +- (void)updateButtonAnimated:(BOOL)animated; +@end + + +@implementation PSStoreButton + +@synthesize buttonData = buttonData_; +@synthesize buttonDelegate = buttonDelegate_; +@synthesize customPadding = customPadding_; + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark private + +- (void)touchedUpOutside:(id)sender { + PSLog(@"touched outside..."); +} + +- (void)buttonPressed:(id)sender { + PSLog(@"calling delegate:storeButtonFired for %@", sender); + [buttonDelegate_ storeButtonFired:self]; +} + +- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context { + // show text again, but only if animation did finish (or else another animation is on the way) + if ([finished boolValue]) { + [self setTitle:self.buttonData.label forState:UIControlStateNormal]; + } +} + +- (void)updateButtonAnimated:(BOOL)animated { + if (animated) { + // hide text, then start animation + [self setTitle:@"" forState:UIControlStateNormal]; + [UIView beginAnimations:@"storeButtonUpdate" context:nil]; + [UIView setAnimationBeginsFromCurrentState:YES]; + [UIView setAnimationDuration:kDefaultButtonAnimationTime]; + [UIView setAnimationDelegate:self]; + [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)]; + }else { + [self setTitle:self.buttonData.label forState:UIControlStateNormal]; + } + + self.enabled = self.buttonData.isEnabled; + gradient_.colors = self.buttonData.colors; + + // show white or gray text, depending on the state + if (self.buttonData.isEnabled) { + [self setTitleShadowColor:[UIColor colorWithWhite:0.200 alpha:1.000] forState:UIControlStateNormal]; + [self.titleLabel setShadowOffset:CGSizeMake(0.0, -0.6)]; + [self setTitleColor:[UIColor colorWithWhite:1.0 alpha:1.000] forState:UIControlStateNormal]; + }else { + [self.titleLabel setShadowOffset:CGSizeMake(0.0, 0.0)]; + [self setTitleColor:PS_RGBCOLOR(148,150,151) forState:UIControlStateNormal]; + } + + // calculate optimal new size + CGSize sizeThatFits = [self sizeThatFits:CGSizeZero]; + + // move sublayer (can't be animated explcitely) + for (CALayer *aLayer in self.layer.sublayers) { + [CATransaction begin]; + + if (animated) { + [CATransaction setAnimationDuration:kDefaultButtonAnimationTime]; + [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; + }else { + // frame is calculated and explicitely animated. so we absolutely need kCATransactionDisableActions + [CATransaction setValue:[NSNumber numberWithBool:YES] forKey:kCATransactionDisableActions]; + } + + CGRect newFrame = aLayer.frame; + newFrame.size.width = sizeThatFits.width; + aLayer.frame = newFrame; + + [CATransaction commit]; + } + + // set outer frame changes + self.titleEdgeInsets = UIEdgeInsetsMake(2.0, self.titleEdgeInsets.left, 0.0, 0.0); + [self alignToSuperview]; + + if (animated) { + [UIView commitAnimations]; + } +} + +- (void)alignToSuperview { + [self sizeToFit]; + if (self.superview) { + CGRect cr = self.frame; + cr.origin.y = customPadding_.y; + cr.origin.x = self.superview.frame.size.width - cr.size.width - customPadding_.x * 2; + self.frame = cr; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark NSObject + +- (id)initWithFrame:(CGRect)frame { + if ((self = [super initWithFrame:frame])) { + self.layer.needsDisplayOnBoundsChange = YES; + + // setup title label + [self.titleLabel setFont:[UIFont boldSystemFontOfSize:13.0]]; + + // register for touch events + [self addTarget:self action:@selector(touchedUpOutside:) forControlEvents:UIControlEventTouchUpOutside]; + [self addTarget:self action:@selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside]; + + // border layers for more sex! + CAGradientLayer *bevelLayer = [CAGradientLayer layer]; + bevelLayer.colors = [NSArray arrayWithObjects:(id)[[UIColor colorWithWhite:0.4 alpha:1.0] CGColor], [[UIColor whiteColor] CGColor], nil]; + bevelLayer.frame = CGRectMake(0.0, 0.0, CGRectGetWidth(frame), CGRectGetHeight(frame)); + bevelLayer.cornerRadius = 2.5; + bevelLayer.needsDisplayOnBoundsChange = YES; + [self.layer addSublayer:bevelLayer]; + + CAGradientLayer *topBorderLayer = [CAGradientLayer layer]; + topBorderLayer.colors = [NSArray arrayWithObjects:(id)[[UIColor darkGrayColor] CGColor], [[UIColor lightGrayColor] CGColor], nil]; + topBorderLayer.frame = CGRectMake(0.5, 0.5, CGRectGetWidth(frame) - 1.0, CGRectGetHeight(frame) - 1.0); + topBorderLayer.cornerRadius = 2.6; + topBorderLayer.needsDisplayOnBoundsChange = YES; + [self.layer addSublayer:topBorderLayer]; + + // main gradient layer + gradient_ = [[CAGradientLayer layer] retain]; + gradient_.locations = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0], [NSNumber numberWithFloat:1.0], nil];//[NSNumber numberWithFloat:0.500], [NSNumber numberWithFloat:0.5001], + gradient_.frame = CGRectMake(0.75, 0.75, CGRectGetWidth(frame) - 1.5, CGRectGetHeight(frame) - 1.5); + gradient_.cornerRadius = 2.5; + gradient_.needsDisplayOnBoundsChange = YES; + [self.layer addSublayer:gradient_]; + [self bringSubviewToFront:self.titleLabel]; + } + return self; +} + +- (id)initWithPadding:(CGPoint)padding { + if ((self = [self initWithFrame:CGRectMake(0, 0, 40, PS_MIN_HEIGHT)])) { + customPadding_ = padding; + } + return self; +} + +- (void)dealloc { + [buttonData_ release]; + [gradient_ release]; + + [super dealloc]; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark UIView + +- (CGSize)sizeThatFits:(CGSize)size { + CGSize constr = (CGSize){.height = self.frame.size.height, .width = PS_MAX_WIDTH}; + CGSize newSize = [self.buttonData.label sizeWithFont:self.titleLabel.font constrainedToSize:constr lineBreakMode:UILineBreakModeMiddleTruncation]; + CGFloat newWidth = newSize.width + (PS_PADDING * 2); + CGFloat newHeight = PS_MIN_HEIGHT > newSize.height ? PS_MIN_HEIGHT : newSize.height; + + CGSize sizeThatFits = CGSizeMake(newWidth, newHeight); + return sizeThatFits; +} + +- (void)setFrame:(CGRect)aRect { + [super setFrame:aRect]; + + // copy frame changes to sublayers (but watch out for NaN's) + for (CALayer *aLayer in self.layer.sublayers) { + CGRect rect = aLayer.frame; + rect.size.width = self.frame.size.width; + rect.size.height = self.frame.size.height; + aLayer.frame = rect; + [aLayer layoutIfNeeded]; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark Properties + +- (void)setButtonData:(PSStoreButtonData *)aButtonData { + [self setButtonData:aButtonData animated:NO]; +} + +- (void)setButtonData:(PSStoreButtonData *)aButtonData animated:(BOOL)animated { + if (buttonData_ != aButtonData) { + [buttonData_ release]; + buttonData_ = [aButtonData retain]; + } + + [self updateButtonAnimated:animated]; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark Static + ++ (NSArray *)appStoreGreenColor { + return [NSArray arrayWithObjects:(id) + [UIColor colorWithRed:0.482 green:0.674 blue:0.406 alpha:1.000].CGColor, + [UIColor colorWithRed:0.299 green:0.606 blue:0.163 alpha:1.000].CGColor, nil]; +} + ++ (NSArray *)appStoreBlueColor { + return [NSArray arrayWithObjects:(id) + [UIColor colorWithRed:0.306 green:0.380 blue:0.547 alpha:1.000].CGColor, + [UIColor colorWithRed:0.129 green:0.220 blue:0.452 alpha:1.000].CGColor, nil]; +} + ++ (NSArray *)appStoreGrayColor { + return [NSArray arrayWithObjects:(id) + PS_RGBCOLOR(187,189,191).CGColor, + PS_RGBCOLOR(210,210,210).CGColor, nil]; +} + +@end diff --git a/Classes/PSWebTableViewCell.h b/Classes/PSWebTableViewCell.h new file mode 100644 index 0000000000..b858e324e7 --- /dev/null +++ b/Classes/PSWebTableViewCell.h @@ -0,0 +1,44 @@ +// +// PSWebTableViewCell.h +// HockeyDemo +// +// Created by Peter Steinberger on 04.02.11. +// Copyright 2011 Peter Steinberger. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import + +@interface PSWebTableViewCell : UITableViewCell { + UIWebView *webView_; + NSString *webViewContent_; + CGSize webViewSize_; + + UIColor *cellBackgroundColor_; +} + +@property (nonatomic, retain) UIWebView *webView; +@property (nonatomic, copy) NSString *webViewContent; +@property (nonatomic, assign) CGSize webViewSize; +@property (nonatomic, retain) UIColor *cellBackgroundColor; + +- (void)addWebView; + +@end diff --git a/Classes/PSWebTableViewCell.m b/Classes/PSWebTableViewCell.m new file mode 100644 index 0000000000..005d2738f2 --- /dev/null +++ b/Classes/PSWebTableViewCell.m @@ -0,0 +1,189 @@ +// +// PSWebTableViewCell.m +// HockeyDemo +// +// Created by Peter Steinberger on 04.02.11. +// Copyright 2011 Peter Steinberger. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "PSWebTableViewCell.h" +#import "BWGlobal.h" + +@implementation PSWebTableViewCell + +static NSString* PSWebTableViewCellHtmlTemplate = @"\ +\ +\ +\ +\ +\ +\ +%@\ +\ +\ +"; + +@synthesize webView = webView_; +@synthesize webViewContent = webViewContent_; +@synthesize webViewSize = webViewSize_; +@synthesize cellBackgroundColor = cellBackgroundColor_; + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark private + +- (void)addWebView { + if(webViewContent_) { + CGRect webViewRect = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height); + if(!webView_) { + webView_ = [[[UIWebView alloc] initWithFrame:webViewRect] retain]; + [self addSubview:webView_]; + webView_.hidden = YES; + webView_.backgroundColor = self.cellBackgroundColor; + webView_.opaque = NO; + webView_.delegate = self; + webView_.autoresizingMask = UIViewAutoresizingFlexibleWidth; + + for(UIView* subView in webView_.subviews){ + if([subView isKindOfClass:[UIScrollView class]]){ + // disable scrolling + UIScrollView *sv = (UIScrollView *)subView; + sv.scrollEnabled = NO; + sv.bounces = NO; + + // hide shadow + for (UIView* shadowView in [subView subviews]) { + if ([shadowView isKindOfClass:[UIImageView class]]) { + shadowView.hidden = YES; + } + } + } + } + } + else + webView_.frame = webViewRect; + + NSString *deviceWidth = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? [NSString stringWithFormat:@"%d", CGRectGetWidth(self.bounds)] : @"device-width"; + //BWHockeyLog(@"%@\n%@\%@", PSWebTableViewCellHtmlTemplate, deviceWidth, self.webViewContent); + NSString *contentHtml = [NSString stringWithFormat:PSWebTableViewCellHtmlTemplate, deviceWidth, self.webViewContent]; + [webView_ loadHTMLString:contentHtml baseURL:nil]; + } +} + +- (void)showWebView { + webView_.hidden = NO; + self.textLabel.text = @""; + [self setNeedsDisplay]; +} + + +- (void)removeWebView { + if(webView_) { + webView_.delegate = nil; + [webView_ resignFirstResponder]; + [webView_ removeFromSuperview]; + [webView_ release]; + } + webView_ = nil; + [self setNeedsDisplay]; +} + + +- (void)setWebViewContent:(NSString *)aWebViewContent { + if (webViewContent_ != aWebViewContent) { + [webViewContent_ release]; + webViewContent_ = [aWebViewContent retain]; + + // add basic accessiblity (prevents "snarfed from ivar layout") logs + self.accessibilityLabel = aWebViewContent; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark NSObject + +- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + if((self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])) { + self.cellBackgroundColor = [UIColor clearColor]; + } + return self; +} + +- (void)dealloc { + [self removeWebView]; + [webViewContent_ release]; + [super dealloc]; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark UIView + +- (void)setFrame:(CGRect)aFrame { + BOOL needChange = !CGRectEqualToRect(aFrame, self.frame); + [super setFrame:aFrame]; + + if (needChange) { + [self addWebView]; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark UITableViewCell + +- (void)prepareForReuse { + [self removeWebView]; + self.webViewContent = nil; + [super prepareForReuse]; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark UIWebView + +- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { + if(navigationType == UIWebViewNavigationTypeOther) + return YES; + + return NO; +} + + +- (void)webViewDidFinishLoad:(UIWebView *)webView { + if(webViewContent_) + [self showWebView]; + + CGRect frame = webView_.frame; + frame.size.height = 1; + webView_.frame = frame; + CGSize fittingSize = [webView_ sizeThatFits:CGSizeZero]; + frame.size = fittingSize; + webView_.frame = frame; + + // sizeThatFits is not reliable - use javascript for optimal height + NSString *output = [webView_ stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight;"]; + self.webViewSize = CGSizeMake(fittingSize.width, [output integerValue]); +} + +@end diff --git a/Classes/UIImage+HockeyAdditions.h b/Classes/UIImage+HockeyAdditions.h new file mode 100644 index 0000000000..dccc74d1ba --- /dev/null +++ b/Classes/UIImage+HockeyAdditions.h @@ -0,0 +1,38 @@ +// +// UIImage+HockeyAdditions.h +// HockeyDemo +// +// Created by Peter Steinberger on 10.01.11. +// Copyright 2011 Peter Steinberger. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +@interface UIImage (HockeyAdditions) + +- (UIImage *)bw_roundedCornerImage:(NSInteger)cornerSize borderSize:(NSInteger)borderSize; +- (UIImage *)bw_imageToFitSize:(CGSize)fitSize honorScaleFactor:(BOOL)honorScaleFactor; +- (UIImage *)bw_reflectedImageWithHeight:(NSUInteger)height fromAlpha:(float)fromAlpha toAlpha:(float)toAlpha; + +- (id)bw_initWithContentsOfResolutionIndependentFile:(NSString *)path NS_RETURNS_RETAINED; ++ (UIImage*)bw_imageWithContentsOfResolutionIndependentFile:(NSString *)path; ++ (UIImage *)bw_imageNamed:(NSString *)imageName bundle:(NSString *)bundleName; + +@end diff --git a/Classes/UIImage+HockeyAdditions.m b/Classes/UIImage+HockeyAdditions.m new file mode 100644 index 0000000000..79cd70042f --- /dev/null +++ b/Classes/UIImage+HockeyAdditions.m @@ -0,0 +1,346 @@ +// +// UIImage+HockeyAdditions.m +// HockeyDemo +// +// Created by Peter Steinberger on 10.01.11. +// Copyright 2011 Peter Steinberger. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "UIImage+HockeyAdditions.h" +#import "BWGlobal.h" + +// Private helper methods +@interface UIImage (HockeyAdditionsPrivate) +- (void)addRoundedRectToPath:(CGRect)rect context:(CGContextRef)context ovalWidth:(CGFloat)ovalWidth ovalHeight:(CGFloat)ovalHeight; + +CGContextRef MyOpenBitmapContext(int pixelsWide, int pixelsHigh); +CGImageRef CreateGradientImage(int pixelsWide, int pixelsHigh, float fromAlpha, float toAlpha); +@end + +@implementation UIImage (HockeyAdditions) + +// Returns true if the image has an alpha layer +- (BOOL)hasAlpha { + CGImageAlphaInfo alpha = CGImageGetAlphaInfo(self.CGImage); + return (alpha == kCGImageAlphaFirst || + alpha == kCGImageAlphaLast || + alpha == kCGImageAlphaPremultipliedFirst || + alpha == kCGImageAlphaPremultipliedLast); +} + +// Returns a copy of the given image, adding an alpha channel if it doesn't already have one +- (UIImage *)imageWithAlpha { + if ([self hasAlpha]) { + return self; + } + + CGImageRef imageRef = self.CGImage; + size_t width = CGImageGetWidth(imageRef) * self.scale; + size_t height = CGImageGetHeight(imageRef) * self.scale; + + // The bitsPerComponent and bitmapInfo values are hard-coded to prevent an "unsupported parameter combination" error + CGContextRef offscreenContext = CGBitmapContextCreate(NULL, + width, + height, + 8, + 0, + CGImageGetColorSpace(imageRef), + kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst); + + // Draw the image into the context and retrieve the new image, which will now have an alpha layer + CGContextDrawImage(offscreenContext, CGRectMake(0, 0, width, height), imageRef); + CGImageRef imageRefWithAlpha = CGBitmapContextCreateImage(offscreenContext); + UIImage *imageWithAlpha = [UIImage imageWithCGImage:imageRefWithAlpha]; + + // Clean up + CGContextRelease(offscreenContext); + CGImageRelease(imageRefWithAlpha); + + return imageWithAlpha; +} + +// Creates a copy of this image with rounded corners +// If borderSize is non-zero, a transparent border of the given size will also be added +// Original author: Björn Sållarp. Used with permission. See: http://blog.sallarp.com/iphone-uiimage-round-corners/ +- (UIImage *)bw_roundedCornerImage:(NSInteger)cornerSize borderSize:(NSInteger)borderSize { + // If the image does not have an alpha layer, add one + + UIImage *roundedImage = nil; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000 + BW_IF_IOS4_OR_GREATER( + UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0); // 0.0 for scale means "correct scale for device's main screen". + CGImageRef sourceImg = CGImageCreateWithImageInRect([self CGImage], CGRectMake(0, 0, self.size.width * self.scale, self.size.height * self.scale)); // cropping happens here. + + // Create a clipping path with rounded corners + CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextBeginPath(context); + [self addRoundedRectToPath:CGRectMake(borderSize, borderSize, self.size.width - borderSize * 2, self.size.height - borderSize * 2) + context:context + ovalWidth:cornerSize + ovalHeight:cornerSize]; + CGContextClosePath(context); + CGContextClip(context); + + roundedImage = [UIImage imageWithCGImage:sourceImg scale:0.0 orientation:self.imageOrientation]; // create cropped UIImage. + [roundedImage drawInRect:CGRectMake(0, 0, self.size.width, self.size.height)]; // the actual scaling happens here, and orientation is taken care of automatically. + CGImageRelease(sourceImg); + roundedImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + ) +#endif + if (!roundedImage) { + // Try older method. + UIImage *image = [self imageWithAlpha]; + + // Build a context that's the same dimensions as the new size + CGContextRef context = CGBitmapContextCreate(NULL, + image.size.width, + image.size.height, + CGImageGetBitsPerComponent(image.CGImage), + 0, + CGImageGetColorSpace(image.CGImage), + CGImageGetBitmapInfo(image.CGImage)); + + // Create a clipping path with rounded corners + CGContextBeginPath(context); + [self addRoundedRectToPath:CGRectMake(borderSize, borderSize, image.size.width - borderSize * 2, image.size.height - borderSize * 2) + context:context + ovalWidth:cornerSize + ovalHeight:cornerSize]; + CGContextClosePath(context); + CGContextClip(context); + + // Draw the image to the context; the clipping path will make anything outside the rounded rect transparent + CGContextDrawImage(context, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage); + + // Create a CGImage from the context + CGImageRef clippedImage = CGBitmapContextCreateImage(context); + CGContextRelease(context); + + // Create a UIImage from the CGImage + roundedImage = [UIImage imageWithCGImage:clippedImage]; + CGImageRelease(clippedImage); + } + return roundedImage; +} + +#pragma mark - +#pragma mark Private helper methods + +// Adds a rectangular path to the given context and rounds its corners by the given extents +// Original author: Björn Sållarp. Used with permission. See: http://blog.sallarp.com/iphone-uiimage-round-corners/ +- (void)addRoundedRectToPath:(CGRect)rect context:(CGContextRef)context ovalWidth:(CGFloat)ovalWidth ovalHeight:(CGFloat)ovalHeight { + if (ovalWidth == 0 || ovalHeight == 0) { + CGContextAddRect(context, rect); + return; + } + CGContextSaveGState(context); + CGContextTranslateCTM(context, CGRectGetMinX(rect), CGRectGetMinY(rect)); + CGContextScaleCTM(context, ovalWidth, ovalHeight); + CGFloat fw = CGRectGetWidth(rect) / ovalWidth; + CGFloat fh = CGRectGetHeight(rect) / ovalHeight; + CGContextMoveToPoint(context, fw, fh/2); + CGContextAddArcToPoint(context, fw, fh, fw/2, fh, 1); + CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 1); + CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 1); + CGContextAddArcToPoint(context, fw, 0, fw, fh/2, 1); + CGContextClosePath(context); + CGContextRestoreGState(context); +} + +- (UIImage *)bw_imageToFitSize:(CGSize)fitSize honorScaleFactor:(BOOL)honorScaleFactor +{ + float imageScaleFactor = 1.0; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000 + if (honorScaleFactor) { + if ([self respondsToSelector:@selector(scale)]) { + imageScaleFactor = [self scale]; + } + } +#endif + + float sourceWidth = [self size].width * imageScaleFactor; + float sourceHeight = [self size].height * imageScaleFactor; + float targetWidth = fitSize.width; + float targetHeight = fitSize.height; + + // Calculate aspect ratios + float sourceRatio = sourceWidth / sourceHeight; + float targetRatio = targetWidth / targetHeight; + + // Determine what side of the source image to use for proportional scaling + BOOL scaleWidth = (sourceRatio <= targetRatio); + // Deal with the case of just scaling proportionally to fit, without cropping + scaleWidth = !scaleWidth; + + // Proportionally scale source image + float scalingFactor, scaledWidth, scaledHeight; + if (scaleWidth) { + scalingFactor = 1.0 / sourceRatio; + scaledWidth = targetWidth; + scaledHeight = round(targetWidth * scalingFactor); + } else { + scalingFactor = sourceRatio; + scaledWidth = round(targetHeight * scalingFactor); + scaledHeight = targetHeight; + } + + // Calculate compositing rectangles + CGRect sourceRect, destRect; + sourceRect = CGRectMake(0, 0, sourceWidth, sourceHeight); + destRect = CGRectMake(0, 0, scaledWidth, scaledHeight); + + // Create appropriately modified image. + UIImage *image = nil; + BW_IF_IOS4_OR_GREATER + ( + UIGraphicsBeginImageContextWithOptions(destRect.size, NO, honorScaleFactor ? 0.0 : 1.0); // 0.0 for scale means "correct scale for device's main screen". + CGImageRef sourceImg = CGImageCreateWithImageInRect([self CGImage], sourceRect); // cropping happens here. + image = [UIImage imageWithCGImage:sourceImg scale:0.0 orientation:self.imageOrientation]; // create cropped UIImage. + [image drawInRect:destRect]; // the actual scaling happens here, and orientation is taken care of automatically. + CGImageRelease(sourceImg); + image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + ) + if (!image) { + // Try older method. + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef context = CGBitmapContextCreate(NULL, scaledWidth, scaledHeight, 8, (fitSize.width * 4), + colorSpace, kCGImageAlphaPremultipliedLast); + CGImageRef sourceImg = CGImageCreateWithImageInRect([self CGImage], sourceRect); + CGContextDrawImage(context, destRect, sourceImg); + CGImageRelease(sourceImg); + CGImageRef finalImage = CGBitmapContextCreateImage(context); + CGContextRelease(context); + CGColorSpaceRelease(colorSpace); + image = [UIImage imageWithCGImage:finalImage]; + CGImageRelease(finalImage); + } + + return image; +} + + + +CGImageRef CreateGradientImage(int pixelsWide, int pixelsHigh, float fromAlpha, float toAlpha) { + CGImageRef theCGImage = NULL; + + // gradient is always black-white and the mask must be in the gray colorspace + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray(); + + // create the bitmap context + CGContextRef gradientBitmapContext = CGBitmapContextCreate(NULL, pixelsWide, pixelsHigh, + 8, 0, colorSpace, kCGImageAlphaNone); + + // define the start and end grayscale values (with the alpha, even though + // our bitmap context doesn't support alpha the gradient requires it) + CGFloat colors[] = {toAlpha, 1.0, fromAlpha, 1.0}; + + // create the CGGradient and then release the gray color space + CGGradientRef grayScaleGradient = CGGradientCreateWithColorComponents(colorSpace, colors, NULL, 2); + CGColorSpaceRelease(colorSpace); + + // create the start and end points for the gradient vector (straight down) + CGPoint gradientEndPoint = CGPointZero; + CGPoint gradientStartPoint = CGPointMake(0, pixelsHigh); + + // draw the gradient into the gray bitmap context + CGContextDrawLinearGradient(gradientBitmapContext, grayScaleGradient, gradientStartPoint, + gradientEndPoint, kCGGradientDrawsAfterEndLocation); + CGGradientRelease(grayScaleGradient); + + // convert the context into a CGImageRef and release the context + theCGImage = CGBitmapContextCreateImage(gradientBitmapContext); + CGContextRelease(gradientBitmapContext); + + // return the imageref containing the gradient + return theCGImage; +} + +CGContextRef MyOpenBitmapContext(int pixelsWide, int pixelsHigh) { + CGSize size = CGSizeMake(pixelsWide, pixelsHigh); + if (UIGraphicsBeginImageContextWithOptions != NULL) { + UIGraphicsBeginImageContextWithOptions(size, NO, 0.0); + } + else { + UIGraphicsBeginImageContext(size); + } + + return UIGraphicsGetCurrentContext(); +} + +- (UIImage *)bw_reflectedImageWithHeight:(NSUInteger)height fromAlpha:(float)fromAlpha toAlpha:(float)toAlpha { + if(height == 0) + return nil; + + // create a bitmap graphics context the size of the image + CGContextRef mainViewContentContext = MyOpenBitmapContext(self.size.width, height); + + // create a 2 bit CGImage containing a gradient that will be used for masking the + // main view content to create the 'fade' of the reflection. The CGImageCreateWithMask + // function will stretch the bitmap image as required, so we can create a 1 pixel wide gradient + CGImageRef gradientMaskImage = CreateGradientImage(1, height, fromAlpha, toAlpha); + + // create an image by masking the bitmap of the mainView content with the gradient view + // then release the pre-masked content bitmap and the gradient bitmap + CGContextClipToMask(mainViewContentContext, CGRectMake(0.0, 0.0, self.size.width, height), gradientMaskImage); + CGImageRelease(gradientMaskImage); + + // draw the image into the bitmap context + CGContextDrawImage(mainViewContentContext, CGRectMake(0, 0, self.size.width, self.size.height), self.CGImage); + + // convert the finished reflection image to a UIImage + UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext(); // returns autoreleased + UIGraphicsEndImageContext(); + + return theImage; +} + +- (id)bw_initWithContentsOfResolutionIndependentFile:(NSString *)path { + if ([UIScreen instancesRespondToSelector:@selector(scale)] && (int)[[UIScreen mainScreen] scale] == 2.0) { + NSString *path2x = [[path stringByDeletingLastPathComponent] + stringByAppendingPathComponent:[NSString stringWithFormat:@"%@@2x.%@", + [[path lastPathComponent] stringByDeletingPathExtension], + [path pathExtension]]]; + + if ([[NSFileManager defaultManager] fileExistsAtPath:path2x]) { + return [self initWithContentsOfFile:path2x]; + } + } + + return [self initWithContentsOfFile:path]; +} + ++ (UIImage*)bw_imageWithContentsOfResolutionIndependentFile:(NSString *)path { +#ifndef __clang_analyzer__ + // clang alayzer in 4.2b3 thinks here's a leak, which is not the case. + return [[[UIImage alloc] bw_initWithContentsOfResolutionIndependentFile:path] autorelease]; +#endif +} + + ++ (UIImage *)bw_imageNamed:(NSString *)imageName bundle:(NSString *)bundleName { + NSString *resourcePath = [[NSBundle mainBundle] resourcePath]; + NSString *bundlePath = [resourcePath stringByAppendingPathComponent:bundleName]; + NSString *imagePath = [bundlePath stringByAppendingPathComponent:imageName]; + return [UIImage bw_imageWithContentsOfResolutionIndependentFile:imagePath]; +} + +@end diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100755 index 0000000000..88a6782d6a --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,81 @@ +## Authors + +Andreas Linde +Stanley Rost +Fabian Kreiser +Tobias Höhmann +FutureTap +Kent Sutherland +Peter Steinberger +Thomas Dohmke + +## Licenses + +The Hockey SDK is provided under the following license: + + The MIT License + Copyright (c) 2011 Codenauts UG (haftungsbeschränkt). All rights reserved. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + +Except as noted below, PLCrashReporter +is provided under the following license: + + Copyright (c) 2008 - 2009 Plausible Labs Cooperative, Inc. + All rights reserved. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + +The protobuf-c library, as well as the PLCrashLogWriterEncoding.c +file are licensed as follows: + + Copyright 2008, Dave Benson. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with + the License. You may obtain a copy of the License + at http://www.apache.org/licenses/LICENSE-2.0 Unless + required by applicable law or agreed to in writing, + software distributed under the License is distributed on + an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. diff --git a/Resources/.DS_Store b/Resources/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..c7424949bdeb236572da2bb5d1e755cfe003762a GIT binary patch literal 6148 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 literal 0 HcmV?d00001 diff --git a/Resources/Hockey.bundle/authorize_denied.png b/Resources/Hockey.bundle/authorize_denied.png new file mode 100644 index 0000000000000000000000000000000000000000..6a97107d4f5280f1cf9b228365e0b49262b91e89 GIT binary patch literal 2340 zcmV+<3ETFGP)5JHe+FEd#ikQ0vwSsM_A&c&A!Py&j8GyHQ^;nqHIHI{rJSbOxtwO$L; zK?q@ofSrpkMPM5#*54DzqK6dIUwib!ZUWCw1fvD)Tzr`W>cg~`H32n=Hvl}q+bEZ` zS69bp!5p%;r%t@K`Q1jjRM%nCeWW6w2JzZR(L5~%DR6ui6oDP2m{Hp5R76Z%z~i%E z2l!{i+xvi;L&MNLIxONtDhcFudL{A<8*Lz8o0o#khj$;D$3m^!QURPF1^~y@gn2zEfFUIfrZ}RNF=E<! z;^$D;m9GvD4AYjm8uBw1XLZR#_Tk<~U00qu1l(4kwOB3_j za32EUDSRgLhn-KQxOt`Jfz#xD)Ycvbs0A}l+`J+Sc-Z+=M19D}kD8Cg_zKr#=z)Tw zV(`+htv$SoYp8Vye1*@Z7&n_ZRjBH>X99a&lu#*mn9lz@i6fxu(8yE@SX+OXO1UEo zD4?j=Z8SB}=eMYg6&{>?a^mC>!vjT8vGaN=Jy0S7wa+Lz`7RZfbLX6xgX515w~IjF zcHzi44b-@CwDQ#t z0~$YV{OFDU@hbuMk3a5`%oZLw`e*~&KMy_fW>(t1^t@a=^QrPp56^@B&d~8FAmVqZ=A_On(($|*H1vq)WWqK;AznW~PKG#;_r%!D zEI=V{UtCVMz{CJ$mPC$B;+r;xP)ni%bQJ-%1kXgJIv-7T9_#byCVyXbn4 ztBxNSPi zVRo1>7Fz_|(J9`Dp@>$GhuR=ud(&C=l6v^CN`8~n^OL4v!SQp6?~)srjw*9h1)puC|Hz)a>=d_Sc(QXr@sP~OlA&SY=Jchmg) z{cb&k43T)h-*rG#4JdEOWOy4m#C^ZtO**)+_V@!E1e_AZ9$hZMM3uk}?W3TirT>w+56emV!1^q2s)2wz^)k6|YTf zwz>_HIZZotOpikEO3y)DHCvr}*upkjo$Y3;6M%SOKshud`#f;OEbzA3>L?4@Y;~@H ze_?UJau+6#2dXfS?5*+cyHHHPEbP0m)dQno-DtZClOUk#$o*B4Mr6e#EU{Lb1|cR> zz)A2xU-NZVQ4sdKpi&hoCUom(+dXjb^zph<6*|05Y=oCQAXKh4IbZm=4b8hKD9>my80C^B&GYyae?qzI9 zPI&g7J#lV4(Ar&!rj#CIn*h!{d*b+5dS=2D(gU1lPn?_a&JELWo-d0e<`au!ubBro;pcc1{32I8D2F20000< KMNUMnLSTZ6$7Z7d literal 0 HcmV?d00001 diff --git a/Resources/Hockey.bundle/authorize_denied@2x.png b/Resources/Hockey.bundle/authorize_denied@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..be5a42c34b6190071783334bb5fe7f7af52cf2bb GIT binary patch literal 4384 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 H;u85k4!}n< literal 0 HcmV?d00001 diff --git a/Resources/Hockey.bundle/authorize_request.png b/Resources/Hockey.bundle/authorize_request.png new file mode 100644 index 0000000000000000000000000000000000000000..7b863cfdd9dfd687aed16ce3e63c199f922e1362 GIT binary patch literal 2448 zcmV;B32*j^P)*yVJfOZf)#s)$s{QoC3XAH^M zIYIF(V3-v|>;Nxd2JsL)=S;AL4Ke}~nladUL_3gnfL2iR(5)b2u&P^K-Bs%Pbm|;z zx~p!re%!iMeY^SrmRP9&{>Lzo2OJ>RRYoLEZvfXo(=RS?HT&Jy*Abi96CuQ(KR<}O z@ibBY{f{D^SRCLh1x+;YgyaHl9AdKKQHU>?P8o{1`F)@|``yJM z;^~8<(~o(ef|>w04mRf|AC|+OY{e6jx2GS=z%NPL5dkfrI6wLDD(Hz;JOO$8?n4eQ z05#Ry+{fnp)T45c6Rdaw@b=w@Jl;Brs@SH5xA2GG&TaC*F!BqEK{*GC^HYzc)`+Zl zBzgPpLkZZ9u*w$x>M-EWPdy4eZ1`gC6o+WMVFM`1*ki>bK`lDrwD5+!%0dXS^rGes zsYSeXrhbIf2Ap0tTJiLtb`ptj&Vj9k=||Q6Es~>l@*$76=CmHN3Qs`O_Y261=L^(M zE=%+$oO7VOFg@xI9vr7C) zLrda1jQDXPkW%sB-8__KXL)vfkm8)rSe_lfM&1*bIzOaUJf#4~mKE8c70a{Z736~F z^@HP!VMw8P-WZFr<+IAo z&tu&5OK3hzL#@`_&Ss3c*sW2I?6B3@x;ARA@fP8##RaFSp#I3H*4!$va?h{Zz}ADV zgL8y?y3i7iePBap5mjsM2A(6ybF&9@6{xPvPh39$cy;LZ^R%QTJZ-@F%c5gUfPPm3 zd<83PiMgQ98TD7FA{C)!|vL=eMO9Ss?~; zR%Ifc&A^QzL$EXji#nX74G4-|B-97vw&}Iv(TJA^XR;|HT96HJzsI7U&bD?&kVRPe zSi)h4ElX&ucr2l@F^?rQrY3`(bMp>|?$n9S8-$A(IQ2CRlLF>8-aVko7LT)9n2a>T&abZ+Wu0jzBlX~R{NI8TL`u^=<}z=AhMcS?8e)ih`GJNB9rtVT6bA1 zPRIK2+gfq@D(OBO?0B?_d6Js_*jZOdbP`We+nS_$JC%OO8s{scChh(2gRIc!>Lv2z zfI#GuXm=3MY1m3Cd21x0c8y0T@$jEl$Ic+PH)v=j9^${_ZSS!6fo?0z@qU@|@7J^o zJ$H}^@T$Z(J|iz))=P18{Kd<9NgzfN@A0jVN$56g0hC|7tSg;)`Sq7owvh&O6we6i zcgotr&a(PONAUoxs~W!ide>Fdoi;K_XI*}wZ13(xE1=!IdPxYjHR#(QWTBEtetPl6 zvc0=^6v+_g)g!hw=u?kk4kkSG_mADZBL~=c^4B>x=*j-MTiC~sBgX{sfcM3p?d~0& z17#Tz?d~1r@kBGB*tXP?HEf)nz)M>H?42365dsG*9HrCMCw(C zmOe1i^GLls_UYsKreZr%kB$%h^`a?zpzwrd;~qtrn1W51!jb+>v2-vc=5aNt!_tKbF;Bbn!&2X#$HqO;V}j>#qo}0x!%`&mSZ**O0p5+S zDq+)7BgBM&*^R6wdxt$zvch{Cu+9{>U%V+1ft>hR|#N&o(Jzl`_BU)`tfgFqYDdiHdsoU|c9 z5nVy@Cp&EY>(~7p-uoDec#CpYNYQc*RDsIhpRO3`Z9Wl?u+uF|Nd6BG@FtLCLMpug O0000u86GlT-2Lz%*0QM^V1>oxT z==&m2(0JX$=+Og55WQU#P``-PF!ln0ARGTqs(Zy!>3|b~yKjWkb$7t|T6@}qloRj-Aq@I(TJxB~KA|@^(Ato-KA4lj1 zfmkkKRF(DoGFK;E47d#IJ2$(8n%WqDG75z*A4Jx-jG750dl8q=@ZL2lRmmv%Z~ewcX1j zOPi}hfBrT}YT;dSO*qmSv&HuOCq6>X<^@st{p!hd zqAP%w9}*uxT0iC;(DnIeBsxP{QM=qwe8lJ-4vFiS?YQ3ZCGElbJQI^P0#Vs@IP~xX ztHdQU(u@)+D0S^*RaCgE`mzccY4EpfD)vF4#?9UzcJ7GV{KIPxgLJ&iyWi0x+V7q2 zHe}`%R*e1FDHL|XC?8ZINP}=Noz}2r^Q{~786LGM{QVVq&V%6uBA#`Zw7vzgD7l!? z!hh3`9-;a&H|cgX{Z%ks0$TdYNy)N_&RTii$2+V!?VifKc?^r{%*LEPU{j{K~4GnY_Bg-v9mUlg?zE08 z|JN3^5%PBe@{)48I|-;hU__#D65%B zwNdjsH<^1eoZggYqAYzuq`F#Q%9 zFE-H&y^^)1DUT3QtAf~!>-Tx2r!e1Hr73Ugyh{qL#I3J|4=DiA%HE)%+{}ss?FlUS9uitoMdpNeUHr7ot}2QzB!(1C*zs{kxmP@_0lE9 zv&nNv3nT_M<%w}?sWncVDxj9mg|goZ7C;q$!B|CoLu+@J&Ith1g%Zrlt>rT;bhSaa z;NfWdWAsjWxply8L`_ySvFBY{Lo?jY_s63;lDNWqxDVgd6~&eG0D1{9Dkex@xQY7A z@7Z6uoV~cZx`Ji)L+8sOE4M^_``0|_Ghg}F*;Y3=^-r#y3hST^Q^zoKu&kkZD0)n{ z8aGiB9^0G%^{OqawF^*T+H+;H9igO1R%EI%IHH!Ai?#P%VHd`;&N$h<)Gi~y6 z1*1B$NugXRhrXYF1K^>Q-_e_(SLZ9h6UW^$LOc9u6^BUzfQ!UFX6qh>0gRf2aKsr` zVjs3AIWD4Svqb+9FDqI!_)!FSY`d9l+X^YWQxFz>l21OxJ-wlaBPrfjnGdV8e7NUM z%N29VcdAY-n8|vpzBMehGtr|-qm?l~aGuJrErej*T(}~qr3H|C(>x=&G95gQ1bFPb zFI#RgDujysW4>9#rVI58*v21s0%~AXBRzuAHRQ;lmprX@F>^lsw4R|eLh_B5O`HtWWR`}(R%PEyIFi#=h_>Sd}b-v zze-WCsUSiGe^iRirFl^ z*phaO>L-*_qm-^y%1+ow?YAeiJnl=B{c~-dYAM~%Sy#}@rWVF6v;*{4_{Va?8`IMs zNYWZS+!tib^5!1@aDZUKR1ZGP9XX6C#aGrW#oqMu7HhnJovdT3vpX~fCaLlef9D%& zi_P>b+Z{;lG*#{>W(ij*o{YtV9o>d{33siIhB}qZr1(=5vp$YT|BfaakMDRwI-Q;~ z4VDTPC@rckx7BvI@VUNbO1thqy+g&fwM{ay|UYe=7Urgr}<-REl`;T{6ek@>82&gAf?&`tXEVmG9(fg!}A!C#6I^{&K~x z3eCiwmH9G`Jy$RbW#~NP_*R-^&C|uPUQ4gi7?4ePk8N*cyTc+n*G0+xw z;sZkyG2ieuFg$gpD%nVt3_<~K<+TG1GghY`;7Rv^5uhhmYCN;lK#SqL>|x!ELu61C zgqu068;S<;sQ?v5JT+3HxQz;WMGgTd}AP6g_d+=VtGT05qFsIqJcF# zoLG2va^h{Fypi0Yuah9gSkUL9VPbMa%V_LXU@qDNS+KReP12_|bsgh-5J{UC6}u+Ha5Tg1MMwrdA*Ypq`u?SC9l8ndWt{LMb@G7=jwWR#gxTrcw8 zGG;qT9deHj`;xfysKc8o0@5J4;6-M`do`^mO1pbSz6_6cCr0yHwp?JJ&}tOk4c+hT zojI9H5EbqqE5`0J?fsn2bUXZWV%O^pixiF)p^Jl}9gFHkh*g0tG@d`axw{3x6kIHu zj|}&1VAj2j%)FF78(74IZSMTtc-TXI=-f$GjH57(eEGDwRJ<|@=Q*n%ea8*6)fhJ4 zwpbcbI)c+pqj?&wDF1Q3T=E_2PKlp#u^ z*PE)F1Ne%bRJqFTe>YjY9dAn!X9S%fPD3($*#|TdDu6H>$$R3)xAb!o(>wTKK zDj!_)_PcLUPyt)Su}qE3XxoF?O8w+k%?^!_8Bz0Bb~eluS9=m|`rgi%mhoWvvfS%O zH2q_p*^1*BQyFb6Q90wl`@~%@ro0 zLQYF(p;NLI!L0y6x#jP)$2bN25Q*(OHqfyLqUb{9g@;h|cQPvIIr$mkt(CT_gEEOt z{uL(f8aIQjKhFYI*^$tk%DwWA&%v`04oHAnT<7cy8rwm%KQpjF+le7!t}ZA23KL#& z%0w%2Gqc1nqc}V7&BVM|x`uJn?V^6KN>)WOUQAl_e62wf7yO8!iy!-1qqwbn;DEo3 zC+HQG20{*TEYHxD*QU(JBs&$re=0LOy)oZf1&;_d3~>Jv$=ubH`f>JmIv2Q@4=Z%+ z>G6AHgJ8|hLLip+I=}}j{9K6;aF@ti^C-v7(J*$JICE)g0xvs@HBIjQWb47p&LJ^GC8;yr?G z{O;OpQQ!!{HS7sZoWc_(Un7rK?MUp((@<}BfkLA7Rk~CZ4Hi>2OP|q+~w6Nv;k`5^m$o% znQe5G0=P>kt$8QA&5K%L`ukf4|5B}$p=l)5B9<{=W2~66F`;flg|vB=uF1qXgf$PF zs_yjfn1}nak5q{+;Or%2Do|>yrv`sQhfzOJ4o@I z1dIH=cf~vW|94i*uD)jIQA8geaKQY{p1W_ zgE@7hc>X6aCuD#+u$sUDEkTQsDV-79X7CO1siYmEH#?DFbRdHUzxi~`5#UW%{f4PsgeA#^&)&NfIuc5=z-*lra$REe>!|PI>hgYuZ-av`anl>@N0E= zm?ER*&94A;1OQsSnUri8qnYW@rStV+Qe25rGxhXHH6ZtCNdEZ<*0QOX8qlx8F zDV@Z?AA}sZX=#jxqyZ=OdP#!_ay7CY6@!nhtCev+a#vlgzAe)7Z4g zzNDHfT=2I(O6W3+8^89)hUm_pAC5b&VR}m$s1U{>xaO~O^-;x(#cc&IR~cU`#H#Pd z*pu$t^}ObPv1-@hCa9Dr&7WchPq>{HcRp=-Yaoq3c=j^~^5&F6z2zM32ws!ttfM*G z75G9zg9XF935nn1o6`5q;EDP?v~m2w=XTdboiMKC=eVbuCP!WHZD6-=XHFt69h~*3 zDeD-UQGt5?(}POgCcQvn9mt6N)1=tM@E`S%L{A%Ui5ZG^%Xk0BgGYCp(%ofCF7VXN q9R~%lDoKrAqEoV?2a^6_XyYS+C_N|jAHeS;5azy?YUw?z@c#i~%qa5! literal 0 HcmV?d00001 diff --git a/Resources/Hockey.bundle/bg.png b/Resources/Hockey.bundle/bg.png new file mode 100644 index 0000000000000000000000000000000000000000..abd2975a2aacfe165e06827807bb6ef46eacc682 GIT binary patch literal 1576 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 literal 0 HcmV?d00001 diff --git a/Resources/Quincy.bundle/de.lproj/QuincyAlternate.strings b/Resources/Quincy.bundle/de.lproj/QuincyAlternate.strings new file mode 100755 index 0000000000000000000000000000000000000000..eafcfb06b61ff3887231fd5954732466b80ae866 GIT binary patch literal 4196 zcmcJTTW=dj42AieU$HgNJ_N0kUS5i#Xq6f+f+p$3LG!GuCD@h>OS0_z-u#9>1x?OJ zE)N?Z?5(an-3{%~=C-gajZN)T z_ssm$mj2wgTl-CS3m@gpFPb^j+J$}xdhTgPWgV>@)Nkf`e&MSJnp0`dseYf@ciQQx zee1v9>o=^;>{1rk{nW;?TG=<6^|e0tSv~Z(=kmX5tYv4so9laJ*Y08L^VptfKR$s5 zQFJwLtQ7<84JI>rPzkG;8S`Fy?4VTn?3t}RGG>BTC6CAc77Of31U=ch(RJnTnALCW zhliJ%5pxHQWmj{??h&hp`mk!GSxZMY{W@^`!}Fth zWoadUuG|wmoBDn$VF%+w$16F;uZ41s_iM(^z%uYr4MAOmU$^V(0z2SEX%6PiotL>6$r+_i3*% z`CJx38H~~+{94bKha=yNDOJ9^B7j(5JGUva&Hv)_GxxWh|NrJU+UW})GCpcxaMP>+ zjIYaRJwvC8D8u(6}_zhBA>j0gd^>YrFBH8^>gh)mFI1<+nj1-q$soMNB<$hsQ28B z?7j1!ouyLeSc=v6S~U`*^w!vw^TAR(fh1XkO0XTdM1SC9flldQ&pd`nJyI#x!+Xw! z;Cr}HJ3lRR$uWGO;7v9nm#Opgzf?w4fsS;^`C&iS-mKXuYYT60WS(MyR3F41EsBF-AW>Ukh0&+oiSu3#BmRhm!z zxxXt|xv9fm>#pmeJp$jSo9ra2oLPJGw|1awiFI73JHVg*S4`*(mbRuu5hx>8g z*{TPhp7qZZLp#f%pyY~^59ee4e!+W`ztoqm!1LewrBdV>exmbCYJ=>|xc6Zw&ghvn znQM8DD<`+56r3l_i#uFd7hNY$X>lrixJ$gOZ9K0n6ttr@qHOYr9#OKYSrLnlr?s|G z=X#z|y)tK4UL7-AsdmvTPzdOfUn{?Imt5pL-jK_Y)s>~+S3)Jur$ioBsJc-Xf|uq= xKE{bGR2un}wIt8t?9*0PqrW%*t|f+&)i?kE literal 0 HcmV?d00001 diff --git a/Resources/Quincy.bundle/en.lproj/Quincy.strings b/Resources/Quincy.bundle/en.lproj/Quincy.strings new file mode 100755 index 0000000000000000000000000000000000000000..a6dc5214f0c28f9db73ad81c96ca5bb12d39e692 GIT binary patch literal 4138 zcmcJSTW{M&5QX>I{~&K01FZq0NYXYhK~SVdjG#qtPU67N5+%h#Wm7Tf*zS*Sd%hV? zxVl7A0vLiwYB@W5=FC<9{rfl_rkwWl<@0;0QL)oqVb5lk`;QJxM>> z^_i}{#yZWV1sw0wxishWgCxJ#XOref_I4&qa=f z3?z51=aJ5aomvR8-nyjAof-r1l})-%H(G(QZ1*h0=k^v2oQefQ>AKbZ#@@j|Rh$RH zxg=b6WPA=Jb8d*(J=TX^6G>egmw{`)Kz`iqeDz7HN4u}xKk2)lzDYmp{!UuP!Zg>l z>ijn|@43HQNj^7*-0ow^HHLbsJ*{kYPr8noPKJeG0uG3ZMLT!3Y|vSVKmL}EVDwtQ zVHBn(#d@#zLT@5!U@eQSx%@B?TH=owZS~yiWR3euSR)yt5q(qw zkFBZ2Xs|sO7j;(~F4BvocsLTG<>(#?b!+(0x9Fqr9k_1n#MYQzwx-0~`;}Z`*PZPd z*g3TQp zJ>tNaQ)%7TAtnc-Up2@+?0WdRpM;h>xmRi!Aok&q&O|P3xBgGbff!c$oqZH|H{Pu; zoZ9q&&uV?J1!OnE@idANW-TZmBprhY=A09murU#kng#JD= zwy8)kTY4s}_t^i=q>a0g)d3dk>UT1^*czZUxmBc93aW}6Yt<;G7^7nyG`Z9k%6!RxVZCT96@NL8h zRgOrYqhvdaiPxw3U2@cC^2ngj8q-~zR>obniPV{9pVyI?-Mr>+ucLo--ukuE@OAu? zQ3qBz!9z~@)Htvjz*;3PoFR{IuGG+&!zGuQDR|PiNZl7M!w#Y@hY}=<(b7^9v=9=swui> zcj7fjXtZx6|nRg`J*?CQIgp?vK+ilJqz|wfi&O zo!cTU#R2K>(y6%S^h8uY>$-{SL)$%*{<%7fWVCDayG-v*LznX)J(4^wM*}NHqC3@z zsbqu6LKtrJb~Ab&kJ<`!dt+EO+xx{N+gzsvKrU-zZOW^|6F zX=^&cua9;309~$3Vrz(PT2RMz@ejGcskNOMnVf~JBUa{J1v=C25a$1O==`H+Ab|G4 zuV7uT<`n0?*6vM<0?*pAe#YXfiTPqXVnT~r`=W8|=G5Q+I#0RAMD)aB>%w$tbSJ9n z+$3K5x^HWIV6oSJ!LP4bR1v4TFaP}?(npeiZTnQ!?K(--@fZcWMptMW)e_{rnhg1% zMAzJ+io6dFyRsi_ydMnbd2Ms}$a!!3&ewm;5odP>Itu+1OUR~7t zSvpe&WL3Sn>R8x)qE)KhSkfj|L+R;MAp8`3>PX)9KejiPX%~9V(ohe%m;MmH=h7~E z=HZ6qK*PJ{p)~N`%#`3!!5eF#duT@$@@@)NC)&RjWmHF=bX@1=wSFW7=tFcPpI|n7 zF&W`fiGEN-Z?a*x{ja*S;8ISOI5@JNPM3DQhk9JGA$;mR7r(45BfVpJOf!LzRm2Q6 z5gII*YW;iS8XiCvv9*bn=NWaE`7iwD8M?2Cq2t~T)8ihGa`xL${YkpU?BG4I5n^NY z7xav|)Vm~=AyAI~TjYcLpT>&_#KxE^nB0gk?1SswOmu>Q;}jYB!*})2kPYsY-8tY^ z5>-@{+xcmKE3>}ML*V*$0QrbGG8aElC$r6E%wsWYI?6%oc6ffcCAY~6pD80|gI7#7 z5eaqNWShPmDb9K3#?0bV@b2pIvP1i?jzb^6=v@8E!Sn|X_1s@7$XYnHRw0h{cHb%^ zTGAbSh8kHevNjQhj;u#5cP8kVL?_l8d^)8b`V{J2irMpK>u>HY+g|Z*R{y&qhZfeS W>e-Xk-N=@{^jiN&3$Fgf1oau5D2GS@ literal 0 HcmV?d00001 diff --git a/Resources/Quincy.bundle/es.lproj/Quincy.strings b/Resources/Quincy.bundle/es.lproj/Quincy.strings new file mode 100755 index 0000000000000000000000000000000000000000..eacdb6eabace244e56955890d8bc5e20c8d2d357 GIT binary patch literal 4280 zcmchb+in|G6o%JzpJG}GkSdgx!wpmjB0?oZXn{oP^~8t7BJr5mPMW?APr|eC0KMX# z_W$*WjXitzT5GTMujBa5-;d&9RB=z&el#(NI!ittdz0UFdtL^^qv5*cI<^wKLaxKXDI5Q|Zj9z8}Zu zI_YtImcGBxx1}{PlLSsbjuT0(;!{z5qR(wochc^u^sidHoQ!sJUFUI;H1s(SGa$7Tt+BhB_Ne8fj1oD^r^8FrLSrt}nH7m~a5GDseV(nXH5h>?&!0l6H}X23T_> zxi9s5nf9O>w9ZD`nJ7#zc%0D=!l}Xi#aN=(x~c9cQ|m%6hW3ia(TJSad9# z$lY3$uk~#nv?rpFif7o(em>J`Dqf-=9j1w5BKfTG8F6-=)rRxmGj~@U z-F!Tf+=V{2U_FX*bm9L?$p(4u9WO)#)$XQ<#&)=_5>K7ZX%eF3itF!bs+yb8bg+{B zi+*!mK>IAOMA3m{jU?4kvYw&uW_#DFoDVnGDKH!8^uB!Pv!Jc@>wo1Z>q@2$gbMMB z7TF{1n#1$;>bi;cUh@7TZrAylsJi%4>!@jTkj*|&G7*&K`ee;=HvnT-#Xhu46`P3H zT_8)?R>i==y|2GswIeIrZP~?3-w9XO9pb!+536kdy`KK*3bat1qw7EErmi`qx|2c9 zSF{)@`l)r3q!Tj=wT8YpRDYx@(HWR#=#Y4&57SNWgTy>#*;K!yR)23JVe4G)h`UO< z;on^C>P~Dtl@6XusArB6?s%4cnDwIud3pf3vG!kzlFE|%9utP8xrf!lfc}aOu6^Cv zgN*R0L_avvJNt9Bi@>uLFV72S8x1Nio;erR#8xq-g4^>&BN! z(%f_wP9e`rA99zVcl;?zA}#xGa*c+kB}?H%dL%LA?pX;x^k>H%*FOCqzWHDMm_3en zh~}jKcfAy!i3852r$&`Gst`8TH)zp|bCsm3WC~ z?VMv~BJ!N|>mF=d+MZVb+k7N1JxRJo;}iRCduCe01NoQOzMD69H}d^``jPhyeDLm` z?8{9i*Wcr(WWDR>#p<3K&J;`KWI`In{X1l%-wATA35ox80v;`8W*UlUgtI2qp@gz;(rcc7N@BqEy z9{9htyt6$XCo>gLjb>tduj9Xt&fkAMu=`fn9eww$wvm-~YL&gy-y>a3Y{wSX*twp~ z>{!=O{%T9VAK8`ttgFVKa`KZXC*p1NJJ$V?C<@yZ@3=i#=zib1$D%2ubEe;i_DGr@ z+Bg3Dt$ssVZF5OL`>~x!YGGfC>MMP2le*)lr?S83@S+*(7W$ssg`N!DBaz4SAvMjd@v zI2T3GjvdQVG$(Em+!KA^nu==a$R@k)I{sn#Ud#FPnkrj+SKL473qANxWnCpJ8r|VB z{)FaQe#H|a3Pq`tTAbf^@Y>Va!o8?udm~v9)rtI^OLpmKLU} z$w&wRZ{LZIsC!EGTqNc3kMJv@>1Jxo8F_Z@_t`4!Ag);=SwTcoD3a}5C2Njd8yeuk zmbtFOf;aL2Psk{|%#z>!}@4Y9)H^sdvitY&vLV|E9mGFJOI= zm$T?VvL=!mS+ZK8ALo1Dt5gp+_bD(NN&8Se40+JC`oq79Q`kzSj)V&NiWMm$-JZkq z)$Y2E_MS(-vDmpfe9%ZI13 zA@&b?SY!rQJWFv)_R)7@V*stG&fkiXZjxplTSZ8Vxm5}SW-2yBzw7TCR6%GZzF}<{~*16SEM0*w0B!S^7s|pWEaAHI6CbM2Bqd$0ll< z&dyGq@+7*#u5y(9V7nN;T9J`?KUEaJypG{a$g{IhEjOc{<~>vqyQoT{#IuM`#FIUV z%!{gDjbPW)?uPo`)+2Qp+hg=)ViK|2T$y3vf#OSUZx_wowR-=QapWC?7<_yrhia2; z^}YS#-bequXs_wvbXhtlThYWm+oBrdJs{Oes<5=Lw@Vd79GE+0(llyEXc|?{K->U&-UfxA|APy5|#s3}Ft&c1Y&Oc1Jgi zw`?*GnX#{4NyAcibWU(&iqQ9CJB@j~+56k4=+0VHVhVKUJd>>UiQL_)dAb?dJb_>8 MzY7`HxTC870e%?a=Kufz literal 0 HcmV?d00001 diff --git a/Resources/Quincy.bundle/fr.lproj/Quincy.strings b/Resources/Quincy.bundle/fr.lproj/Quincy.strings new file mode 100755 index 0000000000000000000000000000000000000000..7000849e36e2c259e74dd3e568d37cb6b7683d7c GIT binary patch literal 4528 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 literal 0 HcmV?d00001 diff --git a/Resources/Quincy.bundle/nl.lproj/Quincy.strings b/Resources/Quincy.bundle/nl.lproj/Quincy.strings new file mode 100755 index 0000000000000000000000000000000000000000..d84491fbbd7251d3afa28beb168a700d0a14263a GIT binary patch literal 4222 zcmdUyTW=FV429>pzhbEbNPQ^R7eELK6%yA8Bbi z_1;TwB{kOCiR3C>cXd71`^JK7C28h6+NqV!bls80j^vq{+Wzd((R-oQLtSsCyW0O| zx?|USy5`+S+Vf1S`?9Jh%|*H`$y@rJr+M3E2lA|FtYvrXnCd@CC%Sg(y|&T~^J7DI zu*zN+lIhFxfowh0Oh~%ie%ccbd+EMr_9ZtnEDOVHlum0j3%eJZ?U~nT!vef%N$+Rf zPi+n~+M031-P*t$OTf02OwS}(UFpZFuJp_e*RXN3;l*yf^4SlmZ0)92Kh(b|pLaTx z{u8b8hz%w6z>sqP$$_a^+*h1t^3x+)$)~ZrEX{MzDfb@_7JbZno$pmydj~d0(h+FR z>v)24w=tge9&0AD2gC{?<<^ZX56RV;EYCX0PV$4Ok9D7D526}*Gtrx8LKC%Bl-J{v z5of*-N@KI9Z*>WZjP`9$@Xr}BQ@$4IKyw4bajrLP^-7vq#vKVAEHCwQyFre)p!e9M zGd5KHTyma8UnJ*qg}9-gK1%bE<<+p37)y3!c^7MZ{}?B;*@K(}J21sQkenKV!}Mq= zi<(umBgy63Xvu<^ex6&4QI6;npHq1}lpN0In$q?e$$qlbqXCD& zVyn@9SL=SOd(gE-?po|;X&vU-<~Pz(tym2g!8x2^tDFp*dhUV~85*_)GlR0T z);hd%C3(HVFQLI%W~9~Nu*x!^;pfnLK51rFc<#Eh&mW{CxcOQU5nFeTQR( z#ugBqvU`np{9m#k(j<9TKFeb@Aa)pRT9om(`TtPC&HIS`Sq;m-*N zn-f(!!BP<-F77#&;y2j>lXDU|izDL<@7&L9@Oz+%KDsa7k#>1HiGgW~4$y6&>aZFd zk-wTA>w0RIu?J(YUuA32!V}%w-;8r)1koe6mt*HR!S{52;kk3gjxYh0%ffHNuKm89aRvqg}=raPPj>er$vbTRXnx0v3vU4^*cmyx5 zU$^m{7n>@9%!V3RSG#Bg8QG)B}^?O3?$QYgea$8uuU%<4~*j)dX{#Pzh zpWe?$UPR4BUza<%xir$r!#;d>dK_5i=TdZS-X&gqj=ZjQJg+t6=y)!Fk{7-k-&Jrh zUjl9I4=d6Y;`@Q#uIt^|0nYP&v6M9I%L=|9y$g1I-oc(}fBN^f-3O+FGr{{+Kcz4B p^ZfI4wW7S{n%}g}?|e};`)o!p#&F*dg5~zoOT{|AB>jyq#qVl;&}RSu literal 0 HcmV?d00001 diff --git a/Resources/Quincy.bundle/nl.lproj/QuincyAlternate.strings b/Resources/Quincy.bundle/nl.lproj/QuincyAlternate.strings new file mode 100755 index 0000000000000000000000000000000000000000..3def446d3ec9a0ef400a93cef664e30bcc7b659a GIT binary patch literal 4206 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 literal 0 HcmV?d00001 diff --git a/Resources/Quincy.bundle/pt.lproj/Quincy.strings b/Resources/Quincy.bundle/pt.lproj/Quincy.strings new file mode 100755 index 0000000000000000000000000000000000000000..5d082845fc3918237c7755c7ade1c8eb5e1a1463 GIT binary patch literal 4188 zcmd6qTW=gi5QXd6zhbZiND)fHiuYI?MSPcA8Mz*ZFD`6#-Y|Tv-JJhW2pB^ z(lcEj+6UVIp}p_d4|OfOH`;R}>4~hmkmkzX)5>@CyGiqXpPkCHsw2zp*s;`qVOP2i z+r94D1NY-dcd#m6S6VZX&~nv9D4SVX3n&3mLutBSS9 zlKw>hV|h|*kH(`E@fmAm>bq6G&p@*?*?@(y^4LChQ@WgKnz$_yUOe8*{9A84mesT~l^{8(zNuQ{}#H|>i((6^L+ z_J$>Ddq*R4-N~HFev}ts2bGXj*$Ce&pGSVKwd}zPG<>QI?dDpy_D+1&c188I$4bA* z!0lYf9Kow==>|KVBkF+y4A!m1l0Vc0QX7}4eV@i{BwoR-Q|G?@oOmj4V81S#Vtwi> zxxmUxM`83@p6~jUb<*{XI2N7(v4KVo<@6!@yEac`^XtSZtR)u*LItM4j*Li`IWljG z-TP>tdh}Ozv(=C77p>d18qR{7=zaTKP8LUog49yKv@IDOoP*sCAzreHDhVb}4Z%i@ zq``qa3{+ANXhi=y%sbuOO3y=giPRTDHW+|xZ0xNn{XaU;1A{|W%JkrYzuyt*Pgt8i zP(ygJaw|s4f7lAPOBBYM$NRo;7&yaoR)PPBO6uum@qYY4V(FPZ(|y$G(FKX8(nXxo zH@FAxIN!;LM;eP01)LVS!4qR?b6OShbRh5=>;7CTiDY&y&IX|^I#BJ*$5@`oc25md zLhMSlf*zgkaFy=*{kWB7yhs)ddaryg+zgL_Npze;j9l6G_Om3xc;!!6h6Fr5@w{R- zIz)`G?N_h1sOg1EfgaVqwwIEjvSxmzzJ8O>a0;VzU^*|=N;l&ArhTTKm$@6KkbgE( z8B_2lOLx(a7{lM3|1uMz@=ug|oGk7ZrDt0@itK+Er*(_;OxQ*z{wvJ8QLj}iYuUc= z=mnn;^WaDHWXvPF#j}4ma@1O!?SgHILPYEKY6~0T26*dk@jKb8;s1N&__j%YeChQy z^%LY1_c~5>oIe+SehZd_x%qM&*&BDLyEvU^Rb(yY`71lyQu)(Y?omON?uj{a(K%p% z@;z}o(|^K`%s# z+`{@eX>PPXCw0D<=o{#Ot*H)jlD=@epEq~B{pyJx+x=o?pT@gq_dLw#?HHc9N3d7V OzEZ67J2Boki~k9@$I{gR literal 0 HcmV?d00001 diff --git a/Resources/Quincy.bundle/pt.lproj/QuincyAlternate.strings b/Resources/Quincy.bundle/pt.lproj/QuincyAlternate.strings new file mode 100755 index 0000000000000000000000000000000000000000..90b375ec234032ec7fee5ec7c3ef6b7b811da35c GIT binary patch literal 4172 zcmd6qU2hvz5QgWvzha35NEK?!#|4Y?4JHltg(^RHnXYyr29}$V|!pLYi+5q zxt;47$-oaImBd2pmXaCkdZO#4-jPblv1FM&)7a29x*ki{nWULn>HUdoKht}q zwR2q`*$3MBk-hKN4|NS~jdt5;a1mFY*^{Pv@2cD zw)@<-huV){pi}&rAXo{xu&$9;!PQol7oCj45hB0Ped!n#?yy_1 z;da#f@hK4)NKfsyOg$Sx@X9U8NDyUYhyr$i@KRX^3mAdz-)U@Qzv<~*Ph@cIk}wBP zB9>QL%Q)Kbl^Hym`;N11PN1EDp>`hIm?H!FQbcZ>W z{U|TO4l*G#u@SykKF|7vWZ44=G<+(CcDZ(H12J}BA4&GurEX@_PM^woOrp#5RpH9Mx1Y5Vj9Y)gGFDK#r_Lp2qbtPt+J5y6 zi#%TXD$pat*Y;9t$fmhp$*JGvGj)Vfx-Q+845b5cebYWupG&^R>Fb}3RK^tiVd*aV z5o2mLXTD@YWc#tW$7$lQC_TTSqpbb!;eXC>*Gt$ubH13zj3eQ zB*&R^>1Vd6l2mTK3&-}x0eKfE^300Nr95+GH(QB6LvfD`s&r4xSr?sy3P5(n3GVJn z|MgbKms<@b5~u3L!Gf&P`2_{**}wZGRVw#J9=DFpb3MH+u175Zk2vAMcsS2_g|0|< zfN|)IGv`J-aAxP*iEe@pYB)IqQ|T6Wdw+W$+|Q}#yxs3r_G!GQc2CBP-i>VOp#%F$ M49G9Wc;i(57m88RasU7T literal 0 HcmV?d00001 diff --git a/Resources/Quincy.bundle/ru.lproj/Quincy.strings b/Resources/Quincy.bundle/ru.lproj/Quincy.strings new file mode 100755 index 0000000000000000000000000000000000000000..65ebcb320c6981e0a84923dd38492ec60d819285 GIT binary patch literal 4056 zcmcIn-)|a65T1v9O5UPAw)Bb=MY1Xf+oUD2jeW+A8zptsCQ{T#3^<_JKn=up|M;Ze zH*>=t2iVt06}n%$v$Heb%zU$a`TSHiB#{TW_ ze89U){~&UIV#@iG#KG%YL4;rSpccJ+VGKr+n(9LEhx*v6RA(LR|A?`J)W51g0 z=>0kFQR7I)&_X*tNFSOL*@om6es^i!)Y%JolH}HMcUm~XbtRwFO51Bgo~j>)%n8!g{ti4 ztXQXc2wm&-o?VUi&g@9{`UHtYq}t@VSroJR0t^%e~XA zVU7{oKJ2~52(v8FX9BePI!inmDMtMaayNKh>m0MDliQC`9YexoyPAV-$n@1BuI}N- zRp*eJY5uug)tu$W*-Bz>QqkJzeI;y#eZ=c9w8Bq7nD2=k;Jb;LU5xB4Me`W@Pt@iC zESV<^VIhSX&P-aQ`Lzh@DTMlk(haPw#v-!ww{AMJuF`;Qrjl z_Fv4XRyaTf<{2VgQ6G7}DaP63&j?*3zlMk<-z}ny8F_hK&aoP~>Q>jZmDfgcsd+lc zI>?1GzQ>#P`HC3HNAUC+{ARTNt3O3cJ+s$y0jsP7&tew7o%{JG-kdd}ealRIKh93mb!zRor6crFDr< zu&#%UOTb|Ru#($)j#1jkj^0s5`|PKt%{aUbEZASzW$biU?6k~R z+m*RVJlQ+)RkI$6(`+pfiYp^3#@yvtnjHN-Sg;XJa9Lk1GAh|}3!S$P7CBPJt%c|M zW%8VFfMYzL*pF@IVe&B9#%!uRx(*$u%HQO8ax=MoTj$8ZjX-NE%FFBu+=YE3)!vY1 zJMXF7{~1k>J7dJ$E=I^|lRV4QxMz!<9#Uj1`**oWzF*K0*M5C0ztv8ck47WwcTM!= z1~7ktC@@~d4s2aW|Df1SnS<{!^F%A0_K@$a&$bl0s)KcHeV+c8@$+uBEQ9~oa~V0!PNCL~EFQG`epa&#ehWN2 zlJ`d}UE<1O%*r!aWuD_ar{=4`If;z)1AM8>*~ibOcJ_-W-PS_h^Mv4DIsX3wQ#xxJ literal 0 HcmV?d00001 diff --git a/Resources/Quincy.bundle/ru.lproj/QuincyAlternate.strings b/Resources/Quincy.bundle/ru.lproj/QuincyAlternate.strings new file mode 100755 index 0000000000000000000000000000000000000000..59dfd4ee7f46122e92df20f04e2db0c1bc90a78d GIT binary patch literal 4078 zcmcInT~Av_5S@p8O5UPAw(iza6bV(tfYUkw3>VYTC@EDNq^OU^HnA13Eo=wqk8gX< z%pLFb7dVkB>wfLd&d!{fIkW!vpXah6Q6Ayq@0|>!B~7`Mp8SpHIo>+5DI*!nHOBh# z5$`VjhfH*=C3o@`Z)42(Tm$kQXc*((hURm~M9H9`ozIMPKk95lCSvD4?iHzGzl!YW z{Uz>6<3NVcLOVW67n-ANLvjnh`!sLr>?J&jg|*zB7LIUT%V)LH_S%r=>W3k7f^`E} zPfX$*4iVTDWnV+`8d7aoNcn-H6=5^?AqEpzJyuWoZL7=jm(G_lvX84Q--Jpi%WlDn zRhs+IwOa2fW|P+M!i#XUrrv>kx<~Kth<4IzUuNwi;IV;~jD8b7cky0C!+mzSGtC<2 z7_sfb-W!ZC%c4Fb(CX?e@nobJ^>fJG;(4QU%$inVKSp&336pJW4mKgvRg1W~gCAF2 zKx(4-=XR9~mLKOUiM>fBYm@h-uod)WE9W%Qa*;|O_G4>y+%{^E; zhNqVM9e6c_pH21MaUcS$qggN4^{bit$7nF7j^_m5#FIE*6vmU@E6jME5wi#s%({VA zEUTKioOH5|SxLr!aj1nV zzW4E~WAtadZ)4^#{F+CIobVKLN02!x-bbhbOhxanrzlC+``GDvWL?qmDU zW>g~_paP2wku9l@BHzr%+2hX$-5|gEh$Y`GqKp}Nc~#D_8oBCD*EE&a26Cl&+RHo0 zr82(9oA&vN6v-#>^acE8w7%4zc}qRBS91ZYtOd_f7QUbQ`6u3-HIjXcO#Cp;W*2Lr z1)o{P#%_jav8p*f%T|uou`d~w-PqsBJ|ne^JnL+l zcxD-ri5Gerb25VaV!SXw^y&2?FDz#O_sg^-?@Gw{+3K6kjKspm;A9oIn0aYk;xnx4 zAmbu9tOr(dTQ4w58`;rY%7{GWkl~57>nY}t$0RWpb7iF2(#NwS$)chc>+}V%5V&mC zzt^zEPAfb+SxouHG55mlWQAL=WbIyH{uYvqfb%~4scAC~ZvqSU7j_vt-OYDe=Bw?> z+$5gt9mT3yjl^lbmI$Sl2^C}R^;nu5{XJN)7EW+kU!7-EvgH;!Zxt+Zq>Nh$FZIjh zIbQ?Ecs{Wo+sxzmIBsJ$(;i)gj#K4tay+@2+`g@IVr=D%DQA>6ba%>z??}w=aPEa2_1CbZ5z!1dRYK7;<>uI;M zv)jWg@msFr4LSlfBqYyp!gE%&diJv_&-dKFcxFvC&i@r-{6mWJ0dLQ=?r9}i(7JC^ zk>OYiJJ`Q5`aqp_?z_Vd6MVJwd8s;B?biK?$~xZP&!Ou?roaZ+Wj!s zSzf;f9vQ@>^+|`8>TAYr~m}JoN*7DNQ-aPpoz#Oy}RmOf~S_;9fcY F{{a3*o0;qVx$honO{Ft0_5H}c(MgZ& zYybXM-yyBBsU&dviJeMnWnXF4V|}iZy6dy&(!VP9ax&V@b>lgGu4AoQILb-KZRaAa*za~e`LHT$yRF?n=-RfY zALL_woV(D>K$6cp=?ksZhQDnAb@YIT6tT$>droF zI+Kiq=R)t`3C_pGc$RyindCoUR>_*st`WcCsjW26G8x4~;C-t5OlQDr@*GYJU$`FU zeta_KU)WEQgidGf6_<9Q=cO)K9Y}9ZI@)4>q7~>>=^1{&}S~&p3!P% zsqsoSPkhJBrGmm+UF>h1o27prZM9m>np!iU_)Zt+a{_!$G?%r(`#6JWD*fcd{?=MF zjPpVk*(2ED{JBd;j~~}Osf?vP$6_@?@WNJ~@UK;-lLx@jm22HPU%@G?q1VT}j(B9( z()ZMUPUC3c&yf+&HQUGdK_~l9-BYBZeUg`%e%hgcHpl)9#=&Qa+~@xbmvxI%jC?$+ zfag&9+}65m(XR#HccC>{422!IL>3L+%Iv}TYF6xvLTqZSkDIJL*^VfgZQxDrB1e(` zH?uf?Ka~#DN^)O#G+937axjl9*N1-2N<>4%ezGvKQls5qfyb_9C446;SAYH~y)aoH zva{VKYh;v(P@uwmS*6V0R|Sb;Y$DP=ttL{T&^lp~tKe(tFjmAwzT%BegPKNl8wuB1 zSd&fYNvLzMljM}1z3B@eGWW>5)O}o3>5Ryy)V(LtBKixmTlfo4j3xb(hGeej0>JKA z^KY~gc2h;8QwV8Moodw{Dkt?by0G;gf(VIIi3a)!s$zc>uVe6{+sk;~nen*0nsnft z)0c2X{lr4*8lGSF6_wZ9O0%pyQ)JF%Q{|l~m5E%*d#Z^i_K(JhL694Jg##s?%gPrc ztUFUz89pA}+ud@Q*oGN=d7YPtcIpECa`KVL6MOPd`jOl5_i9B(-g_Sm=Ge%+1c=fV zC&r>%;eGBrFT7jGdhIxGWsOS`qHm$cp`&@{yuoVpAmqB#F+`o5D1PxIVtPj?;uR4Y z=mX+-2N6iW2F7$6iS?A%?w0q^fy^D59S(!*o1-`|I*=vg#@n&Kw~-s}gZJyZ$%xTD z-swT_kEQ7RQ|zROOWj|pmU!VnK1iHZx+iSe7gdOSl7Fk*Ts400Wc=1iSV#OXofxNu zckvnnuQayfc^|H+f6-Oe9{r3BB?ZmGhtNLyTq-ok4*gE_MJ|o5Hfr+C tc3xjSH=`u#b@}_3`qg+PF1v9?Z^rP@5hBOE)PDvU!9!UL9~WJ2{tw}_+t2_2 literal 0 HcmV?d00001 diff --git a/Resources/Quincy.bundle/tr.lproj/QuincyAlternate.strings b/Resources/Quincy.bundle/tr.lproj/QuincyAlternate.strings new file mode 100644 index 0000000000000000000000000000000000000000..d5fc249f151f39ac1ac6b0b61b28d3fdf7a2de26 GIT binary patch literal 4240 zcmcJT$!;4*5QamQ2U+H>gW9wUOmo~C@diL}-w0)adV^ghN+nL^> zd}VY0?%Trt)?4GN?EE3hq0TnC5A@v=MP&y%J7{-i`hMo82coGY^IG>u_JbrnvhV%= zqwc|NY!h)n`k9@JYh~Yw>al*eaozXbOX*(~XOWC{GhI`A;~IvX2li0%_yrnRQH$JM5k)EJ>n%0ARd;j+|;hRwGW#v#3SK3*Ee{A^J%f3 z`Ce%!#SEBLvL>_}%cl{YjWo|ZStTm)^+eC9WZ>5nEl3Oh-;Q%PJ{j|G>^E^jrwfmY zYrE3>LKj}`NpB>b?65x)1$tF_$3Nh4Wsi4GcJ{UQyKIWcXtcA?dL^4jeq!oeLE)V) z&X3(U3%{T2G@^#4)($AX*M)o};LnlvvNl8?GRUAZPR{IK(V}6<3tm)=@DB24&KW&^ zU5TV)dufNdeiGjU$6`4{h{8s{h_7W&rwG7DH?DQ#{tBPsHT3#Y)RB*zTKJjT<%}H- z;yJ3|rFOeKe$?{*N6(b0XrK6HPd{x@K$}y42jlQ(iQL!!3zt=kQ;vMOs(@!-`fO@l zw&>5o-`mg{Ec(I@T%wAG-^%L2`f^q5%0g^vtjjl9d#W8-GF`))+C`0`=C4 z=#SLCh-j*Os^wrFRjv#DT$RX%$o*ttRHd;_g9Q=0T$PBO=vLkJr;Ng6eelk9mtLcy zjD!Lm=G!V&_O8xH7Go2cws{|tEM2+H$iGn^xM;i#sTKG~ym_6ui zcqH*Dqj}x`pfK}nyw-DQ+v;^sq(#gTRJ4c@-dIceCk?4q(fPsaRQqp5iAPfxV>$?K z(UEHP8@eSuGp4Q8xq++*sbl~%1f8%ub5}8V(Vk^JpUhFQy*_m0K4%u;ik^vu^fIEo zoFyvnv6XhAyihjIWK-o6C*6qp$am_4XZD}g$UBf5dRGG_-pg(mE6`nNdW^V^DQ&wx zOy0&b#PX_NBE#tg%*822qBa~TLYYA}^WWX=cC6dVLJ)uZcL}s85h!Y)TATt^m zGhHOFr&_jMzlRP~>!{}VFnoP?76(R0vV_{WnfDLYYQuf_{q_kmaut}w0TDn~jQO(mn!s9L63{my3!X9Y(yPI{?@w|vDs0TR(Vg$M o{`Q`_o;%US%fEiHcI^GRoXc6g8`<2CU+O;wC9btR#}yU(6N9hZO#lD@ literal 0 HcmV?d00001 diff --git a/Resources/Quincy.bundle/zh_CN.lproj/Quincy.strings b/Resources/Quincy.bundle/zh_CN.lproj/Quincy.strings new file mode 100644 index 0000000000000000000000000000000000000000..56568d7de9789b4c8ecb1e3a7220e9dfd7e61be1 GIT binary patch literal 3486 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 K*MGXCZ1g|>0*&+l literal 0 HcmV?d00001 diff --git a/Vendor/CrashReporter.framework/CrashReporter b/Vendor/CrashReporter.framework/CrashReporter new file mode 120000 index 0000000000..92b400e68b --- /dev/null +++ b/Vendor/CrashReporter.framework/CrashReporter @@ -0,0 +1 @@ +Versions/Current/CrashReporter \ No newline at end of file diff --git a/Vendor/CrashReporter.framework/Headers b/Vendor/CrashReporter.framework/Headers new file mode 120000 index 0000000000..a177d2a6b9 --- /dev/null +++ b/Vendor/CrashReporter.framework/Headers @@ -0,0 +1 @@ +Versions/Current/Headers \ No newline at end of file diff --git a/Vendor/CrashReporter.framework/Resources b/Vendor/CrashReporter.framework/Resources new file mode 120000 index 0000000000..953ee36f3b --- /dev/null +++ b/Vendor/CrashReporter.framework/Resources @@ -0,0 +1 @@ +Versions/Current/Resources \ No newline at end of file diff --git a/Vendor/CrashReporter.framework/Versions/A/CrashReporter b/Vendor/CrashReporter.framework/Versions/A/CrashReporter new file mode 100644 index 0000000000000000000000000000000000000000..addf3e307f235354b5c07667eca18a6d33e63844 GIT binary patch literal 424964 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 vy#!{;6fi&#biO5^VM(xKr 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 b9>15$G&Cv(j)SC5jt8n`>UCuX8$0e zp^gu7u`&MmZWxzWz(ieVCbt%)bL}SCXr6A@j6pF$ottMy^@`hl$5#KJzg}4;jO#&a(-LQM?0)n)u|2-jb4z{PnIxed+x>*Z}Z~tMqbXY zythI6b%@#Ny4w^zo0IQk+(S?ALm=IF`K#I7N%y(`Uw8io&L#D1SxVx~l1i^U)Dz8A z9$w|($6u0FS+Pf)x0jTU1lPG=$GH6_sXe52{@q3U%Y&PXD%hO`yu(g!m2&Su^1aoD z8ony5UK@0mSEszPs3e<11QrkLr3g!5QP_t}?sJyg7t#e4!RoEMH)1Wd(*2yV+Vn`( z{=gpp)SMebA{T>R!kW>ZjsDbWi%C-FUMy8Fl)9ivK;TZ#Vc}#sTXr1NT)kItBGe*rZf8D9!S(7mlnk1>yLy|nq*=ERKDmN%|KV`6bi;s6e@7l4( z`|MM7dR`XGOXu)rDrN=-{nO2+V@n#ueh+NoIWwUyyGrz)u-9w>N`z$DR`sgRsDcaBasrP@=xw3gS zyPZ_o&ZO!thIM+z2=%rpX7rlm?oMB}I;qq97ip&uxx1FsS+^yj%Gy&z9zEPoT4fnA zgThpIdg0M&W9Pgn!{qAT^=3RWd3x@1fthd_soo<}s1%znJ1C;bA{9S5b#Ni5Z+m*ll&NO^j0YT4XphUtRO8fzDBTGLyqTqEv2 zQXjBG8NI{Ne5HXKHp;_~V9ow4OwBtXwQX82@C(9}or2IIEM6~d!HmQO(^JWQrx&{K z*`cOL-g2VHo>tq<@`_rQ9@Qvby^I^_3L|yw@wHBHpes{B~v8^ICP1NY4vJWaDC}N`x1S9 z>*L)w&6&u|Pp6HJH;{R-o$;n#a(X9md6d@#mxt^8xjZ5?h2>4&jFNk^_p;#Wsd%|D`_UymB|RxM*(tttwaBuR zo$0r(a*9_a3GMu7%FY9~t|Dl)^O=HzlA^$Uywc{mfq!7>-l*?bT3uaGUA1)Q0oTKR zYw0HO?Jh6cQ@yn8pmRVCrKgnLx=PF(Se5KrHUa6YlAL0%2xP2LlxEsX{%+`JX4Kgq zm_CcRW82OHJ3o3b#VK~Iw{Z@cNjj=WebjV%igUo^rLk^xRrjtGXXj@MX5^FMJ{AY= zknmYP!a8`6JZB^bjmf3qQC6#878f>TFN?Rv#bt4!u_RIEA0LJ1K4UO0bt^hch3=r9 z`Z3G;&E7}eZr#JeGN!-yKDbGmCM;V2b?rv^r2pW-E3_S7Pfq_!vn%U_H4NEZ(VRE4 z-%i7ttyj)*x}mPD;Zx``3{yY4ae z7(Mt7N4Iy+_`?)n`Hl?O=M!U9y+DO}9?KU1BaJoe!lgX5_W$*43M+n0<{8 z^V?Q9x#E7}I6j=YqST-KLw*MbcL>*xJBypb#hrJYEL;%RjO)dX;iCV8-^#+3;4=P+ z--&*X-#hvj$4NiS?>LVe>-^ReUU?q-)Z^Ie3O!>+6DCfs(!b|-+Iu~}{eMX-&uc&W znwR!2{C|R-tNy`p$jT|h|6csxCj+r1`PBcE-}E6oj&ONi6B}~L^V~w$=jGR_`3+n7 zims%2Kk4r#{WpMS{P*Dh4gQ0aQ`#wx`r4PNV`rOJ&Tq$hWwGm6Pul#jbYer&%5&s* zN&8vyx$y?a`JvkJ<3IOxe%F%lgZL*@=O6IjjQiGrF@~Qa(9*d3QU%X-+wQhFw#p+vDjM!kwobXTvT0 zE*yU8t9zF_&RX?<5r4M&zk$C9zm)Hv@RzIq`}k|r|1VF^p zU;MRaXBqylYxsxpOM72~YoSlRMfiNmX8P_FVb2hjW`(s8_B>&qu)^92`vGCqR#*pN zqlA6g3hN~7b;AC}3hN?l&Ns1Xg>@4)KP;?=utkJ@-_oPl?wG$8DSxZ!H@M}WcbwaC z>9{Q1XK;Updlo0*FXDcLi#p8w#AV>JaJzB)aEEcC_vgs3;pB?I(RJ=6{qFl`&p)8D zq0gItQjV+s+HwAyGH#%47vuj@H)An@@$dxvZEPnpZwPP2{}cS1@o#y?alW&X^#T8R z{J&w@jr$wQ^pC7dtN0y2!YA;51OMNv|J%v@79swI?@;d5{9Ri78P8%r#T&=6{>J=^ zS5Eu?GEeRG43;wU;m+|^T z{LiTW75v{*onPS}*6?d~dS$tqe0u5Mi-b>NXD%#l-bdK43A5Lee!}Mc?aZ(N!rnue zo%SqYHxTxwWe=0?c#-_#zRDVm+l(v0eGYdFx9Aba`CH@%k^hX8OUe@C`uJT=+G|wX zHT!ulum>65uFt+ryNbO^#)GV1AD|6PTYesYJY|;l`w9Lv>i?y-leEhq^$K5OhX@N_ zW2Nsz|0U`$iMz3x`r_o0{G(hS^MZ1w|EmWzD%+6Z_4)DJ)Mp;as~E2l-z6Qt^=Rtz z2>!dMgN%*;#J^MP^Beqo@ypux?m~X+Qp0b+FJn{6H%y&s3Ag)ngs`K8*=xlpVSi4T zeIFVltR+lO+C%K#O&jdN9mIVB_s@?qW|9An>_W;VdQuL#q}~hrJ%6FfE@XK7yzw^e zBmM9?{VnVMibBWvYs&Wi?>f$Y{F0B@8mG+95Wa+P(UTy$8Yaq?mX$$XGJmh4AxBPhdc0J)$aN4!oNXv{P?%xmvOouf4+u)9>3II#(m@n z>ono(zZ+V&rhbY(VKe#?y@$fWA_)5;VRqX^5!OVQJ)Rd5_IQ|{)KT)^O&eC?T5*4e z>%*PF{S5bCxTxc-F}Rhu+i`M5;Nn~#ZA3e^Xgf##yBBx_8Q$J^zD;{eIet!i%X;<| z+U9E3>>}FZTlh`e-%-N4N*!b@eG~sbs{h;g&ARY!_@!O1!bOiTC&TI-L)iDj!eR*v zUlXJj(mrBm2X!vN)#B=LX5NcV1kQ{*so%l@33Z%8m2JrIcJRMVJIH-yWe58?>VAtq zRQJEdzeM+$@7~S+L+gG6{+IB}7+ith)ZI5r-Qx(m7*_Xq!o~@+$C;^j#-m=n&wZJ_ zV+;3fZCKiL!oEnDJqOKvd$ol<1?~eU9Oow7YTPb&rb2Q?}uk+KfY!38=U=Op!bkvf~OUc%&ALN3uU&&A@Gb`dAn zF>L33&vEK-_9b?tT?Vn!f$d-3&wfj7Zg{{;FYy`F``3iq_uNdv-u11SZ7a5=PGVOs z$+uJUJ!|D{%Kz)XbDVFJ?<3?T&sg%zFIU1@Ps;zp>(YnK*)Fjud0+gd{%F!%h0CHo@25U?9kU5rMwmU1atZt6urQKOuPYH> zV{eXo^I^yN5c0Rk4N^ixw=lncL|uxgia2(6i@O z31PvoFqt0_xQ(uldx^Qxdo|Ho_sdv@)J%5kNm~%W&qATY$FX1lY zu6dk(!^ydgoTdCT?q|3+aC5)ToWjW(dlPOsZar=jE{HpdlQV*^;pBP07boW-PMdfB zu^H*d$$5dCFUfh(!EbPeh5Ht+7dL`?9T#z$X9S#_KS)2^jSJx9eBl&M&KBeuP@etd zIZvMNS?dB$#<@H@O8?61a0p1WMH>YupBgz1g9Sqa!c`+1k zeB+W3hZ(lB1-J57Ub{8;Uo+deUZx>G^cMGdd#kNLv%8m(F?^er0OfzIQQS zK5N}?;H~gtO(5}m;bj&#z{@TEf8Z4sf9avn^MR!Ags-#W```r@{{Xh_Ek5Mk-$X^y z$A<9|IN3_S4c=h!U%(%+_(j-UV($$6K`Van@XYf52EN6L|InXuZ?^c;@G6TN!uY%J zpIGs~g%d4KJi^*;@h;dd?_+R=75`&+t;IKfVP^X6@JcKG^Kg>IPr)Cu_(E9xdyevV zF|7DW*sgElpRw1n;y(r3^*IXL?eT5+7EAxr*$Srz_FJ8jbXe7jGV z!}fUj3e2!_FBxBd3ETbgS7CetzR}YEZW#XzUP635>u@vfi>d5eaNozdm(LTkg-O0r zcae`D_x#^PUIlj|Baq!lssDG8QjdRC`8}l6<2*7F`4gnft5M{90#s&CH0l-IAP+KcKN#crJbHszqHS{)h~K| z>X&xDc5h+FL_0_c;S-Qb?TSAmZ)Fyx<&nxSF-vguaBx< z^14&~lE-%ZW_%W^U-H_ke#z?qe(5v09%}LYqVs3!7oB73m-3vzFZqj3yZS}v@9~?l z{w#j`lCq1FGRb(5J`ZQqC4uP~-?&){DBoVU-Aavyn>eHv#E(!QC<@aqR(McX5)3^wS{a6*-O+{myUkzp(3s^n8+I|7La?RHE@}AJNz1WjW=0z>`CM|m(L{I7^m(icCk8VNF%zu-w^ilXFeYpzV zyKwn9X}@@sByAUYPm#COap6ul&QiCS zFNf-8+Rv29F3&Rbq#SY?z1ilL^qZ-h8Gp0QndR7%@=3eC2j{~{nZvJ@=$W}?`ZWQi zPSzazk~X*<-B05DIBA0<6eMl*Mb3V-4Vq?YgLgXC{uH~W4NTokne4L2IFL3l{U+@s z{Tn_Gq|6UvvmN)8mRZ_L(#HIPBTg-I=l`P2@($^plv&;}nSOh_{*^NS6y3LQS2ud? zY|7kEnR~U&1GAKQDf-gi;!OL^Hr7_6XUc5m+idNeik_6&wigYDUmruyj5U+*Z1e3? z=*f7I%j7%T*p>G~Cf=0a=cbzZKWVEVx{bINoSB!Bwr-4bS8c1wS=#DZ^xsKa^`U3l z%CwQl@cD5DJ=4FY?9nre>(Wc;Nn6Qf^k!=-soR_AF8&JV@HnZPw3Vc7|4-Hk-LJ*W zazD>RU+N~8DYLX=9rqpk%0+LsxXWGcrBOZke`qsV+1DfJ$=tK`lHjaY zZ7rUu&^wQlYZCceoTRzo81GPUQa?F|$-qgU7a;vOsry67BRHww*N{)+qz>X2DK;d% z=!uRw&k-AvUh3x?z)lDNK=1PHO&z z@Dn((a}@bs$i2u*$dWluYdhx%Tqo|N!iDg}cRgMX=fgqGHx|x=8`SP1ICqY7vJ@%l zWmt%fXRx~l`L4Nq{)fB&*Z-Vt|z`* z(?`HZV24hT@=j5PLvW+E3+?LE!8K}s9ImzOy$tWdhO|e9$^@jeM;sFS`>2~6KZ%_~ zYUdnb6R_!@=izbK^v5&s7;O5h3m(BqKK&|>T;;S*Y5(5M*+m+~k)2^T8A3X^B+E1F*u+yjdq*NIce zZdmLX?tr=N z@Qa-aqIT#U9i|Q z+zCs5hC5&>kKuM$%4@g{miibz1xtMmx4_aKhMQq&FT+i+w5Q=lSlZig11$YvxE_}N zGJFJ<{xp0D?yI=3#hIPDtNVO=LLM$!*(lkzZ}3^(h#dJay48#R6aPJkDG z&ddKfI39kN@^Ls0e!b3%uZMl`OUe(xvG7^t5;z9_7v(K*H2jQm2E6bZ=j189Uw2WD zDB?}|ABLyUIZHoJB4u586*-S}L+U2$ztl;j)J0@3QuL&(BBhKXrEDUlOd_Q$BG;)* zL5kf3lkNg2rHk`8GxkQCz{8lHqrKb?jr;AV|K4v)i6Y5p&e-zaR# z*9VWlrhL8d1>6s*f4|D7kkapuBc=VM4MgrjP9T$%W02C05lHEuN%9uGmsOrs`5aRE z>sjS)l}~86)IrjIhyB&QQz{=*c^El|{(4Y3sB*8$LX|sIW~p4KGDYR&g3x{0 z$HVr7#cA)R{VaY99OR=GxH zBJ#boUo>)zc6p65u%F#Ggp_;-RQ9NBN4|&nI^}@MT`HF&qljOEd=-0(H2kF~wWqR8 zWwXkoDuXHuRc=waQRQlti7I_6qf}m`%@&gX2vW*FsPfQzxL@>o^D2h}w>mgVc@ca7 zC;Fu-GgYRjyjkUZ9$-c9lJbzsK9yZ6PpLer`n4)6ROYL^Q)QaUor^sC>r^JGT&!~P zy#B&HB<0pM~H0oL7!McmOBs zOEXgPKdiDwWva^MD(m0pw7#k7Q{KmOn#Bv?TAbJ^SGiN=I^+=fELDzCo>aU28vcyR zCsgKMA9`+CeLed#*gUr^g0tYwTHhEr6aI|y5;y~nuhVvi)8TaGTDshZWxrueP`iUTpEHu=o~up%vd8rr!kH`89^6 zZ-B?G^3=n&y(6%l{~@@SdYbxC=hk#9eF<^WF0nW8TsZFO$%hXyH#n_b=RBT>WuF0` z(*5Hoyb$ix_?O@)xJBd7!I7{zuNr_OEc?&FmvD056*q4SId5S3y{nhIbO)ui?#vb-*US2Dlw=*Zhvcr!<|! ziQRtU2Q>Y&g!O88^b+>N8vYhx3t?k-9FBrbJx1V2*wo_!JeQA7#QtmeMT(Bd1oQ^g z?oxE(RImMmPV344wQBH@OEd4aN(b7+W8{meYdiEE= z^&fOjX8Syz4s0();zt`aMKK5>OOd0rR4*cq|nA17?s$GAM?$aSI{9)!)feF{Dc59v645*~m} zzZ`@6Vbd=U!+o&nmm_d5Z2ILPxCb`<5`>?|Nxw8ArCrBw7RuQt@Ismz7^jJ=fMlLKOTj1;b=`?4`;z$+P@FNnWC@p zHE;&psqq0g9X9vf-EbOg?z@HXa_S`YUZ_$&x)6SiIFT1r_NshRsEDP2b%hdW@ijY`;>R@o8N!CF^!@OlmHt_HK+}m| z()WFc_g6aZqn9y{U^7mZe~5Vm_x#kGXV=5suvvGa;4U~r+xe}VsSn(u`eSeh+^swa zw_E97fZO17P2U5bveI|LEpV5nZ-JYw^vB>PI8D9@d#tn};PI=DmAC&IN>`Z%}-PS*6-!$B*31Y8BTYx-Ad-*PMc2wVy$Y5KEpiIu(& zE`;UxXt+*13Flks+u%GnLDN45=UVBHz}fIAO&@@>tn{UDCLFKn^WY3PPI(oa4x9dr zhtptlpOJrXlky?wr1L|se3Oi+1jdAnt0Mnd-rPM`ur^*zSOOSFdcs+6s`;NItS?7}Dx&P~W5Eak*2b=eq5pV(=)b;!= z>JSf`_mi)|aj;oWUWR?}5!D}tqp>07nZPeH{g%-BJ@*#c@s?1#y@_rL@ur=};AD$0 zz)5gSuh-54aDo-z4adV~p0>kru$gzwun#u#s2+}m&Ah0EV_-AR%i(C)jN5#8A#B!# z95@O#<0=!5pia`RFX5MS)3Zov?P{RDo-QjocWOQUX?qMa^8Fd zduCo9z}6|)wBMs}4{ZA533vcD?e#o7Y^6UBkHeye zf%^vBt@iTa9@yB+hPz>7ZzJ3V8++++Cv3)d0^9-TX?bGdcDPV^9^3|-{HG|-DY#wp zpM+arlm8gp44eF4f}3EI{}9{=oBiQgxB)iy``~)GS?%}2M_^;W3qAxJ`%l1iu(96; z*TS!T&YQnYa1Ctgw+jx!xmv$mxRg4|{Mm_=@hNR4GD*XiD9=~LyakAo4LJ z-;L~(J`^rdxkF`gVyK?cD;P&`zt(dhoB*48M!@l~spp&I9S56wPQX6cwA&W+W8opS zcPAVJ8+&WuXxP|Gh8M!dULqU?mutJ6ha=%q%96K)cdrbU|N528cX&$6zXnxVjh4S0Zh`x>{JY?0*pxpXZi0iVzY%VPO~0nX4Y29g zWLWZ$dOV;~Yzk+olr+LikW&9>WhuLaPbP)xKSA6C9Q7k_{*S}su&MtDJO-QkpNB_b zQ~$H@2yDhfViNZcI9BZ~hlgNeZwWjI8++04S=iWH01v>%-raCNoS^o0zcPHEn z8++++4{YqE!ridxpJuoVuGRK_8197owSNx59kA)22jOo!7Ffz8_3lu6%SjD`9v$WmP;byDdlHn$+ z-Il|Ruo*XTa06`G?EqX4A2RbAJ_4WBcH0Rbf=#>S!IDPGRjyKWgtL%R{&mWdw}f{k zvrkw1UAHkVE#3=vz=N9Kvv9k`Rd5?zuj!A%r{D(V7P!UYm}J_+;tb-OU{n4xaHGXh za05J~`47VNuo(v<@DbRIlQH-Z+^G4#3D?1$6`8A2 z>MSg_MLI~abBVGGOPxd}r-bHd?nkI6Y}$1%oB*44Yo$!_aI@C?I2;F?c6$`|!KU4g zz_GAtw};>u*w`C_qv3Y7HwZ6;jlF(23O4qhg(G2OuN#hljlD?9>%iS=F9M$WNXXtK z`jfD+_bNOA8+)VhIBdq*DbkOL{tvuC98XkbnJcx$-VKYw_z>=r*Tc^s$R7zeFUam4vrQ{*uK9yoqSn4iP+EnCe zq>PUiW$8N!uf09gzU3*bi*UcTZz&vvO@HOXRj_H_9Jm}d?VAOc!lu6-hfClgwbu$4 z!p7b)I3G6l>ft=t*gFj8!p7biI2#^QdoRFQu(9_noCzCyU2q0$>^%vm!>0e2+|D`y zxBtNF|9NlEBCm5^VbK0-OMw{u_kFrnGCcO0gk)QKh62ma>VII*SY< zrGNG+OWR6#!|nWgkP)vuN3khRzN_pb-&?=vg?ACw3#Zh3{3P51zgPJQxEt1gXMdsv zehMe+Pb*ULby7q2#tECU>|KN>;VRAVWq87}HweFq6MN^8VlTzA7oSQy!lrynVg9&n zYrWcA0w=(xd<)^5abj-~QtV}0@mYl3so_TmH|@ZmyLJx2CZ7OY2S2I#@OSB*TFXu$ zd;ll;>_SRD?Zhwos@IOk32TF6^}O*Id@yEP+$E{@?!lr$F za02|_8XpTU#fhCLQbEj-WSCion>CcaVPEpC8sw)E@a z0W1Cp+;8!ru=qN-#fq;D)31T;{DNWWtKcLneK~B~D~0X+OW;$~lgq6ybv}9C;;wa} zc71pq&kwL^*CTK{yjk1z5ZnTrab62IS^5XyMtEM6mtO#`$4NgtpfU$3^ClH3{qh=Z zDe?)Gg&RZV$==9)1vcfm6V8QytmWAVZ^4QEiyQd2smKnb*n1c$`NXJvD#Ppla-@W( zBSkNMy>qe&9ciCY$`l9tEcU^Vqif=0;r{g@j)5Px;-le!#S7tFi=)EgBjHRdJ|axt zf$jXJ*3Gm(3CCIKCt%y&IBe%X1}9kMr_LuEEPg@zW%OgAet70%?4MxM4_)vOY{o?= zJOG<<(GK@n`j5lC@Dd#tt#CI^`t7vJ9Z2bqg({orSGRw%LgiSP|3x7w) z!#p?!HtY7p$60sbkyNk0(%}W@NWP``#eV9Y-ndwRlyNb-$&)=uiSJNZnCYBMB@NdJ zUnct>xQ8@`^WfDM=fbt5HSyW-Vk!vMAK3KI6L2hS#>r_o#?oJU7ta-z zek>dXuhIUDfg|B9|frQtlX7>Ss7Q zj2FUlE&V9?(&mspHd=jF`iZTfd6vVR>Y^#>u!SL`px6 zVMF9(u5)sVFlpaskrSkQM$-+$W0u|X@F?7@@#o+RII;UYQp%aOo$r=(+$L=2`2jZN zx*1M_f2HY{!pm`z{&D2hjFZDEU)bSsgUX>#c>K7^U3Yus-=flolyUU@Cq20uDfL^b zvM|p%xf~s-r!SB78$M5*;XL?=#kp_^dL}*_e!z;)f^#j-gy&hD5f+~gM_ch}Vfrbs zonLZT`Xo5sN}mAR_TphX|2VkQDnE6eVZY;3Ui-A=^PKzxubvJ0eD{Zw=hxg%b8hxi zFFfJXJpaH0$}w;j+^@U<&V>7vr?8O$kDl|=kHhJ3ug1R&r@`hsoIW_^)6U6=AA0&d z@KS6_IU-cPMwsyPNcm2qm-JCu?#I#TvCC4REnKfK7g{!Vx<|?e`WO4V&^U*va!QJf!Wj8?J>1 zm2=@5*tBOR9E45#CBs!aos*H;ehF|sPRe_HNw3(vHJv4@)I4AW0u~_giTp?FTwQ%PV1P~ue5;o0`REv4)_o}qMQZS zNxY7ybhs8C*7((M4Q$#Y77i9TC!@4IqT!u5Dc3rcOOR6TQPP?Ac!97HE8TPOF!83G zo$wHB+T%1l2%B=Yzymn3`!rJW6CIJ0=$Z1qMp$HFDBavb?$5BP{~|aZHu)}vtKf01 z@7-`YZ0ef{mllTVn-1TJlX4ug(mg=fI6SF#2H-JxLiss(6gKVrG&};Ed``i`RzA&e zH%{vJI8yRUDGIgc(jwOXqENXO!%4)O_FDiaz$X6)I1VRvBu?yRTJh@$%ZE+*bKtIB z+;2u4xlROkF|XkcT^Dx39m0cNd@kG$m+Cm$0=MmQPHz88FaBou6!GRemPK$2Y`$}u zB)?`jO7k0qo8U<0vv4CEq5Le|0Gsygg6m;NQcIS(o4S8t*t;m@m-JcS=bO1s7vhx%uJG3{L(vOgF05pUXa9GkJQsn0MR z1DpOk2S>xE{{3(iHYA@*_$A+$Rf?`~ilx5_-7MJDe;r&1oA%iOSHUL#y>K0D+W!H# z0XF453U|Qf+`k2W9Ovd+!g-*!drS%A5|+PJ#C2jGoCHTJPmm@7HvKjV$HS&y2H?07 z=VYw*OCP)d83uL^F4P5BDpCfKz54!99E z<;{ZYagxsgq~yO_rRWNGS^7^B)@Rv&79NC6{%7C_Ki~Df?#O`^d~JW3Xe&zi2o8Z`oZ6r@^NFtKca( zUF$mlPr_-+z3>E_qWm;G4ks(O!DDceaw9wnCnz6x5K79kHg)tY2T;de%Q4Cb8y4`tn<3=Cmz5) z+@tq}*We=-?}ZOpoD0`kd=##QgKBSS4f_p?kKM<3g-6VO6t03Nm7j#m;e1X13|tCl zY5aM(1kP4ofc-*?CyCF8Yczcl@pTiT|;r4Uhd^`+i!@U}RfbwU-eacO6CS0ZV z-hwmWa^(y-9X9rI_A}mvhdujeDNhRAto%Hj4CgB^hm+teWgnaXrztOh)>dM3*m(pr@~Qix7u^yNVs2lg8D_kroLxk2X4F{ zNq*14Q#E{7sqNDPPr{i$@Zvk+2{>Wc<7RjqUa0!V;4wJr$6kCrJZi-shDWUUT6oxs zKL8KGv1)%Z$Uf83UqgJKrN0{Ph0XYgfqURWEzf+o8*Vt~m1hcjU2v4zzX*52X1u-x zcfe-+^uz6Np4xjBZiCNidprd{TI0?0DwSy}lT_ZUa-qsM>2p~ZE~>nsvPY%NA&GBO z`IySu`$FTqjeKihQ?BE15H{s{6t1%PA-EjQ9rW5E2$#ZR+E44?5_n2^HCzaradJJJ z4`*xoNH`B3QvKJenU}&p@a(?~XT!;=e-6%qjlB*y(~56`Gb}y?r^99()xv488D~K_ z1#Z)RErFA*_-vTD)|#dHr^3w5>3`>$0Lxk+?L47!^gzg7H1W*E)_$#D1T1Tb=s&Dd z)*9h*l{-{!RGFkQ1}XnO;U&hR==Z67O66&!oF~a#l6aXjB4uuf+^I56Wt_?=m2c4( zqBp8i#;mZ6RgqmPWy}c6SP|K%@{q~{DhrWP-Z%lK#BPZk3OzJgo8o zmC`q&f49mUl^H4%RW4O2Z7=%sRnAp88DL%1{w7^4frt$%J5H{mC3qA`cX#5&@K;kvOc(@-n<8>+A2b=M_2=2Aw z->PEWg3Wjxg}dSLbKZP94|l<)JTJhVu(9_H++oE(3Ae+|TAntz4L0Rzg-^k0TK-13 z1vd5$!_BbCuNH2y^sC@TD}EQ;V8!o%>n+|0AA!w$PJ<7@raY_RI=EiTyBw~CP5=7f z8aPACdp#V4)0OAKRj{egL?!DTT&wm*;ZoSxdkHRqQ#AbmTnL-;JqPE*rhGkc9&Gym z6r2kuY5JpZwiTZNXTfHCtcEk;d@avnI0H82SpcWQ@uog-8XTuQR>67)XKDKLaI%&D zESv;4>v-vd6JVdF?}FoDQ@;DA?4m5RQanG<_}{VWnROJ8-nduZE}Y4cT7~%bb^WVMOI1@n)a&@;>GxY|;kh87b?V%w5Sx=BUU>m5$2S7;6$fq;f!|%s~n7R4H>#_?SwW zQ^GQrLU1Y-!m$uPQp*AY*i^` zm2fGe$Xb&zN9*+}90Qx@yqDl;xJ%=Q zV6h?jP2m?QX+(~w94MzB)c&)C^~1*AQ?P`IewWI2;>~#*Lz8gLQ*DG&<55cv{>2Muv#(64Sij(~E zRZ1G+bt;q4AJqJo5|#j)=b7ta2^0Mol~Kf-`Z|O~TInzCWgc7UFTxTg>Bm$K?+v9t zL)Z{Jp!q)s_o-eN;b~eg{7yTZpd1Ic!LiDV;8Sp++KGZ&;C^KXZidZ$bE1rW72ILw z9oz`#tG!XU0nSps0N2B&Jm=seuqjVFe8@`wI9vyt{2zsDVUz!1xCS=+n!RupZ1T&6 zOJVao908ZW=6q?glzkK2tM$7G=Ued?;5>_8fOBD!-(ff#Hs=d9a29OZ^KLj3Htm@U zXTaT>UnZOmoBE`|X|QR})o=xd*MUygvJ-bb#Sfn-Eb{z^38@1;G}$_E3)Y`p?XAqhWQ8&4SVIthZ|rs z9@oM3@FBII3?G5bdDG4CA=r$^C2*akKPlybO}mZ4HE@mE8-jyyPYo)GjSoe_Xw4d zDx*{q=f=mXj8Pe_(x);`WxUD+l}Rd-Ri>y!$lpkF^A#!iip056;@<~L{&O^Zp6XqN zlzc>A^1T{aqI!i$(aT4QUY>^MYIwGWXK8q*hG%GajWWk{Zu%|1k)l_l;Xw_r((rN(FV*l84KLL2d=1aj@LUbg*6=J1&(!b?4NuqbG!0MD@MH~7 z((nWgkJoV4EVtYP%Kb=b-yRL`*6=P3Z`bfP4L_yfO&Z>);SCx-g_QQ1P&tm2b}O9g zwOc+?+AR+$?Ut+I*&3du;h7pPe_uxQ(=|Lz!&5XoS;LbwJVC?bHC+B44Mm9XX?U!L z$7p!8hA-6ca-@{M^#8K={qa#%*S>oulQ4vU0iumH+DV9Z_#q95H09DZ3Bw^I0R|Fn zYN;~`2~IF1V-hqdwIfn*^wQn|(HD{SrC!>iPz{Q`pm0emwOFw?2wG}Ur@gd>5MY?% z%m4{_-?h)0nKP4268d?c_t!(=``zoTz4qEa&ffd%z2_VtwT~(w`R`^dgVSWs3M9Wu zfRv9ymRne!$MU%>&t>^6mglg18q3Wr&t|!aeu(9JS-ywm?JVEU@-~)l zW4W8<8(H4M@^vh4W_c6K9W1Y6xt--r$Op1$a5C01IvA@M?TpJA%NffUt&Am%g^U)) zJVtzPN2Zs{NZ;F`^m7=eF`5~(8BL7afYk1{Fm7aQ0aE)n;~#1_*+8ln6A)Q7WU!pR zqeJpEmZ!2jh2=(;8(2<8Nxv8N$xaYR_Adfuy|cWB znB|99-of(yEZ@uWJuGi$`7W04VEJ~Ix3PR1%UfB#h2?IRZ)AB3%Qvvx#qxD5Z)SNe zoF>2N8%E@J4v_A*l3L?1$Nac{ni0>Pz_cdcJklJ4r z<8nrPcS+{U#dr!x{&;|J#c&cx>7QWvah7+o{0Perv-}XtJ6OJ-<@Bu_va^Tf?JVEL z@*OPS&hj>v&%y+g{Kx@PeEQxE<=4dW43?*}JcZ>(meT~4^n+~oBHQ%>NlxF_p>ne` zE@v!fEMv4XmM|7FZUj=fZeU!;*bJoh(#CikDDQ6|<>v^H@^hHwhgjah@-7aa;;;uu z{u)NB{t8BX|462f?;J_Q_lzXsJ4Q17JjRVcO1}k2>2Cm1`Yx8QV|g>nn^^8-c`eHw zEU#j@o#lHt+|J=$K=QYZ<=a@^%JSntD)&ytBaDX`dx2E07a4mPyMR=#smUsC1j>8? zWxjwiUo5BZ@Q}Qjh%4whH3+|KglEH7ty8OyCKFJXBh%PlO=WBFW`=dyej z%X3(c?-)t{@ckm`cQ52rZeG^wVLg10NXk#L`~=I7vz)$BMERs|6H)#QqMwZiqFv)`)|02it0!dzi>r{?~j26Z`#<`5SjP%_g{BNjbtYWkS zsk~YkJwPh2lR)z41d#kZ&hk!{A7S}nmLFny2g~=fd@sxQu)Lk+yI8)1<@9YQN-rHq z>7@ZFpGKA&ST0z8k>h(geh-lJEx0b*9V2~Hio&^!vlw$2Yk`PrsA62sSPqo!h_M$) zG+{9P%-ewOcL`5qwo z)6R1G?iAVC!Sd}aZ)17WIMr__ko0PSR9<$LFK2l<%S%{Z$Z`wIb6Gx%JPD+FJj6@Ib^*z6`q(^$dpVqfFs(yP1Cl*6V>XcNlmp358Ia;xfn=wI<@B8_l3Q4g z?`p~LIu17jsUB)s?qGQp%Xa`NUHV=YVH@K%AfJ^p61PIpHu+ z=9`hfr)9{+MT$2INbz!jWN#YF%`DGmxryZ&EKg^78p~5z-pt`94m*LA&nlMNSx(>i zBDt02B`hyw`F0?+i#En>jIE4Y7~PCVfRyh;jQbh)0xAC4Q~9CmYfBaq6)#qxD5Z)SNd z%N;DQVtF~s%UEt@`5_>cV+Z4Y#=VSt7~2_lG45dO0aCf1Vm!%s0!Za5@Q2Ee2qC7K z5)nrtid|wFV>%=WrFH zgRz#;$=Jl$%(#xx#khg7g>fUJn{f+cE8{lCHpcCYI~aE{wlnTw+{?J1v4imt<6*`l zjGc_f8BZ{tWb`neV(enz~ocn8KLKn8ujSn89db%w`O&7t{JN z+09{`#YpSTB&YRf!aPP=pQdmjV+o^`v5c{taXF)%v5L{bSj*^SY+`I?T*v5Q+`!nv zxRKG#xP`HmaT{YB<95a!jJp`y8TT;mW!%r$!FY)AFyj%%PR8SmCm2sMdKgbJb}{xa zdKoV=1{r%9sdJ%nH!vC*Qy5bjX}z2DX#JZov>tBaFq)G{B|E_1=>$E7)3@tWQh@IQ zaZMmEDQQ5`O9#G%a0c)lpb3aFNXZ8NH_!}x8#oR49xw;^Ti`4p#m{A&%b3S#VJu`U z0sbEGtQ;<5EN5KKXlJYfqU>o~U#0^H=Zn&%d=XN<2=P}U<%7bMFTzkhO&q3tk(}~H zNckenVT{ceuE~Cz()kPgp>$E#DF)ylfB+ev>>?ohVbUW%NRRwD3M4-Wsk}&!(jh%c zm-MCq$zI5>64onZJqzpQ0m%-PH=!L!_Ne^G9+fBAD+7`}D-d}wGyzG^2_(H*An7@P zWT%R;1xR`ufTZUFlHNKX*=q)py=_3!YXy?t79i=lfn;wZV>^)ab^%Fm2axo(1Ib<+ zkn9}-l3oXp^!5WuZ!eJS?O{9tB)#K6((440-Vq?#I}9XyJwVdy0+QY-AnAF4WbY)S z!2L&hy$F+D5J-9#fn?7MBztMBmkK1k6d>stfn?9XXaNR}Lh5WsJ>0(rW^e zo)buVwLr4x0Fu3pK+A8SpZyjSBko2|zNv{=1dRu^G&kZDddw`_Z4kW!@ zK+@X*BzxN#4+BZ>5RmjbfTXt{NcQ#u$=*pI>74+Q-f4#*99cK zQ$Vr@F6~jjOM27~lU^_D1%YJmA|v&?q(}WQ=~2H-_NbpGd(>}}f75`ZX9kj9HjwnF zpC)_MZxiMLNpCKY^m2iuHw#Gia)4y73`lxbAnBC=Nv{w{b}WoWv{z6AK%i`7|3hKe zkYO`lH*q+Z!#Nx-A0byExp+VK;|&aJY@bdpX?B;lmv6 z;P43!cXGIk!yXO?Iqc=I5%nbV50v@ma4Lt*95!({m%}+6F63|?hs!x^<*(rhooX6pE4pY0M>kbavIo!-)Cx=@&?BZ}Mhus|B!QnOz z@8xhihYxeOgTp5{+{xiC4tqEp$`elzVelYSvz&*N}8hpilTaM;e_W)3?!+`?fOhg&)9 z=I{;&gC=5Ph@^XET}@bNN2aVKg(E7}FV38I6o&jPj%H z=P0c02Pv%WCn>D$M=87)eh^YWOSprvjj@%{&Dg@|Vr*t~GCCOTjOC10#zMwCM!Nqg z-3^S*jJ1q*MjGevzoC$k#zhL}Fw!_m;dDkCcPUK&5YjkJ80sf!oG74Sq?-~G?Xz?d63)!q;&Ea=Q8Fpx)|3nHZg|E z+s)xny>uW<>FfoP{dUG3KuTvjV;kc(#%%Vtm&0Bl+35k2o#QNTf*#3h867~nzMSKS z@?XYbD`Pujs9d*mI8?r^91fN9Mh=I{J5*1HS>C}IsxO&2_z{Gh{P8k&vEC_04`ZnQ zsU1;%XkJC}XnsZ*YELGFNrmQRgrW9D;|hgCc9we6X%JNUYlujiqE zCV4L7EFi@<1EFHbW;8LHG^}6C<5BA-xhH$?5zHA)RX> zJkChxb0|#bO(>l$jBX&s-@x%)jO!Si8Pjoof%F8B?9e$9LOP#9XoZ~Y7BX5`KbQ4q zG3GE%W2EyL6n`UQ6(bEeWUm)tLOM@D>G$yUF2+-go)97)+1tzLW^^#-G1B=B(#ygA zKG`=j(zymoht4z5^)$v*#uUc=j9VD#d;`U+<*6?Zk@n-sP8Fk_@f0KNyVG^rZzrUEcEUr9 zw69KK+D|8>c?dl(qKpNaxB)UTPKV0HA2V|+a6a?HKpf$hI@H%c&HOkx{Vagu?tqaj zgo*cpuK~XWoX%SQD|6~cwldEHf1UN+;8-`5`g_4YW$ppL$ow35D(sR?2b{i@Ii0zi z&HNtRS2Q;u9Xt4UL;7gv4>Hd~TiFtlgTKza3H*=Dsoh*)J`Fq>c_5oqe$*GsJYo-1 zVcvp$zZ&K@qzaj5(xH8{7g_F_fHh0z)W~|6p8_9^ypg^eo1xReDXkOWR7S+hZxG^c z=1t)B%r}5P2~KhIz97UVmahlj$^0eo-!eZA{t@eo419Be`F`-xC`Ynmx*5O!WL^bM zeKN_{f&ZBKUhu=rJHhoRYtl)-MTp7FbHHuP+rK14BlE-HPcip^{|9rcNr;2Y9pHas z-UU7r^+)zEX5!_s%sp8`xS3lgq0KN~mo3B>(3DAkBlum+_ky=DKL!3v=10DabeZRx z@m#>%GFgbr%$-w&_#)bev9*H4Rc_J`CRZu z=4If|fy*)kf0KDT_&dyxgMY~S^nUmN^YVXzpSa&BuIZaX{0lgxo12fcSiZji_aMuw z6x@R>?^%F*f#uUmuwTL4T#CFf-&Ka+>);+H`}E%BN#JBZ?=F--^CIxo%zp#^H1o>4 zu_waZ1Admd>svy63HLwQ^n%Z3{_s6QSeQ@y4&EWo{4n@;m~U7v!~y2Jz-QrMlI)a! zSBP&hKMcN}`3Z2^VKSr+2Nr%W^Mx%1vrq%fl#7=5BB|IO%VDM2J_IzY6{b<{yHeXFh!$`VEXXr0)iw z&3qU5UEowc`@x@OZvLJSzh<5beu{Ysc=FAvz8$=l`9*LybL*q%dzst8J5=j>V^4sa3AwM;4{9Y>Kp>EWqug^XUva- zf5`k4_!mv8{zY&b^B{N|^Iq^jGf({$bTd_b6Zm50X7DGN=Yqe(d@i^FV9X$C~Ro@Jr%e)Z$UgqWCkAvTm+$L%d3Gph)F%C6@f5Lnt_~_eI`5y3G<{t1G z=Dpx<=Gni+JMNiVz{h`Cjk_E?m$?)CUgoXfEzHH+=x>>qgMY}}1^z|MA;`Cr;ER|C z!5?65`W?nW=AGbgF?SzE+h*9im{~7Zx@INxQ{vK_6iW;{A zd^U40IKF2ueM^5A;}`Qh@ZU3U22Yr(>g)i&gLwydCG#$DH}ljZcsB}jJGh_uUhvFm zsvQq_F>~X4c#dG61O6IwEBG1aPVg_@uEuqN7c$=g-psrcoW8?NE zs@w|xFU*_4>zKEJ|BSiuztMh}w}6lPiW;{A{OioS!0VW&c48jI+zw9P{UslEfE#mE zei-~t=3ej>%+rtJ-eJBR{2k_P;1`*9fZsAhwbKQ@huu5ImggH z&s5`S}?7nOAKz{M<13w#E1 z7x+r%d%=IqTzr6Wgt-Zvz9~v}3cF7O@9Q$ND*^O@&@i?6D7?BLUww}5|#c{_Lu^Aq5&G8cbF z88SD6C+Dhmtl(c|-V9zzd;;p(Bg8u9LGZ22Q~n~ve=;|N|CTwu>-iY*i5PeOM~FV= zZtzK8Q{`L0zs0;2yoLET@Hd&afq%+;J9yF@HSP}Z>CAV5-^08ed_D6$;4d=Y3;rJS z{or2a9pLoMJ@WGqIKIm#`C)Ju^CRFdGw%fd3-jaPHE{@Tttr;NNCG4SXYWdgt;1=JdX0FY{dR z$@!|Cx!`u@dEh@{ZUO%d^Fr{;%uB#?3e>pt4vKr3(|aQRoq0KU2XlH4!{^NH;8W+R zap^q?4)AelKydm_*)8DPz+Yp20{jYdFL=I1)e(Oa;!$vlI~V*lmhS=o3v(~{^!cj3 z>F>CIndgDO$$TC7C*WjfFZlI}s_*&?&j{co-w*y6%Tu~A4`g{E_~$Hlg5SPC)!zkP z4^DaM1%HXl)LUB z!n_B3Be+hef3|D*K@ERf!#~pSeht56am3Cn4KLPkyN0jPaJPp4T*Esw{G^6o((s#> zMAH45hFdlKJ`I0V!=KUcb`5`9!@D&6l7^4AM$)}W!)IuCsfJf*_$m#5NW=eC!#8XA z>l*%N4L_&h`lS(nMr-(Z4X5wXh3cBVvlimFY4~&vpRM8F)bJ7wuh8&^G`vN_w`%wc z8vc@ozpCMHYWQ0k{;`Ii(eQo^Ph1ws*H{fVY51KQUZ~;thF!Q!R%-ae8vY{<->%`m z)bKqTeo({lZN0F4e0ML*PipwzHT;~0U(#@0X(aA-8h*2ee?`N;q2Y@)yiCJaX!wH~ zegKW+ApZUaf8W60H}Ur@{?b13KK$K}zi03le_S5}O=9YItR?drDjFT7HTTt3)D~9M zRn=BEm>L}w4b@c~`mVTraIKoUnx?oH8>;WCX>6))U?WO>gPO#g>V}5;hB@CAwv`o4 z6}7fTnXt{FrW`M+7;E3EX>!b~sI9H6SaJWH74>yZ)y+*|!-IQkwl>r}SkY9ysJh8f zU)4C-px z?W|qdP_eq&*4We_%OWCasII88Ra8|qL_|&1jZL;DhrH70tazwS*rcej30bELaa5b? zoslam)-*KMH`u7Y1_;^qpx5e9tdR)!RX5pC57B~^10)dCA$z2@f?AfXw*EfbLk%@( z3tQ_`ZHV&nt)=r!xDr@R08rH@ZOO&636iU=} z_3n#fpZ$|9N1IP=!&PgtqH%5A3ft=H)hnE9BgRze%9>g#1-0pE)#U~0jZoVM+IFbT zXrivHUDN0o^h!*fa>ksvF@$YRO^U&DPsO`c-S_4J`YMeBpt9$^UDs5zvZlIWPKDEXcXdN!%y=-Y zv4S-;l#VFPk9qzzN zq1%0t+F>t5e?8D6>JHT2I_~xPq48&M4RypA{2GrHgI`4Z3%ANaedBI=@Yw2oHTX4k zaA_QvTUlO%$it(_p!No|uBz%4m;}jI5>=Bi^)?_LW*rR`E1F8>7)wco+rS88a#htg z{4WRA^fH&%`d1^UHrG=0cjY{WB4x?v`sU=TiC15S46r z=3tY(3nr>G)Z1bv9x)ebMoTk<>MA^jJgk`o#^|Y2iqPC-kc+madRyh1l`E^|bX@fy zMr}>qkXC3GYEyNpt1#PXsBfx=McWEn{hB6Q{Ysi#p^vv!Rj;gAQ;S(S&p_qK5f1TO zHWFEN3b|YD#4HS53Fb6W)>orHL>1Zw6h%Yz18Xp{RM{{H)>aL?uJJHrL(O1JM9y`@ zC9Sr$+y;rKy*x;I;UXPqJbrOv?0B$d1!cIdx&e=WcPTN69^98W zsr)g)R~o)*Q}stCTl&Oxlts;M~#g#AjUFY zJftB$oK#4-7_*#k8Dq(A%^*)%Azf)8UP(tOHE@z-eQ19|nlkADss(aUCOj_+dr#|u zWow<)b1Ewut3wYWH0zi{qr;pvYigB$ zgO+Bjw|Kac<#pFx3oRovJE&&VlrmiIVJSD8ipJ=LUG+getQFPb!cuKq&CNEj!oo3z zY1idTOqsM}4fztgdL1tFlcEm=4Nr6~cLuwW&D|VRbH|i`&jt8R=$A z@t_ymLLAno|AscV{p-7c&l*M_sLN{37#UO%YyVs$9>AuWY{p^j!`n$o`^ zRR_(Hrm;4zZCufWja+$0MOp`oF(-_@Ib+BF(Cs-7d8Ap(kW-}Tj_T&d?Dt*)tzXvVD0gbU7QqZVeX=8nO}5Ah8Zb@x?k5{e%^5|fu|R0|_XS$(jt z!LAd=9?erY-?j16#M%R%MQp=3i3$nrS=wyjC(+PC(#SG8jE#|HIm(FkDbyBP-WsON zq6}PHsYOK{S-EMR64i#pOJXCfsDbfEmah8rXTyy}Tm9;pfon{9P!rdddgy6~+s8lY zK&+K(^Woa6IF#v;rJ+tFcw6!sa~BXb7)MHZ}{ZJSq;Us`HgG{3BHiKR58tv(p>iK~z0P(fvWLJbfD%?8O_%$++&hH(%cRoEl!r>MvRm1IQsE2A4h$t5{Rthk2Lc` z#)y;e!Q#Zp@<7#ixgDrER7N9^y6hej`~m~5VaaV() zDNgR=M2nO6LAB%Md{E_~@~uuTujwuipXeE+nBwGkuvFq?cc5y#ybjbHDx=oOHo_o& zBc^tCu1-jXjuI!UgGP#z*FkjSWp)tNI8PR;?@>$KkqpBT^|58>8OngC7}c#%>{6O2 zsXs;;BDJAvBaVARdo^67I5je8w0Pw_sIq3psm2?$xkN>cJC)SfieH3rV-HoP+(gv1 z?nspnU8ciDic_X>q7B{7qdkjP1B03#D&Ly5_mSp$=qPdWJZPjiIUYneUVgQz>Iy?7 zv*FuJ?dw9z1##8XyO9ol$)$=x45=E!UDrGq3}Y#F7Cnr*W*Rz-X3V^8811MD&oH{N zvx8yOqleUClr>}0Fq+)V<;XjXYE;KET-8P2KEqg!YJ1Mep~t9$tk@5UTnTL($iu7^ z_$@=!)kbySLUXZEUD>R|?l|^jI7CMQ5Rz?lA?~)wa1zzdr;~nT6MiBzh-Rp%Psauj z{R{?YWrx(N&^*0{54w#!4j(!jCW1V*p?Vhnk;V{FrehO@$|7{XMwf+Z2Zaz>V2Gc^ z>9*C8A4LqAq_!aF;ch6KsCap{VmLjuUNvob@SHTsLvn-Hd?>eat9xjb>d+~iVG@k= zV$&m=XsU3CTt~%f9KPP8_2p5{5n9)-HIFb#_<-jS`Y~PdbUI5u@WkamX<+#Bh;;GO z`PZhJPM0R3p&F;Ion=wBNu__d{8a9($%^D>3#M4K7bW-q|BMp?NH+ z93oQG2tS>F0uXmFiP6SU8=USNx%H8?Z*?4`{%u%)w5l|i4B?7am52(H{?lm+ zKqJ90^*vaWA&Y*n2z2&s#BOO1wq28R+K3}}Q9T?tgm+PMHFSXCQ*$Ga6Mf!ohdl0-H0Q0ZE!N1j`4rX=5j0au%t_E5PZ2u6IN;r}{AT`pY20bek_v{ZH ze40-G40GNk@&ll_ro!$HIKU)-nI!j|)iXsrZibE;#y?dQa}X-ljvRZg*~+zLMcsan z#KHJFe2NkExY~_FABBp|>d-}YjmCy5w_!{TnltT$j}B0Ueu6f#%#XZ;qkJ1dGMWiu z)Ul~Z)cCbIFm5DP+1d~$@vXd;au3H4#}TI`2njKDZ|zSrE$qvMSr zA$}ac=7gf-jUb_++Ea9gGhAOev^I5ye3~Y-4-i?mQJ>x;+Xl8$?Q5y#sjqz!Hrt|V zyceZvL2Z3yMJ-yToMfvDqv54a)znbeVjggZQjK|z7)mv2UO$wgX6`YRnl&ct*cO*6cvF(KzOkm+ zTHUa^rjcGbgXX-rw4@$CD8Pesl_m0Svf^s|{+ur1_ZW*ytxHN5mPe(txU{sIZasSL z!5tJ@IIO8|Tv(_2B2AZ8H{HcA*a^E5E{MgYcj5Ob4Yh0OmooI0G?{4B?X|d6ea}ry zu`e#A7k&&T3_VGPssNfb^4Ftylhpuyndm@iq|9P`p#oe~QHP&2h88~~$t+saL@T=a z4Gk3o6XJ_QxWXEx{3C~$grgRtB55wET!kO556HlvMzEStO)n;3ZS5}0;?l68()_0S z)io>d`_VgQ7F0Ae-~~m9z|wj3tLYbNcn?tNLlsVIQ-elhk$ioGMo{XksYAoMpZ=1G zQ24Goyov}Cw5kQwP4k{ z&`P}i$+i-&C&0===tXDoRHEM!XREJVwZc}t+S#-gZATM z4*ro3-Z&xTsyg1A(0ISCvATApEhON#{6Rz#dLFA<^0k>vP#sf zpm*dv(VIMw1Z9gQ_+cxGI~;6W)2RNuj@HqI?Lm4E2ya%YLHgmG4VBiQec2togfnw4 zbUy`U6t4x^p%DMtF% zC0+j$V?X1~dR3msXk&bg@yCqx4axsMMQaHCiunIe(PCyqgEx*+x#Jt2nMB3;e>O!E zxKHn-yD{`9e4pZ;9PB=wamUxbIxCm7?a8$@m6~5PS2x^V<8;*5Ro6F8ueiOUVfBMw zy?u2By)SoqJ#Iw)^M}i4ucm;H#$ViK0=J!@Z*U5e8croX$e-Tx_>K#%RRKrgI{n2f z)8!wm57Jv*!+&hHviap@;h-e9JgUaWtt!&}`2NEL5QA{Zi}=T8TYdwHd}I7LxE(%RhwrTI48>3}+6Km|#zzlD30;>oELmZ z6Ga^Lhy9Sdh&9-aO&9u}vf-qfm-cNs@gxi+4pOGn#>h&nA5DM6ceN15{E2EK*rpRV zsZPX}X(LVD2D9OT4XWEMY@_q1aqG5m*Y{ha;4$6={lw0~^2%0@J;@Sj*eoUpNxukyVnL)v?WY^LLHD(t5r zkn;zfaN*tXa9>!ZHokX*2=sR%!t^fqa+VJYh)-kBwf=FxzSwVX(!J8*!*{AJc84&h zA9NpBIT6HgY00+(?QJ*aLF?u7ZA(?@4@fFZ<;LB&Dx*D%eC|AHUo=~E4wKR!c<#Je z(YJp$X!tj;^yBu^V(f|c48fyyOutMWa|4T`z> z@a_`d@AZ(N#&TyoYys0P@|9TE5)%^~&8TPLnBZ9CuS$R<;SuEH({qb`Swfdd8k3#P zmY40n4W_h-OyLl>{V`~CnV|FDx%LbF-~8go#lDMQgkJI@-`}ZSBwn_B&!RIWPR~4f zPw+OANtx`J2XB9MPNxXdcyIr8*LUh>pZlyo@XK?Hd@pAfkgvJ~2er0EKA)3PY({&c zn%{O#m~>Q{iQWTh9;yAU%uJMV7WvlYk@gKrvHz0>nHznxxx)Wu3#UymwkmnyKg}^#|@hr*9A0ruO|Vf5aFB0{d>JyG zV*i7U2q&zyJc1S@ZXNBoiBfsyCZw|9+*L=S^2F|9|J}}+vQ;kfof_XP%NlnF?ym%- z+aI{&T(Lj5VIFCqHhw-nR2y&PMAgPKR3H6;jB_Ttj$3|*qEkjqo^@h@{|x8k(=MI& z4nuP44H;SH1^!Q1>RhnE@6qrc4fkqzuZHV#)Uu(t1`Qt_=C0T4mG$jU!iQr2db@5X z>5HeEEyap%Gs*zIzI|5T?!x_W z#J^P2E5@6I`+g-y(IMt{&+3#U)9HQrCc_1nUX|-1Un=EK?n^M;uLyUJl8`wC zsVh=CW1rzds<7L2CX-W`+E9Dc4-3$%WOjNFWbAV|I%-_2 zgvX@JP>krWiu-j+g6V;MGaP!8m~1@gJz#F87>1(>ri}%y2ZafxgTDz%tHZS0V7hK{ z*3lD=TRa9+>g1bGlsl+RjMn}cH$LqPqN7Hfc}#RTl09IxqwsHDtpWeKYIXRxqc&BR ziUB3#dR?c~E2)!(ZU69pP)uXSw{-(??-+sr}zB#{m>9@Ws&{p`m>%ap4 z6`|@9_i6ZP4fkmHA2s|B8va`i->>0&G<=taZ`bhWHGH#%KN;rzfrg$1{)ORisNekM z=ZW7lDAz0F6cbu&Eo$Q4o)Z6UbpIQ3lpu`;$rz2PXHJ z_9b5U547Cu9#5s=MPYg`msa0Pt@xLKw5+(kQO8Bf_i(cGrAMHp*zq0jAiGPQ@s?J9?pHa*G zM`!v28_$^Rh6@%k%DX#=`~QqEjYU0%#*6mX^{;GbPf-$;8q{ikV9goxZm~5H`kBfo z@7|!Hz5M|7YnGf*-cN(ncgZny?6!8jlA>%5mWv4r-Ftc7|LrRAXXmM-;SEZ&<%wW< zsya6*hIjh;@Hz2&mdWsT9=ytjte56JI&bw_i%HIjzIukP&cfB1xcXOlHD_h$s`-pC zCo0vQCH^C`WZ#H6TPE}~Ait+GQ;r-roJrfIQ(DCCmHJKnf$PpVS5iwgW$7?yFv8}R z9E0qYv*2ICGdf2q+6J|m%iRX$&}%oeq6Lq^{HJY6_rDp%tv9p^hjo4i>ajm?x?BI; zm@|o@rQqxDU*B4nVGZ6YO8h0#N43mPbnBk`dBMWEd`mvA-Kl(f4CRrN_7b%(!_H>Q zYnZ7~dSvZ4NKZ_A_h^6MKz9bugckT#So3`QCXoID-}ByecMJ4^&INAt`?^=`t7u-^A86@rWA=ErhuNdu_WRYVYrET+HFaZ5 zkTL4J?aWqnw=t{krnZ#i{a^iCQ1?{xM$DN#tJN5Hcbn=|wzS*Mtfbq`^AehqZfQ^0 zoT7|+dVz1T^!;(nJl9%&7_p zny9317N&12!dr0ZI`?%up_`cs?eF)!prmd!Dc@F(eL;9(CmN@;r=Yx6nKL_z#`%IEom!xwGlm1!Z{obV%cM2?xf+f-u-YIoW`ALn&6)JoX9dcsyq|WTLe4N3$`!m zPB&4@P27^?p4fV?l5{~g#u~iMmDJjPCjBc}oq34;?A zwqj(4XFG-WlU}PrSLurVQc`PMK}P4qR@gzGyXy1}o>AM>*mo(Dl$W5h7_rUa*av$@ zZ86LElW;BX(uA!~&)<72@h2i%Oquvxmhv(D%yEqFTpG+y)y=~Fe|o~!iO=OYwiJAR zY_k}1!D8qSeDyTdW|H^gt8~{Uc|W_Fq@3G5_JY~?A?BjObfaTu|8$vS$zwbL!Yl8Wd+%`QGuNXPQ5!P#HaZe9V^>2r_7?k;{IT>5 zs+*@!?=sq<+mv`FSvNM_gc28CV{an*O#MznWyCc9O+Z}WXSe;7;?h1A-z zFuy?lY*%I_FY)S(iOSW$KhEztllm3oLFl*=cj{UbGRgMhD?bjVrCM%G+A$uzUP9+m z+*Kpe4ot1b(3p7%MIeoNBJx^3cCnzxMo{P@FE@?Y{Kz(UP4#w-U)+p!Z*u13`V z6$9D<^)Ck8d!{S*Ka=pBG3#+<)h5b$De`^875#JHwiqpX%%1xL^_Q*t_I@tDKkEG) z<+{v-mlB@o5Bxh)?d*hC_+%;d#JZ#UZFhU>(QAJ3GM+-si;vdt5VvA3E<+2BevHu^ zcW$QGRKH`XQuk7Spbi$&57s?H&+A(**KMN~lE}T_hnJ~7s82i!zdLt!ZV}!imvbzu z-R1J`TMfStUH&+@_pzx|j!HSTy|92K?Zz}M2F7f|?d7k$f zT+c8bJFV00y&$gBZ%T0MjwU{x=sAWNW#ZFsknAY+r?WkZzqeXPYZ8g#w(rsWb_}h; z++@C~^G5U7PMta9sPTdks}olPe>x{HQ#=vO5M!TBai@Ah^7mBvX`^oJrWCiqb0TOE z>1N}>w=gpXJ0pwecIXy&HbJT*G)2> zb!iSrt|d|$c=udH8{0Rm=aLG z?*)x*!u#V()Hl+2q(j?!_R<)PW2EUqDJ0^GVJFS`h4;xz#xqH}ap^ni9-p6k@J-wm z9~>h;bCgg!Qv5x6dA<*4QZKCdXUmzf;vc1%C0z}ObCe%i&v+`FpRtNdF*^+2zxO98 z5AK`nQ2ejTo_y5HSj))5iii$%k(oT{YTz$t&5jqL`DVXCY1;Q9G+U&mVOL1g#F|IW zqMyx7Kn?2MdALWYbj_D&?vRMGrqMFMM-knH$_>KH**e{sQxG2(2H#)$Rvlf6JawF|We8AXFcTc*3l4xG{1q?M@Kson~c9jWw9E zB<+lGT)_ow$7KCzKH8D4<)AKeTLDq z4ZH)k#M_^fjP(}7l(u#qWsS}xrqC=pQ+nDT_yFr07?&q+KWMMencP^vNmOX{ zMr8K~{?@NECmuCBt_IqA1o{zOwh1-bp@hc#C&O*|7R9ZsR}PEa7ldur`y$I_zWrF9 z*QNhq&|v!YKhh_nbe8Df*JY(EnV#g)?VpP;709cJxO#Mw{(U{Jq zy0=7($KJ{>ut&3B+^ZwDAL0Gt4+woAZq}9SavW5;M-JwR`tCd&IB}V?j_F=P3c9!8 zoomU`yD&cMbnmAtH+ZI4=0X1W81j0lbBR~4`^PboY;w*Un~+H!pON9CPH~+fbFxME z(xMD&8TIVK+v%cpZ(0lZ-PLzIYN|J$)V)Y$Q*_s4iz}<3yK`c(v!L63$G_oUYiU8Z z$@#9+_yE=4uY-xW?+P1|O_M$UH+?L+QMa?TtS}?dvhy?DOPJB>rdW2PJg`@nzA(RI z-aGSj9%7hd1$)98fzN)7QN;Kn)kGp{!XR=SMz?N<*51IIq)w!g5OHK;4GvRjHQ5Snq7nuhs-03uh>w;>+S~j{fR1^BE zR?OS8P$zwTw_41Y6Qrqg0<3A17b0_(Bjkr`+W&n|4uw5A(D#7Ds_c{H7D_S4@t=Rc z8tCfIe3NSY7RsG&yz{Nmx=d=9)FRV7)cTe>A0MNKOnm9{nAUh!(;BU)W#l(cft*z9#5UuTL{A(>e=1jP5Ubk-IsL4qu61-kres`)f^?^h@&nI}D3GW!^A?%t= zC7OyBiPrf380-k;lfANcNAHG50&}bzmCxUwSfFnXjjPF08~E4iJMHk$9>>){V|TINDX-yK#(z(qy6PUfTGg#5sYx9>Xocr$;GS-? zoz|I){d3W7RI9gzt?FM0y}}*6!%0HK$^~KC@oh5?S;NbT#nh zZWq$fze4*D#r{Jyy6cPmAC9N=Xn*Z`JSSZZq;=~ik%zyRYhole^n0Uj!Gl}zNg`-4fTII307C}sHWQdS|!y>0l}M5mVOy3HITX{=3!Lraj-GMQ4bzgLz}q>bg{TAuC>S-<2*p6?fO_f0(WF!D4r?0<4+p07V_ z@wU^nuBkKG%R)OoiZ2N>y|2jiFvT|~pYo;n{$n2HMO`(!8W?k0@p*EVTx@NiJSe_2 z)_OilE7(Qr`ip$`N>A&tLYR_sHBjH@LS2$viIT5@JTzZlNC=nWD@?xjl51N^crgI{E`wuF$_Jz1R)ooIdWj zX?FoyZNAdFK;gYDaZvT+zobsczpwUP4LsjPbBPk))){HCJawE%KGiR4TCVnQ#B8<1y@RE4~MF@_eOS(w&)zfs$^O zF+(MNcUP!PXb*CN;TH18aJ@n+wF`F_`%cQXrMA1gE|bIQDfXAhdN209A+JU6lV1(o z-bMRQV%m2)i+yP*5!h3A%2TffzSLEMy;1UmEZVI(a<}1XU?SvO!}4O^Cvx6j>`$8c z2<2RFw?2$9=0-=c-^lsy3tT#ny)Vqj$C~=^{9o*AUnIAD^G$n4}%fA{%f|HbQ}g>k0Xw?0qZ<(-OqiTb6!fO4MZ zz0`-V$G&2T@1ksF#lGL%DR+SpOE@MwOyzxn87Rk2d37?{ppddJL)H-^yBQuPS0F|B zm!22$Pi&zTGo8bMG*iy&(eLy8c_^#oj}^3oYN~sz85$bZ8M_xvU8C7d$9MT*@%mlvmX0XGZ5=7 z$ZpGp3@urYr!}?Z>^+xC`~F8~$TqS2oE*h`cjr*LYWd;$b7x;*d-#mN6xhxzZ1x4*(%u)?-ji%EX@I?TVSDKZ@g(BWndAx{R%43&Ay5sr+_aY}7!>zs8*Frt4yf2oXqkgm4=Z?}>pub$BUl`W^8uTN52Tm*aatp{- zvG0ZPxmLx{7f!VXEv+e}hvOv_I7s`0Q-4Z}ScChD-a? zvzSB4wzt-j>}azZ(Ml=yyDr$oSsmff-~U(idTpuF64xT?|17nF4#YML5_?xTHrgHg zpsxw}@G}|r1;jlf`^)H*e-w`Un^hkn<>@ggKOtj3j@X+A#IElQd0p(oL$J*I`c!_xtV#V52KH8y|cl;t5uL$wXF)8kSjnXUjJv&IOnKIT_ z5X+xWHY2Hdc89zySfzgrtw}_e?e#L|1jOu!iCMU>$iLhPYg`W^yDxCHC%S)5z}(V+ z)rWPj75QgBAWvAKUr}cS3;o;Vd_g_g5;@n>7x;6}LjR+(wHEn(7342k+C*T&!`QW> zUK?j%+@l@o$^^$9XmfA%(1{hVoXb)_h(1%^l|}wHs$=`XU-lIHJ@RU#AN&udnfWBpBPN#?%g`aw|6h}|3>OHTl5a2GG5Vp zztU%xee<__@LZ9ZX)X4-WPKL-pR!X~Q{SrXjpz4JZyZ_Y-PS(#f_2RG3f7r3%N0{r z2Fm9SoD+D$j&+@6vOs+w^`zz=tZpeq{`ajU)o&&_?Wd+H_u%S{Jy=IH+d65_7JF~E z-O#FgPB>Z$G7jon`vS>5*V9f`8){X0MRBc&b@j{+T0at4NizMEm|RoBs)~~NW|2QR zO1JBb3#kWX>i;H3G_vsVnN+Ma<)0a&i)ZP@eF?3)*-sR_eyqr!W{vCzQ~uN(kca!u zkpC|nwJM?gU~#b6e@ga!)Ys;h(~1!F(9Q+L{^N1vN8-p^!*c4!i~Sp8!mgNbQ%u+q z6J8z@w#I}lQQ=U({#ZL&c;5ALcF`(JkY+z~&lLH0(ddf({3MK!ndlp5o-vwlIZ@>A zngO}4$p4`%^~lU43vrs|{f+Tqf}H1I)EVt4_B|-?yGJZP^vw75ROHBBY!sd!`>_A- z-Cg8=mMw0Gv3RMw$bTDKv`1OYW{XuG?^>OCMrV;fZ<*{F^hN$cwl+I#jl2+!G~}TZ z7LUv8#lBmjEOsU!R|(=7nui_0^KX&=)f{Tm|5&Sz^D{hjnxQZ7N_Wx)qh6;h^cTq) zhVg4O6B6F1`xBKr(LR5lsg};(Jc=DDou`rVJ)H6$oD;!SE}e3j@*_BxC!XmGe7{?t zsh4Lo*Y}x~&=Xbo-b{QP&)GLCbCHsrQ&MM)k(BO5N?v)b*msX6r6T{iMx=@-3XI5G z`T|8VB|2%bsBeSEUgJeyoWeFQ%e+Ty&VtQtZ1aXFn_JoD@y{w(;qEaupgwP4D{9{t zu`<5fr5ry!N+)i!cI)&LI#K`KV^GqCQlH9vE1xD**ERYAK|J3V`KOFm%hVO))#=k{ zrSjT(XjwV#tu@V-${Ka%c;a*du6R?aE?$5fdw?$8<|&xtTd}*}7dQ+HC!_^+?$mMG zfO%R1@0QY>`0@7Zl?>(UDDQJRb?hnfpEQxZNWOjsE03~|Mn2C;ZLY}w@Cqt%wN-6C zO*KnTmCy7}@f7*XbZV|%j8~Uc}=aaEh3d>Vm(!RM zlg5#*BHt0d_TVyYo_{ar6QR3tZ&#?!-n{YYHVS^6DWMq4hKOlzs{XwyBTe>P!r0q(%wz)R5BCR3mi>&5dw zwZ+iMc6CjZ`wl#>bKl`>vun$$H}J2pQlR?jpEgrlR^ON;%L*e>Z=mUXs2x6Z+2kqm zrL*trW31U>Ekj!S;B~Fg)aj_FD{F94!aWD;7KZ## z&HAj--sZs87By}EkFvDhx+h9+QM0{)o29p-aHh_5`4qf;ioHD%V{P>L1b30|;rSvH zaWSWz23;kGQl#FZ$oHz$r}4En@Q-svzPqKJkAf+vjjfl`+hotMGx7P}z$si!l~>g~ zo;)|%p;r=Gdjo$wr{89HPB+6|^H_n)`(syIjkwLQ$r{XTEApwMoSN1gnbt?aRFj(C zo3QxFzrdF-H(MrUe2eP^k)8_|BmrT>i*DpKS4 zT4U^;K0C@SGS{GPaB}dr(VKSe{wQchiO-fL#`VgjPbGfzY?5-zK4>GC%||~9PJ^x| z6}}4N^UZXU#He#ny&gDACqXMakpq!@A$1ICsMfMI)?PY0#*MZplHJdDnMSL1oh_}Y zx`)^cN_op!tVoT|lD!GF&>td3f_qGB^7DpgdvX3BtzbNrnc8~4cNTLYUsc%FDEQQP zHgVf%v||t2@x=E{tEdH24GQ!3FMj`(BH#1|wZ@F7G0I7QpfafQd>qVh(|ZCdc4$A{5$p?@peZIQW+*uBzIM_2*qV(SG_7XHqo>F&=K`d2lHA*3bX@v4#HG6>6EP zzaEH`OMQ?2c`;45b?niF{=213wau(Rn&UV;*0a#B&V0lc^=cWedZk4*T+-vhGe&5v z3Ox(be39n53waF;weEx$;gP*JkOMng*t4qzn*QIn`3`8(vjv`FdIK3f>Uf3yfc{71 zylJ7oeJSN=q5mf_Hj`j8k8QpYW%C1b!B z1(=!k2L1!r)cJ;5!q1t0E zAASWe-}lp0c|H~Q_S@qr5A%HoWd1|<_R=%7UsdFvkR|t8^$CuLEl73$x0R-8Y`a^h=It*NfxMsC8~C5@4#o9)A@)_JZ(=-tS&0+t`1RU+|2Em; znlZ9;cJ~JU(CvC%nCg|9-A2^g@4D%DW%$h6ZmJ6lU^>+pEw>&X`h56_pcZ99>dO0`DE(r)m z(&IQ0O{4F|Zc|Op?!Ta?G4NF#*7!{ohhU}J-sUK`i076;uetl@Se1-Dhq=C8r!4kg zsx&%&<9|s{dn$CovcdIO4W8^L3$M#1Ze8LXojeWunqT(NX~Yb<8r&cFq+f^A5@V0i zi3rz{?1MO$gOd@7$LMqp&MoM24;nIaaDGuYMx4{|8C%i9RN>aK%)7R*xPCWXSW8BpAgQ-)EM{%y|HZkRs z&bKh<+FEet=$}5*Jr9Px>c1U523{J}x9hUzIZ~&>qpa6H7xnN6==w_HvDYq$@53WK zJj&{D=&y|Nr<(Vj$o0(kzn!||;v2L_A?Eu(Q`?!pDyp4*d|KC<=jhsv)yAwR9mW|; zgU02$NvXFZl}WFwsQ@XJ*VR-KkjkW`!PM_evgl?mzj1xR-;VK9C3aknV(j{~E7dz& zOc3YA8)A*{iEl~f!@oZ9IYI$(qi(!d_!*V;H~$ZH?*boXakh=mZZ=_a00II=M7s%r zhR7ixDyANC*lZF75<PyU#8Q+VB1P|NX!HG0fbDnYrhlduHyLXPzgg=fA)e z!+3M*+qFJGYMmPI>daH+J)q}B+YNcY=!{W$qh7eU3-`HUEitqf^i4ln%Z=3*<(%C<+fUHJl*{~>j-Fp1U{q&4JNf9Rmm3v%0f! zFRi6fq;+;eC+UBMCBCRE@kO-gPX}H!SYn5g(*CX^F+D$xx&Mopp7&zz`{H){>MyjOT*(%} z7ua?5+j}s4+{{yDl*=;ywft7==bbrUwEiN;WlA-@keHruz?=&GaC{(cnP52(o%$kd z7k!ty*OiziJimmEir$pi^>^L$*p&5mJ+}U_eN#5P&@QYYH~1(8$_g3%C`;bXLs`kz zOIm*&>k3+2LYT!_{v1t>m($*CcJflUePOyibwJ~yW%V5C#OcjT(jFd3%I%2~@83s&!S**{4~urAob-=SjC;(H&U9NNR7BkcX(hV}mIv_ssaly+$Q<&m3Q zBK_qKaHh9|6Xi%dK{+>Whn=9DyHoeX+CYe6R z&f7xH+qiu!=k2x@k&Ii6SN_y77w@#1PNkVmYx9!K8^k)7`&pOCJ{->~L{GeVSXY|c zl%CWXwXw%_t6iUCt?*DqT4qNN=Ki)EOXpQx@DEdy%GSJuyQ#gcetgjY{#RnU@Z8yA z-c;0Ui5q~oUS?uWGo3N_=6ikzZ^vY^=Je~#@=lp(%`tTjOt0)L>b1s+RNlnx-yu?? zHduP%MO@NEcr>%^qSxuS*k8dL6P`|U+t2KnWj6`eD!1v%bvaKQFk|K|c=LW@YX zjxx7d@f5~1xy9wUWEI~cPB7tJmk;pvXp%oZakk$wc2KSrsayTg=$ZJqMJ{VkoH*YR@c;0=esKIYl1EYEYwANXvd)V-%7*QonHpUCsPSCQrU ziQ@XT;(EKv$hA-2HyrJVsWg))5Z$d9EYiQJ>JXdlH+SOZpV(9}ep!$K$=9?|4Txbueqd zrWIH%_HGuv&I}jd^pE)`-j#E-jL&dP!kqT9w!$G6-@|Ncdl_>Y=f*61vZKsC3Hn+6 zr9)|@7g{0zFnM3sR=*RsSD9~sgdcze^|-;y60{q<5BQ@*2JY}uCzP)~$vz?(&)F(5 zLQ0hF|3-Pt@5ud>wi`I>yXi~zlWDspRlBLo-paspT37`};19mEAU3{f5%>epddo}Q z@D*Egx;rOynKz2jiMYG*dFL24gDux)Fl&M~gKatg3^t!Lm{pACo#wa>F*+GDSJXy3 z=B|bQ82hW(Yk!5g>o&(#@b(`+8Yi^b>t}NIYI&(Y&V0mZYpS^obHJmft2^)}Ok9t} zS|;bO2l}EOf83vl8$D%yOM+Ny&3y>|{UFB`cs_V5?grip&x7ySTWnXj+VahtOl@K# zafy@7_A=M3r2BIAA4qfbQ*)W!>#fi7luyE}B<$Xc7jZ7Ld)GG_XQ}4nd7f_)^|{RM zJyl7rJWrot2Xxjq@3_7{`F-GhgZ^JVxP*5nDqPu)wV2Pw_T8iAv%PXYE5v;Es(3*> zA>I?4W$Hcr|DHGm=yNg3blZ97GtSzf^BHH8e`P+KW1lv{>Nkl)evwg(cQbOJyM=0I z8!l%y*1`4TJI-vj^e%yQJVo9G|DVfe!mMP)h$ z`F7ePQ@2lT?Ku*hZ}BcxG}0+ybZv;?=e_!1VuDwT!JWe4-{lWW9y=gmk`mIH$ zTk6fNG25aZwX{-}=tnstQ+FBg*q;1f#??n@W0bb~KpQ5h4Zz}i1%|gJ*6?kKQNgz* z_SNHUiBZw8$Z&m72bw-C89g5(4m3Hrc!9V$) z8u^z=|HSSc72=;P>DI^e*EpLqI}%-k9b#QPcmI{x`}@LAu@-TV9^Yl}N>y+|9|F(Ngt z4Zi0b+>Y+B;e>L?SabTN9mW2{v5DyyA1uba*Kbo2-bpj_8!i3&_ve4H_9QiWb>psLMI7$dq)l#d--LYm* zbjmybcr(rd@7HAAjUB=zhx>Kms{w*<=2~a?uNK95UmeC<`2$Qnzqfqox9m0b{MJ(KO6U>M<1U?*_t_`U_TavteL&Ye z?KAw7z@2c|)boaUhHJkocldC7NuK4C8FuS%?(!{%EvUnoHzj$i{jg&Al=Cpd#?qz#uM7`*6SuqhUpC6^Uq0gm^TgW z?B6!bk+5k<@+w!^Wu_|=)~(DE>HXWRd(1B3Zp|OCeUc*z`Lg}C415&}Ps~jCrjPZJ z*bTXU+mU~61QtIM3$uQSn&Cl{@AxObmei2tp)IcCbh+q7-bm(nju zND|}jKK=3YMZF2}*gIVgu<#xH!ijSNzVA*bT9d@Ey%TiI&@MkE%Hm-zG*!t&L7IJMaYtShg5>i6hA| z{751wIPpv~IdDpm3{M&TyC%o7|5AJn<3-qx?~|B~=uuN$Z(QvC!wY4P#+ly9U72(D z>66dfz4r=w1{l#Bp6fB0&id+(vR`YC_QxCT{o0Zpqp{1orjK6&$?E;GB>~@`5WUA* zOsQhjfb9*g47c`eht~Xrw$(Xbef;MxJ8lDLC4O|o?rl=(KOp^6m44@lWkbKm$F%ov zi#2IIxUE8@3_;Es_#j{BFt^>aQ2HQtZ*zzbVmlhY(KIQcZB>r&JlZl~d*Wpn+p+O$ zXZTYxV$TSF!iGn3tgwt0zIT~VfH5z0bE;ZRvBw$d6$a=Cfi@hNC|$ck)@0mfw*!=XQrFBW(tsrM!{1 zdw;zv=G2h*y}LicTje<;?x>jLiZZ=hZ0Zq1CM}y+(SJj)YfD9(%j!PmYR&JenCQOU z@ucgF@AZ>^Ig;&O<%;ex#Xi|?PJh0`lz#W=mKQP{g{aFk67Te$S<_zcy4@k%B5hxX zDfxECnBuqaj-F2c@(gHczv}GwsNJ(ddUUaEOYK?m9WRqBZexkd?kQAf(r0}4og8|k ze_M;owL77$tlgB_ziq&Ft=Dqo_+ihT{y5FCT=y6~UleC~>?K-1{2lvuc#NK}XChW7 z4`+OHPue}n732_DeJY24`i0%|YxtSkZrJX5LFM|T!OuM7%SNvMG;rOoxE=}N8h=t> zlkVNQ3g3~yx@Y5h#QPCklT2$9amO>O_nDRguem}#+ZXSmx0cu+ajSJ{Op-(3ZEDQe zmp#xG4I2^G#L<@44maoXRhN|bts~51rgz>mXNIc;K6Z(t#2>Y##BbVZer!P6piZ96 z4d}QXPq`CX=;4p=z)8ZTHdi|Ok-ojJb5s{;1Je>EZ~qQ*UD^SUi#~h^XJS`k+5pKF zj}pn(kEO+1Hhkmz={H(w<|kMq&on+k8?WfF;cOtWi;^X#4euC;uMP}Ivvn4`Zbd8k zyr9FiGA9XKlxIj+v8&jg?ONI1f&M?;kpWl>z+#;>LQ{IPVZKdCsHl|(O>Tb#V z=Jc``Y_1s&=8q2a`W5K)xOrNypG?+zz2-_|uUDSnGn9DOBY1Z8=A=5uZ#+YD*5&l? z%<^0-&!v8BZ#f!gPRJ;9KOjW%8DAmp_uDj z@H_=yYDk1F#K(43TDh)9ACB*_h-jQ@n=-O5O>mgU_CIjYZ>xy!DUF_i)1<4!8QlLD zX-7I^dfpcOyM%2$Zlc?=W;jayKLG7xeAy1)$JuS^c~4~HSxb^I^<O7AaL zIvz%QQdw&PzWq1m#*UBiJt1>fqW$Aslf42@s?6!b4v3zTs2SE-{wuSZ;4?&>G9^U! zyl0()j+GqJQWT}hth8~WFFF=5T9^$D{S6x0Ck8`9tJ@D9>z6mne+Br!6J08`XqTGecl#{R zr2fR||3CT}0sY_|=bpVG`dQ^zgV|@ux7f^}1GE=tup-+L-IFI~Lq}(P@8M1TzjqRj zLCJ~GOcQR8BxS|-+#H<^`xynE-TYF3{FI5dMD%Y9Az?79WVya4-2bXjZRfrEQ6M z96jF=2P&5ooxZCiYMihTY>rKU<)uSClV8P;BQNcdheQN?#`ZJ&5a(H zx+i%l@b&QLg~|2_TB4Quam)XWQnUUcOe?#K#UG9c+dD5t!$bOci_NtUkXFkxE&R5n z=zZEhJW%syh1=vW@19{Q>fYP1vis9TMco;qrTf$R81TLGq6sCfK}nUaec0QYY%Adx ziR5kYj8f*Bk_Y$RJJCG|meSV;;f?j^ek9 z68mUjY^U}ffi^GU?x4fE#QcIK9X1hr*+Fx9(t!fn#muPmsP4Uqh20`{;DI%?i;JSV zTQUe+(v$b>qg_;*y0eOj;p~#pbu;tLeUQ9E_bstxCUn1f5h=Odu~E_8t0yCedt`1b z^5h~<{AD9-{3ghjQ#Q-qQ#Nck?lzguSpJ!OCi*KecH5q2$I7g!-O&k!-KJQ4H{?`5 zaRlEDsl?YVT7D6SwP7c|Ymu@4H$seT88ha9eE+?E?>~|*;ZsX|*J5O$8_=LO}CU9eutLmTp343`*%sRLcw!64HN`9YKn|F9e zC*Qk^Ubs2mJ6x`?`g?entM~BW|M6Rfw$EQG@Z40761UK6K&d-|=^eV^)#FB-XbN~-=o)fwN}V+Ee4!f>aK<$GV1 z=Op>wy@_lS??naQWIE&PI>tAd3OsWIvhdxS0#9)mY!8E{gu!D&VEVgQiyvBr`*8W* z>v&=V-9KSsdv)En1$6%e?%@mDC++!OKfZnh9{EkoGrqNGk#@J4I(8Lj;jNl8zMla5 z+ZilL!v^mR1>ean@NAYRN%`LK(c!vVfc)BfGeOAR1m0H z)6;b!u9IZqaBt%dc{_N2{uPBa6?9pBN# zW%#c48NB~r;Q3=VOJchVJinI~tyz`@UUrt`@*gemyb|DwY**TM{qjA7o5OYUF>)`- zq8vfp{NZT6dRHUgvs~`M!fojFqnZs(4%DmZCM^se5rk*c#)9{c$*;Xn&9ORUFeHH9 zv>Wld-bMuUcHdEvT+m+N(cVjYjXkIj&}M_=-b;kL3fzDuF!%T1Id-!r6wT;Y1g{r{b< z7^_{$E`{X>7?-?%bm2EZSJN}nDqFT+{VqJZ{!?=qN&uuK; z%WCO&Dk0%luFXd+g9|*%ZIomQX4#|`fp*rbx<(hsH^PTZN-*#XpCG#n{-hztaIUNO_RGW!og{L+sv0BQAzgAG1 zgd^NVwE4O$k!{HLyc+f$fIiO(#KyWx)QFO;}R*?w$H@*>#7`I=Wy3ed2x<^bS7f%%J=H? zXB;@KlP&Ls|Sr&Y~w9P?Tq9F}uZzGw6x)}Z%+zW((2KK+vO+bs0K z@*%PhEL?q-W2`5%Bty6Rzs&b;snm7r!a79k?|^+cfX(;bHxC>bbDa6|unq<1zEFQA z->Z$be9y+PHM!6CILe$N$6|1NtostP)kM~wZ@4oa^j-RVtMGRSsPnIt2DSMx-`g7o zf3D!rF%Ys=dvu*+WIrRx;|ef zTCqG(cfPktjsb0j9tA7bxV{gr+XGw`@M<*trNG}5z~_5&47u~YS4&DJzMznTFElK5 zCzl5ATL%3ej0HJs;A@d#YkQxs2VcYgTvCHN{u^)~hT;Bn93IOcYQf=sP>zFeTmP+m z`#s;=noTZ!9m@Bv4uiFKn}hV4FnUE8TpENi2kHIu^REW37}DzSKDmgs<$Deb#ByF7 zg87HD_Fcz^f&Rc^8|RUjq3ii|n?7ITafkOV4?Rb>K;r+0JHFMR$m(6AG*c{93eZCuT>hY5ugB}sd zwiFxIqpMMKYk-%fj5~hkb1C>bfWv#ZSTBQb{bzvtdw8>eD`I2E{cSig-0ASX?*tD# zKkPc=?#Gt^%JB7ea>gBB(>BJ1w;{4vOVsgKUK)e1Q}p5ee}{K-CGyC==<|7dOle~} zHCiuds|~cHJu$TO!L9sGKoq_hFj9;`%DfNXgS74$?il{7mfn8mxLncuA1y7t2{N@B zslmdUdv%BQoM6`hhc~hKC-R%{cjBAy+WY^Ma5ug#z_x7dOz7$->nYB%=6(hpugWov zM7^9R@IBg316rVVo`8&>&(hjVZ9mo%oi^%VG{U~_OsCNlDJ01k?r`;E|jzI}SAM5ko3;JKArZh=bK$5I< z$Pp*f&IBZBkdoAOuY#w*w%&nUI}EwzAy?cL$Q3Wr&IWSbB6F2?-vg6%$BlG%>YJP4Q0Qkn|u5|eE)CorWVIVZ4PhJRiI-$kB1MO@A>0kK;~hO zi&lQW3**0=*!>clhMt=V+)&gLBPIRBUy2nu8E@ZX`~w?&H?hxWmN~b{oKeWh()`CX z-qXiQX)npBOU(*B$5_t;A>4nHxcdTi57^Y_`;)}23gCW-r$(Zs&-dOjhj*0?<*D&e zAK>Wo{Tj4{(V)c?J2YF~1x(o}V9;)kuN}woJx}m0N2^HlL)R1K%(e9xX-^z$ar_Lr zDTDlN$d_u6x)t!jp;|dn=*N7IRn|Lf^D?}(3I7k$GT(CW^$r4`YHbrr&i8bifiXXH z)Pxls-%3L3ToV7e#A{=_966s&1fObGn}Ie1w9WE8w=(thr9NLia51J}O*hICpOm@S z3zX_A`Gr5ql>zPwgIsBn!tadt`BFeDFwlk{iUz1p7ztT~+1LOagzPj{7 zu3-UrS%d!ky=?kl|7*SjhjV=mF1J8^=-6TtS#87_4z)+ z&0U_e*R14>|770L0}jvS^>uQ_FAtgV_wvhjWB=Ic@Xi;y4jtao2I_qn-mL8Ny>Qgw zZ3xgf4xf>w7j!s0|CDc*(PFS-YIz>l?07wL-5{kMx>(vuw0W}58xoj!?K)w90>vPiog*5=3e|CrP7`$B%@&yTsw_dUi*E%i+xo zNaXkJKY7az&FcKV_fI<1oA|V@p(yRmlll0Pkwe|t8f5sA5oOwSaxlIGgmrq=9N5{w zR)?qNN022R8kTDc;5z`v3ENrx4Y@P+i&}*(u@#<(bxzwr_06L}sHOE}8NPXxR^jlB z4oJy1tj2kT!!s-dx9sE)SS{9Z&ojk(yGP<%N)GSh62R~g{65#o3`b(Ct;^v#6yQli zd2>%PUu^(~*(8T~*f)OPw382i!E$a0lw&{1b%~PPcv7T}aru2eI9Vp&OeO9rXlrtI z3fFcyANhS_1AW9^)A}ey_R;u2o$RBeK)Dy8xBR|D^w9zIk#;kVx~Cl8NqTz(YDx^0 zz`A`WF50j%C-$(zqwNKlhxnco7qt#<<9z`>FQ58?!!tGG7CEBx10$Lm|I-Qf+})?I zeIZfajY(PQ@I0G!;||m)5+7%2)Yn@lsIMEch$Rl=QtE3!8^;=3^3n->FBBfJ-}n3p zhi6TIlewQhF+tjn_NM+tVYc(wi6PQ<{sa$1`=%H?M%iNi&A;poahrH6!VK?!&|3P) z@H0F+)EX;BXSWSj_Hz-Ws6Ua#Z)WXuc;W&QU@rGn|{$FT)bC*py7EwENTbTn3s zbPQ`7w9)VT>dRM8Sq2V7z3GjQNC#>Oy#2ckGObA#5 z_1Ezw`{M4?Ghg6MkN%jm@3QHoIlMKpENzVZ>Pv@bo`F_up#2;)yY2hZhBRxDH|^Ev zdg4pnQZ|5ZcA{QBwcHBK?a9*fmm?|5^L?p3>TcDS7fUO;PcTP9m=!gC*G)szGi+YEFhHoNbG`Gbo3rc_lMPH`Df>K)?p2Gpj z*eAJ=?A=A|J>;Wo(9P>19FuTIAn}nbcs2vq`+Zk{e{-|+ma%@|Zz{D+#ObBq_dVeM z5?HtVzKa1j$yLUbw!-elmoLec0PJf9E(_r4k}D=<1%2}K?&w7Nb)DLtZc>M~ql-n% z{oD_*YL6X?ypZQAyp_Uw4jy-SzBbg;fxYamX!5l5#+X;w<8p1t9gE!2$o;&e;BSv& zhxMXLCnEi_O2-*?fffTlG$H$pw!m5+KW=g4dHwaMN6sef`E|$h)M<=1o_>mV|1oC; z^4XAYC3w>1ioM)^5B4p|7>9QrAJjS+T4#FE@rzoAJd)>)ktJV>zEal5FI2=kyw{1d z)E(-y?qU89{8M)NeY1~SbIqM1^-1jb^1Q#V(50fJ1;_1|SDq4adESFR&f@%zFBWhm z!1#wO?=AvH&G=UyCq4$H`mz7d^Cn8#MH`pef0i_4%xX|B8hE{3g#^(|}scdEV!A@-5IYXY;Ub&2Yi}Lxs;U_`AqsRDe;R8GSt;_SK z$lkSTa|g6g)?G-U4CB%;5B)GF~;tJpUO3TbWiLfF{ou*Ag2|*cWDoA=kV0V?;G0lYLAG! z)#2(fS@OJ3$@22Nt1?&F! zOOV?v9yP7Qm)17nZ`HI$J!VNL`+bjqx~<~r3UdCm`!@A= zp)L3utN0sZz^&{~xh&CP>cZdp649=&+S6P+bNrph@Yf{o#WypzxmXhYjfLGG9W$H6 zn1nrBD%MnVxlC!|&;LB)Qu5#4O`ac0dCIzbA-{Gv1G!D6^dWm-{TU{kAK(Aua}E>A zv}Ew+7vC-jKDW&CE|DHkp7-TJ?EiD^KD`@%wF*9c!=hig$iZ6+yuXl$G4Jrqm!n1- z0S?bxIk&H{V=UNjuE6(_{HDDo`)~Y~R()(ZJQ;FE;rQXR28SnA!J+5e@DC&6_48Fe zE6DTim8&XdTsimavg^+Z;Qz~K)yrSX^A-=y;4H%DHCtB5({I}JYxp|-j!L$Q@>CS_ znYG{74(!GN*5O$@4;=74cpCyfp`zek#1<=WN3=vb({`w*ba~!+Vb2ZF|0qB7xdGT8qC|Gg4q2`>^B0w_j7q z*ZS+?sksf6jjj@BeO*J7vmtLum9xIdT~{l@2@MT(4feW)mF`+G)4ia!vc}fnT;OhO zYN#Z;ZGK&YZB(Oeex0Se<-8gv3U6p=u5WTyYh+PW?^H6g!p7!$+45>pSi2Y%S0nd==7rAM zCLp8$HL84_t~J}q>b162YYeukx@xCr$Pf(^MZ+Y~aHVLNEE=v74OfeXYed5o(QqwD zB!fP2l4z_KH4UP^N>tT1HVD_9;}=#|x!ko*Cadb3o0it=$;Re+I#lmk+UTyTtN}y$ z!pbG&t*KjJ#568#tZJ$mU*AwyRjz4I{0Mp+U)Q+U+0e+It*@zKmzN`B`JD}JG_t(W z*;MWfbp3?cbu~~yGCBl1r`Z=w$yYuvTf&IvQ?nj5N|xoGzU zcd@Ik);Xc5$wjU(<57${dTQN*TV%cC7Ya>)aW^#AR#i4Ha2doH4~q#3WxM`*7*|cB zQ@Cx{LDkLZAk`2ta#XdR)*B>?4d(vN)rj))(!8=#QPkXoF0jpqHQTBxYo%?@bK2_a zowYEeG#EQ;Xo49x)s3M=E_5!eYglS9yQ;c{5R00f&CY6tZ4{+0NWlKWfYHXNVJK-M$yrN7uNLEe7^ZidM#(jf@a)>^ z5@%C$Lv0?%Q08so-`(5#)Pl|~udJ`Hfw6MND6Ga9bI*5UYDm7`R_m?_q0ZK9RVSU> zGFYr^R&HfY%{0(9sCCyau#FpMo7c>V&5(Vp4V?{|u?S9t-=!2?-sali#1(m3#DwK+ zsH&-KY!n#kP0oddwe#ylX;w-|-_Hz?QpT2yoj!JUxq%r23nQCzjh45HEJ95`5sNdh z>*X2QjolhanruV2gRU15o(*ypYn>v6Pk7>hTpX*NTw-iZ4V)~w zplS00=5d3mC?>aB%>pbo7g~l@>zv}6YXfUw$V6jPHi^Z&T4dMSnvJWit*)x78B4cK zR!6JH#2=Df$l<9bEt|X1w$R-uhlggGHmajxq2@d^I;*75@h@a4nyJf&N!nQhAHpz4 z8mm)gfuZe2jka&AN?H29B%Z;%|F7i3xG+pB-_pMGRn9{6S>?hSQe){~5?y*?nnNY^ zs(SLgy`T*r8?rTF$_#eBn5L{k_o{51c)<~H4{h+un&HKrr+eqQYb&wt%F!3GV2z@e zszD7HNDb$#mubZf?!}OAnzPAOSKT@>{u0kdCMY(x1XBL&fjStBe2I|FxGEK|QE%=|7EXz<08*i|N@oJW>YZzaOon2lnOb5%R z2my-K&aB*!5slJ(qPhk&%9z!ummxQY`X=#$b&Eh*;#=wxXh?a{%<{7HDP-oHnWcHt z$_uAEiptB2it|dcOW8pRT^`6@SX@vvJr70w9~KnMuTnD$r_Gw0U0PJ~KPxa;3|gt% z!+Ff=TiR+UAd>cjzW8``_E5V(!|7)c0XTA}1Pg$swbP404$ebxqG}f?2Vc}RmaAo7 zR5rlLZ-Q?HPfawrG2WXh7uJ(Q&Mf-)3o+ARUBZz3Hn~J_6D9Q8BdNmRtZ+dU;c(=7 z!nq=qTq-^H@iTcs)oI4tOLYn2KCrkWMj$E`20PHK#(IHl9Zlil<(iNO3F% z^UwjPLQmFLHn~JSP7iD0A%yUT;*dq1tVI&2*sz5SNndDkm!n}J+FGMagY!)#115g$ zyi1+*>9Pgzk)$x}*o842S>HyEMtO8lPA6Sp6QQ5iXb8>SE|-33qX_tauqz%3>k@0c z3$-%_4TvD4=F@A=QpAoEWmB#$zY<>x(ii*elH7vA(!AW#StWVp<%-Ja>d6;k0yVp2 z+H8EFC~`rHSe$!4hGLS6bI)IkMr1kHoUaUx7}A1sR-F)Pc?hKQ>UzvJ&g~&Upoc@S zu%)It_+8~y<;w5-KX-(`^RchpiStq%uT{C>+v*aGn>0RS{FM_L8>)gRx}#}nO0Y5G zZV0-W0I>aXSJ5ejcUM}JGs{t)TRsh5UUq(7xjk=YZb@M=ya9L|`GvW(!0>iz9%*E} z(HlLNLvun!8pZin1(AD28)g%E=+RWUV8Wg51}7(6TV<22ZvK3nVA@7C3mjz214((H zv$(PW2kxN^O?5c6aN+5Ut#UqCZC6j8=Weo{udv`*&Vq)z=6V|{zHo6@PC9>ap>4ns zH`XaW<*A#`bImk$A~YtF(nFniL$BZ|0yitLzFK-N*oE<#kEUa7|8X8o&&(^CU6>o8 z%k>gD-=I{Kx47;{PHb*ox-crpZSW$;ruaBH%4*zMUv_tJC3dLKr+-Jvx%- zqBEOdqzm-J%DP6K7?wxwZ?%)u@EnSyS03W0$||szcIp)b9aqC@6nuIRsfNgm2COb} zg!V+>()_gPnwu6-SqK)MwemY7oehUj*ub;1J-7FI9gnXnE`HM z=?v9n5omhG>2z=+v@L|L1DY2bC^i(GF>%tBldrn^nkm;-&a0|+&X;Z@ccCmE%W3e* zQn9{Er7NTN9mYMMvD7t4|3-QZIdDg_l{Xrwls>AClw33Erv}JDluVtkc|jsh;R|_c zH)~d*U6kq?55Y=ijn2dVP!kOj11`?_DwWP;fU88FgGJUoPsqf~!u*0+#f%E4mol0$ zt5C*;Q>PLvEyXFHuZ2Qd8n*~&(RltN}YN@?&w!|S>G~1ykXHB>3$&$R8da`hOfOqDs z9EnBm=p`V@5~yXZ2AP_cjWdrD4V#^t%QU(}V_*V8XHT6jaY*XqqMVzUWG|QJl-yig zswDyE=82IRkqk%ffC57yuocoq*7>Yb4LnI!X^wM```xkF$W2iN?@8*dbaa5ecj z*A`qKGHpLBEwUHg7H8aUz>T@txir&e3lIw`8>;VwK(=Yzgpq>ZsH`~s#7to%()Pnq zStyS>3!)Xfv0@e>%TUWrkeLPNq9PloQXMU8lRB{}{edznOVt)9xF}92#W9XC0~8cm zIx^s2+Ni8CaYEZ$85kyJOpXg~zV+H!abU-7qg;)6=$A#mF=NT7MN5?G;LvDC3KXlR zI?i}{W1Cs;tisd$D&y&XW}HY*Pq&pS|CUb)vAWu-oAC^@4$l*{8y|SgqV62ozCSAK z`(yFshcjiSgyO{1%El&JP`VtfvwSM!MB#C9LKBGM*yLJ-yGKT32%tTlL@Vxe!(T)D zRA!w2G_kl*SEJczaK^-|7cH>`!6}5?_39kNCKr}Q@bhH-YWe>){C}KshlTq}808Xe zS6W|pr?UZfd~D(hF>2zJktk}MO(jNUX?%+NVxA_B*_$u-rZ#$9=J zBL$^ovWIL}*hVcvk8pbtJX+TMt5B@&KM)&SV)f%=c?NO5StUqR^MYE~0}viA<6chq zG*j20;nn$tGHx9k;EqhjosPOHA@>Lk+LI8?|GovCsNF})LP zwWrIOqQ0)axdt7aC$GD}b+4_1$CIh=FmV$FIn;q(fp%BTQP-fI7^w$-qS1w6S*@ty z+!RE=g#uCFq%CREGvW!;g2pC14+$57jfc=iDKHu!wy1VXcMlJmSa8w2J8*v6h)X+| zuNpH2HjNsJ?c)?CW|caoXwHkk6M3FpX-|XH?GWA5m2QeUo8vthdFZKK1X7Ndvk}f6 zyHEa~vosIqlIm=kl4zr(PLl`i1ZO5TuRQp1R!^;~UBICElDs6CBX7sZeq(M8MC+XM zE1PSYrd8I$J8RGmJGEI#u0XtRmA3@93LCSVME;?afpD)!{BUUDy$5<$Rzh! zvM<;LwM~sh^BpKHvrb-TY}9+CzPVw6^V|Z%3v0lhX|b~q*K3p@a?h73zWqW~ps$VM zVp~^*Cu>W?tI-TgQvlS#3oz%j^#Upo=V?tOF=;wdB;8hFP38K{vB(#*BHhRm+P*~- z*{-(>W1WFizJ_@vp5o#irvu-Fwn?55D zv?VtJL9VJ1@PU=zNR!*MM(uWlaX%^Xa_h+Tf*od5gQUVX)O?r8;bv&M9DSN|%en}L zRBpU($D=0w`W!4KU`NcS7DHJEXUEDKnQT&5%L3kx#FlGa)*y`c^Ki2_SbWIPWcm8> zzn0%nL%Cjsemoz%|0;RsErk^c?c@oQ3RVGZva7X0sfNsAKGDi-;&oDeFj9-4;-(f1 z9>z`?g=%h!ZoPVT>FLwe$qe`})Kd_WUQ1>~wmrt)?EuTW(|yoO9w7o$rg_BpJHL(j0oMiDUM2aC!!IPQdHd zT+BdWk`{n8qCUW-l})e9t*c+E+ZsF(dG98Z&nIqiIcx3kEHZ;nFU>_KI6y2D& zs2STpfwO5mgOXRhXYom$1IH4)iyYX=34LdbEBA6w5^zVAw<-mK_y&(my11p7Z1q-g z-jl@#mMeOecTTNqY+RaKSG!nyyrMTmbFRaeD?@pjRs&^;a_%{`y5LAiA6h4ZW>_UP z2t4YIqJUE5QU|9%zDlHeR^OnP2m4C5g8(UV4iSS-gU2CQ5iAJt2+;^8gaIl>6!J40 zAg0O7p#P%ugo;@%ajZ+vOC0kMZ_LZO`3o;o&u1)K&libTmrLi@d095==8v(F&&WeL z^g6@KXPJ6FBtzsUjd}Gp>t!1GnTNVC$`(#H>WArmDvz$8aDMim@kf63)JI!4^)r#s ztJ6Ag@U`1P`uHvqT3r%gywTduQmi9oC40Jm8j+CT#DiP(z(QdCvh%G zQ^C1-HGa+|1cxx6OSw>ApVHV^-dLY<`(l}Zzm-zR)cl&t1&t~0 z6=Ja}C~Jy);v^Qcc+zB9vX01_lp(4#5m2!rL7G4k^-JuJL)z?Y2%JS+KlAp*x8Jy8 z3jXS=;c1*eWz2`>p*^bTtSMr#UYDZE<|^J9Q^f5*E)S{4F1N)E*ApM%64r_ps52x( z&?ZyDNd~Q-Lklz6bLa-UIENOrC6e@)kHTvYF~$_x&9_>_@S#qaom%L@$!*KS% zjqeqOR81IJy-lR8DC+zyn#Rvh zH}ux|nGEKzVufz3MsqxGLxXzzcXXHAo$g+gzCxP_)W|rmD5|A(4gVfVX3Y4Gau}5P z?~o+@+ZC{)u<$#|8fMb}7J2{0{^JtyZ;_)gJ-yKV9rdD(U*o0fe}_ol&7%GtHJ{gp zg5&x8vqhNETxgd2e|aAKZnI8civD+W6D;=I))=FWuK14Dt*sgV9)SZktvOe2`aEtd zDe$ebCP{}!qA;1e;g3z4ECAB!b4z~#2i;D14u-sR29bA$#x(;p=?-R1nL%IbO658M zn8D(vWZ|N9m>U#W7dW%9a8&>uF?fNhGU=SnAbh2C^0KBBN;=pJCrz%JG({9@A`}MQ zIrxz};3j*pY%MQf)nZM)kh%&dUR{WW8Gi@=D#V8f{3Uc@--)3APAmL~gZL|(2sH@n z5ne<193kOjAtoctMOcln1K~r2uMtuY2{9F6>{~*-^|lZX?!%v7~H;A0fnPg})8yX$s$rv~?Tg2hRtWYx( z`C5ScK^Sf|a7AG_uu6WtFWJr()y|`a_OhM*5E8e;wouQk?+LL2ZF>jlMK&Qafj@xs z(qtj(RU5uU`cB|!H_0P~=uqj2NN+}7;wzD6ztDDFZ=-^+AKPc_ht=SX^rL({d->{F zAr3)~M^LUV)5_68#6Bm)QQ%j6Ux?vIv#++M;V)mP^lqf5sB|aNg-BDrV@Q{(bRW_c zDjhop?NsSYkY29RW03xdN?(ig0m#F03X%Q~g`a~o+Z2ni27U54@E*vc_uYfQJp)|o z?jWuexEF!D(STbI+#cY5Xu!1r*8$v*4Y`1R~hiS(}&Pczc{6#ibMX@AiOJJ7%Tf$u^&X28_> zPT)QR&N!d!0`5c@?iJvCVR>HzE_$aBpBl7!_*XFNBp#yz>=zkmLHONf; zEkt^>O0PhgWt$Q9qFv#0>|4Nv&#~+~^1q8VbRooULVFSTWBr_`_@kU@?`r7^6}KV| zZ_n>8(4H7%FN3`vhpr!jta^LK+%CkmXajBIexx0$JsXfNL7H>lcBF4r_}xgeos@4M z+Efj^(H`Cht^qjXT+spC-M|^wp$~yu6UIk%Y~Mg9L(8>#Sk^(v{0#6hZ)p8S zdHG{Z`fHHw4+i=4{$ssK?~@Z_RmEk9!{s}B0r}Y8$>={T>K+G~^!|Gq=~p2W?eP@S zzf*FXDuVWS7;=9Eym5?n0ry#0TXcKuK^y!CNth}BM41wTa=xP4(W_$H2j{kD zC}fKCO8}B}J&E=VMSCu(2)1WsCB{y>)}F7BKJ={C57F~5Z>#iWNRLxIS0Q~3(zMe& zr0ohn3u)R1?Y{R@%+tVM@rKrKY|H1s-3FYoFOLFuM;PunaLvFOb$1%L7T}Eb><8}Q zFg~`C^-qTmZ$WqfVH3hmgjW$hK=>2FX#~^bLR^fHjKCjdOqD6<2zp!t{qonV;`-m8 ztM5q{&^P7y1N2RQ%sCHh1=59YV9X<}+tMXfm{-vT+R{Tv|F=p%jd^@IO@B2O<3~(wqyDk=EO8MVrFgo&enbu(l@x zcMv$EozX7X?y-+(?S6f&5I_GftlNm+M$8}Et?&S^IK17?3$&Yc-jDgs zjdov)cIbT`Jzt2wK|c2RQl!5`dKGx?N7|?G8<38AQRCT;^iZV9vm5DANOSDHgS0;0 zhM>)3fj5qsB;c+E?tbJmtM-im&K|~R18znbE)}@yu)Jx&H3MfHgZg;;BXsyFf@uq$ zw<27Qkd9D@@I!#(*V=8yWmM5bVWv@@mWicSsLg_!deb$;thLi`l%_!w>0 zaXWzHUV=aJ==Kul+%W??_UCcOO^Qv($p>KW6|!%3S$l>`hTVeT+V&A3>VqDRwz@0lDSaLRueNlhK}U!rC|mxc<9B$I*4b zT@;3+Jo@-L{xj5v@aaQB417w6BZx0U%pc`Tk}23J+Tl{|+@j*6h{N0a-UZqlgR%x; ze7%kKUW>Nq@4cMk) z@e%fu;C&n6U4;Dzrx19a!gCv*rM!Uf0m5Gqx)J;coMYn=1|wX7FcD!cLIVQN2p&S< zet!o7&qIzP@ciQ{1fF;BynyFRJP&ei!x1fD;zA94_GMd10u zg9toZ;2w~BKkoCmzvI4+`!nw2xPRlmjr(!>AM`t7Vao#%h9O*wa5Dn;TE9i0opbNV z{^y>7^DKSIJ_PO&#~{#mqyPON0{tGY#ax5A7I}7}?XVG^BXFNa|A*s+3Cd`HFS1?EjN<%?#u-{yu-x2x;ji;L5`I zvfe(Iuk0-$TEh4a{_b49F67-7&iBr_eB1WHX2bZly?ZX70L~xAx9`1k`8vR7Q~K1Y zuJ`p+Fz!X(f-t@*9|Y-osug+b!}zxSUZ)1*3gm4IkH5ea6 z-lJiBN&ACzJ=KA{iHCGZi;qX(YXWXc7@t+2?+n?q4q$wR@ugut4F6@Qy!-lLyxbLB zZwOxlc(}?yxCHPh1H1%qx&i(f;Bf}{&XvLa0rPJLe35~^6Yu~7ya%wcJo7!;`bHM! zw}inlfF~RB{{V2F0bU9C8Uwr=u>NCtuLAy_fxh?NPyEPOZ4LC_7OZts~ zFEPM#01q+1wPEl*VQ^a*{Byv@@_q-{WGL^QF#f*)Hp+Vz@K6K)peIA+84dU{1KkeT z$bT!~6a#$);A{iD2e5Jc9|CMP&_4leY`+I^f`R@GU}OJ}`B~_Ap8~kQfq(toq5b0k z-B@2C;DHAIIe?=LaCsQ)1RQUmF9K{az>5JJ?PUdEn1TG!9_|HfGQd9xgYO5NY@k0E z27dtfa?qPF53feJJ`?^R!b1r1XVaocIM&OyF@E@QO%IPEjsmu z%MmX|%z61P#QIO~xBGx&ntJ&Um8OksRB7txX_Y45PL-yfex=gX)o)drdg@eZ>gq7k zjQKmF(yXgjrCHZmm1bQr*u#^bbq!K!)-^(N1R@!PG_F;rI}BcMd#7uNOG_K3>p5#U*Cym)9zajn?iU7 z;ZTd_XHf3?FCjb59@qyNh{Jz-9>iV-fxi(CVLe75FVl=k=kJA~xq}*kdjjD(1lGZR z+6#FZA4E+4&Aael*zaRi7U}&Fs4uqtDDvzu@bgT8W${PZ`5U4|A^-EtAxdF%K7E}r zGEy%m!Q1~~jBfCFf`5GV`n;2`8+Z2h~I|NRKEFz@f-8+?Y)i@>}bn>$Zv zvE`(eH!DK_{snbWE(W9S(@@`GLtQ7qr|W(Ic=;QmMIrz5Iiaq5md78*MK#LnGL+Y^ zRcjaNI)9`-8U#Ll{Ok3mGE>-(eK{Pw*CN;vsDm`{Fz*)B{WI?McFSPm+5r_YtxI3s1-F73%!aDY& zY(GNOBf&b@j*KHh*i;><5$fO>_;+dVMDXeD)n!Qx7Z@;Qxdyxy2(<`oFZ-N%*F%=c zN|x&)$g&aqx-9y99;v*wXuTESNTb9?D4_B!f2_VHyH;kO3Cv{Te!`Q95Xe zpo8yfYjr4B*MZ(PT_&S6v;*ou?>Fj+{TprvlzB1AT#xXCl9{@a`s>EC2qp98|AEYW zhV)%z<})U}-!9a@l=(gIeu3~M0`;uRybCh#P%^&~LFTx1T7R?8b^S%MwTr>0%dC&L zNP12IA7wU{7s)0kfKRtIz1~RUEf;*W7yk5mBiSyW59xGWew)k{`X6;Q7re_6)*$HP zl6hS{*#9bBbw$wCv*2gDjBV5FGS;#ae7df59Wf3cAFqN>?_XW^NV<9#eAE?xI$tDR zv2CBS?)B(P1llpqs{C)_&@Hrf*+K7znJ(T`$pgTVIj9A+#6 z^^%Wx8UovX2jY4Jw(lXtPa?1lOfzN~%uhb@=;t{sgZWuEf2@~v^0xucf%q%kuf@jy zsSo}N5WX^rg;{t!T>26G=Z{3Y9k}c8sFg5&TM17=AYUr@ih)l7UpjC$@DavuD`E8A z?xsJX+&_tS)-eop!uV|^Yz4jkq!1M+MLTT^ZQ4#4zpaGdMPNO9!B>WITR#--Zvpoz z_z2^-mGBmXli+)R^?izGhJO?74+FP~^#R6jE8%4bUhuiV$M$@R@@j#r2A|$O!WE!* zLC$)}T?6=5&vg}ZxXxLA3{4n7VS;I)tJO~!sH`djnL057S2T+i?|dq z+jAY_Lx?9M|6ItC1Ua&R%Rqj@%uhHC^uur94%u%+`*^^qpc5vYunqJj)bAff`&hsu zKqpK(;Y0-16N5Mk^&dsMRwLaiM0+oopqlN3$uD7~&ER_<<+2|(e<|8KfZK<1^?o7z z3IfYp55A^dLj3VX(Y_J5R`3yKd4%6GYwdm2EOzgLUOdp}>%hHi7TXDvkMIuAtI_V~ z_lx!y0Y3veVbTddiNJQWBBuV9fZqlB7vR4OxF+xuCO_eN&?(31cG12Za1H3XT!iN# zSRj7^`96jHc8T`sz~zt+Fv}r)0|M)t0=}mpZ}maZejRX=!AF>UghzwE9D1LMaWEFJ z4Rpe!6CMG21>~D@K(wa<9tJvL(g_a+{pcI8_r0QhIN$-G6DFN728Qg9`RI=rz-J8o zL3lJK5Xw0uO6;ydxy8^+5^#x8Vmo1$L)fbLPNO{9(O>q6_A|(T0`(ClAK{~*(~g$I zF8zQ%2VJ)-!d;-#PUoW@58y+f>vl`{EmhAh$isZEqTR0o_dMj$%OU(M=*;&_C;A=m zQ=seR5Z(g%=Wjr7kaHX0ji3{zoP-}kV7u3VuL z&<oiOQyKLouM@Gj5~0`35vFzJN%f&L)i*Fb+C@LQl0CY|tWpx+PpZP51ueg$;G zq!Zo=`h9?Z5Be^^J3uE)I^m~4zX$N2K;H&<3+RMNC)@^l3*au$Hv?V|I$_cYKM48? zz{fyu1-u4y!lV;!0eu-@59q4_F9)44>4cj=zZ39T(3b$N2c0nKgk7LF0k$lK-vGE8 zbi$+)E&#n2@a3RS1#AbMFzJLnaELyH9h^W+yZIb3=J$nNh&ewURB?wD?ZmvY?S0Yy zA#m?o(M~nb5ZlEK)@Vy57UEs?EE(3go$w%0x_=bV69r#DUHv+i9 z;3G^v!U>8m27J!|-v+)o;LPA7Og_S2W2~`XJc#2EA3}T#{qrH>KOue>G5ygu6ub*D z`JY8h{E(A zJ_+z#(Dwu03p!!^wi12~^g)2H2fYLEF3<_%x0Uct1lIEm_?`g%IQX6i?n&?w#&0X( zCsaMFK;Hp)qoE$ckEwd9Kz|nS!-jeYuR`GX!Ka?3zUG5}J@k?K9>ybZ72qe#`Up=| ze0K1)0Y4Rd1;Ay2k1+WNrz4z3xmLyRjn&Fs5B{&A%ih>vxrC1+Q15?1Ogrd6%>H{z z#jhcME6RT#`S$|%GV&8Ugxe5kAL|wW!zx~dd~3k> zHS(=_($g;usa5R^@dl_ze~BQt?wNUa#W&R9rhqE5B65St`y@@dy>sSbhgJN#ig&7bql)Jt zjz)jpq~f%}V)yu!m=9`kjsRTBV6mMr^-K6`l+XFubCK8`4Sc_a*cSqK;v%t~F!=~~ zgU)&X>Kgd>fWH7;Uk3<(3i=nI9|zqF_z>uX@!LxHWdsxKaZLo~ko zRJ=sR)rb#6&f+0r_opa#4eFT<+|(grJ7N5`63$WlnTmh3;(rnRV}VOC@Dol_{3ld= zSjE3ram7$=-*(Ck`~2Try$@KH>viV)#&SHwGL|vaGKLUhh#`g;VhH(SsO4B5oJn4rsV6I4Oks_jxW2&w%KN4Q#pQ(`5|uzD?e!a@|Ti)z_yGp z$LMi%=Ub!K<3Hqe;f-iu7qK00*1H1R^A5{FKpuZ`p?*}yl=3` zT09{y*74%=Xw*+Tb^552Nu3U2ZS*6SPJGI`CUm{T^UyUDYeXlOPJGn58gw0ua|`1> zOspE6SUPbP`5uldrcN!qkGxoU@l~3})q}2|_!@Lqh`owVES-4Xx*2qX#P7Tw4Ll|` zjZQ3`c+$EtbQqvN*yjXR_Yk6Z?<61^HL>+!!bzlv# z=jg=JiT7Jqp2h0};&tdMh?QmW`oKDIiFHNjG>#+ab`aZ+E{sc@Z(SBTt>Y2Kx0zTb zy0EU|4DwpX7V6}{>&b_86(6N({I%#t(CtHagxEoJVvS#%mYuvWugQ*Hf9({n>uY&E zLhR++qSwUIiC?@edOZiuAfF09CofiB{EYmQf5z+LSGd06C*;M-iy!kPGu{8E(fuCB z{SkF$iA|vsODCRmKRd`jf+tq>BOZ4@Yst^RqpSK6_tXB8ey*VFr{9;nPji*nWprZo zBkrPUeLB#U5WkG>Jh66kV(G-KDDVvGU?{9nXEFhW8t@;4~fY`-(V~ zrsw%7*u?e1e$(^%M&_w67A~>h!t;FbqW9$!blUgV(LE*h7+tsy#8cMYMK_A>DY|>a z#?Xm1KJln^H_t;!EUfsC$imuE5>o z#X4TxMPBchmtKwr;&3Njm}3V*O}JuAnzk*6U)e6 zSx@l}>k6#fjjo7T{wkfg)A`wnXrK%3Sd|yI)70N-+ZNjswnuFbVE-rk={8pH58iTo z$n|eh|0c1)RrSRcZ%ZsxXxeva=ybkkxL>9dOGOvXzxW07(fMxD zzJwQ-;*ZIEi-Duzee3yJU z|KbBVysjm``4;c{=kU0Z!|PgD^Azu+X&!rQci9$m+$i&EVc(Vzi*cNoe;36CT z&!6(V4bCSo=HEqe9!+&~&`lBVN0&=%6FM>fE{ZqOM$oOpN}qy0<8N6<)-#n@gkH?Q zi{eGrI|W`#{srszl=T)XFMdLPlKKzn?-~43(z--5p}L(1rVx_@;G(=q9M2!TB2^b`_mi{fRHp^to&Yx=P|V7}t4X?dZhPiQCA} zvQNs{C!KIBd9m{1W}3#;h^~s`rm53JtO1=^I&m#|?b8kH+avHn@?qbK8{UzuQ~M5H z&vM);UY8yrcJLj%o^>5@75Pfmb&{P5o^`S4jO0?8|Io8R*2)iPLE6 zZw)%F%U6T2)u%)g7` zOXQcSx0iZX;8)3u`FBx#fu=eg=w>;toI0Jv+R=&mcTs$rCS4P{8R7@gog~(XPRzfH z;#t;8`}ZE}xkx;dc}@|#%esrD6W<}P{ko2Ix)0wbANH^K7ES$KN2lv;WF#6GCUy;7 zxcAc?h_tC&PVr}Tcc^0>l*ZpphI_+=^`EdUe zr@S*cuIHP%uiWRpLY)_z+0UD~ufUq0_z6wppSGQ_9pkuEbTN(_Cw7bD#5ztqXx(Mk z-;eGJu}iD!i!WH$ZhI1aAM@IR{uHqj=*6llK5pF+bP@fXMR%0gVRT~Y#D}b_M7PZG zadcJ0_M#I@C*DJT=@#RPM+5ud-Q>l}i%V#l|2A}S&T}36rI1)2IKeY75>k#oW=e38rUG$R*Kj*xP zRY#or&g6No;`8o2&T-B=SI)cmfX{m+x>+9A+Ww4nAy$D-tUBT{@_JlrzsBUMN@zG zY{$^&GtcUN_B*j#=*7~DFO%=#_~+ES0$(C8R$lxnP4&8L+tBB6{CVn~CDw{wEWNma ze4OL+{({#xa6Ngk^5Qz0>K(G(hd!6%$Ea6DY%hAT^x_!#ZjP^}UNKxqUaY*hfTns| zZL`tmaQqVWa*1W47fUaGnakrE$DgC#8u&#nk87?cex94Gw_y7ieHO=W8;Ay;5Su|S zRz2|u`A&`>r`{+$OkS+Kc!;L)U9-K6K9l3?sMkj>#!sU3gw3 z-e%oqbm`QeMVCh`3!PZ?#A~cuWWT2oPiMa`vA-AC?_%l1v*g{Q;wti5hZe4zTDX#YSQqggnm+&9h1K~eLLYsX z`^mpyT*S7c7pq_KX7W0p2YKGV1)WP%ICh~UDg$$i*em8p(`e~9i3R~E6%rWGrB_J z>loJ-V%g}#(uwthM_S+KoG-#(|54a525d5zW3<=Cb2>EV$~J* zS$7Ft0d-GP_cE~y=)}^A&(r<~^?Dem?uWCye(7aQamE?$kK$qSIzN--N8lmy;e3fF z$!mX&lb?bo$cOzVo+q#M?jpYc&yf%7FJ5;4?d0`?(@U%R7pJ`^xexSyYdW0zo|Szd z&LXezG?LGOGs%bXiSx;8AL{+#0yvL+*q7pB^4ibUYl;RFJQL%gBf8 zL|jc?^DQM`16Pp`^B1SSH+g-(+?qUpi(8YgE33FKED>ASy7IbGJV(BV`|u$7dH4}| zvCfA@5FAS3!jgOZ;|h2-`u&v`}FWl z@?zByU#IE5)Q3*@p*>x^e@CnrUAQlaU!|$Pc64QogWqpEaE@3TIYonA*z zFuxXJ$I*q?)#9V%JIQBre?A7+kr%6uxR$);J4rwFa1Hq|e{nTU<0(g{`4(}V>?2l& zF3ewCN?!9VqfP}}LO#r2TuffC&r3MI5-uViUayPu$ZOpEPN0E&IG22whd7ITEBPAo zIq*jEV$DOmfxO1Oh4ZrsUQa&EL%fcrdAwwQY22-hBZb%t_E(sP_&Irv`#kel13x1l z<{_SE4=zw=7Ttpuct6h%h&@Cn=HEr}eexRramM=qzDGXHM|_8TBY8a!xC@Vx7wdTO z2ziaaiTRAd!{oz!#6vXArw^URf1mkWBi4&9%tzcqUgMu*KK*cd>vp2keyLy{rNm~E>_0IPa;(Q7^<)SD!B2Yr~I_%?aXXCw9Q!MDhV`H8QQ*Z#V| z^Q`OeRq|oKi7%5MCBKpT#1%MBUaawmyUA;RbyL3w?jj%do4Avvakin;{^Ivv51b{| ziZ1LoaSM6vuQckk!_DNweiI*~={{J8RlS4gHBNqy^1vZt2hfLci}#TqB+u{W8rTn) zlNW2;;xh6Y=K}pzz@_BFxWy$j^;3vW-9fAXT^P4GpS;FdL!B6$M?Q>O9D84~ zPQm+<`#OvLQAjNReJlG~oM&Asx+=z1iY}d43OcdIE!HnN(zq7cM}3UzD*e7-Uo5cS z#L|i9y&o%kd3=NCR_#aeBbwH83afe(=(T<)c%E{f*f{#Ip5jsRljKub?=ko$d9nHv z-ypB`JIT0j!Pm)$^%P&Dsh`W}w0_f!yN}obKC}6tQu1V$~7fw(bTxU8j@uH$v<>x^Uf!uaVbv zx|KRN;j84sbt{h3bpFm`)$dvKI$y)w&pU~oK_AYa_!RkB@&`D-r{QMuVvS4OL|*4> zg#KFKM)KkOi5qC@rxu;gS2pMG2(cP;;rxlK$?JR-P^S*wPd=PK@#*&`>zsUl^8DT9 z^TbocPP~8R{E3fSSC6iiaXm$MjM!mxVvSpT$hsYm~p8qlT z1GtQQIDg_&n&ua?%}1~Eafo``iRGaW=SQ4NUgu+oaTUNh?HayZt-#Qi<%eXJ^?q77i-+&dh#0QIOA@F>&S<3i)(4>rwX0MsmJw$#46E+af>U+ zYn;W@sfNqRhjELC3X*mD3zGZ2oBcOPtgm2Yzl(dV>p)khaiQxXb_ShT;})N`t`Xf3 zN*rplFwy7m$8qg;Jf6-T1W96Ux!E8 zm-pb?*%yEw4a8FT|*c4iTEmc?Td$u`v%-gKI{{5%?FZosy>k1CySi7 zYGRciSlK7y3hPSI)w3_Ms8>#`1f5vp78hGrfNq#^onrh&#PZRJr4#3o*S^T4zd|^d zeAp-AY?{_H1DiNM8XwouPVWC3h^1QDTf^QZOO#kgPU ze31|9DSk>*KabF9{o;&!j@UzVVLinU$ZP$sG499kB>AwO;?55y$9?95jFnbj{t{7c2@glwtv5Qy?omle}=UbPHPS;aC zSBl(Bu z&k;L=UM#)%v~?%k?+(uUDPqT0^(#K=eqT`M7<^<^zv8_#^;>FNj9%v@oq3cHi=hwa zNnB`MHo6vc`_XM7wh5hB;}dVRZaum&)x z3#)oJ(Chl^(DO)Q*U^XTQGAtrDf|5_*XuR7m%Lcx689)if1UK#2gjB7xW(Nx_0xe) z*WWY7eV$l5x^O*;+sNzsOQB9De2RRy9>pg=l>EL_Wd2Zu=yo*>7Ih^3+n=S{rEx)+7X{k9L?%RFWyR1f7$4?@4KjzOKcOma2<#@lGnc9L!Hg=2J+!L5U0^}p4VViZ;AV( z_I(BCJ0iBI`?epK#Pj59IsXkj4lHoLd`w=fafu(1*S_CRe^20tImaVt3Gm>p*;)y!QQV*6|*ElYF=i#I+wz)~Wt*az8D+%KO#Csy@83pTw2c?LpVU zxQhRf_bG_&Mkm&|#XDVRJ^5Ymj#YKUMKq0j8@hJQ58ZZRThWPCN1Scl26PiYO753T zV(Za~r4z3sul-ZRxHrOU$%p+UUM@=Z_r&%=QS$MjgMMd--7i{sd=TFuuj}nH`MdCK z^5Oau-=b;!*U)Kyq_D0-#IB+X`$c?(y!OX+>Rg8}kq`St+(lEJHro^Eb-iU!uZ7rg z^x^swA0w~p?FjWw!bi!6>rZ@`y!O{)?srGvL*&DL6CWVo!~WXE^Ou8gC3&&dQCvY@ z`)iK+Rd6}^u;0XGG>x|yo%Yvm#<`1F3|-i7;zIJ;Uscp8feXlo{U**OuW{vZ-_C<` z$cJ%?Gs$-{u13b01#ciP);PuM$!lC&xqobg*O3q76tAUeoG-YKYFurMGa~k!`)C-a z_!)VP>jL9^`5~U?kq_e(&(d@~KftQqIC_ng?18$%z)Egm7?$~cD^_b7aWyjbHF zUnj3|o?_fL;cMi>xW!j#>gN(VjZ^o%UShAJ3*#1FAg^&gW89bF^W?+0#kcuIJ*smv zmb{+N{sHfQ5xWsvxt_(>t-FM-lW{#pcZJxi=)@Yg_=0sE=q4GLuKP}6XV8hI6StDr zb$64`*U!Q&vBoRjL0;?6@AVzn1s9PI=R?dNe@%>c3p%ZTHRH@D zwi#VGAL4BCTK}We*$QtWAI^t(J$a35C->(K@H+BgoZ>a)M;TY*c{IGt{Y$KIu7#Jl zziC`0+~4#|m=?*0af%l{oE+z@?x&3NRmS;**bKTbPVqE(jjNw=&cRdU!#KrvY1;3j zSe>UE=rzuBjQb|B>*&L{#aGD>GR~K*?=`rWyjbHF_mI~(I~jK$948;fE$*hNpAK{y zr}p!CV(sX{xW#ScHO>_3bi%FV!??vWtY5`Xd0xiz$ywIp0qZN)IK&4&l6;<5@sa5D zf6~t1y4uJ6jad0dqSwUIiOb06{2tfOr+FR$my#DNFD@bfzs>;UE8!jF#mb8dXWWLrr+k;|s*UHR@J{k#<;Bh9r+>_S zkH?!9xQV=2dGS%2zPEKAtM3b?eKhg6mcKF!|2=$%<7d#X=lHwCMmb)r`WeM)-6wxFx$do4aXaIcjeR^h zo^2mbj^}@I{T311`tgl@xoK8#14O)!b(URM)KK^M-0IC4L~M1DQIynCe|@zQSg_wVyOl6)GxKwhkQi{rbK^Eznzh_%o> z(>{^xf6XV7^L!WcSV!#To|SouU+hWF^KGBtd)x4H@?oChXXIaFo`1;kYvCv4#p+*t zm%Og;)bDWr-NWk`^5J?HSJE_|ZRonW{%aRlPhxrK#Q*RQd`}&Y{aW&Ty(~-an@OyW zyKP&|T*dKEC+Btk)5&@L%a2C`uM#`=>6Lkj&yv?Xez%n0&jg<#ALb?I@hx%wexKtn zz$eLv^Cvz*)A?&~KYxankp$X&gJy?IOPXF|Jo)Md-xRiMM+k|Aldsz}r@hL%fxI9{R@|zZ2d} zUaaHA3l;1&`r+~F^^bP(`@MpI(&h= zSmO{klm7+A^?PMJj=@dj#mb8t$j>wXM)HktJ$bS6;#%?}a3%RVxQ4t~dGU6d=9w$Y z{XHA2_1nO4e~9j%bKFK^>p4!WVK8buZZP;X61e*&Lgk$@UvaqH{o3J;k<}*$iKomzn6Vh2xpQPYkcCReaUq@V|$N% zsd242%jd4_!*TXyxPOSxag6qV8+9*JX9IORh_zBzth(YBkL&jt*IBrE)wsk>bWvuo= zrR|+xPv)=N<{wCM`sb3bhn5cTdgeZlv%k&z66Bs8;Pniw@rZ{%$NSZfIDe<3ff0@w z`W)|9!^(>XT_=uih*Za3%S0ytt0M##Ktb z9tbW&LAJwN!;Lk8Tm%IepO!F z;&Bv{Z-tvzjYFJqkoU=`Kh(P!&7i|!@IJ*SRX>nDCn zejL6}{u%s)yjXehW18wbP#xy`l;5-Xkl20Ifu$4QAwR}(&!}?`zC~WFym*MF^WB3^ z=l3#ot`Li(3+G$hO@5K{RK|Jgg}ca$RY%;yyj15X^IxP+Gj$q?9bx`r>BNVX=eUzx zugBm+%DayEAWiey=XGpi+*QQ(u3AU&9_x0Z)A`*%f4hjq(1r6YE+nsYYo|^zTtGgo zqqv>C)}xSo2i!(JtdF?c$LEoc!(FS67x$6ZdCMZ-5BHJ}=TSUNUgtlZ{0KZmKI{ka zGWjX;lfT1#?h9O(ohu@kE6%=+>zK6x^RBPljOCI8LaCJJV8FJt2pxu z$$p0DCyTlnUs&l!+(v$eek;hg!>#1S8i%-<{4`uez6EX~FIHaMNYi;cf=>JIAmcnr z>=3%JAH`KP&8HNd#=jQb9%3cv!hFQV`?JIb(S>~^?kBJL<}m*uxQ~38zj)F4nNxg! z6kb@B7pK%D*LRY9Y7L{SSy^9k{TF%OMg7))#?M23k;j!U^1RFIE3Tz!oR#Rhc>c13 zaULMH7oC`Y7sY$5+bJFESHZfJ65Ao2uQPEG`3mN{u952o-cDYuI^y-@f4Q5#FZWU2 zXNT947b`E0Xd2IR=2cApI~mW58tya9OU%EE;(40hSDV2~KZ(ACd@cGZViV}a{JSU~ zBfrSJb6KZx_!fCF|1OHJlV5XG z^ixDEA6?k*;ym)v6!YgkRtV>k7xV9;c%JoEod=v3?eBEX+YGV$oR_fQ#rMeTxNOe* zL-;QFP)B@+rg`5)r~OsHej6or16|l};_KF3L8o<#q3b7h8C_UM@g?idqto?rF@v`eYrA|bA9sNBc_JlfO z>BNt%n?kqzi20(MCN_yqES-3Qrg4p;OYCdT&p5H0=)yi157M;1;^?$5uN>p^P-5Na z!afyukxyZNRkM$K;7;;l^(Ss+4ytpEd1_xaQKyO6QRW%;srZQ2f#c3-o#4Y-2iFlF zqG{awy!@6zgv@bU^uI}&Lyw) z8>db_oI^gWr?|uUTJoK6`>MP+?r~I+?}58ljYHf|Ugx`<{2<&%KAeB?E%KQ^VP4^j?uIqa?xoYZ9%t{ zSPr_duf$pEkMlpx{>+0j)t~o+c!{PukI`u#rBG*{*dugdUx^>O&L-+Sfgh}@Bfd}5 z{O+LBykpF7g4k_zVI9P`thiy-^1<&x3Qwhw~|(BcDgUi2PG{mb_T!N&J9( zF1(BUL-;;026SOxiEC-v4;ARNk6wJ0*N?=? z(S>~_E+e1DJ{shDuY^m?`qp@;dG^`|c3Dk9?>j z-b>TCccIhzO>%vf5!;C_tfzQ~b=%Ns{bu#P3$d-}!g`9gShoqC_FEC-$|jb9F6=*X zI(e<%5_K}+H1c6R#m&xlkZ*yTR^`R* zxWc*;bge(;bx9mho(ZH?QhPyosjqthZf* zzL0TjMZcEV%i}A*A3^-$cv8P`obRFhi0iC|pGP7#k4~(9#gDCfh;HUS$5HPQu?OhH z(uwcW)c-iTapJs28kit9hE6P<_!dpNA#_^LZu%W2Hi#~)ulOqYTIPS1^|%IKAum=P z@pKMS8GFIHZBl6)bo`lsL%LX`_7m6d31Q` z8_Wk*UcC5?WPkjfh}Zk?a~;y(0vz3w>J1XRvZ|i=vUOdyXQgKycwI7Zj@TLLy)I(@o<(B5cc^z7KC!Bv z_&81LTaT`S_Mmd!+vyoiS0%g?ib>125%)V zRvqyc@`G?Q`9gRzd9m{1jWmrX1)c6wr&+Ic#3FRzz9n92OzNIBav$Yhx8>*fz3;@H zHgX?@RZskwd^z(tc!>KOJVRcrym*?X{_deG@VxF5yNgaNo%jyzf1n$&eh7UAs&mqH>Z#L8?8K@%;$yDE>#c#~@X=Lu#MLy_DMzPu-pTphM{GB`u7;)j5k!_l-^H&JjC#4Qu7r9w z(48W70-ad>iH}=%#P$$+-9LE!HgK5OLG-pBPi zif72r!i(hR;c4<>9WS1sX&rB((>~OG8YOlEUD%i6Yu5Fmi!shN#@$D(2c1~;#BrLg z5PSBumHUi%j=b(qsnnT=XUT{Am3W$_dEU1jx4q@#wsG8TVmDVECmyn{$F>{& z6!R}+UU6dQ(TlZC;&axWwmoUvXnU072RT1GzryDr#Ex*hSjUSGS$6hEPkvcHXod459dMZ-#e;>Cuf?g_d~#>MNMfv3bCqZ6w? z@ia}>=M8j)#Ou+G61$F0ES>lo`3}~f*YyLp;H%`t%8Rd%FYxgr@MZF1<;9&ejr$Zj z?PKkqHex5yg?%kPZe2aP436hD&_DyRI&@;y6W7voeO6gtp}MT&j-%Yah?T3Z*Hv6f zUi*~SQv+pi3Hh*Z#W9-3S74iGo9*N7aolENn^qks&af`Beg0Li`&X0eS;F(F1!9lC zy0V_)N947iAF~cm;c4<=e~a(aG@iS*w{357Tr2Y$W}c(OZg8Af>mt5JUh~>Xzt`cb z>`fy&wXRK?rZL)3PIGx`i`e`Iq&vD^=i|edAU|WGc z7rmZe?k84`UaWD8%d9Iwr{^_SnBQ(<#puHG9C0Ce&1aZ8F}Q$yn4dVGrupTdYh+#a z@Vb5ru`G0A^(WqF-FkHSjAJ9OD>e{YhfXY=c&&9Wjwb6qJId>6^4riqC-(FxucxgS zKe28ao#wX_-6LXC=)!!(lh)lqSIf9M80Q4B+vvpVUwo6i?l&))?=AQS`EdUc_tP}r z%jk0G?->2DcF`qtV$~79YTbEsTF*-Q>n3&%U07f78S+}s!_+wopC%vHSKLfffA#2e z-DWecW5f=l3)iuDpLM&iy1#5gulvp;>TV~t6@9o5iLS**r8iN2I|J;eK64~b2n7i)dQW7dtJOJ|&A)Ey-@ zj7}__c*wdw+aC03#5m3pYeg@XUVPFz9v>2a$8Ig_ zb%NM2bgitnbmF7d9YU8%f8F$7N9-Uvv2@}C*6l;5^V~(hRmAq93+G+Do4n5R73%DP zcaab0UA&W~bu2=c%Xnw0Q%r0-I|5-hH>hL%?ute->J$ly_&yv^mF-5&Oc!qqqUc}S1#C|{*(N8X~ z%ch7;pcAWK@wjy(wl{41IIfa)e3f}!Bi74tVjU;$v9254GUKSCewn*Fo$wI~l8=)}^Aw^)~rZjtfb zWqg~7Z9*rOPMl#~n(Z3&gVgDzZYr^tzs2)6*A*}QR&qWIzs2XC)LEh~uMMJkbYj&N z&sp~X-8}KNJg?<7Msy#YSUT}N>&DRa(9b>ky-REqome{YP3x|q>m+_3-3?+_(TSxK z_mJ1V%cQ?vI8Hw7L-Bc<&c|8XR`gTo%h9(HYe6qoJ@EmQI{v-HR{tyoY!Vx`^2GFY~;|I`LETx?b)wu4iB7 zafE!he#EmhjsKzT1bXdP-VYhLPi!20*uUa2>qgLxPUaWfJEb^uFpTqYLa^OtzV&%o@`!TY!sbX;}DNn$LAV}^>~Et1~ERzcw;@pSIBGpr|6H*FrpsvVLswB zH1&4^ot`K2+-9JKSR=adyjh%YT`szQ#?i|9<`K(5C)T*cS=Oc7rlRj7&vW>JG-4^} z#nOu->z;opIgUB3o|jL4DfvGAILA#9oA}bo`}E?Q*7u{+IHwu+AhAAlVcgh&yO{UfyDT6Z#ab8{c=~Ie*lMUM#)1p1k%cua^cI;5zbQ---{> zw64{*73j4Ock=vfKe2N3VPA?%$uCaxyn_D9U|yfUCRTsqZRBU+tK_%CTgi)+7jL1d zzif0pjO!$?qc#)Ugib7-c%yY`=ycuiI(lFOv9;*JbtI0gd&cLqQ;eJUV+NM^T=prS z(~4D3{DgcrbuLloIsBNsSb6boe~ufo<@?!*&l~x!^T2juc}?s`>&0W_3-q}$&zr{K zQSxHd6AzQmhk3ji7=ee#i>wvM;VyM+29L@jdi6z8@g&B%eOTdF1oQ zINU*AtUBVnZzb0=>s$QZKIU7>=e9Y-HhzoW+voXHTA1%m4?KtG$cvR1&yv?Xd3+q0haZv;^A_(uk?d#d2_B#L zJm4(vn`|exkh{bIe~Xop_eK_Br1(A6S5A$cKF|zDHAkw{3^fs~^5wKEThML__F9 zzv6!DdeCX#@N*Ocy~N_^!afprTi1!MhI*sa>mt^HPOR~X+pRl=u9`UCjUPBojPI$x z@j0`&fqW&`{{!ClJ`VG9CvQBCix1H>kNxNxsFTiltR}V(oml;e%dIOxr+IBgx0_fo zx-d^MKmU`sAGKlibLlO%kFodR2iU*Cj@u4ne+Xa4PGbA9Yxz08%h;d7<-eUgZZ}r- zN^Cb{Rp*6|uY>QwQ@_pUC+s)wUjx&`CV!jHPkfz;?~sqdhsocCZ<7~mp5p7|3*lqr zZ^HfL#mb9&Y5E>dCpulH*MEc0=ZSTo6H6y(qC7%wbHm{6RoJ(HwN++KO=a3Kc6c3XxC(rj|21ei^@?!NbzDm9fK1co< ze1*JNdGRIkrSJvvm*H2*iBP0P#P_4nX&qlw@pwb599>vfaT)nc)^UXEmha$1rR2q`BQE%MvQGB5d0e8- zICXM~ZTdEkOYTp+k-U!M=j#SG!yCwlI^y*-&2J4l%{#{YQi;8!zpxJC7xXFJQ^r+A z{X_KkjMyAHvBo8yC9id+%{_-;eLZ z>U!*UeErE}UK38|=~9Yqs%@GrxkNt8Hq$o4Hpe#CHqSQSw!pT~HfD>7=p)gutooIu zC9Lwl2&?}$Ilk7qpTnvj>DBN5hUKe}30)0Vx@xR+RgPCWUg3DT<7JMQI^JaFBlX1b z4cNC(CyrHrUACQA^*e^u_(rkncLb|`haDese9-Yptm;f)nOn*@R&^G!sA9j4m@jl0U9q)0x%kfUfI~-reYP}Y17qD8l znl;IFtHx^Gs<2wOO2;c4FL%7m@lwZ294~e}=6Ipw1&-%Cp67V3<2jCJIiBfwhU4ju z*JCyQI;{3bEmq_2u&rd!Oev)Tt9g`THIFjKOC2w9yx8%Wkj+j`usBnvxu!ZbTX%|4Z!0ML^?Tz$!k^f3#0$9G`Z4%JFf>#~dGZ ze8}-Z$NL@caXjvLx8og-w>#eEc(db8jyF19=XkB-HI6qj52l#XU|VlnXIpDqV_R)o zWm{=mVOwrnW?O1oVq0t*vn{kOu+6v4v(2^5vF*lc-*?)!+qPk~|MU2d_DwEU=OqWr zR8z7X&vZP)@pQ-298YyT#c>r?on`8)&JtGj7qN-+?)bdpbB@nCKI8bb<5P}LIzHj} zxZ`7vk2*f$_^{(cjt@HC?|7f%y^a?$Zmm-RR`-W|EdNibvYo_g9urv2V;rmbjX6H* z_=w}fjt@CL=y<>5eUA4!-s5=O@ovYv9Pf0z!|`^<+Z=Coyv6Zm$D16FF=&mukpF1h z1z25oxsK;Jp5=JD<7tkkI=<|>ORl?!Rh>D=+ilyhbeYm`yNuQOTEg=Gltuodc`rCV@A#bKvyRU=KJECFSj}q&%TQ9LvFd-y z@kz%g93OXl%<)mjM;sq^e8}-Z$NL@cbG+B_9>?R37xDyC<0!yt9QjzSLyqHFj%PZa z=6I^(dV(tblIt$I?gCcvTJl=A8ry2yD%(oi3fpqqGTU~n*0t5P*|rI*b&cCjViVUl zR`VIhYCdC*k2*f$_?&sxJcHGEQ{J2$Z)BT~RsXrRS+<#2^$YIsymfPqPdh&4_@v`wj*mJ%;`pHB{f_rJ9(TOk z@h-aeUbEA;$+D?{~b<@m|M!9FIGm!=N>eEUdZb3D~? zy%1J>$;U7H_yw%u<>a-#WwxcZCAP)3G22Eg|4*s6t+lPeYQ5TQXRunYX{^RMh1EDG z9iMP~-0?BTM;#w=eAw|J#|Itncf8N>UdOZkQL?{GtoqBqYM!Z%r#K!tzUbo@eEd9C z`cm?VeP>&28?!C6EwHV}a%f7eZMAI`HnERvm$6!xC9KwE5vy@7I6m+Aoa3{O&p1Bq z_>|+5j!!r~?)aGFqmGX_KJ56A15d7ZQ!w;jXkye?pMKIgGIpL1BPOY{p#UN)z}dLJqut9p61xmeYy!m3UsR&^?{ zs#ETGnd7C7x0svFO<0|WddKS=uXVf^tA2ZIGSqw#5W>Tnww@3b8t_!0~*?^Bm80Jjd}Y$1@$za6H}dCUc{?0jqh|I$q;=wc{0z zmpfkOcn?y+ z8&1_n&1; zZ1w)MxXiZPw!*g3w#v5Jw#K&Bw$8TRw!ya1w#l~Hw#By9w#~NPw!^m5w#&BLHg4Nv z+iTlr+iyE)J7ha-J7PO(J7zm>J7GI%J7qg>ZA)Fjv}E*;d=u*w$iM z`@~m|)DQT^yu_N9tmY+4OIXcAta-_XdFGfkFU2)4Sog7ME*6tGUUh-QeONk;LpqJ)KVdZvS*@3J>PI^DD_uTT^}@Kytt+#x)VdO^>S(=X zYp|-P^;12qr|MN=Rj&fe9P}fu30(tLx_Yd1by(G@wQa+yUMp6*7OZs5Sk-I7s$Mr% zx-P7AomlBQu&UQ?+mDs54=Y_SR=OUn>cz3DH;R>R1S{PzR=Odq>J8dXVWpeIN;iR( zZXBz6V_4Oj$4WPcm2MU*-3(UsrfnmxKk1fX>6Wn4En-z~0jqi$)}>>mOT$W+idDT7 z+dQo5J?+9i(#cJ#7b9yRlR)M3aoVHSn0~J(v@OWuLP@lwOHwD zu+mjyrK`fKUZrglR=P&4bPZVP>anU|7goAX ztm<`ORc{b0T|ZX3KCE=TSk>#X9m7gDij{5zE8Q?w^@gyjH;t8U3M<_tR=NqS>W$kj zV5OVKN;ij2x2KPWNTi(|ua?bl=wa^0Ct8 zVWrE(N~im@>gm2MTY{CY7%N>2D_tR0^$M`6SBaIb0xMlPR=P5*>Xh21vR`piFcE8$ zxc`W$k>EV%bIdVwfw|0FVy-e*nCr|n<|cE4xy{^S?lO0nd(Cn4kh$MHW*#w5nJ3J1 z<{9&ndBL2@c}mP5o0z{j-JEC6F~`gW<}!1MxyoE&t~1w|o6HU7Hgk))%iLk^HOI|E z=6>^-dBi+ro-oguXUt3H1+(^FV*dPxW7D)>#oDK0?MHErS^G__eWtwjleolOW!An^ zzRp}@ZZbER+srNIE^~*u*BmzwnfuLS<`MIhdBQwro-r?(7tHz`9XCZ!juP`X>+|-+ z{LMM$n7P1QW-c*TnJdityjb;X%=$c8++c1qx0t)k9p+wh+&pCNH;d=8sLx-<)pFGv}DY=gs<@S^6^POUzZ~3Ui&g#@u9XFt?do%w6UVbFVpW z9y0fv$IK(9K_iLI`G_1kLOWLs}rW2?tG{-082 ztH(ugfvp}##hJEx+!gCTvU;4B4fjbsPY~<=DXZrdvO~6d9wLt0cG|YtHrqDX*4kFt zmfJGTD1$oHShiwHrLERO@lve%DX}fKjoG%?HrqDZhV||+hx0N5tDhmP>i66BV%1NN zZQQooHrL}_HZNdRXCA9Mla4o{Q@q}`4y$~%j}P;&G*{U6+lF=RF^Bc-GKY0;H;45O z=V{FG5!-OS5|d*bOT;yv1=~66W^HF|!}-@f(tPy1O2_H>nQYikIj|&pUM3s%mmXKd z;c;nL!2gG^IzI0F>iw*Io%0pWmvDbpJZ4*n)$w^)Bq_PJIkx)VnBw~0l5C}|z9*$R z`hJwG9w$^s-;a_`k0Y{uw&C$cj~9v;=pg=|XPb-F@tIiVGi=js^+H$idfQT4eeXv9 z*Y|Do|9<|XIz3qF1=F*Zo=Xa6g|2#}UU(>9E!JVWexp>NtJ> zMONQ`k!`fq_f4cL$11MxzsTx)EwYog`hJdB-#1Y|owglV9pCEXT5Owbn`|@r{(^K7 zR(13}5?OseMYe*t>XzA-S|78%(6+!f-&Wtx(DCiIwYGY|QN3kYR^O*k|MSkz+0NR| z1j})%H)PvkTW4EhtM6|}SHS1{s-I`8?=`3&eV;-34BK?uG}~d@PFsDyLC4jbOKsEm zoLlu%Y$L36`ksO6FWBn)24a2RK(>O5W2#rnKkoNkWH`kY>QeLkI!>QY%j$D!*>2lT+YZ|{+g96V+a}uvTYVm{I<>YnwzIbS z++BHn-Y%=p*=0v<^|`uOpQp>}d5B&wvc?fxJcU@a{!N@*{^i-Pz<$jB6qc{HCp4|x zJO7n^-Cy-%UpjV@BAa3P1>8@*fuF57{=cwaL5COYH{t)O^&|MaZ$+dG zuX{l{eW<3c^LN>w!7s93$aSUX2GYdvKW{&eeSX}23VWq9#PR>sej)yk?Cbj*e{X*V ze;spBO`Qkbi#11mX1m+|6#kd(*YFvjzF#WMH2!P$^%<1z&x!Tn!>4)s+Hh}X&WiWq zZ^Kt#OZX-B>-dm#zx@{cuiEdx|6TmVcs54SzYH4ue`miJ|9kew@gG=Uu!+B4X@3s? z&8(y9RJ@hnyJ5c@zsCL`{vX(1#2>S-H#66;);hKt|2_5_@xNd{Jv)lNZa)wIjQtY) zKeFGC{~zp+ z`yKfIm;LlD^lQHbf69LIyZLz*`|&(}W`_NtnnU`&lV+n>YN_p+o}!vBu_)c5dn zQT8+O|HghE{?F{k@Kf1mI<^d7_Y?V5_`hPm4*#3>Gv6EW1AR$NdpS<-})i;lKsXH^VtBt_T&^#NayV5 ze1z+f?>{JBga1DJar{dAGx%S(zl{H{@e^a)5k-G)zZU=d_S^CATd%*v_&@DueUx!> zjp^95Ux}iR;;Y}u68dtyVHek+@U{9m!Zfd3Wy^_AQM?WcT}&qVCEIewBUxhD~(Fem?#W?Dya=*k8ck#63>OMm0Ph+0Vp3XTRl( z{N5`2{N0J@r}k40F@ElMIyMV`r~N|wI{W-y)aX0*EAjt>{aXD0$9^0Bul`c9PUb7@ zG5hWK-?86~|6Tii`2QVW`)3&c=QET39R7RoH6DGS@|*VK_-*$2{qj+-eSVi>G-jXQ z8z22Q`!W2_{gY&!l*3Wvs! ze+vHwzQ&)^#CdVN4L|Lz$z!MSciJ!b7WZL%=_^k}(Vy6_$G>a86aQ!S7x2@wtp9DU z3;PZDpT*buwBVn&&(DHJe`!CpnZ0d42Y=n$lKL2az5OZtju8Jg_g?$_j9@flpPvVe zem*;SEI<1f{W8AB--KUje;L0{nl2aQ}{LZqdPo)+0VnjW4{Lf=iZwrCK((*6>Di~XAa#A_D& zGx$HTpLvh_^Y-MiCHOn-SK>F>ufy-N--!Qr_Pg;nd@y?qjiJof!Uy?bqNRlD~ob{Ewrk*}nds@CEye_=c-(muMgOn;Cj57PBpGkU|8@H<_-*!E@&DX@8~%^&x8twfkv#VQWA9y{qbjof z;cn8923k63(5O-37#+AGCV&qJYJhYAO#lfXjzAko5UwG?PB#w+B;ll!9BWQ+1_8(M zF*7=Mo{r8aj1R;FFadNhASx(IR2)z2=)@NfVaWH}Rdu>gKNI|a>s$YCt(&z@PMxY< zyY}9-YuB!4KZx*f8GZ=ipUCjT2(OjlM-l#`4F4M8f6DMv2zScx9}vDMN8tPm!qa5< zS%j-(_<4l?D#I@z{E-ZAMR?#?k+%`y=`y?%;Rj`S7s5Ma_zi@=li^l`FTYXbJ%DhL z3?D@JK^Z=b@M|*MhVUsFK8o;&n?zn6;k#wH1K~$yxD(;GW%yf!&$(H^pFnuL44+1L zsSH~_LEkOIHiX}nVLQT>T#?s-@KrLLg76(OOuzf`GZ{`p_!SvWN4QglGY}p+PTQ>5hd)8K(ObzLVie2wycp zof2r<7Xp$ z^+W;x5W*D*6E8;*{*#Q)(9v#Xd?UiilLY)}gl|Te;CHq|2V}hEbM!AVeg(pB$@u3H z?mbz+4-23!5GFboA-qV&&+dS4AmblGc%O_P^#$fwZV~WH5gvvxmHX_MXtOeG`#1bR z3J?0VWh}za5d8U|3*pyfxC-I75q83UmL0?#mke)3IQ>=;Kj)CeGEs(W5MC<71@Bob zn`HO^;OOyMq5_|hpMJflqE zTTS7+OktlX{GciPKc;Y_DSX%zK4}VHJT+AAXj6EaDSWpne7`BY!4!Vh6mB+!byN7H zDcpBjsN9Q9;cQd5$P_L%h5y?WUTzB4nZmy?g*ThR@0-G3n!>$q3(?cp6dq^_rcbLK-n8E>5xXTpo zJv}6cb4=k3Q}|j_c#H|#pR3f z>*H|}J*9UquJo3AB$E6Jk0@eHsmD{{88g@7URdlcE^}A%g58Tm$vwn&`(p2+3B_e) z^NSbUGiE_WxwmwMH;6bkK|`i_7B4IImKK(J7gdy04$W9vvD83@si@ZV`uY}E~!{nngE~@ zq?N8%P|B+aXE;N53V^MMCw9@jD#pQQTASD*@u>i{|ic6v)^I+S~4tiX9 z$@EgM&r>e!QLM~MeV)5ZqsuxA&>j?0T3$TAtklGGsV7zeF~Fjb3(X{h=3XF;wME9s z79ENX?*>fpzH%7OyBC?-cXVG7tTosHPPx%HRGLk=0TtB~kaALwGH&V8vc(IE$@EVy zDJ}OdUbwi_Gp2ay(pjaR%834;TV&&X)Rp$A{E|{gr(!`2F-MVVbXunR$y@9xUEru=9#b?1GSP2hsF|jC*~hzS5FdMD!%Lsnlu3-bGPaGK>nLRut^k-3IUO@=DQf zM(~#rPmUS9XMqPY4qYn@Zgizkb1xJIdot{GG(}_%gtd;F?lStH*bt(}h@B?;ir9(J zzo1seq)nRX*ttc!ik&7pm&#~vxx8Y;2YZv4_@b1xq;vtCAg(20H5pNFQTgC?c#0Qz zi+CSPMFn-BhdE_cRd{|#W|JPAZMkLx^25pgAw(CEJD5*yPk_q;z4iuMA$f z^gwyf5&V+AY$&rJ3fwJO3NH+%1U^j|_9d`~s6uy?C_JV2`p~nKxX}rgl|-kTC^T>g z1|u>`u0tf5q0MrOk4d>Cq!^Uv5_-^55hxyzQmO5n1gPr@- zr6hki*dlFAMfkVja}|3qj`G68@p($eco&03Z}F0)(OvitFCn7f@xH}nC1ZX-vGLpw z6J-%aMfJS#L8Um(A_wOq5xkkjL~uc&M*&6jcfqPP$A>x6h9hHCZnqfY$<$l|dfE(F zHUoFab?+9tDOWuChPuWecOq(vj~c@mJ`ck?-pVoD?w6K~F{Zv^IGQGO@mMo~czM4p zN{V8}1ZN^55tyVe5lE2qD4=i;BPfi?<*f(b% z8Ie=qp7?4L6N!Sz9V?gqvqd>>=NtvKu6r`T!181yUS~_Na zab>A75FxK)4D}9Ue7?mc(UOZq7LSuiIP$xxus72 ztT?dKONo&vX@x_ZNagd+!Z8(dYjVN`FH#nG2XeMHGPNAq8JY$c(hB+CK{1OcE+@h_ z29rLqhuWsR@wC|lLZwGBX-cQE5~WKrqClpF2J=A{WE|%Ko*hliPR<)6Lo`t_W+_%h z%DrK5<`_Vi!=qUi!yd^z3AMruBTFS(64EZ=VziqW*kVq$gu$7)>Aq|;bTA{K~@Q%_;Y&uAhOUXDAOEJkmN#`nliI-kkd=D3DSz6 zuiQHZt6i8wE(@|PViT4S&kmZYSYni(5L1vAGBiDdgprLivi~=l=LGObUQ0q3Y51bj72#{gm7y{tmKujkd7$|$tZ*+`JU;{(F*_3! zoZC$r<}Q_n!7T^zp5pSmOHBpE&mPJtr)mU-o|G&G_KUIVgucf#3YV=mewmniz*xlH z4Uwo2W6jd-4vs{PiKLz}+6|7Lu^fiPJPLIi(_7uhEDXWfNi8gE&*WwrB??87Q(`@> zsObDXD_4yE+-O*|)Gzi7oMq85QJh`r#%M?C#}6bR63f}qaCTK}Fx|5@vZ{*wCPi4Or0>bz@6!q+p>hq8FZ2Hi=Z4l(>Wk2GkS=tu+3ct>kyNHDAqQ&MEPQgS5yXOz-2AOmTV-HNlFpb zl>jnIo{_N8qKt$vF(0p1jVy@-5h1{Cvm_FVA-L{D5(6%dKthmcJJZ|+lZ$2;jX_Xq z0K}7*k=|ozowcmuW=JHdqTt$hh`_|Pm@r2i>BY(uM~=~;@gy1znTXMGriptv={@DP z`)qMoj+-$K-*MpMF&_s!kpx2X_&sGlVU9R_$I26j82Hpp0eIeo;Zxh&J~C4Sitd^js=^D<%nT^Pr2?sTO7vY zW{kso9Qb(b#{o|yfw0;5p7P&ara0Wk$rgwAn9%V!j|rTJZ{b{?RVxqf=!qewI2^|+ zB@VmMpz(N(hD^lhw9qm_47!oi?41jTBypBFtj5d~hu0Xe@tBPP8fRo7Xb+p>4lxWu z#IPl?hY~f45o8&;i^x$DhfxxgmZ&!3ke8UM?lQ%xk(k-y$vGyl$>S9HVya76*0`>u z30Hg(#?75brcy=3Tz9C-6U(%_OmSoyCtG4Y52q|%4a7v9h;P%}dr!GeoFxv=F*C*C zI0kGye$Aj_${@sSu$c*4XG|Bw1rx22Hh%HBhZu+iM0e??fk8KzBE9Htz)fywH;@s2 zTQ|^Q4o^3*k={W!;Ne|rH^8PosT)YC=Dg?a1~kmDbXRrZ&8Hj8VY;_8wCOQyBP-TJ zLMg_w0pHA8jI#`3sg+{g!jxDkW;V;QI*v6N`9)g+@Wpj>GFn@xI|iWx+!l39<~wA_~dEXs_Y25O^Sj z&;&z#7Kgi+gbqa{EYd6p8r&tqiHhfYE4qUd^=i`P*qnIzCb`%(Z*a?(x)Xzx8oO}1 zDKKQkhKCl>L}UW4!*W%2U+>}Yd@E-Uv76_bdzdA-!7~ATgh?JwdBT>d}=-OD3n z;)l!Auv7S&XMzIFSt^6aBN8crIfvXU*+cvuIl51TJ-qK3j-|xNZIB5HGj~P9=@c$M zTTQzW&N>s{Uy~qLL{B!{ZQ8JSHraTaqn(0hov9nPq6m}*MS6nqO;1!t^Txxorjqs` zCdd@l!w;870OEEg5zyFbgWY{S$KEsj7X2XEw{Gb%gHmUbfD|*R+-{cudRoZ%4<{FZ zdV+51J64v2qK}n<_P+I)Ec3>;vm(x%vF9X;&2b5+3-i@r0E4^cdY&hIzgvQQQh1^< zp)AvWtFz11b7;8>D?tX6O9`I@`p7q8^)!Q2vu^D}SSi6we1lX^i;B5dD(>DN+W0h_ z{&ln8C3FBNE~+58Q5%@}$t1q!EcO)1e$&`$7=Kq$#73w{JiPBYYboyUXgy=T=MoO1tp~-J9K^6~Q=zQ! zYco1;C|6v*#6TeCeyLFI_+`e-9?IBlv4&F?zELWKp!<>|F@*AVTX@f_(O|O&$mg5x z7cn;5gtEowCp=#$M_ev~W3RI*#gwTBMTo)V+2A*F^Z>auzGtoG;rV({kQ|PmbwT0z zdQeazeF`@?-Py{-&}0ndZW?19AT)0yM(?3z19y@6TB-@++9!+KU08~HQA#G3Rm?9g zgI01UTTF}wr#c0yL}?KN+(e)erB7`9) zKOt0bm~EOwiCJz@4)KIMWeiOZXZU zB~J}MxEujd>fsmqZ;vrZQGl%jmxlr*iPa`B33rJf~=E9uG^sPmMf zf(jfcz(BerFLXEAt))1BPDwbAF{NnQ)S}6=!^)XbR8&e$kH$S{LB_=4;?l~=<$@ND zx~SAUQ(mwWBqb<_DMd4JKE+eEl1|FdEor>au;w+TNZfN1A@(Unbm2#=NMj^vQ~@9t z^V6fa$tnt-7aAQKl34^TB*4PravW(iCO<>P6#Be0qdU&yDUL2kCMF<7LKMkE4iN>1 zO-6;voI3x%ad16~ftW}zn@~y@6G&+DD$A6jAflpi-ijrQ7vTKp$Pwd`srC!&H(gi|r zVgT)SPsoSi#e74#SFkamthlmrdMU2Hz{v+t?{?$66jyI}yij+yyOb1%0t<0dmbYYa z$wK<5EMJQAS6fyzOJZCi!cb9x$(o4kW%C}p-tL6JK z`KI5H{9hN%W1J%XUl%RHBZ{pYMSR6K=$VA&`TxvCv!I<`ORdrP3%1jdvD<0(wIj1c zQ@yKS+2Z-86V0Wbs~0a_R8d}vo0;eq+6vF`;;R>ryy3d5mlV^jxx*{a94&jl|KdzC z-e_+8-fw!-Z)sXG#P{Jo2*>!#yWVQ40^IZ0S?MP_`E#L!!~7uK>KgoWyJxs&&j^0< zfcj@#5R8mys+LhVSuHL75wKaRcq0C}-3wq#=`67Li^M_scV3C#t$EJKA>M?~_>;Xc zT{$F-Zv-8M`$4}GzsrET)Xjh4IM7#BhM0+@!}c-mxQBhJfpIhEGvkwVEFd?A2#Lhe z_6dGh5jgG&m~(~WGx0)`rjSp#Oue|v|J#0eIuhY1T_g_q37AM6i#qXpCn@-Cx8p(| z${w|H-pm+r-5K5I!KVi(-pvhd2%-&IWXI=Sc59?OfLZ*x;jZv|Tq=$4$%WIcaA{#8L2Ps|8g}e zt7RjH4E=-sSZFpNkvLYRS^4jCo*pcDdEILHODH=JVWeP?Aiu-)<1os96m-nFLf{y? z&uWkicm4s!-5J74ayC~k#sBq4%t4!@|yMC z?VevLn>|MpiK7fSekKbrmmiJ;bl}oIFK#njjw8};cR`*(6TMFWM?qh-7w|%V--4Ir zJQN~}aHrw<3ofaD5+7Cnv08FxPmr1b#<`1L-5emyiA+s(h1>g z%HEva=s)J2$?CpCi)qQrLPewCC+LgiUCIm2A~@TnK@jQ(EaFQU+sERyshxpTR`-F# zu8zvo|8u6RE4xwInLf{bXNaz0g02=<2VrP&@y`}lr}*j|$0K=~R!52s>WNfP%v>p; zxFs)>m#sez^nbJZ8@>0jxvBbt_+kJ)>VMxmn*F0R)oRgKQJkICO(voWG97H)zAg~m z7hyXg1d+hT&6_?AuxgShLZ3h%&CGR_By=5ppitEdq19Di09=U8Ky21-=awdA=dc+3 z&DI|7+eXA6-9|l|wpx$FuwQ057%nBm8HzAHiBCLDP8ka5JHIo$l)L zH?FubyGwg~34VO$Z&szN_nKnXpVb~hF>GcUaEmf{-BMD!#u4(j+2GJ5kpT&Gn(N$=rW^~?0WQmcNQ-j{0CpV0e!t@>Mf574TU{(yHMtvVI& zxjPm<2=i;X2+xo3{1=`Pce7GtJ;^;SWL|`d&sJTXY_(lmF59?J$uareAY2zpb1&)1hHjDB-@FRBrTVb5=$g$4m-WIl&-AAUCa{1-@Rlrwgx|4#_8@s9S# z@qQth$2y&L4-ak>FdT2^KhXI2_YHwd`Fm{OeE!Z24B+p)Kp*~|q*}8ZNyVIl zE$4uoL3l2~a|NEO@!)48E%@m_iwh5a;>$7<&s;pEc$VPt;aQExj|Xj(OMvT?(qZSU z3Znt$s{w)b|0FjYPvAosj?;(;RBF|hr|`ZdS9+p52N?RTJ<&bK2NJ2LGS*0{V}wg>Junb*=e7< zcuUJf?i;pDBt^rQXjl_@H_|dOT||;QinL7e$`G%y;&p>~<%w6W zcom4(B=MRdUem;jtTK?1NyV!~yzUaOVd6DJyhe#vmUu0q7gPS+mv?ID!W?8?W_^dI zf%g=8Ps}u@lfR{3C)0=X^dT*N8(g0bn1-*NDazD?^d9IxjrK*~6Xo}}_|{KsdJ ziGsSv@t$bcHg^!mGwxUjtLRNM0qA{~nQ?q4{;92BOoH1_`9ef|OS%&Eo6RJTcc2u;IT|Q+^|XUVMzW zu`%Tz{mCtPhBQi!P?Eoa@Y!>wnD4%(7_M~^<`cw_P~N9^veJ@A-J2IpRo{)6?8fSy z)Z7d+Og@}31n@bi>YfA?s`f&3cdSq+mw*^m8uJM02H;T)R}5B))EpcFRstkFB8`L5 zwjU);BUQQsK*w@tTb=&>&~KQ1wWWFDY^(Y@Q{L|_8VwbniQ*Z1lVT)8=Mp0*yC1zL z&SApw{}Pt^yCI*FP6n^!xBxy_4#fE0b1T{A-AYY*6)gXmwoks}E3! zW3j&ox#})XON~~&J~8la0!{-k^hi2dv!=mAVfmR*PClOb{@-9Le~9rJ;Ge^c&%^w4 zl=1mb{&|D(xs5*i7+=rwM4R#X8~$lGK7WBvEuX9Z+>Q9b@%-6V@6{}Sj*;%+;CC6H z3;1V=@i_yZ%1)bU@G?gZ6QujDFA&W$vl zyA^4ST=%UEKD_G%Hf0o>bpy*E>)bLgSNX(>d^9$tF%lmfG1m)_fA(%RbF2Oa7s~<7 zb-+}zAy;0Jo*#r=JxSQhNs_Kc_QKds1g_Wne1RtD+NueEUvqV$P%}YDosKpA7N28t!pZ6}K5!i1z zDyRQ>2$ro*<SrO0V-SXa zzxuT|ojLwy*u@tENKVd260(eb$8)sl<6cG;Q~UZQrMv9f(mY5w*i4>+$1A_Vs3mX< zOJ=v*xIXCFsBqQwhUR)BwrD_|MV}AdGxDg1^#Ak;N=m<DQ618Nm%VtT{|_`&e%CYk41mQ z$oV$rgK6M4$M6{VU{Opo-l@K;?geEuJyzI3Q=v)~mg3a8ZG5&+gH57V!-Z!D!jP?* zN9bLLS+WTrtV!NT06W-3TMPisS~>x=FjpI^T7@zb6v*}Q3ydQ)?#>wWP&8F4)k)Ia z!`4&Q9eN9(tOuiJ_SAROH(5S91Ycj-z5-+*BW(${&ow|nbHTG5EI)-!NoDiWU_Q9n z5IXWC1d16H(RK|KMy+vkG>%a0VEGP`s6aA{CiDy>WEyn+PL4;{^3zSEP~8zzoad96 zlUJHE;Jz8f7|DX?j-ZhK*A6j)EI?v z1vy1(4r-_eH2@%3P7TSydP+YCkVwv}J60jP`j)CaiWw*KseG*Ae^zAC%g_r1&ZTJ>Eg0uP9OTuXJw_SZAA!KLk7=(^7WNpG)&37K z&HPUh4u39KYVqgMAOwFJ|0c4C8bgY_fyhzcUrE{ge?VT1|K}3mQ++|R&uh4r;Etn< zk>yO{I=Ob5Ti~iEup+7_6P@86kjt!2u0nW^e|5qpHu)wx;D6q!KHFe9ja| z7oEF}(CQC!cxO#tbOKV2&YBM~(3N8r%|Dxnft|kuoe(s;(dnND%%S*?gYo%@&pxJp z!(JgtYW^954Y6zy#B-&nf_^PLohXCp)31h?rfz-^`PgQe&!PGs!Z!)q+<**+XB_BP z{SN~|^|eWNPWo-;`XPZESq30WlE~tp2I4gSpG#Uy)ENebx1BcXrcg(m9G;7Df=uvc zy7XrO&ooX@p|RFt7}L-^AZX*w-1?!lSzk>>?67{rIOqAwnG~mgCxoBG)l)rKy_1?* zSf5g$lgMVPGvSKt3SZHosltBQUDYT5iCs5qF04Lz=8Nz5davN`H=X`gsmqy_8h1GA3PzQ-C~1}&2F))g*#hZtyByq{#UbGQvAEs!Z*B?+W=?L9^Qkc z1eUCvxY&EEa^gbokChWv-z~hEP2?kv`cJ5h`L1UZ(_xy8u3(4>VxfQ-gz1`z8JN3q z>4Cn=8e5t#NnPEVw8rMO=lI^xmRUUyi$OOgU3ba;k(AaDp?(wW3Rz05FGgrU1T+z@ zrx3K^MoR#r0;v_l)O|rlt;Ko-BS~=38`K0wwYUzFS}2Vu1%5qut{MCZVU%do2hqbd z=C8;Oql96o3l5~Zz3I$g2LEA4)2YF{cLN;C&EBoQ2CYX6PUASoFI%F_e*z zh`g+D7rBXkdH->|WJ(AcAi$nByQG z0|z$oE)FKTBttUXV7*NcJCf!eRMsrAsKlYO=C7cUYXr+JVR_GYlzv{K zF-l^q4@8W#xQV}ufSMR29@HYK4iehX1%g)Jv>wrib26=`P<=T|7gai|&(MlI6v?^v zcGx!+-S=2A3a_tz5P4|cq5iMyc*v$z&8Ij~t?vM*{%RW^So4lvh;<%Zxx!ZXW3Y#1 zivf*!3ExQWpQUQG$%F~h7TVm;g|qKKCq>#pgmNvRZ+#Kaq9)+nPBnZLJ+KH_oBhYA z)zG3EmirE9RY8QlHc@Y}5ncdrZvQAzz}Z@kz_H8)@oc1 ztm+Iv>1y)>m)2jDKYp_B_&l-yKrWEkTXMx@b1t=bOG_^KHWtc@W#MVGfEHRbqZKDM z&&Fn9RV+n4*tgYt1$TEH){{!BW%MEErX3i?J+a-}tNyL5{Wb6V%G4Y2$N2 zJW#=`<){AL8gK#;+zcWcuD~z1JGX3CLd#WRiHrsUz&h6+=wsbG^mTO?Hbe~N29Q=S zk$d3>XaPnt?A^*3oA;k4baE~$mG7|#v9KP%S-ZhPU&+GXtbEB` zP9O1HXfOIp91ctQPQGTT|NAg?OwNazg3u2d`YW2Zo6V&~-fH+1U4xx%33lFIKn%ZqQT5>IxN+&ml)gK|RRoxYsXTZEoF#T_|STYC( znrb1KBnx217%=Y;%(;NEqyUDr8ZhJ=>T<~O5TLZUM@2NtBR(PO(#sDip40w^a;2e2AG^h5W)Il#2{hSw1 zqd7IHNl$-~sMl6yTdiN!=bdu^^R52f&Khn5u$w?k57|^lrhW#y8QSNg98UF|Fe00p z+LNVJ3a)({6MF3ff}iiu=b+%{yGQhw!RS$Y9)PsiH52y#5bfHz%r!2X!M$>r`X;dCG3aLV) zkcvj)8D&cCMX5rgDAkhUIp36;M5!@#$~%O1r-bxL-K+nFMhe`SN=0A9rvqv0hV=;f zP{J)>7yaMGg|lWJ)HRBekFmpnc0%Rah@bZ1fFZhBul66K)#&zvU0n}Bb3cT<{tlJ- zJ)SB&-{T?cVZ_llh3WlcXey7%*E@0iL&is^#sWS_$CKgu^$;py(noA-|--8wb@8YY6_(D75@74KJ7mT>QHH< zMM9@A8B0ndKZK6Rk0j?`_NCJfqCr4Z{ZS_{^gUER#r6WZ)PX$Zqos)N`+D{3Rrs!- zXKVUAi6>QmPZ8i?U*KYOpQOgohwO54E;@vpLCK!wpVmA2AnHZuj&V6-Y=ln#_)yV`t?GDB`XGLeuHhAc?J2!m>| z(<)3Ln;}8AZa0WBWNrgA$^4%b7ZS)92S^|vAyQCA%d!S#dMgs+$wdjn6C~;}BTLlw zuX)&`!D^GfL(?0Z%=bC2K_5-~2>TPU8~^?>kP@n`=&9-E#bnC>7t5#cE10bl6 zR(r7s*|geE(Sai)Rme5KuRDN1t9>dlWEMewyq^#5j6Foy@y8WGtGXR-FwxQ*qbh|q z@W2j6LuE^-i%n4Qez8l3kVBT(ZcQe-Oj7q0qCust7bYR}g(w^D>wV8RUlOO+aDtZ?<9JoqV|3(-n8L`)vg3}nC85YHTZ~5 zzG0EJtilm+z`N>d#r6geWK0}Hvp2mX#eNkPNZ8_!7oEN0CR`;m%f+tx#$^ zHt}M{0}C-{{zZWfrj!#P)rHuTz7w!wPr8$iUSWj~ds;u2FeLXU2^ssvSIA`>5K5wD z0O*dNr*1S-zeGlj4JUcflmoj|gn})57M0dQwGO(1Lu1TZo`X;Q29E6O5GO)Cka8(c zIY!vmbJK5XUcG{8HW*mt zcQTsd)^SvSL=0R1S}XpWw>9hcQjQk?F{dAOi}gK@05cvFnesORwln2M=nY$c84zgC zeTstlG=<4D4yOW?3k^y{CwCztvl|1YeDYuUjfSAM+)*LSXH!{NF+mczssgICdN_Q3V zu-c0$H8QD~Ck>93l#e8JbtkK>qe^#u%W5}$i~v{9PNMb-PKwcghqVCZ51^j0ttKLl z|C86%%g0@wV=NUZY<=3B_)qm0>LO9YPZK6Ab}4C+9`H;x zzk%o6No4{V#|uYh4Pt8=eR&~pf?dpE)2S5h9i+0Eo$;K68v)Dv2oF;-WRZm9ikRg& zgXNiFPrgB1^{l*q0+yH8E3EH955SwjY*sY}JvQtfvsrn^0ADD0xaNsAQAbi;s;S(D zpCVG*s6Y?ReP1ckf@fvSIPMI`aU;iZJ;!lP1dhwhIKotb=EGsz{zKJ4wr%kdL722{ z`s-XB3RBVeF3^;fgctiXj%DxHElDtrU-d%7o3K7M3zXs|Kr{AB_^#a0T}A{_k0g$K zkWP}%`cM#xIR>a!rcefE>%(Z4sD=R}o8(~16*4z8L>ND0y@O}{lxUQVW+IPy%qWrq zbil`1GlB2~QQO(LEOc_y7#nJ!H7Q@OD72k>h?CzKXel7j(oRw{+R3o>R~slzls>}| zm??dX$NbJfNdbZKmk}t((T*!nnr)ypQ93&cZ9b3rqk)zJ0`2$+w3CN0d5cr;m#CKy1e5FzNig?Us10Mwl zpSLJfF>n)TSKCa+#u+G0Qam>brG>}5XrQD3q4eRD3Y`WrRQJ#>+B+Dx=cCOVIGI+= zlm#C`QtPkB3_D}L-;MwL6pZj2jZ;dnr6~}AMOT0odr%2%AY(UE)?ZzYtSB{w_bt>_ z(@_D;b=?kFwNJdRr4^>f=vsdLeyEjPMkUl9q*Ur!%t?KC(&1Q1ffmZu7AvWhCmoHI zw3#RAv63F)Ngc70*6^e^L=tN5I)>Rk@5QWk8pOiI59YUB4+d%_5cO@7gSlF4m;wbP z^@LfFAnyss3L(N#<2~WUfDVmhxd;9z$)7ULQ++jIdroU}NPeLn{ljJmemkpu;yoDY z=zXZEnd$~FCReCZsaEyB0-lz@#r{)g&1po+4XH~3CwHJy5Iax@Fuv!MSXzdB*mX)f zN(-ylsAu_h(wikMWIksCAjg&B+|-zZY1>Urn9JXVp0kk6tzn88a2QeCqVf%@Oqmcu zp?;&MAkBK>Jf0Q<*>voP&EXqov9WjUbP&nOh|PPD_8{+ne=Ur6zY_>(ftsXkj3c5%T&%{ZjM{nzF+q3V@G9UL7?b{p1e5^0)a>{^b>3{-~8VFG}a7CVhy zl*SWWXo~+eWJ7a0l}n3HzS*%z>K~J&%i@sq!FJSuEfXgfUxI4^tAPpO=<~vI?H6Z! zG5Eo-VGgUE3=UYs9q)i3SvS7`D%N0qm)yO9WTU8VHr^{j#?T`uixN?=dPtO8j~U(U z#`dA8L}g8?#g`SUgshk)RHKBzqikYkiwhwV%!~B$UdkF~5|JDKNJLUcIYaa|?M>jh zNi96Urlzxr8S0+C+r1;$#$p0~yf5hI-4bjc_5r#&jx5nXnA?V>0T=!a1RDL1lpmWC zYfHHd&N0cLfhQeg?i4mPmDQTcHx1$z1Bl(8#v0B+S#0%TR55Ma!!Y0gUNj-%plNNA zE3N&bzOSbo4FicZDeNl{V@%o`OL)V$F>qt|Cj8nt4BdR)lc0^Qd+tCeV@D%Ut-BLb zaoVPPpw+yVHU&f|s+P6^8U(0ciCX&jIMq_%9#*%5Bfi^&cs+r_*~S4vjE`o~hDMg3 z&ZcCre^j6}5KhN;Q^6U{h+;yglh+9rMCnu_n4HcjyGOwC8N6J?L~XL8ph+9W8kr?eKS-D^E52ik&T%uL!;Op zDs26w#J5SKHW3PF6gENfvx>K+FxEgYUQ}hs5a<;IhfNONpD^WPPWD<8*|#B;<~?sq zWh>J#wL%D1L1Ls6KqN+qMiuFo6Ev$OAgL1r2|M2q6MF^&3L#ss!jO%zUt!M5$b4WM zp_>45wDm>MYpxgTzY!s$&|vHRA3&S*V$XNH^?pM#o9KXL%3?8!xHo1INvNQ}uaq^Y zOjzh>!g6BfIuE(p=XVj!n~0b70pi6}B{rh4nN7t=#spw$g`d1a(qalV4#uXMM6nww zj4hy=G3G#|grDIlMW#A?3^69ZmG^c*Yng(*F>Xv(V*UeJSi@EOLh>#lR1I$-M%(xa zSrBe5pQ9!oQkbB;uLCs6`=n@xKfxR)tYs(3dqS9kHvE`M+W1o{$s~i3cQ>fgHZ4c9qr$XJw+pEo>*7PGix>q%L>BY-5)R+D;*M64iDf4{JERH^jt9 z!qxB;O4l|%PpzIS%u|A$C?DZCJE`tc))JlY#bmHTez^ChQ_U`s-(xciT z1l8~?6?xo{+%~|lTOH1Am1%5K8zDHwR_oj+Oh1Hp8HwpGiD#mp*`&gg=8qFNf zvbqDnFYlD#RS+8FoO}_$O}JhKi#)hF1j}`t7DC5?juIsY0F<9onc{hd-I^X&>=|>7 zK25k>I)t`?3oR2uBfBr;Ce1`W2kL;}SnUa_$c+U;96`lc2nbe`!tP9E4TmXB_KG3# z@GkLY0t=Jn)jWpNPgTh!sT2LFlu+0?PCb=#Yr0dp`V2lNWT;0VO68{xi_tzA&X|ar#f_<#e%UfCP3@8U{D0u@oB&&~M;}UCdz*p8b zrjrfjS~-j?WT+_zn^py&+gDK)G}IHSjW-~pStq9e0NX+#-W))wg$LOL>=yMm%DZX2 zqgc@p)-Z?iZYvOZMF+b|xOM|5KlWAm`XeJI-u$CEn#1hYVXQV28DeY5yZ=BooTqWt zoZ+?l5a%<^#OG#$z#)=uGWhoTcYQu3%OF6ghEQ5HI^M zgpvV%(}nkwChlXvR!1F?*fw_*FLA2~uh=%K*c_feotTv}AIf8L1^ZG5XwQqDLJ5Tj z$Dzt{rlwb31Lu}ke>$rFStx{7JC-WORQ&_L#Nb}s`-8g*u$79=abO8V?5NdhJ1cno zV8yXcgs=)Vg+gK#>M|G@{gEx9O`^gDg?C0S=v+)$Ll}%@?FkWI2580(B1T)Uljf7c ztWTq66hmQd0<@Xl;xBl|_aLcIzH~IA%dp2RgX~-25#@g2Xhi4Qzd06VHy~GgF?TeE z5i{$nh#8X^{S~s29!!C!VS+FjtCbuAM|4uv<5Jk@SC=w2gCn%P9**!94&DjdHVJ!L z$+jtWfW+3^O9+_THt+pRaS*DYUE|Y3r=KUgwmjAI98;*_GIo^ao}?;$h*V{DCy?vd zW63g*KX49XnN*&y6UJTm>b#pa`WXF>$i$d#s?<${CgwdyxTzu47?3D~kcF`;c7@8H z$5H(gG1~eMNTInl+$i+IC@^R~vH%TjFub4{0j>-4nhITX3&8D>lfth1(I9xac33ShXT)X zsQ24&I*&F0pgzR%jm_GM^7MEA2GL{DXtv#uVfdJOHkp2~V5z?0Yh2H;DDhK8DKr4Q6UrcjV-aPWPP? zc!)kQSBYIZYEof`?!vN{+T4C089fMpY#*Sr@x8sui4@P3dJ6&y( z6DuZgom)DUy*B4F$B~T}ej>$LcQuf;KM3mK?P=9}{u?FGMwaSZMaZUA5227yO)n&> zPk5Hs3HJDb-l?Q5lkG~AJ$t`;oZWFkkaH@%t7dwYfae|!Hr%)&;+Tq;t z4JfjnNy=85QpS(o?%7I<9pcJ3b`dCIHPzpMY3r`8HgW>0&{qcXth>d2=95(*+bHLx zB^MvEoRby{s?b?|GFXRSQ8~?2gU*^Uz@m1t{{G!7pSSMDHr8hecczTp)(=?yyH{Li zDrA*ph8MERT*xY;kSCxhp+bC{+YkH)4HJ0Zz?(`lp*w4uecRgo65VRN19{tegGl9j zyXR_tgI@LbsA!8PV;fDY;sa+(Jm)EmeJN}S4jO2qIQMUe`QEr z>RaFel0x%oknIv=pR{|f5oDj_WdBHzjqD(M&^DsB8Xp#`$KKRFSZV63?xm_Dy4L^= zgn6qCp5))dWqR05(VJ$9wi^`5Dhm8f8?~){6i{iS&a@B1+j^$`3cQHWOYjzk;?Af4 z&kRNTeyBO2+=DlW9?7jv^_eQL<{8S(4o(5(;v=2g9efC_g(5~-9{W_^)^=()4^D(@ z7eG6_9yR_uEQ)`L@0$^R0&gRZz9~#^+ICNAQ}F%`o}nn-Frw+aLz&H1N-NS8Y_?tI zX4@s3E$A{eTQ=FDoP-5A31X6J>OCl)7OQ?cJxwo|dgHlm%< zdW^EmX68AR0UC(U-mOryr`KfqPl6AJ7YST6YCTi?SQfe>d*H{_`jqkz zj;p;uaiYuU2kp>PKxckv9s9veV59)gV@RBY2eT#?+R6De9wUyvDNOH$&@D=P9?vm6 zS@Dd&TXGV=tPGgd z_vi|7T!)}rTc-FvPQv;$$pr&gHnR<@)#Gz+OLMOMniL2(KCQ4>e7!N82$W|xzU%;E z$!)5Wdi8tltl-jbAs!5=_4Xfu`evj%x7eKU+WO~RbfO-iyo*jV$3@)JhJ%Ox1veUz zW?uRSDnbWIN!|-jD?3thfih>D4V{kXm!VExTiY}rh{@wvJ6?or+S=p=h!g7bXVkR5 z@Ap7fcv!10APwX0Nf5Yt3dITFIs$$Qz~Q>##$^DzXh$cOhTuzXSDW>VeuPZqIv&P4 z0-Psj-JKAA($>1Yc;P%!QrLN<_iwC%aMZ6oUgNM2^R?AGM$k!`zLilVnPzlQ=JvMv!CX=wEN^H=+F-z!GgvbrI>cJnV(4 zt#_QyiIJxRjFN+&qNw0!Qt0f_`L>q4^LtY>Y0f*puk~dzx$TUXlP&l*aUCis4%YxE z7$MFd#o(H!#^su)MsYo0Li>Lpza(Fq;jBa+Kf=;<#$~9U9qk89fPv4d2GF^oBz|rP z*Not8>+>=-pZ321PRYS4Qk!!F;-~`KA4Z&dyvo^&cm7Zn{m$QE6uVzkeN*T#8khD3 zT-q-MtKL&2WJ`$!V}tK!@M zz|aC{c0eNm``R$3aAaz8dRg`7o`MH|2&7aEAmZm9jz_dmkvK%lT8l&Uh=fFsFcHmG zw`rrs;DlwpV@hyLmMZ*g8~KyQcx2D`8g+ZwqANSfP52gJp=2y>bIe&D}CMQCsAPCUo) z(8$+_qi+h+dk_#)+E6M0&qgSoVIRlpKxPT=JAVdPb!My4ckXEK0EjyC0Pi~wvdQQ> ztzv*{#dQovX{V;xx{ph^(aq6m0yM=xA`{)pU0~(o-RrR;fj-p{9PeI;>mc5zdH*T^ zX1|Z*oPs{-zRiJ3`as_~z~cJ@Zh=L%ImmVj7Wxuk=EL3rY($HzjatZyqO&}Nk-O+D z523R>h0gLTBm#zamhWTie~A@q67SPl1)5_$ic6GE0|G|Hx?`$O$L&F~n>JYyEek0NzFR5g~Mu zpHN6>jvE!K|Ml0@MYiIm*m)ATItW~SF2$L;$Z7x!6{zl*1F3Q?xKrKmJp2MIOHH+D zGh6lJ7a>zKR*iZ0IJ!54k6q+7NgYfkI1WvnX6QA5|O?Yy*HQ~u!N{xSVmr_F~ zcT=nTNQu&2}52Y#JTjZ4G=Wp&3YG$L-g*pL*#hKr zh_|Z>!d6e<5bp_LtARv45j5jnmRxiZ#vxuDMaLmtq{>6Q7{>-}xmHN2y(m>4;zg?I z5HF=BQK~$|n-2B5#9&v;%eGQGhB?B{xGu>Qv$Q`$)O|0hzv2H%nsEh!iDVqqrCD;C z-lbS=&R}bME=+=^e1;Vr^)XtVQvEOBe~W)B{#%gN;%}r7{xp9pPp6B4TKv0YEU|4= zpr%n?@>^hlvftO&VoFYJiC>MpwN%HeM($H0HFh;}PnuF=RwM68Wm{1-vRzVlwY-rL zl}YS`iA8Xu%}KT@uW&BCgBiaKi7SD|m+-0=ZA0DLGP{00hXUAtN~TrZ>7w+TgD5iL ziscR-!kxlZ6eq0mU^3x>M=9AV&!@mL9`A#A8*%haVS3*!(=@y% z1J3|-jD~SfM;tfqvsHerG6 z7$Vqsx&=?xVAr|Wgz`!j!SRJl)qexYir-#M%V?xae1j+T&ba>-EUDKk_orFBcVsv6 zc}Vscl|uJH;pUGS15$Ax-3avdy}ZNNssT2Pte0QTyaOCbo2d}C_2pk-Kw>@8^_jc^ zpijVtvp?tODosOxBWHY?=Xv^Z2Ukk=esSq_D#}$i(4E@qh7|m7;rB)pHL5lQ-|cB2 zEx+$O>@f#@T?h#&ucRZG?5x`Y5x0Me{!QJSiWu?zKHMKA*!EdiUR|! zd(;n)zoXr1wZ6l6l=`*ueJ{@_^__iRYUu;m*n>H*i~{7E&miW!4;|JM%=@8jXlIU} zV()Kc8|Xdsd+XPMt09D|*{EGPn|X#Gu^GE;kaOqI)}*{Q9WsL>No2rO?8<&!oU82J z0p~q?XdxnLQo5?~dK8pzNCR%=<7DP1o;4e;8UL1QnO{Xdv{Ue>`iXC>mVDn^@>9}DIYmiOrLgj@mb*-Y&@qTSI*)mRok^Hm79ar8Kw)Bh7-6rb(1-5axGfyt^yRmwEtN;%nE(2Z$t9-;a(DAAwFL4;JSqAt13 z!SY3G`!7(>SSV0mm#DZ@n4k1Wn`Gm&kZ-jyjkIfYle)|4p9^`Z8%QA-*Ea2z-Y<{_ zzSXAPVpTON5|>+LjRPn+Czl}2~YD<{8y3FE0Q zXa1h!hejXrUCFo=(5eb%foycGD>kBm*fMF%ua_ZAt7U;~ZB$y$W)kAEE4h7>neLOB zCYUl^PnnKoH}=Ja+`on_7}tU;x-gCD-11h_$M(M4WkcHne3I{51m8I9d#+mZ%2;BE z8xX8(3W;eXaK^=|Hr>;ds&a|5S8N12C)vih;;D{gbw)C-@Joj2BJEZSl2cR;#QdZQ zSe`mpSz+(xbFkbLrjbGEvRS+Mh5L-_{?JY#G3!@Wu86cTr57-517sI_g)Y(0N9)R2 ziWR=wVJWnUX#NySreJouvMT4>UcPr(LCy(26K;j+;QAklcYj7xUONxUOyT715j z&B!?|NMeqGDsS1CJN}vTnO#yyQByPjvTgus8?t-0F)?y$g5ep2!a&$k|Mpmc8BnF|}{54{QUs`88{gkTFNULZ4l2 zqLJaAW7%TGd_OHz@d!~hY%_1Gjse(qVa*@iT-m2;Y_?^^ccLY0xm{OrBc+`9?#1tb z)S2IOymd6_zYvPgxY3Ic%`YLE_BId@Gxa4(-A<{ZH>@5@lH^m4mlyF6Rz!a!q>Jqj z_B>R%p1nQ}wto>eZ@jYl&P3`gUKahf0qQ?f)DO05H+^I?y4>?sUTH#G)Nxp74J5M- zWLn1OaHHd_8$-Hzi(#Nfi`YD_=)%OYnhm_lc(xXoEk!k8*MI?X02 zVVsJWJ=l2IRN72HPA7~^-be?VfyAk_X(qT=H!M~K$S zq2FxpPjNA@ED=~ZF-|`fD(n+qWB1);wCW-#OkjkenOBoNj?l~uVWUYiPoom_O%DPU zCStHgPx>VGRZ1IiEvetTSW*-e!BSd$*|N!dhw$be5@&dmpKi;S&HMvlN2+&{tI3_+ zMC4r)B9G72pIFz`^#%lKJlhdAp2#@*rZgj-=E3Nj-W#yUM$$(*%Z7tPx>u#_9hBsbir-{8Eipwd`qH_;}?_fVRcIe)6mc*P=O|g*Em7=uZ*X%wI%))|?ZwQC{5mtI8Vj)GtCY>l? zI{^nBKs32!^WU`82I@Kyp@p&qz`2tK}1eFq)P+uVywn=KR6vNjBfXz~E5MRLbcTn<)7?(n=zt z8J_B4$Tnvj)}XkJHGa}hFfS)Kwfe^y`k}VxK4$y()Ckh+(C4FA#|X?^^w~ zV8P?^ISDBEQHm1SzzAw|e(JI-nJ_l zRvX)x@SAajmp9*L7Z0Ihxj$6aPpGVeKjTdDfrou=SDT^F*rtl1NxHE?6r0mmVv}m8 z=1sFg=tG;KmYwQ1%BfzSk?M#0zN91vc9CMMR|d1aiL2hlUMoK1?nN1}v0IVxp1c;D zBan>^+3$O^c@Y`N%4}HoGpwoZ0=U@M=o^5t8DgbnBH zsa?>b;t*O;YzYjoo>X@ne@k0owc;AD4)qYsttU%;eP2Mc<*?M|oSBX_pR+m54(nUY z+f2)bSIzCrnd`V8cj`JYE&_i|f&ZedX$sP6H%^9%isZDWg$a}|o^}wG)>B-dN0hE!p0VnweE}{|Y|X~h zSUSsvOtCAz*`lpLEf!Uj;^C$8;BdSdv#B<-_8#t^S@9$aj+| z)5j0cng!|vJEPRE)Ls9-5bO}H1nJ7oSb|Lr6D&z@4iZ zRIBpQnx>DE`|eUsC3&tvL@ypOfFf+3OA&!VG$01`ZBkD4 z#{F))0)64m*qYkX`|g5js8&AUY@Y_qqKy3icgs+l_O&;)4Q@IyID1#1A-l2a``%9f z$J8e#x37YyA`0Dy2=$Qqy?y~P46<&s@om>4^*JMzb^)pToKH0k-uJmgiXEbz>MUFK zF13%~q!ut#4E(8|B8VxSWat9sh%F+0AtH0LcaJ_;*`M!CIc7^<<0b2*+6911BIt$w z&FQ}$U(PMd`YNj(bf^@T3a*%UP|mn?FHSDmS!kR&-8S?a%+ye%XA;3;u2C4^@>B1|Vj|`nZvsod#nT(oya3O&c*^ilyb(v=6sET((`Y|V z22`Q}PtfGU@!c{)`PUky%eFj+%;gB@mho0Jil(;YzRgNklIJq#mRpR5@D;$I_j>~l z=N3;AvNio{P~W|n)$(8jy2WGVG011mVun<<;}%$@>dOuI3j3V{y8^f++en^*yeh8ISBclDi5!e9da7y2ot1aP>_JrpkBS zh=za*cmIH#TIy?r0ILw+SK;kkTZ{j}8yN?vJDLJEY#Ym^8+W<>R)2!bR`tEWUB;!3 zRs!lz+cN3OU9rLpAY8foULeN+>P0~35)j?FEA|F{!tdOD4|ndqUWNGjd076rP-%1W zTWi(r^{E5Kk#$R<8z3>0uQ@oWP5LG3c3Skc1U{iUn)SbTX2!cSQ-_c&v)O~4PjN>@ zy)6sosY=He?9;d*^wB0iGU(%={79pZR{4=aAG_p-ojw}Hhq#g{wD-Tzu05zBY4kwv ze`)o<$%P(qfA@)-c?fRj3JM9g^Bhp7|8p(5scCcDPqlKNFdWyoAg-A~Ttz`#4^f=J zl}fm(4O}lAjDhRBCk?v#25}_^ag~H{T}-&HHE{jmNF*-#OVn~#KQ~xf)gOT!wCHzy zCioF|7zTga7;^Ci%U4CIxCy@w)`$$AR@E<5{xB+kbG4L9%Ga{|A+jo~PHv#8Oh>*n z@q5_#g>G8NKsQdLiNP?xsSSgH6d<$ccXMn*LRXxN9kOot7Id(zZW4Abl50};>_Z4( zqC;L>bq(_IOY&`)rt+}4I6y`}2hKLxehWCq`E1=V5JMag?^Zywb#_X^4SjVy3ApI8 z#D7&$lEX+!L4a6*_Z|det_(u5l|!Zq$k5(OH1n|Q-=_Tr807VDr`LyQSWVe$XHY8L z(PK`U&y(iFN}7lye(BAF8-a?he|tD62!5*RN&t`<5Khull+fTD#_oR^f8jSkW)F`5ONxk#I=nJxd{N#LS9C= zqJ_L5JUW(q?xQ*+`MmdYqa1#8^$sG0e)@s5dd34Gt-b{~xmM3IadZ`Nv~CI|kycMf z05>JGIt~NknJb<2*2ckR39$Hbd;al|-13MlDYw^FliapaD#^{9^d}^7xwVnFNIBbr z{BXIc0Em`bKH-X%+sN3_86p~&-3yy;sJJ5>+}sAqcHo{UUWr7N7sqW(wfPNvZ5r(1 zEcjdblyNa2*t&Y6fok$+6qwV;M%EMePMa^{G_zSdkm!=8=I84|=+31A>87k} zIYp4b)j^7Qy?pi4SoN~~(OC6T{|rUP;9l%y3%2>cQ`HmqeTWj?b~yKblqKdb)Ys*Y z$j;=}+Bbx)`vq{Tw*Kmj{x(*(k(ZyL79@dlnBXK*ziR_c0mPK zlmHX8r$#;N9+QI_7K6g1DO$pU)7|ZUfqxRvoXSlk3qNsNeWm=GFIP631m$_Os zE$A^Y)aoenZoVDa)au!brW1~DLDGvCU0P?vZOgv#1_NBe7#;|co-oL;z3tdyv1WmDhKc>V zi`|Yucx1pV;ddguWMoJDsfN1yPkr*s$T@rw6p)(U8lv%=F_y;HQIDIbmWiTnMU<(R z2&;qj@&#oYqnLWB1Vin5xrVK{Rt3zPniw9R7*0&i;u#@ zgUQFs4KI8QbroJ-p{()@Ufs&|vxPIIWLU*>yt)HX;pJB==EXp2mcM|J1m}c#RR@m> zP2gV$Y+SSa4Pg5+@j*0Z;1`rtOHhhI+S&YYqkks{C%jrRb;aMKARb?Js^M$>xX*kp zY!t8K&*flzCA@IzLJ0V^jt_l$zu^)`ntbT4{ad4wCar$k>>}I zk85uFm5st={3Qk?$=L$?=7yg%O|8nO!WU(J72Y#Qw5e73o7&TeU;Ij}fcrZ5mD<-z zBSx!R_P`wBs^!uWq{CbZQ8nRiC>*Zyy|vg}Aq(L+?S$pe8*Z7zRVYY zmx;eSUh~BO=i@R8+ZX-_rreKVZ$W2$1-Cueo2u@OPn1JR;RjT42LrKoTKplp;ud>7 zEp%46+L!cXCS9hJHUeUAkl|wq&(1WXO*GNORy+aPQ-VaxA5r-W>3Hf|LgRcc$s4(4xx3Ez%WIjlE`-zxVT)L7+LSJoSu!E5%8 zVE7hueb2Nz#G9|^)P$$1)+5&p(Z(*NEW>GUnt~rK?P|r#me|j5ZKP&gcs*?lWWS<& zGNZzqmC}t2!bT5f+y)O*n435t?2BS%85M@9Rr`{hzqG@zZ zfMMqC8m^A=qRih<1?MuuDSRITu`^se=j(=ZyRyZz28r6DdZ)5Q_)c?Qv~9<|e;`*} zp4XamJC&qqQAy!j%=m!szM^JYh0RTCQ(M*hlyr8i0Y)x!uUYo1wh7`+GjYaV%i`>X zxOyh;Bj(N~GqB^{Z?B{cF5@adZ17Km#7fr89+}wg4;e2qzFNz8#H9FCmFYGhc9e_f zyv}fbp*TwoVseX?+w@1a(&|%vc@F}&UDlY7cDL-ZfImkLSMO6LI*obb1LkIB^E74E z)N}@yX`4EPPd9k7>y=b2ST9s0?9q1GCM zoPG;1nBh(>OBb%LUDsiAt(ohumej2JXHiAw+h1S>{>`lCS;%VJjLxH}-Aov5!0&)F zT>Yy{ZvW2B3j~)%2uhoI2#D)9j+w3N4~Db8+UQ(s5L4f#jT;M^x*qPU>#^iaP&U*4 zO`OX~O4Sh{UNg@(m+N9W+hM$s$+wZxbz zgStO9#@k>r*z<)Fcz&MDlx#WCh^SsW85ft&>xNque#qWu01d!L^D&};*)+lMR2Gwi zu3b+>1ug-vbla1_j&9qSsiNIhDli{x(VqK#P0beWAvpEbkll&p$G&dit@~)N?S^#R znQ%5~Ie%?K_S{y?06mu*A^ZCrm=DWl_85lPn+=cID%m}YxyldQ>{bIE^SzU#zM zNf<3IjY_gox+Rp-4_sd!{1u{1{|P5#YDrOXZR|ezg;-BK8zX7Is7!&e>`WOgCzLUHrh$pY=>~}&EKV9QFcedDjWfR={?V( z^`iGY$YH;#B}XTg7BV}tOs`nw7k^k&QyA?!0>Ez_{mU`Gm@pWd-su`H@H?Ua}r;_;nn8FC0Oc$6A9Um31G-YMO@quuB2odyPu zls8FNlEGk{(X8eC2TYUnMGm0QWhbiP`7DD}QHIP(ZGiC9ps1GUU-SH_d9N(IY@tfM z9Y`mP05~9Mj-M}8pNhO{mc79Wa;pb4rN_aAzomV2%}YBG6RytWNGuKT7R0e!?WDxf z*?jNSCO0z&%9@9Fr=sgUv(kz+?vdm+%{8U!V1vqzZcfo_yBK#naQDVo$Z;<9ygO~f zzMR%K3!=JOM(u|E{uQcaEI0)cs?0{o3w1cW26rN190zAI--IxmJThtQQ#Os2<6>|~ zqaTmF(ddV<5LJ!hTS*cHh0kE+-1I39K;qvaHRnjbw>fxR-@sG?F;EDT3I#gV%%t)T z8?`&`dOz2ee;dG-^Vgv-W7CB9SlEZ*f88^-fFrzMG}S}T*b0i1i=k^s+5SM`96GOo z-0*@Csc?J$c4up;iS9Mdd7b14?0(**bt=NL8&NAw@oc}Ys zOWb){3uYL1p7!hIre}nj*=)YtSS!s3;0T9xf7lG9*3p`%`Fdrg9I%B75S}Mt(1rOM2@RAWVC7lfUse=T|g}eLbrVG{GSn$Yzw$uI9;8>7R&f z>9=&JY)98Tc^-hpPjj>iSM^oqYDKX)?YTF;+5CGV%lEA6?_U^%y&U~FdereZpo`3@ zO}MI=vi9@9?b>3oZ0mLG9VXVSCT?K|#84eeYmXb|y~u?xn$%P%jzc8;-!knlb_u=r zGNkYQQx=rAtKZ5&H8r@ZXfMyGPo5FNciF!d0h~nU;VTFJ`&CW$|kc;@e{JQnbZ4S?;Ru4?(r7T%L=?r#6<3k-5k zW6^Zv$u}iiOKj%y_q>K954op-`(*eN39Z5}-7^O)t^0>>UCTi8R#nlfPg-#GP{i_` zPU}Y4;-E<3WmPPgdEp-So3t-3g~65$Cp$jEwrrv0vK_%JcbfAMd+!FY#j-M2dIlPk z;m_Z;i<->51-ZpYJVUaJ*1LVBI}4HKtL!KCj?^q`Gy^)#-H+Er_|6|6Msw>ia3Xq!_STdI}u_@8I) z4Vs!qqHtLQy!;J>hcpOZgD;}n?=T-t?Xgn)Z9E!NWbtp6yJPie5~3$HphWyE+f?;? zV8qagFBjnlJf=oB5*gB9?+ftJs5tGcV{m9=w9dARXEw&Ky-;fWFuG#{NTFVPmxEzn zplXBNf2xQXHs#_WjhWE2-Y+KsbN4wUgG@j*_;DkChVdF+g}>H3d=AQ%HQ<=a@9~l6 z^O8bG;{(o4JX;2ecq&EXv(BUVt?GvO;p)Eni-$I@y0$sG{J!5{7H5-xU2JXg6?{?Q zBLty>`Gz&+E*aT;9zMs|1~tVp8Oyw2*zs4e1!RZc_a#!n_Ej6G>w?#7GMZ!A9JW`r z)YUiq1#EMnv-<97Y+`7mA6;qMfxFOoIfP4R@53un_(d14bKd#_TTAm+;bku~FrhK_ zIE};4Yy`nqyd#qAO`#<9iyhOE@Q3F{7#_U4j=MItXaM2FcTqajj#4Fv-URu$*p^`7 zY4IgkHI5C(UEXtVyKf_tblt1K5nkR9*_gN8CW@=(w)p#RCaQ^xYC%=ZrGfCmV@;S> zQtN()J#{AdV_Dc>_PT%Ns_cN2HeLRTO3g%Sy{b z!SeK3`N6VKQE7?ik0>uMEl(?*mRD5bWfx5;$tw<&2d5NOgv#^C9++HO9_UmNn4DKs z94tsi&~X*!l@t^Q%L9dZ6@k3s@?c)U?7*a8utbsv%Yy}8YH4M0L7=2G6eubw3KiuQ z7hM$uGxRBe^clfGh0U=dl$Sr%8&w(zQX6k<$<&h4nI%F2o?M~z)6pMTpZ;Ksd_O@&V zmLZT|S`hStd9%D=0bD-ZB)T#$SWyHFt^(mAxXHzNQz|@|uB@cg%dhYXDiG&QuJC{s z&g?lYFTb#;B*FMR8F{L9kbLX=QnSFck*qRW!P=v?SOoGgL?}Un>$u2ahP7 za(LKOriqge;?^GZx3ofHg|mIX`DqB^71Glvk`bEvcno949O zw9@j~s{Q4cPU~4#oL5;z=ko@*g68wPKBWw0_>AZUe` zQwR-MU+6iuv#LI2yHv1*Exy3(ahcEV0UML5yZWG#f~;Vuvb-dnohf;u_rI=fH^!i{ zbMwl|iqTj(bYv8uvlUG)!Why1{6I-jaa`;;+g4pv?UJFznqsBq6&Fv!@KAx0pkLT< zj|-%&v(+Sdy}Vp6RVuL{i1t1W-7LT!60E2Q6vY%lwK}u}LyG6O=fuFpdF3TVB~t=D zdITm_a)eXKy9ZF&h|?lCBK{sJPT$Is*a((*e8k8VEYB~_tElkM)kDE)86}fTy_}Q| zadn>^jq1=ntNW<#<8l=>uOD;%^+3nh<76V$)MM0 zTPa2fv13Ao6rYo(+3m!UFr}Tv808Ww=1?JLDs;M(4x_RMm6zwuc1$j&Xjb#VSA3G#ELjlbToVYDbFk#hX2%7L7tcG4`+wTqBr;7D0XOaHIbQ_cJT zm2~JAYFIf!`RdA?Lfoiwcne8i`mab;TDD%|oa zz=0PW(RE?~%d<)>=jy25Nkt`jn0HO@ODNdJIH{H@M@K5JHD7ifU0yT;`i%^R3QG$r zx&(r=uvW_tWw6V$xh9+S=l^F@CMHayE*3|XMFl;JvDn~r7ML?9K)}t7by+4lwER~U z8y$OWMI656#F)7zGI<+HX&XJzJO4jwxsH#K);dUp1pq3OA4>Dj4S8KZMDvvNlc8k&*n zxm;8^S;o5X+)!yQtTGvML4uT(B_?fWs`K9}V!B75EDzfZP6&BZdd(~<4|3}g$O{EZ zCr`$rEzqgb!`j`f!OR>sBd;8LjCh7nDF*67>`ViBlffD|r{AQaP@pbhvFU3{d1+-C zGd{eyzJ2Nz7heXf>L(%pP8HCeOS#T633f?jy2<6iV6L?v7URmc*f`B7%4h2<;T{x? zkNaKE*!Zwjo}HdGE+aKTl{<-Cgh49g%_yB3^ro4S3sgaH7IsO(rQ24I3rf%#%=qT9 zqJp4UM0|ag9int{Zm>860o;BBjjDNE1$psi!B8|MRA@H;HrS)AJUF>%7S{vZ*6=@U zjN&UXj?>t)(kl;6uS63m$TdCIbWPPf6U6#c_&YTAZGp_mfstl6*1moFrqPXIZa!xa z*;eKLpN|A1iYAp~#_5$7oDnR>ez3gPc#b)sF?$3H(opnXW=MAXo*t7htIR5{ESWy( z$|)tGDaBJldD9DKO_@}QOr6u ztU=ksa&xVhl|DK%Dqh~5cO^lSe@#%b|6BExga!~59|69pMh7_!)Y8E|5+l@|X zdCwe-kLe|7Ll`u@IAXLjn@Wg6xs)iBPKr6l%HV+Oxih1~=Iupgc}K|0LqT#gvvbGS zWsvMkvUAc$=4Ol^H86(Gz7=+pI z|4v{`3`*(RLmkcP2xV0Q5?MdCHSZ~Bk|k3t=FTXibmB^h34o>Tsb(|<2PEvNN~Xwu z-z%-im0f_BSB^b@2wN>|+`Lc``g*bZ3d=0H2#zfP-8fB5@te z{e;J9EY}PkiAp-yY_!Z?$kXA(#nT*iZ;(*w=pMz3g%S&l#ZyhsX`e__yUman5p`HX zc(6$086XbI(L5XhQ@CJRUZ{}kBpg-dI?VFMV@M)r?THi`tq!%~!iO8*O^29c%BX?wzuY}Fo%g)6{3scSizm(TEK3=bInc_?=zw= z5$t}579T*axQB@*apclmbVl(6Ew@%_)q?b|0d)DOx^9hQ-ko|-c@D?K+?V(FdJ z?{G|r9h5b4+&OjeNaX0$x)???YIJJdx!6d`IkzquHZraRwI-gp*xWeE@j5#W(!+Zk z5v}1kG&)t-f!)g~l9OV8gpVF4uaV3VXm$v@w<+%6vb4fQ`qG$Hu01sMr;sR@dE6vr zvcQTu=rV}STB;q!-XtW-Au*#uoyrNylYq;P(4*`C9W_}TifL75oJ8luak1v)9f$L7 zh_8LD92@kDO7PaT(j1hIF2$A^o3@-WQe_F^T*6Tt+7gs4F5VG#{)&o?hm(5u>D%w@ zbI$EQAa7EBL2$B}Ft`Mz__((ZGRT#da}aZN7ahCI$Y#ci!I+2!$vhOzl(Ulq4Ocd2 zsAzNyGNHPP7mLJ+dUg%Aq%AY=-eOMvNetlQqc14jPX> zk53(KsPRLxFC{%L$BD;=YY35xGe%+W#01z6&>x$f)m!{N9yHD!m5H1gfXl>SD#4sA zi;m73r_jvNqa4Gav@8<@Lst4w6HLwIKFE0Phi5iYs`$W4!bP;6N7j?Bt>3Aku!t4ndGIqou z3618WC&7rJqwSh#^bs;PizLkymn=>R&8qvUEUT~_3E-8_c+cK5Ksb60q;sc&E}YG| zHlXW)N%_JHFZ42S2Fq8wf!O?z9JnkeGc9vsQ@zl@`QGs0?BqZo8aXVlykI623XJ6P zi!pdQr8IThWkC!WxU5qO^*bOIPCUOE!sU*}FKU7C7!RizkuC-rL@TXwtLnQ?Tj9iUzncmbHg1J$Hi zor;TRiS2OIXLl3StGJ-4-n#{|%YylMwV$t#`jeY_UAuM-AymOJ8NP)9DrJU6ruYovJCL5}$HzKbh6o{!e7!xgD(F7s9X;aTCM0En@!~{2z zG#E$NcaBaiBTe0*QSQXixb0Dz@0p|Ya(g;zkEt(4joK?~ z$s8|}D{&6aHAk{no>x>6Ot$ZxlfAOiG9ID^)6GQ~oN$+vV*Qis&NOjH1u0}>Kg?cd z8&XmTa>^E+O7OO{oasC5>MR79L7#EzmWfwpUPO z7JoQ=rr^v;SK=tA0@rykdQ~KQc(-C9mX8CPH#TQTf4h|N@UEVFSe#koJ%rpFaqC{Q zXpucSZ_t>-4|_#OmSMpPEcIA@=KsO7({Vl_$4u13c9~ME56TJ7WW3|y1~6DKqO@cR zon2Y-9^qhfZ^qOcQ(F?H3r^0fEDnv#E5TZ=+#Y(^G0V(Hyor@Q3l|S728GOE>lK&g z6^zEaHjE_de3a+!zAH}Vb%d!4RzXRqB6IQ(NJ}m?Hy$fojg(cEPYKo*=oeOwR|_+O z8Mt^OikMd;$=;E#T1D$xtBbcE`8da)?a#(GEcXt<6?4F z^BcRj7uPkJpF0M$=~WK7E(>=|7rPm2cqh$9EAs3MS_~?f1+e8Vu$@x6%nY7vCx>{^ z)OAK$5wW<#g$9qtOPWhk(_q1$U4+ivd%(F#v7VLbUQgH-hap(z5lg%N<%7);QL;W6 zN{$``C3|_57(ZqHca_Vfv|H^Yb2*bNs~C*aaV^v?Q(WtH$<4Z-OP%Dn)l7^stC{44 z%aT}Obr<1A#9VgeFyaOmSKR2pBi%;loRQ{oHM3sa2Fmi8U4rXyX~}lO6w@N3*0B*x z#&`OxuGBodgDnbSvEW96=-Cw;hO-McnzFWts+uJWBcp>`C7DS+mbLBD_N**foqHvl z5fH0$mktvS8c-fJ8qB7cY@1?C5|>1*(`0oxl*~65C@yM=uP)@u9jVetoEt~DyntOeo%=-ox7Y0kxu(C*wopG8u!pN-S=+KS6 zvh&GRP1UpGu9<&q&?*-v$wrt{$mmR9*rJpiDB8DGD497#WYdhk4#R69P|=oR6WUJUFvU69mH`&Zj+0juAwTmWW*OcYv_Y~L%+NoXYt*9b> zdL_1jUIl*{RbVcGkL887AvmMpE#>G=E_%lTm+{S>B)S}x)v8el>Z=u*S>onkuyv*4 zxM##irz@^3XO1YXsF*FwS#C>;uQE*8a@*0HHG6ZY54=TW^nj!kjLM5 z9wR=yxCcKKk3I~Om(D%-NqO8uxx|>4OG}Jti0ib>n~z_nOQ&VKbcuMKUXI`KQa1DE zLtpc09_r!p^vkDAmkz-={Tau!uFOuR=4Tq(LhI&_*Y;t!k)(0<@$;KsLW9RWRA6$^E?i-sjLS>VJ6(&Mx!& zB$2xO!v2@f=-mgoS%L9jLR>a z+uzIg)Z01y(Ku5e6c6PQ(JB4C87?o0HN_R)G5x*Eug;CjC(Ue(%g-mC;5x?KxyUmv zL99)7@JA`D*NXEs*;?_cUDS$;wIxQmA13*8k86w_OhFSA=zNq@?!~8$w723-*?m}A z^fvn344TFlEkD5>!t4HX=hk66+@p&A3axG)pJ9+)o#JfIJ{*VYt##)ZHB{X!*5Y-? ztJ2{|8F!IGv;^dp^W*z z(4^~;4KOz&<9F)nYtsJ{egDh);}r2f(Ica4*NmdyX&2jn_4UgCg-XA#Mg1>wuCoor z`g7f*g|E>ZZj}4~^EmkXMxE#o{lBOtwAdrfF}jV;{hh7b&Kdt3g`;iSF0P8W<|)FI zg0)pjAG6>w5C-!itjGHF^FVUZS7g=;IO+~!<)G4XF^IHdEY}#sndM+g|1n%k^_8U) zh%pq`KLuAVeaoQeyugu#!bJcVVps)AW@d3VhIC)E$V=&;VdBA_(WhU2pZ;EkRUsp` zoWnZO5k+K=$+l@hONp(1ad~C*J|_bO<6pewQ}3!Quil)-^;$J=*kBPhMkfJl3pcA} zBvMI+LB}+nf7IX>4brOX6YmfAH^i;faYL#bE^JU`;>QEez_)96*AYJpc#~rzkyUlX zQ|}HtKXL-+{nSYZHlt62#L^6X2Ge5Q#jb;}Jem zo@pndtlR{?rUZ9-fCel!ljws6#O3*v@xgPcI_&47Py z>nrPE+uqh8?yl1!kxkriyR^ynucpCn43 zg!q>_Mk2Ek*o0*pjrbEfqujb|O20D3ca20YhM!3O4Zufs!yi+wBYqw55#1vZ+Q^^& zNOf7-BNBN$K|1Pf>ab@dl9?dhwVz&yKS#%-P8c@}E(38reQ`(9Ck<(KB&f$Drt24p z;Dcnb@~i&Ea{K}D6BER{a=eB3I9KjPm){7TeKBKfNjKV(!S z(yxyA^@zW8bR^QOj`&@OPtS@(u7~fJUst`e9kqqKAv+Q|4}LTqf{g1A_ZZ^h#tNB- zauBx@aU0-A)2aFj0lqp1U(MxVaDsX2X2AF0E0@>SQJ$v|pEe;9XD$e?7pdfrw?Tn^Y9T%R~y8RGgOu3rL~OM#6@knTu279h<xf?kd^_TE6V&~Y>@nqmNCbb| zDyFx}mwLaC__#hM@oXE^d%%OfwKcFK<(Z8%aeH#H#gWqe6X_1uFULRRv$5!UB)R*L z?lI^ctvk_&Wuy(RdKiD^LB}(k;MsjzJ#QBrpTZvXyjG{%FzG{rcRZnUrJaa^U(MRnOa6zn*vV(e=EfW9xY-$b)kYbNSo5 zn7hm(%F-;eEBEeCp-;V-E{HM@)7FQ}mb9#s#}U37a$11>BZL6U$?;Vvdd6}jLa^FRI?$3{dtWOdC5#b+^j`nGda_YyG zvFm%bjN`l1E@Oi|n74fCD1&MF{(1n?{)~KHMcPb})1fQUZ1cR|fR9G_O5wQ=;Z?w! ze2(^y@b!Wxcf;CA@Tmyj3cNLVGO*C1eQ1L@u+KxlF96PmC7B2831BI}Sbp-&2ll!T zTLkQ%J`AjepYr*zoQtHK2UK}kPQI7F>wT07`Rs;H&7kWg2oFfYng@73!sD?C{7uU6 z3BnDxTbmB)i|_YKcp}1G!Nc~n8e!H8+iV%yp8p|2-RStVhJJ}2>URuW)ehJhdVB`C zPNyf&^}H3pX93^e-}4?sm~}NZ8G8c>FGlz^2|s}Fc7&tP-G7&ya@W}|XJWb%1qE5O2zaF}|y1M~bKVVNO>}Fsoz}{Ba zN?;>_?Nit)V3z}H_C-wIJ;0^{J4<0}fz1RqSYZzWo9DwG1-1y-NW}-WO`GR^n^8D8 zxdg5n?iRRJa1X+5hx-EV^d~Id1O5QGLbwXJd*B{~dj;-wxKH4i{(JcKo@5TTU3RuX zQ}3-B*!HnO{CN%S_(VNsqis!vj&ot#8xU^vrRSZ0KH9@Sus%fiQye@#1pJh*&@L{- z{vP4i5dJmA^FBs6Yah}N_PpD7dftZ!??^=*q3(+J155L~9un>|6#FTJ8-L?@lQWPW zY1bn>eYkDM9CsUI{PaKmcHH4G?U>`qYrD{&zxBMFYdr5D?4OPBT?o52eetyzL;s02 zKk$zcJ`G{E*MkW6lW@y<7(0aLOoYb?em=r%e+}W*p?1MPMxToQ^!hH$%8{FK#c;3ld@b}H(_kz!-0o+L@ z>1zfj>cYt{x#*Y=- zfVBXo=b;_IPVn(H7P+s$25-ZC2KOD@*&A`*{5tw5`~ZAD%%6JjG39Pz?K4{Z2jTne zQ+gQt90fU3VIPkBJ5k2=&}}(v(yPkmLs`3^b3fp>N_%b0%<<4?{6OfIsq}O8$9w}# zt$r!uUjg5*U-&TkvAnN9zm>@QBIxAm?-hiHK_|Ay6Xtu~Skb!&!md5;hTi$WbsyaW zY`U*3EQhfJ$}kIVCEWe0JZ?=zJvT@>4vOEoS?%)N2fjoP?ZUj0FmBz4@(hEnt~_ro z@VqCaJmae|2VneUJ(MCmTf$2aenEKdL-#`8*Ybd`d8086)p)b8Ez!p z#c(0G>)=+vt%l=69Xpy3Y_tdS6zt3Ap!l=!=Xw0?=N0fJdT4LzF%kCWc($(^>uQ8Q zfIZGy7_&%pI~(-zofU?)R1#~79`0PI~K zUq@gc`LNEw{^d*C9a#O{@qLhXWjh}S8%~0|4lWFL7u>^eufy$x^WH+6g4?;t^SZ$2 zLw!y*A+(D<2(+~JE);(+eA4RufyX_id55nybW`Ft> z;kP7w!b4Uz$AkVTPYueW%Qz6&9l&%yN&&Xohaq`%T)FCRI6sFAzU+B-!oL#!Bk=i9 zKet|FIm)D*4~l;PzQ4Se9j3f&cXy+_V^QATD2wY~TM_QK+m0*eJ&b*egtHKS7`n5s z6(Rh*gy$hl8&khDl<#fex*rV#=AX+(0Q(2{bp4M8_LUD~|DfFUu)%9^pTcoo`i?ev z7xsYb3CD+guFbOCR~%Q*;OoS1d_4A(;N^MEc(^Ozu7%@y3eRnLmU2GaWpLBrX2Dg% zag4nU?jE@3;a-OO3XU&#c}8$59QXUla6Av00mt)?jc`2g;CTVhmv|oZJz{iDj?MdP*>nAY>|&|H_jjY&t9Zm=jW6C zs^(pXz3dJj-#Xa`YQDzA#7+)>NA^RGroRJxoqc>|vX0k$2a$HPk1s{mpPFwa($4bn zt;71%{}dwaJwCqYuzvGDJCJsZk8howyDIvaM=>rb zdf%U7`vazL^_b@!ui`s`K1R`fL2G$4*4cFmS(tv7kG=|YKb8J5(CLc)5%jr=?(w(~ z@F=e*=yoc;-;?qBUIMy{imw8FwxXX0-AB>;eRR90JnuXee;MeWie3V`ucBW9t@Zs0 zbef7k?`gbpQS@BUI{o9Id#U)3K_@BtxMwhKDmodoj-Lv;gNo02Hom+|KzCR1&w|$F z`5d&i$H~vd+b`8e4|vP2gD8^qF&K0cC4U5HuG9E1{u0n!d+{MX(MRWj<{F0&&*Fo$0eG|0K?;T(I-Jlz)^q>3a{h*tv_=BLiX5>Tt{{vdL zmj+m40yYohn}gQ%ag2{{1^Nud-_J*n2i+d=b1)7sh5KOy`sYZrTR8Istd|I5xtaGd zZ&(|&flu4Ch0ii{gwL{fgU>Sb75_Z=EW=>1_P z68Nl(8Sq(0*T8QDzZ(9r@N3{72mcoM$HTu9KI{HI_-uoJgnt73r{SLn|3&!hI~(D% zjlTh(bIIS~p9=q<@Y$9>gMS+QZ{V}7{sf<6@^A3n!p!SKgjr{NzLGF`zLzk0ev>fuY4WCR6XaUq8`yUEI6gPWSk(ScyOe|X2DsbdsB=d|F>U7#Sd$`e>NG;>U{iYDpTWNo zj?YmWpfjA|g+CFFdai~47#!u1$Hg;_>G_a{@wDFogxmcE<0%|C#s{H!N>8Z9-Pa!BNO=^*5#exT@SYzjyC8F z9;RK3yhF&FG8QQvoQS>PXB;1w2iu_k`5t_J8?cZ1AL{Zec-y^#x`d-HY^O~7V8?pi z%1>;5_rUi*W5Mt2;o5E@U6{vs@LmJA7|!Lva`fwj{p}|g0(NPCF+H?Zs>xb_w zo&B5~>f(R+PLu7=$#Y}&UY|VLc_?@%!4<)=Y;1o_+wn}Ck)TY}U_X5SQw@HnowJ|Q zN%P(WKI*~8@g*A9Sa0<$TeO`f>N6`LkNPohu6qJ-)Y<>s2|m}iT)lP&(>)k-^g|mw z0N%}Te}|(DlEA>U-dXj$Lt=wgpXyXL{CnHlkC5wZaEyp?I%%nF2lWAv*R=uGufH8o z=R@EwVT z=b-aI(Ro+`oo@g?YmLs?FHu{&6MRl**WVJ^`9bheXDu&Ln|vC44Salw`rGT^GwlVu zF5g6Lm+yvLywg8`H2#OS`UJd3y^8X}xqivC#a-%omgQTd{0~;9UfFGn{J&OuMpcJ#U_r?S=$pdj|Y08y}}L z?O2R?M?bHCFHzYJ`uKPUjAbJ)<*{7Uc@xGy{rnyBsGs9==f=u#Bj4RV>}z1Fd>HTg{_Ml%p|Jiu zcwZOCgK>FuN5N8n;fdy33~U|%t;>F37Xa(z9Tny4dNlSEKCBwpMqk*>P+XJBqceQI5JKYJSZbp7()^+X@W``46*2b$Z5 z_prwzsMGR3c5fRU<>S5V0v{jmXZ!hhPn*9_)%u;@!}HGc$xBOsT?y=Y@aeQ?Kw*Dc z-v3_V(`7d>{>D_Nz(Gqc)#}* zpIvy*m%qc*@_663pAX}`-)25+`f%Lg_T|CzOB9a}V(i1SOSVaE=b6AZ0Mq%NI|}b+ zeC>WtHpT;AzA59-KYZAP@#xe3wCEiC&7;oa+FYC~`uHXUF_+<>P4g9$;oZEiY^mjV zhwdxKdSJ!Aa&)e+XD)!FHkdmDta z`uTXbvf5W4ykqItnRfy?AK{7Go_7OR1JLE*9YKFu-lMzK$H)70{_^tP*{eQ2-aqrp z6^^u3@ zrviHo?pEN>!^b$?;!*gt-;LngZ_RA@7J)7h{h4ka+?C+F8u&cPhj_8zm&3mi@q_Al zYq@5{W8}=mc=-q5mmd|g>#v9(i~SO;%f_fmf|}B^dhkX z(=}-5t=aUbt?zZ94;vI{51RgK;)j8wn>4(tiMQsT zQjX8C*9tX3U4m~K@Du-N^&=hx-CEHTK_9E=v7nDp^hnUn6rB#bfuhd`{U4R@*`WWe z=pLYdRCFiM-zoYu&|fRM4d^cw-2(Jyif#z{V@3ba*jw{~qWiYMx*X;137>Xp5C0a> zC&OnyZP5blUE1S&uu&tye{GI-sqi10TmCPB&sX@TpqEPfW%_@BkNLa{e8wNGorym! z_#c7iEBpq*7Xcrm@acjV0lz@uBLq(ao}}=z1aAktlfs(|-T?S13jej4wZoV2Z-X7~ zhd&GcUGS&CUjm==&n%(G!l&I*;gg>)j%ULEzNxpi?I*Eve(Iy&YHHKH2H%W}P2=tT zCyBpb;#c_MXSA^Pyaav-`K=Io4<_0o(A!#iYZgek9!K0?LEo?F4WQHhWc6eGvy$!s zU%ESe>23mjoJw~+(y<*?z-Ku|Abyp!1Iq0tcsta|!H4+pa|G`S{AR`99=K`W65j&xu6*M-KOp|G;vXe`VH+zaSNvr0 zdy3yld|m)ye*0Qm|2^?v7yr-V-y;5O@$=!|1N~FsUk(2R!5fOd`*n-yRp6H#VS5p980!4~xG@{1M_OiGPgvUma`tUlo6q z_&13^UHn4vv&2slzdroCp~pew&3r!=e{(Bu4bQ3ZtfpRLPq6X5#BX+jw?>X9SI>uS zd9jA+KN35?B>svv82{}trs2B?`w{Y;A$~pacS`!_#a}6Y8qUwi-(GwkR+HW->7Ek* zCh_k+)y5Z#KT!N*#NUYvK1~0R_&154FaB`x+l&7l7VzZXA^xr6mx$j={6nJG2jbrY zpY?mQ(A!V5a@LE#Li`f(dx_s%{14k&{#)VS3^^h3bJ}`q)=0hVK$*^OXUDl7?YuRY z3VskW&H(MkInpPCo~`1)IbHM-e}(u%#qT73R>1Q068~87KSRD#q1Q{0J5Jp8|Z0`1mwq{nx#`X49$YWUnI+zNjU{PzTJaR%P$f&P!sKb&RPPw&BJ`A-+W znfL`~VoeSH5%3v*n)p0;B>iS5>p$Po`rko5@jrL**3OarYp@IMfWUG5UE9T5^Pr5g z#P1UPM&N4|z7+J$vi>07eBqk~-0gFTmxI1R@fG^=ZQa%8+n}qr<|&c?F4Fxb^1em- zbqfDN@SVUPRrq_LS4uuiKUCxPnc$xSZz(wbtZP&*@q;2~KjgS_5dR#sD+lRZNk0tfw}{+y zV5ju3{j+HgZ_VY8d3;vS298VUYi?HbPu;yWr>%>{ALwq=?FO#Xy$@Qa+X~u#=*G%@ z8uXk1T8`xN8028@+~R@mxHi)>CjSoLY)4_>JQH{PS0Vj2(Vu#Tgs%|zI|`q~e8k@5 zn*hEN$jgB|p1V7FqkvBXJ_I<==pCK{ob9_m@Ojd{DW@Ie&R6;$1A3mK8~gZ=f*h_( zenDE6=Ns7Ht!F52ui(2>y%OIl_>@08J9;zmUxDvd={Er%jdI;D@wXzr`N_85QO;7( zZ53S&dY4K+7qn~VW3W00=b3wcIPrgfpZ={c}KX9J&bpbv>n311%Y{R+QC@QZ-&Rrq$4pY^i^yjhwz;Y+*A1RpdXZW$aF10uT^wIN%wEq&8-8-_dRI04j?@S z_H%X}1>3C<`z-{%U&fgtVAp|m;|%Gmg|8U6vjg#+g1-TLm!$tAuvb-j(k~0&I+dRI z8Ry!1I`Q0Cd-@)DGtm6b6z5~oNBQVO=h$=y&WVjjLjXvqJDH;7LmUEYMnhA?USAey)$cMC6PFuH_FEyf5(XN`7b1TK-9( z?@{tw`sgMi=Rar1%m4XoYqw81Fhx13VM~2zurebBXS-FuJvCl_zl22D*4xd z*78e0uT=7bJ~~(AWCPdoM+!awctFYT23pHM6*Rw(=GxD3KDxQc!H*H+-q=ZJ@RMO`!QrHYfiDAN{n*c?h`H|6akD13yX0pAA~eFY(cZl5Py})+*gFAKzf` z@jR{A>B5M!rJOk4pWLo+NzZe0(E8yK#hkLxt}= zA0Pf0Npw6QUw7dP`1no)?fNzOjupO!KE6Y+pX;CG`w5Qgg0FzcW#_DsHulC8WB$+7d^|3H`h11$dG0B=o-v_seT?yK)pDC{ZwASx(NtXp&>o)?l)^7-Cw~nTqvq3La`E&!V^XUXy z=hIH|IUcyqrv+%8Pkr!R3wwQwdT{5%%x@3qIV!(@fY$kK1FiFW3u$QgSAgsM)`Qmh zJuZCr0oV4uQ}AWL)0CaJUts0FaRKrdd^p<1tAeirK3?HB30?)4_xQ_AGBYc?&I-fGoI-e^g-Q~b_KI1{_eAZuR z^;vgetUkU&*&h~sKJY8VPQ;4@p9tL9nfS$^pA!3#9tl3`e}V9yiTGWjKjYhicKVY( z0kqSfbSp{M0Jzhi_;08Wr$6a^DOUfFQeyhQkF*~M{%7EYqCfFR1iu@&)1UZlpq~@{ zN!JM9Y~TedeNgbRz+Jl_eO7cJ)vCFL0FmG;mk{#2*9g%1?SV z=;li93eZ~aA3$rlVUc?+a4mN>Xe~DgzIv#?al(HA;txsvG2ecmbv`{o>wG#(y0*Y| zJ|}?I`D{(K<=l`OtH0T>{htM20=!J>kN7;nL%?1A5ibF~LF$k6rNVa+a94lC(?Gj= zA>C8>PWSPh0xFd?Ro-ZxVk|@HN2aEB;#ruLkbwpZxPcyZR>`623y<&Mw4rL2nhikj@mo^MTJ& z`J4-Si{k4nd~JYh{p$<zQw?`+$zCm0@rpa6?{1G zMJm4w1Wy94%i9(7JE|O~2wzj+TCWD6wO&8NHf-k~u^p&-d{4C#+V5@EPDsBl>0Six z+6nPzLA!QBdQyfx_s`1k)+~F5!>6}81#M>-=*^100Cdu`vG~)Gj_vpu;B!PTrrSTv z>ifa4m>r4#UGUAoogIn)Mev2dZxA~YzfSP!z+L+yJ{7cUU!;c$-+92Dy@{UWlbD(5}8ry5UyeONPhn7(!iS3w}26l~NAkJwV^A=+lMoIN;7+ zCwyOD6st$#A6;bge-*gvXT+Znd<}4CAL6S(JNuBX621!Hu3m{x1MTXS zbe`~y@$n7!@uh%%RP4-j=YV#0CY=P@*_m`#k$XCDXJ_Ikfp&H#-4ygHCD#M3<^F

S{@Mj>O zV@7&w^48n&mw!0yDA29t+>P`Y(0fjUW*EN*Ak9!Xo>P{<=lNZyQJ9u%6DDEb!AEuW9Y_eDC|aTa*# z?;mUB>>BH>87$>ol8yZ>XeWpC7SOl9VC9hBfOJfs2A}t9#*ephhK)!27QRvA@Y__- zP7djQpq(7j-6Y+qz{iT7#EPlJhgH7@14 z5_$bHA*K)M1E8Hgq$f|n9EkFq0H1O)F0=WpzSNd;1bpIi!At+@T$|6-+*rAnAn!cT zuH2+A2JP}8JxbDD0DPL1n|ME8x~`yIxf%cZ1I#orE$dK??HdejTzoJ`7l1M>K} zAf^ZDuR%LKNdE)0YnP<|=A(bgxAO1F_tx;Ydw5opugfHBkK6Mk9vJm#P-gY`p)_WX zY0&K}(9RyDcY}6%kiHJ|nm=3lq=Tha&RF0z(w>M9_sJRLlhYUU1|_FC40`TQhPt@aa(7HSifPP5D-wOIHC1)1sqZPe) zs?C2o{2QQ85dK2=6Q_D>K2&mEEwb^?!)N^CMc$e(ls#^c`1uk)2l3CV_&w0|dZfDz z?ZVYF^}n#(maA8}x8|>+S2^mg6KGd1(x-xU?T_@=)2-fx;@?;nlgs$$MDBw~w@>6| z0=rAeC4DpK7NRHVMR3%!u-seA-&V4n5${-G^=w^%b4cO;EAXbEot~sU(77u9S)}9r z%|+s$QfcYWLe>w?uzr8>i)Y(%4HLgPe72Jnv#kI2OdJ2W_*Y(S>CfkQYjfJ8g+bq& z=iq$GM{o7fFZ<{xeDr-jdWDbfKiB5l3qJ1+wVUg$sgVBuEAnpx`gTP(1HI}+s^_g{ zd_BPEDDP*5e*|pwH5MNVpZT174es&1Wb-*5SP#(74y4Zj?d(AMWYEqI zq;G-ax?&RW@v=V41vcbbj3)tGzmI}Wxz_Ua2EIeiqltF{yxAi zkMz@`-$USY@7ajo1KO3J^zFj8%*VF?v@0+9t`)utAKw(vuDs;i589RMOVIV_#p1nr zxW@_F$vqKtZ_v)pJwXox?et3r{Tk>kVy8)txpItx>_R0c2>LhBJEWY~gFYzve@R^0FY(==w<*5upx;sS-z43uzI5wB zZ&iHvf{*sP8Mw0-<%eO1Lt-z|71vw7g6m`UY76YL>tlSRv*2ilA>ea%Abvh*S1!`$ z3ST!LUpvsQ9OPRJ+UZ*j`W_X3C+KHEJ3BlD`fboIpRJ%ztHQoX%DWeIlPX(|U%=$Tu5~_Pr9g%a3?oRm^Uro6Wa; zhroA8>iKQp-zz@SU%@e-55ec`OZ;8XP9M_k=f~*NA-g|lr~lcY|GWVCi2mb1KefQh zT>|_b8BdAN7kn1*@uC;;a`H+0Af3O!Tl?bc9-q~u$NA{CZ4|zze0-}x?^k>`gWjv?nMlX_ zD+KQ9kMeWpdutopZ{(Zuj;ywP7gWdUZzsxhc6E%8ba(Jk-vIbr{SiNr=_Fs$#|mFV zAKxL^$CZnGKfzJnSHQKr4?#OSkuM)|9DN03Uj^Egb0+9pKs!6!2zni8m(L$TZwBq^ zVH4<&Ks$YRfj)hq)#Km5-MQLNz@J@+dwR0oIvYHX3*Q>xuD*!h2D*XbyGi&K`uMH^ z?J2%l!dL9$8wZ^AHWaw4H|BG}LT_zzsW;L?7g@gki(>Wm9`Ig^Vtk~}1Rw2u3izCz zi60Bvm5+2Y;fo+&&G!>%S3dF`fTO%UKE55GUHQm25%L{=6Rs!3u_(;D6M}1xapR*_N=RrHYNk1uk z5BT`*0PXZ9U&AFa`luy%{|(y7IT>_c(9S+dpnm|}UCKQM^j^@N75x$Dj*9*OJkYycc#B|iMO`3 z*q!vku;rT-j@i8pu+nghkMv|Xmg7?JIlB`d1KO3B^l;(3(8qTUXjfkHB?(_gAKyu! zUHQm&r=o8M{S;_tkHP8%KDI zeclC~qw?7*d@lm;ugZI$;LCxxRrxIj?Wy!t!Z#K8AyuxRk1tpFGJJfgKE5-B@8DhW z<@x7bwmh4G>wNwKTI=(e@GS(c`}cK%mjWNH^q(U5Wx(gDabhg!=BnJoh3`DzhuW(4 z23q&CPQrIGaNYin7rYVhVJiQ6g8%DI>=%{&zqr$ucPDV&4&M{}b>LdgM!}!*$+=(f z6+V2q;B$eeDSb+Sv)!Eue3vR`0Q7!EH<$FgR>jxTwpCW|Ex=Qh+;xKA4t%SUQ%zcx zd!F!>0Pm>Mj}*Kea6O)Tg8y(wJpbo+Nc;EU?+U&ixNb+!fY$BkLE*at_)66-ZV`Mj z@cv4_D#54u`$v;-`hQPHxk=t!KzrQ_RpRWbq4t%|` z_ZGpQ06szC4}#Y9utxZ91+MGidclio@%8!f zN~`zZfb05vL-3b@Yx$1}z8bjJ=MKS_`}h|NUIBcmD(5uNx|~7b8wb3*vePiZ2Ljjm zog;Wx;99?qg17O>X(afsE8_WoTw%-qIdGlt4#8jd;Tr{i61eV%e*~?|^MLTJ06s^R zy9%^!uXBZOI`Dp~+*1Y51+MkV7JMl1B*i~S@IF5N?t-5Hyjbbc3bfYaDB=75R;*8z z+^=u7_Sp_xm-la=b$MS9z8c_qoC^tF2t1(VW&>xxIaT7nxFtT_N4MB~{|;RDn->Is z1o#M5o|{1H^3(|5b-+(j`Cct}DezVb&lmh+;H?!tM)36ir@HTti|nk=osC3EBneTX z)Sylb8lq$+kyf%2WwY#tRkAxSY$TO9u*~d&8DN|l64s(pgNlj0$0(9QTqrGGym{t@Y4hi>lA55hM4_pcRq zFZ2y#e)vAvX8*oNah=dtj5u#X+^vc`O5KQiLqgp3in|8-j?v#&NxvNWmZ4uJ{e0*% zM!rvdJieY!e%$V-pF`g^(my8syU@*i=cP|WpD^+rPssQ4iW`7FV#K{WA@1#pYlOaL zlz)r#BhXh3y-IpH^i4zGFa4#^mkj++V2_*oi{hUC7`C4g_q&hT{jd(*O#dI!e~=Kr zApIK&@efG954w3?dav}sg!p$$4<^LFUHaP+;!j9F0^RJdD(Pj=&Hj3&^p`+iG1^-w z{ep!2p9S0OuO|n!e+*(>$VmT~^d;zKf6c;fG`9EOEADFv>Bpu2RYLl(bRYCZqrA6C zKL&lk$iEu4`MZ0);`T%DG2%)R;$Eh>Jm>)Z4=1GmxZ>`DK5LYJC+t$A-hS9chJBOLy%u_&pQY;e85JdVL*str0)=QM-SKp_}D@ zT>4$m&GNfpo8=u>+*Q!c`Nh9|#LoA-ABjJ2otHiX-CXy0K>B^qr;L8MSNbOs;@>Cz zY=Yhno%5I1%6}33X1&gr{#&f?nd=iP=t~~=7oeNl>szqR?KPvgN$3+sJ-;mdGtkZc zx(D{CG5&o7_Kxmn^dy+V2^bZ5PW`jxPq?MD0gipzoS^dI#<;2-<-akR(G_fhGK(9QUNlKyq* ztwuc_l>Q~?K12UF>_+`wSze#w+M#zFac87AKsW30N7S45r~X#>=`Uf89`^fzEx>kR zEzl49AkYWA9(WJ%Dqsls;&D6O^MK6nLg7#0XFgvATIr#mg8j(2>+@CGU)}}nA7ED+ z_Jgo14EuA4;r?_tbZ38Jy7!N}#)`B*(f*Z@ZUgqS?~mpCtow2O8n#pJOJGmK_NbgU zz@C6zYuI0aU1QjvMGUv+$Dl83zh=6A*wseddlh#_LR=Gcw&TsvopvyN&Hb)1x7tCw z?8|oC6<@}9pmKf;z3|JiINFyWj`evK;+l-OKOldn-i+JAKgO*?H{(`dJN0JV5#;08 zhmiMeu$_GV9Cie@(?9Qk{bATn|MkNz|BC&-zE=aeA6y~(g~E$~cz5&pGGD`1Zr{z}+gcEGtY=nb%E4Syr-i>zf-*SR_9 z-LO{;e-G?m$iD_1%MN3%U&qQHf&IbrY&!?~G;FuwpMlM9R_yb7) z%4gg8FF_xLKlcgS-he#@KfeuxKj-qG&%$3~_~+o~cY^3Ig}w@Zjp1K|pWh6kzZQA{ z-jCiRe-p6qJj}x`u>HNT%i$j}{1x!?TS81f3f%+$wBfIZzgPYl=mGeb41W;*QTdml z55T`=_y^(V_lB7NHuQ1$^M7O8tltFu{Pqz21<>c=FEjiLu=yP#`pcoO!}b{d4cPo9 z5&iYhbMgNApyAJh&F>S@AAw#1d(iNg!sfS%=pTY!1AEf&*TVki%WeM@^j6r5hCcxN zf4|uFFG25zy=nLdVDo!M_;YRx`Z(+@f~!uHov z&vA{l8vY#E{N@q;GUk@GcxgQG{{#@7| z`Ge4lVGkJo64>|2KM1`F_PF7%hRyFQG5-nZO|a(;zYq3-SK0mr=smF441X`|+vQ(} zJ_0-Ew{|?+KMLDag1!a2(eQ7>cHUpv z1U>)RuCZ>zUjUomY{H*&J& z^Iw5J2z%S`55eX)p6K6!J_)<To8+$s?!exv zwf#QW1sCA>W%vtW^E*~d-wVAA_ORhEzaaKp%m{Q3{L_ZN9)5n`is@&d2jO2f{1Mpv z))oCL&<9~}8~!1r=XbB@-+?{}f8if&oAsZ9&2M1QUj%&-c7@?zLi(QDY=0&6P52uO z{}$}`cx`_p^n83Lpxf{lT!{Iq-}d)FFN1&B@R!5y3)=n>=pOi|4SzlS{C*bxoST6j zfPcyG2Vu|1zYKi<_LkuvM0$Q#i|MzakHeq;lx?&96R`P>E&2l*7d{6+BdJ6!blK`)1Y#PCZ5Kgbgk53ymtga|VEA*c5_;}Mm|w|X z56pvI(`Wl5uuEVM8vat){9YK-4?%B&Jum+R&)xrW%xa?`3*9rpN8HCdqe(3U_Wd()`IBYggpj3@4sxD{_%@({meeY zI+qW94*pWZKM$MVDP#IF=xeZR4gWf9ezS~z5A>W%Fn==qxv+1#&-MqQSHd2XzaLn2 z37#+elI?Zi< z4Zjb5ejkng1zUGrif+n*K;)?tF;B=-5!ywEi4w62up>WN-=%8utErM;WWuA zm&kI7ESE_C3xF&iy=Un-EwtjE1!OslW4Y%8eTr)WGOiKGI39|azFxXVdad*t>DAJ^ zWk+NOfoN;aD3Ik13x|L#cOJ<6=YTAC7RYjEq)$tqlD-6FK8ry1%L0)3Yyp|iCXo4T z0GSWY&#m}eAmjRgjO)dJjOzh1u3LIUdQdvfx2<@bhg7~+3q%X-{l)V5fK>cT>&q$w^J|TTv`k3@#=|j>7rSAaQ zzgxmhAp5Q9JiGplK=xY$ko|`9d8=JG&$ski={3@;rB_L>l#X=*D<10yRz79YOQn}c zFP83>UL+ms5LUSb((|PUfUJKjko$)Z$odZo>rrVmDaQk3J8(X3Zy)K^(yOFbO0SS! zF1<{8sq_-*#nRo6SjVvR zHR-F;SEMgXUy{BkeL?!X^f~FX(r2VkOP`WHDLpsGt{(>?){i3(_s1<5toJ66`Ud`^ zzAAl1`m*!|>GRU(q)$tql0GSYO!}zw5$S`{2c-8)?~&duJtDnTx=(tObgWaj(8L_9 zUsxCrwhDd1CSjwnL0B*J2y2Bk!fIia5bG#bITgZkVVSU0SRyPIx`iV^?)O8&0bxIo z`#;t-to;M)9oBYn1JTr+BI$+F3#8{u&y$XI6DvMPIy2?=-9i4$XB)`l=MmI6Vk_}m!sb7r!pYN2d>rPZ%%^{ z>nT<{7J+QX0+8*Smp&(bRyx*WtoUi^Q_?4;Pe>n^J|=xsI@W)z{D-9vNgtFxAiZCD zpY&eoJ<_|SSD@0YcRBuJy~}|7-4#oBOD~e1FFj9suJj${yRCe;fXruI`kM4r=~&mX ze=ov-uvO?2HVGSr4Z?b%M>qns_GjUMupfvbb0&p5K<;W?w!hdY{rt}Tz>(bYx zuS#E$zASx7`l9sW=h*qWfy}oE$bQI|o+mw5dNq*aMwPHqSRo7m*)E^3QP==vyLyFK z$FbVA3Pe?MR)8#jS^ARnMd=ID=cUg{pOro%eOmgI^hxOx(#NHbNgtJ7jtM5~Q3hl^ zN`dSLxAY?Eh0^n+=St_qlkwZicT4$h0;&7pXTLQG8-)$RdZ9;HE36R?0NJm7!X9Ba zko}5vCJUE<++P-fY|jFa?U|Q8CmrifmcA}~P4+5~_074!uCGg23S{}k!XhEoo2+uH zgabg9-w$N@eL$AqE4@d0xAchgp!9(BR_Q+JP12`iV_nJ0cLK=zj!GYqJ}i9+$bQE< zlZ6YydEpL_?b;G<2-kt^*Zd1@#5#}NE}+#epw%wvJ<_|SN2FuD$co20k`>=7-6y?C zdZY9P=~#!d;yu!9rPoNWmR=>jQhJ4StaDlAmPyZfu5Dxe$jTS%N>;yZDsDq@>(W=G zFH2vNJ}-Sv`mFRR>66kYq>oA;kv=TF8uhTo5n-he>r7Sap_~yN2R-Qg3Eefoy)2R*1xRn#P6?C&y~&-UTeHj`Yom31XAbsUD>}i!fIia zuu@nd#JZT(Ujd;{*aT#M^$S;l?5`Cd>$41Ge_`Fr(y<<9=?l{5rO!#9l|CbVS~}L% ztbDNEX5}*>eO!7GHYUp}1hTvWAlr%cHA~Nt&PGt*Qu8sLLq%TWflD;T?LHfM( zIq9>~XQWR{pOQW)eM0)U^fBq9(jyn!^$r5rzx<9Y`wQQZwc67ty+JzG^(?(cdbRWl z>E+U~9%$uLEZr@=NP52XJn6a8r-0l}lfnt%xNuB3DjX3G3x|Y*!U18wa0Q5_=PU^q zg!4dd*G(X|=LV4b-8zu{;=;9g+B>rIU~?U+6v+HagvCJS(*R^X^+2Zc0GUs%^cv~a z(y=~h#bMphit7e)I|QV+O7}@02eRBT;izx~$nxiaEPocr@@If7AKyE+(k%dao-hxz z+AXZix6@SsnXVkjbY;>@rI$!AmhP5bB)w32f%JUo-LfOHgFvyK7>SeLZQYn1Mh zUMsyu`WTS=$*6EdI4m3z4hk25?5{cDjBpyr_OA-7(JAb=Dj?fm31s^#q?b$Q_i?G0 zN-vRKEZr@=NP4040_pkE^Q7lW&ymiJ!~BP256T_@vcGzz_ek%S9+2KD-6y?4dcAay z^f@5=aaK4ZoEA5+egtNjq;kquo3aCvH{U{v0S!`$aWFgE+X4OWV?t? zJKeI`F6wL-k?kU~UBtw8F<*|`EGLNiu-r~y4)AS2fR&EBW4VlDJ(!^#Yk+k8l*oxDg=ZhJlP50y4irAoH67GHw#cxCtQR#(~Ul zOgIl@+#HZ`vp~kp0GZ!3kom0u8Mh2%+!By+i$LbLAlw8pZUe};bs*!`fXr_d$ox3& zGLGXg<8~Cc4P<^>LXNwP<2cN?d?54VIL-VxZZp49Amd7aj4K8*j^i}**^{#8WzWi9mc1x@UG}Q%ZP}Z$bFrPQ z_5-c<%g&cwBHJyyLUx(#8rfB{8)SQAx5{pk-7PyPyI*#%>|xo1vd3kQ%AS@zDSKY_ ztn6jki?Y{cugcz*y(v2vjkns5|E%`Q&X-*x+bx^>8~)~SKVy82>?+xqJ6L}1cl6_* zWjD$0mW_SV^7qT`l|3wbQ1-a&QQ6b7CuPseo|U~Udr|hf>{Z#@vNvV(Iyz_$Cr7Q^ z|7G)fJM9wLZrK&G%VgKcu9Dp#+asITi_OS%vPWf4%bt`y zFMC$@vg}3K>#|p6Z_D14or_<-)qbGWe%bl5OJuucJJ*|eotg19@>j`jknNG(D!WN` zx9p(oe%ZaUhh-1S9+y2Tds_CS?0MO-TJ4veFS|sx zTXu!)GTAkm>VUw_4SR+I;U6&zW_D2r>vOkC(=}v!C!DjhRzf{QX74`_7 z{tn6>6!r^efy|%BPnJ6=90xN0G2y6iMCj~yJF+){%x440e3qm~&@Sq1H?b9HwNLGB z5IX(fkv%D#5RM5)g~P%j;efDTxG0<#I@@bnw$(V)XB#%_vngDc-`Vb~vR8zhU$GsW zS1}#;CnD!%bh(9`m(ea1^0-2KUN}Pof2V;=KPtUj7?7X)HT};1T!rzO=_-WfK>AC7 zOj0a#3whrdb>6o`tQYb=Ddxlbqli3CFdyC@#W)^Eh!aBRc*Elb^)e>H-x6Ulkm(D7 z^cM*8g*?%v9uQUwdEX8H@_rlsoy334XAH>rQQ?SiSlEPdlyS~D>(=lH@84it8Lsa${}LhZYhXFNpMm}YVZJa=I3pYq z^8N;<3&^e(=HWUw^Uo2wfQ;jP2F!m`$omaw^L_)O2iI>IR{^BW>)o{TWxHhaIz9co zK2JZd!_(&VciOzpPP<=rP=X6~yM;j^ zug5bVpRh@|CggQ@`gy&b$m{IHIU%pB)8_SbVjjkEo>!ueUD(A5E)0d%dKj@pHjhKJ zJwl(bTR0#b6;27~g)2fHN0?6z{v#F&ONCWJ9xw3MI-U@FWDg2?yr9nUp12@f6>bT6 z+^3)8HL*;{c?0bRA;)9dy}}{kxRB#H{foji;kGaj<0kzamx&y=i8aDTVNlp792RmM zW<1Af;*yZ_c-lKcj<>Wq9uq5swZbN0MA$Fn_{w;Wzr;D=vT#Gloq+xVA?LxgD}|ij z()I~Cp3-*iKOB`kC7c(o2sec}826a2P*^Ig67oJj`gvae?m<1KMaI4xWdt_pd7 zALDW{t`Uob96xDud?hvr1HxY6kkGl$jrYYdeo@GAlQzdyA{Ts#ZXxf3qs{xyh`djY z7!>vihlLZuS)p_P8Sg7&x*Z|MQQ915i4{WZhOUbt@J|W;k#_+Pi}PIw?-J*G4?ZEz zcO3jsyche}%iv>LzPq3c$XC9PW8e6Icqz8ur^N%{4}vp(1N>p>eDA=cP7FAIi_C}b z8^}j_ycS?-#(e1S@_H?Yu+08Uq%8pUkHh{6X~{43(W27g$b&&2`gzzkb)?iJ^==3#L@SN_g1+gWw0mzXaYW{xCT2r)10u_&wsk z0srsf7rqYP))l`3{8!=?;JFvtX^(?*9>9D~gTFzX&vV}?&S$qD5a)B+zZB;)*;nJ( z%(PEcVQoU3&su*$oX=JNRQ%J`cy|&G;*7uUCKsREw97q;eMy|pK|dnSXP$o{&gYw- z62A?c_keM~<@f%s7Uwg}H-j^OKCj#^ozEt}Upk*d{)}`!bNmm|`F!!eOXsu1yr+x# z^SR*{U2JncBYXom^XK!xC#Cb*-* zfOGqP3jDb^29Q4jeli<6FZ;W596C@ z;vWb9lKB1L|3iEh{7La2fnWI|JMAyQkBgr_fcJ8VyTSh+oOR|izK=-f^SpVN+iCgi z?(4<*obDhv?IoKwf7;nXU!S4|t0spM{4ETNGE8t%h-vNJ6yy(~1uHqHo4~z5tTHg~7 zg8xW-0Q^z$3Gn|EUjYA=INy!+d-0qnFvos{UFTx(3&g9zFBRvzlr9(V2ES5#82nY@ zQ{b-=Uj%=h_$K%v@x0&Q8#v;n;C14);BOTVfVYSbfZr}Y0Uj1#0RIc|4e-0fd85LI z#Y@5O5w8aSthf*SKJh;AuZoX?KPb+3Gd(1}1pZIreCN*h#q*xT_fW-4!G9`V3;uKQ zR`6eo_k;gld<=ZwRd%1xfnOlL3Vx}$>$muhr+5+g72*}(SBp1*Un?E~zg~O@{Gj+0 z_)+mC@HdNZf!``#@H<>L7cT>E6X!cF-!5w8aS8*v}_*Twt5zb!rn{)qS-_`is+f&WB2=l3|CiWh@FF3xwTJSpA?{9`L)xhrvH3J_UZSIN!B$zxX=%1LC~V=kLUe z!M`b94gQFDBly3F_kjOIobNaJnfMg=FT|I?pA_E$e_FiYY4rKkcAuAnUnE`+{zCBp z_)EnHz^@YLyGX7Tp98;Md=>nlxa*JD&&By}k~;AU@VAOLfVYTugSU$hf_I5eg1<|g z?-2Qb_&WIA;<^8YeiknQzgN5l{7d4k;D0UN5B_)Jqu}2Zp9TMp_$v7K#rbZJpNJP= z1>`YtzSH9u;#D!YwWc7SXjMAd=~sI;=SkZb9IZaf)9)5K6{_*-^7Q&pS$1AXA=B+ z@n!Ja#jBpP&vma81OCtAtKeJWxw-pXSH0TKrwsgU;;rDH5+4TtC-F(}--<7T7nj*- zT^HB0MIy>zw zc$N4z_#NUU&)w(xf_N49_rzPl&wGuXb`bn}@on&scy}INm?=I9{(bRf@bk*mUM$kR zR@?*LAwCQ~B0dfN9r0E0--+j9(tY)x+WFLi-zGi?-Y-50{@=ux!G9|5x@4cL;Lq%| zt>8z+*TK8Q-TC`mqvAE-KM-#O&wH(%HV9rN-UI#)@iFi(iqC=nyZ9FPg%x(%qD!%@ z#mm5l#B0DG77v2|L3|wi+UxDKo8VsY{O9d+4Tx8Oe_gx@{FmaB;8z~7)8^tGy2r)K zz~2e}l1nj%#k+aWi8q0NTf86qG4V<8i(Y5PFN0T#yI#1@TK8c-CE!ESJ>awA-Qd3x z9|bR}wDXwA;BOIM1plD;68PVWFN6Q5_zHOOjdogoEBRLOHSoK|`Ayt^6yE@UN}S)qEjwhV z<#%-3#J9oEiSK~_P~3IJK3uG@<8#2v#dE>i#Q9y)A@O|hhs62)(O-!3+nDadc0T+L z=9|U&4a`3AV(@Q>mw<1H^Sh-lJ7TBhyVh?J=lj|}D$aLz&x!NB-cN}0-P>2*WT)kO zrt8J|j_D7I^Zn8fiSymkJKz^zhVyvv;u@p{?+3q0d=C6>@fGlY72g1V-cdVk$;%KA z&a|W8UE(X?-xANe62IR!+A*cz$HiO0L*T5#82J6t1I7DXKbAfYe%YJswAT04Fn{Pp0BFMh>7S5W#K_-CZMuG;7N7wPlhIXBz!IVCv0fU{ja z;Af;yga4KEva7KzqzAxvq&L12bAw}cKEvRCaOP9-s(r3cN{@igN}nplZ&p0_ntiSp z9Y_2NMqO3lhrll`#2N|st>Qi4ZQ{M)e<{8R{t59d@cY0o!PpqVnE6fdIq(f}AI7~G zdhD11@G9|ojCE(kSD=4XycGK1idRB^RD25j{5RYA6o9{4d<12k6z2u#cZ+X89}}+y z|CV?$_|M2+h&dtZpK}5n<8401(pQMDVvM~}dv^b-2>t*BbnQ!QW``lLqfL_~#7%fWaR&_=3Tg4E_^?KW6Zs8~oP>|GmM_ zyCv3^OAKCY@B;>alfiE@xZmLI2JbTXyAA$+gO3{gYX<+8!RHOWXz<4jzHRX5z9rVK z%ME_D!EZ45n+<-(;6a1mY4Gogx33ws6EBQYJ?_Lv|4d{Gm{1 z=)i3*@9E}9bHE$63ih_zC8sMYA-A`*MOqIv2Lh*>TW&wl(%BL5cSoWbr><>@C)9RF zbHsnMKhoOi3t#OHb_Tlw5Dr`QL9IGEZ)px3@cF|np|)V8t+NA_e5QGIh7NW5{BfDJ zG`m=y7cBi=}>E)@Dmta!a5yoH}!~AhpA)b1XA8!2)mi zEcWeowC8kZnuzx1me#foR62b~Fx1)N4~H{_*jpz}5vSTZnnPz3yCtrkDLN(2Z~ywz z9QzRl|b#VYs|qZ5@$6EB9JPbdWpY4|ayIA8LGaOyg(nNbc6=u*cuwYwI|3 zh&!>ZrwR;oHv5tzt1-6iE&5=G?}R_n73#3Zqg085U7<7n6G-WCnkE@}Ej$K?3KfDzx-fr0Z(tMU6zK2}7x8pZ*u=FYJpY!2a0 zRML@vJ137Q2ZO;tTT3&C{-ZvBN2Kj^n?H1*IT*af9||WN4|dJ$#x5R8vnt=`NAGmD zr0C|PsyYWPWBXLMh5Rj%&d}MU{*NBK(pI)5wr@wX$)+0b=<;{@eW@yuO=_c5PjjR- zDT&+JAp8|Y59?m1?cR>CecViFpF3SYx~(G%W*`S~XvKh=yixdbzu6wJk7B$|t`Ua= zd$dmLKkOWTQb*WFjMP3ID^feL|3&x8ly%cEojSGsyGre|4=&;4)>{3QLXRFzQs$Sm zv-m?@SNoY% zx{lMqBb-(~lhpOr{PLNkJjyxSGf5ctYuRf**IDNbam9M^D~NQS>N@Rh@pg7ayq%{x z9l@yR_4!XXcLgwy)tt>5YonHCf3d{K;xuNe;kNc5<}LW~VHSYdM=YQnBM2Jp<#d?S z3%?J+@5B@`<;0}KXCrp^BTeCSw=ja6=)H^zF=Tth@S562{sv@CIP7Px|e@9n4 zHjaI^g*hhs^N!{~m*0EptXF3cSwzR>>1>;XZ0!w_D%(s6;&L`xVx1nU!tK`Cq*3GM zP}m>a6R-=O^79nUlI=MuPT-o)*b~#ZS!#6d>$Q%L$b^$sZ^G0$!I`uZIuq!GIBj;F zk)?3P?Kr8!qB~D&cl-{NFjB|H^60_+c#wM&CfK&`Kxe$|%(>-8Kj!f}p6Enp{J3T2V z;dmF_w5A?6k#;6B&c@v}$8>#amVhIz8{@Q-cWmmuYwkv>bp0DUG)7xzOUJtS(>`FW z!&nF2@B!<0>-QaS7OPTBG>z=iHO&Z#SjT0%q@>0eaYiLFg{gj-LZVeq3W}d$L_6bX z2S)a;7M?SCI&sd8F4;Y4Vv?j3C$xCTLwvL-M?8jEPPC7)WY?AAEX#?tGDz3caX}#! z$=oy7$BDFxP71xzT9k>-i=wsXdSLz8p#Q+B=CI#65#hw)0FQMCy1LqY$vu~tS-O@a z#&O=w!gjA6=WjR0YLs1qI#fHm$dtXASX-Q2S^ahLHgBdsNkK(;SXp z*tO4%qfv2tTvRpF8b7w=4Hiw2amR~~@;fJvDLX4o>`lGI!A(MDCRb!U5GVXv)e z!3Dsb1yd9q)f2rP*gIp(;C64t=HJn7wk4~dnBM^``lLS9Hqxfsn~e~bp43Rg&$2T4 zRgI(&PwZqq+5~A<8!%7h((KXd17cjLIDly!mWATtOa;*$n9CEdXv2))#Fqj>oV=l5o=X862f%Q8df15 zimgo@xD%^$u~_`E0kNih}2ZI*So13M5f`G*_}Wd^6MI@)!P zo~$M;E61fsTGOy|h#f=^Sl57#cKH0=IF@my&sN3%2(4upZvdCk5>5i_->Bh^zlM?I z#yrP9i;eq@8|m%k*bL?K0t>G}p|3JTS=WmaX8zj0?6sKKmcYR&+W8S47QA(JJ4Jp(bDs4q9H)Pmovqx;i2UaJ36-$bo3HZ8c*F>Bd2` z7VS>y8FPx(g#%4?kyiaOhNf(hv9pHHhFc=IkZb*-Vta>V%n1{JoQcPO=jV9{ZRD&a z;}mIstG_$`+Hp8mX2PY${pt_=d`@?I+uKgXA`?%6?eQLG^-RHe7YWvBy|bNPWH?-PT?&R#I>-;@zc^*dWd|ORqP2Ch9DU zW!KPM^2n~qahaHBp}?Y}jn1wNdm^FBl6!1jeA?{Fi_Z4Fdo0XI zm|aO>`_7s@7UQJKu8g<=c8?{+r_HWBt~EP%RqV9`)arPp&2J6E$ zUTgK}TGwe@>E7>e-7{vN!|sv0M?BM7?v^h5l9MBomEB7FzwQ5jU&H?&Yp{Er(?_KD zq}%rCQ>6Ded8LndCLSl(L`yx4oF_F!F|_vXX(HOH&#oM?NoMt6Zav4}ZnN1r$z%Q$6dvG?e) zy84614js-aXCwTUgC~w2ys`Fh=Bi{}jY9|P58rhB#I4!Yhpmdc;nZn5y2;5X)vdRg z(xvKNdt=}lqqaioo=uE0dMROZWr$4b&&1f|u1t(D8lP^fI!Q8hL_!Ddnj}-FI32fJ zm!ybG(;=}){B}Iv+M{*#&X2*asS}W{zZ`$oy|w4QN}C{4Pt`^5$B9)i^H$85B2Dk5 zN|UA^lS9+>XmVtxh8{F7CSu_r+t%)$EKQTsCQQ@rH1X*gpC&w055%r5X509TDbln% zRhl$SP7Y1i+T_Sg4UH|xa>Xp$#_pObO@q^=OVjAo;prNlIyzJ1PZ(EOvu*k=Y0@-2 zb*ePYP8FQ4(WzoHH92AFG22$}o-9qn(e3)7<3HbZt$J%+yd%?9yh6dLyMdJGUn! znUkbxYRW`u+L|IZU1L*(ra7~)>mGO6E!JRE#6E4wJVQx3i?OTaq^{#c$$mgCLur|| zMw;qnuGMad(rl5G$8P8Y6dBgU# zRKppzueNmHjvZVlmc`qXU|IX*#6jseOt`X z>bO+l-M4#uymfndmbshPzp_jcz3Dtde8P~tpHJ%~-v#~*GuXX9Vnh6XJ)wWhx(_2m zfo76$^pUMhJAs)(_ityJe@JKdnR1Um_Uy;)4T-mAXDG}(6z#{|VfOnTc!$)U6Iu8C zWk{89B-`&bZlc~xGV3?TyO;NzXqRRsRZs(pW`gO5o}`54&Cq+UCGU&QkSOj5zh4ho zr9GG=MC0~9+;NQihi02VyV`-aBl2d<{rqp2d+=h9 zSfXt<@#so3$R?b+`^jkAI zZ7fyVcG)KaDevKorA}XF%H*+xyB6zA%HnU{jAgKU$%#3{((YP#_M6dZW)_*BGTwRQ z+yEF$mcBjl>0&9;wjz4&wU<(iL|GJJpG@v0eo)e;d+f4-x6XW3-XZ%{bXfnfR6Cff9@=nv%bi!R ztaI51y6D@A_FtQ%T&JVFy{H;pCbe_Uz(4w5M}?wlY~pfPXB6y}Ldy|W05 zn=Pg65%ZihQ--khiCS|#GiAz^0qvGbwnF@}b^=FETE~h0FW;ZUnh8}nI zrKU3+IcU66!4-dfi|*s`FJefD@Q17ylvQ`OH@D$zFfMcWoE0mV_gLK#y!p-38E)(L z_(Sb&VZK5O1N5=F+D<$mg_Ce!b?jY>Z}Q{qAavpRvSW3g<8?tNzSLf%?mgh|P z^*M`%fk6JO42Y$3EGC zSF1t6UUic{a=6>yVt1T<^6m8=I)XtPYZV<4yBUW9&EfC~Ki&a^C%I62uNSr-FJcKr zu)BM`e(pFV;f^34A&uaLL#@mnkLh6F3gNArUf)@~{wF$d;$5}g(|EEB3lYw{8qy>$(EkOHCm%acOKJcWR^?J+G zde0)>7ZYs56$E0GggW)c&f5SV3; + * + * Copyright (c) 2008-2009 Plausible Labs Cooperative, Inc. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +#ifdef __APPLE__ +#import +#endif + +#import "PLCrashReporter.h" +#import "PLCrashReport.h" +#import "PLCrashReportTextFormatter.h" + +/** + * @defgroup functions Crash Reporter Functions Reference + */ + +/** + * @defgroup types Crash Reporter Data Types Reference + */ + +/** + * @defgroup constants Crash Reporter Constants Reference + */ + +/** + * @internal + * @defgroup plcrash_internal Crash Reporter Internal Documentation + */ + +/** + * @defgroup enums Enumerations + * @ingroup constants + */ + +/** + * @defgroup globals Global Variables + * @ingroup constants + */ + +/** + * @defgroup exceptions Exceptions + * @ingroup constants + */ + +/* Exceptions */ +extern NSString *PLCrashReporterException; + +/* Error Domain and Codes */ +extern NSString *PLCrashReporterErrorDomain; + +/** + * NSError codes in the Plausible Crash Reporter error domain. + * @ingroup enums + */ +typedef enum { + /** An unknown error has occured. If this + * code is received, it is a bug, and should be reported. */ + PLCrashReporterErrorUnknown = 0, + + /** An Mach or POSIX operating system error has occured. The underlying NSError cause may be fetched from the userInfo + * dictionary using the NSUnderlyingErrorKey key. */ + PLCrashReporterErrorOperatingSystem = 1, + + /** The crash report log file is corrupt or invalid */ + PLCrashReporterErrorCrashReportInvalid = 2, +} PLCrashReporterError; + + +/* Library Imports */ +#import "PLCrashReporter.h" +#import "PLCrashReport.h" +#import "PLCrashReportTextFormatter.h" + +/** + * @mainpage Plausible Crash Reporter + * + * @section intro_sec Introduction + * + * Plausile CrashReporter implements in-process crash reporting on the iPhone and Mac OS X. + * + * The following features are supported: + * + * - Implemented as an in-process signal handler. + * - Does not interfer with debugging in gdb.. + * - Handles both uncaught Objective-C exceptions and fatal signals (SIGSEGV, SIGBUS, etc). + * - Full thread state for all active threads (backtraces, register dumps) is provided. + * + * If your application crashes, a crash report will be written. When the application is next run, you may check for a + * pending crash report, and submit the report to your own HTTP server, send an e-mail, or even introspect the + * report locally. + * + * @section intro_encoding Crash Report Format + * + * Crash logs are encoded using google protobuf, and may be decoded + * using the provided PLCrashReport API. Additionally, the include plcrashutil handles conversion of binary crash reports to the + * symbolicate-compatible iPhone text format. + * + * @section doc_sections Documentation Sections + * - @subpage example_usage_iphone + * - @subpage error_handling + * - @subpage async_safety + */ + +/** + * @page example_usage_iphone Example iPhone Usage + * + * @code + * // + * // Called to handle a pending crash report. + * // + * - (void) handleCrashReport { + * PLCrashReporter *crashReporter = [PLCrashReporter sharedReporter]; + * NSData *crashData; + * NSError *error; + * + * // Try loading the crash report + * crashData = [crashReporter loadPendingCrashReportDataAndReturnError: &error]; + * if (crashData == nil) { + * NSLog(@"Could not load crash report: %@", error); + * goto finish; + * } + * + * // We could send the report from here, but we'll just print out + * // some debugging info instead + * PLCrashReport *report = [[[PLCrashReport alloc] initWithData: crashData error: &error] autorelease]; + * if (report == nil) { + * NSLog(@"Could not parse crash report"); + * goto finish; + * } + * + * NSLog(@"Crashed on %@", report.systemInfo.timestamp); + * NSLog(@"Crashed with signal %@ (code %@, address=0x%" PRIx64 ")", report.signalInfo.name, + * report.signalInfo.code, report.signalInfo.address); + * + * // Purge the report + * finish: + * [crashReporter purgePendingCrashReport]; + * return; + * } + * + * // from UIApplicationDelegate protocol + * - (void) applicationDidFinishLaunching: (UIApplication *) application { + * PLCrashReporter *crashReporter = [PLCrashReporter sharedReporter]; + * NSError *error; + * + * // Check if we previously crashed + * if ([crashReporter hasPendingCrashReport]) + * [self handleCrashReport]; + + * // Enable the Crash Reporter + * if (![crashReporter enableCrashReporterAndReturnError: &error]) + * NSLog(@"Warning: Could not enable crash reporter: %@", error); + * + * } + * @endcode + * + */ + +/** + * @page error_handling Error Handling Programming Guide + * + * Where a method may return an error, Plausible Crash Reporter provides access to the underlying + * cause via an optional NSError argument. + * + * All returned errors will be a member of one of the below defined domains, however, new domains and + * error codes may be added at any time. If you do not wish to report on the error cause, many methods + * support a simple form that requires no NSError argument. + * + * @section error_domains Error Domains, Codes, and User Info + * + * @subsection crashreporter_errors Crash Reporter Errors + * + * Any errors in Plausible Crash Reporter use the #PLCrashReporterErrorDomain error domain, and and one + * of the error codes defined in #PLCrashReporterError. + */ + +/** + * @page async_safety Async-Safe Programming Guide + * + * Plausible CrashReporter provides support for executing an application specified function in the context of the + * crash reporter's signal handler, after the crash report has been written to disk. This was a regularly requested + * feature, and provides the ability to implement application finalization in the event of a crash. However, writing + * code intended for execution inside of a signal handler is exceptionally difficult, and is not recommended. + * + * @section program_flow Program Flow and Signal Handlers + * + * When the signal handler is called the normal flow of the program is interrupted, and your program is an unknown + * state. Locks may be held, the heap may be corrupt (or in the process of being updated), and your signal + * handler may invoke a function that was being executed at the time of the signal. This may result in deadlocks, + * data corruption, and program termination. + * + * @section functions Async-Safe Functions + * + * A subset of functions are defined to be async-safe by the OS, and are safely callable from within a signal handler. If + * you do implement a custom post-crash handler, it must be async-safe. A table of POSIX-defined async-safe functions + * and additional information is available from the + * CERT programming guide - SIG30-C + * + * Most notably, the Objective-C runtime itself is not async-safe, and Objective-C may not be used within a signal + * handler. + * + * @sa PLCrashReporter::setCrashCallbacks: + */ \ No newline at end of file diff --git a/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReport.h b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReport.h new file mode 100644 index 0000000000..f229fd03f7 --- /dev/null +++ b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReport.h @@ -0,0 +1,169 @@ +/* + * Author: Landon Fuller + * + * Copyright (c) 2008-2010 Plausible Labs Cooperative, Inc. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#import +#import "PLCrashReportSystemInfo.h" +#import "PLCrashReportMachineInfo.h" +#import "PLCrashReportApplicationInfo.h" +#import "PLCrashReportProcessInfo.h" +#import "PLCrashReportSignalInfo.h" +#import "PLCrashReportThreadInfo.h" +#import "PLCrashReportBinaryImageInfo.h" +#import "PLCrashReportExceptionInfo.h" + +/** + * @ingroup constants + * Crash file magic identifier */ +#define PLCRASH_REPORT_FILE_MAGIC "plcrash" + +/** + * @ingroup constants + * Crash format version byte identifier. Will not change outside of the introduction of + * an entirely new crash log format. */ +#define PLCRASH_REPORT_FILE_VERSION 1 + +/** + * @ingroup types + * Crash log file header format. + * + * Crash log files start with 7 byte magic identifier (#PLCRASH_REPORT_FILE_MAGIC), + * followed by a single unsigned byte version number (#PLCRASH_REPORT_FILE_VERSION). + * The crash log message format itself is extensible, so this version number will only + * be incremented in the event of an incompatible encoding or format change. + */ +struct PLCrashReportFileHeader { + /** Crash log magic identifier, not NULL terminated */ + const char magic[7]; + + /** Crash log encoding/format version */ + const uint8_t version; + + /** File data */ + const uint8_t data[]; +} __attribute__((packed)); + + +/** + * @internal + * Private decoder instance variables (used to hide the underlying protobuf parser). + */ +typedef struct _PLCrashReportDecoder _PLCrashReportDecoder; + +@interface PLCrashReport : NSObject { +@private + /** Private implementation variables (used to hide the underlying protobuf parser) */ + _PLCrashReportDecoder *_decoder; + + /** System info */ + PLCrashReportSystemInfo *_systemInfo; + + /** Machine info */ + PLCrashReportMachineInfo *_machineInfo; + + /** Application info */ + PLCrashReportApplicationInfo *_applicationInfo; + + /** Process info */ + PLCrashReportProcessInfo *_processInfo; + + /** Signal info */ + PLCrashReportSignalInfo *_signalInfo; + + /** Thread info (PLCrashReportThreadInfo instances) */ + NSArray *_threads; + + /** Binary images (PLCrashReportBinaryImageInfo instances */ + NSArray *_images; + + /** Exception information (may be nil) */ + PLCrashReportExceptionInfo *_exceptionInfo; +} + +- (id) initWithData: (NSData *) encodedData error: (NSError **) outError; + +- (PLCrashReportBinaryImageInfo *) imageForAddress: (uint64_t) address; + +/** + * System information. + */ +@property(nonatomic, readonly) PLCrashReportSystemInfo *systemInfo; + +/** + * YES if machine information is available. + */ +@property(nonatomic, readonly) BOOL hasMachineInfo; + +/** + * Machine information. Only available in later (v1.1+) crash report format versions. If not available, + * will be nil. + */ +@property(nonatomic, readonly) PLCrashReportMachineInfo *machineInfo; + +/** + * Application information. + */ +@property(nonatomic, readonly) PLCrashReportApplicationInfo *applicationInfo; + +/** + * YES if process information is available. + */ +@property(nonatomic, readonly) BOOL hasProcessInfo; + +/** + * Process information. Only available in later (v1.1+) crash report format versions. If not available, + * will be nil. + */ +@property(nonatomic, readonly) PLCrashReportProcessInfo *processInfo; + +/** + * Signal information. This provides the signal and signal code of the fatal signal. + */ +@property(nonatomic, readonly) PLCrashReportSignalInfo *signalInfo; + +/** + * Thread information. Returns a list of PLCrashReportThreadInfo instances. + */ +@property(nonatomic, readonly) NSArray *threads; + +/** + * Binary image information. Returns a list of PLCrashReportBinaryImageInfo instances. + */ +@property(nonatomic, readonly) NSArray *images; + +/** + * YES if exception information is available. + */ +@property(nonatomic, readonly) BOOL hasExceptionInfo; + +/** + * Exception information. Only available if a crash was caused by an uncaught exception, + * otherwise nil. + */ +@property(nonatomic, readonly) PLCrashReportExceptionInfo *exceptionInfo; + +@end diff --git a/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportApplicationInfo.h b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportApplicationInfo.h new file mode 100644 index 0000000000..2c2ab97086 --- /dev/null +++ b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportApplicationInfo.h @@ -0,0 +1,53 @@ +/* + * Author: Landon Fuller + * + * Copyright (c) 2008-2009 Plausible Labs Cooperative, Inc. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +@interface PLCrashReportApplicationInfo : NSObject { +@private + /** Application identifier */ + NSString *_applicationIdentifier; + + /** Application version */ + NSString *_applicationVersion; +} + +- (id) initWithApplicationIdentifier: (NSString *) applicationIdentifier + applicationVersion: (NSString *) applicationVersion; + +/** + * The application identifier. This is usually the application's CFBundleIdentifier value. + */ +@property(nonatomic, readonly) NSString *applicationIdentifier; + +/** + * The application version. This is usually the application's CFBundleVersion value. + */ +@property(nonatomic, readonly) NSString *applicationVersion; + +@end \ No newline at end of file diff --git a/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportBinaryImageInfo.h b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportBinaryImageInfo.h new file mode 100644 index 0000000000..339fdbb08f --- /dev/null +++ b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportBinaryImageInfo.h @@ -0,0 +1,90 @@ +/* + * Author: Landon Fuller + * + * Copyright (c) 2008-2009 Plausible Labs Cooperative, Inc. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#import +#import "PLCrashReportProcessorInfo.h" + +@interface PLCrashReportBinaryImageInfo : NSObject { +@private + /** Code type */ + PLCrashReportProcessorInfo *_processorInfo; + + /** Base image address */ + uint64_t _baseAddress; + + /** Image segment size */ + uint64_t _imageSize; + + /** Name of binary image */ + NSString *_imageName; + + /** If the UUID is available */ + BOOL _hasImageUUID; + + /** 128-bit object UUID. May be nil. */ + NSString *_imageUUID; +} + +- (id) initWithCodeType: (PLCrashReportProcessorInfo *) processorInfo + baseAddress: (uint64_t) baseAddress + size: (uint64_t) imageSize + name: (NSString *) imageName + uuid: (NSData *) uuid; + +/** + * Image code type, or nil if unavailable. + */ +@property(nonatomic, readonly) PLCrashReportProcessorInfo *codeType; + +/** + * Image base address. + */ +@property(nonatomic, readonly) uint64_t imageBaseAddress; + +/** + * Segment size. + */ +@property(nonatomic, readonly) uint64_t imageSize; + +/** + * Image name (absolute path) + */ +@property(nonatomic, readonly) NSString *imageName; + + +/** + * YES if this image has an associated UUID. + */ +@property(nonatomic, readonly) BOOL hasImageUUID; + +/** + * 128-bit object UUID (matches Mach-O DWARF dSYM files). May be nil if unavailable. + */ +@property(nonatomic, readonly) NSString *imageUUID; + +@end diff --git a/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportExceptionInfo.h b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportExceptionInfo.h new file mode 100644 index 0000000000..623b1d4e15 --- /dev/null +++ b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportExceptionInfo.h @@ -0,0 +1,65 @@ +/* + * Author: Landon Fuller + * + * Copyright (c) 2008-2009 Plausible Labs Cooperative, Inc. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#import +#import "PLCrashReportThreadInfo.h" + + +@interface PLCrashReportExceptionInfo : NSObject { +@private + /** Name */ + NSString *_name; + + /** Reason */ + NSString *_reason; + + /** Ordered list of PLCrashReportStackFrame instances, or nil if unavailable. */ + NSArray *_stackFrames; +} + +- (id) initWithExceptionName: (NSString *) name reason: (NSString *) reason; + +- (id) initWithExceptionName: (NSString *) name + reason: (NSString *) reason + stackFrames: (NSArray *) stackFrames; + +/** + * The exception name. + */ +@property(nonatomic, readonly) NSString *exceptionName; + +/** + * The exception reason. + */ +@property(nonatomic, readonly) NSString *exceptionReason; + +/* The exception's original call stack, as an array of PLCrashReportStackFrameInfo instances, or nil if unavailable. + * This may be preserved across rethrow of an exception, and can be used to determine the original call stack. */ +@property(nonatomic, readonly) NSArray *stackFrames; + +@end diff --git a/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportFormatter.h b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportFormatter.h new file mode 100644 index 0000000000..a32a243f63 --- /dev/null +++ b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportFormatter.h @@ -0,0 +1,51 @@ +/* + * Author: Landon Fuller + * + * Copyright (c) 2008-2010 Plausible Labs Cooperative, Inc. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +#import "PLCrashReport.h" + +/** + * A crash report formatter accepts a PLCrashReport instance, formats it according to implementation-specified rules, + * (such as implementing text output support), and returns the result. + */ +@protocol PLCrashReportFormatter + +/** + * Format the provided @a report. + * + * @param report Report to be formatted. + * @param outError A pointer to an NSError object variable. If an error occurs, this pointer will contain an error + * object indicating why the pending crash report could not be formatted. If no error occurs, this parameter will + * be left unmodified. You may specify nil for this parameter, and no error information will be provided. + * + * @return Returns the formatted report data on success, or nil on failure. + */ +- (NSData *) formatReport: (PLCrashReport *) report error: (NSError **) outError; + +@end diff --git a/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportMachineInfo.h b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportMachineInfo.h new file mode 100644 index 0000000000..58c4baa5f5 --- /dev/null +++ b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportMachineInfo.h @@ -0,0 +1,73 @@ +/* + * Author: Landon Fuller + * + * Copyright (c) 2008-2011 Plausible Labs Cooperative, Inc. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +#import "PLCrashReportProcessorInfo.h" + +@interface PLCrashReportMachineInfo : NSObject { +@private + /** The hardware model name (eg, MacBookPro6,1). This may be unavailable, and this property will be nil. */ + NSString *_modelName; + + /** The processor type. */ + PLCrashReportProcessorInfo *_processorInfo; + + /* The number of actual physical processor cores. */ + NSUInteger _processorCount; + + /* The number of logical processors. */ + NSUInteger _logicalProcessorCount; +} + +- (id) initWithModelName: (NSString *) modelName + processorInfo: (PLCrashReportProcessorInfo *) processorInfo + processorCount: (NSUInteger) processorCount + logicalProcessorCount: (NSUInteger) logicalProcessorCount; + +/** The hardware model name (eg, MacBookPro6,1). This may be unavailable, and this property will be nil. */ +@property(nonatomic, readonly) NSString *modelName; + +/** The processor type. */ +@property(nonatomic, readonly) PLCrashReportProcessorInfo *processorInfo; + +/* + * The number of actual physical processor cores. Note that the number of active processors may be managed by the + * operating system's power management system, and this value may not reflect the number of active + * processors at the time of the crash. + */ +@property(nonatomic, readonly) NSUInteger processorCount; + +/* + * The number of logical processors. Note that the number of active processors may be managed by the + * operating system's power management system, and this value may not reflect the number of active + * processors at the time of the crash. + */ +@property(nonatomic, readonly) NSUInteger logicalProcessorCount; + +@end diff --git a/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportProcessInfo.h b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportProcessInfo.h new file mode 100644 index 0000000000..ffc81ed9cf --- /dev/null +++ b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportProcessInfo.h @@ -0,0 +1,92 @@ +/* + * Author: Damian Morris + * + * Copyright (c) 2010 MOSO Corporation, Pty Ltd. + * Copyright (c) 2010 Plausible Labs Cooperative, Inc. + * + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +@interface PLCrashReportProcessInfo : NSObject { +@private + /** Process name */ + NSString *_processName; + + /** Process ID */ + NSUInteger _processID; + + /** Process path */ + NSString* _processPath; + + /** Parent process name */ + NSString *_parentProcessName; + + /** Parent process ID */ + NSUInteger _parentProcessID; + + /** If false, the process is being run via process-level CPU emulation (such as Rosetta). */ + BOOL _native; +} + +- (id) initWithProcessName: (NSString *) processName + processID: (NSUInteger) processID + processPath: (NSString *) processPath + parentProcessName: (NSString *) parentProcessName + parentProcessID: (NSUInteger) parentProcessID + native: (BOOL) native; + +/** + * The process name. This value may not be included in the crash report, in which case this property + * will be nil. + */ +@property(nonatomic, readonly) NSString *processName; + +/** + * The process ID. + */ +@property(nonatomic, readonly) NSUInteger processID; + +/** + * The path to the process executable. This value may not be included in the crash report, in which case this property + * will be nil. + */ +@property(nonatomic, readonly) NSString *processPath; + +/** + * The parent process name. This value may not be included in the crash report, in which case this property + * will be nil. + */ +@property(nonatomic, readonly) NSString *parentProcessName; + +/** + * The parent process ID. + */ +@property(nonatomic, readonly) NSUInteger parentProcessID; + +/** The process' native execution status. If false, the process is being run via process-level CPU emulation (such as Rosetta). */ +@property(nonatomic, readonly) BOOL native; + +@end \ No newline at end of file diff --git a/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportProcessorInfo.h b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportProcessorInfo.h new file mode 100644 index 0000000000..af027bea5b --- /dev/null +++ b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportProcessorInfo.h @@ -0,0 +1,74 @@ +/* + * Author: Landon Fuller + * + * Copyright (c) 2008-2011 Plausible Labs Cooperative, Inc. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#import +#import + +/** + * @ingroup constants + * + * The type encodings supported for CPU types and subtypes. Currently only Apple + * Mach-O defined encodings are supported. + * + * @internal + * These enum values match the protobuf values. Keep them synchronized. + */ +typedef enum { + /** Unknown cpu type encoding. */ + PLCrashReportProcessorTypeEncodingUnknown = 0, + + /** Apple Mach-defined processor types. */ + PLCrashReportProcessorTypeEncodingMach = 1 +} PLCrashReportProcessorTypeEncoding; + +@interface PLCrashReportProcessorInfo : NSObject { +@private + /** Type encoding */ + PLCrashReportProcessorTypeEncoding _typeEncoding; + + /** CPU type */ + uint64_t _type; + + /** CPU subtype */ + uint64_t _subtype; +} + +- (id) initWithTypeEncoding: (PLCrashReportProcessorTypeEncoding) typeEncoding + type: (uint64_t) type + subtype: (uint64_t) subtype; + +/** The CPU type encoding. */ +@property(nonatomic, readonly) PLCrashReportProcessorTypeEncoding typeEncoding; + +/** The CPU type. */ +@property(nonatomic, readonly) uint64_t type; + +/** The CPU subtype. */ +@property(nonatomic, readonly) uint64_t subtype; + +@end diff --git a/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportSignalInfo.h b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportSignalInfo.h new file mode 100644 index 0000000000..2c5c5fe23d --- /dev/null +++ b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportSignalInfo.h @@ -0,0 +1,60 @@ +/* + * Author: Landon Fuller + * + * Copyright (c) 2008-2009 Plausible Labs Cooperative, Inc. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +@interface PLCrashReportSignalInfo : NSObject { +@private + /** Signal name */ + NSString *_name; + + /** Signal code */ + NSString *_code; + + /** Fauling instruction or address */ + uint64_t _address; +} + +- (id) initWithSignalName: (NSString *) name code: (NSString *) code address: (uint64_t) address; + +/** + * The signal name. + */ +@property(nonatomic, readonly) NSString *name; + +/** + * The signal code. + */ +@property(nonatomic, readonly) NSString *code; + +/** + * The faulting instruction or address. + */ +@property(nonatomic, readonly) uint64_t address; + +@end diff --git a/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportSystemInfo.h b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportSystemInfo.h new file mode 100644 index 0000000000..70167dfcfe --- /dev/null +++ b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportSystemInfo.h @@ -0,0 +1,142 @@ +/* + * Author: Landon Fuller + * + * Copyright (c) 2008-2009 Plausible Labs Cooperative, Inc. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +/** + * @ingroup constants + * + * Indicates the Operating System under which a Crash Log was generated. + * + * @internal + * These enum values match the protobuf values. Keep them synchronized. + */ +typedef enum { + /** Mac OS X. */ + PLCrashReportOperatingSystemMacOSX = 0, + + /** iPhone OS */ + PLCrashReportOperatingSystemiPhoneOS = 1, + + /** iPhone Simulator (Mac OS X with additional simulator-specific runtime libraries) */ + PLCrashReportOperatingSystemiPhoneSimulator = 2, + + /** Unknown operating system */ + PLCrashReportOperatingSystemUnknown = 3, +} PLCrashReportOperatingSystem; + +/** + * @ingroup constants + * + * Indicates the architecture under which a Crash Log was generated. + * + * @deprecated The architecture value has been deprecated in v1.1 and later crash reports. All new reports + * will make use of the new PLCrashReportProcessorInfo CPU type encodings. + * + * @internal + * These enum values match the protobuf values. Keep them synchronized. + */ +typedef enum { + /** x86-32. */ + PLCrashReportArchitectureX86_32 = 0, + + /** x86-64 */ + PLCrashReportArchitectureX86_64 = 1, + + /** ARMv6 */ + PLCrashReportArchitectureARMv6 = 2, + + /** + * ARMv6 + * @deprecated + * @sa PLCrashReportArchitectureARMv6 + */ + PLCrashReportArchitectureARM = PLCrashReportArchitectureARMv6, + + /** PPC */ + PLCrashReportArchitecturePPC = 3, + + /** PPC64 */ + PLCrashReportArchitecturePPC64 = 4, + + /** ARMv7 */ + PLCrashReportArchitectureARMv7 = 5, + + /** Unknown */ + PLCrashReportArchitectureUnknown = 6 +} PLCrashReportArchitecture; + + +extern PLCrashReportOperatingSystem PLCrashReportHostOperatingSystem; +extern PLCrashReportArchitecture PLCrashReportHostArchitecture; + +@interface PLCrashReportSystemInfo : NSObject { +@private + /** Operating system */ + PLCrashReportOperatingSystem _operatingSystem; + + /** Operating system version */ + NSString *_osVersion; + + /** OS build. May be nil. */ + NSString *_osBuild; + + /** Architecture */ + PLCrashReportArchitecture _architecture; + + /** Date crash report was generated. May be nil if the date is unknown. */ + NSDate *_timestamp; +} + +- (id) initWithOperatingSystem: (PLCrashReportOperatingSystem) operatingSystem + operatingSystemVersion: (NSString *) operatingSystemVersion + architecture: (PLCrashReportArchitecture) architecture + timestamp: (NSDate *) timestamp; + +- (id) initWithOperatingSystem: (PLCrashReportOperatingSystem) operatingSystem + operatingSystemVersion: (NSString *) operatingSystemVersion + operatingSystemBuild: (NSString *) operatingSystemBuild + architecture: (PLCrashReportArchitecture) architecture + timestamp: (NSDate *) timestamp; + +/** The operating system. */ +@property(nonatomic, readonly) PLCrashReportOperatingSystem operatingSystem; + +/** The operating system's release version. */ +@property(nonatomic, readonly) NSString *operatingSystemVersion; + +/** The operating system's build identifier (eg, 10J869). This may be unavailable, and this property will be nil. */ +@property(nonatomic, readonly) NSString *operatingSystemBuild; + +/** Architecture. */ +@property(nonatomic, readonly) PLCrashReportArchitecture architecture; + +/** Date and time that the crash report was generated. This may be unavailable, and this property will be nil. */ +@property(nonatomic, readonly) NSDate *timestamp; + +@end diff --git a/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportTextFormatter.h b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportTextFormatter.h new file mode 100644 index 0000000000..61e6689f74 --- /dev/null +++ b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportTextFormatter.h @@ -0,0 +1,62 @@ +/* + * Authors: + * Landon Fuller + * Damian Morris + * + * Copyright (c) 2008-2010 Plausible Labs Cooperative, Inc. + * Copyright (c) 2010 MOSO Corporation, Pty Ltd. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +#import + +#import "PLCrashReportFormatter.h" + +/** + * Supported text output formats. + * + * @ingroup enums + */ +typedef enum { + /** An iOS-compatible crash log text format. Compatible with the crash logs generated by the device and available + * through iTunes Connect. */ + PLCrashReportTextFormatiOS = 0 +} PLCrashReportTextFormat; + + +@interface PLCrashReportTextFormatter : NSObject { +@private + /** Text output format. */ + PLCrashReportTextFormat _textFormat; + + /** Encoding to use for string output. */ + NSStringEncoding _stringEncoding; +} + ++ (NSString *) stringValueForCrashReport: (PLCrashReport *) report withTextFormat: (PLCrashReportTextFormat) textFormat; + +- (id) initWithTextFormat: (PLCrashReportTextFormat) textFormat stringEncoding: (NSStringEncoding) stringEncoding; + +@end diff --git a/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportThreadInfo.h b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportThreadInfo.h new file mode 100644 index 0000000000..7ea39b2593 --- /dev/null +++ b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReportThreadInfo.h @@ -0,0 +1,114 @@ +/* + * Author: Landon Fuller + * + * Copyright (c) 2008-2009 Plausible Labs Cooperative, Inc. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +@interface PLCrashReportStackFrameInfo : NSObject { +@private + /** Frame instruction pointer. */ + uint64_t _instructionPointer; +} + +- (id) initWithInstructionPointer: (uint64_t) instructionPointer; + +/** + * Frame's instruction pointer. + */ +@property(nonatomic, readonly) uint64_t instructionPointer; + +@end + + +@interface PLCrashReportRegisterInfo : NSObject { +@private + /** Register name */ + NSString *_registerName; + + /** Register value */ + uint64_t _registerValue; +} + +- (id) initWithRegisterName: (NSString *) registerName registerValue: (uint64_t) registerValue; + +/** + * Register name. + */ +@property(nonatomic, readonly) NSString *registerName; + +/** + * Register value. + */ +@property(nonatomic, readonly) uint64_t registerValue; + +@end + + +@interface PLCrashReportThreadInfo : NSObject { +@private + /** The thread number. Should be unique within a given crash log. */ + NSInteger _threadNumber; + + /** Ordered list of PLCrashReportStackFrame instances */ + NSArray *_stackFrames; + + /** YES if this thread crashed. */ + BOOL _crashed; + + /** List of PLCrashReportRegister instances. Will be empty if _crashed is NO. */ + NSArray *_registers; +} + +- (id) initWithThreadNumber: (NSInteger) threadNumber + stackFrames: (NSArray *) stackFrames + crashed: (BOOL) crashed + registers: (NSArray *) registers; + +/** + * Application thread number. + */ +@property(nonatomic, readonly) NSInteger threadNumber; + +/** + * Thread backtrace. Provides an array of PLCrashReportStackFrameInfo instances. + * The array is ordered, last callee to first. + */ +@property(nonatomic, readonly) NSArray *stackFrames; + +/** + * If this thread crashed, set to YES. + */ +@property(nonatomic, readonly) BOOL crashed; + +/** + * State of the general purpose and related registers, as a list of + * PLCrashReportRegister instances. If this thead did not crash (crashed returns NO), + * this list will be empty. + */ +@property(nonatomic, readonly) NSArray *registers; + +@end diff --git a/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReporter.h b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReporter.h new file mode 100644 index 0000000000..8ea5e46e6d --- /dev/null +++ b/Vendor/CrashReporter.framework/Versions/A/Headers/PLCrashReporter.h @@ -0,0 +1,97 @@ +/* + * Author: Landon Fuller + * + * Copyright (c) 2008-2009 Plausible Labs Cooperative, Inc. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +/** + * @ingroup functions + * + * Prototype of a callback function used to execute additional user code with signal information as provided + * by PLCrashReporter. Called upon completion of crash handling, after the crash report has been written to disk. + * + * @param info The signal info. + * @param uap The crash's threads context. + * @param context The API client's supplied context value. + * + * @sa @ref async_safety + * @sa PLCrashReporter::setPostCrashCallbacks: + */ +typedef void (*PLCrashReporterPostCrashSignalCallback)(siginfo_t *info, ucontext_t *uap, void *context); + +/** + * @ingroup types + * + * This structure contains callbacks supported by PLCrashReporter to allow the host application to perform + * additional tasks prior to program termination after a crash has occured. + * + * @sa @ref async_safety + */ +typedef struct PLCrashReporterCallbacks { + /** The version number of this structure. If not one of the defined version numbers for this type, the behavior + * is undefined. The current version of this structure is 0. */ + uint16_t version; + + /** An arbitrary user-supplied context value. This value may be NULL. */ + void *context; + + /** The callback used to report caught signal information. In version 0 of this structure, all crashes will be + * reported via this function. */ + PLCrashReporterPostCrashSignalCallback handleSignal; +} PLCrashReporterCallbacks; + +@interface PLCrashReporter : NSObject { +@private + /** YES if the crash reporter has been enabled */ + BOOL _enabled; + + /** Application identifier */ + NSString *_applicationIdentifier; + + /** Application version */ + NSString *_applicationVersion; + + /** Path to the crash reporter internal data directory */ + NSString *_crashReportDirectory; +} + ++ (PLCrashReporter *) sharedReporter; + +- (BOOL) hasPendingCrashReport; + +- (NSData *) loadPendingCrashReportData; +- (NSData *) loadPendingCrashReportDataAndReturnError: (NSError **) outError; + +- (BOOL) purgePendingCrashReport; +- (BOOL) purgePendingCrashReportAndReturnError: (NSError **) outError; + +- (BOOL) enableCrashReporter; +- (BOOL) enableCrashReporterAndReturnError: (NSError **) outError; + +- (void) setCrashCallbacks: (PLCrashReporterCallbacks *) callbacks; + +@end \ No newline at end of file diff --git a/Vendor/CrashReporter.framework/Versions/A/Resources/Info.plist b/Vendor/CrashReporter.framework/Versions/A/Resources/Info.plist new file mode 100644 index 0000000000..04814d9d9f --- /dev/null +++ b/Vendor/CrashReporter.framework/Versions/A/Resources/Info.plist @@ -0,0 +1,38 @@ + + + + + BuildMachineOSBuild + 11C74 + CFBundleDevelopmentRegion + English + CFBundleExecutable + CrashReporter + CFBundleIdentifier + com.yourcompany.CrashReporter + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + CrashReporter + CFBundlePackageType + FMWK + CFBundleSignature + ???? + CFBundleVersion + 1.0 + DTCompiler + + DTPlatformBuild + 4D199 + DTPlatformVersion + GM + DTSDKBuild + 11C63 + DTSDKName + macosx10.7 + DTXcode + 0420 + DTXcodeBuild + 4D199 + + diff --git a/Vendor/CrashReporter.framework/Versions/Current b/Vendor/CrashReporter.framework/Versions/Current new file mode 120000 index 0000000000..8c7e5a667f --- /dev/null +++ b/Vendor/CrashReporter.framework/Versions/Current @@ -0,0 +1 @@ +A \ No newline at end of file