From 59ff37c57f651cabb6f4ef38f3b42744232408bc Mon Sep 17 00:00:00 2001 From: Kazuhiro Ogura Date: Tue, 12 Mar 2013 22:14:34 +0900 Subject: [PATCH 01/11] Use tuple modules instead of parameterized modules --- src/erlaws_ec2.erl | 55 +++++----- src/erlaws_s3.erl | 218 +++++++++++++++++++-------------------- src/erlaws_sdb.erl | 247 +++++++++++++++++++++++---------------------- src/erlaws_sqs.erl | 111 ++++++++++---------- 4 files changed, 324 insertions(+), 307 deletions(-) diff --git a/src/erlaws_ec2.erl b/src/erlaws_ec2.erl index e9191b2..31916cd 100644 --- a/src/erlaws_ec2.erl +++ b/src/erlaws_ec2.erl @@ -1,21 +1,24 @@ --module(erlaws_ec2, [AWS_KEY, AWS_SEC_KEY, SECURE]). +-module(erlaws_ec2). -author (dieu). -include_lib("xmerl/include/xmerl.hrl"). --export ([start_instances/1, run_instances/13, stop_instances/2, terminate_instances/1, describe_instances/0, describe_instances/1]). +-export ([new/3, start_instances/2, run_instances/14, stop_instances/3, terminate_instances/2, describe_instances/1, describe_instances/2]). -define (AWS_EC2_HOST, "ec2.amazonaws.com"). -define (AWS_EC2_VERSION, "2009-11-30"). - + +new(AWS_KEY, AWS_SEC_KEY, SECURE) -> + {?MODULE, [AWS_KEY, AWS_SEC_KEY, SECURE]}. + % API -describe_instances() -> - describe_instances([]). +describe_instances({?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + describe_instances([], THIS). -describe_instances(InstanceIds) -> +describe_instances(InstanceIds, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> Fun = fun(AIM, [Inc, List]) -> [Inc + 1, lists:append(List, [{lists:flatten(io_lib:format("InstanceId.~p", [Inc])), AIM}])] end, [_, Params] = lists:foldl(Fun, [1, []], InstanceIds), - try query_request("DescribeInstances", Params) of + try query_request("DescribeInstances", Params, THIS) of {ok, Body} -> {ResponseXML, _Rest} = xmerl_scan:string(Body), Response = lists:foldl( @@ -40,8 +43,8 @@ describe_instances(InstanceIds) -> {error, Descr} end. -terminate_instances(InstanceId) -> - try query_request("TerminateInstances", [{"InstanceId", InstanceId}]) of +terminate_instances(InstanceId, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + try query_request("TerminateInstances", [{"InstanceId", InstanceId}], THIS) of {ok, Body} -> {ResponseXML, _Rest} = xmerl_scan:string(Body), Response = lists:foldl( @@ -58,8 +61,8 @@ terminate_instances(InstanceId) -> {error, Descr} end. -stop_instances(InstanceId, Force) -> - try query_request("StopInstances", [{"InstanceId", InstanceId}, {"Force", Force}]) of +stop_instances(InstanceId, Force, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + try query_request("StopInstances", [{"InstanceId", InstanceId}, {"Force", Force}], THIS) of {ok, Body} -> {ResponseXML, _Rest} = xmerl_scan:string(Body), Response = lists:foldl( @@ -76,10 +79,10 @@ stop_instances(InstanceId, Force) -> {error, Descr} end. -start_instances(InstanceIds) -> +start_instances(InstanceIds, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> Fun = fun(AIM, [Inc, List]) -> [Inc + 1, lists:append(List, [{lists:flatten(io_lib:format("InstanceId.~p", [Inc])), AIM}])] end, [_, Params] = lists:foldl(Fun, [1, []], InstanceIds), - try query_request("StartInstances", Params) of + try query_request("StartInstances", Params, THIS) of {ok, Body} -> {ResponseXML, _Rest} = xmerl_scan:string(Body), Response = lists:foldl( @@ -98,12 +101,14 @@ start_instances(InstanceIds) -> %InstanceType Valid Values: m1.small | m1.large | m1.xlarge | c1.medium | c1.xlarge | m2.xlarge | m2.2xlarge | m2.4xlarge run_instances(ImageId, MinCount, MaxCount, KeyName, SecurityGroups, AddressingType, InstanceType, - KernelId, RamdiskId, Monitoring, SubnetId, DisableApiTermination, InstanceInitiatedShutdownBehavior) -> + KernelId, RamdiskId, Monitoring, SubnetId, DisableApiTermination, InstanceInitiatedShutdownBehavior, + {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> Fun = fun(Group, [Inc, List]) -> [Inc + 1, lists:append(List, [{lists:flatten(io_lib:format("SecurityGroup.~p", [Inc])), Group}])] end, [_, SecGroups] = lists:foldl(Fun, [1, []], SecurityGroups), try query_request("RunInstances", [{"ImageId", ImageId}, {"MinCount", lists:flatten(io_lib:format("~p", [MinCount]))}, {"KeyName", KeyName}] ++ SecGroups ++ [{"AddressingType", AddressingType}, {"InstanceType", InstanceType}, {"KernelId", KernelId}, {"RamdiskId", RamdiskId}, {"Monitoring", Monitoring}, - {"SubnetId", SubnetId}, {"DisableApiTermination", DisableApiTermination}, {"InstanceInitiatedShutdownBehavior", InstanceInitiatedShutdownBehavior}, {"MaxCount", lists:flatten(io_lib:format("~p", [MaxCount]))}]) of + {"SubnetId", SubnetId}, {"DisableApiTermination", DisableApiTermination}, {"InstanceInitiatedShutdownBehavior", InstanceInitiatedShutdownBehavior}, {"MaxCount", lists:flatten(io_lib:format("~p", [MaxCount]))}], + THIS) of {ok, Body} -> {ResponseXML, _Rest} = xmerl_scan:string(Body), Response = lists:foldl( @@ -121,17 +126,17 @@ run_instances(ImageId, MinCount, MaxCount, KeyName, SecurityGroups, AddressingTy {error, Descr} end. -sign (Key,Data) -> +sign (Key, Data, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}) -> binary_to_list( base64:encode( crypto:sha_mac(Key,Data) ) ). -query_request(Action, Parameters) -> +query_request(Action, Parameters, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, SECURE]}=THIS) -> case SECURE of true -> Prefix = "https://"; _ -> Prefix = "http://" end, - query_request(Prefix ++ ?AWS_EC2_HOST ++ "/", Action, Parameters). + query_request(Prefix ++ ?AWS_EC2_HOST ++ "/", Action, Parameters, THIS). -query_request(Url, Action, Parameters) -> +query_request(Url, Action, Parameters, {?MODULE, [AWS_KEY, AWS_SEC_KEY, _SECURE]}=THIS) -> Timestamp = lists:flatten(erlaws_util:get_timestamp()), SignParams = [{"Action", Action}, {"AWSAccessKeyId", AWS_KEY}, {"Timestamp", Timestamp}] ++ Parameters ++ [{"SignatureVersion", "1"}, {"Version", ?AWS_EC2_VERSION}], @@ -140,18 +145,18 @@ query_request(Url, Action, Parameters) -> {KeyB, _} = B, string:to_lower(KeyA) =< string:to_lower(KeyB) end, SignParams)], ""), - Signature = sign(AWS_SEC_KEY, StringToSign), + Signature = sign(AWS_SEC_KEY, StringToSign, THIS), FinalQueryParams = SignParams ++ [{"Signature", Signature}], - Result = mkReq(get, Url, [], FinalQueryParams, "", ""), + Result = mkReq(get, Url, [], FinalQueryParams, "", "", THIS), case Result of {ok, _Status, Body} -> {ok, Body}; {error, {_Proto, Code, Reason}, Body} -> - throw({error, {integer_to_list(Code), Reason}, mkErr(Body)}) + throw({error, {integer_to_list(Code), Reason}, mkErr(Body, THIS)}) end. -mkReq(Method, PreUrl, Headers, QueryParams, ContentType, ReqBody) -> +mkReq(Method, PreUrl, Headers, QueryParams, ContentType, ReqBody, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}) -> Url = PreUrl ++ erlaws_util:queryParams( QueryParams ), Request = case Method of get -> { Url, Headers }; @@ -160,13 +165,13 @@ mkReq(Method, PreUrl, Headers, QueryParams, ContentType, ReqBody) -> HttpOptions = [{autoredirect, true}], Options = [ {sync,true}, {headers_as_is,true}, {body_format, binary} ], - {ok, {Status, _ReplyHeaders, Body}} = http:request(Method, Request, HttpOptions, Options), + {ok, {Status, _ReplyHeaders, Body}} = httpc:request(Method, Request, HttpOptions, Options), case Status of {_, 200, _} -> {ok, Status, binary_to_list(Body)}; {_, _, _} -> {error, Status, binary_to_list(Body)} end. -mkErr(Xml) -> +mkErr(Xml, _THIS) -> {XmlDoc, _Rest} = xmerl_scan:string( Xml ), [#xmlText{value=ErrorCode}|_] = xmerl_xpath:string("//Error/Code/text()", XmlDoc), ErrorMessage = diff --git a/src/erlaws_s3.erl b/src/erlaws_s3.erl index 9411566..748be12 100644 --- a/src/erlaws_s3.erl +++ b/src/erlaws_s3.erl @@ -6,13 +6,14 @@ %%% Created : 25 Dec 2007 by Sascha Matzke %%%------------------------------------------------------------------- --module(erlaws_s3, [AWS_KEY, AWS_SEC_KEY, SECURE]). +-module(erlaws_s3). %% API --export([list_buckets/0, create_bucket/1, create_bucket/2, delete_bucket/1]). --export([list_contents/1, list_contents/2, put_object/5, put_file/5, get_object/2]). --export([info_object/2, delete_object/2]). --export([initiate_mp_upload/4, complete_mp_upload/4, abort_mp_upload/3, upload_part/6]). +-export([new/3]). +-export([list_buckets/1, create_bucket/2, create_bucket/3, delete_bucket/2]). +-export([list_contents/2, list_contents/3, put_object/6, put_file/6, get_object/3]). +-export([info_object/3, delete_object/3]). +-export([initiate_mp_upload/5, complete_mp_upload/5, abort_mp_upload/4, upload_part/7]). %% include record definitions -include_lib("xmerl/include/xmerl.hrl"). @@ -27,6 +28,9 @@ -define(PREFIX_XPATH, "//CommonPrefixes/Prefix/text()"). -define(CHUNK_SIZE, 8 * 1024). +new(AWS_KEY, AWS_SEC_KEY, SECURE) -> + {?MODULE, [AWS_KEY, AWS_SEC_KEY, SECURE]}. + %% Returns a list of all of the buckets owned by the authenticated sender %% of the request. %% @@ -34,8 +38,8 @@ %% {ok, Buckets::[Name::string()]} | %% {error, {Code::string(), Msg::string(), ReqId::string()}} %% -list_buckets() -> - try genericRequest(get, "", "", [], [], [], <<>>) of +list_buckets({?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + try genericRequest(get, "", "", [], [], [], <<>>, THIS) of {ok, Headers, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(binary_to_list(Body)), TextNodes = xmerl_xpath:string("//Bucket/Name/text()", XmlDoc), @@ -57,8 +61,8 @@ list_buckets() -> %% {ok, Bucket::string()} | %% {error, {Code::string(), Msg::string(), ReqId::string()}} %% -create_bucket(Bucket) -> - try genericRequest(put, Bucket, "", [], [], [], <<>>) of +create_bucket(Bucket, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + try genericRequest(put, Bucket, "", [], [], [], <<>>, THIS) of {ok, Headers, _Body} -> RequestId = case lists:keytake("x-amz-request-id", 1, Headers) of {value, {_, ReqId}, _} -> ReqId; @@ -81,20 +85,20 @@ create_bucket(Bucket) -> %% {ok, Bucket::string()} | %% {error, {Code::string(), Msg::string(), ReqId::string()}} %% -create_bucket(Bucket, eu) -> - create_bucket(Bucket, 'EU'); +create_bucket(Bucket, eu, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + create_bucket(Bucket, 'EU', THIS); %% Creates a new bucket with a location constraint. %% ex) create_bucket("bucket", 'ap-southeast-1') %% %% Spec: create_bucket(Bucket::string(), Region::atom()) -> %% {ok, Bucket::string()} | %% {error, {Code::string(), Msg::string(), ReqId::string()}} -create_bucket(Bucket, Region) -> +create_bucket(Bucket, Region, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> LCfgStr = io_lib:format(" ~s ", [Region]), LCfg = list_to_binary(LCfgStr), - try genericRequest(put, Bucket, "", [], [], [], LCfg) of + try genericRequest(put, Bucket, "", [], [], [], LCfg, THIS) of {ok, Headers, _Body} -> RequestId = case lists:keytake("x-amz-request-id", 1, Headers) of {value, {_, ReqId}, _} -> ReqId; @@ -111,8 +115,8 @@ create_bucket(Bucket, Region) -> %% {ok} | %% {error, {Code::string(), Msg::string(), ReqId::string()}} %% -delete_bucket(Bucket) -> - try genericRequest(delete, Bucket, "", [], [], [], <<>>) of +delete_bucket(Bucket, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + try genericRequest(delete, Bucket, "", [], [], [], <<>>, THIS) of {ok, Headers, _Body} -> RequestId = case lists:keytake(?S3_REQ_ID_HEADER, 1, Headers) of {value, {_, ReqId}, _} -> ReqId; @@ -131,8 +135,8 @@ delete_bucket(Bucket) -> %% prefix::[string()]}} | %% {error, {Code::string(), Msg::string(), ReqId::string()}} %% -list_contents(Bucket) -> - list_contents(Bucket, []). +list_contents(Bucket, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + list_contents(Bucket, [], THIS). %% Lists the contents of a bucket. %% @@ -146,15 +150,15 @@ list_contents(Bucket) -> %% Options -> [{prefix, string()}, {marker, string()}, %% {max_keys, integer()}, {delimiter, string()}] %% -list_contents(Bucket, Options) when is_list(Options) -> - QueryParameters = [makeParam(X) || X <- Options], - try genericRequest(get, Bucket, "", QueryParameters, [], [], <<>>) of +list_contents(Bucket, Options, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) when is_list(Options) -> + QueryParameters = [makeParam(X, THIS) || X <- Options], + try genericRequest(get, Bucket, "", QueryParameters, [], [], <<>>, THIS) of {ok, Headers, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(binary_to_list(Body)), [Truncated| _Tail] = xmerl_xpath:string("//IsTruncated/text()", XmlDoc), ContentNodes = xmerl_xpath:string("//Contents", XmlDoc), - KeyList = [extractObjectInfo(Node) || Node <- ContentNodes], + KeyList = [extractObjectInfo(Node, THIS) || Node <- ContentNodes], PrefixList = [Node#xmlText.value || Node <- xmerl_xpath:string(?PREFIX_XPATH, XmlDoc)], RequestId = case lists:keytake(?S3_REQ_ID_HEADER, 1, Headers) of @@ -177,8 +181,8 @@ list_contents(Bucket, Options) when is_list(Options) -> %% {ok, #s3_object_info(key=Key::string(), size=Size::integer())} | %% {error, {Code::string(), Msg::string(), ReqId::string()}} %% -put_object(Bucket, Key, Data, ContentType, Metadata) when is_integer(hd(ContentType)) -> - put_object(Bucket, Key, Data, [{"Content-Type", ContentType}], Metadata); +put_object(Bucket, Key, Data, ContentType, Metadata, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) when is_integer(hd(ContentType)) -> + put_object(Bucket, Key, Data, [{"Content-Type", ContentType}], Metadata, THIS); %% Uploads data for key. More general version. %% @@ -193,8 +197,8 @@ put_object(Bucket, Key, Data, ContentType, Metadata) when is_integer(hd(ContentT %% S3:put_object("someBucket", "filename.js", <<"...">>, [{"Content-Type", "application/x-javascript; charset=\"utf-8\""},{"Cache-Control", "max-age=86400"},{"x-amz-acl", "public-read"}], [{"name", "metavalue"}]). %% S3:put_object("someBucket", "filename.mp4", <<"...">>, [{"Content-Type", "video/mp4"}, {"x-amz-storage-class", "REDUCED_REDUNDANCY"}], []). %% -put_object(Bucket, Key, Data, HTTPHeaders, Metadata) -> - try genericRequest(put, Bucket, Key, [], Metadata, HTTPHeaders, Data) of +put_object(Bucket, Key, Data, HTTPHeaders, Metadata, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + try genericRequest(put, Bucket, Key, [], Metadata, HTTPHeaders, Data, THIS) of {ok, Headers, _Body} -> {ok, #s3_object_info{key=Key, size=iolist_size(Data), @@ -208,13 +212,13 @@ put_object(Bucket, Key, Data, HTTPHeaders, Metadata) -> %% @doc %% Initiates multipart upload. %% @end --spec initiate_mp_upload(Bucket::string(), Key::string(), - HTTPHeaders::[{string(), string()}], - Metadata::[{string(), string()}]) -> - {ok, UploadId::string(), ReqId::string()}. -initiate_mp_upload(Bucket, Key, HTTPHeaders, Metadata) -> +%% -spec initiate_mp_upload(Bucket::string(), Key::string(), +%% HTTPHeaders::[{string(), string()}], +%% Metadata::[{string(), string()}]) -> +%% {ok, UploadId::string(), ReqId::string()}. +initiate_mp_upload(Bucket, Key, HTTPHeaders, Metadata, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> try genericRequest(post, Bucket, Key, [{"uploads", ""}], - Metadata, HTTPHeaders, <<>>) of + Metadata, HTTPHeaders, <<>>, THIS) of {ok, Headers, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(binary_to_list(Body)), [_XBucket|_] = xmerl_xpath:string("/InitiateMultipartUploadResult/Bucket/text()", XmlDoc), @@ -229,9 +233,9 @@ initiate_mp_upload(Bucket, Key, HTTPHeaders, Metadata) -> %% @doc %% Completes multipart upload. %% @end --spec complete_mp_upload(Bucket::string(), Key::string(), UploadId::string(), - [{PartNum::integer(), ETag::string()}]) -> ok. -complete_mp_upload(Bucket, Key, UploadId, Parts) -> +%% -spec complete_mp_upload(Bucket::string(), Key::string(), UploadId::string(), +%% [{PartNum::integer(), ETag::string()}]) -> ok. +complete_mp_upload(Bucket, Key, UploadId, Parts, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> F = fun({PartNum, ETag}) -> {'Part', [], [ @@ -243,7 +247,7 @@ complete_mp_upload(Bucket, Key, UploadId, Parts) -> Req = {'CompleteMultipartUpload', [], [F(X) || X <- Parts]}, XMLReqBody = xmerl:export_simple([Req], xmerl_xml), try genericRequest(post, Bucket, Key, - [{"uploadId", UploadId}], [], [], XMLReqBody) of + [{"uploadId", UploadId}], [], [], XMLReqBody, THIS) of {ok, _Headers, _Body} -> ok catch throw:{error, Descr} -> @@ -253,11 +257,11 @@ complete_mp_upload(Bucket, Key, UploadId, Parts) -> %% @doc %% Aborts multipart upload. %% @end --spec abort_mp_upload(Bucket::string(), Key::string(), UploadId::string()) -> - ok. -abort_mp_upload(Bucket, Key, UploadId) -> +%% -spec abort_mp_upload(Bucket::string(), Key::string(), UploadId::string()) -> +%% ok. +abort_mp_upload(Bucket, Key, UploadId, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> try genericRequest(delete, Bucket, Key, - [{"uploadId", UploadId}], [], [], <<>>) of + [{"uploadId", UploadId}], [], [], <<>>, THIS) of {ok, _Headers, _Body} -> ok catch throw:{error, Descr} -> @@ -267,14 +271,14 @@ abort_mp_upload(Bucket, Key, UploadId) -> %% @doc %% Uploads a part in multipart upload. %% @end --spec upload_part(Bucket::string(), Key::string(), PartNum::integer(), - UploadId::string(), Data::binary(), - HTTPHeaders::[{string(), string()}]) -> ok. -upload_part(Bucket, Key, PartNum, UploadId, Data, HTTPHeaders) -> +%% -spec upload_part(Bucket::string(), Key::string(), PartNum::integer(), +%% UploadId::string(), Data::binary(), +%% HTTPHeaders::[{string(), string()}]) -> ok. +upload_part(Bucket, Key, PartNum, UploadId, Data, HTTPHeaders, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> try genericRequest(put, Bucket, Key, [{"partNumber", integer_to_list(PartNum)}, {"uploadId", UploadId}], - [], HTTPHeaders, Data) of + [], HTTPHeaders, Data, THIS) of {ok, Headers, _Body} -> {ok, #s3_object_info{key=Key, size=iolist_size(Data), @@ -285,17 +289,17 @@ upload_part(Bucket, Key, PartNum, UploadId, Data, HTTPHeaders) -> {error, Descr} end. -put_file(Bucket, Key, FileName, ContentType, Metadata) -> +put_file(Bucket, Key, FileName, ContentType, Metadata, {?MODULE, [AWS_KEY, AWS_SEC_KEY, _SECURE]}=THIS) -> Date = httpd_util:rfc1123_date(erlang:localtime()), - {FileSize, File} = openAndGetFileSize(FileName), + {FileSize, File} = openAndGetFileSize(FileName, THIS), Headers = - buildContentHeaders(FileSize) ++ - buildMetadataHeaders(Metadata), + buildContentHeaders(FileSize, THIS) ++ + buildMetadataHeaders(Metadata, THIS), Signature = sign(AWS_SEC_KEY, stringToSign("PUT", "", ContentType, Date, - Bucket, Key, Headers)), + Bucket, Key, Headers, THIS), THIS), FinalHeaders = [ {"Authorization", "AWS " ++ AWS_KEY ++ ":" ++ Signature }, - {"Host", buildHost(Bucket) }, + {"Host", buildHost(Bucket, THIS) }, {"Date", Date }, {"Content-Type", ContentType} | Headers ], @@ -308,7 +312,7 @@ put_file(Bucket, Key, FileName, ContentType, Metadata) -> {ok, Socket} = gen_tcp:connect(?AWS_S3_HOST, 80, [binary, {active, false}, {packet, 0}]), gen_tcp:send(Socket, list_to_binary(Payload)), - sendData(Socket, File), + sendData(Socket, File, THIS), gen_tcp:close(Socket), file:close(File). @@ -318,8 +322,8 @@ put_file(Bucket, Key, FileName, ContentType, Metadata) -> %% {ok, Data::binary()} | %% {error, {Code::string(), Msg::string(), ReqId::string()}} %% -get_object(Bucket, Key) -> - try genericRequest(get, Bucket, Key, [], [], [], <<>>) of +get_object(Bucket, Key, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + try genericRequest(get, Bucket, Key, [], [], [], <<>>, THIS) of {ok, Headers, Body} -> RequestId = case lists:keytake(?S3_REQ_ID_HEADER, 1, Headers) of {value, {_, ReqId}, _} -> ReqId; @@ -336,8 +340,8 @@ get_object(Bucket, Key) -> %% {ok, [{Key::string(), Value::string()},...], {requestId, ReqId::string()}} | %% {error, {Code::string(), Msg::string(), ReqId::string()}} %% -info_object(Bucket, Key) -> - try genericRequest(head, Bucket, Key, [], [], [], <<>>) of +info_object(Bucket, Key, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + try genericRequest(head, Bucket, Key, [], [], [], <<>>, THIS) of {ok, Headers, _Body} -> io:format("Headers: ~p~n", [Headers]), MetadataList = [{string:substr(MKey, 12), Value} || {MKey, Value} <- Headers, string:str(MKey, "x-amz-meta") == 1], @@ -356,8 +360,8 @@ info_object(Bucket, Key) -> %% {ok} | %% {error, {Code::string(), Msg::string(), ReqId::string()}} %% -delete_object(Bucket, Key) -> - try genericRequest(delete, Bucket, Key, [], [], [], <<>>) of +delete_object(Bucket, Key, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + try genericRequest(delete, Bucket, Key, [], [], [], <<>>, THIS) of {ok, Headers, _Body} -> RequestId = case lists:keytake(?S3_REQ_ID_HEADER, 1, Headers) of {value, {_, ReqId}, _} -> ReqId; @@ -372,33 +376,33 @@ delete_object(Bucket, Key) -> %%% Internal functions %%-------------------------------------------------------------------- -isAmzHeader( Header ) -> lists:prefix("x-amz-", Header). +isAmzHeader( Header, _THIS ) -> lists:prefix("x-amz-", Header). -aggregateValues ({K,V}, [{K,L}|T]) -> [{K,[V|L]}|T]; -aggregateValues ({K,V}, L) -> [{K,[V]}|L]. +aggregateValues ({K,V}, [{K,L}|T], _THIS) -> [{K,[V|L]}|T]; +aggregateValues ({K,V}, L, _THIS) -> [{K,[V]}|L]. -collapse(L) -> - AggrL = lists:foldl( fun aggregateValues/2, [], lists:keysort(1, L) ), +collapse(L, THIS) -> + AggrL = lists:foldl( fun aggregateValues/3, [], lists:keysort(1, L), THIS ), lists:keymap( fun lists:sort/1, 2, lists:reverse(AggrL)). -mkHdr ({Key,Values}) -> +mkHdr ({Key,Values}, _THIS) -> Key ++ ":" ++ erlaws_util:mkEnumeration(Values,","). -canonicalizeAmzHeaders( Headers ) -> +canonicalizeAmzHeaders( Headers, THIS ) -> XAmzHeaders = [ {string:to_lower(Key),Value} || {Key,Value} <- Headers, - isAmzHeader(Key) ], + isAmzHeader(Key, THIS) ], Strings = lists:map( - fun mkHdr/1, - collapse(XAmzHeaders)), + fun mkHdr/2, + collapse(XAmzHeaders, THIS), THIS), erlaws_util:mkEnumeration( [[String, "\n"] || String <- Strings], ""). -canonicalizeResource ( "", "" ) -> "/"; -canonicalizeResource ( Bucket, "" ) -> "/" ++ Bucket ++ "/"; -canonicalizeResource ( "", Path) -> "/" ++ Path; -canonicalizeResource ( Bucket, Path ) -> "/" ++ Bucket ++ "/" ++ Path. +canonicalizeResource ( "", "", _THIS ) -> "/"; +canonicalizeResource ( Bucket, "", _THIS ) -> "/" ++ Bucket ++ "/"; +canonicalizeResource ( "", Path, _THIS) -> "/" ++ Path; +canonicalizeResource ( Bucket, Path, _THIS ) -> "/" ++ Bucket ++ "/" ++ Path. -makeParam(X) -> +makeParam(X, _THIS) -> case X of {_, []} -> {}; {prefix, Prefix} -> @@ -413,64 +417,64 @@ makeParam(X) -> end. -buildHost("") -> +buildHost("", _THIS) -> ?AWS_S3_HOST; -buildHost(Bucket) -> +buildHost(Bucket, _THIS) -> Bucket ++ "." ++ ?AWS_S3_HOST. -buildProtocol() -> +buildProtocol({?MODULE, [_AWS_KEY, _AWS_SEC_KEY, SECURE]}) -> case SECURE of true -> "https://"; _ -> "http://" end. -buildUrl("", "", []) -> - buildProtocol() ++ ?AWS_S3_HOST ++ "/"; -buildUrl("", Path, []) -> - buildProtocol() ++ ?AWS_S3_HOST ++ Path; -buildUrl(Bucket,Path,QueryParams) -> - buildProtocol() ++ Bucket ++ "." ++ ?AWS_S3_HOST ++ "/" ++ Path ++ +buildUrl("", "", [], THIS) -> + buildProtocol(THIS) ++ ?AWS_S3_HOST ++ "/"; +buildUrl("", Path, [], THIS) -> + buildProtocol(THIS) ++ ?AWS_S3_HOST ++ Path; +buildUrl(Bucket,Path,QueryParams,THIS) -> + buildProtocol(THIS) ++ Bucket ++ "." ++ ?AWS_S3_HOST ++ "/" ++ Path ++ erlaws_util:queryParams(QueryParams). -buildContentHeaders(Contents) when is_integer(Contents) -> +buildContentHeaders(Contents, _THIS) when is_integer(Contents) -> [{"Content-Length", integer_to_list(Contents)}]; % Detect gzip header and put appropriate Content-Encoding. Questionable?.. -buildContentHeaders(<<16#1f, 16#8b, _/binary>> = Contents) -> +buildContentHeaders(<<16#1f, 16#8b, _/binary>> = Contents, _THIS) -> [{"Content-Length", integer_to_list(iolist_size(Contents))}, {"Content-Encoding", "gzip"}]; -buildContentHeaders(Contents) -> +buildContentHeaders(Contents, _THIS) -> [{"Content-Length", integer_to_list(iolist_size(Contents))}]. -buildMetadataHeaders(Metadata) -> +buildMetadataHeaders(Metadata, _THIS) -> lists:foldl(fun({Key, Value}, Acc) -> [{string:to_lower("x-amz-meta-"++Key), Value} | Acc] end, [], Metadata). -buildContentMD5Header(ContentMD5) -> +buildContentMD5Header(ContentMD5, _THIS) -> case ContentMD5 of "" -> []; _ -> [{"Content-MD5", ContentMD5}] end. stringToSign ( Verb, ContentMD5, ContentType, Date, Bucket, Path, - OriginalHeaders ) -> + OriginalHeaders, THIS ) -> Parts = [ Verb, ContentMD5, ContentType, Date, - canonicalizeAmzHeaders(OriginalHeaders)], + canonicalizeAmzHeaders(OriginalHeaders, THIS)], erlaws_util:mkEnumeration( Parts, "\n") ++ - canonicalizeResource(Bucket, Path). + canonicalizeResource(Bucket, Path, THIS). -sign (Key,Data) -> +sign (Key,Data,_THIS) -> binary_to_list( base64:encode( crypto:sha_mac(Key,Data) ) ). genericRequest( Method, Bucket, Path, QueryParams, Metadata, - HTTPHeaders, Body ) -> + HTTPHeaders, Body, THIS ) -> genericRequest( Method, Bucket, Path, QueryParams, Metadata, - HTTPHeaders, Body, ?NR_OF_RETRIES). + HTTPHeaders, Body, ?NR_OF_RETRIES, THIS). genericRequest( Method, Bucket, Path, QueryParams, Metadata, - HTTPHeaders, Body, NrOfRetries) -> + HTTPHeaders, Body, NrOfRetries, {?MODULE, [AWS_KEY, AWS_SEC_KEY, _SECURE]}=THIS) -> Date = httpd_util:rfc1123_date(erlang:localtime()), MethodString = string:to_upper( atom_to_list(Method) ), - Url = buildUrl(Bucket,Path,QueryParams), + Url = buildUrl(Bucket,Path,QueryParams,THIS), ContentMD5 = case iolist_size(Body) of 0 -> ""; @@ -478,9 +482,9 @@ genericRequest( Method, Bucket, Path, QueryParams, Metadata, end, Headers = - buildContentHeaders(Body) ++ - buildMetadataHeaders(Metadata) ++ - buildContentMD5Header(ContentMD5) ++ + buildContentHeaders(Body, THIS) ++ + buildMetadataHeaders(Metadata, THIS) ++ + buildContentMD5Header(ContentMD5, THIS) ++ HTTPHeaders, ContentType = case [Value || {"Content-Type", Value} <- HTTPHeaders] of @@ -494,10 +498,10 @@ genericRequest( Method, Bucket, Path, QueryParams, Metadata, stringToSign(MethodString, ContentMD5, ContentType, Date, Bucket, Path ++ erlaws_util:queryParams(QueryParams), - Headers )), + Headers, THIS), THIS), FinalHeaders = [ {"Authorization","AWS " ++ AccessKey ++ ":" ++ Signature }, - {"Host", buildHost(Bucket) }, + {"Host", buildHost(Bucket, THIS) }, {"Date", Date }, {"Expect", "Continue"} | Headers ], @@ -513,7 +517,7 @@ genericRequest( Method, Bucket, Path, QueryParams, Metadata, HttpOptions = [{autoredirect, true}], Options = [ {sync,true}, {headers_as_is,true}, {body_format, binary} ], - Reply = http:request( Method, Request, HttpOptions, Options ), + Reply = httpc:request( Method, Request, HttpOptions, Options ), %% {ok, {Status, ReplyHeaders, RBody}} = Reply, %% io:format("Response:~n ~p~n~p~n~p~n", [Status, ReplyHeaders, @@ -533,11 +537,11 @@ genericRequest( Method, Bucket, Path, QueryParams, Metadata, _ResponseBody }} when Code=:=500 -> timer:sleep((?NR_OF_RETRIES-NrOfRetries)*500), genericRequest(Method, Bucket, Path, QueryParams, - Metadata, HTTPHeaders, Body, NrOfRetries-1); + Metadata, HTTPHeaders, Body, NrOfRetries-1, THIS); {ok, {{_HttpVersion, HttpCode, ReasonPhrase}, ResponseHeaders, ResponseBody }} -> - throw (try mkErr(ResponseBody, ResponseHeaders) of + throw (try mkErr(ResponseBody, ResponseHeaders, THIS) of {error, Reason} -> {error, Reason} catch exit:_Error -> @@ -546,7 +550,7 @@ genericRequest( Method, Bucket, Path, QueryParams, Metadata, end) end. -mkErr (Xml, Headers) -> +mkErr (Xml, Headers, _THIS) -> {XmlDoc, _Rest} = xmerl_scan:string( binary_to_list(Xml) ), [#xmlText{value=ErrorCode}|_] = xmerl_xpath:string("/Error/Code/text()", XmlDoc), @@ -555,7 +559,7 @@ mkErr (Xml, Headers) -> {error, {ErrorCode, ErrorMessage, proplists:get_value(?S3_REQ_ID_HEADER, Headers)}}. -extractObjectInfo (Node) -> +extractObjectInfo (Node, _THIS) -> [Key|_] = xmerl_xpath:string("./Key/text()", Node), [ETag|_] = xmerl_xpath:string("./ETag/text()", Node), [LastModified|_] = xmerl_xpath:string("./LastModified/text()", Node), @@ -563,7 +567,7 @@ extractObjectInfo (Node) -> #s3_object_info{key=Key#xmlText.value, lastmodified=LastModified#xmlText.value, etag=ETag#xmlText.value, size=Size#xmlText.value}. -openAndGetFileSize(FileName) -> +openAndGetFileSize(FileName, _THIS) -> case file:open(FileName, [read, binary]) of {ok, File} -> {ok, #file_info{size=Size}} = file:read_file_info(FileName), @@ -572,11 +576,11 @@ openAndGetFileSize(FileName) -> {error, no_file} end. -sendData(Socket, File) -> +sendData(Socket, File, _THIS) -> case file:read(File, ?CHUNK_SIZE) of {ok, Data} -> gen_tcp:send(Socket, Data), - sendData(Socket, File); + sendData(Socket, File, _THIS); eof -> ok end. diff --git a/src/erlaws_sdb.erl b/src/erlaws_sdb.erl index f2653a3..335c67e 100644 --- a/src/erlaws_sdb.erl +++ b/src/erlaws_sdb.erl @@ -6,15 +6,16 @@ %% @end %%%------------------------------------------------------------------- --module(erlaws_sdb, [AWS_KEY, AWS_SEC_KEY, SECURE]). +-module(erlaws_sdb). %% exports --export([create_domain/1, delete_domain/1, list_domains/0, list_domains/1, - put_attributes/3, put_attributes/4, batch_put_attributes/2, - delete_item/2, delete_attributes/3, delete_attributes/4, - get_attributes/2, get_attributes/3, get_attributes/4, - list_items/1, list_items/2, - query_items/2, query_items/3, select/1, select/2, storage_size/2]). +-export([new/3]). +-export([create_domain/2, delete_domain/2, list_domains/1, list_domains/2, + put_attributes/4, put_attributes/5, batch_put_attributes/3, + delete_item/3, delete_attributes/4, delete_attributes/5, + get_attributes/3, get_attributes/4, get_attributes/5, + list_items/2, list_items/3, + query_items/3, query_items/4, select/2, select/3, storage_size/3]). %% include record definitions -include_lib("xmerl/include/xmerl.hrl"). @@ -24,6 +25,9 @@ -define(AWS_SDB_VERSION, "2009-04-15"). -define(USE_SIGNATURE_V1, false). +new(AWS_KEY, AWS_SEC_KEY, SECURE) -> + {?MODULE, [AWS_KEY, AWS_SEC_KEY, SECURE]}. + %% This function creates a new SimpleDB domain. The domain name must be unique among the %% domains associated with your AWS Access Key ID. This function might take 10 %% or more seconds to complete.. @@ -34,9 +38,9 @@ %% %% Code::string() -> "InvalidParameterValue" | "MissingParameter" | "NumberDomainsExceeded" %% -create_domain(Domain) -> +create_domain(Domain, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> try genericRequest("CreateDomain", - Domain, "", [], []) of + Domain, "", [], [], THIS) of {ok, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(Body), [#xmlText{value=RequestId}|_] = @@ -56,9 +60,9 @@ create_domain(Domain) -> %% %% Code::string() -> "MissingParameter" %% -delete_domain(Domain) -> +delete_domain(Domain, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> try genericRequest("DeleteDomain", - Domain, "", [], []) of + Domain, "", [], [], THIS) of {ok, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(Body), [#xmlText{value=RequestId}|_] = @@ -77,8 +81,8 @@ delete_domain(Domain) -> %% %% See list_domains/1 for a detailed error description %% -list_domains() -> - list_domains([]). +list_domains({?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + list_domains([], THIS). %% Lists domains up to the limit set by {max_domains, integer()}. %% A NextToken is returned if there are more than max_domains domains. @@ -94,9 +98,9 @@ list_domains() -> %% %% Code::string() -> "InvalidParameterValue" | "InvalidNextToken" | "MissingParameter" %% -list_domains(Options) -> +list_domains(Options, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> try genericRequest("ListDomains", "", "", [], - [makeParam(X) || X <- Options]) of + [makeParam(X, THIS) || X <- Options], THIS) of {ok, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(Body), DomainNodes = xmerl_xpath:string("//ListDomainsResult/DomainName/text()", @@ -151,10 +155,11 @@ list_domains(Options) -> %% "NumberItemAttributesExceeded" | "NumberDomainAttributesExceeded" | %% "NumberDomainBytesExceeded" %% -put_attributes(Domain, Item, Attributes) when is_list(Domain), - is_list(Item), - is_list(Attributes) -> - put_attributes(Domain, Item, Attributes, []). +put_attributes(Domain, Item, Attributes, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) + when is_list(Domain), + is_list(Item), + is_list(Attributes) -> + put_attributes(Domain, Item, Attributes, [], THIS). %% You may request "conditional put" by providing the 4th argument to %% put_attributes/4. @@ -164,12 +169,12 @@ put_attributes(Domain, Item, Attributes) when is_list(Domain), %% [{expected, AttributeName, false}]. %% %% See: http://developer.amazonwebservices.com/connect/entry.jspa?externalID=3572 -put_attributes(Domain, Item, Attributes, Options) when is_list(Domain), +put_attributes(Domain, Item, Attributes, Options, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) when is_list(Domain), is_list(Item), is_list(Attributes), is_list(Options) -> try genericRequest("PutAttributes", Domain, Item, - Attributes, [makeParam(X) || X <- Options]) of + Attributes, [makeParam(X, THIS) || X <- Options], THIS) of {ok, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(Body), [#xmlText{value=RequestId}|_] = @@ -188,10 +193,10 @@ put_attributes(Domain, Item, Attributes, Options) when is_list(Domain), %% %% Code::string() -> "InvalidParameterValue" | "MissingParameter" | "NoSuchDomain" %% -delete_attributes(Domain, Item, Attributes) when is_list(Domain), +delete_attributes(Domain, Item, Attributes, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) when is_list(Domain), is_list(Item), is_list(Attributes) -> - delete_attributes(Domain, Item, Attributes, []). + delete_attributes(Domain, Item, Attributes, [], THIS). %% You may request "conditional delete" by providing the 4th argument to %% put_attributes/4. @@ -201,12 +206,12 @@ delete_attributes(Domain, Item, Attributes) when is_list(Domain), %% [{expected, AttributeName, false}]. %% %% See: http://developer.amazonwebservices.com/connect/entry.jspa?externalID=3572 -delete_attributes(Domain, Item, Attributes, Options) when is_list(Domain), +delete_attributes(Domain, Item, Attributes, Options, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) when is_list(Domain), is_list(Item), is_list(Attributes), is_list(Options) -> try genericRequest("DeleteAttributes", Domain, Item, - Attributes, [makeParam(X) || X <- Options]) of + Attributes, [makeParam(X, THIS) || X <- Options], THIS) of {ok, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(Body), [#xmlText{value=RequestId}|_] = @@ -247,10 +252,10 @@ delete_attributes(Domain, Item, Attributes, Options) when is_list(Domain), %% "NumberDomainAttributesExceeded" | "NumberDomainBytesExceeded" | %% "NumberSubmittedItemsExceeded" | "NumberSubmittedAttrubutesExceeded" %% -batch_put_attributes(Domain, ItemAttributes) when is_list(Domain), +batch_put_attributes(Domain, ItemAttributes, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) when is_list(Domain), is_list(ItemAttributes) -> try genericRequest("BatchPutAttributes", Domain, "", - ItemAttributes, []) of + ItemAttributes, [], THIS) of {ok, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(Body), [#xmlText{value=RequestId}|_] = @@ -269,9 +274,9 @@ batch_put_attributes(Domain, ItemAttributes) when is_list(Domain), %% %% Code::string() -> "InvalidParameterValue" | "MissingParameter" | "NoSuchDomain" %% -delete_item(Domain, Item) when is_list(Domain), +delete_item(Domain, Item, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) when is_list(Domain), is_list(Item) -> - try delete_attributes(Domain, Item, []) of + try delete_attributes(Domain, Item, [], THIS) of {ok, RequestId} -> {ok, RequestId} catch throw:{error, Descr} -> @@ -294,10 +299,10 @@ delete_item(Domain, Item) when is_list(Domain), %% %% Code::string() -> "InvalidParameterValue" | "MissingParameter" | "NoSuchDomain" %% -get_attributes(Domain, Items) when is_list(Domain), +get_attributes(Domain, Items, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) when is_list(Domain), is_list(Items), is_list(hd(Items)) -> - get_attributes(Domain, Items, ""); + get_attributes(Domain, Items, "", THIS); %% Returns all of the attributes associated with the item. %% @@ -316,9 +321,9 @@ get_attributes(Domain, Items) when is_list(Domain), %% %% Code::string() -> "InvalidParameterValue" | "MissingParameter" | "NoSuchDomain" %% -get_attributes(Domain, Item) when is_list(Domain), +get_attributes(Domain, Item, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) when is_list(Domain), is_list(Item) -> - get_attributes(Domain, Item, ""). + get_attributes(Domain, Item, "", THIS). %% Returns the requested attribute for a list of items. %% @@ -330,12 +335,12 @@ get_attributes(Domain, Item) when is_list(Domain), %% %% Code::string() -> "InvalidParameterValue" | "MissingParameter" | "NoSuchDomain" %% -get_attributes(Domain, Items, Attribute) when is_list(Domain), +get_attributes(Domain, Items, Attribute, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) when is_list(Domain), is_list(Items), is_list(Attribute) -> - get_attributes(Domain, Items, Attribute, []). + get_attributes(Domain, Items, Attribute, [], THIS). -get_attributes(Domain, Items, Attribute, Options) when is_list(Domain), +get_attributes(Domain, Items, Attribute, Options, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) when is_list(Domain), is_list(Items), is_list(hd(Items)), is_list(Attribute), @@ -343,7 +348,7 @@ get_attributes(Domain, Items, Attribute, Options) when is_list(Domain), Fetch = fun(X) -> ParentPID = self(), spawn(fun() -> - case get_attributes(Domain, X, Attribute, Options) of + case get_attributes(Domain, X, Attribute, Options, THIS) of {ok, [ItemResult]} -> ParentPID ! { ok, ItemResult }; {error, Descr} -> @@ -374,12 +379,12 @@ get_attributes(Domain, Items, Attribute, Options) when is_list(Domain), %% %% Code::string() -> "InvalidParameterValue" | "MissingParameter" | "NoSuchDomain" %% -get_attributes(Domain, Item, Attribute, Options) when is_list(Domain), +get_attributes(Domain, Item, Attribute, Options, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) when is_list(Domain), is_list(Item), is_list(Attribute), is_list(Options) -> try genericRequest("GetAttributes", Domain, Item, - Attribute, [makeParam(X) || X <- Options]) of + Attribute, [makeParam(X, THIS) || X <- Options], THIS) of {ok, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(Body), AttrList = [{KN, VN} || Node <- xmerl_xpath:string("//Attribute", XmlDoc), @@ -396,7 +401,7 @@ get_attributes(Domain, Item, Attribute, Options) when is_list(Domain), _ -> ValueRaw end, true end], - {ok, [{Item, lists:foldr(fun aggregateAttr/2, [], AttrList)}]} + {ok, [{Item, lists:foldr(fun aggregateAttr/3, [], AttrList, THIS)}]} catch throw:{error, Descr} -> {error, Descr} @@ -418,8 +423,8 @@ get_attributes(Domain, Item, Attribute, Options) when is_list(Domain), %% Code::string() -> "InvalidParameterValue" | "InvalidNextToken" | %% "MissingParameter" | "NoSuchDomain" %% -list_items(Domain) -> - list_items(Domain, []). +list_items(Domain, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + list_items(Domain, [], THIS). %% Returns up to max_items -> integer() <= 250 items of a domain. If @@ -439,9 +444,9 @@ list_items(Domain) -> %% Code::string() -> "InvalidParameterValue" | "InvalidNextToken" | %% "MissingParameter" | "NoSuchDomain" %% -list_items(Domain, Options) when is_list(Options) -> +list_items(Domain, Options, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) when is_list(Options) -> try genericRequest("Query", Domain, "", [], - [makeParam(X) || X <- [{version, ?OLD_AWS_SDB_VERSION}|Options]]) of + [makeParam(X, THIS) || X <- [{version, ?OLD_AWS_SDB_VERSION}|Options]], THIS) of {ok, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(Body), ItemNodes = xmerl_xpath:string("//ItemName/text()", XmlDoc), @@ -472,13 +477,13 @@ list_items(Domain, Options) when is_list(Options) -> %% Code::string() -> "InvalidParameterValue" | "InvalidNextToken" | "InvalidNumberPredicates" %% | "InvalidNumberValueTests" | "InvalidQueryExpression" | "InvalidSortExpression" %% | "MissingParameter" | "NoSuchDomain" | "RequestTimeout" | "TooManyRequestAttributes" -select(SelectExp) -> - select(SelectExp, []). +select(SelectExp, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + select(SelectExp, [], THIS). -select(SelectExp, Options) when is_list(Options) -> +select(SelectExp, Options, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) when is_list(Options) -> try genericRequest("Select", "", "", [], [{"SelectExpression", SelectExp}| - [makeParam(X) || X <- Options]]) of + [makeParam(X, THIS) || X <- Options]], THIS) of {ok, Body} -> {XmlDoc_, _Rest} = xmerl_scan:string(Body), ItemNodes = xmerl_xpath:string("//Item", XmlDoc_), @@ -502,7 +507,7 @@ select(SelectExp, Options) when is_list(Options) -> _ -> ValueRaw end, true end], - {Item, lists:foldr(fun aggregateAttr/2, [], AttrList)} + {Item, lists:foldr(fun aggregateAttr/3, [], AttrList, THIS)} end, Items = [F(ItemNode) || ItemNode <- ItemNodes], {ok, Items, NextToken} @@ -526,8 +531,8 @@ select(SelectExp, Options) when is_list(Options) -> %% Code::string() -> "InvalidParameterValue" | "InvalidNextToken" | %% "MissingParameter" | "NoSuchDomain" %% -query_items(Domain, QueryExp) -> - query_items(Domain, QueryExp, []). +query_items(Domain, QueryExp, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + query_items(Domain, QueryExp, [], THIS). %% Executes the given query expression against a domain. The syntax for %% such a query spec is documented here: @@ -547,10 +552,10 @@ query_items(Domain, QueryExp) -> %% Code::string() -> "InvalidParameterValue" | "InvalidNextToken" | %% "MissingParameter" | "NoSuchDomain" %% -query_items(Domain, QueryExp, Options) when is_list(Options) -> +query_items(Domain, QueryExp, Options, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) when is_list(Options) -> {ok, Body} = genericRequest("Query", Domain, "", [], [{"QueryExpression", QueryExp}| - [makeParam(X) || X <- [{version, ?OLD_AWS_SDB_VERSION}|Options]]]), + [makeParam(X, THIS) || X <- [{version, ?OLD_AWS_SDB_VERSION}|Options]]], THIS), {XmlDoc, _Rest} = xmerl_scan:string(Body), ItemNodes = xmerl_xpath:string("//ItemName/text()", XmlDoc), [#xmlText{value=RequestId}|_] = @@ -560,40 +565,40 @@ query_items(Domain, QueryExp, Options) when is_list(Options) -> %% storage cost -storage_size(Item, Attributes) -> +storage_size(Item, Attributes, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> ItemSize = length(Item) + 45, - {AttrSize, ValueSize} = calcAttrStorageSize(Attributes), + {AttrSize, ValueSize} = calcAttrStorageSize(Attributes, THIS), {AttrSize, ItemSize + ValueSize}. %% internal functions -calcAttrStorageSize(Attributes) -> - calcAttrStorageSize(Attributes, {0, 0}). +calcAttrStorageSize(Attributes, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + calcAttrStorageSize(Attributes, {0, 0}, THIS). -calcAttrStorageSize([{Attr, ValueList}|Rest], {AttrSize, ValueSize}) -> +calcAttrStorageSize([{Attr, ValueList}|Rest], {AttrSize, ValueSize}, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> calcAttrStorageSize(Rest, {AttrSize + length(Attr) + 45, - calcValueStorageSize(ValueSize, ValueList)}); -calcAttrStorageSize([], Result) -> + calcValueStorageSize(ValueSize, ValueList, THIS)}, THIS); +calcAttrStorageSize([], Result, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}) -> Result. -calcValueStorageSize(ValueSize, [Value|Rest]) -> - calcValueStorageSize(ValueSize + length(Value) + 45, Rest); -calcValueStorageSize(ValueSize, []) -> +calcValueStorageSize(ValueSize, [Value|Rest], {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + calcValueStorageSize(ValueSize + length(Value) + 45, Rest, THIS); +calcValueStorageSize(ValueSize, [], {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}) -> ValueSize. -sign (Key,Data) -> +sign (Key,Data, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}) -> %io:format("StringToSign:~n ~p~n", [Data]), binary_to_list( base64:encode( crypto:sha_mac(Key,Data) ) ). genericRequest(Action, Domain, Item, - Attributes, Options) when ?USE_SIGNATURE_V1 -> - genericRequestV1(Action, Domain, Item, Attributes, Options); + Attributes, Options, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) when ?USE_SIGNATURE_V1 -> + genericRequestV1(Action, Domain, Item, Attributes, Options, THIS); genericRequest(Action, Domain, Item, - Attributes, Options) -> + Attributes, Options, {?MODULE, [AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> io:format("Options: ~p~n", [Options]), Timestamp = lists:flatten(erlaws_util:get_timestamp()), ActionQueryParams = getQueryParams(Action, Domain, Item, Attributes, - lists:flatten(Options)), + lists:flatten(Options), THIS), Params = [{"AWSAccessKeyId", AWS_KEY}, {"Action", Action}, {"Timestamp", Timestamp} @@ -603,60 +608,60 @@ genericRequest(Action, Domain, Item, _ -> ActionQueryParams end, - Result = mkReq(Params), + Result = mkReq(Params, THIS), case Result of {ok, _Status, Body} -> {ok, Body}; {error, {_Proto, _Code, _Reason}, Body} -> %throw({error, {integer_to_list(Code), Reason}, mkErr(Body)}) - throw({error, mkErr(Body)}) + throw({error, mkErr(Body, THIS)}) end. -getQueryParams("CreateDomain", Domain, _Item, _Attributes, _Options) -> +getQueryParams("CreateDomain", Domain, _Item, _Attributes, _Options, _THIS) -> [{"DomainName", Domain}]; -getQueryParams("DeleteDomain", Domain, _Item, _Attributes, _Options) -> +getQueryParams("DeleteDomain", Domain, _Item, _Attributes, _Options, _THIS) -> [{"DomainName", Domain}]; -getQueryParams("ListDomains", _Domain, _Item, _Attributes, Options) -> +getQueryParams("ListDomains", _Domain, _Item, _Attributes, Options, _THIS) -> Options; -getQueryParams("PutAttributes", Domain, Item, Attributes, Options) -> +getQueryParams("PutAttributes", Domain, Item, Attributes, Options, THIS) -> Options ++ [{"DomainName", Domain}, {"ItemName", Item}] ++ - buildAttributeParams(Attributes); -getQueryParams("BatchPutAttributes", Domain, _Item, ItemAttributes, _Options) -> + buildAttributeParams(Attributes, THIS); +getQueryParams("BatchPutAttributes", Domain, _Item, ItemAttributes, _Options, THIS) -> [{"DomainName", Domain}| - buildBatchAttributeParams(ItemAttributes)]; -getQueryParams("GetAttributes", Domain, Item, Attribute, Options) -> + buildBatchAttributeParams(ItemAttributes, THIS)]; +getQueryParams("GetAttributes", Domain, Item, Attribute, Options, _THIS) -> Options ++ [{"DomainName", Domain}, {"ItemName", Item}] ++ if length(Attribute) > 0 -> [{"AttributeName", Attribute}]; true -> [] end; -getQueryParams("DeleteAttributes", Domain, Item, Attributes, Options) -> +getQueryParams("DeleteAttributes", Domain, Item, Attributes, Options, THIS) -> Options ++ [{"DomainName", Domain}, {"ItemName", Item}] ++ if length(Attributes) > 0 -> - buildAttributeParams(Attributes); + buildAttributeParams(Attributes, THIS); true -> [] end; -getQueryParams("Select", _Domain, _Item, _Attributes, Options) -> +getQueryParams("Select", _Domain, _Item, _Attributes, Options, _THIS) -> Options; -getQueryParams("Query", Domain, _Item, _Attributes, Options) -> +getQueryParams("Query", Domain, _Item, _Attributes, Options, _THIS) -> [{"DomainName", Domain}] ++ Options. -getProtocol() -> +getProtocol({?MODULE, [_AWS_KEY, _AWS_SEC_KEY, SECURE]}) -> case SECURE of true -> "https://"; _ -> "http://" end. -mkReq(Params) -> +mkReq(Params, {?MODULE, [_AWS_KEY, AWS_SEC_KEY, _SECURE]}=THIS) -> QueryParams = [{"SignatureVersion", "2"}|[{"SignatureMethod", "HmacSHA1"}|Params]], io:format("~p~n", [QueryParams]), ParamsString = erlaws_util:mkEnumeration([ erlaws_util:url_encode(Key) ++ "=" ++ erlaws_util:url_encode(Value) || {Key, Value} <- lists:keysort(1, QueryParams)], "&"), StringToSign = "POST\n" ++ string:to_lower(?AWS_SDB_HOST) ++ "\n" ++ "/" ++ "\n" ++ ParamsString, - Signature = sign(AWS_SEC_KEY, StringToSign), + Signature = sign(AWS_SEC_KEY, StringToSign, THIS), SignatureString = "&Signature=" ++ erlaws_util:url_encode(Signature), - Url = getProtocol() ++ ?AWS_SDB_HOST ++ "/", + Url = getProtocol(THIS) ++ ?AWS_SDB_HOST ++ "/", PostData = ParamsString ++ SignatureString, %io:format("~s~n~s~n", [Url, PostData]), Request = {Url, [], "application/x-www-form-urlencoded", PostData}, @@ -669,13 +674,13 @@ mkReq(Params) -> {_, _, _} -> {error, Status, binary_to_list(Body)} end. -buildAttributeParams(Attributes) -> - CAttr = collapse(Attributes), - {_C, L} = lists:foldl(fun flattenParams/2, {0, []}, CAttr), +buildAttributeParams(Attributes, THIS) -> + CAttr = collapse(Attributes, THIS), + {_C, L} = lists:foldl(fun flattenParams/3, {0, []}, CAttr, THIS), %io:format("FlattenedList:~n ~p~n", [L]), lists:reverse(L). -buildBatchAttributeParams(ItemAttributes) -> +buildBatchAttributeParams(ItemAttributes, THIS) -> BuildItemHdrFun = fun(F, IAs, ItemNum, Acc) -> ItemNumL = integer_to_list(ItemNum), @@ -688,18 +693,18 @@ buildBatchAttributeParams(ItemAttributes) -> [{Lead ++ X, Y}|AccIn] end, [], - buildAttributeParams(Attributes)) + buildAttributeParams(Attributes, THIS)) ++ [{Lead ++ "ItemName", Item}|Acc]) end end, lists:reverse(BuildItemHdrFun(BuildItemHdrFun, ItemAttributes, 0, [])). -mkEntryName(Counter, Key) -> +mkEntryName(Counter, Key, _THIS) -> {"Attribute." ++ integer_to_list(Counter) ++ ".Name", Key}. -mkEntryValue(Counter, Value) -> +mkEntryValue(Counter, Value, _THIS) -> {"Attribute."++integer_to_list(Counter) ++ ".Value", Value}. -flattenParams({K, V, R}, {C, L}) -> +flattenParams({K, V, R}, {C, L}, THIS) -> PreResult = if R -> {C, [{"Attribute." ++ integer_to_list(C) ++ ".Replace", "true"} | L]}; @@ -708,45 +713,45 @@ flattenParams({K, V, R}, {C, L}) -> FlattenVal = fun(Val, {Counter, ResultList}) -> %io:format("~p -> ~p ~n", [K, Val]), NextCounter = Counter + 1, - EntryName = mkEntryName(Counter, K), - EntryValue = mkEntryValue(Counter, Val), + EntryName = mkEntryName(Counter, K, THIS), + EntryValue = mkEntryValue(Counter, Val, THIS), {NextCounter, [EntryValue | [EntryName | ResultList]]} end, if length(V) > 0 -> lists:foldl(FlattenVal, PreResult, V); - length(V) =:= 0 -> {C + 1, [mkEntryName(C, K) | L]} + length(V) =:= 0 -> {C + 1, [mkEntryName(C, K, THIS) | L]} end. -aggrV({K,V,true}, [{K,L,_OR}|T]) when is_list(V), +aggrV({K,V,true}, [{K,L,_OR}|T], _THIS) when is_list(V), is_list(hd(V)) -> [{K,V ++ L, true}|T]; -aggrV({K,V,true}, [{K,L,_OR}|T]) -> [{K,[V|L], true}|T]; +aggrV({K,V,true}, [{K,L,_OR}|T], _THIS) -> [{K,[V|L], true}|T]; -aggrV({K,V,false}, [{K, L, OR}|T]) when is_list(V), +aggrV({K,V,false}, [{K, L, OR}|T], _THIS) when is_list(V), is_list(hd(V)) -> [{K, V ++ L, OR}|T]; -aggrV({K,V,false}, [{K, L, OR}|T]) -> [{K, [V|L], OR}|T]; +aggrV({K,V,false}, [{K, L, OR}|T], _THIS) -> [{K, [V|L], OR}|T]; -aggrV({K,V}, [{K,L,OR}|T]) when is_list(V), +aggrV({K,V}, [{K,L,OR}|T], _THIS) when is_list(V), is_list(hd(V))-> [{K,V ++ L,OR}|T]; -aggrV({K,V}, [{K,L,OR}|T]) -> [{K,[V|L],OR}|T]; +aggrV({K,V}, [{K,L,OR}|T], _THIS) -> [{K,[V|L],OR}|T]; -aggrV({K,V,R}, L) when is_list(V), +aggrV({K,V,R}, L, _THIS) when is_list(V), is_list(hd(V)) -> [{K, V, R}|L]; -aggrV({K,V,R}, L) -> [{K,[V], R}|L]; +aggrV({K,V,R}, L, _THIS) -> [{K,[V], R}|L]; -aggrV({K,V}, L) when is_list(V), +aggrV({K,V}, L, _THIS) when is_list(V), is_list(hd(V)) -> [{K,V,false}|L]; -aggrV({K,V}, L) -> [{K,[V],false}|L]; +aggrV({K,V}, L, _THIS) -> [{K,[V],false}|L]; -aggrV(K, L) -> [{K, [], false}|L]. +aggrV(K, L, _THIS) -> [{K, [], false}|L]. -collapse(L) -> - AggrL = lists:foldl( fun aggrV/2, [], lists:keysort(1, L) ), +collapse(L, THIS) -> + AggrL = lists:foldl( fun aggrV/3, [], lists:keysort(1, L), THIS), lists:keymap( fun lists:sort/1, 2, lists:reverse(AggrL)). -makeParam(X) -> +makeParam(X, _THIS) -> case X of {_, []} -> {}; {max_items, MaxItems} when is_integer(MaxItems) -> @@ -769,10 +774,10 @@ makeParam(X) -> end. -aggregateAttr ({K,V}, [{K,L}|T]) -> [{K,[V|L]}|T]; -aggregateAttr ({K,V}, L) -> [{K,[V]}|L]. +aggregateAttr ({K,V}, [{K,L}|T], _THIS) -> [{K,[V|L]}|T]; +aggregateAttr ({K,V}, L, _THIS) -> [{K,[V]}|L]. -mkErr(Xml) -> +mkErr(Xml, _THIS) -> {XmlDoc, _Rest} = xmerl_scan:string( Xml ), [#xmlText{value=ErrorCode}|_] = xmerl_xpath:string("//Error/Code/text()", XmlDoc), [#xmlText{value=ErrorMessage}|_] = xmerl_xpath:string("//Error/Message/text()", XmlDoc), @@ -785,10 +790,10 @@ mkErr(Xml) -> %%% left below for compatibility. genericRequestV1(Action, Domain, Item, - Attributes, Options) -> + Attributes, Options, {?MODULE, [AWS_KEY, AWS_SEC_KEY, _SECURE]}=THIS) -> Timestamp = lists:flatten(erlaws_util:get_timestamp()), ActionQueryParams = getQueryParams(Action, Domain, Item, Attributes, - lists:flatten(Options)), + lists:flatten(Options), THIS), SignParams = [{"AWSAccessKeyId", AWS_KEY}, {"Action", Action}, {"SignatureVersion", "1"}, @@ -805,20 +810,20 @@ genericRequestV1(Action, Domain, Item, string:to_lower(KeyA) =< string:to_lower(KeyB) end, SignParams)], ""), - Signature = sign(AWS_SEC_KEY, StringToSign), + Signature = sign(AWS_SEC_KEY, StringToSign, THIS), FinalQueryParams = SignParams ++ [{"Signature", Signature}], - Result = mkReqV1(FinalQueryParams), + Result = mkReqV1(FinalQueryParams, THIS), case Result of {ok, _Status, Body} -> {ok, Body}; {error, {_Proto, _Code, _Reason}, Body} -> %throw({error, {integer_to_list(Code), Reason}, mkErr(Body)}) - throw({error, mkErr(Body)}) + throw({error, mkErr(Body, THIS)}) end. -mkReqV1(QueryParams) -> +mkReqV1(QueryParams, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> %io:format("QueryParams:~n ~p~n", [QueryParams]), - Url = getProtocol() ++ ?AWS_SDB_HOST ++ "/" ++ erlaws_util:queryParams( QueryParams ), + Url = getProtocol(THIS) ++ ?AWS_SDB_HOST ++ "/" ++ erlaws_util:queryParams( QueryParams ), %io:format("RequestUrl:~n ~p~n", [Url]), Request = {Url, []}, HttpOptions = [{autoredirect, true}], diff --git a/src/erlaws_sqs.erl b/src/erlaws_sqs.erl index 8ac9e71..3c0bf35 100644 --- a/src/erlaws_sqs.erl +++ b/src/erlaws_sqs.erl @@ -5,14 +5,15 @@ %% @end %%%------------------------------------------------------------------- --module(erlaws_sqs,[AWS_KEY, AWS_SEC_KEY, SECURE]). +-module(erlaws_sqs). %% exports --export([list_queues/0, list_queues/1, get_queue_url/1, create_queue/1, - create_queue/2, get_queue_attr/1, set_queue_attr/3, - delete_queue/1, send_message/2, - receive_message/1, receive_message/2, receive_message/3, - delete_message/2]). +-export([new/3]). +-export([list_queues/1, list_queues/2, get_queue_url/2, create_queue/2, + create_queue/3, get_queue_attr/2, set_queue_attr/4, + delete_queue/2, send_message/3, + receive_message/2, receive_message/3, receive_message/4, + delete_message/3]). %% include record definitions -include_lib("xmerl/include/xmerl.hrl"). @@ -22,6 +23,9 @@ -define(AWS_SQS_VERSION, "2008-01-01"). -define(USE_SIGNATURE_V1, false). +new(AWS_KEY, AWS_SEC_KEY, SECURE) -> + {?MODULE, [AWS_KEY, AWS_SEC_KEY, SECURE]}. + %% queues %% Creates a new SQS queue with the given name. @@ -34,8 +38,8 @@ %% {ok, QueueUrl::string(), {requestId, RequestId::string()}} | %% {error, {HTTPStatus::string, HTTPReason::string()}, {Code::string(), Message::string(), {requestId, ReqId::string()}}} %% -create_queue(QueueName) -> - try query_request("CreateQueue", [{"QueueName", QueueName}]) of +create_queue(QueueName, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + try query_request("CreateQueue", [{"QueueName", QueueName}], THIS) of {ok, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(Body), [#xmlText{value=QueueUrl}|_] = @@ -54,9 +58,9 @@ create_queue(QueueName) -> %% {ok, QueueUrl::string(), {requestId, ReqId::string()}} | %% {error, {HTTPStatus::string, HTTPReason::string()}, {Code::string(), Message::string(), {requestId, ReqId::string()}}} %% -create_queue(QueueName, VisibilityTimeout) when is_integer(VisibilityTimeout) -> +create_queue(QueueName, VisibilityTimeout, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) when is_integer(VisibilityTimeout) -> try query_request("CreateQueue", [{"QueueName", QueueName}, - {"DefaultVisibilityTimeout", integer_to_list(VisibilityTimeout)}]) of + {"DefaultVisibilityTimeout", integer_to_list(VisibilityTimeout)}], THIS) of {ok, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(Body), [#xmlText{value=QueueUrl}|_] = @@ -76,8 +80,8 @@ create_queue(QueueName, VisibilityTimeout) when is_integer(VisibilityTimeout) -> %% {ok, [QueueUrl::string(),...], {requestId, ReqId::string()}} | %% {error, {HTTPStatus::string, HTTPReason::string()}, {Code::string(), Message::string(), {requestId, ReqId::string()}}} %% -list_queues() -> - try query_request("ListQueues", []) of +list_queues({?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + try query_request("ListQueues", [], THIS) of {ok, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(Body), QueueNodes = xmerl_xpath:string("//QueueUrl/text()", XmlDoc), @@ -98,8 +102,8 @@ list_queues() -> %% {ok, [QueueUrl::string(),...], {requestId, ReqId::string()}} | %% {error, {HTTPStatus::string, HTTPReason::string()}, {Code::string(), Message::string(), {requestId, ReqId::string()}}} %% -list_queues(Prefix) -> - try query_request("ListQueues", [{"QueueNamePrefix", Prefix}]) of +list_queues(Prefix, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + try query_request("ListQueues", [{"QueueNamePrefix", Prefix}], THIS) of {ok, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(Body), QueueNodes = xmerl_xpath:string("//QueueUrl/text()", XmlDoc), @@ -118,8 +122,8 @@ list_queues(Prefix) -> %% {error, {HTTPStatus::string, HTTPReason::string()}, {Code::string(), Message::string(), {requestId, ReqId::string()}}} | %% {error, no_match} %% -get_queue_url(QueueName) -> - try query_request("ListQueues", [{"QueueNamePrefix", QueueName}]) of +get_queue_url(QueueName, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + try query_request("ListQueues", [{"QueueNamePrefix", QueueName}], THIS) of {ok, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(Body), QueueNodes = xmerl_xpath:string("//QueueUrl/text()", XmlDoc), @@ -146,9 +150,9 @@ get_queue_url(QueueName) -> %% {"ApproximateNumberOfMessages", Number::integer()}], {requestId, ReqId::string()}} | %% {error, {HTTPStatus::string, HTTPReason::string()}, {Code::string(), Message::string(), {requestId, ReqId::string()}}} %% -get_queue_attr(QueueUrl) -> +get_queue_attr(QueueUrl, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> try query_request(QueueUrl, "GetQueueAttributes", - [{"AttributeName", "All"}]) of + [{"AttributeName", "All"}], THIS) of {ok, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(Body), AttributeNodes = xmerl_xpath:string("//Attribute", XmlDoc), @@ -177,11 +181,11 @@ get_queue_attr(QueueUrl) -> %% {ok, {requestId, ReqId::string()}} | %% {error, {HTTPStatus::string, HTTPReason::string()}, {Code::string(), Message::string(), {requestId, ReqId::string()}}} %% -set_queue_attr(visibility_timeout, QueueUrl, Timeout) +set_queue_attr(visibility_timeout, QueueUrl, Timeout, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) when is_integer(Timeout) -> try query_request(QueueUrl, "SetQueueAttributes", [{"Attribute.Name", "VisibilityTimeout"}, - {"Attribute.Value", integer_to_list(Timeout)}]) of + {"Attribute.Value", integer_to_list(Timeout)}], THIS) of {ok, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(Body), [#xmlText{value=RequestId}|_] = @@ -198,8 +202,8 @@ set_queue_attr(visibility_timeout, QueueUrl, Timeout) %% {ok, {requestId, ReqId::string()}} | %% {error, {HTTPStatus::string, HTTPReason::string()}, {Code::string(), Message::string(), {requestId, ReqId::string()}}} %% -delete_queue(QueueUrl) -> - try query_request(QueueUrl, "DeleteQueue", []) of +delete_queue(QueueUrl, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + try query_request(QueueUrl, "DeleteQueue", [], THIS) of {ok, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(Body), [#xmlText{value=RequestId}|_] = @@ -219,8 +223,8 @@ delete_queue(QueueUrl) -> %% {ok, Message::#sqs_message, {requestId, ReqId::string()}} | %% {error, {HTTPStatus::string, HTTPReason::string()}, {Code::string(), Message::string(), {requestId, ReqId::string()}}} %% -send_message(QueueUrl, Message) -> - try query_request(QueueUrl, "SendMessage", [{"MessageBody", Message}]) of +send_message(QueueUrl, Message, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + try query_request(QueueUrl, "SendMessage", [{"MessageBody", Message}], THIS) of {ok, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(Body), [#xmlText{value=MessageId}|_] = @@ -242,8 +246,8 @@ send_message(QueueUrl, Message) -> %% {ok, [], {requestId, ReqId::string()}} %% {error, {HTTPStatus::string, HTTPReason::string()}, {Code::string(), Message::string(), {requestId, ReqId::string()}}} %% -receive_message(QueueUrl) -> - receive_message(QueueUrl, 1). +receive_message(QueueUrl, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + receive_message(QueueUrl, 1, THIS). %% Tries to receive the given number of messages (<=10) from the given queue. %% @@ -252,8 +256,8 @@ receive_message(QueueUrl) -> %% {ok, [], {requestId, ReqId::string()}} %% {error, {HTTPStatus::string, HTTPReason::string()}, {Code::string(), Message::string(), {requestId, ReqId::string()}}} %% -receive_message(QueueUrl, NrOfMessages) -> - receive_message(QueueUrl, NrOfMessages, []). +receive_message(QueueUrl, NrOfMessages, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> + receive_message(QueueUrl, NrOfMessages, [], THIS). %% Tries to receive the given number of messages (<=10) from the given queue, using the given VisibilityTimeout instead %% of the queues default value. @@ -263,12 +267,12 @@ receive_message(QueueUrl, NrOfMessages) -> %% {ok, [], {requestId, ReqId::string()}} %% {error, {HTTPStatus::string, HTTPReason::string()}, {Code::string(), Message::string(), {requestId, ReqId::string()}}} %% -receive_message(QueueUrl, NrOfMessages, VisibilityTimeout) when is_integer(NrOfMessages) -> +receive_message(QueueUrl, NrOfMessages, VisibilityTimeout, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) when is_integer(NrOfMessages) -> VisibilityTimeoutParam = case VisibilityTimeout of "" -> []; _ -> [{"VisibilityTimeout", integer_to_list(VisibilityTimeout)}] end, try query_request(QueueUrl, "ReceiveMessage", - [{"MaxNumberOfMessages", integer_to_list(NrOfMessages)}] ++ VisibilityTimeoutParam) of + [{"MaxNumberOfMessages", integer_to_list(NrOfMessages)}] ++ VisibilityTimeoutParam, THIS) of {ok, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(Body), [#xmlText{value=RequestId}|_] = @@ -297,9 +301,9 @@ receive_message(QueueUrl, NrOfMessages, VisibilityTimeout) when is_integer(NrOfM %% {ok, {requestId, RequestId::string()}} | %% {error, {HTTPStatus::string, HTTPReason::string()}, {Code::string(), Message::string(), {requestId, ReqId::string()}}} %% -delete_message(QueueUrl, ReceiptHandle) -> +delete_message(QueueUrl, ReceiptHandle, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> try query_request(QueueUrl, "DeleteMessage", - [{"ReceiptHandle", ReceiptHandle}]) of + [{"ReceiptHandle", ReceiptHandle}], THIS) of {ok, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(Body), [#xmlText{value=RequestId}|_] = @@ -312,40 +316,39 @@ delete_message(QueueUrl, ReceiptHandle) -> %% internal methods -sign(Key,Data) -> +sign(Key,Data,_THIS) -> %%%% io:format("Sign:~n ~p~n", [Data]), binary_to_list(base64:encode(crypto:sha_mac(Key,Data))). -query_request(Action, Parameters) -> - if - SECURE -> Prefix = "https://"; - true -> Prefix = "http://" - end, - - query_request(Prefix ++ ?AWS_SQS_HOST ++ "/", Action, Parameters). +query_request(Action, Parameters, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, SECURE]}=THIS) -> + Prefix = case SECURE of + true -> "https://"; + _ -> "http://" + end, + query_request(Prefix ++ ?AWS_SQS_HOST ++ "/", Action, Parameters, THIS). -query_request(Url, Action, Parameters) when ?USE_SIGNATURE_V1 -> - query_requestV1(Url, Action, Parameters); -query_request(Url, Action, Parameters) -> +query_request(Url, Action, Parameters, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) when ?USE_SIGNATURE_V1 -> + query_requestV1(Url, Action, Parameters, THIS); +query_request(Url, Action, Parameters, {?MODULE, [AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> Timestamp = lists:flatten(erlaws_util:get_timestamp()), Params = [{"Action", Action}, {"AWSAccessKeyId", AWS_KEY}, {"Timestamp", Timestamp}] ++Parameters ++ [{"Version", ?AWS_SQS_VERSION}], - Result = mkReq(Url, Params), + Result = mkReq(Url, Params, THIS), case Result of {ok, _Status, Body} -> {ok, Body}; {error, {_Proto, Code, Reason}, Body} -> - throw({error, {integer_to_list(Code), Reason}, mkErr(Body)}) + throw({error, {integer_to_list(Code), Reason}, mkErr(Body, THIS)}) end. -mkReq(Url, Params) -> +mkReq(Url, Params, {?MODULE, [_AWS_KEY, AWS_SEC_KEY, _SECURE]}=THIS) -> QueryParams = [{"SignatureVersion", "2"}|[{"SignatureMethod", "HmacSHA1"}|Params]], ParamsString = erlaws_util:mkEnumeration([ erlaws_util:url_encode(Key) ++ "=" ++ erlaws_util:url_encode(Value) || {Key, Value} <- lists:keysort(1, QueryParams)], "&"), {_, _, Host, _, Path, _} = http_uri:parse(Url), StringToSign = "POST\n" ++ string:to_lower(Host) ++ "\n" ++ Path ++ "\n" ++ ParamsString, - Signature = sign(AWS_SEC_KEY, StringToSign), + Signature = sign(AWS_SEC_KEY, StringToSign, THIS), SignatureString = "&Signature=" ++ erlaws_util:url_encode(Signature), PostData = ParamsString ++ SignatureString, %io:format("~p~n~s~n~s~n", [Params, Url, PostData]), @@ -359,7 +362,7 @@ mkReq(Url, Params) -> {_, _, _} -> {error, Status, binary_to_list(Body)} end. -mkErr(Xml) -> +mkErr(Xml, _THIS) -> {XmlDoc, _Rest} = xmerl_scan:string( Xml ), [#xmlText{value=ErrorCode}|_] = xmerl_xpath:string("//Error/Code/text()", XmlDoc), @@ -383,7 +386,7 @@ mkErr(Xml) -> %%% implemented above (plain requests work fine). Signature ver.1 based code are %%% left below for compatibility. -query_requestV1(Url, Action, Parameters) -> +query_requestV1(Url, Action, Parameters, {?MODULE, [AWS_KEY, AWS_SEC_KEY, _SECURE]}=THIS) -> %% io:format("query_request: ~p ~p ~p~n", [Url, Action, Parameters]), Timestamp = lists:flatten(erlaws_util:get_timestamp()), SignParams = [{"Action", Action}, {"AWSAccessKeyId", AWS_KEY}, {"Timestamp", Timestamp}] ++ @@ -394,19 +397,19 @@ query_requestV1(Url, Action, Parameters) -> string:to_lower(KeyA) =< string:to_lower(KeyB) end, SignParams)], ""), %% io:format("StringToSign: ~p~n", [StringToSign]), - Signature = sign(AWS_SEC_KEY, StringToSign), + Signature = sign(AWS_SEC_KEY, StringToSign, THIS), %% io:format("Signature: ~p~n", [Signature]), FinalQueryParams = SignParams ++ [{"Signature", Signature}], - Result = mkReqV1(get, Url, [], FinalQueryParams, "", ""), + Result = mkReqV1(get, Url, [], FinalQueryParams, "", "", THIS), case Result of {ok, _Status, Body} -> {ok, Body}; {error, {_Proto, Code, Reason}, Body} -> - throw({error, {integer_to_list(Code), Reason}, mkErr(Body)}) + throw({error, {integer_to_list(Code), Reason}, mkErr(Body, THIS)}) end. -mkReqV1(Method, PreUrl, Headers, QueryParams, ContentType, ReqBody) -> +mkReqV1(Method, PreUrl, Headers, QueryParams, ContentType, ReqBody, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> %%%% io:format("QueryParams:~n ~p~nHeaders:~n ~p~nUrl:~n ~p~n", %% [QueryParams, Headers, PreUrl]), Url = PreUrl ++ erlaws_util:queryParams( QueryParams ), @@ -419,7 +422,7 @@ mkReqV1(Method, PreUrl, Headers, QueryParams, ContentType, ReqBody) -> HttpOptions = [{autoredirect, true}], Options = [ {sync,true}, {headers_as_is,true}, {body_format, binary} ], {ok, {Status, _ReplyHeaders, Body}} = - http:request(Method, Request, HttpOptions, Options), + http:request(Method, Request, HttpOptions, Options, THIS), %% io:format("Response:~n ~p~n", [binary_to_list(Body)]), %% io:format("Status: ~p~n", [Status]), case Status of From 0288641eb14f3d79756203883f66c4d83dbded16 Mon Sep 17 00:00:00 2001 From: Kazuhiro Ogura Date: Thu, 30 May 2013 14:00:33 +0900 Subject: [PATCH 02/11] Add erlaws_s3 initial test suite --- test/erlaws_s3_SUITE.erl | 253 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 test/erlaws_s3_SUITE.erl diff --git a/test/erlaws_s3_SUITE.erl b/test/erlaws_s3_SUITE.erl new file mode 100644 index 0000000..caa7d2f --- /dev/null +++ b/test/erlaws_s3_SUITE.erl @@ -0,0 +1,253 @@ +%% common_test suite for erlaws_s3 + +-module(erlaws_s3_SUITE). +-include_lib("common_test/include/ct.hrl"). + +-compile(export_all). + +%%-------------------------------------------------------------------- +%% Function: suite() -> Info +%% +%% Info = [tuple()] +%% List of key/value pairs. +%% +%% Description: Returns list of tuples to set default properties +%% for the suite. +%% +%% Note: The suite/0 function is only meant to be used to return +%% default data values, not perform any other operations. +%%-------------------------------------------------------------------- +suite() -> [{timetrap, {seconds, 20}}]. + +%%-------------------------------------------------------------------- +%% Function: groups() -> [Group] +%% +%% Group = {GroupName,Properties,GroupsAndTestCases} +%% GroupName = atom() +%% The name of the group. +%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}] +%% Group properties that may be combined. +%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase] +%% TestCase = atom() +%% The name of a test case. +%% Shuffle = shuffle | {shuffle,Seed} +%% To get cases executed in random order. +%% Seed = {integer(),integer(),integer()} +%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | +%% repeat_until_any_ok | repeat_until_any_fail +%% To get execution of cases repeated. +%% N = integer() | forever +%% +%% Description: Returns a list of test case group definitions. +%%-------------------------------------------------------------------- +groups() -> []. + +%%-------------------------------------------------------------------- +%% Function: all() -> GroupsAndTestCases +%% +%% GroupsAndTestCases = [{group,GroupName} | TestCase] +%% GroupName = atom() +%% Name of a test case group. +%% TestCase = atom() +%% Name of a test case. +%% +%% Description: Returns the list of groups and test cases that +%% are to be executed. +%% +%% NB: By default, we export all 1-arity user defined functions +%%-------------------------------------------------------------------- +all() -> + [ {exports, Functions} | _ ] = ?MODULE:module_info(), + [ FName || {FName, _} <- lists:filter( + fun ({module_info,_}) -> false; + ({all,_}) -> false; + ({init_per_suite,1}) -> false; + ({end_per_suite,1}) -> false; + ({_,1}) -> true; + ({_,_}) -> false + end, Functions)]. + +%%-------------------------------------------------------------------- +%% Function: init_per_suite(Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% Reason = term() +%% The reason for skipping the suite. +%% +%% Description: Initialization before the suite. +%% +%% Note: This function is free to add any key/value pairs to the Config +%% variable, but should NOT alter/remove any existing entries. +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + erlaws:start(), + + AwsKey = os:getenv("ERLAWS_TEST_AWS_KEY"), + AwsSecKey = os:getenv("ERLAWS_TEST_AWS_SEC_KEY"), + try + case (AwsKey =/= false) andalso (AwsSecKey =/= false) of + false -> + throw({skip, "Environment variable ERLAWS_TEST_AWS_KEY and ERLAWS_TEST_AWS_SEC_KEY is not set"}); + _ -> ok + end, + AwsSecure = + case os:getenv("ERLAWS_TEST_AWS_SECURE") of + false -> true; + "1" -> true; + "true" -> true; + _ -> false + end, + [{aws_key, AwsKey}, {aws_sec_key, AwsSecKey}, {secure, AwsSecure} | Config] + catch + {skip, _} = E0 -> + ct:print(E0), + E0 + end. + + +%%-------------------------------------------------------------------- +%% Function: end_per_suite(Config0) -> void() | {save_config,Config1} +%% +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% +%% Description: Cleanup after the suite. +%%-------------------------------------------------------------------- +end_per_suite(_Config) -> + ok. + +%%-------------------------------------------------------------------- +%% Function: init_per_group(GroupName, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% +%% GroupName = atom() +%% Name of the test case group that is about to run. +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding configuration data for the group. +%% Reason = term() +%% The reason for skipping all test cases and subgroups in the group. +%% +%% Description: Initialization before each test case group. +%%-------------------------------------------------------------------- +init_per_group(_group, Config) -> + Config. + + +%%-------------------------------------------------------------------- +%% Function: end_per_group(GroupName, Config0) -> +%% void() | {save_config,Config1} +%% +%% GroupName = atom() +%% Name of the test case group that is finished. +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding configuration data for the group. +%% +%% Description: Cleanup after each test case group. +%%-------------------------------------------------------------------- +end_per_group(_group, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: init_per_testcase(TestCase, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% +%% TestCase = atom() +%% Name of the test case that is about to run. +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% Reason = term() +%% The reason for skipping the test case. +%% +%% Description: Initialization before each test case. +%% +%% Note: This function is free to add any key/value pairs to the Config +%% variable, but should NOT alter/remove any existing entries. +%%-------------------------------------------------------------------- +init_per_testcase(TestCase, Config) -> + AwsKey = proplists:get_value(aws_key, Config), + AwsSecKey = proplists:get_value(aws_sec_key, Config), + Secure = proplists:get_value(secure, Config), + S3 = erlaws_s3:new(AwsKey, AwsSecKey, Secure), + [{s3, S3}|Config]. + +%%-------------------------------------------------------------------- +%% Function: end_per_testcase(TestCase, Config0) -> +%% void() | {save_config,Config1} | {fail,Reason} +%% +%% TestCase = atom() +%% Name of the test case that is finished. +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% Reason = term() +%% The reason for failing the test case. +%% +%% Description: Cleanup after each test case. +%%-------------------------------------------------------------------- +end_per_testcase(_TestCase, Config) -> + Config. + +test_erlaws_s3() -> + [{userdata,[{doc,"Testing the erlaws_s3 module"}]}]. + +%%test_erlaws_s3(_Config) -> +%% {skip,"Not implemented."}. + +test_connection_1_basic(Config) -> + C = proplists:get_value(s3, Config), + ct:print("C: ~p", [C]), + + %% Create a new, empty bucket + BucketName = lists:flatten(io_lib:format("test-~w-~w-~w", tuple_to_list(now()))), + ct:print("BucketName: ~s", [BucketName]), + {ok, BucketName, _} = C:create_bucket(BucketName), + + %% Try a list_buckets and see if it's really there + {ok, BucketNames, _} = C:list_buckets(), + true = lists:member(BucketName, BucketNames), + + %% Upload 01 + Data01 = <<"This is a test of file upload and download">>, + {ok, _, _} = + C:put_object(BucketName, "foobar", Data01, "text/plain", []), + + %% Upload 02 + Data02 = <<"Another test data">>, + {ok, _, _} = + C:put_object(BucketName, "barbaz", Data02, "text/plain", [{"name", "metavalue"}]), + + %% Download 01 + {ok, Data01, _} = + C:get_object(BucketName, "foobar"), + + %% Download 02 + {ok, Data02, _} = + C:get_object(BucketName, "barbaz"), + + %% Try to download not existing key + {error, {"NoSuchKey", _, _}} = + C:get_object(BucketName, "bogus"), + + %% Info 01 + {ok, [], _} = C:info_object(BucketName, "foobar"), + + %% Info 02 + {ok, [{"name", "metavalue"}], _} = C:info_object(BucketName, "barbaz"), + + %% Try to delete before emptying + {error, _} = C:delete_bucket(BucketName), + + %% Delete 01 + {ok, _} = C:delete_object(BucketName, "foobar"), + + %% Delete 02 + {ok, _} = C:delete_object(BucketName, "barbaz"), + + %% Now delete bucket + {ok, _} = C:delete_bucket(BucketName), + + %% Try a list_buckets and ensure it's not there + {ok, BucketNames99, _} =C:list_buckets(), + false = lists:member(BucketName, BucketNames99). + From 0a513dbe622fe04119f77d0c2b4462387b6f95a1 Mon Sep 17 00:00:00 2001 From: Kazuhiro Ogura Date: Thu, 30 May 2013 16:33:37 +0900 Subject: [PATCH 03/11] Add initial version of erlaws_sqs ctsuite --- test/erlaws_sqs_SUITE.erl | 269 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 test/erlaws_sqs_SUITE.erl diff --git a/test/erlaws_sqs_SUITE.erl b/test/erlaws_sqs_SUITE.erl new file mode 100644 index 0000000..7bc44e9 --- /dev/null +++ b/test/erlaws_sqs_SUITE.erl @@ -0,0 +1,269 @@ +%% common_test suite for erlaws_sqs + +-module(erlaws_sqs_SUITE). +-include_lib("common_test/include/ct.hrl"). +-include_lib("../include/erlaws.hrl"). + +-compile(export_all). + +%%-------------------------------------------------------------------- +%% Function: suite() -> Info +%% +%% Info = [tuple()] +%% List of key/value pairs. +%% +%% Description: Returns list of tuples to set default properties +%% for the suite. +%% +%% Note: The suite/0 function is only meant to be used to return +%% default data values, not perform any other operations. +%%-------------------------------------------------------------------- +suite() -> [{timetrap, {seconds, 20}}]. + +%%-------------------------------------------------------------------- +%% Function: groups() -> [Group] +%% +%% Group = {GroupName,Properties,GroupsAndTestCases} +%% GroupName = atom() +%% The name of the group. +%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}] +%% Group properties that may be combined. +%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase] +%% TestCase = atom() +%% The name of a test case. +%% Shuffle = shuffle | {shuffle,Seed} +%% To get cases executed in random order. +%% Seed = {integer(),integer(),integer()} +%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | +%% repeat_until_any_ok | repeat_until_any_fail +%% To get execution of cases repeated. +%% N = integer() | forever +%% +%% Description: Returns a list of test case group definitions. +%%-------------------------------------------------------------------- +groups() -> []. + +%%-------------------------------------------------------------------- +%% Function: all() -> GroupsAndTestCases +%% +%% GroupsAndTestCases = [{group,GroupName} | TestCase] +%% GroupName = atom() +%% Name of a test case group. +%% TestCase = atom() +%% Name of a test case. +%% +%% Description: Returns the list of groups and test cases that +%% are to be executed. +%% +%% NB: By default, we export all 1-arity user defined functions +%%-------------------------------------------------------------------- +all() -> + [ {exports, Functions} | _ ] = ?MODULE:module_info(), + [ FName || {FName, _} <- lists:filter( + fun ({module_info,_}) -> false; + ({all,_}) -> false; + ({init_per_suite,1}) -> false; + ({end_per_suite,1}) -> false; + ({_,1}) -> true; + ({_,_}) -> false + end, Functions)]. + +%%-------------------------------------------------------------------- +%% Function: init_per_suite(Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% Reason = term() +%% The reason for skipping the suite. +%% +%% Description: Initialization before the suite. +%% +%% Note: This function is free to add any key/value pairs to the Config +%% variable, but should NOT alter/remove any existing entries. +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + erlaws:start(), + + AwsKey = os:getenv("ERLAWS_TEST_AWS_KEY"), + AwsSecKey = os:getenv("ERLAWS_TEST_AWS_SEC_KEY"), + try + case (AwsKey =/= false) andalso (AwsSecKey =/= false) of + false -> + throw({skip, "Environment variable ERLAWS_TEST_AWS_KEY and ERLAWS_TEST_AWS_SEC_KEY is not set"}); + _ -> ok + end, + AwsSecure = + case os:getenv("ERLAWS_TEST_AWS_SECURE") of + false -> true; + "1" -> true; + "true" -> true; + _ -> false + end, + [{aws_key, AwsKey}, {aws_sec_key, AwsSecKey}, {secure, AwsSecure} | Config] + catch + {skip, _} = E0 -> + ct:print(E0), + E0 + end. + + +%%-------------------------------------------------------------------- +%% Function: end_per_suite(Config0) -> void() | {save_config,Config1} +%% +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% +%% Description: Cleanup after the suite. +%%-------------------------------------------------------------------- +end_per_suite(_Config) -> + ok. + +%%-------------------------------------------------------------------- +%% Function: init_per_group(GroupName, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% +%% GroupName = atom() +%% Name of the test case group that is about to run. +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding configuration data for the group. +%% Reason = term() +%% The reason for skipping all test cases and subgroups in the group. +%% +%% Description: Initialization before each test case group. +%%-------------------------------------------------------------------- +init_per_group(_group, Config) -> + Config. + + +%%-------------------------------------------------------------------- +%% Function: end_per_group(GroupName, Config0) -> +%% void() | {save_config,Config1} +%% +%% GroupName = atom() +%% Name of the test case group that is finished. +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding configuration data for the group. +%% +%% Description: Cleanup after each test case group. +%%-------------------------------------------------------------------- +end_per_group(_group, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: init_per_testcase(TestCase, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% +%% TestCase = atom() +%% Name of the test case that is about to run. +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% Reason = term() +%% The reason for skipping the test case. +%% +%% Description: Initialization before each test case. +%% +%% Note: This function is free to add any key/value pairs to the Config +%% variable, but should NOT alter/remove any existing entries. +%%-------------------------------------------------------------------- +init_per_testcase(TestCase, Config) -> + AwsKey = proplists:get_value(aws_key, Config), + AwsSecKey = proplists:get_value(aws_sec_key, Config), + Secure = proplists:get_value(secure, Config), + SQS = erlaws_sqs:new(AwsKey, AwsSecKey, Secure), + [{sqs, SQS}|Config]. + +%%-------------------------------------------------------------------- +%% Function: end_per_testcase(TestCase, Config0) -> +%% void() | {save_config,Config1} | {fail,Reason} +%% +%% TestCase = atom() +%% Name of the test case that is finished. +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% Reason = term() +%% The reason for failing the test case. +%% +%% Description: Cleanup after each test case. +%%-------------------------------------------------------------------- +end_per_testcase(_TestCase, Config) -> + Config. + +test_erlaws_sqs() -> + [{userdata,[{doc,"Testing the erlaws_sqs module"}]}]. + +test_1_basic(Config) -> + C = proplists:get_value(sqs, Config), + ct:print("C: ~p", [C]), + + %% Try illegal queue name + {error, {"400", _}, _} = C:create_queue("bad*queue*name"), + + %% Create a new queue + QueueName1 = lists:flatten(io_lib:format("test-~w-~w-~w-1", tuple_to_list(now()))), + QueueName2 = lists:flatten(io_lib:format("test-~w-~w-~w-2", tuple_to_list(now()))), + + ct:print("QueueName: ~s ~s", [QueueName1, QueueName2]), + {ok, QueueUrl1, _} = C:create_queue(QueueName1, 55), %% Set 55 for QueueName1 + {ok, QueueUrl2, _} = C:create_queue(QueueName2), %% Default for QueueName2 + ct:print("QueueUrl: ~s ~s", [QueueUrl1, QueueUrl2]), + + %% Test get_queue_url + {ok, QueueUrl1, _} = C:get_queue_url(QueueName1), + {ok, QueueUrl2, _} = C:get_queue_url(QueueName2), + + %% Try a list_queues and see if it's really there + {ok, QueueUrls1, _} = C:list_queues(QueueName1), + ct:print("QueueUrls1: ~p", [QueueUrls1]), + true = lists:member(QueueUrl1, QueueUrls1), + + {ok, QueueUrls2, _} = C:list_queues(QueueName2), + ct:print("QueueUrls1: ~p", [QueueUrls2]), + true = lists:member(QueueUrl2, QueueUrls2), + + %% Check visibility timeout + {ok, AttrList1, _} = C:get_queue_attr(QueueUrl1), + VisibilityTimeout1 = proplists:get_value("VisibilityTimeout", AttrList1), + ct:print("AttrList: ~p", [AttrList1]), + ct:print("VisibilityTimeout1: ~p", [VisibilityTimeout1]), + + {ok, AttrList2, _} = C:get_queue_attr(QueueUrl2), + VisibilityTimeout2 = proplists:get_value("VisibilityTimeout", AttrList2), + ct:print("AttrList: ~p", [AttrList2]), + ct:print("VisibilityTimeout2: ~p", [VisibilityTimeout2]), + + %% Check visibility timeout of QueueName1 + {ok, AttrList1a, _} = C:get_queue_attr(QueueUrl1), + VisibilityTimeout1a = proplists:get_value("VisibilityTimeout", AttrList1a), + VisibilityTimeout1a = 55, + + %% Set and check visibility timeout of QueueName2 + {ok, _} = C:set_queue_attr(visibility_timeout, QueueUrl2, 45), + {ok, AttrList2a, _} = C:get_queue_attr(QueueUrl2), + VisibilityTimeout2a = proplists:get_value("VisibilityTimeout", AttrList2a), + VisibilityTimeout2a = 45, + + %% Add a message + MsgBody1 = "This is a test.\n", + {ok, _Msg1, _} = C:send_message(QueueUrl1, MsgBody1), + + %% Read a message + {ok, GotMsgs1, _} = C:receive_message(QueueUrl1), + [GotMsg1] = GotMsgs1, + MsgBody1 = GotMsg1#sqs_message.body, + + %% Another read, shouldn't find anything + {ok, [], _} = C:receive_message(QueueUrl1), + + %% Delete the message + {ok, _} = C:delete_message(QueueUrl1, GotMsg1#sqs_message.receiptHandle), + + %% Delete queues + {ok, _} = C:delete_queue(QueueUrl1), + {ok, _} = C:delete_queue(QueueUrl2), + + %% Try a list_queues and ensure it's not there + {ok, QueueUrls99, _} =C:list_queues(), + false = lists:member(QueueUrl1, QueueUrls99), + false = lists:member(QueueUrl2, QueueUrls99). + From 52a29714ba43da92aa23cc0f812561927a3def78 Mon Sep 17 00:00:00 2001 From: Kazuhiro Ogura Date: Thu, 30 May 2013 16:53:51 +0900 Subject: [PATCH 04/11] Make pmods fix working --- src/erlaws.erl | 2 +- src/erlaws_s3.erl | 40 ++++++++++++++++++++-------------------- src/erlaws_sqs.erl | 10 +++++++++- 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/erlaws.erl b/src/erlaws.erl index c1f3fa7..c4b4b9e 100644 --- a/src/erlaws.erl +++ b/src/erlaws.erl @@ -6,7 +6,7 @@ start() -> application:start(sasl), - crypto:start(), + ssl:start(), inets:start(). start(_Type, _Args) -> diff --git a/src/erlaws_s3.erl b/src/erlaws_s3.erl index 748be12..1d765d5 100644 --- a/src/erlaws_s3.erl +++ b/src/erlaws_s3.erl @@ -35,7 +35,7 @@ new(AWS_KEY, AWS_SEC_KEY, SECURE) -> %% of the request. %% %% Spec: list_buckets() -> -%% {ok, Buckets::[Name::string()]} | +%% {ok, Buckets::[Name::string()], ReqId::string()} | %% {error, {Code::string(), Msg::string(), ReqId::string()}} %% list_buckets({?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> @@ -58,7 +58,7 @@ list_buckets({?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> %% for information on bucket naming restrictions. %% %% Spec: create_bucket(Bucket::string()) -> -%% {ok, Bucket::string()} | +%% {ok, Bucket::string(), ReqId::string()} | %% {error, {Code::string(), Msg::string(), ReqId::string()}} %% create_bucket(Bucket, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> @@ -112,7 +112,7 @@ create_bucket(Bucket, Region, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) %% Deletes a bucket. %% %% Spec: delete_bucket(Bucket::string()) -> -%% {ok} | +%% {ok, {requestId, RequestId}} | %% {error, {Code::string(), Msg::string(), ReqId::string()}} %% delete_bucket(Bucket, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> @@ -299,7 +299,7 @@ put_file(Bucket, Key, FileName, ContentType, Metadata, {?MODULE, [AWS_KEY, AWS_S stringToSign("PUT", "", ContentType, Date, Bucket, Key, Headers, THIS), THIS), FinalHeaders = [ {"Authorization", "AWS " ++ AWS_KEY ++ ":" ++ Signature }, - {"Host", buildHost(Bucket, THIS) }, + {"Host", buildHost(Bucket) }, {"Date", Date }, {"Content-Type", ContentType} | Headers ], @@ -357,7 +357,7 @@ info_object(Bucket, Key, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> %% Delete the given key from bucket. %% %% Spec: delete_object(Bucket::string(), Key::string()) -> -%% {ok} | +%% {ok, {requestId, ReqId::string()}} | %% {error, {Code::string(), Msg::string(), ReqId::string()}} %% delete_object(Bucket, Key, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> @@ -376,25 +376,25 @@ delete_object(Bucket, Key, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> %%% Internal functions %%-------------------------------------------------------------------- -isAmzHeader( Header, _THIS ) -> lists:prefix("x-amz-", Header). +isAmzHeader( Header ) -> lists:prefix("x-amz-", Header). -aggregateValues ({K,V}, [{K,L}|T], _THIS) -> [{K,[V|L]}|T]; -aggregateValues ({K,V}, L, _THIS) -> [{K,[V]}|L]. +aggregateValues ({K,V}, [{K,L}|T]) -> [{K,[V|L]}|T]; +aggregateValues ({K,V}, L) -> [{K,[V]}|L]. -collapse(L, THIS) -> - AggrL = lists:foldl( fun aggregateValues/3, [], lists:keysort(1, L), THIS ), +collapse(L) -> + AggrL = lists:foldl( fun aggregateValues/2, [], lists:keysort(1, L) ), lists:keymap( fun lists:sort/1, 2, lists:reverse(AggrL)). -mkHdr ({Key,Values}, _THIS) -> +mkHdr ({Key,Values}) -> Key ++ ":" ++ erlaws_util:mkEnumeration(Values,","). -canonicalizeAmzHeaders( Headers, THIS ) -> +canonicalizeAmzHeaders( Headers ) -> XAmzHeaders = [ {string:to_lower(Key),Value} || {Key,Value} <- Headers, - isAmzHeader(Key, THIS) ], + isAmzHeader(Key) ], Strings = lists:map( - fun mkHdr/2, - collapse(XAmzHeaders, THIS), THIS), + fun mkHdr/1, + collapse(XAmzHeaders)), erlaws_util:mkEnumeration( [[String, "\n"] || String <- Strings], ""). canonicalizeResource ( "", "", _THIS ) -> "/"; @@ -417,9 +417,9 @@ makeParam(X, _THIS) -> end. -buildHost("", _THIS) -> +buildHost("") -> ?AWS_S3_HOST; -buildHost(Bucket, _THIS) -> +buildHost(Bucket) -> Bucket ++ "." ++ ?AWS_S3_HOST. buildProtocol({?MODULE, [_AWS_KEY, _AWS_SEC_KEY, SECURE]}) -> @@ -458,7 +458,7 @@ buildContentMD5Header(ContentMD5, _THIS) -> stringToSign ( Verb, ContentMD5, ContentType, Date, Bucket, Path, OriginalHeaders, THIS ) -> Parts = [ Verb, ContentMD5, ContentType, Date, - canonicalizeAmzHeaders(OriginalHeaders, THIS)], + canonicalizeAmzHeaders(OriginalHeaders)], erlaws_util:mkEnumeration( Parts, "\n") ++ canonicalizeResource(Bucket, Path, THIS). @@ -474,7 +474,7 @@ genericRequest( Method, Bucket, Path, QueryParams, Metadata, HTTPHeaders, Body, NrOfRetries, {?MODULE, [AWS_KEY, AWS_SEC_KEY, _SECURE]}=THIS) -> Date = httpd_util:rfc1123_date(erlang:localtime()), MethodString = string:to_upper( atom_to_list(Method) ), - Url = buildUrl(Bucket,Path,QueryParams,THIS), + Url = lists:flatten(buildUrl(Bucket,Path,QueryParams,THIS)), ContentMD5 = case iolist_size(Body) of 0 -> ""; @@ -501,7 +501,7 @@ genericRequest( Method, Bucket, Path, QueryParams, Metadata, Headers, THIS), THIS), FinalHeaders = [ {"Authorization","AWS " ++ AccessKey ++ ":" ++ Signature }, - {"Host", buildHost(Bucket, THIS) }, + {"Host", buildHost(Bucket) }, {"Date", Date }, {"Expect", "Continue"} | Headers ], diff --git a/src/erlaws_sqs.erl b/src/erlaws_sqs.erl index 3c0bf35..ce38521 100644 --- a/src/erlaws_sqs.erl +++ b/src/erlaws_sqs.erl @@ -346,7 +346,15 @@ mkReq(Url, Params, {?MODULE, [_AWS_KEY, AWS_SEC_KEY, _SECURE]}=THIS) -> ParamsString = erlaws_util:mkEnumeration([ erlaws_util:url_encode(Key) ++ "=" ++ erlaws_util:url_encode(Value) || {Key, Value} <- lists:keysort(1, QueryParams)], "&"), - {_, _, Host, _, Path, _} = http_uri:parse(Url), + + {Host, Path} = + case http_uri:parse(Url) of + {ok, {_, _, Host0, _, Path0, _}} -> %% R15B + {Host0, Path0}; + {_, _, Host1, _, Path1, _} -> + {Host1, Path1} + end, + StringToSign = "POST\n" ++ string:to_lower(Host) ++ "\n" ++ Path ++ "\n" ++ ParamsString, Signature = sign(AWS_SEC_KEY, StringToSign, THIS), SignatureString = "&Signature=" ++ erlaws_util:url_encode(Signature), From 76cfcf543fe85c73b29c4ab0dd4339ab678bbacb Mon Sep 17 00:00:00 2001 From: Kazuhiro Ogura Date: Fri, 31 May 2013 13:34:37 +0900 Subject: [PATCH 05/11] Fix erldoc specs --- src/erlaws_sdb.erl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/erlaws_sdb.erl b/src/erlaws_sdb.erl index 335c67e..5a49190 100644 --- a/src/erlaws_sdb.erl +++ b/src/erlaws_sdb.erl @@ -55,7 +55,7 @@ create_domain(Domain, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> %% are deleted as well. This function might take 10 or more seconds to complete. %% %% Spec: delete_domain(Domain::string()) -> -%% {ok, Domain::string()} | +%% {ok, {requestId, RequestId:string()}} | %% {error, {Code::string(), Msg::string(), ReqId::string()}} %% %% Code::string() -> "MissingParameter" @@ -90,8 +90,8 @@ list_domains({?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> %% to max_domains more domain names each time. %% %% Spec: list_domains(Options::[{atom, (string() | integer())}]) -> -%% {ok, DomainNames::[string()], []} | -%% {ok, DomainNames::[string()], NextToken::string()} | +%% {ok, DomainNames::[string()], [], {requestId, ReqId::string()}} | +%% {ok, DomainNames::[string()], NextToken::string(), {requestId, ReqId::string()}} | %% {error, {Code::string(), Msg::string(), ReqId::string()}} %% %% Options -> [{max_domains, integer()}, {next_token, string()}] @@ -188,7 +188,7 @@ put_attributes(Domain, Item, Attributes, Options, {?MODULE, [_AWS_KEY, _AWS_SEC_ %% Deletes one or more attributes associated with the item. %% %% Spec: delete_attributes(Domain::string(), Item::string, Attributes::[string()]) -> -%% {ok} | +%% {ok, {requestId, ReqId::string()}} | %% {error, {Code::string(), Msg::string(), ReqId::string()}} %% %% Code::string() -> "InvalidParameterValue" | "MissingParameter" | "NoSuchDomain" From a871d10c9ab82c3c8bb6994181e4147e37698c6e Mon Sep 17 00:00:00 2001 From: Kazuhiro Ogura Date: Fri, 31 May 2013 13:35:57 +0900 Subject: [PATCH 06/11] Remove unnecessary pmod tuples --- src/erlaws_sdb.erl | 78 ++++++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/src/erlaws_sdb.erl b/src/erlaws_sdb.erl index 5a49190..defd9fd 100644 --- a/src/erlaws_sdb.erl +++ b/src/erlaws_sdb.erl @@ -100,7 +100,7 @@ list_domains({?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> %% list_domains(Options, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> try genericRequest("ListDomains", "", "", [], - [makeParam(X, THIS) || X <- Options], THIS) of + [makeParam(X) || X <- Options], THIS) of {ok, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(Body), DomainNodes = xmerl_xpath:string("//ListDomainsResult/DomainName/text()", @@ -174,7 +174,7 @@ put_attributes(Domain, Item, Attributes, Options, {?MODULE, [_AWS_KEY, _AWS_SEC_ is_list(Attributes), is_list(Options) -> try genericRequest("PutAttributes", Domain, Item, - Attributes, [makeParam(X, THIS) || X <- Options], THIS) of + Attributes, [makeParam(X) || X <- Options], THIS) of {ok, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(Body), [#xmlText{value=RequestId}|_] = @@ -210,8 +210,15 @@ delete_attributes(Domain, Item, Attributes, Options, {?MODULE, [_AWS_KEY, _AWS_S is_list(Item), is_list(Attributes), is_list(Options) -> + Attributes0 = + case length(Attributes) > 0 andalso not (is_tuple(hd(Attributes))) of + true -> %% [ "attr1", "attr2", "attr3" ] + lists:map(fun(X) -> {X, dummy} end, Attributes); + false -> %% [ {"attr1", 1}, {"attr2", 2}, {"attr3", 3} ] + Attributes + end, try genericRequest("DeleteAttributes", Domain, Item, - Attributes, [makeParam(X, THIS) || X <- Options], THIS) of + Attributes0, [makeParam(X) || X <- Options], THIS) of {ok, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(Body), [#xmlText{value=RequestId}|_] = @@ -384,7 +391,7 @@ get_attributes(Domain, Item, Attribute, Options, {?MODULE, [_AWS_KEY, _AWS_SEC_K is_list(Attribute), is_list(Options) -> try genericRequest("GetAttributes", Domain, Item, - Attribute, [makeParam(X, THIS) || X <- Options], THIS) of + Attribute, [makeParam(X) || X <- Options], THIS) of {ok, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(Body), AttrList = [{KN, VN} || Node <- xmerl_xpath:string("//Attribute", XmlDoc), @@ -401,7 +408,7 @@ get_attributes(Domain, Item, Attribute, Options, {?MODULE, [_AWS_KEY, _AWS_SEC_K _ -> ValueRaw end, true end], - {ok, [{Item, lists:foldr(fun aggregateAttr/3, [], AttrList, THIS)}]} + {ok, [{Item, lists:foldr(fun aggregateAttr/2, [], AttrList)}]} catch throw:{error, Descr} -> {error, Descr} @@ -446,7 +453,7 @@ list_items(Domain, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> %% list_items(Domain, Options, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) when is_list(Options) -> try genericRequest("Query", Domain, "", [], - [makeParam(X, THIS) || X <- [{version, ?OLD_AWS_SDB_VERSION}|Options]], THIS) of + [makeParam(X) || X <- [{version, ?OLD_AWS_SDB_VERSION}|Options]], THIS) of {ok, Body} -> {XmlDoc, _Rest} = xmerl_scan:string(Body), ItemNodes = xmerl_xpath:string("//ItemName/text()", XmlDoc), @@ -483,7 +490,7 @@ select(SelectExp, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> select(SelectExp, Options, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) when is_list(Options) -> try genericRequest("Select", "", "", [], [{"SelectExpression", SelectExp}| - [makeParam(X, THIS) || X <- Options]], THIS) of + [makeParam(X) || X <- Options]], THIS) of {ok, Body} -> {XmlDoc_, _Rest} = xmerl_scan:string(Body), ItemNodes = xmerl_xpath:string("//Item", XmlDoc_), @@ -507,7 +514,7 @@ select(SelectExp, Options, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) wh _ -> ValueRaw end, true end], - {Item, lists:foldr(fun aggregateAttr/3, [], AttrList, THIS)} + {Item, lists:foldr(fun aggregateAttr/2, [], AttrList)} end, Items = [F(ItemNode) || ItemNode <- ItemNodes], {ok, Items, NextToken} @@ -555,7 +562,7 @@ query_items(Domain, QueryExp, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) query_items(Domain, QueryExp, Options, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) when is_list(Options) -> {ok, Body} = genericRequest("Query", Domain, "", [], [{"QueryExpression", QueryExp}| - [makeParam(X, THIS) || X <- [{version, ?OLD_AWS_SDB_VERSION}|Options]]], THIS), + [makeParam(X) || X <- [{version, ?OLD_AWS_SDB_VERSION}|Options]]], THIS), {XmlDoc, _Rest} = xmerl_scan:string(Body), ItemNodes = xmerl_xpath:string("//ItemName/text()", XmlDoc), [#xmlText{value=RequestId}|_] = @@ -676,7 +683,7 @@ mkReq(Params, {?MODULE, [_AWS_KEY, AWS_SEC_KEY, _SECURE]}=THIS) -> buildAttributeParams(Attributes, THIS) -> CAttr = collapse(Attributes, THIS), - {_C, L} = lists:foldl(fun flattenParams/3, {0, []}, CAttr, THIS), + {_C, L} = lists:foldl(fun flattenParams/2, {0, []}, CAttr), %io:format("FlattenedList:~n ~p~n", [L]), lists:reverse(L). @@ -699,12 +706,12 @@ buildBatchAttributeParams(ItemAttributes, THIS) -> end, lists:reverse(BuildItemHdrFun(BuildItemHdrFun, ItemAttributes, 0, [])). -mkEntryName(Counter, Key, _THIS) -> +mkEntryName(Counter, Key) -> {"Attribute." ++ integer_to_list(Counter) ++ ".Name", Key}. -mkEntryValue(Counter, Value, _THIS) -> +mkEntryValue(Counter, Value) -> {"Attribute."++integer_to_list(Counter) ++ ".Value", Value}. -flattenParams({K, V, R}, {C, L}, THIS) -> +flattenParams({K, V, R}, {C, L}) -> PreResult = if R -> {C, [{"Attribute." ++ integer_to_list(C) ++ ".Replace", "true"} | L]}; @@ -713,45 +720,50 @@ flattenParams({K, V, R}, {C, L}, THIS) -> FlattenVal = fun(Val, {Counter, ResultList}) -> %io:format("~p -> ~p ~n", [K, Val]), NextCounter = Counter + 1, - EntryName = mkEntryName(Counter, K, THIS), - EntryValue = mkEntryValue(Counter, Val, THIS), - {NextCounter, [EntryValue | [EntryName | ResultList]]} + EntryName = mkEntryName(Counter, K), + case is_atom(Val) of + true -> + {NextCounter, [EntryName | ResultList]}; + false -> + EntryValue = mkEntryValue(Counter, Val), + {NextCounter, [EntryValue | [EntryName | ResultList]]} + end end, if length(V) > 0 -> lists:foldl(FlattenVal, PreResult, V); - length(V) =:= 0 -> {C + 1, [mkEntryName(C, K, THIS) | L]} + length(V) =:= 0 -> {C + 1, [mkEntryName(C, K) | L]} end. -aggrV({K,V,true}, [{K,L,_OR}|T], _THIS) when is_list(V), +aggrV({K,V,true}, [{K,L,_OR}|T]) when is_list(V), is_list(hd(V)) -> [{K,V ++ L, true}|T]; -aggrV({K,V,true}, [{K,L,_OR}|T], _THIS) -> [{K,[V|L], true}|T]; +aggrV({K,V,true}, [{K,L,_OR}|T]) -> [{K,[V|L], true}|T]; -aggrV({K,V,false}, [{K, L, OR}|T], _THIS) when is_list(V), +aggrV({K,V,false}, [{K, L, OR}|T]) when is_list(V), is_list(hd(V)) -> [{K, V ++ L, OR}|T]; -aggrV({K,V,false}, [{K, L, OR}|T], _THIS) -> [{K, [V|L], OR}|T]; +aggrV({K,V,false}, [{K, L, OR}|T]) -> [{K, [V|L], OR}|T]; -aggrV({K,V}, [{K,L,OR}|T], _THIS) when is_list(V), +aggrV({K,V}, [{K,L,OR}|T]) when is_list(V), is_list(hd(V))-> [{K,V ++ L,OR}|T]; -aggrV({K,V}, [{K,L,OR}|T], _THIS) -> [{K,[V|L],OR}|T]; +aggrV({K,V}, [{K,L,OR}|T]) -> [{K,[V|L],OR}|T]; -aggrV({K,V,R}, L, _THIS) when is_list(V), +aggrV({K,V,R}, L) when is_list(V), is_list(hd(V)) -> [{K, V, R}|L]; -aggrV({K,V,R}, L, _THIS) -> [{K,[V], R}|L]; +aggrV({K,V,R}, L) -> [{K,[V], R}|L]; -aggrV({K,V}, L, _THIS) when is_list(V), +aggrV({K,V}, L) when is_list(V), is_list(hd(V)) -> [{K,V,false}|L]; -aggrV({K,V}, L, _THIS) -> [{K,[V],false}|L]; +aggrV({K,V}, L) -> [{K,[V],false}|L]; -aggrV(K, L, _THIS) -> [{K, [], false}|L]. +aggrV(K, L) -> [{K, [], false}|L]. -collapse(L, THIS) -> - AggrL = lists:foldl( fun aggrV/3, [], lists:keysort(1, L), THIS), +collapse(L, _THIS) -> + AggrL = lists:foldl( fun aggrV/2, [], lists:keysort(1, L)), lists:keymap( fun lists:sort/1, 2, lists:reverse(AggrL)). -makeParam(X, _THIS) -> +makeParam(X) -> case X of {_, []} -> {}; {max_items, MaxItems} when is_integer(MaxItems) -> @@ -774,8 +786,8 @@ makeParam(X, _THIS) -> end. -aggregateAttr ({K,V}, [{K,L}|T], _THIS) -> [{K,[V|L]}|T]; -aggregateAttr ({K,V}, L, _THIS) -> [{K,[V]}|L]. +aggregateAttr ({K,V}, [{K,L}|T]) -> [{K,[V|L]}|T]; +aggregateAttr ({K,V}, L) -> [{K,[V]}|L]. mkErr(Xml, _THIS) -> {XmlDoc, _Rest} = xmerl_scan:string( Xml ), From a0b4f903253ecfddb73c4c37762ae304c794a99f Mon Sep 17 00:00:00 2001 From: Kazuhiro Ogura Date: Fri, 31 May 2013 13:37:26 +0900 Subject: [PATCH 07/11] Remove printf debugs --- src/erlaws_s3.erl | 1 - src/erlaws_sdb.erl | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/erlaws_s3.erl b/src/erlaws_s3.erl index 1d765d5..07807b2 100644 --- a/src/erlaws_s3.erl +++ b/src/erlaws_s3.erl @@ -343,7 +343,6 @@ get_object(Bucket, Key, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> info_object(Bucket, Key, {?MODULE, [_AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> try genericRequest(head, Bucket, Key, [], [], [], <<>>, THIS) of {ok, Headers, _Body} -> - io:format("Headers: ~p~n", [Headers]), MetadataList = [{string:substr(MKey, 12), Value} || {MKey, Value} <- Headers, string:str(MKey, "x-amz-meta") == 1], RequestId = case lists:keytake(?S3_REQ_ID_HEADER, 1, Headers) of {value, {_, ReqId}, _} -> ReqId; diff --git a/src/erlaws_sdb.erl b/src/erlaws_sdb.erl index defd9fd..90c2298 100644 --- a/src/erlaws_sdb.erl +++ b/src/erlaws_sdb.erl @@ -602,7 +602,6 @@ genericRequest(Action, Domain, Item, genericRequestV1(Action, Domain, Item, Attributes, Options, THIS); genericRequest(Action, Domain, Item, Attributes, Options, {?MODULE, [AWS_KEY, _AWS_SEC_KEY, _SECURE]}=THIS) -> - io:format("Options: ~p~n", [Options]), Timestamp = lists:flatten(erlaws_util:get_timestamp()), ActionQueryParams = getQueryParams(Action, Domain, Item, Attributes, lists:flatten(Options), THIS), @@ -661,7 +660,6 @@ getProtocol({?MODULE, [_AWS_KEY, _AWS_SEC_KEY, SECURE]}) -> mkReq(Params, {?MODULE, [_AWS_KEY, AWS_SEC_KEY, _SECURE]}=THIS) -> QueryParams = [{"SignatureVersion", "2"}|[{"SignatureMethod", "HmacSHA1"}|Params]], - io:format("~p~n", [QueryParams]), ParamsString = erlaws_util:mkEnumeration([ erlaws_util:url_encode(Key) ++ "=" ++ erlaws_util:url_encode(Value) || {Key, Value} <- lists:keysort(1, QueryParams)], "&"), From 3719d4a088ec47bd9c515e99ea4bb25a61513a23 Mon Sep 17 00:00:00 2001 From: Kazuhiro Ogura Date: Fri, 31 May 2013 13:37:40 +0900 Subject: [PATCH 08/11] Fix indent --- src/erlaws_util.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/erlaws_util.erl b/src/erlaws_util.erl index e8808cb..168c45e 100644 --- a/src/erlaws_util.erl +++ b/src/erlaws_util.erl @@ -75,8 +75,8 @@ url_encode_char([X | T], Acc) when X == $-; X == $_; X == $. -> % url_encode_char(T, [$+ | Acc]); url_encode_char([X | T], Acc) -> url_encode_char(T, [$%, d2h(X bsr 4), d2h(X band 16#0f) | Acc]); - url_encode_char([], Acc) -> - Acc. +url_encode_char([], Acc) -> + Acc. d2h(N) when N<10 -> N+$0; % Commented out to strictly comply with AWS requirement -- goura From 7b338a579f8e5fb39e9d8282970d39ae1c98775e Mon Sep 17 00:00:00 2001 From: Kazuhiro Ogura Date: Fri, 31 May 2013 13:37:46 +0900 Subject: [PATCH 09/11] Fix comment --- test/erlaws_s3_SUITE.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/erlaws_s3_SUITE.erl b/test/erlaws_s3_SUITE.erl index caa7d2f..3f03571 100644 --- a/test/erlaws_s3_SUITE.erl +++ b/test/erlaws_s3_SUITE.erl @@ -203,7 +203,7 @@ test_connection_1_basic(Config) -> ct:print("BucketName: ~s", [BucketName]), {ok, BucketName, _} = C:create_bucket(BucketName), - %% Try a list_buckets and see if it's really there + %% Try list_buckets and see if it's really there {ok, BucketNames, _} = C:list_buckets(), true = lists:member(BucketName, BucketNames), @@ -247,7 +247,7 @@ test_connection_1_basic(Config) -> %% Now delete bucket {ok, _} = C:delete_bucket(BucketName), - %% Try a list_buckets and ensure it's not there + %% Try list_buckets and ensure it's not there {ok, BucketNames99, _} =C:list_buckets(), false = lists:member(BucketName, BucketNames99). From 1866619fef4fa704e354eb0c9aa48386aa9c0e43 Mon Sep 17 00:00:00 2001 From: Kazuhiro Ogura Date: Fri, 31 May 2013 13:38:43 +0900 Subject: [PATCH 10/11] Add initial version of erlaws_sdb ctsuite --- test/erlaws_sdb_SUITE.erl | 291 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 test/erlaws_sdb_SUITE.erl diff --git a/test/erlaws_sdb_SUITE.erl b/test/erlaws_sdb_SUITE.erl new file mode 100644 index 0000000..b013af7 --- /dev/null +++ b/test/erlaws_sdb_SUITE.erl @@ -0,0 +1,291 @@ +%% common_test suite for erlaws_sdb + +-module(erlaws_sdb_SUITE). +-include_lib("common_test/include/ct.hrl"). +-include_lib("../include/erlaws.hrl"). + +-compile(export_all). + +%%-------------------------------------------------------------------- +%% Function: suite() -> Info +%% +%% Info = [tuple()] +%% List of key/value pairs. +%% +%% Description: Returns list of tuples to set default properties +%% for the suite. +%% +%% Note: The suite/0 function is only meant to be used to return +%% default data values, not perform any other operations. +%%-------------------------------------------------------------------- +suite() -> [{timetrap, {seconds, 20}}]. + +%%-------------------------------------------------------------------- +%% Function: groups() -> [Group] +%% +%% Group = {GroupName,Properties,GroupsAndTestCases} +%% GroupName = atom() +%% The name of the group. +%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}] +%% Group properties that may be combined. +%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase] +%% TestCase = atom() +%% The name of a test case. +%% Shuffle = shuffle | {shuffle,Seed} +%% To get cases executed in random order. +%% Seed = {integer(),integer(),integer()} +%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | +%% repeat_until_any_ok | repeat_until_any_fail +%% To get execution of cases repeated. +%% N = integer() | forever +%% +%% Description: Returns a list of test case group definitions. +%%-------------------------------------------------------------------- +groups() -> []. + +%%-------------------------------------------------------------------- +%% Function: all() -> GroupsAndTestCases +%% +%% GroupsAndTestCases = [{group,GroupName} | TestCase] +%% GroupName = atom() +%% Name of a test case group. +%% TestCase = atom() +%% Name of a test case. +%% +%% Description: Returns the list of groups and test cases that +%% are to be executed. +%% +%% NB: By default, we export all 1-arity user defined functions +%%-------------------------------------------------------------------- +all() -> + [ {exports, Functions} | _ ] = ?MODULE:module_info(), + [ FName || {FName, _} <- lists:filter( + fun ({module_info,_}) -> false; + ({all,_}) -> false; + ({init_per_suite,1}) -> false; + ({end_per_suite,1}) -> false; + ({_,1}) -> true; + ({_,_}) -> false + end, Functions)]. + +%%-------------------------------------------------------------------- +%% Function: init_per_suite(Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% Reason = term() +%% The reason for skipping the suite. +%% +%% Description: Initialization before the suite. +%% +%% Note: This function is free to add any key/value pairs to the Config +%% variable, but should NOT alter/remove any existing entries. +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + erlaws:start(), + + AwsKey = os:getenv("ERLAWS_TEST_AWS_KEY"), + AwsSecKey = os:getenv("ERLAWS_TEST_AWS_SEC_KEY"), + try + case (AwsKey =/= false) andalso (AwsSecKey =/= false) of + false -> + throw({skip, "Environment variable ERLAWS_TEST_AWS_KEY and ERLAWS_TEST_AWS_SEC_KEY is not set"}); + _ -> ok + end, + AwsSecure = + case os:getenv("ERLAWS_TEST_AWS_SECURE") of + false -> true; + "1" -> true; + "true" -> true; + _ -> false + end, + [{aws_key, AwsKey}, {aws_sec_key, AwsSecKey}, {secure, AwsSecure} | Config] + catch + {skip, _} = E0 -> + ct:print(E0), + E0 + end. + + +%%-------------------------------------------------------------------- +%% Function: end_per_suite(Config0) -> void() | {save_config,Config1} +%% +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% +%% Description: Cleanup after the suite. +%%-------------------------------------------------------------------- +end_per_suite(_Config) -> + ok. + +%%-------------------------------------------------------------------- +%% Function: init_per_group(GroupName, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% +%% GroupName = atom() +%% Name of the test case group that is about to run. +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding configuration data for the group. +%% Reason = term() +%% The reason for skipping all test cases and subgroups in the group. +%% +%% Description: Initialization before each test case group. +%%-------------------------------------------------------------------- +init_per_group(_group, Config) -> + Config. + + +%%-------------------------------------------------------------------- +%% Function: end_per_group(GroupName, Config0) -> +%% void() | {save_config,Config1} +%% +%% GroupName = atom() +%% Name of the test case group that is finished. +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding configuration data for the group. +%% +%% Description: Cleanup after each test case group. +%%-------------------------------------------------------------------- +end_per_group(_group, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: init_per_testcase(TestCase, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% +%% TestCase = atom() +%% Name of the test case that is about to run. +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% Reason = term() +%% The reason for skipping the test case. +%% +%% Description: Initialization before each test case. +%% +%% Note: This function is free to add any key/value pairs to the Config +%% variable, but should NOT alter/remove any existing entries. +%%-------------------------------------------------------------------- +init_per_testcase(TestCase, Config) -> + AwsKey = proplists:get_value(aws_key, Config), + AwsSecKey = proplists:get_value(aws_sec_key, Config), + Secure = proplists:get_value(secure, Config), + SDB = erlaws_sdb:new(AwsKey, AwsSecKey, Secure), + [{sdb, SDB}|Config]. + +%%-------------------------------------------------------------------- +%% Function: end_per_testcase(TestCase, Config0) -> +%% void() | {save_config,Config1} | {fail,Reason} +%% +%% TestCase = atom() +%% Name of the test case that is finished. +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% Reason = term() +%% The reason for failing the test case. +%% +%% Description: Cleanup after each test case. +%%-------------------------------------------------------------------- +end_per_testcase(_TestCase, Config) -> + Config. + +test_erlaws_sdb() -> + [{userdata,[{doc,"Testing the erlaws_sdb module"}]}]. + +test_1_basic(Config) -> + C = proplists:get_value(sdb, Config), + ct:print("C: ~p", [C]), + + %% Try illegal queue name + {error, {"InvalidParameterValue", _, _}} = C:create_domain("bad:domain:name"), + + %% Create a new domain + DomainName = lists:flatten(io_lib:format("test_~w_~w_~w", tuple_to_list(now()))), + + ct:print("DomainName: ~s", [DomainName]), + {ok, _} = C:create_domain(DomainName), + + try + %% Put items and attributes + SameValue = "same_value", + {ok, _} = + C:put_attributes(DomainName, "item1", + [{"name1", SameValue}, {"name2", "diff_value_1"}]), + {ok, _} = + C:put_attributes(DomainName, "item2", + [{"name1", SameValue}, {"name2", "diff_value_2"}]), + + %% Try to get the attributes and see if the match + {ok, Results0} = C:get_attributes(DomainName, "item1", "", [{consistent_read, true}]), + GotItem1 = proplists:get_value("item1", Results0), + [SameValue] = proplists:get_value("name1", GotItem1), + ["diff_value_1"] = proplists:get_value("name2", GotItem1), + + %% Try a search of two + Query1 = lists:flatten( + io_lib:format("select * from ~s where name1=\"~s\"", [DomainName, SameValue])), + {ok, Results1, _} = C:select(Query1, [{consistent_read, true}]), + 2 = length(Results1), + + Query2 = lists:flatten( + io_lib:format("select * from ~s where name2=\"diff_value_2\"", [DomainName])), + {ok, Results2, _} = C:select(Query2, [{consistent_read, true}]), + 1 = length(Results2), + + %% Delete all attributes associated with item1 (pattern #1) + {ok, _} = C:delete_attributes(DomainName, "item1", [{"name1", true}, {"name2", true}]), + + {ok, Results1a} = C:get_attributes(DomainName, "item1", "", [{consistent_read, true}]), + GotItem1a = proplists:get_value("item1", Results1a), + ct:print("~p~n", [GotItem1a]), + 0 = length(GotItem1a), + + %% Delete all attributes associated with item2 (pattern #2) + {ok, _} = C:delete_attributes(DomainName, "item2", []), + + {ok, Results2a} = C:get_attributes(DomainName, "item2", "", [{consistent_read, true}]), + GotItem2a = proplists:get_value("item2", Results2a), + 0 = length(GotItem2a), + + %% Try a batch put operation on the domain + Item3 = [{"name3_1", "value3_1"}, + {"name3_2", "value3_2"}, + {"name3_3", ["value3_3_1", "value3_3_2"]}], + Item4 = [{"name4_1", "value4_1"}, + {"name4_2", ["value4_2_1", "value4_2_2"]}, + {"name4_3", "value4_3"}], + {ok, _} = C:batch_put_attributes(DomainName, [{"item3", Item3}, {"item4", Item4}]), + + {ok, Results3} = C:get_attributes(DomainName, "item3", "", [{consistent_read, true}]), + GotItem3 = proplists:get_value("item3", Results3), + ["value3_2"] = proplists:get_value("name3_2", GotItem3), + + %% Delete item3 + {ok, _} = C:delete_item(DomainName, "item3"), + + {ok, Results4} = C:get_attributes(DomainName, "item3", "", [{consistent_read, true}]), + GotItem3a = proplists:get_value("item3", Results4), + 0 = length(GotItem3a), + + %% Delete some attributes associated with item4 (pattern #3) + {ok, _} = C:delete_attributes(DomainName, "item4", ["name4_1", "name4_3"]), + + {ok, Results4a} = C:get_attributes(DomainName, "item4", "", [{consistent_read, true}]), + GotItem4a = proplists:get_value("item4", Results4a), + 1 = length(GotItem4a), + + %% Delete some attributes associated with item4 (pattern #4) + {ok, _} = C:delete_attributes(DomainName, "item4", [{"name4_2_1", "value4_2_2"}]), + + {ok, Results4b} = C:get_attributes(DomainName, "item4", "", [{consistent_read, true}]), + GotItem4b = proplists:get_value("item4", Results4b), + 1 = length(GotItem4b) + + after + %% Delete the domain + {ok, _} = C:delete_domain(DomainName), + + %% Try a list_queues and ensure it's not there + {ok, DomainNames99, _, _} =C:list_domains(), + false = lists:member(DomainName, DomainNames99) + end. From 7bda1446eaf811e9084075b521e00fea3eaee05a Mon Sep 17 00:00:00 2001 From: Kazuhiro Ogura Date: Fri, 31 May 2013 13:39:15 +0900 Subject: [PATCH 11/11] Rebarize --- rebar | Bin 0 -> 142996 bytes src/erlaws.app.src | 8 ++++++++ 2 files changed, 8 insertions(+) create mode 100755 rebar create mode 100644 src/erlaws.app.src diff --git a/rebar b/rebar new file mode 100755 index 0000000000000000000000000000000000000000..29bdd595a50ffe0f617e793e41f6b88e4254fb31 GIT binary patch literal 142996 zcmZ^~bC9Pox3=5e)3$Bf_Oxx=wlQtnwrx$@Hh*o~Hon<=AJltlpQrL9smfjXYgMh} zx>rbu7+js57>q3K7)zXZFFjnz!|4#rOB$&nXErkDGecJPgn?rUlG#Gb@)Sa%!~JfQ*vWRjuMHl66C$Fv3wI zOEojV%?nnu;d5qflcvO4jB-X>u7$|h4t%3ZdDWUsgR?!IOShYKz;9GaEVb~BIzI>2 z&>T~;8I}aOPW)HfN>?o~^GS-0syb**+=y2_jeQp^Du?eQCtp;nv5-6WLA10%F=W(^ zkxVO$c=qa+XpPq`-YG7gDiy0kj%g5Wr8S)2I|QioD^fB?x)hrtlT?E=u<{Mqqm}x( zml!|QwtT(IDxV6EvZ|LNr-gHgGfmi*JNzvH+}ijX*;T3Hu_A!`cv91@s;7_WQu{`Y z&=zTGax8iUs^WtF0(y6w7g)G#`iKn%hc=gB_HLd<3IEFa?4dmm4204&9p_W6K3?u$CDw{@2ycF zML<*v$5ZX5y^mdr{kphS8`R9kDmXoridn5@+>>>kk%SV*}X;nvp{@>3rOpTNF-In2^qJ( z^qwHU0xO^3EOb&l)D*suOesmuF(b0wEp(zE;8Ub6uM-0C<*yT@I8hacH*Gl0B&L%K z;IXYtM9}t0DidR|?gpty_$-P^;wgF}DoYnSfs42tiQeMQ_FQ_B{|E?VbHoj8bMSm5 z-Vk8%-(N4m6 zM8!yd<0YWdJ%_w&p2OJQ!qAP5s@6*KR)Q7vJI2k}dCgTh$TQ9iVyCI_XC6b1XL*|l z@#1|5aaV~1PjQ;@I^x^ree%vrxl?iPYeJ30iaY`JT40*HN$;oO75rF)H!dt&fb!{3 zKO_!480Ly0Le<7Nxo#HK(sTXaMe+@ExJ+8Chrwz6mzxPlYELf&du_ooH#&>n}jc! z7427{Vsgs0;sq(Sb@no!N9#>Pw2~CIcumMex)P&M-Tv zx4D)|4$Dm!+-zMtTa{LXCxKj3>XeAvWpoxj)|Zhb>uFSFbl_~Q$wfScBXx5KKo2|6 z$pdo}x(s3tU6ao3^@_hmlbh0PquoHegj7Vm=}v8uT)KzYT_2icU|gDF3aiO9U|l#2;a9h&|_`|Futx8PQTr0Wg}$w`vz&6}ZA z&AC_fZog?xEiVR!A37Lq+znqp>2-tvCFL;goVpSR{g|EG<2gAOw@$UQ7bC4Y`hG|= zJPZ@U%jG|wTH(6oNwGo25-(YBSp5cBD&(pMDT-e zYa$Fd?zjbaf33N_m=+~Ad^LvAr1|5GJ`7z2m{ALyxnZkZ-WR`Xnl`9t+DK@yJLwQj z^-+#C)FzNgFI?;sds~HIT>_x^jn z!awSXTUS`m5>}C0aP6H})?n;1U^vHHT9{ws2fv{xHZ??oXq3d9Yu)d+FdW}nojH3;XJL5>wEBab& zxE=7apoTQS0;VJHQN5ncz^Yu=#bTrGqhq@0CeU!W=q8$~P&;bhQ)V5@iG^QAR&m`N zJ~nfmYrCPX&#L88&)sm;XB#sq)f*UXYdy_PmptDzlJO=$V&wgImbjAmQBT2eDulo0+4pqW@RN{?^+oUJL2^a0izED9g z8LCVCDihb4!7$*P)>cPO^BebE$wMI3B+eK?)v@f4&ei4M@HEtw2a{{_*jOk(#gG(Su~{xdG9=-V2mx8Btz?ZhmX zRQ$>b9vK7a>f)qS4;UFwJOIPfKw{ALj!f~JJ)u6XySOQ>gdI@RPWB*Quv-a*Xt=W8{12_0}at`yw@i~&xFO1cw&P&vMC&t}T4*SkiBu^>= zCWW3;YS2;KF&T9d`f8^b%c=)v+EPE(Fu*5{)%X|xHe13Vgbc5Yd0^`9Ii-A@Jd8i;;M9u+uG+Qd@rpx7u{4Lk%$eVa#saLr`t_c%<*ZE&iIZk0ep4;3QS3RE6`OLC zV9!i(h`hDvJqICs#Bwl~u;;>H`StPO9)@*ax>}SNQ^fr}WL#$^F+D;gb&J;kMUc-z zMk8HL@aazbDnA@c#nse&>kVdDx+NJ~4@7CrwwmO6DEtKP(Pma!ZSI9e;-SjoPTx3xAYl=yZ$xsTB;!T zPhZton$&sFwnUM0OyzSuf~e{mJ`$*C1nM$=!K*`@Q{3Jal2#z|{jq{&HRTB1_BEBrx$#YzME>~>#R6rRqRuZG+IF!&1GnE+c%M4e6i*nHLS z_MHo4H6)#%SiBQY+^{^%hgPbu9h&D?t6DMwe0rHbxU{AgEPG@;!absi=5NdBX*w*}3)HZ*_&bxg)>z3yG zYga5y%*_*rf0I1RZm`Lg>Dp$oy)(vqR{b`1>ML0or|i$x;f&w@{^N^uA3R4}-Cxe} z-ot&96VVsjL5(Ug?m7q8E7!BUj(5A!YDBIs)v@i>LO!0&cXe+Ar=u zM+5@4px-ku9BaRRK8MccqSnF?4!_?oQwtCfzN>JLFYrAsl9W+?@W0Bo1lsrJeF)yZ zZ~Fx<-(y~_&yL)Gc%Hj=jz19mye^*-UcOHncEo=E(%t2~d|w0XZM}Krx-2?|=yp?z_M+T|Ku!qyIi8(aSxmZ*3~-<3i}%EaU;hcDyF} zCr1f@OLV*z`P9^NMd^U-%h{v9dVfm+?&huBP~s?`QY z!YK3AxVbmkd`F%EPJ@}rEcn)?;r!aV*ca)AuOGQ$YILrsa_)|bs^wo!A=_TAt=?ZR z&>0_0T^{n@NuX9V=bi|3nX8@`tfm_1b^SD2zMv5JTJG)goUQ2G?zLT{WqOUIv;KF7 z^qdIf-k@GFyXaEMm%wu3j zSo6A|tJ1NYfxTVov%)n8R?{;c2sz?Xc!D9BH$)*{gYD%^K5lD7f?3>fvOdUHNyQ z%bxdy`NQr?KKj&=$3)+*1B z2=j;jjD43_JUpC29vQ?fx23c1ooU%DC^@cA=byLUo}YskpB-B-wQ3t;$j?Bt6R;n^6{!NmQBkoMT}A`46zBY$#< zFO-6av(UJ(U_$zV?iG)^TzH8Q#1EW?ipTn>T}ZLjs5B;UVTGa%=F*i=B5k5Ym8~Xt zabYSdacAYJofH^`K;J?Le>%8}swtsD=8vFDiVsHa;<54BN*06_$TZDkDOmeuw#d^H z2d!9<+CuYHf3qPCRcSOyv9Tr^?UHbUi!R=Y=*-M2YN~qTQY#)AkXB;aW5tAuW9b<4 zveIY$4$iY#ArFbU7aljE#fINQn3`ZgSJj{E^TOW^ul7I$S5Vlvo+P8>8SfU^+|^VUcq3t%bPbkJ=Ldpbfd* z5i_@5-YofXoL%xV-F$W_U3px#>L!+JT_IIzSyM2e+8>LcndBcx_^4MRL}=-j(B#r# zpI)}GLehk$Dtd$x$G7I0mTwDQjkds7BxVI~2Jt+eL02K-7NOgp5izCFs5ut);0;wWh?W@GTb2}8CGZ8RFhi}*5Sq5g^ zqFsqC^M_ketUm^<|JB&u1`ghI3v}Ax>WMv~ipqJA<{Jwb9UklxIowrbYmp^3FcTe1 zJDeV1%C59$NjV~?6ypTX0O^b~`m2mQzFKf2Lu8@{m4VT|s?_rfvfWe2R55R4RpH zQ1{4mm$JWK`Kt>pN4Jg&xZuNv{XXd)(?01u7beoNBsg*4qQy#-G9>e{UR*4Z*U`jA&g&h& zL(1(?=i;ef3xd!x8RD{u&%vwgwAttyOfgyHY=T8j*d@nY-Vv`^+cF-GYf44za|s7V z$rTSge13iV@dZ2umI9tZlwsjYXq^>UO8FE8yQE8 zrfeUnA&154n28l~g=CI86u;_Wf+{xGfKstcZ0KuLN8EIgm7Kg-Rx|>6|14yT^-Q{oJ|DqBm+jOzZ4OJ*OJ$NlhB?oPGd~wz5in4xfiOu0DimULF>SE z1W=ux&;1I(tmiV!Bo=euLx>clwQj31 zGwGMUq+B*AP<}~_ZOJ1~#w@MB{oy4@jhdu-zh+v*4)`6$@rZtTcQq!L8`F?=&`q*v zpLi!6a)@?z23bIPk`V?v$)r&0R%5tpf((yb1@_LDLlHUGC7iLdp@35JTHWy)aBdFi zVt}p1bj;iqcR~Plya7JU9lj=L30){ZL#=sQpPu1_gqh^8D9S%z3gFR+QdWWnW=XJ# ztMoRCg3P+RSK#6toL=z(98lG1CLIl+>$|q*X#?y?cvm(AxLUdWUahl>XNDh&70aT{zauPOBek?pQnB+swU*5lWt91>kmcqm+}4mdd6bWb1M<@wnhjxgQ_ai=>g{ z({JGePT!#8Y`eJFR{?1#C=cK_V!R|*H`Rag@pW$io}czW<=SZny)RqbaR3XzgVnre zmmG>h0)1Y^12~^=!gW>}3q;cQkdv^V%5eG{>?Q>JwOpM}t_Pf>x$j$#z02C59eEs1 z*EIx=TH70h-1D6{w7Y<-v)@mGbT6blK9ze6Ui;5LW}Mum&PO3lJnxdz+iWNOl~=g4 z`zG7`Ytt__3VW~prA$*@zsD6ZDSYj>w}aj_&*R5#Ccx8`tF{2{PWMY$1BRNYZ|8AP zCECboe++^j6|eh_xHo;brOz>x)l)!V&Pi(OOy=vr&#w3RsQgdU-FwSu42E5%;~<>7 z3aIi1c%pI;60s^&lVS~#C~BpWySJdU@2F3R$z@3hwc7QW4v z?rhzCS`znsfgPl8HVt}B9(ZHe_&l6J__o;3cd8<|@_EcUogEDn5!Cu#Y@6HpzMSEB zzvTL!oy=t1OSx-v&x`#B*H+7QsQJcwgiJOo z7l#Xj_v5YP!TzgQb=B`AVq3g@Br*;ErLp%0@0RA|CbPTD=kiZXr{i<(exts?dt`hs zZ_CwD(HW!PC?7`QD7`GN(^OB%OJ@+o2}d61+u`%qiQ3M{&~~h@LzeYqs~w%+wZSZ2 z+73)_*Yk4g@= z&)NOMec3QUd>u~sw+N=O6G{RX%qBNKV=Bf4XaDR@6#@E>0@13D7iZ;9%ns1 zPP&k#D{DbC5?H{{-(B^?Xcm;pO*wYo=6G=_%bE=xU z#e|1wUmZ@C8avlkqA<;JwBJ&|;V5KLQ$bCCglRK2UYfY9u)=gkJd+B(k+3RJrm>j^8&H##K-Z?cwuPPnms|;@Li4gwOJ?r<;q(!T#Dc;FT@Abgnw%a7=Rw zO_~X50RgsffTXu;^X8keII8Q;lfVRhsff-OAo9U^vAMRZuV?ELxTzI|Fbo5Z!y}TL zY3vv_TNA)a%{d!}zhQH2_rXA+T^&B@VkV-WB1?>aD1gr$q10@#eX zDKk0nj@jH_oFy2G^WGO0AJ8TO^PvI1+6wRu0=UdJ&?H6q-{8asjG7!ZM?viq@vV}O zRuyCUomb?@nsQ2t%D8g@g}{z6vF#r5qY4jTsF-h9+~*-AhK1u}dl)x}Hcu8Cpfem3 z!K54wD!IQ*v5-d^z^pw94>K(55~uRt2l^w38LBvqsXQXXlsHX3l@wesm3K3ooX5US zj`_kyG~wuOJhE;K3Y;BDjl-mKzRKC_oF(Q26yT9H3q`6YvUP}=xV}V$8l8fN4j+O2 zWYiYo>#NZTnZo}Po|ZHl^GKXg1?o<~r=l)8z~*_>2m;AVX zCEkQXVk#Pe@=2RG(oprKuzhd#+N=8k|7UcfJ(0_N{0qxVJRl&n|1CQIr!oLa0|oG$ zH%@8-84WLEq8K27xCRB9Eb0O&Bq&M=VrP&}Dhz32jgG2af!1PS-E#fBwnD#db+I5? zQ^W7mO8=x~R24i_ zRCfgpBNf9d#|<_qx1Vu}%=tlVhOWg~O+3C2o3m)~Duq*fsC3r4#WQfaE5`a@I+T3o zI7K4|I7Y55=KNo%iq4kjsKmXhb<3o%ZguR-FpjiV~Otx&~CIl820)vqPr*4QjWfag1rU-tAd!m4m#>HJwUm6pf0X}usd9do)Tid5CSaKS5~Jvh$J#KvJw*T(7N zE$Zt2AND$ZYLrCE7Ds#rrCWavkb6SVm4UXWo9W_$`4P?JT=Emxpyl~f*41`)5{4z5 zH^M@}G=znD&|5GQ$e@AyIiM?qkzL0-@?TCk2Cf4elx^HyT_Nre!e}0zZM&l=yLTN# znLu?-A?_X^9&Ir}i6TX?dSwAYU<*ILg>mWKA1Hxxk>?>%j<8OH8rg-?QXcL@cU;GV zLs%u!2l$;Rfy36Uz+L72bJ}n`-HVqW^jSf4?9$USfW>BDSuj4t`Qm0R()V1lydfku zyQY4HT*UnRxGLg-t>u^zR7)k_eM4I2xfz}R_~+&(Sg{Pt0{XO*uyfw?O6mAmTINqA zrayPKuzotVleh_0t3Bf%JBs5`IeX>8SI@6TBeTu$2LtT}@x_h5^(p3r@-?zkgGUzr zQm2=SOE#4}g6ptoRqjRn2}CewiE?G*oD4-IjhXi_rJUY{YmTHRCxxvVg=~-XojXYk zrdQr`4pgO1RcI^#os>9gj!a>$PK>W8=S1s4WwrR@BZr_U!FZmHUm8D;Uh-`5(89L| z7a+?6QbX}eyKZ;G*((w{iaayXn;iU}i?38Dsf?t36vbll?2PhKxBiiw&wLkXGN0j)53VM)awsLmucet-s9&}E@nMby&iHw}VB2?--ehmmHW zVF@G+YwkUVL`vw$i}_ReFGgx4f!s~y;xe#j5F^z&B-qj6Uun$I6T`RKU#zUsLr)+P zBtrY2ko%yZjqVc&52iPO|bpnWB#Q&gI1K_$ns2kHXNcfLKTSF#hPTlo)zHP zII#xK*@ClcZ4#9V(4)=Hj7mgwam&nvl8GFhRD)C_VGZd8VEJG=obd&rml-*+)@ehM z8>akJ8mSII$;@a2k|vZqVB>?S|3M4bpnO9$L#d<+2O*5e05wjfaG(%KYzh+R3P?!0 zK!qpedBmK=W%a;!r2?%-xW3x3v-rggHWgTCCi3i%S8zAwAO|PHnt{j?WVR}IrfTBQ zc7BhScx(fgeJX?%*n&c)x;6NuHa9`#fhrLk!li%feDz&&}fk^kRqAji5ePI zzE{8oB+;tHQ6w5{PE+~eij4lM0w=$D^YN|$E8p8a*wtng4$Sa1xZq~;P?bpSO9~Rl z^Ba=8ELc!-p#_A3{GUV@* zZGp>mVZru*#`mx2Y%++(_k;RUhCW2>=0qSh{%DzJ2pZUU=6E(VF3L2iUk;YtyR0hM zNwW%;j3p%HsCX%nNmTj8;ACu% z+HisV=Xm~JMZr(cH#s5Sa-w@T-e}xxF4AmE5~*{!qGJ-Bg;^e}efL0L`G`BaM-{ph=PwQ7XE-b0SoXkI} zA`UxL^4t}uv$s5A;?^pmQoE3psLq8&vL;0MJqAH@V0eIq1?x?Bc^U5lqWU{I)M%dD zEW}l3PBvy>90U^=Is!8$smR~d;MKwf1c}{v={1C?US-m2SW_h-IfXHY8WxvkzJEzu6HyAOibO~?euas$)aVf3 zh#IPt&0-#UKDoeRBG}?_ONC1AJR~I~W>7F*wKl1 zjGE}~=o`e4+(Lp)P7cTt%|b2TaxTe`a&XUreBVMzGEIfqOEiJMUbBvZ{PTI7TlI2s&^CfvwUvy|%8P{kS(EM;M=RCcwehc;mtBmnF*TKVM} zV5XP?n+a~n7qk)Vrw^S(IK(}+Yr=>u?QZ}Lc}_I1>|E|etRael9K7Urh~rdlDHN}% zl2cE{BK333sk)j1)!T15C(XOb;RjHK;9olJ0K$?M8KnW%PE8hkJ(#o01hu1F=hg1DpF#KDpggGweQdfjOQU0(M!rDNX#gv zqCw6$@mp-V`y?PzW?li^WH9g=j5pnKNb-AcYXbT_CJ-ztoi{{s3|}&-Vh#XGK7HF- zs0%7{K3-4{mUMTzDJ`sxz5k^&macY=QFQPtx=ql3+Mo{vT+4|P!bH7hx*%JoK9bG^ z(@=O$>I+jBvPe37aYc5`aUpc2at9W+4F(fWGdT$;(1#7xbbj2eDD6T($Pbm5qkpN> z@F7q%H8u&K6j(+t7%`X{IdTVLz)Zn@x?mlWlMJW`Ii*!=bsft^D4lJI7StYHY7bVB z6NSQ@SH_SAGvmy@z!n?SC#sMx>YgaNX%3VRyhjUY;lP1vYIuBYuK9NpUgm+rJ(R_I z0hjn+=0tg+MPQ4QV+A}=+>&youd14t^4tx8syO-+sS_IlI5$z@8?eL8;5gWvOF&MU zQ~At~*-1r@8+TJV7^rX`m|r14H-`Sx)D5GHETzv3Xxtyf-+_<|5sDO82?#90X|;h- zD!J$fj!001z^8Z+h)NW12m$d&pO9Z>DFPIm@;=BPJn9dsLi1Rae`xq~LY;=%5OgD}q2X!VL0!DSo-iW0VuW+>&^E&1ujNlH;XFwR@~p0C~ixNj_B}|9)8{ zZ=-DlSoM$WM$$Q8@5;jn5w?h#| zjB5|#c72oN6C1;h=}W9IcXWlHj!begg(Ct4_D_KfTn5loneBFyht9c8nvXp(y{xub z9*-;IsvhH?)(`yMU9K-(GM%o~sxOC=Hv5sw2Rj0HPdwUk+QgIJf6R{)9Dit|djO91 zQE!0Sl8r+Wl39YUW3#W+6^2?*>j(YQ_r2v8UclR_b_R-4dd2wYln0O=h}#EfCN{G&2QO^tRB5 zR7s~O@w6#!xRbWq1LZpqTi*@~DY}f!mUXpo*qwBHht+#Di!&(bBzz{nzD6+KcVg!ePKSYFieq7Pjxv``soN)k7*ftEX=2c@b5R zKsjK!ZvFnX$LagsNmS!akaf3uWnZ?{J-_|EaDSX7ub07oz7-Ppe5&6D zejEm2Vn51|UZ4J9FT>AY;Aupmt;A-pes*#2z5O_~rGGKlup)eeo|4OBRbO34N2i&w z*mXCRs<0)n0j`IusQl!%efje74;*DXPf6}4RwW{!F>rEiIy*=#&o2Y>Fz|8kQ#pIM zk9Xavm+ze$uD+e~H9eDxWWQOg2ePCgZ`{bily9TFh#4y6IId%;CndL%`FXodXMap) z6}DD(>Un$LEMd-WelzUD*hAFDR(n>NJbz@K_sT$dZad$pY?rjk)68bY;=zBvP8?3^ z=h*yh>xz%OUno0TQo(NorX;N@6)+Y2d4 zs-4czsH@fO_H&rC?y(NiO)NVA!22Fe!)>EwJp<@F8d|B@U$A`++6EKJa-LH;)^DMh zaN;n&zeL5o5q!uh6C7>u!*Er6Gpu~*1o+e{MZ3`Y=hC*h$ zzNc!3Ee&_;ze%3?wNj_teoCYUkwxURV>oxT@5YgB9%+sghsRK4W5&E zIz6tYVsf+2qdQ~I*uHCjPh{TG*In~m>hPNY-Tz*He2iah8(ukbIqmEZMMvIjZ>PO} zRbH=hoxL8k$+pi2j8=|r+Sl30d2kcGg|u7S+4ExDosZH;`}!6O^K~;#eQ(@r5*(^j zMtGl{d?4VBqAhKE72NfGBb8oOmInfiRn!q6a_DT+>xn(T^TkWf@*t9BD!og7=V5KT zY`ysjk1@eCEja0a*B_eLiNz_ZKu{VBx^X$|%mJRbZ1bdZStaopS!;iJlop&h5 zYBrha>D?81_D|DaX(T=q*muE6QC+Nf7Gispi`Pn@h_6#7Igx0yp<>B>^(;KD# zq#N&^Bsi8)q7TnQm=Z61SZs-;u;~V)A~Td(G;U zX7-gx5KFfEauJ`%tdpnRaieCzz+UuQE|Xxp0=N%7_u zdd_(`TZvWM;?WWZZMKOeM@^_DiGeAps(hii)bP0*eCzSQX;kf1t9$gd%DI)>h3Icg zU**W6C%DC1s>(u9ZD?)s`{DP~`n{3`$g}F?l>K^`Fpn|^?0l^&>v~+JeE*spS+P;(phfg_@tYd(y790i26q{a?(@3o-YaBq!GK3S ziWI8D*or{rR$#v+kH8Y%2?!1i8qw1cxROW9!cg6|{2VPf;0Yv~o)$HQ{~NR?8Q6n* zHaQ%pDnZC$oP4`HxqG*EmE8$P1GaQzdt(w^NFS5o~i^2jMAxTjfbjt=W)eK84 z%S@HDEcxkixfWQ!u?fQL5k9rLihAS(uqu`&cTB5hAHDB~RlkX*CCNf#yv%va`dcZ` z-++l(M`rrSqQ4q)RG|Bg@0;X3VA67RuIqo?wv+fnADqtQqGqQ{yf;f-_Bu@&(ndOM z6=Xxi5ITa4{=d~qYwc-fp#Sw83la#3^nanH`i4e!X2$x)_O=d|Hl|MhUAd&I>SLp< zirRa#1$eHhzFh$@feYhbfS+Ezx4J=|9@-UIkBnk6_F2?-wbpHEZ@ip;U}g~n!zz3V zi<0;j_P=I?ha$#^l`{9nU{k<8V2pPCFy%#-{k)Lv%OmvyWM=?m?Jai(}B74i_xZY<{tx1ye8eP zL>J3x38XEp#K!llZ8`!}Sz8Lrt%tje(#62yBZ#Q%$8(#r6RNw$Q{rgE3d<)$t21kn z`-_{KFW1@sAp!^Qzuij9D)Ep+sB9O`q$y3NGFyqdSXB07HsBhn)MM2(o9v7x;+FKP zi-|$e-O|=vY1J2*dWtKVfJRKlnyy9}GJAg@S&lL;*9Mkc!>S$|T{I`uU~OS6X}AcY zB`_zX!ogc~Tx!2(pmQL-$fB<&5HgvY14zAo2j+fW%i-WS<^j7v9EoI#oWCXzHr_sK z@at~KNe1I|u^w^$kYqS%4KYl0TJyQrE0Ys3KYh~}Sq!Gu!wPd#NEeffP{ z`F)UUG|NChad zVIu#nX3lQ{rXKVNHj;E`S1^_DK?RB&#yjc=G1%l0h&BGry-Fvtzx~dM5(OH>WY0Jj z6c2uh(u~Mt=xUTyOMzbU`k2GoJ4Upg>~)5H_wikwJpSFe4^nd}32vcZ@5!OnFem{v zeAU?PciBoyosa%uwK;h#9l;HfB4BTN{8xi8-4k8I_5<9xvN%7MQEPOLXW!qN^SSbq z(TW3Q_fLL5fb8X`xZKXW=V6((a>8~mLTjBgdoB0t{V72idL_qpD|PGL2j9(B$ItV$ zHM#iBwYH1bhw+CV?#}DVP}i5RJO}@Gf9uJnyk4e~fbZ$#=c>w)H~`j<$8GQxLtCw< z>0=E~f5m;L{pLb+xDDUVH)9%4+`f2CAZ+N>qL#-Tdhvw@ufQAXf9f?wS*4;z4P9+8|y&h(= z1$4)C7|E^=z-{3gQGveq=Yd+2k)*e*Jxn?;5u{4E|5qMhx+kl(O+6;&5n>``NN0Vf zv^uA}w{ZBj(_s|MF(`u{Ozlbvx9FSi2lStw8{ToWFyLPX>4O6TBKTjkh=YT^tBa+L z^M5B0S9L!dlozz#hZh_DY#pZ=E+#}s2zF2-WPK+&2}r6F&=X`(Qjkd2=0u%K>AyqI zp6(~89de>LHuZ&YOe?a~>k1Z~ZhnNRmgj0U*h=cHEy^&JtT9sm5DUOAiMzut=UM$_ z`%OrvRLryI#YaHk#%JfnH#(Z&w2<;TO*rGRr`Z^ta^eLqvTy+|mA^(aNvncY(=ECS zkJ-jOfu(vqasmy)+|``E26`=5#9C5;f@W1kU1Rkr;p4ay9OOhUu0-(UI*| zop!3_vWn5+v?fS1?q+LawP_|iIP|HS9?eW*|Di#5S(b|ot8p3I#`KXisfG&C4WLwZ z@-E|nSIYlhOwAd#$pWCt5Lm8TAN9NZ*B0bGj3$ohlm9?2hZGrK7_imEa5ld&>XS~N z8*LFMj>D8^w}rE(vNIfcgOpoF|CmQjl}ajG6w8PWTg=SFXTs3Tw1a%2g6h$iJLr;8lnNUSf9Wc*^8^c6$ zAzD}N5$XMKi6xlIIZEh9k#p8PQRq(*WV3+;Q~hXCx{q_To%>%s$`#Ct=unMh9H9)Q z<&h3z#mQvI$)rob$4TYJ3LJNS9S=PtFCOnsc(XL;$w2Wzg-REB29TDAVDFiAOiN>n zfD1}>Jk&DO7SOarAy831NsT*F(H+nt@`N7k(WuBiMq-SMlK~Toy#Ynh&$fZP$8I7a zDB<8ZvcL89Lo`a{$0S(b#@S(*C!U!{X|kc=ltr496VlKt*vh8C1EDX7nYO_?RU(;a zl}T*Ijb;)fqNueIW+XIcnFJEZi&v44} zXercwTgZ)Q%0ho5xO@#&2H;2!Fj1I7i{GeKXm)VZwR*qj8v}`BDQk(KBg_DQ@c*ra zqllU%V@~y(zN&{;GX^3{Ag6@Tqm; zqaM7AN!H9W_Brhw-#0Zz1B1Kj;B5M)A zNO63$a|&(?iTLh+i$qh3z=;yo=8tUF-+^jWqt0#5wgf0=X_s9PIob~R_qB+oC?sz* z580Nk$@=JM)8ZwC^=p6|Yhou>1PL?e;U$Xl^ta%_wv_yBHS|3%HX7wqiB1IbXJV^b zmkRD%OTGrFMG_U{1sx&o(K$1FX^Y8o(negbM8@4B<1D4z3H)e6d!K_Z0>qlbl^H<4 zgN|P14!I@*54eh@z~~qyT;#6>dmAigIecpJ_uGi&5%J!_qx^KpJM^Q?>=5j(aq7iATI zH5sX-AR$JHR0R?V$q0XMKS|-ab|d zZ92ls7|JN^3!qh@Mhe|RN;dov9#|fCEP)auOif}hiLy(~$%)ivUND zxk3hUV(s!IamwR=aN~ZaRUT03cqdjp)DX;r%o-ug8_C3cqejX6TRd398waBnA(E>e zQn)#~HWjvU^MDe0u%Z{WOnL{S;@KcGf<&uzGbo$(FoM^=@;pPGG5Xa@rr?$hhRP zKiB)Q{Cb{Cv*>!*zn8iE(Cz#lPZy-L3TmdIQ@D&1mS(%XtffF>Gjl~52fw6<))}V46XZ7YyUi?bkAo})mp!rC7$1Q z`(7+8&nAlbex&UwU&V=?-SR$bT*gr01?S~|k>!4oiCnWZw&`)$l`QJQi3PZNc!Qev z9V)Dx-nZp#cIy^ZjUJlBg|nGm=lXd4I~&={c1H@1Q)xwa&l@_(9v3fhui0NRPMdd+ ze?Ae(Ch~k4+1R*MvjKG`n$6~T%~Q8losof^kWYQ7q&JQQ1sY}Z1^TGD?k;%c7(SX5 zgduFElmv>>r_-D~V>LZ2_(b{BCKU<_!bV?ia=DpX=$PdGNu)XrON-ll>m-xqH9saq zFL!tSzN~1&9pwXxpEzI$0$)|E^|BNobRuu1P!=SkIiW@r43d2yLs*h3Z?9X_i=IcF2I=TRBVPfWweOw^RVx+wg>ivdR#9|ZFpw-! zkTg4an&LLeGTCNg9x`?kp@|XFz%|Mp7+K2BrSotMWwS~LSuSw^Xvx>LmV0Ajo1IWvaN70>htznmltorU zN%>SfRXgr(I4Ugee{`tE6|4bgc?uGEimE}`rx!4VAl@V%gK%}1y+@AFSWL2Zb42Id zo7yorwa6N{MGtc3rWGAW0??XWWh*?Hoa7UAoKzbBT`t2^?#s%cLOZ2tA(Tdgu7~;b6j8ZkgVa zUJu38e-~iU3q9!A&`hJqg5jXjIi4-WqZGiS0Ufg4zo6X7$>&AS&$+&+SwI{D*G|Xx zPBU2LD16WWR`dr>d<5PQRMM-{$5cY$^JQFa)MksO^x|f8c@SY-bgsixuzykxL z4dC`B945jeGm@DSaEP>m0kH2QbB{|Fmq4NN7#Er&lcg0yz=tdzs=Hu%ARjC4$h3f* z;z>*-b|;a1KT<3M^HEoG63(YIjMYO%QL6&)5|oIK;u9S|j-$vQ|9t zIdF(HYE^vztr~M&LzaLHK@~|z)W}VWU_c^iWrU{2+<>R ze(bOO3~wc+)+|rHUy&*)ukK~60j0)W3oid>hi;FuE(qQrwajupq}b|nNNO1Z zThDQTVkkX2v7b048U#6)8^}$IE@_kt_n5?16BnsSf|nvD!W)sK2r|z7A$Xvve^mlN zlF*|=02xKsUpQMI`KWW~v8@mL6v?8-QQjSr?CP#b{FCE>o4q~upA8KuJFe&tv)CiB z2So5Sp@fr9RbDOHuz@aOof!o8VxZtXN-0%9Wq_$8@pYWf)FUwtz@QAH#G}uZq=kB9 z>{DaF89+RG9HP=ac^aSqC@=;?E|AOH6{h=>pB!mu*N{Ky8 zN;IDoAA_jTz+AJ$zLCsBXbal#Q=St zvl3rd6ik4`d(3}UE)3NCHTu?WH7T$#a$azI_&t0sULJA+f3Zk0UxNfLAcri6YDHO4 z$p>eWj?R7{Q4S7B-ay^yoD)RWD{2x62rThGf{2oT;cr*JUI^6`6Hg7~&RL|UvqJ|r z;EgAM*%SN~4!#*kJOJs*^06f41FtOe6BmiQ8Am{g%`9j+q=ae%s|&FTM11BpNi%GO zgTigBvrRwNO-=oErlA|G#5>yaY>vSEh0&|ngJO!OEyb$7$$(-E#G750f1gp?ZlF_MR`Nv0%=`ljP4Uo+o&Rp_OGVbz+T38~b zvvuSa48v`b(%B_yYgxq?ej-IS6^{_e7{!UfIUtGftoO334H4S}s$sL7S)pYKT>k`? zhe|PR-j_57_J;z1ova4DvuCpojBv1WrA{BF>yS0#v@zhs)SX;C<276p`*aTkQ?=TT zl80Tc0lvZowCVs$*eL>;2No678BhcM=ByEci(t~OgA72xPxx2^FFS3~A5esxhk;>j zDsPj1J;}<0b7Y7bTBG&Yv!UMt9-8A}X|dwI1^xq!?5IYa^$u|8a)AzZc%2^W?srfl z-OD3cM%HkwH-8SX`r3MHLT!a+gyL`k){&jP>*BTpFUU;zSfckN`oT~jNTuz6+z%WT z-P=PXZPHBb6p|_4eL+O(fpa$m{t5?05r*Y$F5kBftpQ?Nfg2X%|NL82EfNpzb+pRI z9Zj27nh{t6T$y!K$lrN9y5zkqZ)|l4a6koq+_c*AgKmD}c(`y^59-?L0}M1hFy-3^ zYWbkbTBqM#^~6sdUy`|s3UwLUUQF{CDlMp`gAq#*O|{`DTHMF}3T)Ok?imjwm7mp4 z?h)rU?iaj@hU=;U=XOU7az&KN&EdUH;qBidLw+9G5;SwClxx(q(fW~!w;UA*H?`qy z9g^Ls_aTBUIx^6kMOw7Y^M|)ah;X;-)Q{6S_4ahfLxU`=ty|yKSo~S*xa)~^cU)-y zI6Tib`gSj}NbuLH&hv<@pRPLh*A=GSj?PE7 zD!Q$gx!Jt?YV}6GEFY;~*Q4`BnMXg&&(+P!oi--novD8{A0GzewtvRrhM^*AGokRd-ArEXZaVBd z4fi1*XPc{k&3_Kde??lqPwVBJ0%euA`ieNNMCg5;*bdL3rF zq0(xJXSyCYs^NGt-}{Hgms{CTaI4c{^5&K`OU+;0cCTH;4lop59L!Kw?L$Q_4{Hx);_rXNEEKStZ-J8|09Y`;e*) zAu_Vby*Um`_dwrf4LwNRVgu!rAV_~v9d?Q?mfvqjco>SbVnQ#Ri9`i5#+q>b=Ndqs7>2_|3BL25M%2*sa)|$NT)a46Xy>3*yw>~>#n8U-J3Tn z-|YKve?sxY6&QR43{6hhM5^A>Ag;S z+3kFO@7TI^*pGi}01#IeRr)w|-YNs^<$?iBscLJ_ZFk+QuY1pzE6YH25;6@TzBfz- zK80Igh-|Z{I|{ts#TcJ~x|W$6Y^TZ_=|g?8$2}ha+(JW?=r{%gTC0r8 zQ_0cnI1iAFR-gGMqf{wG=x;)4XR}GG6+T;c2ZjiYHjOTYbE@n{>E5b3UnOdUnp}lh zk5WK~S_sa=8LYZ&bzE|=FcNg{we<+MFIpO0X?EzGWMvYTq`5h8)H$o&Z3Bc!b{re@ zFjmDYzB9JeSAMRQoj(Ysp104I@y<&*yb4q@cyXa_l7 zl1*MzBo9j^5D=&fPn55ipe((IP&<}Kj^|bpXfGxf3kpP)ifq_u!oHCO2+`^QcdUFL zGDxXpHE>rPoB#q@C2~7_LTh^_Z^aBfCfZU>u3U-E@jigc;3BVdL_3lgf38zoZbR}W zB(&NpFRM@pgcPO_NLEmy26QXhQr*CeyjZ}h5myxOhJ>J}4d1ZD7}rVgOuv}8mZk{q zEK}X|^>-UOkiYilw}>2086$?MQfrSIDnT*HM3sxA+;@>jSk3`Y#HG9z_=tKCh&iJ1 zuXpE(EbA4ujUC@&83?%9r~*>pQ795vmSWt1L84?hc;%Osj5iR6R7hH2r@%;wzy>VV z*C+`L*@6hEL1Fw+dV-Ayaf#APX1M%K<;i=?0SJ1>Ti;UZs`LN1P#p2aUU`Rk12w-f1aC~WeL~&rFbU*5T zcA(tj(G0$ROZi{-KnQBygm-dMB<8x)%o8~S-w$s&ER{!6)ufl4nJ=Q}^4^3!)Ksb0 z8RH*>EIbvz;LYl#8%?FfsPrC;e_yWq-`5zi2!rNMn+SvT($`t!sJ7%zw@DkY@SESz z`6W(Lx7aaW4o0EbT3s^ubiA@1TxdNy{xU(OI-3?@q4Bj`G}C0Y@K|8;Y}EfQI@K?3 z3dvom>{@t#-BU58$_3$!bY7NRNeyoDT4c2!o<`)OX?u6}et*{wx=DL3_`E$GSFZLv z&HfUz)@Hk&%{bw){^he}|2$j&nb^6ZImPAl_TF^rM6JGGnf7-1Srse2-e_$m!|ZN% z8Vuo)`@V0#Td3zDi?isp-n_f-g39W_e7jp^i(B$@_<8BgZ7Id|kn&^jIS=@b)3xxv z`;_8iwf2oMeTLa+k6=hgEL|6F|7V3|9oHmF4b~F-qG+Hx%$qwlUq-%m%}PNI-A&Ji@DQj z({UpWP4#+90APVOqB-%S_mx9CG+Pcb!z zw_i)MkX5+^YFPax!h;b0Qf52PqNGoto$4Kb8~>7bQP@+tr*vKonUx?nnSGQycoR#l zeO11oY;~uLo}KGNKqZ@(ZGF3I$&c0dYpqZyc4g2t$ZTnE!2hjO`NEkJ?EFh-k^k2r z_P^dEh6YY1|9OwN{1-X1>eGA3l}MIJ$9qXGFWr{IMF9U3 zM4&Cd!*$#-ie$1e%A|CsYQ;LO?OJqfJ$%h}+OkSAS4Rut)9|5Uo3e(BgigC&>Uudh z)^=KhB;UUJqM7?(T}@C(NO96;Q=P6=r`B`%#-c-ZQSWx2v_`YJrMswT+g8O&*SdJ4 z3|Vb&%j<8KX&PH3%cK>`rR=bqhGTZ-l{K=~wdwSZL1)dO>s#II?MsCVbju-~u2Z$? zqU#`)>U3k#mSweO;U;P&l}ab#GiybB`IWf_?Q36%@lUP6^0j&K#Y63s!hUqqjWSku zjgDK*hwg=J(OQ$Hl>wYY^U|&^dK75=P^(cx^@C>Fg|I4it8MdNOQT^3Es{~2d7B6- zn@$@km`_hB*b>TxNR{f4YtP2#24<)G2f@Gn%~lDKI*nQ{M0rP-ZCEW>_t`C4Ef%9f z^I{{L7|}HvwnZE2&z`Lrg~uR-2^~6g>$Y9C-P#RNHQU1S2^C7frU@xlw#_DDoJk66 z2Npw}Ji=<0s+A^et^n(-hWioMN8zq9csRb~CG$)6)!E+T=r~xcp(@Iv>o43xOB90o zc+HZyVc(C>Bl&EsRnTsM1Nz0={sf%S6*w|rMLPXwN@59#D*d`{s2xQ4x{Dnv{9Q4Y zlMWVjI}AkN8l{F%1F=@@RFQMKE#PF-f{YC^5JJb$@4wT~ziIC4dri6qOZ_WekKTBnWbjK~lEy6@-G;QVZ*Kh$}Ft z3vMLEF>MSPtLFcbGP2u7f~2Z-jhSj`kJu|&kJ#DXB%*tyl+&cHDw`snkf?KvqbgBz z4UQo$QYpkxOiI?Af(SbDh`se95cUIllQ+H)dwy%@SEHP?a6f zRK21NZ91sxYeZx~jVvFd|3HR}Ca<2u+WIJ}8dH7Dn0) z&gcttS#>4xWcZZk@%+QtVU!2o?g0+;ruZWbbLjo@WB!!{Iz4YTR?2#2<6`C|b z6a*zuZ=nf$xO1gJGfY9l&cZuJ#=y)cSX+0+**Wx-GnVc2wa4d|pbu1u+7}}JXzbw@ zZh#3?3LMAaR|(Qi=GatdT@jcS<#8@b@6LQOqcFH3XxJWIwFbP6Q#LGe@#Q=KB zCy#=@|K2-ED6rOtP$-*Nn)UNHGH1jNMs-~I^JKFj6?#|Y$r`}P6>`eCsGU-zHJ)=w_j?RHzK-1m!`d%>l$#UB7rW+ zuO#_ul#ZSUlb8Ao<+riXeZw|K*d!$EQZSZpiHI#d)*E?5s0~+{K8%PumehP!6nQl9 zH>kjWK?UP&CXFQxMf38W&|EA5@Q_X@1%3Tm_`6}7&fbNhED%K(%I#-{=ky&hLuI6o zM>>Yi=4Us2%@9hI9Bc?OlyKSTs6uC+o}VP#;Br3tp9p(|{tLeJn2L0a4GAZowPoW# z+7HUy+=?Jl1Iwor_w|{+mcor=-9O=T#Q(VaTmL_h@O2 zUqi2}Kx!ug<^9V^`IrOh1SlbA&<3yeM$$7uXRDFq#o&@)2T?~$pQBgaC4lwhq2LSo zXa=xR4DpfdD_JV{%oIdx`DCG>J9Wk{sn&p@qm`X6bwVs2)p#6DLCjPVXhj+_8DLUx zW;XXMp`>7Iqeo4g20$V}ae%k#`MHSNE;~b&(2q-MPIX zH_Bqf#U5%IiqdFAG8v?P!y14D_x!niF^hj{;Z4`5+mJuzQ}{)l2PipvZv~r_8Qpma zl|BWOdAjI~L1Ju5W{!Z493W5t08NbdC&yWn6PHCnLmp1NkqXH@bh2( z<3k^^k3_QGkbJV<0Zhs&ZGVEW9r@#GG9XN;O+(~!(YT`CwNm3PA3%P-(w z8`VUmI(sL@gq1_fIMc~2dPGXS1L>pkk2>X??rzX|FG|B#2l*G0WDVpr$J5#~b2Sk^ z3|d#O3Gq6|5e%&QpE!~Zz$weZu6qe9T`}F}nefr*h@13F3e95%HuWoPT!shyzc}n% zaAwoY-GT?0+4pY1_ji zKWDj>Z1*U$fj-E@0R#PB+(40m!t!wBpoIB2`RwG#V~hT$w{JQD(sbf5?!3EbWA=D) zAgp*`7CK)@0nn$fZqsCce(2A}`9nAHAPwdKR9(yPAe*nyG{M`JNksQuX{rqp&`(`xv_z1mS&R~1q^}7()kb}86ym~ecSp8y(>2&;x zd>_N03K)*5^n7UEud)1CT1tF;TrYOReBYOP=#sv=SLQkgpYysp-(zll?)HAKCV6<4 z^QM0H*!+ITpn{J79P9ObEpGlgTr_9x`rJ>T{a){6xyAnWJPvFuJlArdT)zHz+{JMD zN$&nm@_o#ASKIaUIJ`^g{WSKbzt`S9S-t)F*1LDvTl9u{a07r4i974eDS++J-?rv@O-~=T7N#K?RY*9gJyPnKSqD0eot3E z=bN0B*WRmhZm#@Z>Aou*#M0d+zMzMKy3aos{{%qK zD!{z~o9oFNbq8Rdo;z0_A9%f`V8&U}C?kxi)b7J!M$$j$y~piv*nd|q`w{cl6J7T5wsOrUd-3OXcu5$0Dyn_8UIhT zi=L6SiGl6^_`p?^#;uSP(ES>T%XO}pB|-uR<_IZCHi^qe7XMP!*V9;3rwtexp1^)Tl0wXEQ34|KJAVB@dx7qBh-hZfy~|;FBF08Vu@A%>3#3Hef8Z1y`82_ zRRpl-BQtwIdyF*!0LVpxhS677s8?JruoT3;v(9_{C*p^>_WIdouqdBi%t4%uxn9Xn z56D-ux*M-h_W(YlMpfYxz$~9LQBN-y2*w4%Ush<~5l2iAc+#IVi;LH1T8$(!?7N|D zC^+E!-2}8IZxaR0ZeSLlasdMT`ndr10R|M+A1opy!bJhjz)30=CxpgB>G_>bcf*#x zI`8B(!K?ypqiQ(^DFKl6-ErdGR^M*_qDj};^m_I2@u|>HyzCRt%`N?iUv`W)*+>Sr z&<8NIHW+q=5gJ}C!#8h_4YfzQs|%E#pf7_E9ILvh0cEitVqsAiB3;77=2&E&OB}vA<@I8E@n&`f9hcSLd=BQ!kF8L! zL)dh5Kw%Z&thYR`0lXe+x+sIFyfI`kIJ6w_;{EO7o8^$z-Q@&$kG!7yyo37B-)3-W zJshUzQRH&&MB0DgO=_cpF>WU7{SGB72g}bn5$>{jZXvj`UJh z1U{S^e84%*W(LExM_=Vs3ToRa)W~sr*BcmZ%G0;NdK$w-k)_s|Z4m2OaUG&gErd!W zuKA4Vgt~KQxeRssU2&NjvFoB@UA0=<@j@02N}F*4X3=iLq^M($$!4>GDJSSvRy0hL!Ix<)lhLP@>(FzmOs5!-K#DGE+P-_@QFX>g zQgB2}*iJM384OI^bUS6RrU2U_b!(Y* zF8_O>`2fd)-vR?+^FKMbFuD5yO)AVF1zw1sf!8oEugFK7#+~F|A1&jB8$^?ys^FIj zB4j)Mq-VnO-9x1d6dFt1K4!oQdc{zH0xJ?*RC6LZPlpQg65w!9Mt5!0dTFCplB}6A zW<2XZ*5sGdh&lv59fT+T1W}Ejz5J@j{tPE6=ROgj7&h=MBETdfNA`aUxc}LOTb5+LwRz=x){@WkYp3Bu93SaPUHKbe8GHFo)Dx-4jsFyMh9f)&W5jiBDtLF;m zex1-dT*AbX@8zEBfx#KZnzh^gQNpWsQ!qxN1hx0n*XI&%PpfqO>geRThUeAUB%sDASVBednv}uAkQV#k z+hxL8)o;nvU`<)r?< z8z)v+ZySN{1J;WMtq^t>{nfUjCedV~( z@&2UGhL#ekAM~@bBP!wcb2`+|+u{b`Q^JVOaFIKUhN56hUYS$gq}!z&+g=;RZ~zf3 z5Bhir#V2DrnjGSv3^Zl@>f`!g? zJZJEr*Fqd&o7CzMp1{=D7WMXjrvv*n*c%1{WA{H3BpC&i(wUYu2Q{h;F8r$YO<+?j z$r3>qz#H(S<{a>2S{w32UJmz^=CVNAvWSDirB6x6nb{)zQD(3~s19wms0T$t#gLxZ zMR9JY?%vFs1DEl) zA1Me)H`z=5odDR|fq7uE7jw++&h3$PL3E2#6@h`^A};FbV`n9*NhM1+$<6GUfQ&6u zYAz)CYIwE%uLv(kdH6q=z9IWnb(8Y{5#IlfWUS*M|%$_3tHquiW@0w-1h)BYX-Gr5&IbO&XnBdL3OywH=cLgomR|Fh@)38vp5EYMSn z=W0y4#dfW3Aw4(`SJWVA`)=(hhDf)AGTNNwxi4WOmmcTnCY*{dtTv9onMXwhW#@5t zs|VzAax4)z7^3OyB%8xI`Feh=me@V{p(Q((w5bGXRUOf+wJvxfW%aNlH^3fqF5C+^ zW4I=pAYDAMLf%C)7$cE!c|IcOK1q|uE61k;z)@C}1)Gb(73;9g^ty2%g{!8I zvojpJ0h}$1Z76V0mvX^#B*}|kBC`o_YnJGSJr|0o2{?AZd@*W&&%pVE_yVCgs-Yst zXiDho?FGJAF?%NI!Q>GF05qWA-W-&A)=={tF z+9rv7IRi|BO+D4VV!>2FGcd^}lnSB)7&J{Aw7%#l{gVR0bhMDv2BFvlICuE0lIRJ+ zt^IW_ECoMSwEBH|{=$~d;^B#Y%lA$AJb_(@^o|=c*aBZ0t02xHc-9YcUz2R$Gz)xH z_`IIoCz)IrzU1OXs8rqCbp2TG zu8 zxq3(L@5p=FD4Fit$RZkU&)44d*2@0w&ifv!NUm4U#rxsWK3QCzMs&}fI-l3K9hxhw z^Y)ms_lVw2w%2`2y~S_N&XL|RA2OZIjn7BS>x!A1_xaMQYZ4k9wC~e(H{VzB+jlkJ z?{t72$Slv((&KGc>&tRIlivH$ZKv(LR__PC#vx%DjqiWV&BQ!}dqf3=!QYWRQjZFjmTIQ3@F@e0l<1aiowZyL&5W=fR7o(Z5Y z)goQ$^{BwrY$}xuF=`sf0L5l`Tjc4&x(B9JkwCZMLU?Hzxoq^wyd3rn4P;fZ=Kb^* zw#+fOQN#OvVJh)V3Atl3(os1Y$V)Uy7Zy%-?lPDqYbSl<+rELGwXq!^h$0;C3+q_0 z&speECC#hEEUwQ8ka0T5p0~Z9e)dO?%i>6ndA=}9U%R*HX;79H41!kpQOh+DCxvj> zY4u~#Vdv11=ne*B8fI&wv3e+>8##R{%wG%hxu(gqK3T_^^2Xs$w*Rapy0%E>1u*~s z_^AK@F#p#Q+|Kd8CrEg!Te)K`XRN$MSKq6TVt61+`v*&iA_o#OMvw|mfW+ExShi=b zHBqx^q>i+NQPx7`UCIYHle0drxiATB9ju+o;p}xuW~I zbL+fyGc}$hAek*vKh3`NUVZJvzV2{-Es-Zrz{SPsa4%EIf(jYv0s7-mT@anYm8UR#4A9XHo z-6;#GS+{&aoZk^N*@0r~g3Nh#kC80kTuc#vdM7;9I+j#oq`IMUnbg|1C{rMx!Q4}N z%9JT%VN)wh%}i)XT|R%9iVR&Hsy@kWK;B^P`gVceZ#p`@3|Xn#u$=OuG|#NMxW1%Z z_3U%2nTtfG(k;NWSCBe3taP(OvJ!vtj5zk?ys({SwfqDkfl_J~mD)Y@c>a>E*3LQ4 zrB%+%ZIVT-{Dh3Ltd-L$pQiV2>q@d?*+Z^fqEYj)3Cg!~@6f)dJTA{|(5`5LX7gc7 zUntWg+7;O4(xdTMJg!i?u$YmPOM88lMv)8|k#1czU&5?M+FENOl*d&_(7g#GFAU;s zTGx3fIWZ&5VSDl%gj(?dtXHnl&Z&7uku2^~yFclogf3^%0yL-!sBWjI7049o5$iw8 ztx=Io>2Dthpn$`rNWRmnmxsu<1y$Q0|57eEPly)bA%EZu1}k*to8s4G(#)=2=D!}K ziM!_A^>|+i=s;7KVozE@j013fV(Bx@Yu%-qY2{XAZg|64Nso$}_>ykV3@hVF08%90 ztvH5AMfiwAZNh*p^XdbwpCk8N9>^|D;CKSz*ptAW&OHZ$mEweeaGe-(sOa1wSJSF@ zY+j2`J+X4BC2*=NCw?jx;p?}yu$Wqv0y%3BcY^6M1{LR)$VUXlYkiKdRc*U|+YHWA}~$$o#W&{`}oCA$mFpM0- zLC;1nW%eGB#ISz4kuTi0 zv+!N2mluUH<;DX@ATI8;#&{ulPS;lqE>-y|lP1``ButXO897Pe4sNr|F7%JHNWruA zx*SAaNo|lDI!YZZ9-OzjE*G0fOzD>-;FNj6c% zs@i27iK4^7sX;MX@6vxsqDCgS?=FTyVn1-;TRak(ToF&A9s@ZmiE|<+uKGj_!4BxY zB+mGh%185o2)6TeIyI^>p<)R80C0CGQ3^faxhKdI?X}6&pjJ#zm6XfBZ5{?(4X3q} zm=asLWqJG%(Ii8#l#DMqdrgm1FNs@WTW3j$WYCO2P-76Eco zhaTISL}V*^_{>VWfnu_7z3x$(qvw&M*jI=99(b{O2&tq;_3To* zVAJ)p&}nHObaSSSf&8go%jMJe7+)gmtU+W3;2b00L+H$G|1zJ(j{8q;*kFOto6}#z ztO=&0U5FJM9o}+m7cq^YI&q+@v{n5wr=>VhAQ4m>_b7^pS7jf04Zk;&&-kfTLnM)??|D zf=En*tNmb?so5F(RyNjaD|I-MFqR3*zUXLM!`cA}n za3&+|)Rbi31!#PRshJ2_YypPxM^OsPxlO3X%~&$;&#Rqslhav6L5?-~#z>2NSgL9k!gxIa=xe@YVQ%_$;W%lZYsz3N;$}5tAw``l3Qe>630&5EL<`eiZ@P16 zBxZldKG0FLRCuHjT)yFa1c;Gz3#Biux7Q7!Z=*mG542ehVW&eREnrZMF|bT)n>W(O zAUXzjA>#IfHR<9EiJC3R=R%zwa_q$4cURV-E9KBq1(t;t6bE2PFroeM&k`WlQGSErifV9AXbL;@iNZM{Hi< zSS(Cry2r=cV{?g9p;@Va6LwVJcIB1B;AuLzqOH*W4S8Z6BnQ?DxoD6|Go-$WpZEAq*n zt_{hM7rr!l6z^ZdKmc#mDBc=%*F&WUASL1Q7ClL4s!aH0S;r}l9)do{vAutI6yOjs z1_h88g?tUvB# zgIAnXQK;kCleyms7``p#Z~ta;;+mufDx>tZ02&rXD|0t#?E~Zl?zx6O?G`U2NE;T9 zSj>6U)Of>a4-DY;X+zcTR{Dhe99>4C`u%;|VEP)nuE0j!)I1`v`vNf{Y834akhEi# zSzI0TcU$;OFEX<~MB;aVu->$(d{l19hYY+djc86SpWd6%5WT<4U8%KTo!S!WTT%%% z$ISkRVooQdS8eWwBsCP`Ft5&qrKRR7Y!eXX8~Y|w^5fQOjX&9(irv~ zFSr4Ls|%QAM*6N&gDAb>$xt?>wWb8R2lPrx-_IuW6d}ZlbMXOFiB7Z+E-ATx7cj38 z(Hqf;VLE`|Up`Inxd(N9I&PZ*+coOHYfvAD*(Y57M-#g(2z zB5zO=%_5{%6xM3Hcjl5nh5*r;5JGJ{*GazCpVMV#JNl_HA?Ww$Em?wA5@<2&im0hP zv_==IK|UCrteo<>zslwl%4IC<@~TkssVVqLf1G&AKFIgPDgREEBIz&WySi=(3?4gC zlQ+j;>w;(|HQ5NvlOpHDsRPxrYXpvr|Bme^OS&gjBhY1m3<8wx04c z{@hI7b8*44=*7lt z=)BNUr7XUl@{{R>k8L7vjK&-VX6X+;t9z3Q&dM3G9s=j8I_c2!p+oUPbztpP1ex0g zRQek-$)aRx!|=Hn@E0KZF)4pzCk)=RmA>2f17!ZPUw9zXp3VrY~`U1GP5d zMDOyFQ~O2@!clC{NcDAhf46Lp+o?1OT?ZMM7Jm&Q!%#%JR${lG3EaH?8#S3=lM7<= zW14QCHOU7?AD!TIy#@Z|!R28HNVy9xb&D?tkozG+x{M}O zI*gSj^f=d8UdQai%42YSm&5`xmlWaI-Sn-E z_~v|O=DxXtVsq*+>AfO}t2O|)EJO1gxb&|I%(GPt1LI(0Par|9a?^U|zCzHeN^W<4 zlHNI$CDIg=0y0|OFLVGn^$xyra{xMM3eYLlM&CRFZ9?CL;o`!KM)R%VBV+g1ZDN9$ zSE+|h@cZ;@7iTA?#BAyNYcTOQ*OcV!1hy5}u?z1&J}!sVnZo>Bow6>HbLx91Z-iaR zOH=dP>9T-m zU&A?;*XNnbz7ip5A4Y%%JpkP4gN>6f_&LDzt!~=xO%vG&KAYHM?P=GXHIkqDvO6bq zr{u1OYUmX=6w&R1BUD;?hNC?EEPewvJOoCYBiI}%g68lkxhlkCxl!0e@mW(HS#pAd zsv7{^(hC6!sDg~uoA-}7Mh-lzVFmCBX5pM4I@qjRTKj?mw%h42ZB&3RHV<2izUhr) zqmVIpX_=C-OoD!|l}~&%KC(l3JC<1XdAvJJAO{3Sj_pMXl)mR}k0QA%#711=XcDnf5VNXE3Bcfx`>yeyei?t1ffUTlgah>V6pd~tndUzTFjTr** z(f(jJn8HCs5xa|tp&nqdSsUZZ)f@h5Ef*bQI_Y`j=ykH__1gXU>;VAuH!+D(7LfIu zDR9Z1(i`N1nl=6M&?huIC@#fhmXah}1f4m;F(hxGeK@a8!ID0e!;LgEbN+~x*TFrT zSTd;ue?swTac%3-|6?dIdf?DEsc6qcpH+jY(5In&B_clXW#cCD5N4DZQmmm$YA)zxxx^X;|ZVz z?M?D-Lk!*P_b8o)PZRaN1?P0tkv86x`cWUzhUyEsB04pA8uJk1 zrVGxM6?kZY7!3EpC1@j2G9CJ9U$B!qk&D!a$OVS*d=OG@2gHDXr?+RRzQ#P`NFCxcwHWNf4t?6QfXZCcB13WMKkBh}}Rv?`soAKyvpz0r0|9}m4+o_v)guR*h-$E^GN$@;Op zLWJA)`q~$};=We-Z#ta3rG@3X`-m1yA3m=uE`hsK6@cz1$L#5T0c-@yV79uNn0uTl z;jBzlJ~#QZ|8Hv_-?W=W{kH5kcp3-1z7G;XLgtH` zN9xNCA`i9l!%6m*%V<%r+j2FRQ5}ylxP)6v1QkAd`fVhvtu)R&M7!^E+rPiu_o~(^ zU*Zm4{kPx}A#&T9QSRaj1&_Ut$_eB9b8WOu*j>>UMr0rYobaqiAp-X7MLIT?nfUBa_4Rv zOQPqqZ{LRn;{X|ossA1y7dlU55BFpMYQNxK%#gSi2>;~m2PXLIEpPZ!>UTd;IZ@p0 z{j5i7;*a_wf!sv-nHDx^}6ES^A2Z32byb;YT9H z^=Fc5%xu_d@urDjP-8xFw!7}kRV~~1cRT@HSiEWWs-5vuWfGC_`A||26MAy?EapdB z?~uHYVeq>?!4 z1+n;8ymXlkGwhn*b;`x6G%%haB;y8&=mqc(h)^d#nDH|5Kn*5EqYSz=(+xiPR(o{g zzVyd?WpJt=`x)cbQyiu|MQRwkM|kZ&IVq2OM=wE|Q|$bU2{ILG6(_tpy#qx!(^_1q zT$7_N)Uf##2k_W24vby+`NrdN|k}pQV8Rc3x6b+3L;^fq+E+VY~kax%)qe zI|CEf|0KBlG~qqbo-^~;0j3^}riScI#L)XD6A;3h?CYi+Wcw};9U#QeM$m*Hlja^t zwvB{F=4qjo=rpa(W#fw=BLte%-qa)!7}A%kg_PFuDob6#YKbDgXsLh2${{zrZ+TdP zz&~~KPN%yc^LGKS{y)2!B^50_IYkfV%WNz3)oRI3(IgU7(c060l`SL@!qrqX*&>z= z$*3)`WhO*5nt7UZltUU!InkQJmCnV%P~}|7GYP0x9HM_s>q&7DD1&pQLRBUeLHwIU z^4C%>3AH|t9xc&qUc^&#)DhR9ES3CCT}Eb3L`K2vCL$;;C_BO5L^dWIWuH7bf*@*= z=qMRyTF&R}9H{hB^nFzy_B(PZ^P&~xoLlNfmKr4y32l!^^UMOIoNoNQ8xiv=&) z6_&O$32KDAlmlB%^G|3}68Ut6qJ^?LUDT1FqLLYe(F!nErL>T$-97=;7N)B5etik0 zB&)K)IgFTzXLiMIuzC3PyeLNyY^EN2_Ea8IwwiY!hEgM1v}LF*?-B+0(0wF0DlS2! zrf_cbi3TTjq9#}7l6C*V;!naxa;CIsNv)-DbVl(6(GfE#hMMZg0{TC^_`f;WQX(ZG zvhnbb5;Q6(b~7~{C0b}hXt9G#ZD60I=)a_OtmS`+4bw%O%v9e-KWV4w*vg+|jKoAD zk`v5|aXG&sP}Z@NNwoG&X@3E?^Q2uhyOW!EN5XJ)*KTVO;XVBlIgb}pbJ30Vkali0 z5-(d0)Kt*AkRK6#pxnNBmhh}ADWHtj>a(P=WJhDEn&78ArIb__g|v!VWX3`e>f5hF zzBeU9PYzyd$MM z#8!ALy%L7J9)IAp{Y|+hCUA^K1D1%U#hW6D)u4j(-K|1P;%RV%7DcqtLn6W~Iw9gC zyWSXrp_h6jQ7c`j_{0NrO-zT7XU}3LK(}DixFNdtydh?hNckqZV7QN;r!g-3AYJRp z&gP1^JlYqPRBb# zVooCe#00cq-A;@>za|J-AdY$64y30m-3gy$MR%2WAgk;;cm$NGEO3?d5!!Ds#cmq& zoJGry0N14JhV5`)mrWNT6Zl{c-xt3ygL`lj7yIdt0aA?#tHf3C87VvA;hr;-*%uC? zMT8-jPc5hGJMGvaCi1~WkJdLD5b$g*FFX)o-Rl z;RrJQZ>m|0Y-k|Xu2>701p*=mo_+V1J{S#&Q#2f{&p-=YA}+&1HU7fT$rebTxJVKn zLe}*30e(?`|5L_gsooaW05>poTy69Z>fkQ|Yj$9j5#=tbtZ7iN z1{jQsz%v0-%Wz^o%&0;pFm_Em30+;1VAr$8#;)3H^@!)_KqJS~KV8JXo(ulJF9&{sMPQ3~YC-0iRFE{gt}3W6**xHeEL zq;FbQD35tHu(wTiUDAK<*Lte3;K=t5X#NHb)zX!G(N(0;h^ z@`6*tqn5GS!LiyPV@cV(!|01wrUmOq1+=`AtBxu}y;SG-azSrTgCuJ7M)s~iTp-`5K$s^5Oz4?e0}^^Vp;&5T<$>q@1$*L={X@$i zv2?~@RVdlkJC$eoVd%4)#Nx&@Uh@{yFyo_kX8v@3%L za7V9AsNi><4O!Vz)=_&;PECfA4Pg+Ivh5T_Ez%q;uqaDakaQ(M=AcISIW=6Pw4(W! zZuyt;brIu^eZVww@3kKx2^DQ3GA@08l@f|h63SJ%-m6D&O zDnaEsBU;uLM0w&y>SLxO?A+dvnsnl+tbi zuV6raS^=H8C#XgD(q__topq*T?NW^QuSE8PB~au`P(KQehqYm;W?1+aI(7t4pb6By zw&)mP-K=$yV`;}QIE5ivMcLDaDs;y}Z&nwA)+q?R0>f-Wvj}Y6wzD;W@ur%Afvgrm zF33RnwKJQBTr`a=>4)Gt%Dkrs);$@E_MsKe9vZ%D%jmY4qaP(-H!NC40J&axeO^SqpU!81DTE7OID7mbQ8Dwc4^>Pq8o{qZRmP$v1-j-^Z%WDL%^Y%om))*E0Gg*q5433g2m{g^`c;7E-j zK4P!!la_4dUPHj(%k?~@n)=)&hv5H~%WNxl?|7-$+uM20#H zV+=9@vM*?fge%z^)DUu1VkC}>bwtJZA<>Fz415RZXm5St zp#s#f@s-K%s$X&yV8&VS0qVFukg^BnZ1 zmC!jS?9j|^@VBrLLz7Hs-Vs|B;%+@8WSE)#H>!Sqe`^)}&EsM+0VmvLex`Fd)&pF8 zI0Digy88g{W%&S+Mbcf_PM6PehxNq}3LDu?ZF`@3YA;$ZT3ucT%Ad!gp+B1jr+?kI z0q=zI@t-R8_A{_*#R@+@M{Ss^0zYCSU3Sxn1mS*OU*|J2xcuC1k)OxcgnaMXLgn{^ zFRMD{ssx`S8Vq`V0xsu8vjo}6U3X_~uY^)J+&=>{5_z_+b$@NYwp)4r)dSjhxIR3_ zjTFxQ{99`%j3R%h@itd4zyN(s=g9l%Pp!|+gE~eZ3nhNedzi!g#A&bZY-8&=2keXe zcJGsf21AQGzRgZ!&-b~??za2KU$2kxwo`;t{)>Qjj?il?@+wr19sLHtG>ylMv2N9i zcly^&VZ=tc1Aon1o#Lf|Z~iyk=YzqD9e;!EyW!ikmyhzRoy~cVK$_>5hTLgg{cT&X zrvxAfF~&e(`(fN<#KFVY>x8-oF!_ym+Q#wv^$vNO=kqyDXE8JF=OMgL<8gJw&+tj> zvl;+69|07ykJah(<@*5MS88UG@XQ^DM@r!fUjw?;%dNhj`{13WomO~KwFK5*1`Dz4 zdhcJN?^5MlGJ|4C4eGpK|0GYFOnk_UA<|Fm>a@=Ga&P+Ho_gT#=J!!I92!W&{KEVG zR-pEnX!+c^K5w7W^Hp;~pUQXLuNWk({&YVxb#$5h4q(&E`ZH{~CBr z9&Q8}-o`Wuc-_L!Cb%tL$OSyr-L7fe-}U%x8t{GX#eb``YEx{~Zpd#pnD4a(biEGV z|1DG4etiR8=oW6EPG*q3XwBjJxC~R!^*XVv(b)c&O6qoD`BV^ab^J%-2}$a>Y{?|d zEpI!`Tv@*PG5b0s_fWX}Ty8q0|7RdPrK25nHs-ybB%f7AY0%{Bw6;DP@BbWwdW__G zpQQPIEDh;MpvMXL(PIf5ddLwx61h0e;GVMN_cVS6TrwkEON~Hq)N%FP!{~jZu~;1Q z-}ct!*X;P#e@CC%8D?Y!Q~W?r;=eRGNVWaLcn%3~`=X z3?$;YEOy8XOw#;z*+gvI>K_(L!}h+Fv*;e0BTsM#4cUtM?RKPTd4@l#TNr!#2#>#- z1qU}X4Iwh-rEPJ>>0$%VJ9E$x9_Qj+T6pNoppac`HmdKNF3YxpPed->{U)DfY&eQD zD?2(h&A~`2B#zbDp{{1O<>CQH_mV+^rd3APoDHV1?ZWue;xkbVc|+y@bO|28u(Q3X zBfu~ZKD&jJC)7>8UMHRm4US2R9KHBsg{P`~?3v&iSxHG-|HA2YGO4R`VViGTM~|!Lmn-}hjwMU~$&0f=?$~D4d#`*Fx?^wr z-@8=x)DOp4rDCUwu`TF%U!=cKv9L~sg9&=DqEe+t^-B_|X>MdD740P{Y@thvT8%v9 z5r5IRbW6VhU!53mRezH3h9Ii0RaJdGz6k$Y9Z;aJ%cK7XH{fOaKXRF-X8*s9i^p9=(r6$0nsai`}3mJ!0}_OW91MzIdg){0%qn)!HsV{u~Me%TJ)Vz2b8r?y>o2zZc=$)Ou2X7!OKp- zGcFA$X4)EF`)fwoWu-2EmAi&KpnQIXJ3Z?M3c<*3*kQTb+l(^v=y=rDYJed(IkM<6 zHtjYQgB_whoqJi^XD!>NnY(iB`NwqjcZb%VfRoeSZ+5 zk=9eKW|pHFyl-VoP@(CGRn_jd;Azn+6R>A%&tA({V<@vuZL8*MP&M=w3(f5=yR=OY zi^~zLcSZ#6#ZFOOuAS;7S+x3Q_jT^8Mb|F--*UCnvlw;jlyA#7GZ{k_qBQ*}_MVyG z=sKmO7(MRbrlq7xcF*HR-&^?48uj9qAS|NzO1j%Yh|pT9LmCnHohrI_C2wjy%BGu{ zGvk(R@ui5KNBx%8jxJuE8X28#WvT)=Jm6jWkLNf7&dfB4$^}=_NSu9GkyY&+yJ-ij z_gOl%pyq8&>r`LIB9I>}&O^5BCgpq%jYkg1GAlWPl_7X#KK_YRC2!8%TErILmKN3c z72HT&Dw-W=~k3Vq+>X_ZFLQm!F7fp=v_WNWduhUR=Y1U9J%X)#UcJVdBuOW``#GS3T=EkX^kGppSQngUDu zikqjGBCzOgH!3xW9cda2bmjPUBvho+AtWDo)Tt7VqYfUd!RU670|&$uAa3)F2-z#u zH!Q23T#mP;kK_sE>Ld0K1Qg6u?l?{3b6Ele;bpvC?i&5TfFc9!3q@b{!9r(P!MG_e zvpO|wyCOwb;c4FS;=9e!5pgeQ-U8W8SyS9Me+}!J(w=kGniS8K^)80v5_XRPp+RhVsmAgD*O;7uy*mCmI$x!ad^ zx$?AC8tO4@^Ch3FTs#d4^>1V1Ww+*mkR`^n36Oy3)a}WMXMQVqE-fR9A?# zimGbdLLn%{yYwICs8^6rOCHLx6US#R&xpmz-r7*2C|~y;3yk4nuba_ql1(CsRm!2K z4-lc3(V*GHXFo?0=w23M%2APBJnKU=5CE1d8i?H@1D=O)z9Jcr71vAq8C7tTzB&_AECYnxh z{C_whOI7!|&6iNJ5<8oWtNrMRq^S0~=a$eX`_q`F_afkpWO+)f#&oCE!YbmhOO<(R zsizB>{HB!CB;ZBPO6f)mv}3r#j@;BZ!f1|0X-rvnEGz3<%GIu5RrKi4T498A=#Eb; z!MNYku6CD20yOA`n8tY))1HS&HiKB?{d@vVya?@W z0{ij+a}Y5hocta#eIvxyRbakK6y<)r50n_E5gJMQ zs&U^pb+`F$g!y+aB{uq`wsOiFlE@u(;00*j@6FjD=~n6a6Tf$i*Y+Q-)#38>;q#e~ zw_@`P*pEixoaeqRTcEuDGt5T?pQ*jLzQl1CfshdChG*YIy8Sjh9uelX=gx_Nl{`@} z&WLzg*Bl5Yrh=l{1HBdTmH;gJ8#|9Z3W^@LDXf=cFBqK{8Iz2u(;(xK;OLK$F2w2U z{+GyL%9uPSco^UBk?%D}AUEIP1FbOp5_H?iIBg7YSme1yqaf16IUEc&>JRaGK_m7u z)y13zS!cT?L-ExXRKHb%;I)XtGe)>?hcNtsRnR$S*QNS$`b=tcT`j54asYTX!oee{ zq-|s@c}JkaIQhL`xu(QC6CO9*L`R74#@3PMm@H53aeN?7{F<2J5SjInt<5hBoN?E=#phfhg(+-DcC5X&~%^)^|SK|kNOy0iAhX~2D__^s%-b_B|f7%BL zhDPnqkq7b>*A9&-eYu4wLI?94-=w&mlH}1G52V7-nZL+>^Jnsh3fL^6bhwh`%i!WT zD<6G=!+C{U)CUa)*-KgBOWYQ%+>uRSv4M?n$w>^BKW9_(cqQc5$FIL*N6a3@8ar)J zb$G3sd9-(Osbw{Q7O-gfa|e*kD#T;b`AgrG;r%d$<^DY;{iC~M$@AqpK}oN`^3q%=7V8v35PEn$;o8YbDSchbBWCdFk!%yvV%8j2^`sh%VzUPpnV|Q%G#l_o%af3#a9S zw!8Q_axG>~WQHjx<&!3`=;5S1WVnP-yI|;Z^PK%@>UV6t{L@qdpglJT{HYwo_+2FC zU1$JnPNq|;)Y9eed;Ou+_^msut5^)-LMZla;OZ&ISN(u;i$A^b3#G_e zitZ&MU&a!xI77xR^`c{-mFtVd>$d0$=+xm37KUfH?#d%mxQ~JMI>8^ZL?6kUR?sog z*x8EclplNq-1gm-ar8$bDIk0U=(?=;1zY;$wBb}L^tuTlfIon9HlIS-7ooGSyngmR z7>OyCIz6`#=Fg~8lyVtLqm{$0d#04Cp}R$zLAVo5Nb+`c&XwsQbA{7(>d2?gi|>H^ z9HZP2+GgG{!`?S+dwn&|K8BsOvO4iXrM%gaAWwb!)lD7?vug1T^e=SxHc)uSeHdNO zaE435(|nD!My21fX2McVQM=40$-;opZa8KZEZlr|7G_uh=vE%~GoauJ%-MGKo_6G? zLNH6^Ku7_^iv4CruJ#}B;>d)eS@TkqIj;4^jv?Q^QPFZI=bBNARm-W+B!;1?cFQ27 zr^DZ7ox*is%ofmcx0bxTA7b{Ci<*t zw0=;icVrq5D$Zz${>qH^?J!Q?jDUT{4fb|gVS5x@uKDQQh?xyjnj53BFhyq;#Zp73 z>nV_WIAP8%CP=}bIEyz^h@GQxH!AW3?XQmC*@yz_243oDu57|1C0|n2Skj_$qumNT zF>mT`gCEAr6k-~&X^OKIUB(}(3T~E#tPRLN6tbAxcp9Xxyu)x5A=@}2H-vO^J^j}V zUoDw<-YP(2k4MbH{Pc1f#VFwKNkn4#N|h2!k^>bHDnHrII@vY1>k3u%aIY9w%Lip_ zYWuSsD6gW4VN|>{7ZdUQINV&%KExfmull)igYFx*2pQ^(yV1VgZZ>i0|4cku{pSH zMh|INAk5yWkxh^Q^7K!4HP&9mbIk+6oAe10QprNrx2<5xjhzBJ`w(GN>b8;vag%ai zxHMA9%513IuS-~E;D~3P=+}8xcWdVDnc>zSzX6zNh`vD4gr<8nZltZ;(i;47M@)y? z1O~$JPjCk^TM&fbhN~kHHpR`CA$Q-569NDDBrsj`;OC%6ia_@g#YIT4^rRW+1=iMH30A^Jd$GZWy`eA69Gj-%dIuMhWXrj&=y@*hPrtg<$ z1a*irDd3=rOEA=eoDcm! z!k;+nPalHt{-7^@+t@TmF2eb z+FtU@i(4fPKtcVkOV!-)THmj<_SB9mqi1#Z=PJ@#YcObpw;{V(LeJfI$Q3TY7#vdV zWkMSfcH1!kwegt@Ks5m%shZ1os1Zi`Nmrtg4J~NymizMu2~H!0`2y+t0D1z_r;}B? z2qj66f;r$g94!($Fxcaa@R->7$f5Z=0Q^5ezc6)ZRn%3SJR=ae#KZ*XU|4pf1R^*v z)D$9XNw~-&MGTtgFd!vhEjXg0w1`=1OFQtYBGnq@X5)Ano0huv+vw;>np#Twa&)xI z-n*Y{UCXC#7lKpYsdRw4?fG0p?z+`~Uu}DaMMApdK&Z;z!pdXK-GM*Acrs9HMHK%a>EmrlV zkft<|`ml@=tVY3x1d9Tpn-wlXmm<@a4qm_)oQZvM?BCbVBxa3>{;kERP5mLCLO9udX~8^)2~ zia#8TPI}HF7>ry2StNc$iv&*sFBnehjDQZ2H#?c(GXBVQmoushbc;D~NPg#w-OVms zLPO>^kVa%!lotG*3<90l->0~uJc>sl`B7x$DtcqgBz0QiW!^Qnk zJTJa@mJxpO>pfb9Y)*Kt^0X=;C$n~_cC2g*8M9W9Xx1w|HeeF3dxkMmkNk=Ac- ztl2c6qj#d|@_0@EsOjRuav|&JE2lYm6&PwG$*)Sodo{3y;t=x|rv{?(xwXlwQAdt+^*q$ICu zXj5%A+2JA+GYT7Kb=qmR}Hlje#NEYa988tDRxS&sxD^FuT0DRfS8STSV?Mb1e#`_?vQp5%@LzM zKwegY9I|R27_+noN_j$M;QGqETIoQY1VeDlk8x67Is<}kfR^-6Nw61UhcunIa!JaC zG?ut$!afKSRSVo+HI<6DdO}_*Q_m9u!whv#2w_QDIqObNYWVC%v>K%fGCj;sO^OpC7sMCysGles<0y`y zQxBK;Kc}7}vVwzC=TQ~=sSy;H>biEXvrzTA|Cz**PSM#t;dkzPMf$}4(a_EMzyzjX zoeeEzO-@lW3Jlw$sO8)*Wotx)UU0}kAo)-i6Jm~58jh6|osw?cv4xzR+me0I`$XJ; zPwOM+gurE9i$brNYAOq2Ds(DNpOhxhP}-21>%t>EA`3{9nS?4qP%jA|Cj{sAis7g| zSUVHeC8A*aqH!(GZGSW&r1_3; zEKejO?Df0m3=ma>Jz!d3Nnuc1MhSPFDQrQHscRsRK=75wsV9WB9n`rP#&6;sTtScz zpqvJX_b)gXrhf3t#BKOXo@>cN_`RuvzmB@#{e+jPfr3;2J_&#^F3GY?(GsGc;Pg9Q zp^hvc_$HX6wUP=HH~7O5fjm++cboG{5si& zV^QEwnub#9pB3WAJB0Vm2kv>`bw)L^g})cnLGu&Kd4U_Hg{N_l-|I5`qty4(|6&hB ze8TFFgPfF(9kL3U8+81OXC3oYO!EgjdZlNo*})X?D!b%BX3n31`TG~oEU1tdr$FGC z@KxC(Epj(td~=_Pa5f-fHM*Xg@ln&$n*PWT`m{)A^tamOn0#a}4#`Dgo&B44ua24; z7lnWvp3|{xu?l!{;{O6;&O_u+0dE4{K zxtq;KZBM(qQ^${=_14JCTJ|AFO&me%i&LUsN9(`ySOuYcqt`jpk2KA(p|W1Rn*T}) zzR>(vL&U468QayFNBLZ>W_H`bEJd9Gt-P%OZ_#o^Qb0DX7yg5-&dzVUCJa6q{pa=m zoi2CTk7IE`!xQkX=u+WH^|5E_vah`NrZI$op}XFk9?D+fl>(i)t8~JhZMMNb-_P8; zagCjcFPsX_dJIYVXhvp zw3?dKylrMNs(yV6i?Ok}}y?4ESAnITKyT1nn8!JdyPr-+X#g}AS5awcic6uD@Ft~px zc@<`Lp7Z~d$_+HMk~jVnYksD?yFgJnJ98>?DScZmbh2TTB;)`WYrEdn>jZbk#)ovL zFf_Q?CsrZCNfE3LoO}G#b!2k8%2#dY3*mGv5oeIkm3E8a0&BgGQ(N8buCMI|QNRE%WoeT`83xx9| z|K7y=@H&pskGdLSh7`;$La6u&{yp8>^*(B0r$MjBQ7anhM~zciE$w3pIL?}iY%d0> zJLh80L&WEFdPu_Zn5DaF00%O z&Nf$=t(K8HOg$ohHFe8rX>_y&T(6%w4u?VI{W$n4?N^8D=d%~$>9sYps@M31u{9QL zKwN5TR^zfp`vTmU(Z-tFHO7W-qY?q!w_~F*huw&qOFn8HFL^}R*p~|&r?E0VDG!S0 zn^8rr(anshlapL!I2y25gQ#A!e2>W%v*yZMBFj_*Xy4xqI%ukWox!L^RpRII%0&E~EdJ~nyvEdg=<5B!!1e$x$3ElwU5YPXec zItHz;pN*uqtFLvn)_Fb7H!Vdv_!xx#Ye>5(C(B1J94p81dasWWU;N(>(1^2PS;pCo z6Lhb$Gceo_m4>%%&#=)RYi4|QGV2!&_WDz;$D{{h@BuygE@pdggK}N3aNktzKZbKN z{lYpCPyOGYShK_<20vfl@4z0ORk@Q#C?X4Y&7(a(kKO6ZC3b+>Yno+;-BsJ2E&xjm zSn7(Se;acR>(|B5MV5#dORqI4%``={1xMWp}@YLlF*l zzLxzSJ>O&N+wJ76s;tVx=W8PaZGrrZi!J&~FYk%2>K}odoqaz3{nY2hM<<89TDVzh z@m;zzpP|p>E^n&d2%i9FjewZzL4}(O2gbJVqgwqyE^kbf!OOaJ|{dz&`U&ItAwBLQzgs1sBs3H~wA;Cmig5SJ36U8#@;ad@cy zpZV7nqJw;ghM={$X3%=QD6Xg$|FyW@eP2e2Lw(JVUciU(4N^|FO<4Z05kY-ctjB9BtU=V<7_hb1{Wb^sq~N4ATz~4MxD~Ei1%gn)z(t=6rO3lJTX?! zK!Ta%_{8M+95zfsKQ#>l2l;Y>{N0mOMdrA0X({STMdpOcqw*6~sGPoew7;@-DZd#E zxCs>%m;pX620t(HFWYAJ+REMitTcWl@UISm-E83U7J&X>`Q9 z`^QA5bqGGpy;(T|O2j+D@+G$@Qe%>aHR`_#g=rIuzdICGah-|YT?du-S;i%TY`eFX z;J(pl-u^cC);2A1U1H@{$(z!WQP9jLR4Y)J_L?-6o|v7v_(ohhyY*@%Kf-bE;)yiY z3FVitd<~%}Kdp1|dk-zKl5kQ%`Z=Hg!2jE2Zwys2um8vS%7F#~BKhCik4&wM{u|Ho z5A$V-dx{-!yOVrNCNojR!xfji)~jisuHNL=ctmC|^6^1a8Do)sMuWoAK&`_i>6 zS6*IY6w1|ZyRpWI_0OCT3+fq`-W~G-dTS!;#!ix%KiZROLtk! zVJtrHg3IiqPyXra*N#Rf4>!ORr|_-{b@6a)l&>rt>D3{0uKWr|rZbx%%e!}P>7vxE z-FPpO{H=b9{Jhx;9kx?PReO!R$H+FQVY68Td~(ii;cuhZd#QzX>`Rdmc3vXKlW>?v zWw2E`UZJt(GvnjK)S+Bi%~3nswhbZ-^DpEfdse-ZP81r8OEwWrcUc`{*4)n3$X0K3 zye`u;r}ewg&KosT$<+c<+_JrQGGVuEJ1pWMH+)RoTCr9l@G{F3cphEn+^qO9^i^xn zNc;46Rcd4Qhgv0j?M@m``$ec!*D9mCmQ>3ahaCoN`}_JFW(XB5zH+PfP75?Yavb7G zThCaNX1iiara#!S&iG>{@;=j4>_TL} z_T*cQH~nV!09FDy*tBfdLUxS&SWvvYD56MNV9-c|U3+*)OP%Ti9|Id)gOlIXWjBh@ zBhXnQnq-m2g}*K1OfBVeOStHyT%I`5(V}5lgZ^+UamA+Vr4}oJajF$|fcIN!&KcvA zD5+4~XZ=RA-*=kIJvf%3)=Z{ z-dLS~0pLZ3O}eF5FpX1fEvdTdcOuIiHOEG4vo}mEl3b6kp%We0uTf~ik=C3YnS4aS zNI^wZp8^#2Vw1CkOpa3@!e%y@2Gx=Mn-1z%5`1eF5w$#sh*XyhrxhlxOhwKTHaT^O zSTB?-c~bQCXrhr|4GI|w6+N<4xOMNUSmm`Ds)%ALO3o7K z0PS(f4%RZIoVgW5D;=<0upH8joiL%T@b0&q}VA#f;)G)N#epr`|6^^Qopz^$mi|Ls~t^dVG2Im;X9M;;+=;Q#K`d_wX3jBYx) zenR*wHbjw4Er?+EkORF)hPThg_YwUB2nTnQ5+VcI;q$FdGLZb2+3$q;`TpVOftHM~ zk_o%GZ&w%Q0DK`kCPw~@us#ckZ1$>T_#26~=@WymtK$};*_CGqKTp@(ZVnr7+v`4} z=C7&OI6B?@p>s>tC)P~Pwx|^jdxjf;*!S%H>}$7=e}_n~OaH|-04DG4YQbdJ>#&8; zU0o((mrM6!rJ;6Efx|oOK@v}|;?Zo_P2KRlntPDf{>$nYo@xFLT@%q*Ma$$2K z@P=5Y(?;-iU$-z6u59volD-fe#j@euGllAQe;p|Fir znU{OZK-<^&zL}ft9RqNqupw=W0ruKV#Z>-rz%I<@Dg`Z+>&;i|&>xr+|+wjc66(wu;5e%B{(tz{Toq zD7yLb&gsN@6e%fo?4o_qtR(LfxP)HjCou|b@Z70JxUFI!$J8$CMmv;wg z#VOZjs5)P`FE(bqvZuuJ$LFJ7#lG{yaflA?vkv052jD$|6a4zyjJ0>^Zq4g|axF(9 z*rP1}op=9_u5*YIEo#zi+Rl7w+r~@Vwr$(iOWU?>+qP}n?yTz3UtPV{IioYXqZ<)l z>^=GGwftXcf5t|(hX1{F=B)CoAv4AJp59sG;dFMmYvYY?JiyFo&bWBR=DdnG-$3h# zMq4jd$@t?BGQH;NLd&zG{!&2W^ha4vcv(hskrzk`tDKOPpj4tg$2YQ0ywV_-p9*~= zs5I3t;9s!~)d74llpF6yyRv|ghtm!B%|qAD?@_lchQpr3AM8q|lex}unnV?tG!BYI zXdeI)1Zob$AW>YGon-aTm6@FpXjx#5%e%86nqWzR6?W?_P zo#afdYRpn#^RFG+slgwRx@r^HLAzos+62>TN=a#2B}+on<#^3q+4h5(Nsg^5`SKC) zrkP4d8(8={ZIU#qJ*Rm*r!qpXKdv~!}P0Fe#{FEmi&okMf0?9 zl@%qDL(FiE6&U5a3#Fr)jQ0GGEcXenbJfN2jZ+3C$< z_ahRp;Uq$L|B6E(B)C=cBBH$6qsg|W9x@=^r^cMrrAoK|Zm<}sBgxi!5mT1E;#S%a zn~tbNL=HxjjtX=kA-%7CW4nW$hrjTQ#>ygh&XS0W2UYC{4Jn}~78XXJr7?!-$B)v8 zd&QFVR^B6}BiWnR4T`w?r+omKti$Ua7t*~)j#87!?%6TBOQ4hT_$Lx`5hbTNcAL3z z8!UUcqX;>IH1IFN-IFDx+)`c&M|47gR{Q*t_wu`tWa%?YVBq%AX{pGo;^s2}jsx~9 z>abTpo$6u76?YONb>HVsbuif0K5ZI_rmx30pLU20xkm*;_g77J{W9kjsX0Aodrdo zz!%Onh}FZPM7-NaLBA;-FYsMQohgvr2c1sX+O3K<wjg_Q8x>j>NidH zmti$r&-Xg41G@DoGT=S0(nXH!XJJ&((}AwFO6cOBj)QO~h%FAkpYwl^EKD;?x|vlnb%-OHX`*J)1J(Ak{b zWr|0gviz^1)=oMwxXQSzU5}4ADYjfL*Pr)-$AeSB!TpW$AzJMPre!=3$Y>(@(qXv) zgyqI*Dnl-sG;8mee?X*G{0SC?sI%M?^&3f{EJUPT|0wh4Of|T}{MWSSGjQSKS;?g2 z1udi0R%zU*sBZ2~f;3~wc-S_qbs$(U=`5`bSekiu7Tey5n2E4xJvqJIlX_`2sms%9 zrS-GywbyD5863{U!JVRG_6|;Q2JAula3<;!cT7IoxiOgoue9I1dfdl70y{3`NbLmw z7Y87u@LpTiL9--t6NkfY&8j6Sv!Oz>g>joBoaHMmqGv{%J63)*`|k(sKkKny?by)= zDgeMB>Ho)#HPPYA+cXUAx;=R8Q^;SpI~~w zFqohULP={;q2KT`+%zAUAViR-SnCdH>>dXnqfzSG+Ip1%)kkfNl6;49hpf`n1&hBPyLy#-Rc$79hV@c-!jeiy;7orTT_l}FKiU$#cm zmU##n7L7(QX2?4yuH4Zc%rFYpluSm`ugw7zoYuSG#6qzo4wYNH6DV>ufEVu<3TKo7B<}Jr z#6GQM6;zU=>RdlBLxxGm8V$6_sZgov?BK(utszF~Nh6?SC5H|;r4XunyUyD+^5~i2 z#!kJ~JoPa!WI2F;Vk=Es;ci!|MlEYloJnWKsBHOp5$bLU0j#gZsQB7ZS%}_rEZwe2 zr+P|>t825=P2V6G7=uEWPTKQ*Aj*PUis}-**TP+qsY$_7yn|{-@s8A_IQez1f~A-_ zvM5O;1e>7C#O>)>Y67{}$>z5`fKDsjcm|+St^|_T!mt1sC?^FH(L`d|vnWT+d>PUw zT5sV@VV`2bf}yiIZ{u+USFlJ7H)YA{{I`Dlph+W2C1;Z^ptDikg1}d}6TxiCQi_(s z86h()EqaA>t8{X)(+7$wIHiAcW7x!7g3J^@0n0jOg}Y|{T4T(TLr0Ty(yG?e_D`|91Pfos<` ziDOBm>u*@9M^g5@P{ERZl}LdgD;6<87JRcs-Wkz@9Jw_s)kR^AHto`dPK4B)Wi}h< zOfZy*C`k$d*C7+m)A*4wAh`i^F#`M{dALkTzF~}#Ws*agGK0}k0QzuZw~1==S~;u6 z(zHA<0l5lh5=G5X4$*m0iwukE1r^@oc*rLdBkHlYk5af&(FFOu|5LdTpUH(>zDhLj z;qC>o3Ym`w>u3Vg)1Vws(!M(fXxmDLbv*LimIZRrJwX;>^$M2rP5r)%fqGc-&;U~r z=1^zTBwOJE<oU=h*Mu#{veda;vVm*nx-++aY-F9Wj_7}DXG5ZOcC4Zm89 zTB5Z|oFea9BMHLjc{vdQ2Pwa&Q7~69EGcYAnR1V|V~=?G+lwgopplJxugQZvy*{W_uJGg^((-3|^x8keW)cA!y3PCa zCWSSoiYS|o?9ye)sshO`*_K|k0rs%$Wv4opHgqSkRvU(?7P9#%WQv|E)5N?S`OKhv zqdAv9L+@ytQ^or~oMCq_=oCvZu$3*!o@LhUd8r*!4ozmO@=gh|ZRtEkt?nde$5YfI zlT#a@s^FsR`5p2!o6K8eH1Yjj{DV#p(?28*?Qq)8Hs3(a(rsTD&6}Ssxs!;& z=-Qdml9xQE7Eb-*iUj;b`LN{49H^M|VAxN+^T5#+Q60;r89#9Myk<7kpb)AN9S0h# z*hK2UU~Az0Cdl6zn2rSuY(~PnW9*e%iLB1OQOJ;F0sjWTO__-VK6X9Dh_2sQz84{`fsa(TaEvLQ=Ko@8_OQ4O zGO2WyQRA#arrLz()%c0>Sf2VReM$i6)$@fL|v08&q}P`?u4x^(}s z*Hep1SkiysyeD_tPhX9U6I3{Kc0Ii7vO6@-7(#|v;B;j@Rp;tEPPof4Qv+6a_qi|rHNhZ@aBf|%K(>>G)5&5@pq#^sJTT{1-j!9&;lLg4{OB^ zod^ugpbKoxL{J4_031x>*|)&AAsA%g+oVKP1+-WG9h}1KQwnIo4iab@TVi)(o(-th z*hN`;RmB~umRnV^lLkd9=}f2-zzV3g?>qh(tj1GZTlz9Iw4uJn+%U9JaZQk4k!V{t zTYa;Styzh@s*Y<$I1uBqVseZn@?O(e5wl>sccyGWbb?}w2$E*at#?)egqR@DXANJJ z4aPA^t3IzA#LpmVDFrFeBl3Qyt3zg{_KgShMYoCP@K+W{TLwR9hmYG<| zyH#3{j@w*n+?yEJ%iPZxS0WRipm9>%bX}pC|MhCDO$EhN zqk}{uLQ+|^U0UK-_o`8_Hpf{M)Qy%}UJ@v)B5EjQq!THRb`rxlKHVi4nyeaE7&9@i zVN%d|5^e~E*~4}^QGL2t_n0r}KRm(gHu;dWC&Qgl36u1ZwR{W>R|||HP(eSn$rP_U z17HEK>hXG%gHBo7L#|y~P+%w3Khy67d~PxBh#A1`2)MU_y0cHSVIgMAt(h!1J1`gt zDH#oV7|qQf5fUR2^3y_+Y@uI9KecZ9BWXVOcUH}w(yAKE^jx%yZ$%L}9)x1SXs}H# z%o5-%5=e6t<_QMDW4zpADY1f7qqiE^kQNg&F)(F>tp!U>Qo!Fk&9Cx~wxC6N;&fNR z9F3!tKC#_jC^uh$Wj~6aYSHn1pwj}NB72VM^Js1R>zj^;AKN{4op$P-gYiVN6lQ0o zD6Bml@(rrU=1a!=*P3%OkBZzX(&PFi^GQJux)fXCg1@;LG0C_Dj5)M%EH9^lXXq%)LuDmbN}1pXG+=vlMuK66NZXD zf#rFBrv-yaAgQ1fvjPWflHynQHU$^L&6eO1=(X4%rzE-PvuOio%*+{&a4?_GrU{XI2Kexq`XO-O8Cc4~_P3teD?#TyE2s3JTaXjPN zF~1KLMwTho*+1PCYh3Mi1`@jdP?;jBp>)%XMs%N=uqn2SLHZr&oEuowJ{<+08aW^M z#2#T|Pq4S`RC!~l4IpD1{~4Ke(&=@H82kkGhjJ~>pGOX14*A`G#~)KVj29}57pn9X zaD^M~CVACZsLFKA%U<3TxWS!slcJ8GPRzU*Q3L88+|hTO1m2u-8n4@keC9ul5~d8} zQUJl(|3r7cIX|`-;u7uZZZmyn%^`;aKDQ`4BvGtT%f!?JH#;&Q z#LM-@F%hm2)dlrF#=2hm(?#VxUy9zoFxziKMgEj8II4C!e!ZqX)KGH`-V6vHe53TF zR4epy0R*YX;vR8)RnaZM1L0t{IpcVNLMdm9MdCP?D8T9=T4mtV75o~^77 z86_~Iva<~0gv}+>x7Zq*STovCUdD%dO+RGwkYUn-3*q;I8dOPdVabHFy)fHcdh*v0{E%t6#M+thh2+A5W9o;7T#)M5}Wa8O>(Owtu}G%0hdiw&ER&L7%x7RdOMo zGL4DB?s?=cq~rhH)Nd>NWN{q()I5$~2x-~4)D3>!i9G3@Gri8mBU><~^hW$l_Xd7# z`iLEC@J2jfe^(oRpy2u7{-d+Qcj;m(V;s{u++cK4nWHGGe+@mFstf;MNdJ;bk0x&0 zZ$!p98Z&e{X()amOmEZ|>&&QL6g!rFB(-c~Sb^VcH-tmg(Yhaf5YCxE!Ff{atTmcV z;24v&;U0|_*hY6J8kO>9Na=~9U^maOR~TV8PxAN7^OqQ}kP@l*`sIT|EH*Rpt-D#e zqZ;~=YP)Hxw`oq?ci=Nm^9dQgP8lHM+nLq(62|NcMM4ojXuD?~l9PMLMG*fWPJCjY z#4R3A&fI#@s$0qDMVKliTl9*J;}yX8WS-jtwrzU)^$VK8diPkhOL-w~-s#=@g@`(C z!4#E<&0`E=<@kUJS?Eu(J9- zytIFcBxL~&Y|OqUG4mQnsnrp8kHr}^``PLj%85i}TD){fdqkPs`Gp@GMGuf-e^atw zEd2qG9V>}arPtXvRdQL+RCKU^?;x-^LWt8EzsK57yB0wa;{G!+bU0kmm^adtNy^rN zRKwron!A2s{++yhGwCWDRXvUa=bM4zrl|#Fi*I0CehK@d93j^e?clSZyXR2I3m>rU zPBo-Ubkli-`5VIBguHgOTmP`T>oEWBeyz}F@||Jy*|6nMlIS0}0b|$(#_+z@PU_0p zzd=~H^|wP6Gd-jDo%0H-H$_x?(10Y+w11p6Em{Yki2XPU{J`h_giwC)HTIX1>dfJ_ zJ2*r$Ubq7M08GD*&QuDkLZlx@&3+ozW)8e0OcZ{Xtp4D1=M=r2Y!Ub4fj*|n&f=l7 z@mFOqZ0f0aqc*-hp!_6Ke{sk10MDa+L2cm^1%66){vB-Cm-LYL3jW$L{DrmZs{Mt$ zLG)M`_YLh)^zx<7bm4JZ4@UV3m-zsm?FLw1G52AJ{p|Q8n+dpG;opm(_y$M%CJO2i zlI!lv$#YNof}E(S{bWjZExo*{<%&7dt?iVia?&_>%yDaxV!K;fHc0QAW$>V9j?{^) z#r8)3LMMQUu0s(VQX~E0r6+c$7ViNvboaxBy{(CTrD9M4rB|JJQ$|;vU_Wc1?d{Ro zN8lzsWA5Rf4qz_sRhmDW6g~PxAGSU0jKGM0 z#)V(?;qrU1@cV8rOz64+Ga4b0U1`zI@gjHU>3>yIAFYrDO3eX#{K-6SQ!htE6uqBH zaOj~pd8~=96EUFcLzhFn@0UY$XuIf{y-wqxU(&wMe2D*{Ly|qS{(5oo>W+WponZQN z<@+7w@GL3*BeGAyz4OJnG-Dcn1Kn0r`tylr^usRxBXpj3>Y7Eg#RKsgXiyWx^F0Fl znwfNOS_E7ET;fZZ-9qw16`pewj@k6Q`;a{7jS+|#)9ij7^V&`NmJ{bAzZCKF#@t;} z#1r#V^E*`kFN$lTyBxAA_6Qm<7<@WPSpX|AxdN8}98f_15dwn)Ca{~*0I9m3V}rOH zdnKPAVOgj!Gzvqg84(*{h};m+@;t{nheGc#hnz^M^2J&{>U**~cd@V6vGB;kaV||mBE^Nyh`={N; z;2o|?yH*eL+u^YAk2uejX?tfi3-oBsVaI5v0;jd+jG{95EpS|m&-y0gQQQpL?Yomf zuA$ZZ!Nd4d(agg>!IXPy*y|Nkv(KJg(w&_|=0TgTP_U>%EU{--=)8MUCwv5`de9f|5}LZ&$a*PvYyhO$Ub*TGB|-k z6)f<+~Ui?_7(pC0mvC(LEbocdoE|T0|c_g({@U*6MZg-grOL4`BobW+*d0Bc$H!M5mS&;eZds$e>mKo}Ff383B zSj>_UJwyEnGKz=SEGMBu()Ywadv0R}8*^fQ z+rovycp0xblFnxs9-tiZPhfaYHNVMjgyWhoH=q+AG)oZIk|ANQ;-c&I-Q z76R;wE+|Bx2Pi)J@m3zGH=7^ZW+0Ov@D~XH>1G2_CD{|F-;vw~u$Epnm>%B_ty~Gw z%zj)IepaEnLQs6Z@|2X4lTb1DtJ_4I^5X6(>|lF}?J1)=y)Njq*%4<#3+aPWlSoK~ zvc`O_1R`f7{$h|=g({`I0WU^s)$&E*Ujore28L=v94f77Ir}7`yc7ckqi7wHAtbv$ zq|*6*rV12{Y~=s&y5)%vMT15WF;k}Q3_Kmbgs|!8a7S)#Vy&%iSrK_16ce%SB?g^o zwPd5oGRg?(i)+}J=0+w~Hb&sT$goKw|Lv^-!O?|Ykz}lSLW!959Uq0l&NxPq#XMFz zw<1ZV!`gr@1+3@7EW&Arb4+3jRV*VTNkzZb87x6{AuZIi=5r(#$t)t1OG=8Vfsl%X z`6fXSp&-QxRW0s9Mc#rispqOy0OLWu{SPa&p#_|b3M>GC+piV+w|)Nq%G7^*=k7*M zR{z;PcUFV9Ls7-p>1s*QjHbRcrW5xMqHYU{4U%MSgFeK&jo&=N#D^r#tDIXOL;ZJW>RG@Y$lc5^K%beSk6tyoNT$S8opF|xo)t|N znPx>toG47MbqP}MI$LQW`MR!Hajml2oyCy06Ptad6C2x340m#ym~oM82rh)c=<5Fb5lH<@C8h~3iD5vxeX z0dFW9OROefX>iOdlRYoa7C*&gFF5iSM843$H#uKxOa~}#x6X_78S#oY=B}DK#qjJd zE;%uZk#5y&cFU?lO)2v%jU)Usoin$Y&J!m&oxw~nYy=H&WvwW^nwj(-dp&Io6)jzE zOfY8O_b|Wv#FH-A=ag@vY3V3g%W^8MZm%eNJYW9i8%N`31?jL6rF8sbfYM9OKVziP zWX*k0UmR{_s4=5<)b|qfXEvb?27m3kUQeH3z@>f`=`+WIWHh0@8CFr(>+8J(2F-{d ztC<6kj4X~aU5;f*>U##(y}f%EyGrGJYiX7I$TA*fs%congw~N;4U$m`?~#4rHYbRa z-{{`AfqqzTvp2jt-Xbjtjhs+~B{Owk^hO^&@-IA*hfrG`?9~tp`Iw$B0A8|}k8YHx zJU}=*GZiopCj?wlC{o2{sM;lE8+k(pX2I#waH&$1VPudYA!a;V-9a@{jjT}vv=Cwk z(CS43*Z5zzeIb1WBFIR+Fs{cC-f()dsc@KQf|$%d#th+Fr9pA4+$(nCi1?kIqDJuk zYm8~!D%2CX;NO|A%$52my*`6fAx6){_^4r*>PIN&0wv>go@hP0+V|kLLmS%?{ExJM zytXOx2C1NtJOM|Hi0e}9rTovNv4MT5>GoGi89(f-GABvg=+cNU2@~)t=@iwv8~@^= z+p_cn!b$qXxK+xN3vOd#PXMvfSJ1C&MsSUbXR}Y#dSN^d=mR0>$RJvv=p)u5qb416 z3ADB7wE7=S)BtXV*Mewm`gOsU3IUcxEE6T`!enFdcNz8lb;BJ{00AeytnzyeVt+#v zAcdKcjD6U3#XbB4KH;J!g(tj0`!J?B*2Wf;9}C7-kG7YGJUSv_;2l-y!47q&GGLkW z3-ZcTgxm);00Z^XkEb|dG=eIyW4;W?yzyV`qKM`!ix5EwkLDLJ2qE^069N#8r2}fK zCWVAW1~$w+9)imQa)wG0yB3aIj2PrAkfU(74#5|$5D_)8i`eLFs8pIig*aB4U9SEx~8 z(7hV&w2+zed)ta8SdT4&T6c+Hluus7PQ^ajm>B?zK;*9rs>GLca>|1+bU3v85aVif z@AJ9AylSUyi{_zZtW%2SF^Rb&zz4C;-q$V}K(C9K1#DtM3=w-`Zgy_!t3rG;rcfbA z9%{RuZ8xe#9#MR~;tf*e0UxXrDH*i$o5`MWHw-o%c*hpw#ESL>GQAQlW$XvS7y}TL zoYgK8)No+l@+eL(9ihl)tfUVEd;HFy@t0+4X8TYS8}DssCn~+?<2b$MWsZ}s7tibalqH?5JhS zSXl}V$-?~pUCIbRxdh19gguq=W`0;>Iq-_W0Q~Cwz(a^44f!y)7WXh3(U0ofgQbY4 z@cj%b`$){h_khXpNU%b6VYh%qlPk4eOynW_WsT4TO4JeDzk$bPN`X`-$5w1Xs+P+? z8tUD_tKjK+Jxh?OVqyuo4x?>6Z*pp8q-+wY%&u7IJ20!tSN#^mqiWau(^Wu4sG$;o z4v9xM+~Hz|PE*_GwX-6ZO@U?d^hVQ2rHA?iS@17d8nN{EQ4v2Z$O;Nf`AK!&Q~C|sCPhL2q#>7a%_tn5?{U8cIZQ{{Ey&!9q7umg-Xf(I54~ysi)a*iy~?9b!G-e7ZmgDv-foWWGp5_NXGU*Ya)0?USBlKcD;!}$;9j%q7jUL(*cFhbSG&{gwa`@_w{0yR{NwX`#RcyBzAdM# znf8kf)67=Gwb#e4Y`LxJFFfi!Evw^)r_ML3`A0VNk7Vl1~tC@kN{imGlDW-Qv=da89%V-X* z7w3Cj{Ryp0x8m9NL-mFZH=UQ0>*njyMh}L$p6=(|A)X|gkMqsld-&Fl`{RC`@6Tzd zZ|6~x@&&xrhxy$l+dvN-D@2Aw47Dt-*Y44u9uV+L%iammoS!LM-mdR4U!FPFoAm;cy$}pwTi>t8(cq8v+c3um+0Yl|Do)Rs$F<$J zykWd-$(xQ^Q}Y*O;Bn*_-1GO*vSzre{JVto71Yem_b<(C@2{X6urG`$&-|aPo9!Qv zk9V|e_NDI@L3rWvo@bI2QzOg!R95?UB##fg3)*R~w}A^sZL-dg7My2aTaUx-6&Gvn zt^3R7ayCluA9kOS2TL8VGx4`NU5prS=c5Nr+d9na43E6)(Ya!#7ucMR`;nb1U-#oz z>6_Z8y8+3JZZC?*k`6o`kG6=9PYUCyk?nG~Pl>~k7ptEmBKT#V+N$TiV1L%P&t_=T ze>ol=;sdJFJV(>mKWn-?hFmUBL&H>_340$&axODdo9)gs#XWWJad_R04W=viDZ?{( z?A=FBu7|z;vS0hlgMAx@NpzK`?x$@#J34P|DQuP9ya{gaeSu}c+I3`U`fY7)1#$RI zAE*gECs39Zwb2KS$>MZYMGimCEE;M%!7SVwvj-W_@2;uq@~1xg6}Td7L$RKXS?8AI z(Ys^ZYf?v)=us#QAj6E537#H^L$RFQmAgK-@?9k(&C;o=my*a+iIRr1a8w6eMncDv z^*6Yy+qYF_BO%q1T)v-^kIz@$z-i)Y7Y?`BeV(UmB>A^yk-J{^VVZ4e7)zVt0Ky{E z%L>QL193A3LIcIc7_yWJVkgYnhQ~*Y={#xUr|T#IEYgOZU7BF5BKwBGfpg|2`W5p? z3hNr2`!soStwn}#gFB0Cn0V;tc-bbXB#Ox=V+APvR1R<_Wjs=@0UD$b=Vy#t0B zZ(I zBklqC`AO{@g{Sr`IkQcfdFM8o3od2P@$nNw(t?sl=9R{YjT-sq6eLUv)qL5laZx3W zo$@v#>TpNFqe84ql~IY&r6OEO(&Q8?waE&K>#>?shtk?nWGq{8I4_ez7HL9IH_0V+ zc+-MW#!Zn0Wsq0%4kVD;$}mf1=N>9GnPFow{W58YAfA)!NNY|SQ|Kg^BCJY`nM(`d zdxQ87)q9Bs=obI7?vxiywB|(bE0c%dTGI(=3QlL_&;va~sxd6ED~f{S71j_z6jWLp z*7ueE)wjz7p2jDWEiz=%WGx|7CXsfnw1#|%q0{xjB3Y%v}D!k^*!b)g5fXt;b91_EOLQ*Y^Ti>F%N_-9iTAADNTAB2M z9Y*opf$VhxP)efseDL?ny!raqlL&)Y=ix0lw8{~r5iHUU4CMU^@Ewi_& z#)8L69vUCI))_1ir^nmc)f*0`gHl?4wa zF(Mg-CwPmYdf|=_y`V*|?%(y%s2Fkug~oc)TjkR(2n+#I@-w$7XR4FPHnp|U6r!^R z(<<(If(V==u8Jj43<<4W1%faRlZtxLGK9>vo&;9)__2+QTQMIZw?eO~ zFGo=O#zgSE-bLF z$u8A_Yl?+|2c={BUTE>guh(4Yp;4CDTvs2J@npmbbY6e>ji%rIGu5HD>8n*LLKVdU zNM66donRQ6Ka}X~t=5&Qx4hgBgwMQXP+h7-1R@Q|Z=+FcCh*DP)-rGwGY#2}7M#-m zk+=(B15n(Woo-(gU>&@3-*4^FYb~Vh8U+Kp6#<*Gx2{?Xg&U<7k1r%u7=75ngh8CH zKXDgZ0Y$v^K$*A;UDm&-Vh{4p(BK_9CJK+2j1fJhmfZz*W~dZBfRFb4Bvv&j-ffiv zCN?DznR;*~D9vLLlu?whI6EUA^e{LDKpVcH5ESAp0D(7%@7P!-*_=eogCbSc-(8(x z0caB+oVFAZ6-&>a1!Zw{tdeg_EIcUn2s^a*{!1Q`i$o@RmzE{|@!la7pb<<8C^$z_ zr`e1TGFTL^BnY<(f&o^JV10CccAa!}*M8NzsOf|M@~kgV7a{{(uL`SrsvgaACC&-D z*{>z^t20$SLW~1q7y@0B69Y1-ftntATl%F{U7)uxcUz@-=h`UJqySqW^{^4eARv{H zKx`g5xY?QUicc2N;t5zxDoB8P6kMqyA70omUN7BEw-a3Tp^O|_AU*=rAmBa- z1t0;xS*(KR7OaBF1{EJ=#HYIEi=UgdFE;1Qas^+xbq;1Ms0%K3$L#V2AL<$pIaulBM02$76h74@HjG$cRmW8YiRI)ZpFDg)bJxN& zf9(7eZ}kcc*98o9SK~wJ+q~xmZFskLeA>_4W4AfwI>&yVjgNZU*Ao@aOEa18%Mwuu z+h=#p*Y{mC_k4vaT*=b<^l*>O`w-m#?2L?ZjAYN-b%>)zNp)^U_0(e^@B&ClU$ zLjN|EYjxLOuYmXD=Ie!&1Tq_+f^x#UV5R#7_~I{D+phK_i-HfU!-YTK+up5>=O=Bu z*(=TCNxB~=(A;FvT@TL#>yFhWysO^d>q3kz@3UnsQ{8t3DP(w`<&QnS8qUwp*C*mT zn!e<7ISaD5ad^1rpF2UeKlSUX-L0Q*Gwm)nYfl5by6Z1fXuh0oPZp0KWX)|mT-xu` ziV_@a%0DmqN4%yj);n6-E^himuT6aKM<>_5pYS2g!5p6ESG!p~Rb1}R%c9>KH#v;7 z>64 z=YQRQwjM;wMkskDKmR^{27E!?`ZzwaMIL;&0-8C7 z%^(|UIBR-a21BYdU4go9t*QZ0xBk%Msj0t8Jq9bof%ab=;d?MMpfF zfEmyjy)UWlp@P(nFoVuDOb)>+6S`wQ(Wp@akr@bz`u zuMS|^@%MIdJkM{Y=3|%1%*b^8T{5Skg8Jc;PyE4v`2?jS|JX9=LPqxe&^SR#T6XBz zzMZHtIZ&{gvkdYnT)A4r+7p|)N&Jei<}IcBn2KH@U%{*btHFvI4qgJB@kMQVfTb)n&6zS;M zrer&Zt`p(D47wZow{rG61&B$zaLYGqEiy;_y6y0H_BlYNOVO6)V=rZ;^6ALId(DdR zb_~|G?m)B;zA;cm6CEe_cG@$l8n2>p`okaBMdK>9WfqDf(1+;~%%@EQUOQe-sN;&Uk04jC2_gZoM&l3N_W6b}+`^&B zY8CLL&zqQ7CS}#JL6^y_QyQ?f!;g{gD&+Z;hHr^8+%}ALDi3LXL1ECqBiJsLq+D`- z>Z_$SF1n-}zI2=5YDkt3wdNfMqz4&B)KKzQ?(vSP1y2({*fzkZ=W?t7tPih%!+2)u z<^t=vKKrfZ);~0u@IDLf$A051K0E{1WVN}g13&V)a4ht(y&f5_<>c4OI+`t@Z;Jwnk0L1=n z7IYeZ2ckwP5Bqt<1F@?z{R$N{hM*Uy8hN&r;-G>MH_BK68Cst-fg#YaJ>~oq@ov&J z_Gk4qA)!QY-a8f4MTEwPTDH+7!J&C=lvs)N62Lq#teO?~sj=t|jc_YS_biXVT>EIr zpS?99z*{Cux|M8scrLXck189sq!#?pkt3)z?|pv6wgt)yw-ImrvrzF^pR{x>$mY}kjC%+Ke@~b#X$PAxAC`nPbZ4<<;g7&(w+;xFk4mYz7%pzU#$_8^Xb!f3PEV9LbrCHdQ65$5U@HB!y1|Vb zm!)mwmC%5!c$uZ|XomaVAEfMZC`RH{vSM^D&bC1ucU~9&xWZ$~SMJ`6dI9(;m692# zumq#5RhoMfbq5m7el$iUORx-b5Sfud5wNq>6tGdT@q~vG-f=fA*MRBF>Vly zDP{M;fo_(I@=&`iB!JX4$ia2xJ+oB|O5622I@OrcQamT%(mf1r)!Tlzg!nLq+6HIM zy`t)X#arQ8eYV-67;pkvsiZ5g7TV{3^|9R%o)S%*uyA18zv@v*m#N^w@`)=UC6_#k zm}=1*lR;tbAfn?+@x_1-Q9$f+Xx#EjZ|$jtXRf#fjrEDs%%Q6#+9D^y5XvwU7EXWPDK}1=qVKs%nh;TI`L6iy?VNy zviI)uMK3oA$ImdAT8>ynx_NU3Oh(z;xZA+{I(?-|Baei%10H9}IoinZisws>ww-RB z9AXZajXj<3pFImL-@_od5yw$ce2B%ZC?y1b>U%nO!NHD@#P0(nMTRW}aOgus;L~k1 z$u2q^GBCXx=h3qb+fqw~0t1*1B_BQmO%LxTPvo`;y>Kfj3RdGbYE_>(FNR%Ct{Xtv z%q}OlYT$%+kMMZ7k}pQ)3YXPAN|O^LPjJjI=mrSbl&t9?Z$_5ri`yZ8%tdQE5n-ur}G}o~)Bzfv1O!Zg6rb4xo=& zn^{(vS)DzxC*qgmak8FOnf8CdF;_^PLVLjq^K4s%9V`kqjo{i$j$Ms!y?i!BGr(i& z&Rl(90~Lf4yTC8RvR;0eS@F3Ow|GQv>1vUsj0on28@#I79L%`}`r}}M-!OE0M1*$6 z!!VS_iEm^m8<}Ms%@adwRKYC)S_TFPDQ9r~4VzenfkO&6_@UF07XJQ7%P+0jfBR?g z40}_j>^aJyO;bSSW%*p0JmMHqQM743ktNxIL32%BUED!3Ec(VQ`F^H*ZCQU5V|BV;Xl*h*AB>#~yPq?kH2oc-sh z^cqr|+gVHTcUR$`DOfz?nOZO-^{m1H%J+`1>D|=+yI-V(V#(qF+q48JV?c7xWve=$DDvwtW** zV{`^>`|@1tn#d>1(3G0}$2f?rIrv?{sY0ksNKShi=Yd6Xg{3&R%}(J;VzSiGmF8pm zoE8;uQp(vuWbuaAi3bsgeBV?d!y&3d`@}+_>R=%uX_ga69tKpGcg%4BO%2EeOj5^RTP6V}5o_OkWhxJiFV zAJI)I@tzrvBUl$gBow{-X1i<8U+%V=y$Mjd_A;zl?pvX z%xJ{$K}#HCV1$>=`A-TH1B_tIZPQ34nYVb0py5?43_gh0s{h!~=)r!{$tvjzyWaXY z8P|utQU2t=s@spn8lcoUJ3$rOBt61qqDj&f#AQCfdU+L4cA_^m=_~pK@d`1<%EgiUQ#YsSYcu4l?e_e3g=|`+6 zfzz-+QdPKPhdtT5PmaH8;k{gGW6mapHO{HEMOZ*_X`d~qHG}fTgTAu6`%BE}XD|)8 zX@T1}mDQBcUuQJM7ZZ$aM!qgPYm85mG5jzWb``nAW@E^&}(6+R^?4nX4pl!%t?giC~LY|d#XY# zw(8J3aC{)FGrP%Dk1*>x7+;i4YbI6*nG^{0Z><0JuI=tb7Pg?Whw6CG60l z!EsOwr3Ag_qG=31Np(EDr*8fA+91pW1`auLg0RP&i-UIpUKM00tA2(8m`LguYkYWD zN1ZbppE?!G%g4jNB!FOxtqUIU$!-^Fg%eO!fIsyBxe)1+vOBl}bkj)7K3C!Ang^tZ zJ995yw-f&uobQSrhr4`id{i&r2B$e}KlToss=(9-WzwLnW`=YD)s9A2|-m&R?1du%zZ5zp@6kmmz``RFa%=dyz#p zkZ{ro!0f?P} z7zftf+0N7|S z4Bbn~Fy51Us7}>P`!jcM+}+iUOzq#wsncDfLM7>cZ5F$SdN_LvnBaFKh+)04NT0_} z#i17ga9?!@n=2zqflfbZ^cQ0)W6T%}751M^J!#KfStr+#T+j!iI)}8IWukGsF5vjf z8rya|M3rY1XPK-Fl%UX@G`HtsxCVXAlIW|d1KDKpskHNUZEd=r--(5>#lw)KBWs{# zZoeT4TYdm534-aM!~@+ro1!RJ6^*oMS1`yPM@`&WoV!Ff#(DJ)WnrO+MT)Uo_pxXT zM^Vzixu(}YwZx}m5v(iXTZ`bxGf6B;w96%21y;yxdZBIBUm;EntbEX|1V!2hL2d@W zuCqSiZj`#N;@Um&uxPgxG0(lwU3;=y75_m~oLNwBVM3T)o?#w6KE`NH+}jZfpR_J? zY8Qfe2r{e@;V`8b=lt`d08Gek7oN0~5=PVwA8w+Q&7M|>G})l}dpdG0#@sm^@eolG zEkP%ux^!sI{mFFn!8CuqkJDGu7|qbYTRFKCeoxxaM*ku;;i)L4&CNB)INLTr zwbE|Uf?8tu%SDX4?gWmKB3yhxCV;0ZIFy~npCodyRao zLG}Wfs4pWMk1$E?4wLj%Hc~67tf{uYm%9$J2qv9ea}&c;_UjYgP7iYEQ_7&0Kh+Wd zQWF89k`RPgjr1#elZJ3KmkB>;CD!GLLD^+QdBd8Y<}~nCqVA8dhPa_F9fia7sd1?6 zu0Z_$MSbd~>iUx(*WbA?^>n24b!k4TGq}KFYzZu+q=s3Ahgf~7dBWfIwDI-64A-s` zx!Dtj46s`FP&LW> zM7{15zy4s^>5JF+^o{$cq%9qZt7I&sn05{jNDhzbHdj2$+`{jUfLmWr0mZDpF*0(8 zLP>swugxz>Zkv>#<6C$xs7a7ZQgz+b1ts!ggS2D=6uLz>S|E!)3lYmU5LQ%45#_;C zk_5{-+vx%*~jLyV&}3Cf|6~BvW_eFH2dd zJg0}#fe^!{Z6t57vY!RjLVm^u#bTdC?GLSP*Pog{Zq);OP)smTv)zHlj<_DAsnXIH zeD3Y>jpB~zIb|=sSyWnQFP?W`It60%*XaaNL8=)B4Lg%w1 zEhqSms2ZPdSo$Kk5a0B9k`=|D29-*4^mPI`;zw@Qbiz8tl;7mo!EP&$t;%D)=(7+X zOI1w92Uc0^mdbajSc;R4uiD(kXs%Su=85s=d5^&jy6}@1t;p?pB0-qYH;U_N=RU>> zMB_jZ9ed>!aypwyK66aR@f=OuPMWRXKOhp`a9`QeJzFgJQ{Kl9>`rd@AsX@<)8I|U z895Hpgvq606j2ew{q^LXFxQ{YX!cHQ^tf*bc8?3L`;a*?m zxx<3C-M)Y4Cn0=UUKFvc_^l6mJrmIZ`x?y zJYgnV^qbns_G1K+Qmfb9#`=U`5ny>udUCb|G9;*(ig0k}O`VY8;m=?CzK3(%n zL9y^uKMNOd(N#=9I{;JrFbNb8RZ&7dDWhX#K)Tl)dh z3#e!;`0=XsZ^>&0@9t&+y-QZh$mv zKts&-u9_rMJRKhi*N=5|xjY z3XHe(r#rzUlUAGb(8PlWi*bo!f~J$GUv)A>9zsprhQ0H<1NKq`Qc4!j?kNG_T?~{2 zD4Oik*I;{K)YI32NT2~w9yGxW&s^P~pd)57&5ac-UGOOEf%|^= ziQ1w~;Jc2@2I}h_!ZPY&3|#HO3f|y_&mz<+c)+P;%D+_=vMr_akE%<8i>W?Ll=Q;Y z{T@Qx5PpI{)RxZ)e{luS;fGYT4JwHV^yHgVZ`)Dv;@3N@{`3yIn__4h$^>k!4B{F; zt|?%#c+1d+Tv>TW+weoX|9U*exn?O?>e{aW3{&}$B6Sd^677mhYLvv^^l7&kSuc!9Yy%x1OPAA~rk z^MXgB_vq#>J@pEWVC%1B&*{zx1gIIiwG6**fLx#f@C(>tp*gZ)49AmecuFoQ=k24Fu`uFHtkQA~gJg)Ix=t)UP&W`<*&)H(}LL5>E4Tj$IT1!X#B=$UyI}ufr_s zo!T-i!n#5^+tp@t%hs%Sng6mUnAI6$AE3(1*lV_L$b<~G&%x&g)C$E<~@RCrp1SnB!{J082q3(zzx2}2YW+t$ODFp<1_+d$3GpfwPF5& zeUUkdJkAu#I5F_VpwZ`OKQVaAY3Q{{BnZgiK^KvTfOc#3&T+@um@qY2oVP{xu-3}4 zh%w&>ZK=ti4B#>eBkRU?!LLIrs6tyC<2)#$GQCm`9cF5s|Nc&?fjv7d^c3 z=x;FQUP!xoYn3VuYvCL~vm~A(AixRg2<3JLi&O@<&w??M?x;l)oE^rW&Nq@TKYHYP z)~_yjQ8jRY>tH{wM4(5*)5HSuxDc>CPRB}k8VlHSXYGQ}F1U-9YezM7c1)NX%lzp1 zy$>SFKm~frwVXOVr|gp&FBY)6%4|%$zA2K1Wke!tSj&9-2eU4sAt;?Ojd35(sXs{Q zJ8Nj8DWL|r37yR~Cc2-_}!JeJnN zxt@k~Y(g}EAN-c~N~y}<9__{=D|GRmKRdJ|l_nCx5Co!KI#U*I0%FNx_-mvpJ7<88 zSyoY7C?(N`lA3#*Ff_m@;v~v6Q3z!A8*1Qd32TlHNDKS-8*vBs9Dyn`6Q#Rl-!m6p z2o+I|s3Mk4qRC4%bzMMeV^Yc5r2#iUFUUj-M~OS51DDt->b?@3LQE4;CJH)A5d9*E z)<&5(F098o^sHWSz?ht;1YG;~+VY2-lgR`dFCDP4NZ!y(%a|mRE&O|on!USd8p&%s zBSQdm(-Qu=KhhlTp}`5Vc?~LlWtR~L@4(U-ghwwNX7!UY?=NogjP7ipMbRcO)!!wU61Ej_iRr?qtxAcGh%huHKoKpEFYj9*a0r11dR9GEZy_1ZNb~J z++$J!uJr;o^kI-;k)Fg7cBVBXQe&7z^CUK8!cYx5#OFqN^fM`mU*i}>Lt5N`9fKd_ zSgCtnmFWtxsAs;n#GVXJpb9;wp0)`w2M>S;EZ|R}Wi-qd9yD!WQLP^4gnnG6bSsVV zL>JTP+HVH*2Ji;NC_s~dzxO2Y6ktIBSI(D!jsTA$a0nLr30n{*5|<$h5r6+jqzqHR z8FnY=c0hnAvyD`Ndh$UShj<B}F%h2F2P7f(Z}j&# z2tQJJ@aU|Z2>uZxmZ!OFj4lfoU2J}X2p%{}-Ml;j*Qq=`k@#PcefV<{#9nd*s(JAk zKN0+V(eP%jj#5a!w9)*%kTF+G!TU%q&Mcl7l23{P9j2A3 zmf{Foz^BeyDArd`tWU>~Ut%&5><3x)GT-Vj86v(_--4m<-|26!hJW#18NyCNzSQfP zzz$+Aet;Rm8_l_^?9B3Q#!!cdKi^!uNF3vL$;1gztQ_MWIuPx@TU`migrCrHN8L1q zx)qCx(Vx^Tj1>gV>ocX;nRl*k^hj&sr_SRx#N2f6+atYgMev`}D?C|0Ez8#)tS{lk z3s&B`y{d`s^uSsK-|FNptnWawXK}2bAEDgU`PD88&~a=F7SF!u+t&|RiiBN5dTRj> zeWpqj^%Q81N3Noe)nMTla*UL8=M^xFTI7z{zryp3%=K|~p8Ewuka$PRJ%GktQip~l za#AaeuNW}t5jft-aE2~##C_p+Km+WF9uzyo8x1FUcf0?lCFCe}(n94h;t)Bd_Y2Ql zYH>!3MdCWE&rga5DKIL>ySb8}rP#ehSYX5>K27-_@AK;6U(OAYW}zn+J}C2d%nS3u zBR}y8{87z)v1&a@6MtkqXCT(8UHm9wv?Y2S^4=?dsHf`b?TFq#COQ@L$dMSyO-15Z zhpQZOFCq@WlEkY!08q{SzDxDiLOuKh;Z53N@;)?o+>^3Ku(!|n3?nQ~{Y0)EC+lj| zt8zT`A2Yb#{DZ>yWhry1u3f_qZQ}6GyPPz@H%cMl)k43RuioTyt%LEe?eA=Fq_?CO zr0`ERGg*&!(>VV`asCPM^W;Naz-sHEn>0Et;sfTV+x$wkQ%Dx%pXo`&`{#VV4w`yL zT9sCF7^4n;jd8r;Im2*(UGec{7lw!H^W8On<;}YKZfP6c7OWrgT>dRP`B8W>^G-Zk zzhlQ7Pk~+U-#H5&DynXm`d5SBv9`^|d--PWAZcm#iwq@yLm4~2cU}{@`P@o$GM@Q8 zc00YDinVs@Afap)i4Hv#Cu6Ew!}n%4u=%{J3@we0lg(^yyF(UE-^$luE7tePhe?^n zc)Rto)A3;B)9UU0g2L0p ze8L&|;EA4W)?3lbEaS>{O7u>(?i|6P*YW$7uGY40$I~CrpTD!tjVGmSMLT)hha38@ zbzt|v{FmID_a|7==0o4*o0PNOHZp)ZhPdO>T4>?p?HtT5ix0kLsXaS__NuB5DpEf8qoQ7Omtv=Y`M+!|9zZ+((8)&` zyUFag*;Qz=)%%SMPRFZhaLaeTbCjP|kQ#gIUTaXX%TL^3+Ro4~%Cy>)I)`5l>ofqo-lH<^2xOb~hg zOO0ZVPM`F7-mmvZ`wO}1efUwEsQV1rRSA|&$A72IVH~~}&Fg%!@kPe6rR(JHCOb!W zeE{m6FNeq6yS-xzM91shyy|p(a*v;ERgP+3YNE@?p2U~A;ZSH9ps;E4Q17|aGy~45 z758IvJlj|6Lhlux`}18f97`E$cEI+n=6pR{XztWAW*de3_%v|=%81xBBagZ1da&@^ z@-}_V$JgTHcXU0o5sYkKi|w#Rf42XCv%|=W8SSS_{V=U2-{pIk4jwrl{EgRHT)!c7 z9#4*Ydc@7g^=Yz=-mBcr@4gau=5?9O2|GizvYy$yI|G(2mo7FU26r2u)#o+ST=_om zC!73XNC=N;lps3Wl%Io9mnUeiQ^K~UTkJ^nt%fmE`$8!G& zccAHG6qXvF!!7>)W32FF-_G<>cirwH{Xtp14CX;=eX!3`&2ID0IN+C-IJiEGoZscx z8h`c6(#Ncgas1Y73?{cj=98!ANH}o!hYg$Cj1A6?rSinL{7Q8)8+zT>Yv}cJXf`?< z8K>@pKK%;z@2%rA{T-YPUANxj1?dOe zxJGz5?|f*qpG=o^)wfH1;rDz_7rW;bLgSlnzr)WJY4QzV|9ZRLhQmFUi@*b6?(Lsi zkNwo6h`RBr_Xs|d%r8!z(R-%)J_C6?g%cYO2@jrNE65%2(T|X^E^XMoyBW9m zN7HWLZ9=EZgEq)`^KaV01KbC)->k4r;A4{OEaClZF^heL>701lR^dFFL(G#mVh*`e zG~?-!Dv>z++B%Y=GV{f@4F_LHWKHiEIIOurNAE%?~0H3 zyLd~)D7q6rS!ZwS4VY+*`}t$ojYa`+Pw|;C2`7hG^_2SkQ#-v(+Pdw@=a| z<%K-ty``oT{{41MUi=2JpOlg}GdwC$rA?lI2$&tdI;n%$wTI?hh#6_B323{I_H3dI! zH32^{4ZpR7*SEEugPNk9o8d?GXCiSY9A&?~*ttL8e^TfyZhD_0zZ81&FNIF<-vA~v zOB+*tR~Jhg=l{jfe=o%UrvbY6!9B80uD0hjr|lWp=`;fUz+)DR61994+pFc~Hf< zLbwW8%nP@_+@=KSKkB|0znd4n*^dX`bnJEB{nGMM`KRy?x93c+tRkBh6wy!*VO-@H z3nA%>`R#%OrGAMsNf4z!F=t^N z=kHVu+vfp z?j~9PPVX-2$V^nu>!?LmaAp^-!?I=r^$^vJJ#eJU2qQfU8pqrpqIspa>auNaR zrKP_y7(0h2VslR>jOV)O=@mF5p8f^Oy$~o9E3sFTGD0PWro&`4#LI!U1)yZu(-8=D z21Jj;KgeO3nvxx#!4Gu;1Xf_6Yb<0427y3Yb0)WmKqkmgfLMjO~yB znpV4GLUj`iAS1y^4|o+s3QT_`RE*#_C!T|gX1@TrtR&NMEr7Zm6Wz8N00Om$!kM6x z!o59lHV2i+a}|8HVJ+#)DO<}TP>-h)?S&_-Lj81!r~+M{lGdb0$9cll$=zs(N-wR) zp3@np6_3*zaa$zra!D(tQ()1C?zIRryC|?VWe^~mUjFDf7aCeS!tekEU=Q*=QnKK8 z5_EI>HfktnBCG}M$42WXtp$83C{)YaQ{z1ESYqj_%+PSY-RP6BMI4R*O5iAqaC)R%T4K;?Oz*V zo&$nxNl|MO&3LoPbXkbiYHwkoiW^)XDR$VqE}jeWtO3@$2vE0CHk z)?+`ZOU13x$u5q4_-BLd0l7;W@dd;>M}J$q%R0v`(7iq|CERqC`cF_|F#K>{D2w3^ zB7LP3d6kNRgS#CLrDJvGX;;i0r4qg=A|3h#ycb)fcJ03Y)Qf8GFpfxv5nl!a8k{=7 z3lY7(fE%zg+Kk)rJBC#&l$po-<+JN>+%-moqC+SL1Qj9$jT3-a8%Hfnq}%f)0_n2f zU6(LZ^t0!u&uuFqAl|IYF^yra-9x6qsU1trGAhG10J50OsfNL-R|*P>xF5}-0uFE& z?6)OQ0NpHVY*}BEhx(Q8WDN@mR@4Ab(WzIQ4rX)~IDIq21|UBMkB9xj(5@Q*FNK%} zzAU5Weoxya{qOQW8wj*7hXnRrVhF%!&m7(okDJdG7m*gYAZXSRs!G3HQr@j3%S{<*$t&loj zpES&n9G*+4j}AZ|jc@~F2sW{x%~ozZmR7O-)&tKCxGSrJR-vgdOE>GiVexm2X0*Da88+{L71sVw0Mv!4ZBj5^#MS1n^+NhVd-w)>vJVTFitWw9%=SKD z0(_a=wZ4~9+wlu7#+OIu7dSr$x99kdMt1P)kK3MfydFFAI?lzmgV{gc*W9W9I`!J0 zCXw%MW^D8CdHqCw#%9mGjLG>fE7~t@kKLYix!In&YRvQe{*ZY-O`iB?{48pd`#)Z( z&#LEp7G38?I`_Q}qvJj6c^+i5D~U!JrYEFOcO^=B`&Yyw*v7X(z z@wmEACzJ10;!djD@VMjm|2{lc9+Uf8cIEQEUOsYrHZiWhs)Ensxc?jctf#__bV;3T z=W~$$q9fboxBWh!d{gHi>;3fM{BixZDg1Xz|K@t*>v?zkMD6b~=Sq0m??`_pWALm=mLSJpS*N1&}nUTE$z2EzMRNbae!vSqn=9kB;?dYhV z`Ki1zxZSt)e(?=#%n!5E_27=b54XbG4!!?z!%Eq#{#P5drTg{QRGMs|xSay`BgVCG z&Lg?VISwEs(ys#5U&?!*lyB7?x1lYln$(LK@p56+JPYjEnv#K6IIkvMVjJ^xlzGj&{O)-%4nz{LT@?`i|r1bces&n4#0&!`vfD zYnj&5SE+y-<>}qW&%XS07!Ly@7VZXXomX;+oK^Yq`q51)ue#7H2vELp3?1e%B*pV& z>*E>W%4N) zl{!;Oi^6Kk-OAj`y}tg-6|Y3Z$YVqxC<%yd0TeYZ1eHNBfIx^qLq|3`egTgV6$^p2 zE1rrmkH4RLr7|bko9R6JcFnoN`*JL2E?`WoWkolnIsDkHn1!l|nzmAsxho5D0a+9rtnoXbo!x z5Atz!o7#lT5;*@+Ot_)^4?`-_u79pn^!NY7GsAqoc1E5@V5PZ}P z#5s-?p)Ks)rS9flXy>zEw-l-yKkaI++$E1wQ^CXSc0YVXY<*i=IG~G+4j(EcOWafD z{`2K_uTqkOdtdAQm|bSFP-6eH<~zC@XAkYVK?HoTc1OEIUN4>2xvSCFUH$!r-*-;c z?hb{`AL+XFX5;jn71Ve)aMufdK#dJ~f=K!^bUL%%8@%Hhr}yUM_*30|b9@7PlvZ3v zqs86*wX{L%YF}dpUw-oJ>*!pv59AmA6vFqOk)_S}wEAG@esX*~Ws5AIQTk#RFO$V3 z{j#_nn=m~kHqv1GVuQViTd~6P)VBm*Fk;} z@A?!$Zr%8jcxyvWy;0bJ`5kGFOZtvU2cV3Clny2Au_duq-eWeIGORNJF6riY9#* zG)}gVVYIRTTpw3!8!LD+^l-(@&B&kzuaQx$DPgihDu1Z$h#O1s`k-pLx?l*&9RXtP`a& zOs!cEBhy2slNq71u1*9^rBaS`Fq(RPhGJ9B<+%aMC@Gn0q~nCrrfRoezShs1EyrFc z7r?qvV^QvpKfr%JrQc0#Bb&* zl5d^D+GfGYI?yaj?`73)T4qYpc@|}?KCM-4<}s3Wl)@_QQQpBuQ6;zPpgRV8A+}cc z)FNPjSr)bg9@S%I*UB|BAZk$1MLSj%*|kf2OX5WU=4xZBZ-SZk#=6@gJ(y4EuH zS7zLWNOjEC0&gj{@>pB&>$Sox(l@|oMOlV_RB%}4sAXQaL6kG0Bl zlgH4!?{CFF-g`%A9tNGs);w&8(1HNzqOThOJ@CE;R4Ey0H3>;1qM!iF2%3r5pA3N{ zAr;Xek%cC{4&hH3?hc-YK>S14Uy>10I-cZj#s;bPWw*4{Ws!82J;EkjDPQXC2P8>D z9hYc+QaRN$4_P`T$$`8c`RkMd6?)~b1=BY& zPb3l!HQC;#;IEkoWFC-a5&=$9Lq=b57wew`Eb!I_hKY!o(UtbxNR+3YAvbh+GxP0*gO5y{rb zc84fiDWB;E+0eR`)jyLf=>-8ma&=88Yl&GJImeLY9>HzkoTajGJTD3UB-*iL`q=?| z0RRxd5CK57j}G5Ep#YZsO9AizOuTPW5rGXHjAAJWfbHZC?<5zUR>YL)JOO+(4Q}fpN)uq4Sk<;~WZd!J8clQw0N9x{vmSOuI2Wp&6(rV!KQzXAKb|Ky`PFdgtN=bEw^1#T~50`0WVkC zfNO8KM~7>_u%DOv3r^ElPC-+TeD3cn`5lEy@M!T<*j;UI)D#zHol!zNzj3;vfz4fB z9sPP-MmDC})S7>WyTZK#IU4izwUp82xwZ{a+WLKPL(`A-GPU;&Xxo@}GRJ*_qnA*xMTZXSj1!hjl~=-BAO>A1w}@+RS6NN5SkvwN!!D%wncs=5 zTh&S9n^2_VUrJ#m8eI5HDJG@t%0N}o%%|}eT_xBRg|4_vIjmN@TCY(lvwIJBk-0P| zODUW--UmA9SpU~%!j(iB9rx~zq!`* zfip#!7GC3_Q%#lS(TLDq%M2AmSvyro(v;asjS`int|C3AdAeYIEoROfSBI>|T;)Z< zpKnXQ_zbHZP)6^!>2YW-w(>p|{QTAyJ{D&I~c#xzC6U5zq>${Ox4T9ox* z`t(53QS)^nMKG1P0;x1CK}OR{DzJz5Xx*bgWh7|?!91O>-?}o3ag0#Ii1zT~H3%pW zAz}7-DQ05^m2$mROL)36tg>`Trn3B4W>m#cP&vFZRfen-ZBbCQsgl{sP{-WR8Hv`1 zlwqGqWmzE_l(fbRMM7js#T3?e&h7ZGQobpLWi;c1d*yW!7;Bvym$a9fbfumnKk29H zW2;7c0^ByDZFESnq5ZZDrd6$qLX7=mOn^DbUz_EJb@uo1UE=UH8^Hz|Rp-nvyalEL zcRXo)1=c3U&r&5Jh1L>5fSx&Yyf#tX4WkU8D5Ki`7-*F@b|@zFVR7i%&I%;jtpL!N z{g?tHl6{Inql82^gTpaFM0a9sc!VDGy?~yS5)ri`1|ugFIJ!|u!mv!&Rs4R6SY0AH zDY7_XJCXk8%Svv7d5*=nxUlt+3J%I4Cb25g$X_YCw|W7)p*lbxb#0ny=V|4`JBe3` zSW^9X|98wSgPBNU2fiay+NtjD9{MSxC%aaF8+)7!el*_0a^K9<> z;(*5~?c7BjlY<~sjY<*AjXOG@fdGi{fsaf>bq6!*RiH?En3E_F9YE8h8cN*F{^du2 zB8a?x_7-e~G0kX1*b8j0xngLrKxjx1pi2!GdQ22?MD^tu5hK6=4nQhk;|o_33po}r z$*-CqP%ytn!UW|^S;~T_aSqspLR>I_7bo>g_$X&GkoOSGZITG2l4e|#{dJ7XHC6lk0&%k6>~uwy!K1?W1%wywR|x(!?~pVx$QF+G#S z3HWsxad46~V!RvKA3p#$F;EL=6Qm&m5_9S4n7gtO-wNIam>8sR^6ybOJQ4aq6%q1x zMJPkq8yw2?_2smHT*H2j0o{>kPVDwDhz7(}8Xfko}V+J3!%xY(WPg z1~a>PLpzl*wR5`^kdQa>l|QYXiVz7<}dy3o@9sm?&5-2N1=) z?*l-Vn=Sdr4z0jF_~z2n2jUJXtuul5#>555DBzH3;*3DJQ}(t1Db=HpgFkwA8nB!c z05xw+9Kfms2#PdGo*)H5ks#4$0L*r9BDo|Ynfr|*3!cc8Sn5(tNZ`15A1ETTQKhqs9|oP-WY=ayUnFys};k2i0S^HtVrJ zx381kqVIk%X*2LQ8#^D|SFSou{GZunpW$x&O-B=J0}b<>_%waK!(FA;J9LI)6}#S5 zjl%P}_&e;aC(l##_MGqO%Nx(V9-l@#)9m~|#~{id1FdD-YuW8b{r#o1;MY5DQl6Jh z>0C3kSgUV3C&kHeE-rt64i*;pIOku!1DUb&^FMls>3;((yTj?y=@z;#G{5%G)ma^j z?(nRP7lrTjg!E1gub;)0^%FM#&QBe^S@q9MDeYDn|NGGyCU#iwtLRk)IzLDAN8Tv! z*%bBmBW?6&wW&8HQ@5p@v+>suHk_NRGd=ryFflwU@2x-+-{VNwEIx03kIxFz*ZEs! z*-4(YWyb@@!oMcge_w7ETLZxI>L1_V=cT_VJ8`^ee6H=!`Gp}3cd(WddD=yp={rBm zk&tDTem`Hr+{gL8CA6MFT2CimZQ(C88gB!~yzXsw8J|)&@!ucg!6$ege)3!M-tc|y z7m)9l;XOWw;rRNN9$)86Jwv0R__OBxZ*SV};_&*Z9mF4Qd0m~ZuUEvf(QccM3zX@z zttIp>cF|iqPw~`equ|`a=sM)Hn<6 zh4FkmcQ?8APk6k&_q8jnKjA$Mx9lFAWoqY^|2`NiedwsY_1A7lMJK#9HcqM|1lTQM61_&R{ifj4V?J)T1b<<4yU5|M|jn3RnDVmoS zFR^6{UiEQ%uKc&JdYVrj6*lffXYIDT+r_5_*MT1B@1XyTxc+`~r*~v^o_g%P}h}O9sjW^^}>vAeNahCpUTu&5x26-}6)> z0V~<4#EqQ1o0(A~NA?qE9LcYjM=q3jtI}&R3&@V|(@<<W> zAn>*R&1x{e-=MhvZF01CanLt(aQNTpG4!{J3;6}L@8KnhJ4c7(1BQ?!P};^XWcOEs zOUPf~W1Q?!_zBTB1;uRsfg>}+=j{GR@SJOMI3-f5c&if5HMLRJMSBGn4YKmP0j(14 zuGqQ)%=)YuuI35~nJOZ(p~kA^g;!r_*($5F%=@MH#&`R}Z~G(f25i|LMp*?G`Qk=< z!~D!M%Be?EQ6-0cF~x|)wJJj;N|deUB$`t;SF<&URI|}3>ue_TIo7g-x-rVE@_&+% z-a=}O?W~YYs1-)1qH0`oF;z;6od%unaGU(R6XuAL?MZhYrDdh2?z7BAsEwV+XuTCF zD{qZObrVmq#i=N*xz?W^FbL8XqM8b+!qEy6%QKL8!U<~T%0F6GrCM`KK_iDynz?H= zRwI}q(n!;(3)D@WiRv`&^9q1GHCbk*Ru)o*t4r&XNOQhsfPpHjmK}($eU@ioTAi-iACADqTlxbF#TCyfl$-bzS zDwEhTk+XDE*+?O`m?iuEY|X;T!9z_5(&D~`;Xr{O8K4RhJoWE)@1|cswrtcII>3Ve zoH$`p!igxUjKF4C22%Zu3aADAEdfn$(<~~*qA^4nAsVFUhf48*c`7n)@h}#z%ufT& z#7a;g%7Um~6*9&_a76J0%A+8@bA42Jkz8~}l4KN#WF&co4AUPr0=U(XJmm{ZLne_N zA@y1*QNCcjD+oC9>w|s*Pl!N9*(ZTq7?BLeNFrh+85?j6B8efuF=x0p^ap698?KAw zIQqrzG&S=opB2t6-vgAdM2L1rh-!c<_ekV*P25KRmPkdP?`1Sa$( zOUBfR--lNaeAka$y-DAdS_sxc7(x)XfjK?oY2|#o6VXqt0}cX13Ds7Ye9@mad*lT! zbnkYR^lrP%7utG)B%Fcr1X}k4L2}UNtVy$wwLR&bW)nT@R;ll|wKo5LZ0h-;O}{w^ z38Vt)OFK6?2stgTA*hqj>C_NFeo+ttTVm8CN3pPjt6>2&)S5v+>4{35U5$jsXS@uk>`J5{zP<^73*QkMLX1CQ{@_z+6A3YLf*THQe}u zp&^BfP@XbbK%sWXv+IR)TVb&Uc_qu{qzB!TCGliFv<=65>v{z^7yg!D-?_X2O!UB* z$;giy`y3?0F@&JL8z!S!?;eIEKB}kzP%FF@NU;ZlLja)v6?_}eRfiP=ivydnI*O1y zP&w+AOWW+B2YL?T+1VuqRQTbuCufO|&$ z#az3Gyz9eFSD8uzhl9<8VuDVvLaRW`qJ6)_&Q~6}q}Yj4yOp-AMKvo!U$z33QJAOz zKU_#2ry*F_RKma)xMRT6=j4cxcw$n)4|%Pv8`eh}JEXrVf_0mRUQu8b-5X}jfVP7h zb_)>3%`nyQ1E@8;6ukhkqnmC)hDN|9)CMRX9&n5dLJV#wj=m9D3YrTS85K)z4|C$K zA4kw|!oXC?OP-;?C-v0?8(@UEKHu*`gvTT4)DJOEYUkw~O9s$g0PHOOWCEH425XFgjXD|a){@*7$Jjq9B#VFZqBUK__3QlhFj zT)+f8dl4b;7(Z7{INUgY6a9@A_{`~2Y{j(s-Gw=LoGU^U4HopJv z^Xu|eo0`te#_ju^`HpYp?LdWlb7Vig7=3rc{X&18T%29LyNkcbEFKbGT&%B$^|7Ay zykzN5F8Pa{&%N1f-bO>(`A8{h`t#G*UBeB0^D?e4p{D1d;8}8d^SnQ=`N%?NPEtF}?icVJXj->N>$&Q? zn|i5(^SSY{6t?$0^0c2`U;C|LcJvg_(wR5F? zoB8wJbI^1$y|B&4b5V9j&!d_f4E}vP8aJT(l(_QwHfOFry10EYR-S$_dj+GL<7zaP z)1W>E{=(*u(e-vdf;(&PcP{ms=cDrz-r;*R<~$=>&gzx*_@HR+w=c)#CO9+P#%i>7HK}k2yV*+# zk>a+yiT<;>(4@8VJh_ms$p3Sd$9p#*gtokH>mT;o`tsPWkTU@GqszgIx!t={UKhSdF8}G zx9pmEa;zq4JbHNVJ zIg^S#G@pU9X1vlX&niRV7g8C~N^2dBgl&9ue||lQa(OH-75cCIR;p>giQVX&l(;5N zA#qMkm-=WA)g#r2lF+x^go5yu2x=`K?4@O5wv+Ak?;3D8hpASH=gz?bmA zu-ydENE`o}phHMb28uBTi3I>2{6bnCwAZ&=o0|nJDQPK>P}l#>-TO6f)^=UjbzPV0 zUevn!Z?<^#`5uoeD%H4(om@7(@Oj@nz{0G{uWqZ%{A@yFwk%{Astg3Pn9HUZ=k|n|!$(d8DUS{plme+(7*>m=dMN2Y zR@X+&lJ51j>w|M)_6ev#ry|YL*Hf;oU5#4B*^(A0*98MDcV1Q3notAU-`EvPL;q=5 zDd`QTgWIHZ-8Kb#Dww3{(tQs4qx0BHl~RQqicU~d>D2=&)TypdU*B^TJA3T`-2X?{ zIR$qXwp%{7la6hpW81dvj&0kvZ9D1kAKSKVb~HItb1~nUGk3dc*S_0T?|Pr-x7Nzb zeYCqm-78n$^5&&2B<~E=s62Ec^DDu(xUA}gJtl8LZ5dZ-J(N4Xe-~OXWvC1>=uRX9 zRh7s+^hl{vL-5geI_BdGY<{FXoKCIS? zlNBhAp-YummxNNKvA$UG@lainh#Zwl$9U!|OtK5<@c?zTQnj(2K@dq5y%~|#$8Y6S zfzz~rG(aNN23}v+9FKzsQcnGHIv{JS*#^32es^nobkZ;0yUiVpRQgB5hq`uoqrFB03Rf+GmL3WEI0zKp$U+ zrUUF5APB-J#8TH&w{2h6=#0KW%Tk-u)gBxKVT$wAL&h=0hL zi+a+>$BvQW6uiT^|5vP5tOtpUTkGFX3NRKXq&fMi0Km}~mnLcA<3 z^zX7F*G+GAZ+ZfWlw5{y8M}2?+7?{)21Z|@Se>+ZHHPRmlcNK4ek%IJuWY}}GzrQI zj|{LZRgn3?f=CvO_)Fe9Ao?iJu+gms4%s1~4lM6m}-w zlFbs>8k;e26vLu`5@<&GPBUJf?M@v8o$)+mZNA|e6G|2-mYGJX(L49|A4(rmySs?T{EkO&0nf%~GxlI+T zef_T<$5mIB7TtG(qw*`v!iSJvX56`C215-o7uqdX>n9$78Yb>{S3$i*n5hWJVFfuXyBSanChCOm-s6*&YE;QDH^SI%y6e~P)`$&&BDbhctjcuY>B7?~jTC?Vr)nXs|9 z11t=>p}M%>%|hZF+hXZGP*yLeu~kAPb8~BHBK(TRYY}6Z^I~m|*-)J*XAYNDUya{)R*|LJQ$2x@lz?Z2 zTOS%g#{FkKAUJ37s-Goroox~W5Uwm*j+~3E#8WPGf#)jh2w~l$4Mueptj;kev+nWR zklg}64DW^ZJ}F-`1k%vVYq1W#B}XnmKE({9op~7L%w3aP##>cyx}h3^W$#UP>!cNC zifV6UKPIQ(A2__H*Q2x*Yz9}(=OeHr6IBPs^CtD$n#rI)Bd@;d$e3a9Gn`bGMlwQa z$ah7{Mhi(Hh9NIRQcmy7dKM+nFQ~`-2cu1dr~#zmlslBzx~dF^kW>kp%3$H8K@nOs z*yk++S}eC>4>E-rOKvC+13SVi7n(4jmKbH;CkhLtxm1AVjO2{jPrO|*k~?RhhjRf} zzHi;QWFQ~d%{{h)WN`GimKq;>WfBtR%)er_fZu>kN~%c6l0B{pMw96GFb3n4dDu*G zwPBcXuDedto+BFWA98=?WA6%SblOHjBvo#B`>|oSuUa-BMOR+5|GVc zls>dm33no;clWq~Y6+mw5(Aal5ik~L6a1T5c8aTJeOTiDbc(C! z>ll=0L4B^p;WgcVXEu%Q`A}BP95!}8VK$bT%oIQTZx8(~iy>vK7S$jf;wh&*>Elzt zf{PyqiHnAaYMn#IppHjXV!kkC%jfys?8gTiLAP(|f?s2aSeD<88R0 zDXyY9zFed1R5koyL1jbRB0M;`f$Vh1?TsN4^Q(=7fF2 z6+TA}gfj9F_}g1=JtmUYmZkl2MLz}P=WR{2U1ZHh18yt_KzzWh3E2jZGOJ3gsISP&d4JfXSNvHRkg)Q3`@evuJNmAx};(O;Uf z^$oz+iYhx}1X%>(YTHlE7&zZ-=FNcsjuI$%tBT7H8(4o37Kb&3mMO5Wiq9U+tt zj$X2(o^k9XtY+_WcU~%F#)aO07=^@?zKWiGJxbtLlN1>8JRoqF#Qps2QHkE-*feyc zO$?zZ{g7lNS>pL7+jx#nxxq!2h;x$ktLXx=0L5INN5EyUOwvivf|RUacM0Bj5&}vHfH*hQw~S+W4*O>0d7R7?zu5S zR|dh3k68DS_%0C4XXzMzo7)4IOd-1PKcl{ebuy4b0(MQQdr8)@GW$0TXGG&J2|uuU zv#NRrW%lMZYZu}~f#&ky;N*dEv?iFsJ>n=;<-fJAt7^TK969 zO?Xc_DVRDpJUSoVMICtDo%nrrX*<dGkaYHXCQD;?g=S9P(Y>+C1 z4xbSSBC0{Za)+KiLE{k&Dj|Ar>R*BH_kg_1AGiVVzG(y3kM|c_FkKTDTV?e@Na>-| zUCJN%GC`0}3UDL|5KL0R^O$4auJQM4f(d9h(7NL{s;j(`zVV4mFze4={c#`8T*EY$ zJg`??1)n`=c`57WAK57>K^(gsXJ?qQg%%5NXp5&d{oF}-B57fKZ6FH11^p0=g z2c=Q)zO)~gI1@K;0^x`0%U@U@*8g`MbeQDS)oV+f>OM};n_-aPXF>-@{0c_w4z=i$ z&zG<-FPVfG;k6h47Xx9g+rHe2_VB(gy~a;0dm!*$~~K7o}7SDHbo zoSr?dlUKH+FvL-@YoCKQasDaKsgP0{k3f;^sTn$S zu5-6VnVH(}5aoFvbgFW^7j{QGN=p!gMWi2V89T%nkUBrEpdwm_M*}BmZg*?YkjTH_!uEgjKl1GTd=NZ185XqboJmxN3la;w?^MNq1=Oh zNFPb8P&pD$gQtjT4eb<)tV4M+8NFl*u{;=^l$cluZki!q4vC2 zbUVT6)|0h^e8hZzG_hsSqhnP-ugtxEyk>7;Zjk*=3L3vRtrW8g2)#wj6iv-QSki|& z>4z264*Z_F@`nrN9(2?{kEEd;!nAE9`cDY@?$Y|5S<|>ee6wD17=Etrx>jmm-Z4Mf z$Q?qAo#1D${LdORKkb1%s9k#z0#IDn&!Q}XwWqk-S$|t#Z1LEzVeJ~JICrnR)PZfe zdv?iByMi}8k$X3)FRx$kb9Ll{p`&z7!@g$pX~!n?Cnz#qz6v@j0R5ix8gmhs;nncC0h%6 zca{OO4@Le4vrzDqh4yhzzO(iRGsucLh)y1mP9F6os;4?Zv75{BDP*Y6HU{56DRK3Y zGSZ5>u*|vN-3mWxe8}RSUIeLSqTYijdGtmqxnf3;EXLQ$F_Ly7hx~eK2%w{R$V&WS z4{=0Le%D8NfVY`A(b_D(*zze=KcB4+hI3EDIsIv+r35jXWIoSaUe%%AZ~nQh$mP)G z*y9()lHCWUHN>JRVdXgmk=OQOx5BeV`9@e$#3zjLFXHB*#1oa~5{t-7h9u{C53Q)L z1+p0|i>ja3UD_rqqrSK{bXo%U)k!-}UbD;6`1fRU`zxWTF6^zh#aiBLv{!3FJHaJ# zg)h?ofV4VAXAGijWc|i+o3tPE7WNN15>JSmqsS1S7!r6EdsXHc#JdVbPY}k0A8AZ` z&${%7NjDnRYDK?O=pMbLWnk!@SynPdHmKgonyIiL_v<8b(DJNEbKGm~<~)Qa__j$s z%Ke<8a<#Sgous2a+oZW8dzihj+qvFPNX_M!M^ocKMXl*e#Ozzr3JD80d-`ta4) z`0a)IjW~K2rj#e|VL(&kkv9W)A}Gn$@1}dWdF@q>)lYutc}C>k;VfJAhqqbndh2@O zyL%+_PWOwcK7|x-Fe`7@a)!q zc=Pky*IlbS=bUhcNkO|_lP zRd8sKPPAY~lcpy+RM+(W|AMWoc$eFb+2&%#i{$# zTcBpscLVM9bFuCVR}_M8>OB7X6@%}V_X@K=Q@E>-2H}gm{^qRz_gPm@b8lwhC5zMr z!MxK+^eX~o|8h9TWaK&c$HzAxw%CT4K+XTTPMmlg@ie?v^2F632kZ5#EOv(HYQxtl zzpJw3PL*n6xkYiCVx@CEev+|zvdu5;6LhaH&Z%(QF=oe)A#aHUL!U2RUe+SnXMtBY zqf^w+X}=sqdFStU>SoY#`?JJ$rFTT#4o1+>x=RfVS=#~VGwqa*=WF#)vCFpI{cq1# zeEKx?vd**=VkKtRGnmz60=ELEiCCJUiiSjkww|iOp~A`l^VEQ+na=e@v$UitKJuv^ zY#(f#$*G(%r#cjR4(X;AYT+n8_vjRESrOCr)(kc`(Fs|urIV*MkKQGvxNF_ah|MOq zbzVc(jA_O68oUZcS!cvtlU za`r1#BA+pVqSy+Uvi9RkqsMq+A11WrCW;UfVAdkE!p&qXv7gPEL=} zv#P0Wt%sEok{hg9RkU_SzX5ct(Wv3m}cg~+1GfxbbN3puqX4|_fkndF^ur_)}wbtlfBRdG*e7nb`eF=?=NW z%fGOxXLc37dRP6YB#l~9-czX+nlq_3st(rDHsO{-Rx%2!Qp*e4Kbrczt%t5U z$-d>AH01aMyK9FDIo_r~C%RlLJkLG+KUT!Of=rX&F`MEAvuqQiQXrD-QqW9Jx$OLp z_AZSPuqa!T)4_+Ri3vDZN?*d6ZIC2Gfx!jCzj$;mp)gK_Kr(( zZLWn?g@;H?SFl4l-Q1Yo7|7v#jhDJ{KUv4yP7AGe^n8pCEh1#~t@X22m;ct}5x7@W z!)A%IfxqV2Oz7>iyuYyUWmrv{H!{p|Q3g&7Rr`BllsJOF`UK|Cn z3U0eNHRIxRHB+x=W%3R*G@?odYQ1;e@tP6v=kavpjTYzXAEH+2_sCsX=j7New<{Oi zkEs6hZ8HpeFfAT`+_PJ$l1nhB*_!IYT3W9}Z5hKSBS?07z8Ym)wQudCv~oID6P6d6 zOaydTykDuxYdE-yf+_mD%}jVjkkbzqEwnE3blH`Q;$n+$Zs`NL`40L>esgIY4S0af^;*+af!WNRZ9_vK>mvP>@Z zQKi!appN?SsRI*L>w_2>(1XPSvJpi9+3+I$(Obem%&uZ+xsV(DAhmgL^@hVg74y6f zK(Nz8&(NX0(5-Bs+u+(HK+7472L^SBIwzO{0a!TvU|#ih4Cz1FcB=<4?tPbVBlUj? z?1MgIky5XKG2C-``+zlez)uX;U}6m~;(@HvWr3X1V}O*?dqG|Myv{&8f$&#hPW!r9 z{Ot_3;h5RffAJZ=+kzz<_<#m4r99ugt@;@f!T_jcDhNfmB1hi-^(l&dNNL8g)23Tb zd^d_!XwY6EWB<9C1n#ApKDX+HTV@!E*==ga#aD}m6+*+J92W)k2)wnY#tvc~bp-z) zL?0J3!DK;)tlHJE7Kt4y2!js96dc6{rU*x#+-D3-pEsbTgwnKfTU}csir@bTBEeDo z%}kakGiJ6NAc8WMZSlE7?qT;X958JwcWo^~5aRfQvv4>pE2q9&V zZ&>hd!Nd0)_>J?Q?9r=OZ+trBPZ1z25YW$J>;EkRw0HdB#Eo3c%$)vnw`HT|XN{wd z=Xc^3%g|cq^UNu`z;Z?An&=vF;N(4)Xrs$ushdP^Q}hU_QsOlI%-J#61MB8`x-3=2 zMHJauIQvSW{e^aRIfiSB zx_r2}sw!$J=I@xCWyqT6pOT8B`KivomcN#igk_03;Y}@D&5{GZweho9oig7ils#Qx zZo;o_aY_@GHg@5wNwo!tCs(iH!+mhovm6bvwQbddwK{j zIJTibC{*T~zXtwEn-^s+J8_{K**tRlu#}yWbEqF`=G1O{Q)Savpt$nnuEI{q)|;xI zNKKtSL$6?qY+M@djQpu+QJa-o(xH7?RE^-8Vl05<(`bcrTyK6#(kh8wGAGiqbgX9A z8Wp>a8QGt6cc!BNrVzy`l(V$bXol5_K*|d8AT0mK3EPf`WDriXO8$GFD}j=uxHvw) z80tA(;u6{Gfc@bIdQ=##c6fLq^7@pty>}!rU|-rK`VtvH zWoHgU0oaZ5zYxRd{S`-3K}Lk6sPxcB>oqEcu_HF-Aqc%EFiPTBJYFAf?@(jr-~xp^ zh0yu2eBSEtI}jjs9*SfDN;ImE#exVDP=1M?PstT!47Wo4kOhuOXd%3D!Uza4gzJD} zDaO&-;p?*l_$I&^VtT>p9)JXSL;hG6V}}f*gAHW{bU$48WY~AGo`=JU`;YAtOK2DX z<4dY;w>fYtz+61j_Br)*TxANQY#DL8=kpt9m2Lf%&Jvx;gxm7%ga8+1i1YCIAW)@4 zfela-hWG|xy#ZgLsG=K@g-ncKxySQ6soy)o7!kq-ARu0luOhMr#x@3aHU>h+2P_Jq z0$3pL5U;+MX%hZ5Y@{H-Ab4$15LE%k&~s<`3_KZxUZh07BpWIel>CEHNYB$euwY*e z#<)bj4a6dw9$!cVp!|Vp`@qW~-SJ`vPCh8^{9R(np^$LBK=YCNI5J}permgQjO|=l zs(4y9fc7$1!;8u;Tsgc!4K~d0+p|}%VvpGb(g5PXysVrDc@4T_s43kvKY8J@C(JZ~Z&=f|Q7mTzcr zd-&fx&u6w89hQgP z2%P9>bJO5Tz^go_Y8@y6+)cD@^{$)9h)#;A$ zb-V8rOuXwEh|O^`z}`Hy@pi4w@jc`0Y*$6>&`|LET--||ew?>aaPNFCOq*YOniwul z&O5m;p4DAu3lk9Tt#%`nTm|1SQp(JAUW%5twwNB%-GR)9-S%s#4IdyiAD3gUe}&#R=)J< zim=0Hq2%K3)37%;>(ZmEUhq+uGf$AUM@# z!UQp-32*NKNQ$}^!o;4*%xC}UQRT+}^MqWu6A_9l49X;bN?hGQ zy-6svPl`f#6s34a1@-w1I;Xn}5H^l&x8)M#D7v@t$Ry;Ae7^?lR1 z0lLy=865iCH3EP5znfA&xav`WV1R%G5P^U&|EJ^K!TA4B#jrnAG1hn9Ox#2QEJhv? z5u#(v)B*>eF~w?dX4*E{#-MM1yT1UXoS~VyUfq2U43SAR^JB3Y>9G>}YZl#lS(KO_b4_VNP7|8BrVqWT^pSH7;j;b0 zT6S)E`kKy~*&*t1Gplwwy_Wf-&KOgg6q>YIM+1b+FW7}OxU4zZE=4MTA8jppwcqe9 z3pz{ykTu7(M$OhBMP>Hj#@q#EaskvgqjfzmmgH!oipW1e$`Qg^wT0{|FqS@C+OrGG zDoi!7o&`Eg&h-@~PtADA)SO$TIAU}47?GH_jhPEtt6Gf~=|WoUY2I|gOQN}2(&jP0 z!^Xks>a-Ejo4_G^jv6I>q5IFqtbsF18*SC3pyA4jGnjFQBV~e8<eBTyvgXE@f=pRWKyWVo zG4ubJs?ltkLDp#zHQ@?Z1#$R9MSl6)bt>Ib^@zbA(;A|quJU`0`RC$Y-+s24_wG-W z@aV(&EA@>8Sq=jFzx$~CmAz&8CdUbieMR(b2KZ3ngmIu6Zo=NsC9h_D1iAv&G3WL# zQ?WV}(Nvc!wMbdTG{R(5XsmvHS2m(BvS}iTiZyVQnTXYZMyl?j0fk)r^;$bjoKD7B zLhGmcom52Y)VI%KQmRr$t*#oAc|PZ)>OJxjv6d6$?5-(n*C&PtODzu!wV(lnibxB#ZPHYYI4@!9* z8rJku@EdRscw*Kg42?$5lvpquhZgWN?tVb{?6@s>VweUod_$o&vQ~+&q=k**o17t8 zua<>jdx~Dck`4HSoT5=Xy7xhsNJg|qgFV?#O_)K0OSEab>TX9CqmDL7Kc5P(b3j{O&sK3=uFu-iklMtH_zRbAb4%m3(swwc54Nmc;fgr4>j+EJ$z^`igf0hK)r9#1b zt!9S?q6XN~Z|l&yz!bO3P{Kx8i#)=yt6abF?;TB|UwUIIRA4E&;q9~(ZaXIBd4pbdpr$L;sT3iDmqVTE_;BlL)r;Csy&l$zAN z)*=;pn9HRAm0g{|fJ@wJz7@pd_q`5y45Hx(4M7bdxj>h=brB*2^A*rz&-Cnobq+HV zS~hR!SKzks9iXAs)IqJ(?8y!wcTh?=|H4YHy$wK(LAzjb-x$)q0HqTRmMvy5fN&EZ zIkZ3?P`xQ3V)3gQbtSM5b0wNwyMGbNpvXTaGz1gY0j{wfW3w_-Uv*$c8I8HMu_Yra zBp4YzwyE6|+)oC4~sqvthPBION;mf&5DKo#WMX#XZC*9&ygyGvFU(+$$YgoP$`o{-9kE z&$#M1A_%s0l&8JRA`y8n=jtkPmHI{C9QuSO9EZ(jZhy6feu@hxuY%|->U(Wll5znh8d<+~yh z9{0y=4(yEF-~8}iuB95bX@Yu|Y+HQ}0xN~-OnvpFweGIxgl@!5kJ8yS{*{K4ZOEAq z?vI?=Jaa@5J)aW-{eP#iVLNZT&tY~x*IjLOUY{{EyuI!>o44z|UUk;kkEQhd^?T>n zh--9C3|}Tb;g$`2j};`BpMb2Q^N^S8<1ez!zw{kuEc0h}9pAf`R@3YVx*y|b4GSmo z!**vI_SQdi_G`na@kdf`$BX0b#|7!>>tm|bn!WSxDZ>VR<}`)84wt98XEgr?wgp2@ zq^`Duq1(nC41*4yw^m#$zc*9L#`f1|@9j>y`w!rC6a8(c{LXt|zHQFXb0>3oYDIB! zv)=apx-sKR)H(C~Tp&)t&VRS>B#x3BkkNLW2hY=a^SE|hTGw+w7Zj)Cy+F+W{kP6> z@1=^ock}(R?TNW=yw~e;K%0Qz=8Rqca}S>2rDuM*>pKp^@3udWE4EwzpQgvkmQ}3x z!(E8g%eVK{gBQhS(soZuZ}}B@9jU8bxnL>+Fc<}V zw+Z<>4tX4^IIpt;p6lAI^^u(GQpXS1>go;T1X>>&5iYk^G|E!TSr&NrTK+`gv1l^c zRpBF{rxL=%EOGhmWN33e7?gb~;U`{SzpnsB#18#-;;WW`+x)B;aq zsbreBzh;}EKwb?-19+yX8h4pNDCFGIxNAbx>H%5knw?=&_4cW9sJ<3<-XqV0xv#II zw3H7C(k$hGsuZNsj8a%ThVoWgh!<4+&Mw7&X}%1phngEH%cL zy7*&A!~9W=1pkw2bTqa8zhXH})qSitmXRr*D3cTi|4Am}oisI{d5;q~Q~F8B{n3go z=7>NgkzA-1J)cLhp(zy!DZZa`5e+5PUADDcF2$UM7gi&LZ7tAPX4MWr7fq$r3~d!) zV|h2e@OQQFZ}}|JRD;{7H|-MV9g#BMojlm;-ub-VzEhmMUR?t)#&G0d;p04Cq5#0; znBbTfPt%h^nfKC{MV4r^Y|Bsov4<2k)gRwaeKeoR9wMR#R}`A2#w3niHANjkEHp&l zI}_rcL~K#ZEvL$>(*gE)%KkuK<# zAL6?rW1_3zb{YNBow2F6_G;DIlR)6JOWUeE0>50#XR_qhi{9T1>R-sZ&0Z*1*&N-3 z@6Ra5+>W-dxA=7*s;nWS^J|1hp}Fc>mlP$i*8+Z}5(ry)=nP4d-DWK*v}Lw2281hmGzwYd@~DO`mQwmLB41 z3GgUy25W!G2%{G3U=$>ba^>VO)dr^lOU7$5!t!92`YbME3FVJ>qd;gs3L)!KX!(;h zCy~Cm)#kh%wNc|Dr#g}I=LCw;5c5khU3zlyOU~aKGNOS4$;McN6Al;-br365wLeg^ zTQQ{}cAfc(6XFi@f|K@)a(JRKmIJ@)Jjw#8yIsQXsb89$J64sPrf9Q@)AqpO`3|aV zM8QYqY2KmLk)3=Pzn{xs9f(dCR%8s1W_D?UH&3G=?q=OPP|Z*GdTDi_@v&*@1#AD#9ZYBLUqRX zezU=C5V^URis75AWRb^0$>~1WPWNP8w3I0fAYo==P1JRRX`-z0;OaK@rMh0*qiepYC>i9npO3)1+B4OShXvstHK&u~qaOeptefg%jf~brdaL1+wdMKej z$sD|8(u}mPP}78~N1GghesLsJ0LfD@GlN2RZxaEX^BXH6oyt=bVlNWf5OmlHAZzeL zEqt?jU&%S)7wNyA)bzCiX;57{A@%{qG#_Ly!p=CwsQ?o$WBnkj}sRJF|7bi_{u_r3vEMMpH|#JTyY76`c~k7QoDsn^vHgqlps!?OjNC)ALH2G zIyzJ##C$ofvs5$6$tP0jriQ9-Nf=F zsMF;dHFHL}*A?gi9d%oA^qQSFe4N+%mwGVK2phfRQ3S>kpHvtTZV>g@iY44L^OE z&`f$GC4JaFW>H|FQG438+r0OPOets~7pa+_Bzm}+PD4@!nr1-{QN}KgB55BExDXsQ zP@Nu!VTrM3#cU*XLilYLyth#;KIckHYZRhr6kxL{WCjx5I6DBN-Om9A)pWH1PYYD-~G)gb-FhXwjEIf<%`h;e|QI62!8UX>diU&6Trdg0N;$-Z^Ala@)1C z4n$MdIGk4xV?h&RL3N}l>-<8HC9@5qIBZ$e=wC%nwXBkNVVT9}p)Y86;w$9&Z3O-( zF0qfXuc%*+H)jMAl_>Pc>jJ* zzJ{7|$hgn@jvV2QjV~RZx5Xv%pR~`JjodyQ1W7CStC*x6&6e*s^<0n74Y%h!ug8|& zYVU_z*w4J~uH(xY`J0c%*7dKkTz=o83o8Qm1PXCG9xvas8GMhUg{||H*etso--E}c zjrW>5_OE`_Xd=e@9#twq?HUSICz zrN&zV8edPktvR>Hw~4IWovx#2b#^x!$89d3e?`+2ez(~e?$-(OGcNi#55aMKAD6FI z%pVh8JHA|B)3b3qpYHLWA$OD9Av-*eJH{B={Qt~v0e0KV_K|IQ_qNZV@Gv6nqTurH@d5Bb|%d{;61?vGYRh7weGi3>tql;ec`1CtJA^SbSZEr zjL;VKRruZzlfSb$HEB^{wX;oMjX3R8Va)JYNWeh+4 zi7S1sm6v<8&?=HXs+5pWcy(SwjfP(|DMf&QE(L6XHJc>}75F&$tZ7%WFzAt;bQ*-ku5|(K_{^H= z8*H<}m(W_aEEFuQ*NugB)MIlO--(#r_NLp;eey z96)D^#*s1q^%rNR9BJWZI&NBufEIm5&a?S^g2f*_7G$V*;fPlYlW2mZg|EfjWd0mU zG1KnWX37)1RB!vIKXd@w> z55o!_3H73J{8>Ms#Lt&nOOg zixS*5;+}=O2HuJSETrh?g#@ouhz8SCQeYJ=e<=mXRk`dYW zd_+vNjqleL9exARd?F)&LP>Oa&5Z&lUsM;>oPSmQLJwtbDE`w%7{5 zk4Ugzf+zoy=~9Upi6fIL!EK@dj>n=M^dQme-WavLj2Lt=5{Djy*SaEIN?a$oxU4E-;xMl4QkRXreDEIws5*_}b$U zm7^vEh&(wFyyI$~gi#2nhcwXRX|A;{kyDg8yApx)ao{+g`PLnx$3NaJK<*xK4acI6 z6Z0Eo2369X01s|TibsP82eUD-`XLX4_1{60rfw$e#>;xvHkNg4SbyCDfzHM|6p<{v zn4%00N?O|w=5f}xV@yiB$s@kiX?kN!b{61JDm}*N^MZswFB4Gw5xeVt~5l7_VJz|i8!9ol$K@>_R4;c>;1_>u@VvF>eMh;n7 zV%DHqNs7CcDi@nYBBiK|%q)GjvnFYHDH@c2vKu21wt7OGHw&@b zf*cagp;|8bvZVUSAEgS@++9>iO9DsnLzhM#q~HSwbF;O5Go7=0oICWe7hy>jX#^I> zG*6r@4$&|@kfW0&FR?DF1{G|*R4>u=YcM(8wh3>A;O$KKi}WJUp#F|Jf;_D^7bJLA zu+fG*{Q{YYSQ8LE$0Nllp-N@$j{=9ANcqJC2S0EeR^!ci7pt%+i6X~^z%cPNFvisr z=w4=)MzE}>I%}yk-iPqeKWh|mL>&c5w1pt$Nk}?MX`;=L?RdXb3<_3=F0)tjc?P#X;7x8%{)CCPtGeHj;+>L_iRT&0 ztO#=KujvX!n0J3In5qrLHE2+_okR>?i#ZNuUc{(h66gYh98mU00;3IS)_zsPRxx49 zY!O(q0c4Vx6$l$ey%g1*FbEId@DEvH+Pdtnn#Xg9ZFB)Sb1F;&rb7?N!9qXW<*2Iv z){sFj>OGa_8`)sJBMD&2yn{?M&|d*YrzELOz&*9&*7`5F1VEem6uHMNlf6kNM!*K& z3IFbt5s{(r#Qn(bkVzackP20c zhg5*w`c(h+Tc{~(18BpvFhD5>N+BIuMGDSaXLt7ksu7ZMdT9#fBKlZ@msY5rHN>2=ufX)~X9UUKpC0CXiMn zlhsRBl)KW&fsm$hydf}drd%juIIC@2vcV|;2)W42w3!mu#@kRCR1#witQG`y42p)w z>xdV0y(uPIU{Ma{3|EfE2SJr=H;%H!P)n6|Cqae=Nn{lpK9q(K?16=EXj2GFsuKE&W{auYIoj`t%E(y3~rst0gZw%l)- zXdS`k5E-*y?+3!SDy2HLJ!DC+J0Nm<e!MWI>J)n z+`zho3It%c-r)m&c{5CS1SeGJWo=lk9?N<>@LU@z0ZoM-{qU7s;73L`iXIjP0CM^9 zi77X)OWWWQ1b5KwCG!iPh?Jc`%MeUT9{Mca>2@g#Ms6g!r|4kx@B|}%(3x)L*pAKl z9?g?dIA7ly!(e@LsN2uoQ?XQS?PQ+l9Q5;m(#3_9*b`2?=Q$t4LkQ>bYUb7GH17Tk zgm~Ok>i`fqVfGNm*XKaon}Utd2&U|u5o6>zw|gH0E2r9;DFx7unka4mUuh;CeD3VR zwj^rnYD>4c6P5lvLCo$ZIMwuJy>&F$0gPvSAO;J`A?tXHo5G?yn69c%i~?!ZP$c$> zM@=zSt62>8F(R8;3=S~{N$J%eo^#PzT8pw(M4(0OBoj3lltCCcaiT}}LUR@wccmjT zK7U+gD%nYBSMr`9VwLK1$T zn?DN1T~G>90xgwY{~W>JQZ zb#5<-Q$&1`PQy>&u?z5Mk$JuG{awm^O9tO>d{aGwe|%J2zhS(3*1GW71P0Qn+%AGt zY|Ul^@FTM!XSI=LvXh-!2LU2wk8Pi*UtW6vv<_4P1+zdXewt_gq|s~ z(OTFH6{`gzk>pTs1GrFbL<_E255V#dRCL-X+6{!SU>TBb+!N`=$&sG_s-ur;<*oY5 zGC^y@$=A1~zYnlT*u(uZ>iRSgumb~hz((Om#?ei(a)#FtW=U5FL$3$@u-gf*vlBr0 z+W*9o%}mmqY>n3bix#5~tKA>W!8kp+J%tt{a7@ylYI8acU}@0G{l?@389umxeEyr+ zHcbLluQ?muCdox+d%sa{s^Z@+Tr;pXkQyYTvc~ByPA# zf+;>z639tUn?m&yRu4RP?=$rh`)4&Wg!i?$U2ZtjH3?d43r zk3235(dwiJP_(0bu00PoeBP+z%-8cnuA!hf!d$0T!_2 z$tbdc$s_KRgBkjqmrL>O3MV3eD>;s`xI2eme`IwcaM7BWj=My0T-HmeyE8f0T`gxc zPF^d%lk_5x(zn(>Lt7}a|GBlkC9#*0cLVyPw>(UJ>w#5b>IN*RdA_(7>~V7mR+CzFxL_ zJHK1vSK$4!G@k|BdT+edeP1%~@?U(&-z*;8ig$c}F77yz3HV(vgL=KX-$KOL=5mke zdd8b$QjV(~2-ZFg$KZ0uMtWZN-W3QwK66jf?uNE<4O&&3z8C1u-Z|WHbGG>z9s56e zepl>o_pf}w1-yH-KQD|99FO(X#~s`w9DC_o^}fYn)Hw5dUR`+5>AResQRw)8Z>G5o z`|y^(fV>DCe*97S`7FQL+)>gs>n?J$-6cqe!+6eZ<9wC9YP?tGL z%y9Adcy4^b^?sa(D*5*mRiQSIqcxdH(g18%lDDDB2sg@SnRi`l|oY|2{Rl z>Gqzzz5LL*;gXd9vikUZ?ai~h=={{&xe@l@XS$vD0c5j$U0nO}--Mv4IPIW3*uT$g zFwl3u9rR1|w4a5YGvu9W8ooN8_mt{))E$+xC3H-~gY|3&n0XO_1S^*DRuC>@?LSE zKi!(4V`%%Ci^DN7BUtoL#o~9q&U(q-F4OBi+}*%gCFA38?S4F{RR4M&>5cCCL})SS zvg>xY%y16x_ybu z*LdFCpYR|+)?0)3wiMqQu#{fUzehVkX^lC1}L9XrR zbxT3ppz>Dt)2!+}3Bo;Z+kK>Qj6&A_8D@aM9$T70tNUhusBi^d|Fu6XLA?@bgC9u zt2Ry}*t5BL^+Ru+_xW~d9djm5zB-<)`dNu!CVwakK2HOt2|e)ZkMIIU=YrLx-qS}R zCrciN`KT#%4gVmR`PIww&($)`Gk<$SrL)BTe)@&`wd>0eFno*J(#r21{-nX5HTk_z z{>j_(pS=&2OikK?OQEn}QF6R^N|ac6c=3JC0daCnKT`Z?^S{m+nICMXIendu8BA^L zPfsteettN-D6&$O)kF3+Hg4}am+lfVF5*!em-x>wc9(b=hSVd(R^`p*#be7Es)xhR zEyML_&(AGr(|uLTDeOIXLGT`6#D z`cv0eelY&`rx4naoe$eT*?}q~5D?LSPYIpetenjZ++FPd=VQoC9m)|!4bwlnRjLI_ zhGR|8in1X&Sj;k;{iqDO(aN&*$l5LjLpY1P_KbbZeszE;akdg$v5hw2{CnyUwrM|-g9je9TWco|*OTb6K4i7tnTJsYbkRl5Wbx31y zRt`vD1_;ouF0^$#r4bn2tQc4rfijtos(kSfX#=qyGMjR$wrK+z*@BihDm!ru<%YH? z8QL7UgpLv`l0PI>1*Hu2f&n;Hmh~d`q6Qze44pCVaFRJkIr|?{66MWudR4YmXq;j? z=-_C2EvGfhO1)&3YPRsKCr@>Q%s;phnA1IZHBnMa*j4~Ex=sU)<@xZc1xvJ4Q6#kb zul7{Rr;|oks&H*ls3FS*1i z5Ldc*PT?Mh4dLYY;}Io)20nhWdbu^Ti0CtOvQ@Le%jW5WjqQo`Fyt*lsW5IZr|;u= z36|ZwbcbC>=Il>L55nL9t*^1tsrfG`so5Cn*f7G*uJT-bzqW(=)dY9T17W?kY%zRP z@~n&*!-h}|&>+Cn`{V>~Ax(j&{t8lX%;K&IgwR!$k0YY`us~=Vl0n@mUj?ITL_Fl3 zB^HFM9@PFuZ#DTPp~w}&8E+w_f7)L>Z8(D8<$mN!&we=WZ33p;=XYwZ8H^o%2A7N5 zr?5FH62y29bmCHv=~@Sia0{OC&4Be?uT#4hz}do&jHrr#6W*A~Dip^EetT)p6nclb z0xT3#!ipWyL!QRI&MJ-`zQy^2i4IFKOb8pvgh_%E!nlKx&c*-BqzvoC5SAsM?X@+- zm*n^xMTdHWrGqzo$@d2GAv5XtBO^j6>&dnx+^kO%oJ9<7g$S^BYx>b~o~ z?FOC~8B*z{jrw?p=LzNU`%TRzdu7Ml;l3|MV!E44wYs;>-u`7hx#-Um?Fr6%frWwl zef$T4|J~@>>F=(0w&_cSgkKfaH0;~r{OW8*ccyM8;D;y{BUODU6))(uW-Blh{g!voZf;-)&bE_*5 zOZ*Q{!4AjqJ5Ihkjqy+0*YVp6ozS&4J^>Lr?L!Hr(4=ELN{wdpl=3`=m?o}2qNYmtum zk1|eT7-!O^HV>)_N~pjCoaRScw2guG3EpHSjDgfL4kq>91;3Z`>=f*~bRT_nbjrl7 z``e?i_^ocQoV8i#8BO}|L0OeiwV*1!snL|sv43S(*)S9TZIumd|E z6b`4Mr=h;VEI?D|@cw4xXJYjFJ9t0p=4@w@E7h`;o~rf=YKL`mj0ON&ZYY!zGaJS% zkD^+%wi#~ZLaP@S7d;&@=ln#^{dKs1C3tQZ`LZVZq9dRR5uA-jXpS`$ z@yQKkjY&s%QnV8LL#=G;gGZ4F8M^!i)ESk62>YmL#rGV4HRV5PZO%Sh0g+mvoS^G0 zgzJ5zSPnt$RJN$UK^_zkDB^P{{szHq0zm(F&F)Efo9cQoKc0%q?Zy_oepreV@9dZQ z1tb8Yn^V{pQ8ppY3Hxx+d!F()a2E2;miEYh^pWV;{--DXfOY5%7#fRyKaZd@Pr=1+ ze-G-=%Rcz^w9E_t=}$$XVWLH-b|fX}Xd0(To2P%Yu6- zcOO$pu&>iWXO|=>$GEIm24I{E?5MiX4wYpe z1TQ}Qx;(tD24~=UI=q)`d|`MN&KDq2#LYSV`wSQQxMrU-z=n)5ZL6fjRbU}iluiyi z{Qedevtw>4&h$;v#`5^`e$LipxoUkx? zE31lkmBhN8JP-4kdaC4GbKjpp@&^n>uzQfOD)w>((~d(qC`yn*(TwR&YxUk#3ovP{ z5me6fGmOK`9|a8#{8*QaiT(VU;Zhd72G4UJ+qbTitC#l>iZA=tjSKgyxwGA)>{b?s z*M0?`q5HGD|;k!*#ZS^XcVTBS<0Jet1K z_BU9=m`9#9FKU8O;}93>!+-Dy=6A^_K_0_;*3{L&k;65E7lrNpXOG-c4gYq>HU=oL z)O)1gz`h+)Ob47T>{RDkczZG^7utld5h@0o4+ZlSb5CE*>b!^-YX_KI?V<9>*C`oN z5!d~!C8?3(Qbmw+3?odE<3n=rgcLYJ-lzgVTL~XV=SB$R^Q!}(0>j&qC?A}Foc|+g)>D(QMUlYFd$MiFmSdOglF#|Wi@r>!R-Ws2DF{-S z#pxugNNA7T7Nq5~GCh2u=i!mtS5DdetDHgYD0~rDTRsiCxj+a1b5&jL^&7YvZ1=nAEc{g_Rf z+rm?5i4Ch>sj;X2iFmv33niWMXC`&3g&GBe0u5m1r8tWALyamW4pkyw zE8R+%Qkkfq8*yn3DfTl(Db^+LnpU;!BCSRrl#+mO50$O?f?xo}Rv?EY!QL|!ste9% zLD$WSm_`esO3EUQ5XR6Sgbt3Kz+)<&ZYh$!Iwl$Ucu^#Fzi$dPjD}%2543dTCU7CGbsf} zMA=!zAR|%K2^$_6$pdHR5Gx>Zl8heygw~}mjuqk7;Hlp!8}}poTM0agsun;^Gno-A>H7|rZ)^- zBCMJ9bkgCC58F1>_7x~sNW$_rRK*R|Yv6^K3PaAkH#ZqmS0`2>NvkD7g_RQbLKj}Q z^Jk=a7J*8RVY}&sc`TIU$a;J+G1eQjjD))}fPWjm1(g>Puy#}-4**V}hmxyEZY_S)6 zrZSCY{4!(E{;A*b(GtCi5I^R#LpxnNbK~FE(J?-@bHyN@@VV`J(5uJye454=AR9m9 z|8=#yHCpZN`0!ENLo1)pF`_K{@|&P2Jzih$?L%AE8@>Dc?CKzaVP|HC=e055<1`FH zp@)lQi>sHngT2wy{B82SvAx&YLC@>4CW&Te^`oJ9+b`6kBDYun>m_a`pq-2Nbzpbw z2f)1pUQUs6!6F}aoB?O?JM;;zOL0L^lOk8f;M61bqesT)IbJMjC=!0R!2$vXCun3v zCBJJq;C24P!1sBk+y3Zsetxh?kF0(#a7LN`gw=1Mo)6lQ8!==hhW9fr{8+yr)V=g5 zCA|$^v&eQG)o7A?SGLo**;NDrk4}f9qQverCUMXe53*flfhDCl>>Y1&@*%SanN$f)x4-(J~|BaT@FBp>70>O>4T+N*S=Otat1?7rzlF5>l#0q#C7$upsVrdz)GEJZhqTA@ZfdPXZUJZ5> z2Nc)S>Ht!)pd2BG0zrQvpNE^uNTn!J%1JHo#+PK<-#7eeS80$L>ySxqVIU$JDS@(O zmui%1?c5Q--YA5*x$>Ft$$ui)@%t8*QEy38QSJYFl5?XuSB~P3Hcn7NO~b7n%@ra` zO#Vo&HbS~8Lvoq4*v6!3F%v9#LCZx8Sg;YOzic_NrLmS!XB!WP|BT+% zRbis8RgDs|le+j>v4M4&v18vz*KIk|T1Aq}64ot z#vQ`cLW7;+(vd1^5Ljk&nUWT1dzP&7`x;16eabM?9r!)$&`C5!iK8++8JS2_7HZU5 zGKyRU_^%w>MJFz?ISo>j_zgu`GxHdiH>BF($^bfP)IUgMlZOhl>sC<}z+&-#OoWC&d>5k45wex% znHe0?M9xxqt0uZqhkvlV(G<+SuRvev1(Bw3?xV%D2l=HL zqb!l>u+S&sj>W`$t^H^w4~-aaacY}bmtt1h9H z#5=Wh55%7vMQGj>V4vgp`W(evsh5McvS=DQh2VAOn(O~Us*3cCivkV3M)7JSjl1OJ5Bp2rO8 zF&}L_jA9+p5h9Izkus!lH!-L*9NsO0_sd>vsp^ZW=ABv#q$Vi4mrjhUmrYHSD?>`O z+ZW_Ro+k7G%TkQJx03Gz2mS!hK(7xuzFaq_Y|JdMi#KO~TR&DcXF<|b0A5ZRCKDnO zF#oHqnmDQodg^p1sEeJ=yD~JyV2EJy5V$ZdQ4g#JqzYkgS~m~{hCuWCt*;h;V-g%dC6PHeC z0aFGE(jXGr${xFAa^96akm@oLPhS-X8_m>4HL)0J0+M~%e5A)CQXM-VWOQ1Ryjrp+ z^G#*7=FSW&*cvcBr%PmhY5pB-&aDLghi>{6IF1I!FouyvfHR5H{#sd4KAT2mN@>0G!zvQ-OVxqwtfk zdzkK)SHamrso7AnTYav8wEgBX9o}H{=QYyHKct*NdxJ#L$L2PwE`f&ph?8u0mKalM zeeS@vCuZClRG5&Yq|mzfsZuBh2jq!xMyU*e$?nGVS_An}vd4y#=Sgf$%{JorS3vdj ziHb1D(h_XC8FO{A3ydZ*nZwDT_JY9OOAU|}tzFRVEY*_QdL?W^q}tvW5_+yaaIp zmi9fNynjHw$c0sBK_+NbrCeaDxFVzq;DlYp}gZSfR3uvlSf7#q> zf5Rb3kZ6h#pMu>na<`pRt5)jhfG#h`HoHSDv1-1-&nIsDc!-EE?QUZ0Cr54pg!4f$ zSG;Hc%Gw;GpGQG*ypItht^z&N*9dFY8JEjD`t*gZ5+Q!{!Ip6+WtV9*rMMARW8)bwQz4)QT2RCSpl8GD($19b+FCaf3Gi;j&mgLTj=LP2xb| zQ7*B{|Jqq&x(WozxD=z4r3=v}H%6jMN=!Hg#Bw!=)zJA#l^E3^;+4ag{lKJusp*h53$#R_w2xX=t9)ssK!Umut`B*b=bGOz3khM~lTdZR zX2D~1`irj+O>ks#s09!11E&`VV@`;Awcy7>p0Igqy1F`8LLGFKB05rCQAi%^XGZo~t&Td$(Pwn!%>UXFhKOna?ZW zbO@6<&QNc**=tLj16)KlE~5z80lBD)m~3l?B@5>kTt63vYF8Hiin<_&0<*GL>6XT; z(0DsQmd35%60MEYhMVXF(A&83i75?!x%S z5#td@HOm5LGmD9An0*$0P~>p2B~rGcx8?JOXAXr*0Y_{&aHp^j1k6U_`FHVz&A{&; zyo9hs-+q~b$ljPI!Nx{|F&~TCaUKXctKgc+B!b{ux|%3}K8w6qj!;?l5oNRuf=XD) zUmHa|fD4s=u=(MN611Jp)A9MFWI$3)BFg-xCH~=%$?YgcqN8P9@mm-LQdUK&_1fk1 z5uvceHzeALLKu}vn#*T<*rQ{>BH-;d>Ln4Yo82_3J!VHt>nl0_5_Fq5*Q3fIVZ7gE6c|8IbryZJexK)>J zr##k}n$2`iPX_N`OL+d)457{`8t!x)l=fHv7n~NHup#o!L3r(sORQ%J?b_o1>Uz$Q zm>EjNt4kLKOQFO_m#S|GNqq)QbqX!SDRsbG5_V!fyJ_wW^5yz(Ar?1!Q#NY@I)aGv zvR2rG5ps2z38Yd0)I3F>&E}Cw#uTNUHYM}e!*f=hd94K)21|2OE3Ad(Hnh?N^J*_! z%)^3x%N=1|{ImzuRZp>k#P?bUPu%GiDGW&oKRA=8jy6VCV6p^hj&JZ&v(eHTo9g9 zWLxI+0*P`ac4o!6ZP4+USNLf_d=* zP=*aXfkXo5P+9eM^R-_=^q zm5qlrGu%4`2M)hhQ^Q%{Bk-{_Leru z5JI^x=zenF#lZJ&9lX}G-=qRn@7x*?A=42{E981DL>IClWM^##E7>_Cj}M?7zU#hL zP>3YiZ1Qnu%FXV2#^i1kGpdk(@QJY^U1nE||efA1}!O_>r|O43`uZ-bU_ z)S=y;H^SjxClYY4`CsOGq~OMuz2%<*c!x?_hM(u&7x+9o&)?y1GSlrIBUkN}r!iU8J31eY zL1Qx$4y~8IotEQ6F)pQ5mwAs@v=NDo*Mbigb|Q@ipOyp6_OD0g1_vp%zP7r1KVlmw zLpVDd+duuk)neki41CJ{|1J$t__!!ucV*()Kc;nvEW;!)^ezT?4Iq?~ z8o-QZ*Iu1uRGRTS z3zDBHM}S7ilSG}W-07&K@u+un-@U1H=7`%t_I<4mYffi3cw34}xONoSXx^`1kUAjA zS6{jAc$X!7f2w8o(dhXxx_%$X&kt}YdAb-d+N%Tw1fb=wd0p+``<|Je*o?u=fXU5F zGO_u4y*7^KvD|#Q45$R;e7io0v@Z})J9u4ZZ}}8@CG^a*SqSaf+@$e-?cq%LDCG3K zms<6n@x4Pn2%q03?hpZvq(MAv?DU>i`TJE{tFW_q_3r#Q0{1+W2M@z|e6;_j zfBzz}J_^G>7|Im*9#-q=cvf}Z96I4=3l&(Sb-(LhP{7eq$HV%0wb^0tdn+h950azk zef#X(52)eeydtpp9w7KRd(13XQSf87F%fEy_VMTM`hMbY_!?NV5QxktczL`UFqnCr zs)YF}ZS_b;tD)q-3&AelD?Rb&H`?sUnl1+$$2p_(OAr;{^ew;WDGL}sqJTx1#QDL( z+xQ&x)5*iezJBT`)z4^J?K(-?`u-rN-RAK)VW{$Wu&bq~G=wooo@x5(c})K)_T_cj zQm{z*tDOfabTunay>QcbLDOm8c){AqPU6jX19CdQ!UJUL=SLwZY48GCRl?jDwdh|t zo$|Pl#tqM9Ge48ECHU_xll$T6Q)NZc7z^T(CAvy)4a~Z}fT8~S)t@R^`d6tq-S}wQGACzt zPJbLXF2eY%4!303Kjp%Fmmar-_Ng+R-U0SCy~gsqcvwU7$ll67j6?C--DkX;L~=7L z{JC!)Kt|(JK9*!DT}4U9kftjo=B_s7A%@;odCYJNE|H}1uH$B^qSIcg@BCufkzxQ4 zeA6F4rK+ZVN@QV2yGrSlV=SHwfW!^~6GEkhUuP3p6xD_r>t6h9V2jf1P*9)14k{OhHu z1tHw?c;)Nkrgz8x$3%mILC;MGRLm))y8mApqavq}@<1%Q5TJurPEqK3!t6wboU+=M z3P_El_KN(?Do`yscAb+YX&#g8OuCGhw$L5rsimvjxfB^pkQqr_ymYN8Of{!vr1^V_ zdj35$XTlhlDmkrB@5;m*MT!bfO3Jl2M-oF)nmIWhXUm?OdW4i5q2d|q2$)>PRa?y& zIal&!+%-RS5);K)sJ7}4afR4eac7aAvMZs@aM_rsf)H^`D+BSK(KR5J*y7&)2ory_ ztRW|dFf0hqbRWCO>CU)mwR80suQL|5!@pNYb_QIODJ`~=afvB$r8x|7W^8J_7IA&X zFgp&_a8pBegJ(J|OO2*-5-`-k2{XBrlIalYSrXb3YKph>-Cy3FKe!TvVp%rq6{O76 zMQ4ecG6}SmTNz^P4k3(j&H!}|Gtz}{(e)M09D^3BG}%88lbnU8;Tc>qd5M;=CII#d zY0?VxK|*Du$(U)LG|~5B zgBl2o@fOMe2o;@(&0%#$qB+}>c(PVGCE1AcU@hvyVqB?L&CcJA)3d2!zTi?sO-Us+ zb#^Ashm2IL!ajxU2*ek5L<`G%vROq`HP2tFfbk!=pjSa@xT-n*DV*12Scl_sK@@9y zVPBfUHuXVnG59EX`NO}&(V1TAqISj^C?=zm55)H-VQ893aKt1Swg-vTQy=Ug;R^%NY&_g@^eQh8=Oh{#NkP8 z+_pAFo$zhhvyrDjB!zDtq8JfdpQFd6V3m3j>sy0yhZc75*)TpZ%jLJ2=lfR6|By|6 zk!Dl2;Na*0w6bDe=(tczo(Zf_cT>bT_n0^i*Y=Q6V-JXS>$%Y=BIxQv2)GNw8d*kD z>18NlXq&Y=5#`BC_eh3SbJu7h01J%ImJSg1Yvx)=5M7y9jQDh^+2YJznPDaaEY{E% zjL1D=MAKqBWa&-ByB*c^?-&8mV#5~B!O7yGawiOpAunVEXw-9yG0*Ra!M;mdpT z(fr!F`+*>hA#HMsiQ<%) znA`(3gdn;Sua3U$ z=bRNSC)b@sRY9tA7EGK-x5-=T*YX7iN1+ip5VI&bt?1$SS6=Esx;I5(Txi0BMO^3B z^*28^73k+>oheYikIK9e!-o4;VUz6nU1c__27xTviy=Pr&@?(8MWhesn9jX zKtaQ=9fP$DpiD3pV%|4n00tNLbTlhOt0Wtz zM!zZyZ)J>@FD@b5bYL^MvOY_ubHDZg-C-4*ikM995|^V~3jTz}7td?e6xr_s}^^02EvsR2q2AnNG8N5WksVI$B#l-N~5au>J#s?yn zSH;F~)6h1;^Ej=67~wZ}NKVQj;=M9nop(@-$0z($Z&q8izCEd7wFi5MuBDrD+0vNE z$r6c7%*OehR`BhZ7We&$;WKAAJ-%kbPS$N@bXo+&0JB@)WQvAGur@+*`EUpqtDy)%7r{uZ%1q)Mz;E3RV z5>L?+iT71tjmd7pH5thGaU(-sWq~uLFOLY(e&I~88jpCB-Hp61xV^&`&VcN>WvH3j zu|=kwW`x9?HWM~V<#_IJnO5VN;;*56ct+w~Jau&c3I^@{DHBH~Y58#Dm*f8M-48$+ z1<6$qv~VDIuVNs@t?QA*n3(Elt{#kkV%Pf?SvfoS&gK3wZp(T58 ziIPVW!0E@ifsz9p&mY*_dA8GYtP^~>M8GDvGYce4;*{m8M5I%wYTAT;&fdi-d^ZV-0v?u%^4_$Vy=-u*`) zK5Q;)eZL3O9uV>)@dFrqKD+G<;EujOZ?B$2Ko{H#7j&L)B6%FPx1IXFdbjf3-Jk6J z3=57%vv~jk&#P+~%jD19Ugy7uPdg*9J^VfIu?yB^ZSX!J-Mx_+Dhs9tw z>t}ZP>R)3kjkl5~U!6L+l?Yu+2m%zK8Ak_L1&h%qJ@&#Lcg{QLHVP$e^iQN%7<^OjP1%$Ma0(97zCUKgeTartx`ipOO2P zYsZo3T_Ck}O4irVl3_uQkeL$)$XFgG8$i|dwPw5P5|A2_LYc3LpGf!a7-h1BPznYA zPHTacKOys87xkuC4vf9cZ7Xz-f82++)ELMc@c3bEZ~1nDT~n5%lph!$;*P)A&{IRm~@&>vRi9T1J3h{Oo@{{#Xd*#0`u2#1H=@rFlzGb7B<<*{ZoOS6v!Yt_pfulmH4i;Ha zN|La8cI6@#7S7Lm&Sc0_ES(8*N0pz0<03TiId&;xp&^KN4dZM$Sy(8zFR!^QyX81N zV_Fd8`?>qSD6G!3-iQCvw2%{UxUatX=&pUK-fl5D;g-{R5*T~DZ65Gp;#Me|sB~EXMu}|9o%4 z$s0}MOGhC=A7BMdAn9^QOzvVh9;j+zB&Fn>)4pvnR!va z0IPK^tu*K=P`%qK)R}o-vTZ+nGiueZUFI2a3DfOf-IEKKl+_qGbZ#E4`wQmb+p(%v zsbMp?Cw9M7u1$Xv6UfGsS&2Gs-yI{HGnQ7m^|oZy%YMbdk)e}`)NH*q<6U!;PA%r( zzbn_iN%-ze8_(+;vrN5Y9`5kUk+E-S`*AjEu3pv!AgYSB&p+8aCc?jn0c3h>+SB>4$?}4KuBY4FRD((p0mNda zNCVzcsIJ81kn|G@%R&Y;C-?TI+r@mYqLUCSmu_5UB%z2FCwN+ zs}GfKtrIraw4_shQ*^qque4`M+}f4z;i4dQ3IAl+@+i4PO3Pg+|oEmU6iM_qY;pK+sMzxn6Z1M8r>Nb>saGks|)kdKu2tBbXxd0u_>!k-(Ied>1 z8MI*G&dTEGu-Ie084>!1gHz@8UAaKLmN(`HSPA5Aiy<JfGQtHLBVSGGO3x!pZVd$WTD#l6G0* zgUiD#_Nl7CiGt6b^inkdYq`sbgF}qOq#_!*AM+M8y)3wsimNGYV3LJ05sq2ER^5*n z5~a3vHaAP0q+@L%i)R1&;R2H^$R-Nt_i(s2V=|rj0Cmw(^qMUPPb~#PfsTIk;x$Nf`h4LGU;AkxMg5BEVo3wzB`z^8}BU)dZT1h^9tY++^Q5L5fTdj7!m1R$$ zG7!^Mx6K!5GlfCs9Kjtq2|GRTy%*EsJg;tqr<3*V%Syq-?M?~MjNG%_it>3O_8vfd?It)$VRtsF*U%_kCZ|1m-i?R8xdTs)5wHXBjh7zAwh4y z%GfY=AC9yDk6ImGhT1R1jKFnTNvvBnuTp_>4mGo@4_%^&3oE3c@^;$>CB_kTmt6njY5I$1ev5rD46g8JYdfJFT_#9#%#m+s)n;2U>>$}VC* zCk)TN6C}?MYA_Ek^dR;4FxcT%*sKRtS7+HC-ez0I6xgsq?1&h%OP@9Su=Mh6H32QM zusTm+KI_ZN7#EYHZq?jdV3T2un_etQtBqhMdeea8gb)l`c>oEv0WA;hn4q#^d8RxRk*KL~RuK38~buB0IPaM0nz ztde4KJaM~HcnKggB~`vbAoYzG(&PBz7Z}LkEDNNc#-2wArc+K{VpFR$j2z+)dW<V}8DbuM(>cB*H|*AQ@#~+!a{eoOD)vBi%6bUF;SQ$OAnvvWbwr9jn2NdecrL zDYzA(lnz2+8Xi6g95p5rN+g;Pi#8TOXg-8$m_xaNy?^$icsjzAm@F?9kC zr|^Mpi-aT@tYxDtb57jP)`A%=If`GxwxKU^oQHeiQa0LT9y?VD7 zO?iQ?94ps>gw~yB?*6L;o4|1wGb?ZWs0^dDjB&TW1s5H~Al&tSg-yLCwwuJ`50Qurvvl{wZx`m$(CfvRSrrK_l}fA<`@Ie2&MMT zU;p|Tvo@+uXwI@%vFa_oIP||E(+--%_OowsrmJ#_C&nM<$`TUMHcBW1EUZk@?|j$` z!=jFxp_re;%R`*`J+`>BOca=oVoTg#@ojOiU2){YbR$F6+`VD)JVmJ1sqfW@$m~P4 z1$XSqKYV37aS=BN(HohQxA@?N8}$vVt07)Y;f+8LR4v|g_y?6i<3={nya#cR{TD$6 zH=3N}(0wOX1SoB@aXx`zkm|wc`54DG!c|7$Fv;r~(-a*e!+-cFRa%;OGwVjR62_D! zqbwaG9nzxoHgf%y27zTQ^2j{M4%L?%n;}k@fU~X&7dS;B_WO}gM$X{NbLgJq_k55$ zt_9oEI!q`FRTZuK_tKR^@(*v!e{W+(dYB}#9qTY|w1w@3pTP$;bwU)L-&Sqn!SI^Q ziI1039t~+SdRK+i8ymSd2y6hd05t5npuEzraBAsW~k(G9P3v0~+7LlbakTFWIi%JG3av^qcB_ z2=3mkx+@5yE}7>5r|YTLkQEt-o&GB1m3eh_I&?+qn?fC#{(BXs@UgqlV+^P8ao0qk z)afF5Ir3%Hi^eiy+$Ox*CQLdHr8{#OYAw7yf-SLu(C0$&{#5g96El5tje<}|#1tbq z)v_aF?gWi8qcWz1erMr7{f_a4+X(k8Q6`rsp&2BJm{khLlrOU;B>It9!H zp4&UsIwf!xJotPJRiPO~9t*eQiGCeBO%zgymqL?|>p30<5|7^wp+iy*u*TEP=F9mS z^tK5Ua||_v#EX|Y4StG?(XEXSeU+A(Qp=a75#z!@ieQPKPp+S!yiQ1DE@CM?Lvnhq=qihn1xJC?qn-us!M)`EPd78bp_iviB z{$_`b8W`Q)duA4pHS=#%ARj_$yXLQcpkR+0c&^Kp+~Z)&k&fN>aP?$O8gcOF%)bR6 zGIEgQO<9dalObmX$Y!*a@`t(&UOU*GyoPft(xpy%`1 zn4=4f45#>|R^d^>*bBjfRzfGp8B?A>1%>rZA8+E|D?&Zul+DPaK;>b0RS5AylPE4^fKH@yKzvr3DZ(j6x!Lj9NF+|hs1(r?E z(lIipJ$T20ZJRY^Zw&JNg4|v)LEDlFyecX7Ku{+azdCUBAfgy$yx1kI$3ds3<_>)- zS*xoW$>@}>YtDUoCwdnwWA~Vm8n+z_d>vu=h>*d4%!>JqBGZ?}Bh{0UQg8kx`eU!c zmdEvsHHO}vJ%=u6aw}b?a>MwPgLo2y0^aDE%BX8mk>574e(%I*OvsF0w6SjksW%8i zrhEcH^>}gp$Co11CpE-pyh3}#Q#HeTZP@O|9_>#LoLlcDK&U~F>g$Z#8V)-ia=q`D z*7expBiLimd$PIs;c=-fcY44E$TwU89DpA2N5{xFWC5xmJxe($@vP*B@r9qF2cNcu zu^^&WGUhccxx45J&U$PlTY-w#E!!d6gVo$TrD0x6YM*_=({FSly&#I$wn+Sf>+6w( zw~fA)w0u{|aUS|n$Xx#Tz)YcloKfg4KK;)erac>%iw6{@`bWh~h~hN#?jz&}f5=@X zUJT^ENtWTK5NR2%EXcCjDhDXTt_j2NCm+j^FaIRB?Nj3Z37Fp%HRYp24mPiPv5=cO zeJ6I7Sw8}?+LM|yAiyeRAYaet8_SShN0W0}hK!lTf+}WQkVtN4a222*Hn$gm0AbsS>q#g+UwF!asZyKX~_A8|DjOab4hp zSf@kz_$y@G{pNXgu)_+*OW_0Q#69p69kEziJjIT@5RQwQ9_ETn<&4lZ{TZg^I`C0H ze8jvWdp4-6QIS1z%$k^vFE>u3{%%(AnXl(%iR|`#Cqjd^uOY^$U6LJ?gi1tR#H&>K zsg*w93PkR>9}guz`L;hW1fF2`r|e!kjQJ3QF$?f=epa}PIIB3;p&tvfl(y8OJ@}}- z#6Z8`KzPH$Hg>zn4VfgdvU~g2z@zx;=d49OXd~U&nR;987?uKyb;B~%i^z4$8fV2$tVZ_lo9xsr zS`A^SKA3YQQD@F&RH(96y;CAiNz>shwk=jFKUrOE)E-#sVh_qZb*uZc0?YNrI>l$} z#yaa@cZy6A=6SR)UE8j{@{`D;Hz^pdt24%OfMDi9(JP z)EevB-vpUUY+1mzpK0qiPpxat{@OOz*1`A+42OG$qW%+@jlM$Ghffo1TTMZcR|(6L z*9*3fIBIYA8!JcB9kHLQm=584p0%Bs> z`GZ{RlRglZQ-YwOmR0Q@DL7M_H=*P?-B5j))zk)8j5o_{HVX}^>Z=>r}fmeoTe;N~7pVzQqTMd@WR)s{PvUUte^s7R?v7Jn5-nz2r3Bb_#Wva)JCs z{r$%Cn_X5n6;c6N9b-IQ03uwf3bUTT3P2DcNq}RBgCZQx00sh3Ou&H@+6_l#Yeqn{p*zEJ}>#_aI_xf_m zPagrK60`{5f#{}O91|6w8{`EU$?_#F6*^E=a0?W7Za+N9T4mszjX*d_Ic!}Hs5+zv zIYP<__{#;^0G;rgPI2YWgf;8{oq%7q2uXRbttLr+CUk)&D6fh)mH_9Et?w+NxPtaUHgazE()iW!J2#=XN`I<=oyLe zYVw*B?lop%=lx0j2*v2UJ6mA)g`$;;asE3xln@_{q`cZnE8X&&Mi>3=wywn0Y?YBmKVQKU?u! zXP{wJRo2*HFg0zn_W4m{UskzyA(ejkr$+cm?jff*Zb7YoikrhJdeGD!HD0HPL6#Wt zLV&^fuw=sILd-0;{;4!5U0mvo+=r#e8LyB`6HJq(?@@{dgk%u=`z{~Zk9J< zPrh3b543j6atGUuqkz@dF@1G^Y+xfiWp*KQZA>=yQanCK*CZ~zRo^#b)JYpZPho|U zCg-HBVxRUtuX`z#e6KKV0#!)LRPInL8V;}3fU|vF>C@yFl~1e1*Tb$>;(N%O+guQ} zat)I8gaMm1^m$3{Yon~P=TN}SbJ**g%G<96P28YTpV!>q*I0DrOw&sbdQ)%27hA>F zwM6U>z7BV1{*6br^4p8ur}{tLbnHoG6hHEh^P1k7k~Plp{FOK6e3`D`ihWq*xv(4$ z@^;_$z~d=+M&M34j@;LNvc3Ay-N~~wUGBHpe!r`3F~u@X7475RFS@;Ew>_Ud48gk0 zm~3vy$IWUR4>7}m2a8P~#c zUh3RxR(+(NzizRk6GYFjeTsnB_bF}*dmUQPP13|ydt84KbB4G1gJj%YOdDRQ>+ktm zhGqs+Ev=i+y&0Quxg!4n;RcA`Er18!=gdnmf^!0O5n7%<1-q%K2+SNGP-W=*mE2j# zjqGw9V0V2!gS@%5a_PQ_w{mH{skeNo59^c0BMn$H!Fy*AmV>>PkPkE&@P-F075<=C zz{?=q#PUpY3Z?Ng5F#TH=rX7o23$(;Gc*2HPl`0N@gqs*gj8uH;?piv#+$I1IPrd& z*`p&zjb(1gvzdL1f^uS99}WJ>vBeXZNj|xp!m(3E5GV^HGafM{Cql6zDOu479+M%Z zI>jg_fdEW{hK@KAk515{gGR0j$=GN_gB02LwgU#4{4Chph|y>d_l*7~7V*V?lSFVT zheO9aE*hCX?EGSC|IZgJX6Q@E$TKuMxKx5EWQS8~GVw~{eu9tz+!Y-hWZldaU7gQ; zze529L>O#vCy#xcbJF8QH=__B9KB!#k2t$H-ULjF2UD5_2K3eurj)39!JQ)7K~4Kqd3Fj5eDJVM|?g#0hUqIoh2p zqUB7?;VM1&5v4osNOL?#QY)j`Qmc`slO(lQgdK#nrc`haWkSnVfX!7h%+{i@P@~J< z^34mq77v8o1vDDP`>W2|1^e-;!Tjl&PLf)y%@vI9y7xHmF84am`KokkS2(#Brn!$e zQwm3B^-wfy7tb6+3|)q}JbTT#jPd83c(3VsnXbBC0?qIY8Z@UedwR2U7cc$%;f3ZV z4XTv0+DDCEOAOhHSD8*0?NlP?FdCHFV~pX^WZLeXm*0{JtmqVWvsbA_v=?9gG`fuO z7+vKt3O7*@FSOiyF8!K4^xY14(arqZU| zS`D$jK8*&OYQs5=DNHl%Qjqk}q{P`7H7RDdLY>K(lhLB31Az@y#;G7fLItPdOCgU77SBGdRfH5z`?n7 zM_)#Jzux$3KPAW!AuI@jZLyK%PG(T$k za%iVg0n+O0H3&q}9!R%s6x(f!7aI&{hBKK%J1F%V(xMnNa+YWZHxY2zQ6^bVWAjYZ z?3?a2iLFs$MTzDQXPB0c1oW_PuB%`DEERVOj`#unJ3IiaraGzbRk$7)Kmda&#@n|A0oYs{!e ziKUTEIBGF(<8%e8cCf4DKyFnQsS7LpfR*l`2ho6N3;UF2;Z25km8jTam~MLU^YT4; z6UzPy4?7pHxSG-0uj3SNU^JgPH-{p4^@H=8d+MWCb$7R}wveO1vTQwc!O2`!Xzlof zO)5{MJB0tn@p|B`cz57w3f`0q{5=rc;kBq#PVwa>*mzYUct>ns@ zr1H>Y8pdF>oC*taKCnb8UTRp1z?W54DUeGjTwfYo8IXt_99LRanwf5F)$%PdLIa|tRbiOG1}*X-bF#c7gOK76G2d_(-jq!Q8OHF3`))!1Gpsa14% zgvX^WG(pAH_AY($q2;TULps`8gJCL6^(`a+=uv{R8=Rsl%ZqnN>f%}?nTOfCQTBiva03%?8)H;C_zs_19A8u$l2~An zhBuTx&a`>?d$r(ObdiVbBcl%i8uBS*qJI=P0=O;rwD*j#)z+JE>QaxYMF%Tlxwat2*@qq9v_$7a5ZGjJ=X|*zf=$A5 zOxAuBBQl|fGw<&%SeHh zSTGfWBABNHkgx3GqIDxw&=nm5)PfH|$Rsv^iUE^O8HFNC%X1+dnL2`EiW=XSD>_08 zrVz^Mu*v~2Mg~YQ(18Kq5L8Xds((lpi6|v@QB|es0TJ~lj^iPw*)re-+{!vSV#l;A z8pDnu#iRo8uGY=HXcb>F{Ep_m7T|bv+>7Z&bf-o~cLg+4_nT{#6gn7zn5r8{ zifKTEAPVA*Wmm0z%8hf@F2Kql=-CChFuOwb?2PW6VAztWusv+VJ}R1uMWsmZsc=$| z_M;$1AlQ7oDoCk1Ap`|SB^_9a83lQ%a74ij3>uThB*i4GF$vQW^en9be-AZ6J>>w( znX;2eH|#knAu8$&Ml8`l21Z{_hs;R_W>`Dp=)h|#Sp@eCZMcac#=HPCJ|ZFP2!SO$ z36fVcm+rxywXy* z!Xfu2AN~+ys-MAfF)Wr=K_WqNr;}RDy(X-jQOIq@+8e?lz*;KHUXZ!hBOCJNk!LB+ zPvO)LMO1UJQc)>;5!ruumqxv63Mz~h8}rp%a6aLSTEO=9^gvwelZ8N3gyt?%fwe-Y zE|<|g)zY@rdBuWPcWbXMiiGN8 z2DO~DRC+@9$XN{W1t;tS-H`go5NH-xBn8VNh5eBMn7cvlKuk-$Us-9bpDi(RZS!;!~|oLm1#0(YupMDP3xzn z!Yl9@5AZ@c0XN>`uuLMc$yo?7Ch*Pah~WfB+^}Nj!$Tdfcp=Q#hEqajyk)Rds)8K4 z25L~(@peaXXbyzBySmZp8CJYS7|jORqWm>6GEzJMjH`mV1%=eFiW)!XfY4_B`-C+L9^bE05W$l$X4I$t2655a2R13CBE zQq%yc=98q*{`4!WS=lZ&*W`t}Xb-Y0lY3Vu{ElV>8^Z72)W$jL69Pw5M`)HW$Pir% z7faWy*Rgly&LVaT3m2M|t7v7RZTWm<>3zmV%=&grh0eRJ*1|TA57)k`@v>rY`y!Td z1`R&k33l;$^XP%S(6)YXL$i26w>doz%ePgmzdlU6L=3Ms@%r)L77GsLI1TF)Ql3AO zO{Lg5n_M5aeiGjE53&V0?+dPlyOQS_&UE5%pfL7>OYv65tp_#x!GPs(DpKpTcUzZv z_QOR+Sboo29eeqo_1?T&R6ReSO+)*<$D52-@2?x2uHY@7fZMau!iVyGW6-O(Qq5S?u?(SEg+E(P2pLhN|xLdd5wzIe&N9NmEzKGW6to}NdQ zpLp-v+p7dYz_F^mJMK31+KPIpG;@|w90@oN_ zWsfK0ka_*TF0$|X{g1hY_MYRpO3Le?(9iHZK4Sy*dwX{ec&~LlJghFL@jGwkHJ(TJ zyygAAUKU@N)!uyly`DE7ugU0q8mQg)zlJYk-^%$|G5g;ZV)eaTcec7O%n0K^1So@h9 zjn4lTS}yN3w`W^cF`k%unt2JPqIT(jA88+5EUH$X-hMf0) zGW)?}3j2(QbJj3qOL1P#(L)3|ekb7<@>a(3T}L^yhcIo7=Yzm(#L?+?Oxw!B+U{v> zUSI}0T+!vNiv{kJ_r6?2Ik-tC^KUOs6p_ z&g7(#_vc^G+YvtJ$5!W|^!EH8BjICm`FL2A(?+xxC(bI{>Uu?A`r0ZxKbec}E{UvF z(a>F;GpSfosM$Wl^$j>)3uC2Lhk)SZEz2|}usx4nS8_drT7B{mlf=cw9?i?e;L6Z4 zTz#$VMJ}(S&Ao{ezq#{NBl)>fCgNh?@wE)tW>9TP>&x54VQDkPD3{%+s^clLh{y{}*5f0Dx*s zuh3uTQvdqHYJQf8lsI|DTT`{_%VLp;lHRKmY(~-|$M9|L>2D z44o~F?JUjcjC)me?XwwBe2>+27%Qa^w14-Cc~)={iT<3Aoa<9bH4U_^#o}fnL;m@E z-8={)q0sEA0?6fHDHLe)MJ z{K1DTIVho8$nfmwjlKoa5HP*z#p{k!DvOjPRA+)nZGNrd`({3cEY`AS1GZhNFqCl| zZnBGIt0s*Cy4&7|PR>sbn-Fl#L*R1^dX0z z`oV1(?m=X}HMG(c%Qd$~-a)Gz9%P2uwnu~LvW-Lmo{*9gj$+2NX-HP12BJtmB*;uE z%@Y&6#9u0Z4`jE_=_&73%Jfu@F|l9iDtoEVVCMIT%2v>vDNx33~WW6ni)jkofMB16@esp#7 zo8(N2ycr-NO4Sbp2rZ+-yI`!Ys>9!V z>zGH;y&!)v{*&85}+Xi--5__#g5o?<)3 z4x9KeId#7>9mhO9U_x^LdA;vNvv24BKK#v#*B@wQJ++0$`PmJc@xFBp?x@iH+ON=W zZ&Q!Jn#RLW^U!t_ib!v_YmSYb$i<1~3cGw*AFIF;VslT_LiF*=vauiFU*YMW;RNM_ zx=`tREW^Kr;D3zizlOP*PMT&?MrKlyc3Cl^Zf07Za$0gpQDL$+gdx?+*!rQPN?OSo z+6hTofJriPG;_i;)6%O_lv7H;&rz|-k5$FkmR18LJC_L+wLteEdplvr z0DTgFbjuyW7mB?TQz1eV#_*Pf@aN)UX@a((T}l{4> zTMWaOhv!_8VZhk5VOockJFLp$;|Obc0WAfqZJ;=z4n6V_g)A9&3vb67g3R_sH~_c6 zu>9M@GJv_|_*;3!P^+SX6f+)RPMCx@_@7P#zpmJ3ex)Ig$!qK$lw_K0#uv zw8F)*WBj2x0FIL$r$B{@QHYcq%m$fNkQl;2tHjwOy1*Zy7SppVT4T|BOW)#+F;eQp zWDFPiuvxXHA~89n1n9O>c}iuOWqKNCJV;6>38i$y1hJHfnH1@KE=in8VtdtostzNGUJ80IywyeIgFrnE1LI-X4B51P*&$(;Pp7@k*pBON=&Ir|Iv=97a%ECtef zWdwGinUvXd%eOAcFc}-uM+0?|U$LfNGSwv=3n%aCUAF!x(MUX;SYnl>$&P7@F=A9_({w&!8W(68j;}~=b`hs zfK|!~Hn?5ApCkAS%wXy`N}s|5tsx1`F+;1>RK z$-{InhRW-|WaTXSeC}jN$;7(u)#;SMuvX#lxF8_d+m4hvEOhinI5y~7yb$eVZ8)p2 zQO^drtMVr#D8`aiX_wRjZXuv|%K?Yhm4<)*dZzj90ejSO*`E9=b?fG_E+E7WD$I_| z4+QPZ(G-1Q*x$*1k(R@@A=kyR#z<}33##Sc>;-rFp6^{qp7_{y@uW0Rf6SkE{3V;k ze_8*f_WUd6%X$Zxs4I`u3+J-2ZEQUaB82)? z&vpN6Nq7Faqa5a-L);aHsl4P+MJ;8m_pIURZ>vRL`LkMu?=Y(WEq}qq{g-h1pQAgS zBrV;eYl(SN*wXxMpM<8+|C}$)|r9j3>($UgPvP#cNF%BsKdcH^>c0p4c z8z1Kka}O#F)C%Npu^s*XAlL0%iLGxT_R^*twWF22MS6F))>hDQhGeFn5(!7Zi5K2mBd#!;aMPU=um@O7ib)hM z)op((h`!icQz?lgRV0c2)nDZfg-Rp2@}hKqa1$oc6vzKnAo_K*Q9NFL-6QQddXQcu z)90T7`|tUWg(=Udm=Z}|(m{%;2SzdHYC6Uxz0{E+#l0h?0(Ek7y+4vl6Cm{BQ6 z!G}agC#4DmiB7yOA>_8UIzZ}h?F&LFMLRLL43RpV(a?<+B9j4r^KAdSY6Gyp^`hS`8J7F$N|1(=x)cr%N%Gy})`7AFToi52r%t+JBmr_wIfyA7tgOQ}E zcbq4+2p__i=$ya=D60J6^<=jKJ znkb~l^86$!!GqfUy};(2qEEtIA=cH`kS#cTC1vDKb=%B>?0)B6rWemVz z>y}fay;um-{HM$e!gv9NyCh@2zj27s+yU72$o?M6)>ycxwM^MTqd$ z%^LNzgXl2~TAYt7mNlLw8A$!|O|$gRg4vROkU-%&JRD=Gg~1PD3w|^=c{oic7!rn6 zAggBkgzZ$YJ>FN#7H2W&9O~>ELMTwIE}^n|AcMX@x`NHhHrLSXYt3@VH%-m+Ch2n7 zy*?aksrqwHbZ&`5XUi#HCr_ugPT?}9HzGK+K(Z*{2R}&(guDC4SuCvRAmtBPWOl1;H8N86(iB)zhREBj^%5^`+}k3` zNfec%N5Cwjgr8cMpMTqgm5RhTiG7cr^0yHFuTlIj0f~BM`u{5+(YI2FLy{<@)ab*` zscJ)`ft&&SZGxub7qW1EH!k=s|J9Xj?M?nY?0_*bDO*{yK6G@rC{siIiI!q1a;Dy2 zkh)8HfHe{vQpgJ{3#!9S2SPY7KX(NDTa%w`>yK*R%_V)yf9>~QP5$S8fA3T#{{QxS zq?EXiq_v|2qYQpT&_W~$*b4k_N>S?`OLBiJB=Rjp-|`=`!?#lUE~d^d&i~08`_f9$ z(ox?wabdnoa+Xq7+IMD3W=T)CvrDbZ8JiGHfqu{&1AA)AB;beTzTl7xQ~xJO zXC?OOQkReec1@MDMmyg8@|uFT_~<05Q0fh+T5THZ(63s8;C;_(@;hS;sffLBfVP}| z{LKv>u^XD;AM0qu&yP^=V66bJI6TR&E4vu}EMu?3>z^2RaKf8-<%U}r7=#@IH_<)E*Xe?DLdY`m3#DlMn?6Js81nKX5E z=9QveQ-)H;k{rb9Cv(eE#^cRyC=akty|5_ZP8OrREqRv|wUx+>S+qw#*;n>S1Zh3gULyCzl+D0jrW2V!>+$D@PP0S+;5kREjg+x1{pXGsEETO}P}(yiwo+;QIC{#!DnQftQkUmuOAkxHw}(tL z(K5BL81;2#(04C*~rhY?J*F=;1I#lcAp3W&mc zC0ulZOl)l>pP78DY3-&gsQS2^s>5BU4LlmcGxBD>JDTpv^nBSfu)QCQEdegk7D%>S zTbgwg>)=*JGor!ItUk;{t{ z*eJBll0IhT+1#e2c!NS2)3csSMf_}blGp2!Oqy9yWiSvW(0)6gTF@heJD7zPm+SUK zJGsp4FT@(OUq_*PVXPl@+6q46lZ?f~0?!g_Fw=r&oQ3Wdnnh3iJi>K7^?VH@POeQz zP8vzAuIlSV8_pl`<<7?a=LE{*x7Js8P}PR!u2xD0+Yeqz9^hFJe&h{DVV#bR=U9xX z-*$3uW~n5$Mm1JJ>~$;dmMgL6YxQ;Gyg%JGl=rH)Olg$7D4oe+3uUDjM}{whLGRGW zftrJ}cN?;^f8Rm!E#ku3cnOukJ=HPo47I zbl@PG;>rhP#9@!UL-NdJNs4Nm=B+TbcQb#;a<)c6eaarPjiWLT)}0hHik@poDdQtY zOG`OaV~oj^Zht`GfHM2)Uhy<&g1~=z);MqLOX080+;-QtTCV0ZFVatGgzn#90hgieb)fhaD)xb!&HTZ&97y_iS<5M9DH2Du=0- z8G2!1f)|Sa{9E~z&lvVy$hUKIeP0dy*M#(stnk;rznIuBH8%e`M)RN5cU(|V(K;*x zjpC*F(Lg7?0;J7NvYBiS(IPUMW2!`O>N6ns^|+asMEbKSyVdt3>~;`;e)F2ob9}#j z8;3rYFAvId>X6kT*-~+>SUld~eiH(X>i8P~hso1*uU|~)NawfWM7?L`Ky>nJzo|0w zpL?a05m|zFw!oo0FSUlFd(aH*_Z_{FWBL$6lE|Q1rHWtHdTKhurOm)lrGgSaS%20D zz}ki63R2c}?lZ7HrT;P>41v)6AU7jGU5e9b10QZu5OAWXgJAbG6DjB^WUn?%Aq ztrRwr=B$Xty7Y*#1A zTg*0#87HXj`0|x!WteV{4npngjw$Wo`954LKNaLwPoD|Sh@ngPNmT@}rqX2%^T}>m zwEZ?xB13%ak|xHDn{iV}WwJ@OPW7a9mzmZPBUWV2v?`QM0#7q#A-MJ0Hd6%0MxR6# ziVA7xZQ1M8C21I!(t{;Am&G1dPt}I`8;^v#pYxq)?Nj!_&f{k3b#;RRnURe+qONv%h=QLD#2P7HrSo9?Vy8{_~ z%AY?m>++C)!JNtDQ)1M^8a|^=Z6oH}%TD_Ytwb=4%L6gbUnx zRodBtWqs98vNtW+S?%Rw(d=(K=o9{-z%LLfR#Kdg6LCvm0RE%bUbfpa|KBSgO`^}3u~ET zu~(+)rlK7)dL4zmOp#;2a#*+onr*BPId6r5Nyl_b7IYjPhckc0a^Wj@$#WvN;CHE2 z*i}z-QQQ3TuxP7zJ4kYX?z`?KiFSe!E9~bfWxAbD z72ke3*`|&TyvhnaFK1;m^-_HGAU4dCS3o;Sg2`9HZaI1ZqtEXN1Q_EqhHk~YU(H$M z5@{9VY&$J4^hv{FG)E$ZM%wvnrs*|}>Q=wfd%2_`dY%#lL@OS#4!uCAqu!CN?{ICt z$#Oy%KVEH74b+W!-9UXUm_?%>ToLSSIa4dG6MGl}OX8&*A8tDYCiPffiq-Ux)9E^S zP3fY3LiimUuB2qZzG*63Pnr7*V`Uks`ba11_rzfQV4D9}rTqDQ>k!HX^m>(ooWQZd z&e1Qvag1)$+pRpex7sVC%Co?lj~2(=%@%(k~;BFmO=w`iaBtk#iq$0C+^E!!dhqqEK8 zMkQJI9)8_|dj}dV-`pHA?K5O?k)nis5xFKQD}}a%m8m>0*b`GpqtibrNsy z&AQBseB2K0OAO`uKAyPp^Uu_#O*wKYNP2v!>*rSV!wY5(Y_UTAu>bdSiY0jWxx?>s zis7l@9F0v~dGI#Rm<{K($=9#}>0xM$x{3<4tT8-$8G?Ut1u7nEr8oE^ zX8YwPiPDvj+VGPw!z?2&78>Yb(ISxzOd>}R8sge#y%V64OnFjg6Wa6~p^XJeX>7P+ znWq?G7`=PNnfql%Hp(|(Rih7h1_p-#xD)nQ%&dMMSsZshcvc2jbG^xnZ>Q46+`de= z1_g~}O+Z&bBLr<^b5=^b+Flxcp(kym;YTFr;*EvuxnS=Dz$p7N%V5v?S!pr*lG`I0p0` zb-B0s#5}R1XSwb3$fT*w|CGXpzFmSM6)Z}(jv%Qu*x8z0>j<6WEMke`g6Rg5@AgH7 zPdpNtEtDNPxIK7vrcy8aJr@S58Ruh(brDIa+%63Ihw;4Ww)KF#yqOSNOgK{eywtLX zu^)O0)S4>YKJxcf-L96g*79DPr^pIOS$Rl1%p3h+t6OEGTIKtyN*c7pVCu8}?yOM% zTs3kl-Ka0ek0_NJMId*G`a|DG;N)KOCWvD)Srw>MKq(W_I;$_NcONbRx;Cra&%Ukz=L23m=m6w zkr*4j_3J-PNnjEF7*p?7RTnSCVP06MpS}P2yi-252#sZ=$qDe*3fsusx{`-Yzy1Da z8CnNzgpCf;8@1~~cIQPTq~zigcBJS9I)e4nz!7wXg%|=DBdl+j7x0-L!q}l;8@ufb zuGs;VvyuG)GlKN)gE)hXkp*e+a7~-%BGPL#dl)@~B(T}YAR6-f8LCXsRs&cYu!>T@OJQU-YqRDW-%$3zg+&Uk@km2KzLRdK@jQJoH7fFcA@JE<2hpt;1}8v*sMnIR@;rLhTgqpVcXIIBdh_8lEnkX8A~;H2YnQqoZ8NMIJi^gk zhEo!{5uN)ldJO=(+)&7B{hR6~srT(>#CoE!NYyIK(+#W^YJB<+GhDVT;-QBLI4h37 zB#CxOpc%_>L@k8LK}k??MYT8-M}{N3;jB_2#vrxo`wSppAS1Ay;On-urP78LKhWk1 z1M57H6Mfi2+;%0E3N#YDlR}r4gzeTC$rERg9L969O`_ zk-|i*-ufN|A$K%DBE!{YKZ0h7mx$KNZT0d@V{Q|tq<}@#>nv*GG^?T{>ni}6|G-nM z%0;91%%Dk|#Cn6eFp(Ca5)AT$M_tu>g4mI0|ytz=V?vE4IJ9)5V#|*%{9ya;jWJD2va0 zwW3SzMwYs4z=GqC<%al(8IK;|z<>VKz5=f|j7;eqyjOcSiyqspy15H2xGS~--BtN# zua39mhlrINCF{I{Q~}@oPOfCS>U=Y%5p#dhsTpId?^zBkUAS>(h#P`*R`xyH@f#|V z=EjDTa-44ExE{HdHkqn40=udY95HB%%4~0r3lbHcumxfl%Y^H-(pp8V?elI?0>=$^ z2cCH|#{o8O|Ku53`am2Lh$WllD)Y3YWh3H)CdO1RC?{-vo{^CO!5rh*c`aouLwVGp zO_U$cwJU!cz4A|Yg=0Wpxv9rU>S!OddZz<7CTZey?-GO_V2e%T!;I0YFaw9i8PHX) zANK&xcy$Nrp!Zjr<*Z^JEaWzWnjo$IeyyY4MKyt@0Om!#pRl+od6Zc7e@wjcfyZCJlb+|Za_(rObHn+z0YCNB$mRj@ON_XVq&d#Zlq z@6YkS(y~M^PO5~7J1n;=cn<`j86`t(CcfjnP$p)RdhID`zQ2TK;2ekaIO>vFDdqOW%lzsQD1c^&f5R4r#Dt4+GYc_FjD9U>or<^My^P0? z>QZCXeo+3?n#wn}dxkePHFUFVoO-8sZEqW!h(qK-didy|zh%(%16x`IC)|Sxyyo;# z2V*P;ff_taH>p%5GNMT0DqBDis0~3i$s_KfQ8kx*kmhkn;Kx?*D_Lgnat34tcw#z< zZYrC?jfsyD`UlGHR>q?q?ocpHf2tR#1^j0gdu%GXZRNcI{(4U+z8o(8l|z7&gA$hz z_y{EYPDqx;7-t)N#Es)YC#E;ga?Syh1Gc*?C9>o)7F+F;=b(T+Z1wh~Vz)_a-mZ)J zhcauif?EqyqQZJ-VaX|IguhA^SxnGFtXUAWO+P+BX z@Av%^zU8^0p(GuBucwZdDy~C4ztvOMG}RV2;%ki-QMXm5iIPy}$>n+zo;KeXeM%yZ zpF}ZlN|=@mM>9_xyRHm*a5LOYeiPet;cM%okWa0ks@$K>-%_id^$VQmdKVV?Cy)bD zQ^~$ulIGfCU?L5_N`TwW8&Wv~?A_ZHQkyrQSlK@8ta9Xg zf5*Ryg(iAN^Ks{OMOfx4VETm0icP)8mejADi|$~_3uxc;Iy4NzO(rnbpJyc^``RCk zk_eb-^hhbQIakm>iNzS>#uwQ@h(EY7pzKDy5{QQg_O{dlGhZ2;*^&ji`+NDkrxPW? zkn3P+@>!c!`kAnBHJWeJzXh|HE{L1M>rDS?o6Oau+Dw(C;QoclEQp~MTTz#l8spUJ z!{}(D;bdV7TGo)Gjw(F*kxHsw$+}TI=RDY?=cU0y){ci|1f4?Z>-cI{{XRDi8+|%% zzKW8Eu?|RWsL7v!;lLLV=qEx5%kSDW69AJuOVfdK>o69wy303x6aQ$Sd0nfernG)@ zO5KADlqPd^6fctJxWy8{@wN7<{6b^FZxYjCeQ2tAfAz=1^zd<3Z7R~(ylrEX?vCr|&t8FnhN5ZQGivAN}O<#-HO?$wcp1&*IqJ7Z7qj{$4TVp{%BIH@Fz@H6@8?5}qTxDx0k7kbcZOePRIW#R#kPZz^N?q;4 zr94w>q|#Ol<(6RuLcercoqHD&O`N7zNZm0j5kQo*0qvqvE9V zem?7@>5D_#t%BbGNo0=?YUPI>S|YkJUTF$FD5`clBq_RC zB^re6GOlrL?lm&%8)cN8kwjLK5h2QoBt(5lHlaw$mX-NCl0JO;xZmIP@Ob#cdEWPV zpZ6J`bKbA>k@%5#A*QqPMee7>6t~*E4%Q|TBl@?C-x~0vqV6GO|0X)>ypyV zR&}Icv_PDu#rUIzV|6A=gF&8GNoYfvL*c(hJKK#uh*%%Q9H1tZ3fud2OQ7_a-L4

A*2Y6;sXO(=y}L5%li# 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 O-E`p5ju~ufp#BGK%(k8Y literal 0 HcmV?d00001 diff --git a/src/erlaws.app.src b/src/erlaws.app.src new file mode 100644 index 0000000..6478c07 --- /dev/null +++ b/src/erlaws.app.src @@ -0,0 +1,8 @@ +%% -*- mode: erlang; -*- +{application, erlaws, + [{description, "Erlang interface to Amazon Web Service"}, + {vsn, "0.0.1"}, + {modules, []}, + {registered, []}, + {applications, [kernel, stdlib, inets, ssl, crypto]}, + {env, []}]}.