From 03c7953254e75994f98db56e616b5b3eec498a8c Mon Sep 17 00:00:00 2001 From: Konstantin Lebedev Date: Wed, 9 Dec 2020 17:11:49 +0500 Subject: [PATCH 001/128] init Iam Api Server --- weed/command/command.go | 1 + weed/command/iam.go | 97 +++++++++++++++++++++++ weed/iamapi/iamapi_handlers.go | 81 +++++++++++++++++++ weed/iamapi/iamapi_management_handlers.go | 69 ++++++++++++++++ weed/iamapi/iamapi_server.go | 72 +++++++++++++++++ 5 files changed, 320 insertions(+) create mode 100644 weed/command/iam.go create mode 100644 weed/iamapi/iamapi_handlers.go create mode 100644 weed/iamapi/iamapi_management_handlers.go create mode 100644 weed/iamapi/iamapi_server.go diff --git a/weed/command/command.go b/weed/command/command.go index bbc2e0423..fea1dd9d3 100644 --- a/weed/command/command.go +++ b/weed/command/command.go @@ -23,6 +23,7 @@ var Commands = []*Command{ cmdMaster, cmdMount, cmdS3, + cmdIam, cmdMsgBroker, cmdScaffold, cmdServer, diff --git a/weed/command/iam.go b/weed/command/iam.go new file mode 100644 index 000000000..ddcddbec9 --- /dev/null +++ b/weed/command/iam.go @@ -0,0 +1,97 @@ +package command + +import ( + "context" + "fmt" + "net/http" + + "github.com/chrislusf/seaweedfs/weed/glog" + "github.com/chrislusf/seaweedfs/weed/iamapi" + "github.com/chrislusf/seaweedfs/weed/pb" + "github.com/chrislusf/seaweedfs/weed/pb/filer_pb" + "github.com/chrislusf/seaweedfs/weed/security" + "github.com/chrislusf/seaweedfs/weed/util" + "github.com/gorilla/mux" + "time" +) + +var ( + iamStandaloneOptions IamOptions +) + +type IamOptions struct { + filer *string + masters *string + port *int +} + +func init() { + cmdIam.Run = runIam // break init cycle + iamStandaloneOptions.filer = cmdIam.Flag.String("filer", "localhost:8888", "filer server address") + iamStandaloneOptions.masters = cmdIam.Flag.String("master", "localhost:9333", "comma-separated master servers") + iamStandaloneOptions.port = cmdIam.Flag.Int("port", 8111, "iam server http listen port") +} + +var cmdIam = &Command{ + UsageLine: "iam [-port=8111] [-filer=] [-masters=,]", + Short: "start a iam API compatible server", + Long: "start a iam API compatible server.", +} + +func runIam(cmd *Command, args []string) bool { + return iamStandaloneOptions.startIamServer() +} + +func (iamopt *IamOptions) startIamServer() bool { + filerGrpcAddress, err := pb.ParseFilerGrpcAddress(*iamopt.filer) + if err != nil { + glog.Fatal(err) + return false + } + + grpcDialOption := security.LoadClientTLS(util.GetViper(), "grpc.client") + for { + err = pb.WithGrpcFilerClient(filerGrpcAddress, grpcDialOption, func(client filer_pb.SeaweedFilerClient) error { + resp, err := client.GetFilerConfiguration(context.Background(), &filer_pb.GetFilerConfigurationRequest{}) + if err != nil { + return fmt.Errorf("get filer %s configuration: %v", filerGrpcAddress, err) + } + glog.V(0).Infof("IAM read filer configuration: %s", resp) + return nil + }) + if err != nil { + glog.V(0).Infof("wait to connect to filer %s grpc address %s", *iamopt.filer, filerGrpcAddress) + time.Sleep(time.Second) + } else { + glog.V(0).Infof("connected to filer %s grpc address %s", *iamopt.filer, filerGrpcAddress) + break + } + } + + router := mux.NewRouter().SkipClean(true) + _, iamApiServer_err := iamapi.NewIamApiServer(router, &iamapi.IamServerOption{ + Filer: *iamopt.filer, + Port: *iamopt.port, + FilerGrpcAddress: filerGrpcAddress, + GrpcDialOption: grpcDialOption, + }) + glog.V(0).Info("NewIamApiServer created") + if iamApiServer_err != nil { + glog.Fatalf("IAM API Server startup error: %v", iamApiServer_err) + } + + httpS := &http.Server{Handler: router} + + listenAddress := fmt.Sprintf(":%d", *iamopt.port) + iamApiListener, err := util.NewListener(listenAddress, time.Duration(10)*time.Second) + if err != nil { + glog.Fatalf("IAM API Server listener on %s error: %v", listenAddress, err) + } + + glog.V(0).Infof("Start Seaweed IAM API Server %s at http port %d", util.Version(), *iamopt.port) + if err = httpS.Serve(iamApiListener); err != nil { + glog.Fatalf("IAM API Server Fail to serve: %v", err) + } + + return true +} diff --git a/weed/iamapi/iamapi_handlers.go b/weed/iamapi/iamapi_handlers.go new file mode 100644 index 000000000..c436ba998 --- /dev/null +++ b/weed/iamapi/iamapi_handlers.go @@ -0,0 +1,81 @@ +package iamapi + +import ( + "bytes" + "encoding/xml" + "fmt" + "strconv" + + "net/http" + "net/url" + "time" + + "github.com/chrislusf/seaweedfs/weed/glog" + "github.com/chrislusf/seaweedfs/weed/s3api/s3err" +) + +type mimeType string + +const ( + mimeNone mimeType = "" + mimeXML mimeType = "application/xml" +) + +func setCommonHeaders(w http.ResponseWriter) { + w.Header().Set("x-amz-request-id", fmt.Sprintf("%d", time.Now().UnixNano())) + w.Header().Set("Accept-Ranges", "bytes") +} + +// Encodes the response headers into XML format. +func encodeResponse(response interface{}) []byte { + var bytesBuffer bytes.Buffer + bytesBuffer.WriteString(xml.Header) + e := xml.NewEncoder(&bytesBuffer) + e.Encode(response) + return bytesBuffer.Bytes() +} + +// If none of the http routes match respond with MethodNotAllowed +func notFoundHandler(w http.ResponseWriter, r *http.Request) { + glog.V(0).Infof("unsupported %s %s", r.Method, r.RequestURI) + writeErrorResponse(w, s3err.ErrMethodNotAllowed, r.URL) +} + +func writeErrorResponse(w http.ResponseWriter, errorCode s3err.ErrorCode, reqURL *url.URL) { + apiError := s3err.GetAPIError(errorCode) + errorResponse := getRESTErrorResponse(apiError, reqURL.Path) + encodedErrorResponse := encodeResponse(errorResponse) + writeResponse(w, apiError.HTTPStatusCode, encodedErrorResponse, mimeXML) +} + +func getRESTErrorResponse(err s3err.APIError, resource string) s3err.RESTErrorResponse { + return s3err.RESTErrorResponse{ + Code: err.Code, + Message: err.Description, + Resource: resource, + RequestID: fmt.Sprintf("%d", time.Now().UnixNano()), + } +} + +func writeResponse(w http.ResponseWriter, statusCode int, response []byte, mType mimeType) { + setCommonHeaders(w) + if response != nil { + w.Header().Set("Content-Length", strconv.Itoa(len(response))) + } + if mType != mimeNone { + w.Header().Set("Content-Type", string(mType)) + } + w.WriteHeader(statusCode) + if response != nil { + glog.V(4).Infof("status %d %s: %s", statusCode, mType, string(response)) + _, err := w.Write(response) + if err != nil { + glog.V(0).Infof("write err: %v", err) + } + w.(http.Flusher).Flush() + } +} + +func writeSuccessResponseXML(w http.ResponseWriter, response []byte) { + writeResponse(w, http.StatusOK, response, mimeXML) +} diff --git a/weed/iamapi/iamapi_management_handlers.go b/weed/iamapi/iamapi_management_handlers.go new file mode 100644 index 000000000..a46a506f1 --- /dev/null +++ b/weed/iamapi/iamapi_management_handlers.go @@ -0,0 +1,69 @@ +package iamapi + +import ( + "encoding/xml" + "github.com/chrislusf/seaweedfs/weed/glog" + "github.com/chrislusf/seaweedfs/weed/pb/iam_pb" + "github.com/chrislusf/seaweedfs/weed/s3api/s3err" + "net/http" + "net/url" + + // "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/iam" +) + +const ( + version = "2010-05-08" +) + +type ListUsersResponse struct { + XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ ListUsersResponse"` + ListUsersResult struct { + Users []*iam.User `xml:"Users>member"` + IsTruncated bool `xml:"IsTruncated"` + } `xml:"ListUsersResult"` + ResponseMetadata struct { + RequestId string `xml:"RequestId"` + } `xml:"ResponseMetadata"` +} + +// {'Action': 'CreateUser', 'Version': '2010-05-08', 'UserName': 'Bob'} +// {'Action': 'ListUsers', 'Version': '2010-05-08'} +func (iama *IamApiServer) ListUsers(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) ListUsersResponse { + glog.Info("Do ListUsers") + resp := ListUsersResponse{} + for _, ident := range s3cfg.Identities { + resp.ListUsersResult.Users = append(resp.ListUsersResult.Users, &iam.User{UserName: &ident.Name}) + } + return resp +} + +func (iama *IamApiServer) ListAccessKeys(values url.Values) ListUsersResponse { + return ListUsersResponse{} +} + +func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { + if err := r.ParseForm(); err != nil { + writeErrorResponse(w, s3err.ErrInvalidRequest, r.URL) + return + } + values := r.PostForm + s3cfg := &iam_pb.S3ApiConfiguration{} + if err := iama.GetS3ApiConfiguration(s3cfg); err != nil { + writeErrorResponse(w, s3err.ErrInternalError, r.URL) + return + } + + glog.Info("values ", values) + var response interface{} + switch r.Form.Get("Action") { + case "ListUsers": + response = iama.ListUsers(s3cfg, values) + case "ListAccessKeys": + response = iama.ListAccessKeys(values) + default: + writeErrorResponse(w, s3err.ErrNotImplemented, r.URL) + return + } + writeSuccessResponseXML(w, encodeResponse(response)) +} diff --git a/weed/iamapi/iamapi_server.go b/weed/iamapi/iamapi_server.go new file mode 100644 index 000000000..00c4a69a2 --- /dev/null +++ b/weed/iamapi/iamapi_server.go @@ -0,0 +1,72 @@ +package iamapi + +// https://docs.aws.amazon.com/cli/latest/reference/iam/list-roles.html +// https://docs.aws.amazon.com/IAM/latest/APIReference/API_CreateRole.html + +import ( + "bytes" + "github.com/chrislusf/seaweedfs/weed/filer" + "github.com/chrislusf/seaweedfs/weed/pb" + "github.com/chrislusf/seaweedfs/weed/pb/filer_pb" + "github.com/chrislusf/seaweedfs/weed/pb/iam_pb" + "github.com/chrislusf/seaweedfs/weed/wdclient" + "github.com/gorilla/mux" + "google.golang.org/grpc" + "net/http" + "strings" +) + +type IamServerOption struct { + Masters string + Filer string + Port int + FilerGrpcAddress string + GrpcDialOption grpc.DialOption +} + +type IamApiServer struct { + option *IamServerOption + masterClient *wdclient.MasterClient + filerclient *filer_pb.SeaweedFilerClient +} + +func NewIamApiServer(router *mux.Router, option *IamServerOption) (iamApiServer *IamApiServer, err error) { + iamApiServer = &IamApiServer{ + option: option, + masterClient: wdclient.NewMasterClient(option.GrpcDialOption, pb.AdminShellClient, "", 0, "", strings.Split(option.Masters, ",")), + } + + iamApiServer.registerRouter(router) + + return iamApiServer, nil +} + +func (iama *IamApiServer) registerRouter(router *mux.Router) { + // API Router + apiRouter := router.PathPrefix("/").Subrouter() + // ListBuckets + + // apiRouter.Methods("GET").Path("/").HandlerFunc(track(s3a.iam.Auth(s3a.ListBucketsHandler, ACTION_ADMIN), "LIST")) + apiRouter.Path("/").Methods("POST").HandlerFunc(iama.DoActions) + // NotFound + apiRouter.NotFoundHandler = http.HandlerFunc(notFoundHandler) +} + +func (iama *IamApiServer) GetS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) { + var buf bytes.Buffer + err = pb.WithGrpcFilerClient(iama.option.FilerGrpcAddress, iama.option.GrpcDialOption, func(client filer_pb.SeaweedFilerClient) error { + if err = filer.ReadEntry(iama.masterClient, client, filer.IamConfigDirecotry, filer.IamIdentityFile, &buf); err != nil { + return err + } + return nil + }) + if err != nil { + return err + } + if buf.Len() > 0 { + if err = filer.ParseS3ConfigurationFromBytes(buf.Bytes(), s3cfg); err != nil { + return err + } + } + return nil +} From d7719d0542f58d9f12448b8bfe43c4ebc0374a17 Mon Sep 17 00:00:00 2001 From: Konstantin Lebedev Date: Thu, 10 Dec 2020 00:03:14 +0500 Subject: [PATCH 002/128] base handlers --- weed/iamapi/iamapi_management_handlers.go | 167 ++++++++++++++++++++-- 1 file changed, 155 insertions(+), 12 deletions(-) diff --git a/weed/iamapi/iamapi_management_handlers.go b/weed/iamapi/iamapi_management_handlers.go index a46a506f1..2347c9a80 100644 --- a/weed/iamapi/iamapi_management_handlers.go +++ b/weed/iamapi/iamapi_management_handlers.go @@ -2,44 +2,179 @@ package iamapi import ( "encoding/xml" + "fmt" "github.com/chrislusf/seaweedfs/weed/glog" "github.com/chrislusf/seaweedfs/weed/pb/iam_pb" "github.com/chrislusf/seaweedfs/weed/s3api/s3err" + "math/rand" "net/http" "net/url" + "time" // "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/iam" ) const ( - version = "2010-05-08" + version = "2010-05-08" + charsetUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + charset = charsetUpper + "abcdefghijklmnopqrstuvwxyz/" ) +var seededRand *rand.Rand = rand.New( + rand.NewSource(time.Now().UnixNano())) + +type Response interface { + SetRequestId() +} + +type CommonResponse struct { + ResponseMetadata struct { + RequestId string `xml:"RequestId"` + } `xml:"ResponseMetadata"` +} + type ListUsersResponse struct { + CommonResponse XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ ListUsersResponse"` ListUsersResult struct { Users []*iam.User `xml:"Users>member"` IsTruncated bool `xml:"IsTruncated"` } `xml:"ListUsersResult"` - ResponseMetadata struct { - RequestId string `xml:"RequestId"` - } `xml:"ResponseMetadata"` } -// {'Action': 'CreateUser', 'Version': '2010-05-08', 'UserName': 'Bob'} -// {'Action': 'ListUsers', 'Version': '2010-05-08'} -func (iama *IamApiServer) ListUsers(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) ListUsersResponse { - glog.Info("Do ListUsers") - resp := ListUsersResponse{} +type ListAccessKeysResponse struct { + CommonResponse + XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ ListAccessKeysResponse"` + ListAccessKeysResult struct { + AccessKeyMetadata []*iam.AccessKeyMetadata `xml:"AccessKeyMetadata>member"` + IsTruncated bool `xml:"IsTruncated"` + } `xml:"ListAccessKeysResult"` +} + +type DeleteUserResponse struct { + CommonResponse + XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ DeleteUserResponse"` +} + +type DeleteAccessKeyResponse struct { + CommonResponse + XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ DeleteAccessKeyResponse"` +} + +type CreateUserResponse struct { + CommonResponse + XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ CreateUserResponse"` + CreateUserResult struct { + User iam.User `xml:"User"` + } `xml:"CreateUserResult"` +} + +type CreateAccessKeyResponse struct { + CommonResponse + XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ CreateAccessKeyResponse"` + CreateAccessKeyResult struct { + AccessKey iam.AccessKey `xml:"AccessKey"` + } `xml:"CreateAccessKeyResult"` +} + +func (r *CommonResponse) SetRequestId() { + r.ResponseMetadata.RequestId = fmt.Sprintf("%d", time.Now().UnixNano()) +} + +func StringWithCharset(length int, charset string) string { + b := make([]byte, length) + for i := range b { + b[i] = charset[seededRand.Intn(len(charset))] + } + return string(b) +} + +func (iama *IamApiServer) ListUsers(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp ListUsersResponse) { for _, ident := range s3cfg.Identities { resp.ListUsersResult.Users = append(resp.ListUsersResult.Users, &iam.User{UserName: &ident.Name}) } return resp } -func (iama *IamApiServer) ListAccessKeys(values url.Values) ListUsersResponse { - return ListUsersResponse{} +func (iama *IamApiServer) ListAccessKeys(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp ListAccessKeysResponse) { + status := iam.StatusTypeActive + for _, ident := range s3cfg.Identities { + for _, cred := range ident.Credentials { + resp.ListAccessKeysResult.AccessKeyMetadata = append(resp.ListAccessKeysResult.AccessKeyMetadata, + &iam.AccessKeyMetadata{UserName: &ident.Name, AccessKeyId: &cred.AccessKey, Status: &status}, + ) + } + } + return resp +} + +func (iama *IamApiServer) CreateUser(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp CreateUserResponse) { + userName := values.Get("UserName") + resp.CreateUserResult.User.UserName = &userName + s3cfg.Identities = append(s3cfg.Identities, &iam_pb.Identity{Name: userName}) + return resp +} + +func (iama *IamApiServer) DeleteUser(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp DeleteUserResponse) { + userName := values.Get("UserName") + for i, ident := range s3cfg.Identities { + if userName == ident.Name { + ident.Credentials = append(ident.Credentials[:i], ident.Credentials[i+1:]...) + break + } + } + return resp +} + +func (iama *IamApiServer) CreateAccessKey(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp CreateAccessKeyResponse) { + userName := values.Get("UserName") + status := iam.StatusTypeActive + accessKeyId := StringWithCharset(21, charsetUpper) + secretAccessKey := StringWithCharset(42, charset) + resp.CreateAccessKeyResult.AccessKey.AccessKeyId = &accessKeyId + resp.CreateAccessKeyResult.AccessKey.SecretAccessKey = &secretAccessKey + resp.CreateAccessKeyResult.AccessKey.UserName = &userName + resp.CreateAccessKeyResult.AccessKey.Status = &status + changed := false + for _, ident := range s3cfg.Identities { + if userName == ident.Name { + ident.Credentials = append(ident.Credentials, + &iam_pb.Credential{AccessKey: accessKeyId, SecretKey: secretAccessKey}) + changed = true + break + } + } + if !changed { + s3cfg.Identities = append(s3cfg.Identities, + &iam_pb.Identity{Name: userName, + Credentials: []*iam_pb.Credential{ + { + AccessKey: accessKeyId, + SecretKey: secretAccessKey, + }, + }, + }, + ) + } + return resp +} + +func (iama *IamApiServer) DeleteAccessKey(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp DeleteAccessKeyResponse) { + userName := values.Get("UserName") + accessKeyId := values.Get("AccessKeyId") + for _, ident := range s3cfg.Identities { + if userName == ident.Name { + for i, cred := range ident.Credentials { + if cred.AccessKey == accessKeyId { + ident.Credentials = append(ident.Credentials[:i], ident.Credentials[i+1:]...) + break + } + } + break + } + } + return resp } func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { @@ -60,7 +195,15 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { case "ListUsers": response = iama.ListUsers(s3cfg, values) case "ListAccessKeys": - response = iama.ListAccessKeys(values) + response = iama.ListAccessKeys(s3cfg, values) + case "CreateUser": + response = iama.CreateUser(s3cfg, values) + case "DeleteUser": + response = iama.DeleteUser(s3cfg, values) + case "CreateAccessKey": + response = iama.CreateAccessKey(s3cfg, values) + case "DeleteAccessKey": + response = iama.DeleteAccessKey(s3cfg, values) default: writeErrorResponse(w, s3err.ErrNotImplemented, r.URL) return From 82b0463fac817d434fd9272d2745bd4c2fe75329 Mon Sep 17 00:00:00 2001 From: Konstantin Lebedev Date: Thu, 10 Dec 2020 15:36:04 +0500 Subject: [PATCH 003/128] handler PutUserPolicy https://docs.aws.amazon.com/IAM/latest/APIReference/API_PutUserPolicy.html --- weed/iamapi/iamapi_management_handlers.go | 143 +++++++++++++++++++++- 1 file changed, 139 insertions(+), 4 deletions(-) diff --git a/weed/iamapi/iamapi_management_handlers.go b/weed/iamapi/iamapi_management_handlers.go index 2347c9a80..22bc8748a 100644 --- a/weed/iamapi/iamapi_management_handlers.go +++ b/weed/iamapi/iamapi_management_handlers.go @@ -1,14 +1,18 @@ package iamapi import ( + "crypto/sha1" + "encoding/json" "encoding/xml" "fmt" "github.com/chrislusf/seaweedfs/weed/glog" "github.com/chrislusf/seaweedfs/weed/pb/iam_pb" + "github.com/chrislusf/seaweedfs/weed/s3api/s3_constants" "github.com/chrislusf/seaweedfs/weed/s3api/s3err" "math/rand" "net/http" "net/url" + "strings" "time" // "github.com/aws/aws-sdk-go/aws" @@ -21,11 +25,19 @@ const ( charset = charsetUpper + "abcdefghijklmnopqrstuvwxyz/" ) -var seededRand *rand.Rand = rand.New( - rand.NewSource(time.Now().UnixNano())) +var ( + seededRand *rand.Rand = rand.New( + rand.NewSource(time.Now().UnixNano())) + policyDocuments = map[string]*PolicyDocument{} +) -type Response interface { - SetRequestId() +type PolicyDocument struct { + Version string `json:"Version"` + Statement []struct { + Effect string `json:"Effect"` + Action []string `json:"Action"` + Resource []string `json:"Resource"` + } `json:"Statement"` } type CommonResponse struct { @@ -62,6 +74,14 @@ type DeleteAccessKeyResponse struct { XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ DeleteAccessKeyResponse"` } +type CreatePolicyResponse struct { + CommonResponse + XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ CreatePolicyResponse"` + CreatePolicyResult struct { + Policy iam.Policy `xml:"Policy"` + } `xml:"CreatePolicyResult"` +} + type CreateUserResponse struct { CommonResponse XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ CreateUserResponse"` @@ -78,10 +98,21 @@ type CreateAccessKeyResponse struct { } `xml:"CreateAccessKeyResult"` } +type PutUserPolicyResponse struct { + CommonResponse + XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ PutUserPolicyResponse"` +} + func (r *CommonResponse) SetRequestId() { r.ResponseMetadata.RequestId = fmt.Sprintf("%d", time.Now().UnixNano()) } +func Hash(s *string) string { + h := sha1.New() + h.Write([]byte(*s)) + return fmt.Sprintf("%x", h.Sum(nil)) +} + func StringWithCharset(length int, charset string) string { b := make([]byte, length) for i := range b { @@ -115,6 +146,96 @@ func (iama *IamApiServer) CreateUser(s3cfg *iam_pb.S3ApiConfiguration, values ur s3cfg.Identities = append(s3cfg.Identities, &iam_pb.Identity{Name: userName}) return resp } +func GetPolicyDocument(policy *string) (policyDocument PolicyDocument, err error) { + if err = json.Unmarshal([]byte(*policy), &policyDocument); err != nil { + return PolicyDocument{}, err + } + return policyDocument, err +} + +func (iama *IamApiServer) CreatePolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp CreatePolicyResponse, err error) { + policyName := values.Get("PolicyName") + policyDocumentString := values.Get("PolicyDocument") + policyDocument, err := GetPolicyDocument(&policyDocumentString) + if err != nil { + return CreatePolicyResponse{}, err + } + policyId := Hash(&policyDocumentString) + arn := fmt.Sprintf("arn:aws:iam:::policy/%s", policyName) + resp.CreatePolicyResult.Policy.PolicyName = &policyName + resp.CreatePolicyResult.Policy.Arn = &arn + resp.CreatePolicyResult.Policy.PolicyId = &policyId + policyDocuments[policyName] = &policyDocument + return resp, nil +} + +func (iama *IamApiServer) PutUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp PutUserPolicyResponse, err error) { + userName := values.Get("UserName") + policyName := values.Get("PolicyName") + policyDocumentString := values.Get("PolicyDocument") + policyDocument, err := GetPolicyDocument(&policyDocumentString) + if err != nil { + return PutUserPolicyResponse{}, err + } + policyDocuments[policyName] = &policyDocument + actions := GetActions(&policyDocument) + for _, ident := range s3cfg.Identities { + if userName == ident.Name { + for _, action := range actions { + ident.Actions = append(ident.Actions, action) + } + break + } + } + return resp, nil +} + +func MapAction(action string) string { + switch action { + case "*": + return s3_constants.ACTION_ADMIN + case "Put*": + return s3_constants.ACTION_WRITE + case "Get*": + return s3_constants.ACTION_READ + case "List*": + return s3_constants.ACTION_LIST + default: + return s3_constants.ACTION_TAGGING + } +} + +func GetActions(policy *PolicyDocument) (actions []string) { + for _, statement := range policy.Statement { + if statement.Effect != "Allow" { + continue + } + for _, resource := range statement.Resource { + // Parse "arn:aws:s3:::my-bucket/shared/*" + res := strings.Split(resource, ":") + if len(res) != 6 || res[0] != "arn:" || res[1] != "aws" || res[2] != "s3" { + continue + } + for _, action := range statement.Action { + // Parse "s3:Get*" + act := strings.Split(action, ":") + if len(act) != 2 || act[0] != "s3" { + continue + } + if res[5] == "*" { + actions = append(actions, MapAction(act[1])) + continue + } + // Parse my-bucket/shared/* + path := strings.Split(res[5], "/") + if len(path) != 2 || path[1] != "*" { + actions = append(actions, fmt.Sprintf("%s:%s", MapAction(act[1]), path[0])) + } + } + } + } + return actions +} func (iama *IamApiServer) DeleteUser(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp DeleteUserResponse) { userName := values.Get("UserName") @@ -204,6 +325,20 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { response = iama.CreateAccessKey(s3cfg, values) case "DeleteAccessKey": response = iama.DeleteAccessKey(s3cfg, values) + case "CreatePolicy": + var err error + response, err = iama.CreatePolicy(s3cfg, values) + if err != nil { + writeErrorResponse(w, s3err.ErrInvalidRequest, r.URL) + return + } + case "PutUserPolicy": + var err error + response, err = iama.PutUserPolicy(s3cfg, values) + if err != nil { + writeErrorResponse(w, s3err.ErrInvalidRequest, r.URL) + return + } default: writeErrorResponse(w, s3err.ErrNotImplemented, r.URL) return From 9f26f2815c756d2125bf35032058ddd0d0efba1d Mon Sep 17 00:00:00 2001 From: Konstantin Lebedev Date: Thu, 10 Dec 2020 17:03:55 +0500 Subject: [PATCH 004/128] SaveAs S3 Configuration --- weed/filer/read_write.go | 8 ++++-- weed/iamapi/iamapi_management_handlers.go | 33 +++++++++++++++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/weed/filer/read_write.go b/weed/filer/read_write.go index 7a6da3beb..bf9159b41 100644 --- a/weed/filer/read_write.go +++ b/weed/filer/read_write.go @@ -41,8 +41,12 @@ func ReadContent(filerAddress string, dir, name string) ([]byte, error) { } func SaveAs(host string, port int, dir, name string, contentType string, byteBuffer *bytes.Buffer) error { - - target := fmt.Sprintf("http://%s:%d%s/%s", host, port, dir, name) + var target string + if port == 0 { + target = fmt.Sprintf("http://%s%s/%s", host, dir, name) + } else { + target = fmt.Sprintf("http://%s:%d%s/%s", host, port, dir, name) + } // set the HTTP method, url, and request body req, err := http.NewRequest(http.MethodPut, target, byteBuffer) diff --git a/weed/iamapi/iamapi_management_handlers.go b/weed/iamapi/iamapi_management_handlers.go index 22bc8748a..4316f0fd5 100644 --- a/weed/iamapi/iamapi_management_handlers.go +++ b/weed/iamapi/iamapi_management_handlers.go @@ -1,10 +1,12 @@ package iamapi import ( + "bytes" "crypto/sha1" "encoding/json" "encoding/xml" "fmt" + "github.com/chrislusf/seaweedfs/weed/filer" "github.com/chrislusf/seaweedfs/weed/glog" "github.com/chrislusf/seaweedfs/weed/pb/iam_pb" "github.com/chrislusf/seaweedfs/weed/s3api/s3_constants" @@ -213,13 +215,15 @@ func GetActions(policy *PolicyDocument) (actions []string) { for _, resource := range statement.Resource { // Parse "arn:aws:s3:::my-bucket/shared/*" res := strings.Split(resource, ":") - if len(res) != 6 || res[0] != "arn:" || res[1] != "aws" || res[2] != "s3" { + if len(res) != 6 || res[0] != "arn" || res[1] != "aws" || res[2] != "s3" { + glog.Infof("not math resource: %s", res) continue } for _, action := range statement.Action { // Parse "s3:Get*" act := strings.Split(action, ":") if len(act) != 2 || act[0] != "s3" { + glog.Infof("not match action: %s", act) continue } if res[5] == "*" { @@ -229,8 +233,11 @@ func GetActions(policy *PolicyDocument) (actions []string) { // Parse my-bucket/shared/* path := strings.Split(res[5], "/") if len(path) != 2 || path[1] != "*" { - actions = append(actions, fmt.Sprintf("%s:%s", MapAction(act[1]), path[0])) + glog.Infof("not match bucket: %s", path) + continue } + actions = append(actions, fmt.Sprintf("%s:%s", MapAction(act[1]), path[0])) + } } } @@ -312,11 +319,14 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { glog.Info("values ", values) var response interface{} + changed := true switch r.Form.Get("Action") { case "ListUsers": response = iama.ListUsers(s3cfg, values) + changed = false case "ListAccessKeys": response = iama.ListAccessKeys(s3cfg, values) + changed = false case "CreateUser": response = iama.CreateUser(s3cfg, values) case "DeleteUser": @@ -343,5 +353,24 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { writeErrorResponse(w, s3err.ErrNotImplemented, r.URL) return } + if changed { + buf := bytes.Buffer{} + if err := filer.S3ConfigurationToText(&buf, s3cfg); err != nil { + glog.Error("S3ConfigurationToText: ", err) + writeErrorResponse(w, s3err.ErrInternalError, r.URL) + return + } + if err := filer.SaveAs( + iama.option.Filer, + 0, + filer.IamConfigDirecotry, + filer.IamIdentityFile, + "text/plain; charset=utf-8", + &buf); err != nil { + glog.Error("SaveAs: ", err) + writeErrorResponse(w, s3err.ErrInternalError, r.URL) + return + } + } writeSuccessResponseXML(w, encodeResponse(response)) } From 40938d6a47136a47f612d7a4c93862ff5e17ec45 Mon Sep 17 00:00:00 2001 From: Konstantin Lebedev Date: Thu, 10 Dec 2020 21:03:25 +0500 Subject: [PATCH 005/128] SaveInsideFiler S3 Configuration --- weed/iamapi/iamapi_management_handlers.go | 24 +++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/weed/iamapi/iamapi_management_handlers.go b/weed/iamapi/iamapi_management_handlers.go index 4316f0fd5..322c16d73 100644 --- a/weed/iamapi/iamapi_management_handlers.go +++ b/weed/iamapi/iamapi_management_handlers.go @@ -8,6 +8,8 @@ import ( "fmt" "github.com/chrislusf/seaweedfs/weed/filer" "github.com/chrislusf/seaweedfs/weed/glog" + "github.com/chrislusf/seaweedfs/weed/pb" + "github.com/chrislusf/seaweedfs/weed/pb/filer_pb" "github.com/chrislusf/seaweedfs/weed/pb/iam_pb" "github.com/chrislusf/seaweedfs/weed/s3api/s3_constants" "github.com/chrislusf/seaweedfs/weed/s3api/s3err" @@ -216,7 +218,7 @@ func GetActions(policy *PolicyDocument) (actions []string) { // Parse "arn:aws:s3:::my-bucket/shared/*" res := strings.Split(resource, ":") if len(res) != 6 || res[0] != "arn" || res[1] != "aws" || res[2] != "s3" { - glog.Infof("not math resource: %s", res) + glog.Infof("not match resource: %s", res) continue } for _, action := range statement.Action { @@ -237,7 +239,6 @@ func GetActions(policy *PolicyDocument) (actions []string) { continue } actions = append(actions, fmt.Sprintf("%s:%s", MapAction(act[1]), path[0])) - } } } @@ -360,14 +361,17 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { writeErrorResponse(w, s3err.ErrInternalError, r.URL) return } - if err := filer.SaveAs( - iama.option.Filer, - 0, - filer.IamConfigDirecotry, - filer.IamIdentityFile, - "text/plain; charset=utf-8", - &buf); err != nil { - glog.Error("SaveAs: ", err) + err := pb.WithGrpcFilerClient( + iama.option.FilerGrpcAddress, + iama.option.GrpcDialOption, + func(client filer_pb.SeaweedFilerClient) error { + if err := filer.SaveInsideFiler(client, filer.IamConfigDirecotry, filer.IamIdentityFile, buf.Bytes()); err != nil { + return err + } + return nil + }, + ) + if err != nil { writeErrorResponse(w, s3err.ErrInternalError, r.URL) return } From 8a95f9c10c3d4c9e1f6761f5620da3e5253398f5 Mon Sep 17 00:00:00 2001 From: Konstantin Lebedev Date: Mon, 29 Mar 2021 12:01:44 +0500 Subject: [PATCH 006/128] iam GetUser --- weed/command/filer.go | 25 ++++- weed/iamapi/iamapi_handlers.go | 18 ++++ weed/iamapi/iamapi_management_handlers.go | 119 +++++++--------------- weed/iamapi/iamapi_response.go | 93 +++++++++++++++++ 4 files changed, 167 insertions(+), 88 deletions(-) create mode 100644 weed/iamapi/iamapi_response.go diff --git a/weed/command/filer.go b/weed/command/filer.go index 534bd9e04..b256e385f 100644 --- a/weed/command/filer.go +++ b/weed/command/filer.go @@ -25,6 +25,8 @@ var ( filerS3Options S3Options filerStartWebDav *bool filerWebDavOptions WebDavOption + filerStartIam *bool + filerIamOptions IamOptions ) type FilerOptions struct { @@ -89,6 +91,10 @@ func init() { filerWebDavOptions.tlsCertificate = cmdFiler.Flag.String("webdav.cert.file", "", "path to the TLS certificate file") filerWebDavOptions.cacheDir = cmdFiler.Flag.String("webdav.cacheDir", os.TempDir(), "local cache directory for file chunks") filerWebDavOptions.cacheSizeMB = cmdFiler.Flag.Int64("webdav.cacheCapacityMB", 1000, "local cache capacity in MB") + + // start iam on filer + filerStartIam = cmdFiler.Flag.Bool("iam", false, "whether to start IAM service") + filerIamOptions.port = cmdFiler.Flag.Int("iam.port", 8111, "iam server http listen port") } var cmdFiler = &Command{ @@ -119,22 +125,33 @@ func runFiler(cmd *Command, args []string) bool { go stats_collect.StartMetricsServer(*f.metricsHttpPort) + filerAddress := fmt.Sprintf("%s:%d", *f.ip, *f.port) + startDelay := time.Duration(2) if *filerStartS3 { - filerAddress := fmt.Sprintf("%s:%d", *f.ip, *f.port) filerS3Options.filer = &filerAddress go func() { - time.Sleep(2 * time.Second) + time.Sleep(startDelay * time.Second) filerS3Options.startS3Server() }() + startDelay++ } if *filerStartWebDav { - filerAddress := fmt.Sprintf("%s:%d", *f.ip, *f.port) filerWebDavOptions.filer = &filerAddress go func() { - time.Sleep(2 * time.Second) + time.Sleep(startDelay * time.Second) filerWebDavOptions.startWebDav() }() + startDelay++ + } + + if *filerStartIam { + filerIamOptions.filer = &filerAddress + filerIamOptions.masters = f.masters + go func() { + time.Sleep(startDelay * time.Second) + filerIamOptions.startIamServer() + }() } f.startFiler() diff --git a/weed/iamapi/iamapi_handlers.go b/weed/iamapi/iamapi_handlers.go index c436ba998..962717ad9 100644 --- a/weed/iamapi/iamapi_handlers.go +++ b/weed/iamapi/iamapi_handlers.go @@ -12,6 +12,8 @@ import ( "github.com/chrislusf/seaweedfs/weed/glog" "github.com/chrislusf/seaweedfs/weed/s3api/s3err" + + "github.com/aws/aws-sdk-go/service/iam" ) type mimeType string @@ -48,6 +50,22 @@ func writeErrorResponse(w http.ResponseWriter, errorCode s3err.ErrorCode, reqURL writeResponse(w, apiError.HTTPStatusCode, encodedErrorResponse, mimeXML) } +func writeIamErrorResponse(w http.ResponseWriter, err error, object string, value string) { + errCode := err.Error() + errorResp := ErrorResponse{} + errorResp.Error.Type = "Sender" + errorResp.Error.Code = &errCode + switch errCode { + case iam.ErrCodeNoSuchEntityException: + msg := fmt.Sprintf("The %s with name %s cannot be found.", object, value) + errorResp.Error.Message = &msg + writeResponse(w, http.StatusNotFound, encodeResponse(errorResp), mimeXML) + default: + writeResponse(w, http.StatusInternalServerError, encodeResponse(errorResp), mimeXML) + + } +} + func getRESTErrorResponse(err s3err.APIError, resource string) s3err.RESTErrorResponse { return s3err.RESTErrorResponse{ Code: err.Code, diff --git a/weed/iamapi/iamapi_management_handlers.go b/weed/iamapi/iamapi_management_handlers.go index 322c16d73..e4daa081f 100644 --- a/weed/iamapi/iamapi_management_handlers.go +++ b/weed/iamapi/iamapi_management_handlers.go @@ -4,7 +4,6 @@ import ( "bytes" "crypto/sha1" "encoding/json" - "encoding/xml" "fmt" "github.com/chrislusf/seaweedfs/weed/filer" "github.com/chrislusf/seaweedfs/weed/glog" @@ -19,12 +18,10 @@ import ( "strings" "time" - // "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/iam" ) const ( - version = "2010-05-08" charsetUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" charset = charsetUpper + "abcdefghijklmnopqrstuvwxyz/" ) @@ -44,73 +41,6 @@ type PolicyDocument struct { } `json:"Statement"` } -type CommonResponse struct { - ResponseMetadata struct { - RequestId string `xml:"RequestId"` - } `xml:"ResponseMetadata"` -} - -type ListUsersResponse struct { - CommonResponse - XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ ListUsersResponse"` - ListUsersResult struct { - Users []*iam.User `xml:"Users>member"` - IsTruncated bool `xml:"IsTruncated"` - } `xml:"ListUsersResult"` -} - -type ListAccessKeysResponse struct { - CommonResponse - XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ ListAccessKeysResponse"` - ListAccessKeysResult struct { - AccessKeyMetadata []*iam.AccessKeyMetadata `xml:"AccessKeyMetadata>member"` - IsTruncated bool `xml:"IsTruncated"` - } `xml:"ListAccessKeysResult"` -} - -type DeleteUserResponse struct { - CommonResponse - XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ DeleteUserResponse"` -} - -type DeleteAccessKeyResponse struct { - CommonResponse - XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ DeleteAccessKeyResponse"` -} - -type CreatePolicyResponse struct { - CommonResponse - XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ CreatePolicyResponse"` - CreatePolicyResult struct { - Policy iam.Policy `xml:"Policy"` - } `xml:"CreatePolicyResult"` -} - -type CreateUserResponse struct { - CommonResponse - XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ CreateUserResponse"` - CreateUserResult struct { - User iam.User `xml:"User"` - } `xml:"CreateUserResult"` -} - -type CreateAccessKeyResponse struct { - CommonResponse - XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ CreateAccessKeyResponse"` - CreateAccessKeyResult struct { - AccessKey iam.AccessKey `xml:"AccessKey"` - } `xml:"CreateAccessKeyResult"` -} - -type PutUserPolicyResponse struct { - CommonResponse - XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ PutUserPolicyResponse"` -} - -func (r *CommonResponse) SetRequestId() { - r.ResponseMetadata.RequestId = fmt.Sprintf("%d", time.Now().UnixNano()) -} - func Hash(s *string) string { h := sha1.New() h.Write([]byte(*s)) @@ -150,6 +80,27 @@ func (iama *IamApiServer) CreateUser(s3cfg *iam_pb.S3ApiConfiguration, values ur s3cfg.Identities = append(s3cfg.Identities, &iam_pb.Identity{Name: userName}) return resp } + +func (iama *IamApiServer) DeleteUser(s3cfg *iam_pb.S3ApiConfiguration, userName string) (resp DeleteUserResponse, err error) { + for i, ident := range s3cfg.Identities { + if userName == ident.Name { + ident.Credentials = append(ident.Credentials[:i], ident.Credentials[i+1:]...) + return resp, nil + } + } + return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException) +} + +func (iama *IamApiServer) GetUser(s3cfg *iam_pb.S3ApiConfiguration, userName string) (resp GetUserResponse, err error) { + for _, ident := range s3cfg.Identities { + if userName == ident.Name { + resp.GetUserResult.User = iam.User{UserName: &ident.Name} + return resp, nil + } + } + return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException) +} + func GetPolicyDocument(policy *string) (policyDocument PolicyDocument, err error) { if err = json.Unmarshal([]byte(*policy), &policyDocument); err != nil { return PolicyDocument{}, err @@ -245,17 +196,6 @@ func GetActions(policy *PolicyDocument) (actions []string) { return actions } -func (iama *IamApiServer) DeleteUser(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp DeleteUserResponse) { - userName := values.Get("UserName") - for i, ident := range s3cfg.Identities { - if userName == ident.Name { - ident.Credentials = append(ident.Credentials[:i], ident.Credentials[i+1:]...) - break - } - } - return resp -} - func (iama *IamApiServer) CreateAccessKey(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp CreateAccessKeyResponse) { userName := values.Get("UserName") status := iam.StatusTypeActive @@ -320,6 +260,7 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { glog.Info("values ", values) var response interface{} + var err error changed := true switch r.Form.Get("Action") { case "ListUsers": @@ -330,21 +271,31 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { changed = false case "CreateUser": response = iama.CreateUser(s3cfg, values) + case "GetUser": + userName := values.Get("UserName") + response, err = iama.GetUser(s3cfg, userName) + if err != nil { + writeIamErrorResponse(w, err, "user", userName) + return + } case "DeleteUser": - response = iama.DeleteUser(s3cfg, values) + userName := values.Get("UserName") + response, err = iama.DeleteUser(s3cfg, userName) + if err != nil { + writeIamErrorResponse(w, err, "user", userName) + return + } case "CreateAccessKey": response = iama.CreateAccessKey(s3cfg, values) case "DeleteAccessKey": response = iama.DeleteAccessKey(s3cfg, values) case "CreatePolicy": - var err error response, err = iama.CreatePolicy(s3cfg, values) if err != nil { writeErrorResponse(w, s3err.ErrInvalidRequest, r.URL) return } case "PutUserPolicy": - var err error response, err = iama.PutUserPolicy(s3cfg, values) if err != nil { writeErrorResponse(w, s3err.ErrInvalidRequest, r.URL) diff --git a/weed/iamapi/iamapi_response.go b/weed/iamapi/iamapi_response.go new file mode 100644 index 000000000..26dd0f263 --- /dev/null +++ b/weed/iamapi/iamapi_response.go @@ -0,0 +1,93 @@ +package iamapi + +import ( + "encoding/xml" + "fmt" + "time" + + "github.com/aws/aws-sdk-go/service/iam" +) + +type CommonResponse struct { + ResponseMetadata struct { + RequestId string `xml:"RequestId"` + } `xml:"ResponseMetadata"` +} + +type ListUsersResponse struct { + CommonResponse + XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ ListUsersResponse"` + ListUsersResult struct { + Users []*iam.User `xml:"Users>member"` + IsTruncated bool `xml:"IsTruncated"` + } `xml:"ListUsersResult"` +} + +type ListAccessKeysResponse struct { + CommonResponse + XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ ListAccessKeysResponse"` + ListAccessKeysResult struct { + AccessKeyMetadata []*iam.AccessKeyMetadata `xml:"AccessKeyMetadata>member"` + IsTruncated bool `xml:"IsTruncated"` + } `xml:"ListAccessKeysResult"` +} + +type DeleteAccessKeyResponse struct { + CommonResponse + XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ DeleteAccessKeyResponse"` +} + +type CreatePolicyResponse struct { + CommonResponse + XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ CreatePolicyResponse"` + CreatePolicyResult struct { + Policy iam.Policy `xml:"Policy"` + } `xml:"CreatePolicyResult"` +} + +type CreateUserResponse struct { + CommonResponse + XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ CreateUserResponse"` + CreateUserResult struct { + User iam.User `xml:"User"` + } `xml:"CreateUserResult"` +} + +type DeleteUserResponse struct { + CommonResponse + XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ DeleteUserResponse"` +} + +type GetUserResponse struct { + CommonResponse + XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ GetUserResponse"` + GetUserResult struct { + User iam.User `xml:"User"` + } `xml:"GetUserResult"` +} + +type CreateAccessKeyResponse struct { + CommonResponse + XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ CreateAccessKeyResponse"` + CreateAccessKeyResult struct { + AccessKey iam.AccessKey `xml:"AccessKey"` + } `xml:"CreateAccessKeyResult"` +} + +type PutUserPolicyResponse struct { + CommonResponse + XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ PutUserPolicyResponse"` +} + +type ErrorResponse struct { + CommonResponse + XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ ErrorResponse"` + Error struct { + iam.ErrorDetails + Type string `xml:"Type"` + } `xml:"Error"` +} + +func (r *CommonResponse) SetRequestId() { + r.ResponseMetadata.RequestId = fmt.Sprintf("%d", time.Now().UnixNano()) +} From 2327c0756bf0d4da8d376377948bcbcc0a553d6e Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 5 Apr 2021 23:24:26 -0700 Subject: [PATCH 007/128] fix to avoid loop --- weed/server/filer_server_handlers_write_upload.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/weed/server/filer_server_handlers_write_upload.go b/weed/server/filer_server_handlers_write_upload.go index 03db942c6..81b2ce1b0 100644 --- a/weed/server/filer_server_handlers_write_upload.go +++ b/weed/server/filer_server_handlers_write_upload.go @@ -74,10 +74,10 @@ func (fs *FilerServer) uploadReaderToChunks(w http.ResponseWriter, r *http.Reque lock.Unlock() // handle read errors if readErr != nil { + if err == nil { + err = readErr + } if readErr != io.EOF { - if err == nil { - err = readErr - } resultsChan <- &ChunkCreationResult{ err: readErr, } @@ -86,6 +86,9 @@ func (fs *FilerServer) uploadReaderToChunks(w http.ResponseWriter, r *http.Reque } if len(data) == 0 { readErr = io.EOF + if err == nil { + err = readErr + } return } @@ -120,6 +123,10 @@ func (fs *FilerServer) uploadReaderToChunks(w http.ResponseWriter, r *http.Reque waitForAllData.Wait() + if err == io.EOF { + err = nil + } + return fileChunks, md5Hash, readOffset, err, nil } From ed79baa30fe5687a35a9a61e2dcf3b4750064d36 Mon Sep 17 00:00:00 2001 From: Konstantin Lebedev Date: Tue, 6 Apr 2021 13:43:08 +0500 Subject: [PATCH 008/128] add tests --- weed/iamapi/iamapi_handlers.go | 1 + weed/iamapi/iamapi_management_handlers.go | 47 +++---- weed/iamapi/iamapi_server.go | 48 +++++-- weed/iamapi/iamapi_test.go | 157 ++++++++++++++++++++++ 4 files changed, 217 insertions(+), 36 deletions(-) create mode 100644 weed/iamapi/iamapi_test.go diff --git a/weed/iamapi/iamapi_handlers.go b/weed/iamapi/iamapi_handlers.go index 962717ad9..fdaf4dd69 100644 --- a/weed/iamapi/iamapi_handlers.go +++ b/weed/iamapi/iamapi_handlers.go @@ -55,6 +55,7 @@ func writeIamErrorResponse(w http.ResponseWriter, err error, object string, valu errorResp := ErrorResponse{} errorResp.Error.Type = "Sender" errorResp.Error.Code = &errCode + glog.Errorf("Response %+v", err) switch errCode { case iam.ErrCodeNoSuchEntityException: msg := fmt.Sprintf("The %s with name %s cannot be found.", object, value) diff --git a/weed/iamapi/iamapi_management_handlers.go b/weed/iamapi/iamapi_management_handlers.go index e4daa081f..470731064 100644 --- a/weed/iamapi/iamapi_management_handlers.go +++ b/weed/iamapi/iamapi_management_handlers.go @@ -1,14 +1,10 @@ package iamapi import ( - "bytes" "crypto/sha1" "encoding/json" "fmt" - "github.com/chrislusf/seaweedfs/weed/filer" "github.com/chrislusf/seaweedfs/weed/glog" - "github.com/chrislusf/seaweedfs/weed/pb" - "github.com/chrislusf/seaweedfs/weed/pb/filer_pb" "github.com/chrislusf/seaweedfs/weed/pb/iam_pb" "github.com/chrislusf/seaweedfs/weed/s3api/s3_constants" "github.com/chrislusf/seaweedfs/weed/s3api/s3err" @@ -16,6 +12,7 @@ import ( "net/http" "net/url" "strings" + "sync" "time" "github.com/aws/aws-sdk-go/service/iam" @@ -32,13 +29,15 @@ var ( policyDocuments = map[string]*PolicyDocument{} ) +type Statement struct { + Effect string `json:"Effect"` + Action []string `json:"Action"` + Resource []string `json:"Resource"` +} + type PolicyDocument struct { - Version string `json:"Version"` - Statement []struct { - Effect string `json:"Effect"` - Action []string `json:"Action"` - Resource []string `json:"Resource"` - } `json:"Statement"` + Version string `json:"Version"` + Statement []*Statement `json:"Statement"` } func Hash(s *string) string { @@ -252,13 +251,16 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { return } values := r.PostForm + var s3cfgLock sync.RWMutex + s3cfgLock.RLock() s3cfg := &iam_pb.S3ApiConfiguration{} - if err := iama.GetS3ApiConfiguration(s3cfg); err != nil { + if err := iama.s3ApiConfig.GetS3ApiConfiguration(s3cfg); err != nil { writeErrorResponse(w, s3err.ErrInternalError, r.URL) return } + s3cfgLock.RUnlock() - glog.Info("values ", values) + glog.V(4).Infof("DoActions: %+v", values) var response interface{} var err error changed := true @@ -292,12 +294,14 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { case "CreatePolicy": response, err = iama.CreatePolicy(s3cfg, values) if err != nil { + glog.Errorf("CreatePolicy: %+v", err) writeErrorResponse(w, s3err.ErrInvalidRequest, r.URL) return } case "PutUserPolicy": response, err = iama.PutUserPolicy(s3cfg, values) if err != nil { + glog.Errorf("PutUserPolicy: %+v", err) writeErrorResponse(w, s3err.ErrInvalidRequest, r.URL) return } @@ -306,22 +310,9 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { return } if changed { - buf := bytes.Buffer{} - if err := filer.S3ConfigurationToText(&buf, s3cfg); err != nil { - glog.Error("S3ConfigurationToText: ", err) - writeErrorResponse(w, s3err.ErrInternalError, r.URL) - return - } - err := pb.WithGrpcFilerClient( - iama.option.FilerGrpcAddress, - iama.option.GrpcDialOption, - func(client filer_pb.SeaweedFilerClient) error { - if err := filer.SaveInsideFiler(client, filer.IamConfigDirecotry, filer.IamIdentityFile, buf.Bytes()); err != nil { - return err - } - return nil - }, - ) + s3cfgLock.Lock() + err := iama.s3ApiConfig.PutS3ApiConfiguration(s3cfg) + s3cfgLock.Unlock() if err != nil { writeErrorResponse(w, s3err.ErrInternalError, r.URL) return diff --git a/weed/iamapi/iamapi_server.go b/weed/iamapi/iamapi_server.go index 00c4a69a2..7698fab71 100644 --- a/weed/iamapi/iamapi_server.go +++ b/weed/iamapi/iamapi_server.go @@ -1,10 +1,10 @@ package iamapi // https://docs.aws.amazon.com/cli/latest/reference/iam/list-roles.html -// https://docs.aws.amazon.com/IAM/latest/APIReference/API_CreateRole.html import ( "bytes" + "fmt" "github.com/chrislusf/seaweedfs/weed/filer" "github.com/chrislusf/seaweedfs/weed/pb" "github.com/chrislusf/seaweedfs/weed/pb/filer_pb" @@ -16,6 +16,16 @@ import ( "strings" ) +type IamS3ApiConfig interface { + GetS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) + PutS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) +} + +type IamS3ApiConfigure struct { + option *IamServerOption + masterClient *wdclient.MasterClient +} + type IamServerOption struct { Masters string Filer string @@ -25,17 +35,22 @@ type IamServerOption struct { } type IamApiServer struct { - option *IamServerOption - masterClient *wdclient.MasterClient - filerclient *filer_pb.SeaweedFilerClient + s3ApiConfig IamS3ApiConfig + filerclient *filer_pb.SeaweedFilerClient } +var s3ApiConfigure IamS3ApiConfig + func NewIamApiServer(router *mux.Router, option *IamServerOption) (iamApiServer *IamApiServer, err error) { - iamApiServer = &IamApiServer{ + s3ApiConfigure = IamS3ApiConfigure{ option: option, masterClient: wdclient.NewMasterClient(option.GrpcDialOption, pb.AdminShellClient, "", 0, "", strings.Split(option.Masters, ",")), } + iamApiServer = &IamApiServer{ + s3ApiConfig: s3ApiConfigure, + } + iamApiServer.registerRouter(router) return iamApiServer, nil @@ -52,10 +67,10 @@ func (iama *IamApiServer) registerRouter(router *mux.Router) { apiRouter.NotFoundHandler = http.HandlerFunc(notFoundHandler) } -func (iama *IamApiServer) GetS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) { +func (iam IamS3ApiConfigure) GetS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) { var buf bytes.Buffer - err = pb.WithGrpcFilerClient(iama.option.FilerGrpcAddress, iama.option.GrpcDialOption, func(client filer_pb.SeaweedFilerClient) error { - if err = filer.ReadEntry(iama.masterClient, client, filer.IamConfigDirecotry, filer.IamIdentityFile, &buf); err != nil { + err = pb.WithGrpcFilerClient(iam.option.FilerGrpcAddress, iam.option.GrpcDialOption, func(client filer_pb.SeaweedFilerClient) error { + if err = filer.ReadEntry(iam.masterClient, client, filer.IamConfigDirecotry, filer.IamIdentityFile, &buf); err != nil { return err } return nil @@ -70,3 +85,20 @@ func (iama *IamApiServer) GetS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration } return nil } + +func (iam IamS3ApiConfigure) PutS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) { + buf := bytes.Buffer{} + if err := filer.S3ConfigurationToText(&buf, s3cfg); err != nil { + return fmt.Errorf("S3ConfigurationToText: %s", err) + } + return pb.WithGrpcFilerClient( + iam.option.FilerGrpcAddress, + iam.option.GrpcDialOption, + func(client filer_pb.SeaweedFilerClient) error { + if err := filer.SaveInsideFiler(client, filer.IamConfigDirecotry, filer.IamIdentityFile, buf.Bytes()); err != nil { + return err + } + return nil + }, + ) +} diff --git a/weed/iamapi/iamapi_test.go b/weed/iamapi/iamapi_test.go new file mode 100644 index 000000000..f989626e6 --- /dev/null +++ b/weed/iamapi/iamapi_test.go @@ -0,0 +1,157 @@ +package iamapi + +import ( + "encoding/xml" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/iam" + "github.com/chrislusf/seaweedfs/weed/pb/iam_pb" + "github.com/gorilla/mux" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "testing" +) + +var S3config iam_pb.S3ApiConfiguration +var GetS3ApiConfiguration func(s3cfg *iam_pb.S3ApiConfiguration) (err error) +var PutS3ApiConfiguration func(s3cfg *iam_pb.S3ApiConfiguration) (err error) + +type iamS3ApiConfigureMock struct{} + +func (iam iamS3ApiConfigureMock) GetS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) { + s3cfg = &S3config + return nil +} + +func (iam iamS3ApiConfigureMock) PutS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) { + S3config = *s3cfg + return nil +} + +var a = IamApiServer{} + +func TestCreateUser(t *testing.T) { + userName := aws.String("Test") + params := &iam.CreateUserInput{UserName: userName} + req, _ := iam.New(session.New()).CreateUserRequest(params) + _ = req.Build() + out := CreateUserResponse{} + response, err := executeRequest(req.HTTPRequest, out) + assert.Equal(t, nil, err) + assert.Equal(t, http.StatusOK, response.Code) + //assert.Equal(t, out.XMLName, "lol") +} + +func TestListUsers(t *testing.T) { + params := &iam.ListUsersInput{} + req, _ := iam.New(session.New()).ListUsersRequest(params) + _ = req.Build() + out := ListUsersResponse{} + response, err := executeRequest(req.HTTPRequest, out) + assert.Equal(t, nil, err) + assert.Equal(t, http.StatusOK, response.Code) +} + +func TestListAccessKeys(t *testing.T) { + svc := iam.New(session.New()) + params := &iam.ListAccessKeysInput{} + req, _ := svc.ListAccessKeysRequest(params) + _ = req.Build() + out := ListAccessKeysResponse{} + response, err := executeRequest(req.HTTPRequest, out) + assert.Equal(t, nil, err) + assert.Equal(t, http.StatusOK, response.Code) +} + +func TestDeleteUser(t *testing.T) { + userName := aws.String("Test") + params := &iam.DeleteUserInput{UserName: userName} + req, _ := iam.New(session.New()).DeleteUserRequest(params) + _ = req.Build() + out := DeleteUserResponse{} + response, err := executeRequest(req.HTTPRequest, out) + assert.Equal(t, nil, err) + assert.Equal(t, http.StatusNotFound, response.Code) +} + +func TestGetUser(t *testing.T) { + userName := aws.String("Test") + params := &iam.GetUserInput{UserName: userName} + req, _ := iam.New(session.New()).GetUserRequest(params) + _ = req.Build() + out := GetUserResponse{} + response, err := executeRequest(req.HTTPRequest, out) + assert.Equal(t, nil, err) + assert.Equal(t, http.StatusNotFound, response.Code) +} + +// Todo flat statement +func TestCreatePolicy(t *testing.T) { + params := &iam.CreatePolicyInput{ + PolicyName: aws.String("S3-read-only-example-bucket"), + PolicyDocument: aws.String(` + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:Get*", + "s3:List*" + ], + "Resource": [ + "arn:aws:s3:::EXAMPLE-BUCKET", + "arn:aws:s3:::EXAMPLE-BUCKET/*" + ] + } + ] + }`), + } + req, _ := iam.New(session.New()).CreatePolicyRequest(params) + _ = req.Build() + out := CreatePolicyResponse{} + response, err := executeRequest(req.HTTPRequest, out) + assert.Equal(t, nil, err) + assert.Equal(t, http.StatusOK, response.Code) +} + +func TestPutUserPolicy(t *testing.T) { + userName := aws.String("Test") + params := &iam.PutUserPolicyInput{ + UserName: userName, + PolicyName: aws.String("S3-read-only-example-bucket"), + PolicyDocument: aws.String( + `{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:Get*", + "s3:List*" + ], + "Resource": [ + "arn:aws:s3:::EXAMPLE-BUCKET", + "arn:aws:s3:::EXAMPLE-BUCKET/*" + ] + } + ] + }`), + } + req, _ := iam.New(session.New()).PutUserPolicyRequest(params) + _ = req.Build() + out := PutUserPolicyResponse{} + response, err := executeRequest(req.HTTPRequest, out) + assert.Equal(t, nil, err) + assert.Equal(t, http.StatusOK, response.Code) +} + +func executeRequest(req *http.Request, v interface{}) (*httptest.ResponseRecorder, error) { + rr := httptest.NewRecorder() + apiRouter := mux.NewRouter().SkipClean(true) + a.s3ApiConfig = iamS3ApiConfigureMock{} + apiRouter.Path("/").Methods("POST").HandlerFunc(a.DoActions) + apiRouter.ServeHTTP(rr, req) + return rr, xml.Unmarshal(rr.Body.Bytes(), &v) +} From f5f8eec8e2f75045d7cc8685dc5fb86508700d2b Mon Sep 17 00:00:00 2001 From: Konstantin Lebedev Date: Tue, 6 Apr 2021 13:53:56 +0500 Subject: [PATCH 009/128] fix get filerGrpcAddress --- weed/command/iam.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weed/command/iam.go b/weed/command/iam.go index ddcddbec9..17d0832cb 100644 --- a/weed/command/iam.go +++ b/weed/command/iam.go @@ -43,7 +43,7 @@ func runIam(cmd *Command, args []string) bool { } func (iamopt *IamOptions) startIamServer() bool { - filerGrpcAddress, err := pb.ParseFilerGrpcAddress(*iamopt.filer) + filerGrpcAddress, err := pb.ParseServerToGrpcAddress(*iamopt.filer) if err != nil { glog.Fatal(err) return false From c5b08bac1b5bfed90b824bc089681ae6eca9a053 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 5 Apr 2021 23:29:29 -0700 Subject: [PATCH 010/128] remove mac specific mount options --- weed/command/mount_std.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/weed/command/mount_std.go b/weed/command/mount_std.go index 8e5b7a483..f0c59b331 100644 --- a/weed/command/mount_std.go +++ b/weed/command/mount_std.go @@ -149,8 +149,6 @@ func RunMount(option *MountOptions, umask os.FileMode) bool { fuse.Subtype("seaweedfs"), // fuse.NoAppleDouble(), // include .DS_Store, otherwise can not delete non-empty folders fuse.NoAppleXattr(), - fuse.NoBrowse(), - fuse.AutoXattr(), fuse.ExclCreate(), fuse.DaemonTimeout("3600"), fuse.AllowSUID(), From 3be061994fbaecf2bae8ad155064df6b580063d7 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 7 Apr 2021 00:54:06 -0700 Subject: [PATCH 011/128] skip connection reset error fix https://github.com/chrislusf/seaweedfs/issues/1971 this is because the connections are pooled but the volume server has reset the connection --- weed/operation/upload_content.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/weed/operation/upload_content.go b/weed/operation/upload_content.go index e891ae03b..40e96fd8c 100644 --- a/weed/operation/upload_content.go +++ b/weed/operation/upload_content.go @@ -235,8 +235,10 @@ func upload_content(uploadUrl string, fillBufferFunction func(w io.Writer) error // print("+") resp, post_err := HttpClient.Do(req) if post_err != nil { - glog.Errorf("upload %s %d bytes to %v: %v", filename, originalDataSize, uploadUrl, post_err) - debug.PrintStack() + if !strings.Contains(post_err.Error(), "connection reset by peer"){ + glog.Errorf("upload %s %d bytes to %v: %v", filename, originalDataSize, uploadUrl, post_err) + debug.PrintStack() + } return nil, fmt.Errorf("upload %s %d bytes to %v: %v", filename, originalDataSize, uploadUrl, post_err) } // print("-") From 42a761ee201ec0ef7315b68f8d280431ddc07b57 Mon Sep 17 00:00:00 2001 From: Philippe Pepiot Date: Wed, 7 Apr 2021 22:47:23 +0200 Subject: [PATCH 012/128] Fix typo in weed filer long help --- weed/command/filer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weed/command/filer.go b/weed/command/filer.go index 08385e62c..a723b4d8a 100644 --- a/weed/command/filer.go +++ b/weed/command/filer.go @@ -114,7 +114,7 @@ var cmdFiler = &Command{ GET /path/to/ The configuration file "filer.toml" is read from ".", "$HOME/.seaweedfs/", "/usr/local/etc/seaweedfs/", or "/etc/seaweedfs/", in that order. - If the "filer.toml" is not found, an embedded filer store will be craeted under "-defaultStoreDir". + If the "filer.toml" is not found, an embedded filer store will be created under "-defaultStoreDir". The example filer.toml configuration file can be generated by "weed scaffold -config=filer" From b06c5b9d9907063fb4a3fddbcce1cfcc67be38d9 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 7 Apr 2021 20:59:48 -0700 Subject: [PATCH 013/128] upgrade raft to v1.0.5 fix https://github.com/chrislusf/seaweedfs/issues/1974 --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index e792af37b..2a187d6a7 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/buraksezer/consistent v0.0.0-20191006190839-693edf70fd72 github.com/bwmarrin/snowflake v0.3.0 github.com/cespare/xxhash v1.1.0 - github.com/chrislusf/raft v1.0.4 + github.com/chrislusf/raft v1.0.5 github.com/coreos/go-semver v0.3.0 // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/disintegration/imaging v1.6.2 diff --git a/go.sum b/go.sum index 31f5520a8..043b9b0dc 100644 --- a/go.sum +++ b/go.sum @@ -155,6 +155,8 @@ github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chrislusf/raft v1.0.4 h1:THhbsVik2hxdE0/VXX834f64Wn9RzgVPp+E+XCWZdKM= github.com/chrislusf/raft v1.0.4/go.mod h1:Ep5DP+mJSosjfKiix1uU7Lc2Df/SX4oGJEpZlXH5l68= +github.com/chrislusf/raft v1.0.5 h1:g8GxKCSStfm0/bGBDpNEbmEXL6MJkpXX+NI0ksbX5D4= +github.com/chrislusf/raft v1.0.5/go.mod h1:Ep5DP+mJSosjfKiix1uU7Lc2Df/SX4oGJEpZlXH5l68= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= From 995ae9100758921f16f02a8134269433142efcdb Mon Sep 17 00:00:00 2001 From: Konstantin Lebedev Date: Thu, 8 Apr 2021 11:16:36 +0500 Subject: [PATCH 014/128] add DeleteUserPolicy --- docker/compose/local-dev-compose.yml | 3 ++- weed/iamapi/iamapi_handlers.go | 9 ++++++-- weed/iamapi/iamapi_management_handlers.go | 27 +++++++++++++++++++---- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/docker/compose/local-dev-compose.yml b/docker/compose/local-dev-compose.yml index 05103a7fc..01d0594a6 100644 --- a/docker/compose/local-dev-compose.yml +++ b/docker/compose/local-dev-compose.yml @@ -26,9 +26,10 @@ services: filer: image: chrislusf/seaweedfs:local ports: + - 8111:8111 - 8888:8888 - 18888:18888 - command: '-v=1 filer -master="master:9333"' + command: '-v=1 filer -master="master:9333" -iam' depends_on: - master - volume diff --git a/weed/iamapi/iamapi_handlers.go b/weed/iamapi/iamapi_handlers.go index fdaf4dd69..2e5f709f3 100644 --- a/weed/iamapi/iamapi_handlers.go +++ b/weed/iamapi/iamapi_handlers.go @@ -50,20 +50,25 @@ func writeErrorResponse(w http.ResponseWriter, errorCode s3err.ErrorCode, reqURL writeResponse(w, apiError.HTTPStatusCode, encodedErrorResponse, mimeXML) } -func writeIamErrorResponse(w http.ResponseWriter, err error, object string, value string) { +func writeIamErrorResponse(w http.ResponseWriter, err error, object string, value string, msg error) { errCode := err.Error() errorResp := ErrorResponse{} errorResp.Error.Type = "Sender" errorResp.Error.Code = &errCode + if msg != nil { + errMsg := msg.Error() + errorResp.Error.Message = &errMsg + } glog.Errorf("Response %+v", err) switch errCode { case iam.ErrCodeNoSuchEntityException: msg := fmt.Sprintf("The %s with name %s cannot be found.", object, value) errorResp.Error.Message = &msg writeResponse(w, http.StatusNotFound, encodeResponse(errorResp), mimeXML) + case iam.ErrCodeServiceFailureException: + writeResponse(w, http.StatusInternalServerError, encodeResponse(errorResp), mimeXML) default: writeResponse(w, http.StatusInternalServerError, encodeResponse(errorResp), mimeXML) - } } diff --git a/weed/iamapi/iamapi_management_handlers.go b/weed/iamapi/iamapi_management_handlers.go index 470731064..4ca3525ec 100644 --- a/weed/iamapi/iamapi_management_handlers.go +++ b/weed/iamapi/iamapi_management_handlers.go @@ -144,6 +144,17 @@ func (iama *IamApiServer) PutUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values return resp, nil } +func (iama *IamApiServer) DeleteUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp PutUserPolicyResponse, err error) { + userName := values.Get("UserName") + for i, ident := range s3cfg.Identities { + if ident.Name == userName { + s3cfg.Identities = append(s3cfg.Identities[:i], s3cfg.Identities[i+1:]...) + return resp, nil + } + } + return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException) +} + func MapAction(action string) string { switch action { case "*": @@ -277,14 +288,14 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { userName := values.Get("UserName") response, err = iama.GetUser(s3cfg, userName) if err != nil { - writeIamErrorResponse(w, err, "user", userName) + writeIamErrorResponse(w, err, "user", userName, nil) return } case "DeleteUser": userName := values.Get("UserName") response, err = iama.DeleteUser(s3cfg, userName) if err != nil { - writeIamErrorResponse(w, err, "user", userName) + writeIamErrorResponse(w, err, "user", userName, nil) return } case "CreateAccessKey": @@ -305,8 +316,16 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { writeErrorResponse(w, s3err.ErrInvalidRequest, r.URL) return } + case "DeleteUserPolicy": + if response, err = iama.DeleteUserPolicy(s3cfg, values); err != nil { + writeIamErrorResponse(w, err, "user", values.Get("UserName"), nil) + } default: - writeErrorResponse(w, s3err.ErrNotImplemented, r.URL) + errNotImplemented := s3err.GetAPIError(s3err.ErrNotImplemented) + errorResponse := ErrorResponse{} + errorResponse.Error.Code = &errNotImplemented.Code + errorResponse.Error.Message = &errNotImplemented.Description + writeResponse(w, errNotImplemented.HTTPStatusCode, encodeResponse(errorResponse), mimeXML) return } if changed { @@ -314,7 +333,7 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { err := iama.s3ApiConfig.PutS3ApiConfiguration(s3cfg) s3cfgLock.Unlock() if err != nil { - writeErrorResponse(w, s3err.ErrInternalError, r.URL) + writeIamErrorResponse(w, fmt.Errorf(iam.ErrCodeServiceFailureException), "", "", err) return } } From ba175f81b5bd95dbc283b6b3f7681659dbd45aa8 Mon Sep 17 00:00:00 2001 From: Konstantin Lebedev Date: Thu, 8 Apr 2021 17:40:47 +0500 Subject: [PATCH 015/128] add auth aws signV4 --- weed/iamapi/iamapi_server.go | 10 +++++++--- weed/s3api/auth_signature_v4.go | 30 ++++++++++++++++++++++------ weed/s3api/auto_signature_v4_test.go | 2 +- weed/s3api/chunked_reader_v4.go | 4 ++-- 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/weed/iamapi/iamapi_server.go b/weed/iamapi/iamapi_server.go index 7698fab71..d0a033ed6 100644 --- a/weed/iamapi/iamapi_server.go +++ b/weed/iamapi/iamapi_server.go @@ -9,6 +9,8 @@ import ( "github.com/chrislusf/seaweedfs/weed/pb" "github.com/chrislusf/seaweedfs/weed/pb/filer_pb" "github.com/chrislusf/seaweedfs/weed/pb/iam_pb" + "github.com/chrislusf/seaweedfs/weed/s3api" + . "github.com/chrislusf/seaweedfs/weed/s3api/s3_constants" "github.com/chrislusf/seaweedfs/weed/wdclient" "github.com/gorilla/mux" "google.golang.org/grpc" @@ -36,7 +38,7 @@ type IamServerOption struct { type IamApiServer struct { s3ApiConfig IamS3ApiConfig - filerclient *filer_pb.SeaweedFilerClient + iam *s3api.IdentityAccessManagement } var s3ApiConfigure IamS3ApiConfig @@ -46,9 +48,10 @@ func NewIamApiServer(router *mux.Router, option *IamServerOption) (iamApiServer option: option, masterClient: wdclient.NewMasterClient(option.GrpcDialOption, pb.AdminShellClient, "", 0, "", strings.Split(option.Masters, ",")), } - + s3Option := s3api.S3ApiServerOption{Filer: option.Filer} iamApiServer = &IamApiServer{ s3ApiConfig: s3ApiConfigure, + iam: s3api.NewIdentityAccessManagement(&s3Option), } iamApiServer.registerRouter(router) @@ -62,7 +65,8 @@ func (iama *IamApiServer) registerRouter(router *mux.Router) { // ListBuckets // apiRouter.Methods("GET").Path("/").HandlerFunc(track(s3a.iam.Auth(s3a.ListBucketsHandler, ACTION_ADMIN), "LIST")) - apiRouter.Path("/").Methods("POST").HandlerFunc(iama.DoActions) + apiRouter.Methods("POST").Path("/").HandlerFunc(iama.iam.Auth(iama.DoActions, ACTION_ADMIN)) + // // NotFound apiRouter.NotFoundHandler = http.HandlerFunc(notFoundHandler) } diff --git a/weed/s3api/auth_signature_v4.go b/weed/s3api/auth_signature_v4.go index 5ef7439c8..0df26e6fc 100644 --- a/weed/s3api/auth_signature_v4.go +++ b/weed/s3api/auth_signature_v4.go @@ -24,6 +24,7 @@ import ( "crypto/subtle" "encoding/hex" "github.com/chrislusf/seaweedfs/weed/s3api/s3err" + "io/ioutil" "net/http" "net/url" "regexp" @@ -132,6 +133,17 @@ func (iam *IdentityAccessManagement) doesSignatureMatch(hashedPayload string, r // Query string. queryStr := req.URL.Query().Encode() + // Get hashed Payload + if signV4Values.Credential.scope.service != "s3" && hashedPayload == emptySHA256 && r.Body != nil { + buf, _ := ioutil.ReadAll(r.Body) + r.Body = ioutil.NopCloser(bytes.NewBuffer(buf)) + b, _ := ioutil.ReadAll(bytes.NewBuffer(buf)) + if len(b) != 0 { + bodyHash := sha256.Sum256(b) + hashedPayload = hex.EncodeToString(bodyHash[:]) + } + } + // Get canonical request. canonicalRequest := getCanonicalRequest(extractedSignedHeaders, hashedPayload, queryStr, req.URL.Path, req.Method) @@ -139,7 +151,10 @@ func (iam *IdentityAccessManagement) doesSignatureMatch(hashedPayload string, r stringToSign := getStringToSign(canonicalRequest, t, signV4Values.Credential.getScope()) // Get hmac signing key. - signingKey := getSigningKey(cred.SecretKey, signV4Values.Credential.scope.date, signV4Values.Credential.scope.region) + signingKey := getSigningKey(cred.SecretKey, + signV4Values.Credential.scope.date, + signV4Values.Credential.scope.region, + signV4Values.Credential.scope.service) // Calculate signature. newSignature := getSignature(signingKey, stringToSign) @@ -310,7 +325,7 @@ func (iam *IdentityAccessManagement) doesPolicySignatureV4Match(formValues http. } // Get signing key. - signingKey := getSigningKey(cred.SecretKey, credHeader.scope.date, credHeader.scope.region) + signingKey := getSigningKey(cred.SecretKey, credHeader.scope.date, credHeader.scope.region, credHeader.scope.service) // Get signature. newSignature := getSignature(signingKey, formValues.Get("Policy")) @@ -427,7 +442,10 @@ func (iam *IdentityAccessManagement) doesPresignedSignatureMatch(hashedPayload s presignedStringToSign := getStringToSign(presignedCanonicalReq, t, pSignValues.Credential.getScope()) // Get hmac presigned signing key. - presignedSigningKey := getSigningKey(cred.SecretKey, pSignValues.Credential.scope.date, pSignValues.Credential.scope.region) + presignedSigningKey := getSigningKey(cred.SecretKey, + pSignValues.Credential.scope.date, + pSignValues.Credential.scope.region, + pSignValues.Credential.scope.service) // Get new signature. newSignature := getSignature(presignedSigningKey, presignedStringToSign) @@ -655,11 +673,11 @@ func sumHMAC(key []byte, data []byte) []byte { } // getSigningKey hmac seed to calculate final signature. -func getSigningKey(secretKey string, t time.Time, region string) []byte { +func getSigningKey(secretKey string, t time.Time, region string, service string) []byte { date := sumHMAC([]byte("AWS4"+secretKey), []byte(t.Format(yyyymmdd))) regionBytes := sumHMAC(date, []byte(region)) - service := sumHMAC(regionBytes, []byte("s3")) - signingKey := sumHMAC(service, []byte("aws4_request")) + serviceBytes := sumHMAC(regionBytes, []byte(service)) + signingKey := sumHMAC(serviceBytes, []byte("aws4_request")) return signingKey } diff --git a/weed/s3api/auto_signature_v4_test.go b/weed/s3api/auto_signature_v4_test.go index 4c8255768..b47cd5f2d 100644 --- a/weed/s3api/auto_signature_v4_test.go +++ b/weed/s3api/auto_signature_v4_test.go @@ -370,7 +370,7 @@ func preSignV4(req *http.Request, accessKeyID, secretAccessKey string, expires i queryStr := strings.Replace(query.Encode(), "+", "%20", -1) canonicalRequest := getCanonicalRequest(extractedSignedHeaders, unsignedPayload, queryStr, req.URL.Path, req.Method) stringToSign := getStringToSign(canonicalRequest, date, scope) - signingKey := getSigningKey(secretAccessKey, date, region) + signingKey := getSigningKey(secretAccessKey, date, region, "s3") signature := getSignature(signingKey, stringToSign) req.URL.RawQuery = query.Encode() diff --git a/weed/s3api/chunked_reader_v4.go b/weed/s3api/chunked_reader_v4.go index 734c9faee..b163ec2f6 100644 --- a/weed/s3api/chunked_reader_v4.go +++ b/weed/s3api/chunked_reader_v4.go @@ -45,7 +45,7 @@ func getChunkSignature(secretKey string, seedSignature string, region string, da hashedChunk // Get hmac signing key. - signingKey := getSigningKey(secretKey, date, region) + signingKey := getSigningKey(secretKey, date, region, "s3") // Calculate signature. newSignature := getSignature(signingKey, stringToSign) @@ -117,7 +117,7 @@ func (iam *IdentityAccessManagement) calculateSeedSignature(r *http.Request) (cr stringToSign := getStringToSign(canonicalRequest, date, signV4Values.Credential.getScope()) // Get hmac signing key. - signingKey := getSigningKey(cred.SecretKey, signV4Values.Credential.scope.date, region) + signingKey := getSigningKey(cred.SecretKey, signV4Values.Credential.scope.date, region, "s3") // Calculate signature. newSignature := getSignature(signingKey, stringToSign) From 6deb647a8f07fe3828ce87a7cdd6c5925ea7f09e Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 8 Apr 2021 19:47:31 -0700 Subject: [PATCH 016/128] mount: fix possible memory leak if many files are read repeatedly, their metadata are accumulated in memory. This fix cleared the metadata after the file is read. --- weed/filesys/filehandle.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/weed/filesys/filehandle.go b/weed/filesys/filehandle.go index 4419888c4..57aa41b8a 100644 --- a/weed/filesys/filehandle.go +++ b/weed/filesys/filehandle.go @@ -200,6 +200,8 @@ func (fh *FileHandle) Release(ctx context.Context, req *fuse.ReleaseRequest) err fh.Lock() defer fh.Unlock() + fh.f.clearEntry() + if fh.f.isOpen <= 0 { glog.V(0).Infof("Release reset %s open count %d => %d", fh.f.Name, fh.f.isOpen, 0) fh.f.isOpen = 0 @@ -211,7 +213,6 @@ func (fh *FileHandle) Release(ctx context.Context, req *fuse.ReleaseRequest) err fh.f.isOpen-- fh.f.wfs.ReleaseHandle(fh.f.fullpath(), fuse.HandleID(fh.handle)) - fh.f.setReader(nil) } return nil From 4d4acc715e517ae8f7cdaed220908dbd8e0b7773 Mon Sep 17 00:00:00 2001 From: Merlin Gaillard Date: Fri, 9 Apr 2021 12:13:19 +0200 Subject: [PATCH 017/128] s3api: handle 304 response code from filer --- weed/s3api/s3api_object_handlers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weed/s3api/s3api_object_handlers.go b/weed/s3api/s3api_object_handlers.go index b3cfd9ec7..f1a539ac5 100644 --- a/weed/s3api/s3api_object_handlers.go +++ b/weed/s3api/s3api_object_handlers.go @@ -311,7 +311,7 @@ func (s3a *S3ApiServer) proxyToFiler(w http.ResponseWriter, r *http.Request, des } defer util.CloseResponse(resp) - if resp.ContentLength == -1 || resp.StatusCode == 404 { + if (resp.ContentLength == -1 || resp.StatusCode == 404) && resp.StatusCode != 304 { if r.Method != "DELETE" { writeErrorResponse(w, s3err.ErrNoSuchKey, r.URL) return From f952f979d1cc6a0975d73491d7e6aa5c4e1b522c Mon Sep 17 00:00:00 2001 From: Merlin Gaillard Date: Fri, 9 Apr 2021 15:04:17 +0200 Subject: [PATCH 018/128] filer: return 304 when If-Modified-Since == Last-Modified --- weed/server/filer_server_handlers_read.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weed/server/filer_server_handlers_read.go b/weed/server/filer_server_handlers_read.go index f90b070a2..cdfdf9b49 100644 --- a/weed/server/filer_server_handlers_read.go +++ b/weed/server/filer_server_handlers_read.go @@ -79,7 +79,7 @@ func (fs *FilerServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request, w.Header().Set("Last-Modified", entry.Attr.Mtime.UTC().Format(http.TimeFormat)) if r.Header.Get("If-Modified-Since") != "" { if t, parseError := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); parseError == nil { - if t.After(entry.Attr.Mtime) { + if t.After(entry.Attr.Mtime) || t.Equal(entry.Attr.Mtime) { w.WriteHeader(http.StatusNotModified) return } From 93f4146ffa91c3a7851add1dcb19af4297e299c5 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 9 Apr 2021 12:36:39 -0700 Subject: [PATCH 019/128] properly release the view cache --- weed/filesys/filehandle.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/weed/filesys/filehandle.go b/weed/filesys/filehandle.go index 57aa41b8a..f04952e96 100644 --- a/weed/filesys/filehandle.go +++ b/weed/filesys/filehandle.go @@ -200,7 +200,7 @@ func (fh *FileHandle) Release(ctx context.Context, req *fuse.ReleaseRequest) err fh.Lock() defer fh.Unlock() - fh.f.clearEntry() + fh.f.entryViewCache = nil if fh.f.isOpen <= 0 { glog.V(0).Infof("Release reset %s open count %d => %d", fh.f.Name, fh.f.isOpen, 0) @@ -213,6 +213,7 @@ func (fh *FileHandle) Release(ctx context.Context, req *fuse.ReleaseRequest) err fh.f.isOpen-- fh.f.wfs.ReleaseHandle(fh.f.fullpath(), fuse.HandleID(fh.handle)) + fh.f.setReader(nil) } return nil From 0b82edc0d28424597b6537308756971d017f59ce Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 9 Apr 2021 13:05:15 -0700 Subject: [PATCH 020/128] filer: avoid stuck uploader fix https://github.com/chrislusf/seaweedfs/issues/1980 reverting the file upload batch executor --- .../filer_server_handlers_write_upload.go | 170 ++++++------------ 1 file changed, 50 insertions(+), 120 deletions(-) diff --git a/weed/server/filer_server_handlers_write_upload.go b/weed/server/filer_server_handlers_write_upload.go index 81b2ce1b0..3ab45453e 100644 --- a/weed/server/filer_server_handlers_write_upload.go +++ b/weed/server/filer_server_handlers_write_upload.go @@ -6,9 +6,7 @@ import ( "io" "io/ioutil" "net/http" - "runtime" "strings" - "sync" "time" "github.com/chrislusf/seaweedfs/weed/filer" @@ -20,143 +18,75 @@ import ( "github.com/chrislusf/seaweedfs/weed/util" ) -var ( - limitedUploadProcessor = util.NewLimitedOutOfOrderProcessor(int32(runtime.NumCPU())) -) - -func (fs *FilerServer) uploadReaderToChunks(w http.ResponseWriter, r *http.Request, reader io.Reader, chunkSize int32, fileName, contentType string, contentLength int64, so *operation.StorageOption) (fileChunks []*filer_pb.FileChunk, md5Hash hash.Hash, dataSize int64, err error, smallContent []byte) { +func (fs *FilerServer) uploadReaderToChunks(w http.ResponseWriter, r *http.Request, reader io.Reader, chunkSize int32, fileName, contentType string, contentLength int64, so *operation.StorageOption) ([]*filer_pb.FileChunk, hash.Hash, int64, error, []byte) { + var fileChunks []*filer_pb.FileChunk - md5Hash = md5.New() + md5Hash := md5.New() var partReader = ioutil.NopCloser(io.TeeReader(reader, md5Hash)) - // save small content directly - if !isAppend(r) && ((0 < contentLength && contentLength < fs.option.SaveToFilerLimit) || strings.HasPrefix(r.URL.Path, filer.DirectoryEtcRoot) && contentLength < 4*1024) { - smallContent, err = ioutil.ReadAll(partReader) - dataSize = int64(len(smallContent)) - return - } + chunkOffset := int64(0) + var smallContent []byte - resultsChan := make(chan *ChunkCreationResult, 2) + for { + limitedReader := io.LimitReader(partReader, int64(chunkSize)) - var waitForAllData sync.WaitGroup - waitForAllData.Add(1) - go func() { - // process upload results - defer waitForAllData.Done() - for result := range resultsChan { - if result.err != nil { - err = result.err - continue - } - - // Save to chunk manifest structure - fileChunks = append(fileChunks, result.chunk) + data, err := ioutil.ReadAll(limitedReader) + if err != nil { + return nil, nil, 0, err, nil } - }() - - var lock sync.Mutex - readOffset := int64(0) - var wg sync.WaitGroup - - for err == nil { - - wg.Add(1) - request := func() { - defer wg.Done() - - var localOffset int64 - // read from the input - lock.Lock() - localOffset = readOffset - limitedReader := io.LimitReader(partReader, int64(chunkSize)) - data, readErr := ioutil.ReadAll(limitedReader) - readOffset += int64(len(data)) - lock.Unlock() - // handle read errors - if readErr != nil { - if err == nil { - err = readErr - } - if readErr != io.EOF { - resultsChan <- &ChunkCreationResult{ - err: readErr, - } - } - return + if chunkOffset == 0 && !isAppend(r) { + if len(data) < int(fs.option.SaveToFilerLimit) || strings.HasPrefix(r.URL.Path, filer.DirectoryEtcRoot) && len(data) < 4*1024 { + smallContent = data + chunkOffset += int64(len(data)) + break } - if len(data) == 0 { - readErr = io.EOF - if err == nil { - err = readErr - } - return + } + dataReader := util.NewBytesReader(data) + + // retry to assign a different file id + var fileId, urlLocation string + var auth security.EncodedJwt + var assignErr, uploadErr error + var uploadResult *operation.UploadResult + for i := 0; i < 3; i++ { + // assign one file id for one chunk + fileId, urlLocation, auth, assignErr = fs.assignNewFileInfo(so) + if assignErr != nil { + return nil, nil, 0, assignErr, nil } - // upload - dataReader := util.NewBytesReader(data) - fileId, uploadResult, uploadErr := fs.doCreateChunk(w, r, so, dataReader, fileName, contentType) + // upload the chunk to the volume server + uploadResult, uploadErr, _ = fs.doUpload(urlLocation, w, r, dataReader, fileName, contentType, nil, auth) if uploadErr != nil { - if err == nil { - err = uploadErr - } - resultsChan <- &ChunkCreationResult{ - err: uploadErr, - } - return - } - - glog.V(4).Infof("uploaded %s to %s [%d,%d)", fileName, fileId, localOffset, localOffset+int64(uploadResult.Size)) - - // send back uploaded file chunk - resultsChan <- &ChunkCreationResult{ - chunk: uploadResult.ToPbFileChunk(fileId, localOffset), + time.Sleep(251 * time.Millisecond) + continue } - + break + } + if uploadErr != nil { + return nil, nil, 0, uploadErr, nil } - limitedUploadProcessor.Execute(request) - } - - go func() { - wg.Wait() - close(resultsChan) - }() - - waitForAllData.Wait() - if err == io.EOF { - err = nil - } + // if last chunk exhausted the reader exactly at the border + if uploadResult.Size == 0 { + break + } - return fileChunks, md5Hash, readOffset, err, nil -} + // Save to chunk manifest structure + fileChunks = append(fileChunks, uploadResult.ToPbFileChunk(fileId, chunkOffset)) -type ChunkCreationResult struct { - chunk *filer_pb.FileChunk - err error -} + glog.V(4).Infof("uploaded %s chunk %d to %s [%d,%d)", fileName, len(fileChunks), fileId, chunkOffset, chunkOffset+int64(uploadResult.Size)) -func (fs *FilerServer) doCreateChunk(w http.ResponseWriter, r *http.Request, so *operation.StorageOption, dataReader *util.BytesReader, fileName string, contentType string) (string, *operation.UploadResult, error) { - // retry to assign a different file id - var fileId, urlLocation string - var auth security.EncodedJwt - var assignErr, uploadErr error - var uploadResult *operation.UploadResult - for i := 0; i < 3; i++ { - // assign one file id for one chunk - fileId, urlLocation, auth, assignErr = fs.assignNewFileInfo(so) - if assignErr != nil { - return "", nil, assignErr - } + // reset variables for the next chunk + chunkOffset = chunkOffset + int64(uploadResult.Size) - // upload the chunk to the volume server - uploadResult, uploadErr, _ = fs.doUpload(urlLocation, w, r, dataReader, fileName, contentType, nil, auth) - if uploadErr != nil { - time.Sleep(251 * time.Millisecond) - continue + // if last chunk was not at full chunk size, but already exhausted the reader + if int64(uploadResult.Size) < int64(chunkSize) { + break } - break } - return fileId, uploadResult, uploadErr + + return fileChunks, md5Hash, chunkOffset, nil, smallContent } func (fs *FilerServer) doUpload(urlLocation string, w http.ResponseWriter, r *http.Request, limitedReader io.Reader, fileName string, contentType string, pairMap map[string]string, auth security.EncodedJwt) (*operation.UploadResult, error, []byte) { From 98c08a3dcd3d3a3115828dc04b5f11e56cd67489 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sat, 10 Apr 2021 02:36:53 -0700 Subject: [PATCH 021/128] raft: fix possible nil panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x1962c8a] goroutine 68239935 [running]: github.com/chrislusf/raft.(*LogEntry).Term(...) /home/travis/gopath/pkg/mod/github.com/chrislusf/raft@v1.0.4/log_entry.go:59 github.com/chrislusf/raft.(*server).TakeSnapshot(0xc00014f320, 0xc028b5a600, 0xc03995bc00) /home/travis/gopath/pkg/mod/github.com/chrislusf/raft@v1.0.4/server.go:1276 +0x50a github.com/chrislusf/raft.(*server).maybeTakeSnapshot.func1(0xc00014f320) /home/travis/gopath/pkg/mod/github.com/chrislusf/raft@v1.0.4/server.go:1221 +0x5b created by github.com/chrislusf/raft.(*server).maybeTakeSnapshot /home/travis/gopath/pkg/mod/github.com/chrislusf/raft@v1.0.4/server.go:1219 +0x98 --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 2a187d6a7..310f5585e 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/buraksezer/consistent v0.0.0-20191006190839-693edf70fd72 github.com/bwmarrin/snowflake v0.3.0 github.com/cespare/xxhash v1.1.0 - github.com/chrislusf/raft v1.0.5 + github.com/chrislusf/raft v1.0.6 github.com/coreos/go-semver v0.3.0 // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/disintegration/imaging v1.6.2 diff --git a/go.sum b/go.sum index 043b9b0dc..1ea4ee761 100644 --- a/go.sum +++ b/go.sum @@ -157,6 +157,8 @@ github.com/chrislusf/raft v1.0.4 h1:THhbsVik2hxdE0/VXX834f64Wn9RzgVPp+E+XCWZdKM= github.com/chrislusf/raft v1.0.4/go.mod h1:Ep5DP+mJSosjfKiix1uU7Lc2Df/SX4oGJEpZlXH5l68= github.com/chrislusf/raft v1.0.5 h1:g8GxKCSStfm0/bGBDpNEbmEXL6MJkpXX+NI0ksbX5D4= github.com/chrislusf/raft v1.0.5/go.mod h1:Ep5DP+mJSosjfKiix1uU7Lc2Df/SX4oGJEpZlXH5l68= +github.com/chrislusf/raft v1.0.6 h1:wunb85WWhMKhNRn7EmdIw35D4Lmew0ZJv8oYDizR/+Y= +github.com/chrislusf/raft v1.0.6/go.mod h1:Ep5DP+mJSosjfKiix1uU7Lc2Df/SX4oGJEpZlXH5l68= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= From 5021bea69838a5946b4b0bfa37c6269bfe66fa27 Mon Sep 17 00:00:00 2001 From: Konstantin Lebedev Date: Sat, 10 Apr 2021 23:57:45 +0500 Subject: [PATCH 022/128] GetUserPolicy --- go.mod | 1 + go.sum | 2 + weed/filer/filer_conf.go | 1 + weed/iamapi/iamapi_management_handlers.go | 150 +++++++++++++++++++--- weed/iamapi/iamapi_response.go | 10 ++ weed/iamapi/iamapi_server.go | 41 ++++++ weed/iamapi/iamapi_test.go | 60 ++++++--- 7 files changed, 226 insertions(+), 39 deletions(-) diff --git a/go.mod b/go.mod index 2a187d6a7..ee33926c0 100644 --- a/go.mod +++ b/go.mod @@ -42,6 +42,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 github.com/grpc-ecosystem/grpc-gateway v1.11.0 // indirect github.com/jcmturner/gofork v1.0.0 // indirect + github.com/jinzhu/copier v0.2.8 github.com/json-iterator/go v1.1.10 github.com/karlseguin/ccache v2.0.3+incompatible // indirect github.com/karlseguin/ccache/v2 v2.0.7 diff --git a/go.sum b/go.sum index 043b9b0dc..52166e601 100644 --- a/go.sum +++ b/go.sum @@ -437,6 +437,8 @@ github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03 h1:FUwcHNlEqkqLjL github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8= github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jinzhu/copier v0.2.8 h1:N8MbL5niMwE3P4dOwurJixz5rMkKfujmMRFmAanSzWE= +github.com/jinzhu/copier v0.2.8/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= diff --git a/weed/filer/filer_conf.go b/weed/filer/filer_conf.go index 8e549f5ad..ab5afc5cc 100644 --- a/weed/filer/filer_conf.go +++ b/weed/filer/filer_conf.go @@ -18,6 +18,7 @@ const ( FilerConfName = "filer.conf" IamConfigDirecotry = "/etc/iam" IamIdentityFile = "identity.json" + IamPoliciesFile = "policies.json" ) type FilerConf struct { diff --git a/weed/iamapi/iamapi_management_handlers.go b/weed/iamapi/iamapi_management_handlers.go index 4ca3525ec..b00ada234 100644 --- a/weed/iamapi/iamapi_management_handlers.go +++ b/weed/iamapi/iamapi_management_handlers.go @@ -11,6 +11,7 @@ import ( "math/rand" "net/http" "net/url" + "reflect" "strings" "sync" "time" @@ -19,27 +20,77 @@ import ( ) const ( - charsetUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - charset = charsetUpper + "abcdefghijklmnopqrstuvwxyz/" + charsetUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + charset = charsetUpper + "abcdefghijklmnopqrstuvwxyz/" + policyDocumentVersion = "2012-10-17" + StatementActionAdmin = "*" + StatementActionWrite = "Put*" + StatementActionRead = "Get*" + StatementActionList = "List*" + StatementActionTagging = "Tagging*" ) var ( seededRand *rand.Rand = rand.New( rand.NewSource(time.Now().UnixNano())) policyDocuments = map[string]*PolicyDocument{} + policyLock = sync.RWMutex{} ) +func MapToStatementAction(action string) string { + switch action { + case StatementActionAdmin: + return s3_constants.ACTION_ADMIN + case StatementActionWrite: + return s3_constants.ACTION_WRITE + case StatementActionRead: + return s3_constants.ACTION_READ + case StatementActionList: + return s3_constants.ACTION_LIST + case StatementActionTagging: + return s3_constants.ACTION_TAGGING + default: + return "" + } +} + +func MapToIdentitiesAction(action string) string { + switch action { + case s3_constants.ACTION_ADMIN: + return StatementActionAdmin + case s3_constants.ACTION_WRITE: + return StatementActionWrite + case s3_constants.ACTION_READ: + return StatementActionRead + case s3_constants.ACTION_LIST: + return StatementActionList + case s3_constants.ACTION_TAGGING: + return StatementActionTagging + default: + return "" + } +} + type Statement struct { Effect string `json:"Effect"` Action []string `json:"Action"` Resource []string `json:"Resource"` } +type Policies struct { + Policies map[string]PolicyDocument `json:"policies"` +} + type PolicyDocument struct { Version string `json:"Version"` Statement []*Statement `json:"Statement"` } +func (p PolicyDocument) String() string { + b, _ := json.Marshal(p) + return string(b) +} + func Hash(s *string) string { h := sha1.New() h.Write([]byte(*s)) @@ -83,7 +134,7 @@ func (iama *IamApiServer) CreateUser(s3cfg *iam_pb.S3ApiConfiguration, values ur func (iama *IamApiServer) DeleteUser(s3cfg *iam_pb.S3ApiConfiguration, userName string) (resp DeleteUserResponse, err error) { for i, ident := range s3cfg.Identities { if userName == ident.Name { - ident.Credentials = append(ident.Credentials[:i], ident.Credentials[i+1:]...) + s3cfg.Identities = append(s3cfg.Identities[:i], s3cfg.Identities[i+1:]...) return resp, nil } } @@ -119,7 +170,16 @@ func (iama *IamApiServer) CreatePolicy(s3cfg *iam_pb.S3ApiConfiguration, values resp.CreatePolicyResult.Policy.PolicyName = &policyName resp.CreatePolicyResult.Policy.Arn = &arn resp.CreatePolicyResult.Policy.PolicyId = &policyId - policyDocuments[policyName] = &policyDocument + policies := Policies{} + policyLock.Lock() + defer policyLock.Unlock() + if err = iama.s3ApiConfig.GetPolicies(&policies); err != nil { + return resp, err + } + policies.Policies[policyName] = policyDocument + if err = iama.s3ApiConfig.PutPolicies(&policies); err != nil { + return resp, err + } return resp, nil } @@ -144,6 +204,60 @@ func (iama *IamApiServer) PutUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values return resp, nil } +func (iama *IamApiServer) GetUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp GetUserPolicyResponse, err error) { + userName := values.Get("UserName") + policyName := values.Get("PolicyName") + for _, ident := range s3cfg.Identities { + if userName != ident.Name { + continue + } + + resp.GetUserPolicyResult.UserName = userName + resp.GetUserPolicyResult.PolicyName = policyName + if len(ident.Actions) == 0 { + return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException) + } + + policyDocument := PolicyDocument{Version: policyDocumentVersion} + statements := make(map[string][]string) + for _, action := range ident.Actions { + // parse "Read:EXAMPLE-BUCKET" + act := strings.Split(action, ":") + + resource := "*" + if len(act) == 2 { + resource = fmt.Sprintf("arn:aws:s3:::%s/*", act[1]) + } + statements[resource] = append(statements[resource], + fmt.Sprintf("s3:%s", MapToIdentitiesAction(act[0])), + ) + } + for resource, actions := range statements { + isEqAction := false + for i, statement := range policyDocument.Statement { + if reflect.DeepEqual(statement.Action, actions) { + policyDocument.Statement[i].Resource = append( + policyDocument.Statement[i].Resource, resource) + isEqAction = true + break + } + } + if isEqAction { + continue + } + policyDocumentStatement := Statement{ + Effect: "Allow", + Action: actions, + } + policyDocumentStatement.Resource = append(policyDocumentStatement.Resource, resource) + policyDocument.Statement = append(policyDocument.Statement, &policyDocumentStatement) + } + resp.GetUserPolicyResult.PolicyDocument = policyDocument.String() + return resp, nil + } + return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException) +} + func (iama *IamApiServer) DeleteUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp PutUserPolicyResponse, err error) { userName := values.Get("UserName") for i, ident := range s3cfg.Identities { @@ -155,21 +269,6 @@ func (iama *IamApiServer) DeleteUserPolicy(s3cfg *iam_pb.S3ApiConfiguration, val return resp, fmt.Errorf(iam.ErrCodeNoSuchEntityException) } -func MapAction(action string) string { - switch action { - case "*": - return s3_constants.ACTION_ADMIN - case "Put*": - return s3_constants.ACTION_WRITE - case "Get*": - return s3_constants.ACTION_READ - case "List*": - return s3_constants.ACTION_LIST - default: - return s3_constants.ACTION_TAGGING - } -} - func GetActions(policy *PolicyDocument) (actions []string) { for _, statement := range policy.Statement { if statement.Effect != "Allow" { @@ -189,8 +288,9 @@ func GetActions(policy *PolicyDocument) (actions []string) { glog.Infof("not match action: %s", act) continue } + statementAction := MapToStatementAction(act[1]) if res[5] == "*" { - actions = append(actions, MapAction(act[1])) + actions = append(actions, statementAction) continue } // Parse my-bucket/shared/* @@ -199,7 +299,7 @@ func GetActions(policy *PolicyDocument) (actions []string) { glog.Infof("not match bucket: %s", path) continue } - actions = append(actions, fmt.Sprintf("%s:%s", MapAction(act[1]), path[0])) + actions = append(actions, fmt.Sprintf("%s:%s", statementAction, path[0])) } } } @@ -291,6 +391,7 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { writeIamErrorResponse(w, err, "user", userName, nil) return } + changed = false case "DeleteUser": userName := values.Get("UserName") response, err = iama.DeleteUser(s3cfg, userName) @@ -316,6 +417,13 @@ func (iama *IamApiServer) DoActions(w http.ResponseWriter, r *http.Request) { writeErrorResponse(w, s3err.ErrInvalidRequest, r.URL) return } + case "GetUserPolicy": + response, err = iama.GetUserPolicy(s3cfg, values) + if err != nil { + writeIamErrorResponse(w, err, "user", values.Get("UserName"), nil) + return + } + changed = false case "DeleteUserPolicy": if response, err = iama.DeleteUserPolicy(s3cfg, values); err != nil { writeIamErrorResponse(w, err, "user", values.Get("UserName"), nil) diff --git a/weed/iamapi/iamapi_response.go b/weed/iamapi/iamapi_response.go index 26dd0f263..77328b608 100644 --- a/weed/iamapi/iamapi_response.go +++ b/weed/iamapi/iamapi_response.go @@ -79,6 +79,16 @@ type PutUserPolicyResponse struct { XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ PutUserPolicyResponse"` } +type GetUserPolicyResponse struct { + CommonResponse + XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ GetUserPolicyResponse"` + GetUserPolicyResult struct { + UserName string `xml:"UserName"` + PolicyName string `xml:"PolicyName"` + PolicyDocument string `xml:"PolicyDocument"` + } `xml:"GetUserPolicyResult"` +} + type ErrorResponse struct { CommonResponse XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ ErrorResponse"` diff --git a/weed/iamapi/iamapi_server.go b/weed/iamapi/iamapi_server.go index d0a033ed6..18af1a919 100644 --- a/weed/iamapi/iamapi_server.go +++ b/weed/iamapi/iamapi_server.go @@ -4,6 +4,7 @@ package iamapi import ( "bytes" + "encoding/json" "fmt" "github.com/chrislusf/seaweedfs/weed/filer" "github.com/chrislusf/seaweedfs/weed/pb" @@ -21,6 +22,8 @@ import ( type IamS3ApiConfig interface { GetS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) PutS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) + GetPolicies(policies *Policies) (err error) + PutPolicies(policies *Policies) (err error) } type IamS3ApiConfigure struct { @@ -106,3 +109,41 @@ func (iam IamS3ApiConfigure) PutS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfigurat }, ) } + +func (iam IamS3ApiConfigure) GetPolicies(policies *Policies) (err error) { + var buf bytes.Buffer + err = pb.WithGrpcFilerClient(iam.option.FilerGrpcAddress, iam.option.GrpcDialOption, func(client filer_pb.SeaweedFilerClient) error { + if err = filer.ReadEntry(iam.masterClient, client, filer.IamConfigDirecotry, filer.IamPoliciesFile, &buf); err != nil { + return err + } + return nil + }) + if err != nil { + return err + } + if buf.Len() == 0 { + policies.Policies = make(map[string]PolicyDocument) + return nil + } + if err := json.Unmarshal(buf.Bytes(), policies); err != nil { + return err + } + return nil +} + +func (iam IamS3ApiConfigure) PutPolicies(policies *Policies) (err error) { + var b []byte + if b, err = json.Marshal(policies); err != nil { + return err + } + return pb.WithGrpcFilerClient( + iam.option.FilerGrpcAddress, + iam.option.GrpcDialOption, + func(client filer_pb.SeaweedFilerClient) error { + if err := filer.SaveInsideFiler(client, filer.IamConfigDirecotry, filer.IamPoliciesFile, b); err != nil { + return err + } + return nil + }, + ) +} diff --git a/weed/iamapi/iamapi_test.go b/weed/iamapi/iamapi_test.go index f989626e6..09aaf0ac8 100644 --- a/weed/iamapi/iamapi_test.go +++ b/weed/iamapi/iamapi_test.go @@ -7,29 +7,43 @@ import ( "github.com/aws/aws-sdk-go/service/iam" "github.com/chrislusf/seaweedfs/weed/pb/iam_pb" "github.com/gorilla/mux" + "github.com/jinzhu/copier" "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" "testing" ) -var S3config iam_pb.S3ApiConfiguration var GetS3ApiConfiguration func(s3cfg *iam_pb.S3ApiConfiguration) (err error) var PutS3ApiConfiguration func(s3cfg *iam_pb.S3ApiConfiguration) (err error) +var GetPolicies func(policies *Policies) (err error) +var PutPolicies func(policies *Policies) (err error) + +var s3config = iam_pb.S3ApiConfiguration{} +var policiesFile = Policies{Policies: make(map[string]PolicyDocument)} +var ias = IamApiServer{s3ApiConfig: iamS3ApiConfigureMock{}} type iamS3ApiConfigureMock struct{} func (iam iamS3ApiConfigureMock) GetS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) { - s3cfg = &S3config + _ = copier.Copy(&s3cfg.Identities, &s3config.Identities) return nil } func (iam iamS3ApiConfigureMock) PutS3ApiConfiguration(s3cfg *iam_pb.S3ApiConfiguration) (err error) { - S3config = *s3cfg + _ = copier.Copy(&s3config.Identities, &s3cfg.Identities) return nil } -var a = IamApiServer{} +func (iam iamS3ApiConfigureMock) GetPolicies(policies *Policies) (err error) { + _ = copier.Copy(&policies, &policiesFile) + return nil +} + +func (iam iamS3ApiConfigureMock) PutPolicies(policies *Policies) (err error) { + _ = copier.Copy(&policiesFile, &policies) + return nil +} func TestCreateUser(t *testing.T) { userName := aws.String("Test") @@ -64,17 +78,6 @@ func TestListAccessKeys(t *testing.T) { assert.Equal(t, http.StatusOK, response.Code) } -func TestDeleteUser(t *testing.T) { - userName := aws.String("Test") - params := &iam.DeleteUserInput{UserName: userName} - req, _ := iam.New(session.New()).DeleteUserRequest(params) - _ = req.Build() - out := DeleteUserResponse{} - response, err := executeRequest(req.HTTPRequest, out) - assert.Equal(t, nil, err) - assert.Equal(t, http.StatusNotFound, response.Code) -} - func TestGetUser(t *testing.T) { userName := aws.String("Test") params := &iam.GetUserInput{UserName: userName} @@ -83,7 +86,7 @@ func TestGetUser(t *testing.T) { out := GetUserResponse{} response, err := executeRequest(req.HTTPRequest, out) assert.Equal(t, nil, err) - assert.Equal(t, http.StatusNotFound, response.Code) + assert.Equal(t, http.StatusOK, response.Code) } // Todo flat statement @@ -147,11 +150,32 @@ func TestPutUserPolicy(t *testing.T) { assert.Equal(t, http.StatusOK, response.Code) } +func TestGetUserPolicy(t *testing.T) { + userName := aws.String("Test") + params := &iam.GetUserPolicyInput{UserName: userName, PolicyName: aws.String("S3-read-only-example-bucket")} + req, _ := iam.New(session.New()).GetUserPolicyRequest(params) + _ = req.Build() + out := GetUserPolicyResponse{} + response, err := executeRequest(req.HTTPRequest, out) + assert.Equal(t, nil, err) + assert.Equal(t, http.StatusOK, response.Code) +} + +func TestDeleteUser(t *testing.T) { + userName := aws.String("Test") + params := &iam.DeleteUserInput{UserName: userName} + req, _ := iam.New(session.New()).DeleteUserRequest(params) + _ = req.Build() + out := DeleteUserResponse{} + response, err := executeRequest(req.HTTPRequest, out) + assert.Equal(t, nil, err) + assert.Equal(t, http.StatusOK, response.Code) +} + func executeRequest(req *http.Request, v interface{}) (*httptest.ResponseRecorder, error) { rr := httptest.NewRecorder() apiRouter := mux.NewRouter().SkipClean(true) - a.s3ApiConfig = iamS3ApiConfigureMock{} - apiRouter.Path("/").Methods("POST").HandlerFunc(a.DoActions) + apiRouter.Path("/").Methods("POST").HandlerFunc(ias.DoActions) apiRouter.ServeHTTP(rr, req) return rr, xml.Unmarshal(rr.Body.Bytes(), &v) } From af313dff58bf82a731dbce72535b72f1979d6740 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sat, 10 Apr 2021 23:47:47 -0700 Subject: [PATCH 023/128] add gateway for easier POST and DELETE blobs --- weed/command/command.go | 1 + weed/command/gateway.go | 93 +++++++++++++++++++++++++++++ weed/server/gateway_server.go | 106 ++++++++++++++++++++++++++++++++++ weed/util/http_util.go | 22 +++++++ 4 files changed, 222 insertions(+) create mode 100644 weed/command/gateway.go create mode 100644 weed/server/gateway_server.go diff --git a/weed/command/command.go b/weed/command/command.go index ce754702f..b6efcead2 100644 --- a/weed/command/command.go +++ b/weed/command/command.go @@ -22,6 +22,7 @@ var Commands = []*Command{ cmdFilerReplicate, cmdFilerSynchronize, cmdFix, + cmdGateway, cmdMaster, cmdMount, cmdS3, diff --git a/weed/command/gateway.go b/weed/command/gateway.go new file mode 100644 index 000000000..a2a97889f --- /dev/null +++ b/weed/command/gateway.go @@ -0,0 +1,93 @@ +package command + +import ( + "net/http" + "strconv" + "strings" + "time" + + "github.com/chrislusf/seaweedfs/weed/glog" + "github.com/chrislusf/seaweedfs/weed/server" + "github.com/chrislusf/seaweedfs/weed/util" +) + +var ( + gatewayOptions GatewayOptions +) + +type GatewayOptions struct { + masters *string + filers *string + bindIp *string + port *int + maxMB *int +} + +func init() { + cmdGateway.Run = runGateway // break init cycle + gatewayOptions.masters = cmdGateway.Flag.String("master", "localhost:9333", "comma-separated master servers") + gatewayOptions.filers = cmdGateway.Flag.String("filer", "localhost:8888", "comma-separated filer servers") + gatewayOptions.bindIp = cmdGateway.Flag.String("ip.bind", "localhost", "ip address to bind to") + gatewayOptions.port = cmdGateway.Flag.Int("port", 5647, "gateway http listen port") + gatewayOptions.maxMB = cmdGateway.Flag.Int("maxMB", 4, "split files larger than the limit") +} + +var cmdGateway = &Command{ + UsageLine: "gateway -port=8888 -master=[,]* -filer=[,]*", + Short: "start a gateway server that points to a list of master servers or a list of filers", + Long: `start a gateway server which accepts REST operation to write any blobs, files, or topic messages. + + POST /blobs/ + return a chunk id + DELETE /blobs/ + delete a chunk id + + /* + POST /files/path/to/a/file + save /path/to/a/file on filer + DELETE /files/path/to/a/file + delete /path/to/a/file on filer + + POST /topics/topicName + save on filer to /topics/topicName//ts.json + */ +`, +} + +func runGateway(cmd *Command, args []string) bool { + + util.LoadConfiguration("security", false) + + gatewayOptions.startGateway() + + return true +} + +func (gw *GatewayOptions) startGateway() { + + defaultMux := http.NewServeMux() + + _, gws_err := weed_server.NewGatewayServer(defaultMux, &weed_server.GatewayOption{ + Masters: strings.Split(*gw.masters, ","), + Filers: strings.Split(*gw.filers, ","), + MaxMB: *gw.maxMB, + }) + if gws_err != nil { + glog.Fatalf("Gateway startup error: %v", gws_err) + } + + glog.V(0).Infof("Start Seaweed Gateway %s at %s:%d", util.Version(), *gw.bindIp, *gw.port) + gatewayListener, e := util.NewListener( + *gw.bindIp+":"+strconv.Itoa(*gw.port), + time.Duration(10)*time.Second, + ) + if e != nil { + glog.Fatalf("Filer listener error: %v", e) + } + + httpS := &http.Server{Handler: defaultMux} + if err := httpS.Serve(gatewayListener); err != nil { + glog.Fatalf("Gateway Fail to serve: %v", e) + } + +} diff --git a/weed/server/gateway_server.go b/weed/server/gateway_server.go new file mode 100644 index 000000000..608217ed7 --- /dev/null +++ b/weed/server/gateway_server.go @@ -0,0 +1,106 @@ +package weed_server + +import ( + "github.com/chrislusf/seaweedfs/weed/operation" + "google.golang.org/grpc" + "math/rand" + "net/http" + + "github.com/chrislusf/seaweedfs/weed/util" + + _ "github.com/chrislusf/seaweedfs/weed/filer/cassandra" + _ "github.com/chrislusf/seaweedfs/weed/filer/elastic/v7" + _ "github.com/chrislusf/seaweedfs/weed/filer/etcd" + _ "github.com/chrislusf/seaweedfs/weed/filer/hbase" + _ "github.com/chrislusf/seaweedfs/weed/filer/leveldb" + _ "github.com/chrislusf/seaweedfs/weed/filer/leveldb2" + _ "github.com/chrislusf/seaweedfs/weed/filer/leveldb3" + _ "github.com/chrislusf/seaweedfs/weed/filer/mongodb" + _ "github.com/chrislusf/seaweedfs/weed/filer/mysql" + _ "github.com/chrislusf/seaweedfs/weed/filer/mysql2" + _ "github.com/chrislusf/seaweedfs/weed/filer/postgres" + _ "github.com/chrislusf/seaweedfs/weed/filer/postgres2" + _ "github.com/chrislusf/seaweedfs/weed/filer/redis" + _ "github.com/chrislusf/seaweedfs/weed/filer/redis2" + "github.com/chrislusf/seaweedfs/weed/glog" + _ "github.com/chrislusf/seaweedfs/weed/notification/aws_sqs" + _ "github.com/chrislusf/seaweedfs/weed/notification/gocdk_pub_sub" + _ "github.com/chrislusf/seaweedfs/weed/notification/google_pub_sub" + _ "github.com/chrislusf/seaweedfs/weed/notification/kafka" + _ "github.com/chrislusf/seaweedfs/weed/notification/log" + "github.com/chrislusf/seaweedfs/weed/security" +) + +type GatewayOption struct { + Masters []string + Filers []string + MaxMB int + IsSecure bool +} + +type GatewayServer struct { + option *GatewayOption + secret security.SigningKey + grpcDialOption grpc.DialOption +} + +func NewGatewayServer(defaultMux *http.ServeMux, option *GatewayOption) (fs *GatewayServer, err error) { + + fs = &GatewayServer{ + option: option, + grpcDialOption: security.LoadClientTLS(util.GetViper(), "grpc.client"), + } + + if len(option.Masters) == 0 { + glog.Fatal("master list is required!") + } + + defaultMux.HandleFunc("/blobs/", fs.blobsHandler) + defaultMux.HandleFunc("/files/", fs.filesHandler) + defaultMux.HandleFunc("/topics/", fs.topicsHandler) + + return fs, nil +} + +func (fs *GatewayServer) getMaster() string { + randMaster := rand.Intn(len(fs.option.Masters)) + return fs.option.Masters[randMaster] +} + +func (fs *GatewayServer) blobsHandler(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "DELETE": + chunkId := r.URL.Path[len("/blobs/"):] + fullUrl, err := operation.LookupFileId(fs.getMaster, chunkId) + if err != nil { + writeJsonError(w, r, http.StatusNotFound, err) + return + } + var jwtAuthorization security.EncodedJwt + if fs.option.IsSecure { + jwtAuthorization = operation.LookupJwt(fs.getMaster(), chunkId) + } + body, statusCode, err := util.DeleteProxied(fullUrl, string(jwtAuthorization)) + if err != nil { + writeJsonError(w, r, http.StatusNotFound, err) + return + } + w.WriteHeader(statusCode) + w.Write(body) + case "POST": + submitForClientHandler(w, r, fs.getMaster, fs.grpcDialOption) + } +} + +func (fs *GatewayServer) filesHandler(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "DELETE": + case "POST": + } +} + +func (fs *GatewayServer) topicsHandler(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case "POST": + } +} diff --git a/weed/util/http_util.go b/weed/util/http_util.go index 135d10c45..1c1b2b377 100644 --- a/weed/util/http_util.go +++ b/weed/util/http_util.go @@ -124,6 +124,28 @@ func Delete(url string, jwt string) error { return errors.New(string(body)) } +func DeleteProxied(url string, jwt string) (body []byte, httpStatus int, err error) { + req, err := http.NewRequest("DELETE", url, nil) + if jwt != "" { + req.Header.Set("Authorization", "BEARER "+string(jwt)) + } + if err != nil { + return + } + resp, err := client.Do(req) + if err != nil { + return + } + defer resp.Body.Close() + body, err = ioutil.ReadAll(resp.Body) + if err != nil { + return + } + httpStatus = resp.StatusCode + return +} + + func GetBufferStream(url string, values url.Values, allocatedBytes []byte, eachBuffer func([]byte)) error { r, err := client.PostForm(url, values) if err != nil { From f62c1532745c1dca7d81f1f23d39f18f5c8985d5 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sat, 10 Apr 2021 23:48:18 -0700 Subject: [PATCH 024/128] go fmt --- weed/command/gateway.go | 6 +++--- weed/filesys/dir.go | 1 - weed/operation/upload_content.go | 2 +- weed/util/http_util.go | 1 - 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/weed/command/gateway.go b/weed/command/gateway.go index a2a97889f..28ec326cc 100644 --- a/weed/command/gateway.go +++ b/weed/command/gateway.go @@ -68,9 +68,9 @@ func (gw *GatewayOptions) startGateway() { defaultMux := http.NewServeMux() _, gws_err := weed_server.NewGatewayServer(defaultMux, &weed_server.GatewayOption{ - Masters: strings.Split(*gw.masters, ","), - Filers: strings.Split(*gw.filers, ","), - MaxMB: *gw.maxMB, + Masters: strings.Split(*gw.masters, ","), + Filers: strings.Split(*gw.filers, ","), + MaxMB: *gw.maxMB, }) if gws_err != nil { glog.Fatalf("Gateway startup error: %v", gws_err) diff --git a/weed/filesys/dir.go b/weed/filesys/dir.go index 46457f858..7b918e769 100644 --- a/weed/filesys/dir.go +++ b/weed/filesys/dir.go @@ -372,7 +372,6 @@ func (dir *Dir) Remove(ctx context.Context, req *fuse.RemoveRequest) error { return fuse.EPERM } - if !req.Dir { return dir.removeOneFile(req) } diff --git a/weed/operation/upload_content.go b/weed/operation/upload_content.go index 40e96fd8c..7a7f8aa0c 100644 --- a/weed/operation/upload_content.go +++ b/weed/operation/upload_content.go @@ -235,7 +235,7 @@ func upload_content(uploadUrl string, fillBufferFunction func(w io.Writer) error // print("+") resp, post_err := HttpClient.Do(req) if post_err != nil { - if !strings.Contains(post_err.Error(), "connection reset by peer"){ + if !strings.Contains(post_err.Error(), "connection reset by peer") { glog.Errorf("upload %s %d bytes to %v: %v", filename, originalDataSize, uploadUrl, post_err) debug.PrintStack() } diff --git a/weed/util/http_util.go b/weed/util/http_util.go index 1c1b2b377..1630760b1 100644 --- a/weed/util/http_util.go +++ b/weed/util/http_util.go @@ -145,7 +145,6 @@ func DeleteProxied(url string, jwt string) (body []byte, httpStatus int, err err return } - func GetBufferStream(url string, values url.Values, allocatedBytes []byte, eachBuffer func([]byte)) error { r, err := client.PostForm(url, values) if err != nil { From 0df5b53ad8258414350169a923d887c90742a8b2 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sun, 11 Apr 2021 00:26:28 -0700 Subject: [PATCH 025/128] adjust help message --- weed/command/gateway.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weed/command/gateway.go b/weed/command/gateway.go index 28ec326cc..8a6f852a5 100644 --- a/weed/command/gateway.go +++ b/weed/command/gateway.go @@ -38,7 +38,7 @@ var cmdGateway = &Command{ Long: `start a gateway server which accepts REST operation to write any blobs, files, or topic messages. POST /blobs/ - return a chunk id + upload the blob and return a chunk id DELETE /blobs/ delete a chunk id From 742ab1ec81795d1705bf3be48d56727fddf625df Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sun, 11 Apr 2021 19:47:11 -0700 Subject: [PATCH 026/128] 2.39 --- k8s/seaweedfs/Chart.yaml | 4 ++-- k8s/seaweedfs/values.yaml | 2 +- weed/util/constants.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/k8s/seaweedfs/Chart.yaml b/k8s/seaweedfs/Chart.yaml index d8d069032..c296ce0c6 100644 --- a/k8s/seaweedfs/Chart.yaml +++ b/k8s/seaweedfs/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v1 description: SeaweedFS name: seaweedfs -appVersion: "2.38" -version: 2.38 +appVersion: "2.39" +version: 2.39 diff --git a/k8s/seaweedfs/values.yaml b/k8s/seaweedfs/values.yaml index 19c0c78a1..e615090c1 100644 --- a/k8s/seaweedfs/values.yaml +++ b/k8s/seaweedfs/values.yaml @@ -4,7 +4,7 @@ global: registry: "" repository: "" imageName: chrislusf/seaweedfs - # imageTag: "2.38" - started using {.Chart.appVersion} + # imageTag: "2.39" - started using {.Chart.appVersion} imagePullPolicy: IfNotPresent imagePullSecrets: imagepullsecret restartPolicy: Always diff --git a/weed/util/constants.go b/weed/util/constants.go index 40f4deae2..fce35379d 100644 --- a/weed/util/constants.go +++ b/weed/util/constants.go @@ -5,7 +5,7 @@ import ( ) var ( - VERSION = fmt.Sprintf("%s %d.%02d", sizeLimit, 2, 38) + VERSION = fmt.Sprintf("%s %d.%02d", sizeLimit, 2, 39) COMMIT = "" ) From 519b0e1e4909d875988cee83f8dad5100e3df256 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 12 Apr 2021 11:56:56 -0700 Subject: [PATCH 027/128] filer: upload to a directory without "/" suffix fix https://github.com/chrislusf/seaweedfs/issues/1988 --- weed/server/filer_server_handlers_write_autochunk.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/weed/server/filer_server_handlers_write_autochunk.go b/weed/server/filer_server_handlers_write_autochunk.go index 2808042c7..c4f10d94e 100644 --- a/weed/server/filer_server_handlers_write_autochunk.go +++ b/weed/server/filer_server_handlers_write_autochunk.go @@ -142,6 +142,14 @@ func (fs *FilerServer) saveMetaData(ctx context.Context, r *http.Request, fileNa if fileName != "" { path += fileName } + } else { + if fileName != "" { + if possibleDirEntry, findDirErr := fs.filer.FindEntry(ctx, util.FullPath(path)); findDirErr == nil { + if possibleDirEntry.IsDirectory() { + path += "/" + fileName + } + } + } } var entry *filer.Entry From 1e033d45b80ce12bb4a0fc57cc76e492856c2f95 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 12 Apr 2021 12:04:53 -0700 Subject: [PATCH 028/128] simpler logic related to https://github.com/chrislusf/seaweedfs/pull/1981 --- weed/server/filer_server_handlers_read.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weed/server/filer_server_handlers_read.go b/weed/server/filer_server_handlers_read.go index cdfdf9b49..6bc09e953 100644 --- a/weed/server/filer_server_handlers_read.go +++ b/weed/server/filer_server_handlers_read.go @@ -79,7 +79,7 @@ func (fs *FilerServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request, w.Header().Set("Last-Modified", entry.Attr.Mtime.UTC().Format(http.TimeFormat)) if r.Header.Get("If-Modified-Since") != "" { if t, parseError := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); parseError == nil { - if t.After(entry.Attr.Mtime) || t.Equal(entry.Attr.Mtime) { + if !t.Before(entry.Attr.Mtime) { w.WriteHeader(http.StatusNotModified) return } From 5985a7d38d4823b868117f93dc958dc9635e8950 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 12 Apr 2021 21:15:51 -0700 Subject: [PATCH 029/128] add log level possible values fix https://github.com/chrislusf/seaweedfs/issues/1989 --- weed/glog/glog.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weed/glog/glog.go b/weed/glog/glog.go index adb6ab5aa..352a7e185 100644 --- a/weed/glog/glog.go +++ b/weed/glog/glog.go @@ -398,7 +398,7 @@ type flushSyncWriter interface { func init() { flag.BoolVar(&logging.toStderr, "logtostderr", false, "log to standard error instead of files") flag.BoolVar(&logging.alsoToStderr, "alsologtostderr", true, "log to standard error as well as files") - flag.Var(&logging.verbosity, "v", "log level for V logs") + flag.Var(&logging.verbosity, "v", "log levels [0|1|2|3|4], default to 0") flag.Var(&logging.stderrThreshold, "stderrthreshold", "logs at or above this threshold go to stderr") flag.Var(&logging.vmodule, "vmodule", "comma-separated list of pattern=N settings for file-filtered logging") flag.Var(&logging.traceLocation, "log_backtrace_at", "when logging hits line file:N, emit a stack trace") From ca0f07a188152ac8ea6b6a54f628a30f61634515 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 14 Apr 2021 00:29:58 -0700 Subject: [PATCH 030/128] move file reader, entryViewCache to file handle reduce file object size --- weed/filesys/dir.go | 3 +-- weed/filesys/file.go | 45 +------------------------------------- weed/filesys/filehandle.go | 25 +++++++++++---------- weed/filesys/wfs.go | 2 +- 4 files changed, 16 insertions(+), 59 deletions(-) diff --git a/weed/filesys/dir.go b/weed/filesys/dir.go index 7b918e769..391d28ebb 100644 --- a/weed/filesys/dir.go +++ b/weed/filesys/dir.go @@ -109,7 +109,6 @@ func (dir *Dir) newFile(name string, entry *filer_pb.Entry) fs.Node { dir: dir, wfs: dir.wfs, entry: entry, - entryViewCache: nil, } }) f.(*File).dir = dir // in case dir node was created later @@ -408,7 +407,7 @@ func (dir *Dir) removeOneFile(req *fuse.RemoveRequest) error { dir.wfs.fsNodeCache.DeleteFsNode(filePath) if fsNode != nil { if file, ok := fsNode.(*File); ok { - file.clearEntry() + file.entry = nil } } diff --git a/weed/filesys/file.go b/weed/filesys/file.go index 2433be590..202951082 100644 --- a/weed/filesys/file.go +++ b/weed/filesys/file.go @@ -2,10 +2,8 @@ package filesys import ( "context" - "io" "os" "sort" - "sync" "time" "github.com/seaweedfs/fuse" @@ -34,10 +32,7 @@ type File struct { dir *Dir wfs *WFS entry *filer_pb.Entry - entryLock sync.RWMutex - entryViewCache []filer.VisibleInterval isOpen int - reader io.ReaderAt dirtyMetadata bool } @@ -154,8 +149,6 @@ func (file *File) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *f } } entry.Chunks = chunks - file.entryViewCache, _ = filer.NonOverlappingVisibleIntervals(file.wfs.LookupFn(), chunks) - file.setReader(nil) } entry.Attributes.FileSize = req.Size file.dirtyMetadata = true @@ -274,7 +267,6 @@ func (file *File) Forget() { glog.V(4).Infof("Forget file %s", t) file.wfs.fsNodeCache.DeleteFsNode(t) file.wfs.ReleaseHandle(t, 0) - file.setReader(nil) } func (file *File) maybeLoadEntry(ctx context.Context) (entry *filer_pb.Entry, err error) { @@ -294,7 +286,7 @@ func (file *File) maybeLoadEntry(ctx context.Context) (entry *filer_pb.Entry, er return entry, err } if entry != nil { - file.setEntry(entry) + file.entry = entry } else { glog.Warningf("maybeLoadEntry not found entry %s/%s: %v", file.dir.FullPath(), file.Name, err) } @@ -336,44 +328,11 @@ func (file *File) addChunks(chunks []*filer_pb.FileChunk) { return lessThan(chunks[i], chunks[j]) }) - // add to entry view cache - for _, chunk := range chunks { - file.entryViewCache = filer.MergeIntoVisibles(file.entryViewCache, chunk) - } - - file.setReader(nil) - glog.V(4).Infof("%s existing %d chunks adds %d more", file.fullpath(), len(entry.Chunks), len(chunks)) entry.Chunks = append(entry.Chunks, newChunks...) } -func (file *File) setReader(reader io.ReaderAt) { - r := file.reader - if r != nil { - if closer, ok := r.(io.Closer); ok { - closer.Close() - } - } - file.reader = reader -} - -func (file *File) setEntry(entry *filer_pb.Entry) { - file.entryLock.Lock() - defer file.entryLock.Unlock() - file.entry = entry - file.entryViewCache, _ = filer.NonOverlappingVisibleIntervals(file.wfs.LookupFn(), entry.Chunks) - file.setReader(nil) -} - -func (file *File) clearEntry() { - file.entryLock.Lock() - defer file.entryLock.Unlock() - file.entry = nil - file.entryViewCache = nil - file.setReader(nil) -} - func (file *File) saveEntry(entry *filer_pb.Entry) error { return file.wfs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error { @@ -400,7 +359,5 @@ func (file *File) saveEntry(entry *filer_pb.Entry) error { } func (file *File) getEntry() *filer_pb.Entry { - file.entryLock.RLock() - defer file.entryLock.RUnlock() return file.entry } diff --git a/weed/filesys/filehandle.go b/weed/filesys/filehandle.go index f04952e96..0236fd1cd 100644 --- a/weed/filesys/filehandle.go +++ b/weed/filesys/filehandle.go @@ -20,9 +20,11 @@ import ( type FileHandle struct { // cache file has been written to - dirtyPages *ContinuousDirtyPages - contentType string - handle uint64 + dirtyPages *ContinuousDirtyPages + entryViewCache []filer.VisibleInterval + reader io.ReaderAt + contentType string + handle uint64 sync.Mutex f *File @@ -125,20 +127,20 @@ func (fh *FileHandle) readFromChunks(buff []byte, offset int64) (int64, error) { } var chunkResolveErr error - if fh.f.entryViewCache == nil { - fh.f.entryViewCache, chunkResolveErr = filer.NonOverlappingVisibleIntervals(fh.f.wfs.LookupFn(), entry.Chunks) + if fh.entryViewCache == nil { + fh.entryViewCache, chunkResolveErr = filer.NonOverlappingVisibleIntervals(fh.f.wfs.LookupFn(), entry.Chunks) if chunkResolveErr != nil { return 0, fmt.Errorf("fail to resolve chunk manifest: %v", chunkResolveErr) } - fh.f.setReader(nil) + fh.reader = nil } - reader := fh.f.reader + reader := fh.reader if reader == nil { - chunkViews := filer.ViewFromVisibleIntervals(fh.f.entryViewCache, 0, math.MaxInt64) + chunkViews := filer.ViewFromVisibleIntervals(fh.entryViewCache, 0, math.MaxInt64) reader = filer.NewChunkReaderAtFromClient(fh.f.wfs.LookupFn(), chunkViews, fh.f.wfs.chunkCache, fileSize) } - fh.f.setReader(reader) + fh.reader = reader totalRead, err := reader.ReadAt(buff, offset) @@ -200,8 +202,6 @@ func (fh *FileHandle) Release(ctx context.Context, req *fuse.ReleaseRequest) err fh.Lock() defer fh.Unlock() - fh.f.entryViewCache = nil - if fh.f.isOpen <= 0 { glog.V(0).Infof("Release reset %s open count %d => %d", fh.f.Name, fh.f.isOpen, 0) fh.f.isOpen = 0 @@ -211,9 +211,10 @@ func (fh *FileHandle) Release(ctx context.Context, req *fuse.ReleaseRequest) err if fh.f.isOpen == 1 { fh.f.isOpen-- + fh.entryViewCache = nil + fh.reader = nil fh.f.wfs.ReleaseHandle(fh.f.fullpath(), fuse.HandleID(fh.handle)) - fh.f.setReader(nil) } return nil diff --git a/weed/filesys/wfs.go b/weed/filesys/wfs.go index ba5eb4b6b..aaff1377b 100644 --- a/weed/filesys/wfs.go +++ b/weed/filesys/wfs.go @@ -111,7 +111,7 @@ func NewSeaweedFileSystem(option *Option) *WFS { if err := wfs.Server.InvalidateNodeData(file); err != nil { glog.V(4).Infof("InvalidateNodeData %s : %v", filePath, err) } - file.clearEntry() + file.entry = nil } } dir, name := filePath.DirAndName() From 3f3268cd1bb8c2824864b9293d419649285f435c Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 14 Apr 2021 00:30:16 -0700 Subject: [PATCH 031/128] go fmt --- weed/filesys/dir.go | 8 ++++---- weed/filesys/file.go | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/weed/filesys/dir.go b/weed/filesys/dir.go index 391d28ebb..63631b857 100644 --- a/weed/filesys/dir.go +++ b/weed/filesys/dir.go @@ -105,10 +105,10 @@ func (dir *Dir) Fsync(ctx context.Context, req *fuse.FsyncRequest) error { func (dir *Dir) newFile(name string, entry *filer_pb.Entry) fs.Node { f := dir.wfs.fsNodeCache.EnsureFsNode(util.NewFullPath(dir.FullPath(), name), func() fs.Node { return &File{ - Name: name, - dir: dir, - wfs: dir.wfs, - entry: entry, + Name: name, + dir: dir, + wfs: dir.wfs, + entry: entry, } }) f.(*File).dir = dir // in case dir node was created later diff --git a/weed/filesys/file.go b/weed/filesys/file.go index 202951082..2d1c9a86e 100644 --- a/weed/filesys/file.go +++ b/weed/filesys/file.go @@ -28,12 +28,12 @@ var _ = fs.NodeListxattrer(&File{}) var _ = fs.NodeForgetter(&File{}) type File struct { - Name string - dir *Dir - wfs *WFS - entry *filer_pb.Entry - isOpen int - dirtyMetadata bool + Name string + dir *Dir + wfs *WFS + entry *filer_pb.Entry + isOpen int + dirtyMetadata bool } func (file *File) fullpath() util.FullPath { From 90677e1097e18567999d98436d8c1e8eba4cc611 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 14 Apr 2021 01:33:21 -0700 Subject: [PATCH 032/128] ensure to call line.Close() fix https://github.com/chrislusf/seaweedfs/issues/1995 similar to https://github.com/peterh/liner/issues/104 --- weed/shell/shell_liner.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/weed/shell/shell_liner.go b/weed/shell/shell_liner.go index d79f67032..1dd611ca5 100644 --- a/weed/shell/shell_liner.go +++ b/weed/shell/shell_liner.go @@ -2,6 +2,7 @@ package shell import ( "fmt" + "github.com/chrislusf/seaweedfs/weed/util/grace" "io" "os" "path" @@ -25,6 +26,9 @@ func RunShell(options ShellOptions) { line = liner.NewLiner() defer line.Close() + grace.OnInterrupt(func() { + line.Close() + }) line.SetCtrlCAborts(true) From 270645f8d7b706f8eeb7ecb7ecc985efa481ca59 Mon Sep 17 00:00:00 2001 From: qieqieplus Date: Wed, 14 Apr 2021 18:29:28 +0800 Subject: [PATCH 033/128] fix #1996 --- weed/operation/assign_file_id.go | 1 + 1 file changed, 1 insertion(+) diff --git a/weed/operation/assign_file_id.go b/weed/operation/assign_file_id.go index cc1359961..ffd3e4938 100644 --- a/weed/operation/assign_file_id.go +++ b/weed/operation/assign_file_id.go @@ -86,6 +86,7 @@ func Assign(masterFn GetMasterFn, grpcDialOption grpc.DialOption, primaryRequest continue } + break } return ret, lastError From 606c6ae8e8c5a0f11c0b6b577b679e6e74797d2c Mon Sep 17 00:00:00 2001 From: zhanghc Date: Wed, 14 Apr 2021 18:57:49 +0800 Subject: [PATCH 034/128] fix volume-service helm template indentation --- k8s/seaweedfs/templates/volume-service.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k8s/seaweedfs/templates/volume-service.yaml b/k8s/seaweedfs/templates/volume-service.yaml index a2b0b540e..0a9173fde 100644 --- a/k8s/seaweedfs/templates/volume-service.yaml +++ b/k8s/seaweedfs/templates/volume-service.yaml @@ -23,6 +23,6 @@ spec: targetPort: {{ .Values.volume.metricsPort }} protocol: TCP {{- end }} -selector: + selector: app: {{ template "seaweedfs.name" . }} component: volume \ No newline at end of file From ff4c1d59652953ea2d81ad317ba92f34ab5e3c0c Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 14 Apr 2021 10:04:09 -0700 Subject: [PATCH 035/128] adjust logging fix https://github.com/chrislusf/seaweedfs/issues/1999 --- weed/command/export.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weed/command/export.go b/weed/command/export.go index f100f3af5..1c32e1050 100644 --- a/weed/command/export.go +++ b/weed/command/export.go @@ -215,7 +215,7 @@ func runExport(cmd *Command, args []string) bool { err = storage.ScanVolumeFile(util.ResolvePath(*export.dir), *export.collection, vid, storage.NeedleMapInMemory, volumeFileScanner) if err != nil && err != io.EOF { - glog.Fatalf("Export Volume File [ERROR] %s\n", err) + glog.Errorf("Export Volume File [ERROR] %s\n", err) } return true } From 9d50867d08557acf389878c6f42bd22d0c2cc709 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 14 Apr 2021 10:26:26 -0700 Subject: [PATCH 036/128] volume.tier.move: avoid data loss when destination volume server already has the volume fix https://github.com/chrislusf/seaweedfs/issues/2001 --- weed/shell/command_volume_tier_move.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weed/shell/command_volume_tier_move.go b/weed/shell/command_volume_tier_move.go index f7fa94031..d6a49d6e1 100644 --- a/weed/shell/command_volume_tier_move.go +++ b/weed/shell/command_volume_tier_move.go @@ -133,7 +133,7 @@ func doVolumeTierMove(commandEnv *CommandEnv, writer io.Writer, collection strin // remove the remaining replicas for _, loc := range locations { - if loc.Url != sourceVolumeServer { + if loc.Url != dst.dataNode.Id { if err = deleteVolume(commandEnv.option.GrpcDialOption, vid, loc.Url); err != nil { fmt.Fprintf(writer, "failed to delete volume %d on %s\n", vid, loc.Url) } From e75633c64fd4b93339787933dbbf4d8fb93ccff1 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 14 Apr 2021 12:40:13 -0700 Subject: [PATCH 037/128] volume.check.disk: break loop for read only volumes fix https://github.com/chrislusf/seaweedfs/issues/2002 --- weed/shell/command_volume_check_disk.go | 1 + 1 file changed, 1 insertion(+) diff --git a/weed/shell/command_volume_check_disk.go b/weed/shell/command_volume_check_disk.go index 5a0d46869..0f156ac2f 100644 --- a/weed/shell/command_volume_check_disk.go +++ b/weed/shell/command_volume_check_disk.go @@ -85,6 +85,7 @@ func (c *commandVolumeCheckDisk) Do(args []string, commandEnv *CommandEnv, write } if a.info.ReadOnly || b.info.ReadOnly { fmt.Fprintf(writer, "skipping readonly volume %d on %s and %s\n", a.info.Id, a.location.dataNode.Id, b.location.dataNode.Id) + replicas = replicas[1:] continue } From fc0cbf565f273eebe8483d1d0ecba34f950f28ab Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 14 Apr 2021 15:37:24 -0700 Subject: [PATCH 038/128] add option to obfuscate the file names --- weed/shell/command_fs_meta_save.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/weed/shell/command_fs_meta_save.go b/weed/shell/command_fs_meta_save.go index ed19e3d01..d154fc12b 100644 --- a/weed/shell/command_fs_meta_save.go +++ b/weed/shell/command_fs_meta_save.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "os" + "path/filepath" "sync" "sync/atomic" "time" @@ -46,6 +47,7 @@ func (c *commandFsMetaSave) Do(args []string, commandEnv *CommandEnv, writer io. fsMetaSaveCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError) verbose := fsMetaSaveCommand.Bool("v", false, "print out each processed files") outputFileName := fsMetaSaveCommand.String("o", "", "output the meta data to this file") + isObfuscate := fsMetaSaveCommand.Bool("obfuscate", false, "obfuscate the file names") // chunksFileName := fsMetaSaveCommand.String("chunks", "", "output all the chunks to this file") if err = fsMetaSaveCommand.Parse(args); err != nil { return nil @@ -69,6 +71,11 @@ func (c *commandFsMetaSave) Do(args []string, commandEnv *CommandEnv, writer io. } defer dst.Close() + var cipherKey util.CipherKey + if *isObfuscate { + cipherKey = util.GenCipherKey() + } + err = doTraverseBfsAndSaving(commandEnv, writer, path, *verbose, func(outputChan chan interface{}) { sizeBuf := make([]byte, 4) for item := range outputChan { @@ -78,6 +85,12 @@ func (c *commandFsMetaSave) Do(args []string, commandEnv *CommandEnv, writer io. dst.Write(b) } }, func(entry *filer_pb.FullEntry, outputChan chan interface{}) (err error) { + if !entry.Entry.IsDirectory { + ext := filepath.Ext(entry.Entry.Name) + if encrypted, encErr := util.Encrypt([]byte(entry.Entry.Name), cipherKey); encErr == nil { + entry.Entry.Name = util.Base64Encode(encrypted)[:len(entry.Entry.Name)] + ext + } + } bytes, err := proto.Marshal(entry) if err != nil { fmt.Fprintf(writer, "marshall error: %v\n", err) From ffee470cfcc2a76fe11a5903b6ccf23672ba8ba8 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 14 Apr 2021 15:47:09 -0700 Subject: [PATCH 039/128] Update release.yml --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2a5e66a6e..dba704800 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -60,5 +60,5 @@ jobs: ldflags: -extldflags -static -X github.com/chrislusf/seaweedfs/weed/util.COMMIT=${{github.sha}} # Where to run `go build .` project_path: weed - binary_name: weed- + binary_name: weed asset_name: "weed-${{ env.BUILD_TIME }}-${{ matrix.goos }}-${{ matrix.goarch }}" From c04b7e106f1e83101580d57fa4ea33e0a4416194 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 14 Apr 2021 20:26:13 -0700 Subject: [PATCH 040/128] mount: remove entry from Dir object --- weed/filesys/dir.go | 81 ++++++++++++++++++++++----------------------- weed/filesys/wfs.go | 3 +- 2 files changed, 41 insertions(+), 43 deletions(-) diff --git a/weed/filesys/dir.go b/weed/filesys/dir.go index 63631b857..087f31588 100644 --- a/weed/filesys/dir.go +++ b/weed/filesys/dir.go @@ -53,17 +53,18 @@ func (dir *Dir) Attr(ctx context.Context, attr *fuse.Attr) error { return nil } - if err := dir.maybeLoadEntry(); err != nil { + entry, err := dir.maybeLoadEntry() + if err != nil { glog.V(3).Infof("dir Attr %s,err: %+v", dir.FullPath(), err) return err } // attr.Inode = util.FullPath(dir.FullPath()).AsInode() - attr.Mode = os.FileMode(dir.entry.Attributes.FileMode) | os.ModeDir - attr.Mtime = time.Unix(dir.entry.Attributes.Mtime, 0) - attr.Crtime = time.Unix(dir.entry.Attributes.Crtime, 0) - attr.Gid = dir.entry.Attributes.Gid - attr.Uid = dir.entry.Attributes.Uid + attr.Mode = os.FileMode(entry.Attributes.FileMode) | os.ModeDir + attr.Mtime = time.Unix(entry.Attributes.Mtime, 0) + attr.Crtime = time.Unix(entry.Attributes.Crtime, 0) + attr.Gid = entry.Attributes.Gid + attr.Uid = entry.Attributes.Uid glog.V(4).Infof("dir Attr %s, attr: %+v", dir.FullPath(), attr) @@ -74,11 +75,12 @@ func (dir *Dir) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *f glog.V(4).Infof("dir Getxattr %s", dir.FullPath()) - if err := dir.maybeLoadEntry(); err != nil { + entry, err := dir.maybeLoadEntry() + if err != nil { return err } - return getxattr(dir.entry, req, resp) + return getxattr(entry, req, resp) } func (dir *Dir) setRootDirAttributes(attr *fuse.Attr) { @@ -115,10 +117,10 @@ func (dir *Dir) newFile(name string, entry *filer_pb.Entry) fs.Node { return f } -func (dir *Dir) newDirectory(fullpath util.FullPath, entry *filer_pb.Entry) fs.Node { +func (dir *Dir) newDirectory(fullpath util.FullPath) fs.Node { d := dir.wfs.fsNodeCache.EnsureFsNode(fullpath, func() fs.Node { - return &Dir{name: entry.Name, wfs: dir.wfs, entry: entry, parent: dir} + return &Dir{name: fullpath.Name(), wfs: dir.wfs, parent: dir} }) d.(*Dir).parent = dir // in case dir node was created later return d @@ -138,7 +140,7 @@ func (dir *Dir) Create(ctx context.Context, req *fuse.CreateRequest, } var node fs.Node if request.Entry.IsDirectory { - node = dir.newDirectory(util.NewFullPath(dir.FullPath(), req.Name), request.Entry) + node = dir.newDirectory(util.NewFullPath(dir.FullPath(), req.Name)) return node, nil, nil } @@ -250,7 +252,7 @@ func (dir *Dir) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, err }) if err == nil { - node := dir.newDirectory(util.NewFullPath(dir.FullPath(), req.Name), newEntry) + node := dir.newDirectory(util.NewFullPath(dir.FullPath(), req.Name)) return node, nil } @@ -290,7 +292,7 @@ func (dir *Dir) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse. if entry != nil { if entry.IsDirectory { - node = dir.newDirectory(fullFilePath, entry) + node = dir.newDirectory(fullFilePath) } else { node = dir.newFile(req.Name, entry) } @@ -450,27 +452,28 @@ func (dir *Dir) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fus glog.V(4).Infof("%v dir setattr %+v", dir.FullPath(), req) - if err := dir.maybeLoadEntry(); err != nil { + entry, err := dir.maybeLoadEntry() + if err != nil { return err } if req.Valid.Mode() { - dir.entry.Attributes.FileMode = uint32(req.Mode) + entry.Attributes.FileMode = uint32(req.Mode) } if req.Valid.Uid() { - dir.entry.Attributes.Uid = req.Uid + entry.Attributes.Uid = req.Uid } if req.Valid.Gid() { - dir.entry.Attributes.Gid = req.Gid + entry.Attributes.Gid = req.Gid } if req.Valid.Mtime() { - dir.entry.Attributes.Mtime = req.Mtime.Unix() + entry.Attributes.Mtime = req.Mtime.Unix() } - return dir.saveEntry() + return dir.saveEntry(entry) } @@ -482,15 +485,16 @@ func (dir *Dir) Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error { glog.V(4).Infof("dir Setxattr %s: %s", dir.FullPath(), req.Name) - if err := dir.maybeLoadEntry(); err != nil { + entry, err := dir.maybeLoadEntry() + if err != nil { return err } - if err := setxattr(dir.entry, req); err != nil { + if err := setxattr(entry, req); err != nil { return err } - return dir.saveEntry() + return dir.saveEntry(entry) } @@ -502,15 +506,16 @@ func (dir *Dir) Removexattr(ctx context.Context, req *fuse.RemovexattrRequest) e glog.V(4).Infof("dir Removexattr %s: %s", dir.FullPath(), req.Name) - if err := dir.maybeLoadEntry(); err != nil { + entry, err := dir.maybeLoadEntry() + if err != nil { return err } - if err := removexattr(dir.entry, req); err != nil { + if err := removexattr(entry, req); err != nil { return err } - return dir.saveEntry() + return dir.saveEntry(entry) } @@ -518,11 +523,12 @@ func (dir *Dir) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp glog.V(4).Infof("dir Listxattr %s", dir.FullPath()) - if err := dir.maybeLoadEntry(); err != nil { + entry, err := dir.maybeLoadEntry() + if err != nil { return err } - if err := listxattr(dir.entry, req, resp); err != nil { + if err := listxattr(entry, req, resp); err != nil { return err } @@ -536,30 +542,23 @@ func (dir *Dir) Forget() { dir.wfs.fsNodeCache.DeleteFsNode(util.FullPath(dir.FullPath())) } -func (dir *Dir) maybeLoadEntry() error { - if dir.entry == nil { - parentDirPath, name := util.FullPath(dir.FullPath()).DirAndName() - entry, err := dir.wfs.maybeLoadEntry(parentDirPath, name) - if err != nil { - return err - } - dir.entry = entry - } - return nil +func (dir *Dir) maybeLoadEntry() (*filer_pb.Entry, error) { + parentDirPath, name := util.FullPath(dir.FullPath()).DirAndName() + return dir.wfs.maybeLoadEntry(parentDirPath, name) } -func (dir *Dir) saveEntry() error { +func (dir *Dir) saveEntry(entry *filer_pb.Entry) error { parentDir, name := util.FullPath(dir.FullPath()).DirAndName() return dir.wfs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error { - dir.wfs.mapPbIdFromLocalToFiler(dir.entry) - defer dir.wfs.mapPbIdFromFilerToLocal(dir.entry) + dir.wfs.mapPbIdFromLocalToFiler(entry) + defer dir.wfs.mapPbIdFromFilerToLocal(entry) request := &filer_pb.UpdateEntryRequest{ Directory: parentDir, - Entry: dir.entry, + Entry: entry, Signatures: []int32{dir.wfs.signature}, } diff --git a/weed/filesys/wfs.go b/weed/filesys/wfs.go index aaff1377b..c2937f914 100644 --- a/weed/filesys/wfs.go +++ b/weed/filesys/wfs.go @@ -131,8 +131,7 @@ func NewSeaweedFileSystem(option *Option) *WFS { wfs.metaCache.Shutdown() }) - entry, _ := filer_pb.GetEntry(wfs, util.FullPath(wfs.option.FilerMountRootPath)) - wfs.root = &Dir{name: wfs.option.FilerMountRootPath, wfs: wfs, entry: entry} + wfs.root = &Dir{name: wfs.option.FilerMountRootPath, wfs: wfs} wfs.fsNodeCache = newFsCache(wfs.root) if wfs.option.ConcurrentWriters > 0 { From 6bc09b18c4a6ec75e3febe48393d3b0525bac486 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 14 Apr 2021 20:26:56 -0700 Subject: [PATCH 041/128] truncate is a bit faster to reuse the storage --- weed/util/chunk_cache/chunk_cache_on_disk.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/weed/util/chunk_cache/chunk_cache_on_disk.go b/weed/util/chunk_cache/chunk_cache_on_disk.go index d724e925e..6f87a9a06 100644 --- a/weed/util/chunk_cache/chunk_cache_on_disk.go +++ b/weed/util/chunk_cache/chunk_cache_on_disk.go @@ -88,15 +88,17 @@ func (v *ChunkCacheVolume) Shutdown() { } } -func (v *ChunkCacheVolume) destroy() { +func (v *ChunkCacheVolume) doReset() { v.Shutdown() - os.Remove(v.fileName + ".dat") - os.Remove(v.fileName + ".idx") + os.Truncate(v.fileName + ".dat", 0) + os.Truncate(v.fileName + ".idx", 0) + glog.V(4).Infof("cache removeAll %s ...", v.fileName + ".ldb") os.RemoveAll(v.fileName + ".ldb") + glog.V(4).Infof("cache removed %s", v.fileName + ".ldb") } func (v *ChunkCacheVolume) Reset() (*ChunkCacheVolume, error) { - v.destroy() + v.doReset() return LoadOrCreateChunkCacheVolume(v.fileName, v.sizeLimit) } From 1adc8f86ea66fdc78d148b0a4e848c59a9ce4f86 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 14 Apr 2021 20:49:15 -0700 Subject: [PATCH 042/128] lighten up File object file.entry only exists when file.isOpen --- weed/filesys/dir.go | 11 +++++------ weed/filesys/dir_link.go | 13 +++++-------- weed/filesys/file.go | 2 +- weed/filesys/filehandle.go | 1 + weed/filesys/wfs.go | 3 ++- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/weed/filesys/dir.go b/weed/filesys/dir.go index 087f31588..f64b4ea80 100644 --- a/weed/filesys/dir.go +++ b/weed/filesys/dir.go @@ -104,13 +104,12 @@ func (dir *Dir) Fsync(ctx context.Context, req *fuse.FsyncRequest) error { return nil } -func (dir *Dir) newFile(name string, entry *filer_pb.Entry) fs.Node { +func (dir *Dir) newFile(name string) fs.Node { f := dir.wfs.fsNodeCache.EnsureFsNode(util.NewFullPath(dir.FullPath(), name), func() fs.Node { return &File{ Name: name, dir: dir, wfs: dir.wfs, - entry: entry, } }) f.(*File).dir = dir // in case dir node was created later @@ -144,7 +143,7 @@ func (dir *Dir) Create(ctx context.Context, req *fuse.CreateRequest, return node, nil, nil } - node = dir.newFile(req.Name, request.Entry) + node = dir.newFile(req.Name) file := node.(*File) fh := dir.wfs.AcquireHandle(file, req.Uid, req.Gid) return file, fh, nil @@ -157,13 +156,13 @@ func (dir *Dir) Mknod(ctx context.Context, req *fuse.MknodRequest) (fs.Node, err return nil, fuse.EPERM } - request, err := dir.doCreateEntry(req.Name, req.Mode, req.Uid, req.Gid, false) + _, err := dir.doCreateEntry(req.Name, req.Mode, req.Uid, req.Gid, false) if err != nil { return nil, err } var node fs.Node - node = dir.newFile(req.Name, request.Entry) + node = dir.newFile(req.Name) return node, nil } @@ -294,7 +293,7 @@ func (dir *Dir) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse. if entry.IsDirectory { node = dir.newDirectory(fullFilePath) } else { - node = dir.newFile(req.Name, entry) + node = dir.newFile(req.Name) } // resp.EntryValid = time.Second diff --git a/weed/filesys/dir_link.go b/weed/filesys/dir_link.go index 6266e492d..de330e7a4 100644 --- a/weed/filesys/dir_link.go +++ b/weed/filesys/dir_link.go @@ -35,11 +35,11 @@ func (dir *Dir) Link(ctx context.Context, req *fuse.LinkRequest, old fs.Node) (f glog.V(4).Infof("Link: %v/%v -> %v/%v", oldFile.dir.FullPath(), oldFile.Name, dir.FullPath(), req.NewName) - if _, err := oldFile.maybeLoadEntry(ctx); err != nil { + oldEntry, err := oldFile.maybeLoadEntry(ctx) + if err != nil { return nil, err } - oldEntry := oldFile.getEntry() if oldEntry == nil { return nil, fuse.EIO } @@ -72,7 +72,7 @@ func (dir *Dir) Link(ctx context.Context, req *fuse.LinkRequest, old fs.Node) (f } // apply changes to the filer, and also apply to local metaCache - err := dir.wfs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error { + err = dir.wfs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error { dir.wfs.mapPbIdFromLocalToFiler(request.Entry) defer dir.wfs.mapPbIdFromFilerToLocal(request.Entry) @@ -97,11 +97,8 @@ func (dir *Dir) Link(ctx context.Context, req *fuse.LinkRequest, old fs.Node) (f } // create new file node - newNode := dir.newFile(req.NewName, request.Entry) + newNode := dir.newFile(req.NewName) newFile := newNode.(*File) - if _, err := newFile.maybeLoadEntry(ctx); err != nil { - return nil, err - } return newFile, err @@ -147,7 +144,7 @@ func (dir *Dir) Symlink(ctx context.Context, req *fuse.SymlinkRequest) (fs.Node, return nil }) - symlink := dir.newFile(req.NewName, request.Entry) + symlink := dir.newFile(req.NewName) return symlink, err diff --git a/weed/filesys/file.go b/weed/filesys/file.go index 2d1c9a86e..a6bc734f3 100644 --- a/weed/filesys/file.go +++ b/weed/filesys/file.go @@ -286,7 +286,7 @@ func (file *File) maybeLoadEntry(ctx context.Context) (entry *filer_pb.Entry, er return entry, err } if entry != nil { - file.entry = entry + // file.entry = entry } else { glog.Warningf("maybeLoadEntry not found entry %s/%s: %v", file.dir.FullPath(), file.Name, err) } diff --git a/weed/filesys/filehandle.go b/weed/filesys/filehandle.go index 0236fd1cd..240d25a3d 100644 --- a/weed/filesys/filehandle.go +++ b/weed/filesys/filehandle.go @@ -211,6 +211,7 @@ func (fh *FileHandle) Release(ctx context.Context, req *fuse.ReleaseRequest) err if fh.f.isOpen == 1 { fh.f.isOpen-- + fh.f.entry = nil fh.entryViewCache = nil fh.reader = nil diff --git a/weed/filesys/wfs.go b/weed/filesys/wfs.go index c2937f914..4b73aaa05 100644 --- a/weed/filesys/wfs.go +++ b/weed/filesys/wfs.go @@ -162,8 +162,9 @@ func (wfs *WFS) AcquireHandle(file *File, uid, gid uint32) (fileHandle *FileHand } } + entry, _ := file.maybeLoadEntry(context.Background()) + file.entry = entry fileHandle = newFileHandle(file, uid, gid) - file.maybeLoadEntry(context.Background()) file.isOpen++ wfs.handles[inodeId] = fileHandle From e41766feb6ca05090ab3730762c2b6e9093ea4f7 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 14 Apr 2021 22:38:34 -0700 Subject: [PATCH 043/128] fuse mount: dir lookup avoids extra conversion to filer_pb.Entry object --- weed/filesys/dir.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/weed/filesys/dir.go b/weed/filesys/dir.go index f64b4ea80..e51fbbee5 100644 --- a/weed/filesys/dir.go +++ b/weed/filesys/dir.go @@ -272,25 +272,25 @@ func (dir *Dir) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse. glog.Errorf("dir Lookup %s: %v", dirPath, visitErr) return nil, fuse.EIO } - cachedEntry, cacheErr := dir.wfs.metaCache.FindEntry(context.Background(), fullFilePath) + localEntry, cacheErr := dir.wfs.metaCache.FindEntry(context.Background(), fullFilePath) if cacheErr == filer_pb.ErrNotFound { return nil, fuse.ENOENT } - entry := cachedEntry.ToProtoEntry() - if entry == nil { + if localEntry == nil { // glog.V(3).Infof("dir Lookup cache miss %s", fullFilePath) - entry, err = filer_pb.GetEntry(dir.wfs, fullFilePath) + entry, err := filer_pb.GetEntry(dir.wfs, fullFilePath) if err != nil { glog.V(1).Infof("dir GetEntry %s: %v", fullFilePath, err) return nil, fuse.ENOENT } + localEntry = filer.FromPbEntry(string(dirPath), entry) } else { glog.V(4).Infof("dir Lookup cache hit %s", fullFilePath) } - if entry != nil { - if entry.IsDirectory { + if localEntry != nil { + if localEntry.IsDirectory() { node = dir.newDirectory(fullFilePath) } else { node = dir.newFile(req.Name) @@ -299,13 +299,13 @@ func (dir *Dir) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse. // resp.EntryValid = time.Second // resp.Attr.Inode = fullFilePath.AsInode() resp.Attr.Valid = time.Second - resp.Attr.Mtime = time.Unix(entry.Attributes.Mtime, 0) - resp.Attr.Crtime = time.Unix(entry.Attributes.Crtime, 0) - resp.Attr.Mode = os.FileMode(entry.Attributes.FileMode) - resp.Attr.Gid = entry.Attributes.Gid - resp.Attr.Uid = entry.Attributes.Uid - if entry.HardLinkCounter > 0 { - resp.Attr.Nlink = uint32(entry.HardLinkCounter) + resp.Attr.Mtime = localEntry.Attr.Mtime + resp.Attr.Crtime = localEntry.Attr.Crtime + resp.Attr.Mode = localEntry.Attr.Mode + resp.Attr.Gid = localEntry.Attr.Gid + resp.Attr.Uid = localEntry.Attr.Uid + if localEntry.HardLinkCounter > 0 { + resp.Attr.Nlink = uint32(localEntry.HardLinkCounter) } return node, nil From 36c79de3f4110e52ea83383fc6b8d3780504968b Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 14 Apr 2021 23:21:24 -0700 Subject: [PATCH 044/128] fuse mount: dir ReadDirAll avoid extra conversion to filer_pb.Entry --- weed/filesys/dir.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/weed/filesys/dir.go b/weed/filesys/dir.go index e51fbbee5..8880d3506 100644 --- a/weed/filesys/dir.go +++ b/weed/filesys/dir.go @@ -320,12 +320,12 @@ func (dir *Dir) ReadDirAll(ctx context.Context) (ret []fuse.Dirent, err error) { dirPath := util.FullPath(dir.FullPath()) glog.V(4).Infof("dir ReadDirAll %s", dirPath) - processEachEntryFn := func(entry *filer_pb.Entry, isLast bool) error { - if entry.IsDirectory { - dirent := fuse.Dirent{Name: entry.Name, Type: fuse.DT_Dir} + processEachEntryFn := func(entry *filer.Entry, isLast bool) error { + if entry.IsDirectory() { + dirent := fuse.Dirent{Name: entry.Name(), Type: fuse.DT_Dir} ret = append(ret, dirent) } else { - dirent := fuse.Dirent{Name: entry.Name, Type: findFileType(uint16(entry.Attributes.FileMode))} + dirent := fuse.Dirent{Name: entry.Name(), Type: findFileType(uint16(entry.Attr.Mode))} ret = append(ret, dirent) } return nil @@ -336,7 +336,7 @@ func (dir *Dir) ReadDirAll(ctx context.Context) (ret []fuse.Dirent, err error) { return nil, fuse.EIO } listErr := dir.wfs.metaCache.ListDirectoryEntries(context.Background(), dirPath, "", false, int64(math.MaxInt32), func(entry *filer.Entry) bool { - processEachEntryFn(entry.ToProtoEntry(), false) + processEachEntryFn(entry, false) return true }) if listErr != nil { From 07f712c83fa012eeb51b25f5f0b8ce4a86400eaf Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 14 Apr 2021 23:21:38 -0700 Subject: [PATCH 045/128] fix typo --- weed/filesys/dir.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/weed/filesys/dir.go b/weed/filesys/dir.go index 8880d3506..2e00708ba 100644 --- a/weed/filesys/dir.go +++ b/weed/filesys/dir.go @@ -166,7 +166,7 @@ func (dir *Dir) Mknod(ctx context.Context, req *fuse.MknodRequest) (fs.Node, err return node, nil } -func (dir *Dir) doCreateEntry(name string, mode os.FileMode, uid, gid uint32, exlusive bool) (*filer_pb.CreateEntryRequest, error) { +func (dir *Dir) doCreateEntry(name string, mode os.FileMode, uid, gid uint32, exclusive bool) (*filer_pb.CreateEntryRequest, error) { request := &filer_pb.CreateEntryRequest{ Directory: dir.FullPath(), Entry: &filer_pb.Entry{ @@ -183,7 +183,7 @@ func (dir *Dir) doCreateEntry(name string, mode os.FileMode, uid, gid uint32, ex TtlSec: dir.wfs.option.TtlSec, }, }, - OExcl: exlusive, + OExcl: exclusive, Signatures: []int32{dir.wfs.signature}, } glog.V(1).Infof("create %s/%s", dir.FullPath(), name) From 3e669e6d7b57387ea3b77ab2af9edc3f80c4dae2 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 14 Apr 2021 23:33:37 -0700 Subject: [PATCH 046/128] mostly refactoring, add some error handling --- weed/filesys/dir.go | 49 +++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/weed/filesys/dir.go b/weed/filesys/dir.go index 2e00708ba..bf192f384 100644 --- a/weed/filesys/dir.go +++ b/weed/filesys/dir.go @@ -167,8 +167,9 @@ func (dir *Dir) Mknod(ctx context.Context, req *fuse.MknodRequest) (fs.Node, err } func (dir *Dir) doCreateEntry(name string, mode os.FileMode, uid, gid uint32, exclusive bool) (*filer_pb.CreateEntryRequest, error) { + dirFullPath := dir.FullPath() request := &filer_pb.CreateEntryRequest{ - Directory: dir.FullPath(), + Directory: dirFullPath, Entry: &filer_pb.Entry{ Name: name, IsDirectory: mode&os.ModeDir > 0, @@ -186,7 +187,7 @@ func (dir *Dir) doCreateEntry(name string, mode os.FileMode, uid, gid uint32, ex OExcl: exclusive, Signatures: []int32{dir.wfs.signature}, } - glog.V(1).Infof("create %s/%s", dir.FullPath(), name) + glog.V(1).Infof("create %s/%s", dirFullPath, name) err := dir.wfs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error { @@ -197,11 +198,14 @@ func (dir *Dir) doCreateEntry(name string, mode os.FileMode, uid, gid uint32, ex if strings.Contains(err.Error(), "EEXIST") { return fuse.EEXIST } - glog.V(0).Infof("create %s/%s: %v", dir.FullPath(), name, err) + glog.V(0).Infof("create %s/%s: %v", dirFullPath, name, err) return fuse.EIO } - dir.wfs.metaCache.InsertEntry(context.Background(), filer.FromPbEntry(request.Directory, request.Entry)) + if err := dir.wfs.metaCache.InsertEntry(context.Background(), filer.FromPbEntry(request.Directory, request.Entry)); err != nil { + glog.Errorf("local InsertEntry dir %s/%s: %v", dirFullPath, name, err) + return fuse.EIO + } return nil }) @@ -228,35 +232,40 @@ func (dir *Dir) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, err }, } + dirFullPath := dir.FullPath() + err := dir.wfs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error { dir.wfs.mapPbIdFromLocalToFiler(newEntry) defer dir.wfs.mapPbIdFromFilerToLocal(newEntry) request := &filer_pb.CreateEntryRequest{ - Directory: dir.FullPath(), + Directory: dirFullPath, Entry: newEntry, Signatures: []int32{dir.wfs.signature}, } glog.V(1).Infof("mkdir: %v", request) if err := filer_pb.CreateEntry(client, request); err != nil { - glog.V(0).Infof("mkdir %s/%s: %v", dir.FullPath(), req.Name, err) + glog.V(0).Infof("mkdir %s/%s: %v", dirFullPath, req.Name, err) return err } - dir.wfs.metaCache.InsertEntry(context.Background(), filer.FromPbEntry(request.Directory, request.Entry)) + if err := dir.wfs.metaCache.InsertEntry(context.Background(), filer.FromPbEntry(request.Directory, request.Entry)); err != nil { + glog.Errorf("local mkdir dir %s/%s: %v", dirFullPath, req.Name, err) + return fuse.EIO + } return nil }) if err == nil { - node := dir.newDirectory(util.NewFullPath(dir.FullPath(), req.Name)) + node := dir.newDirectory(util.NewFullPath(dirFullPath, req.Name)) return node, nil } - glog.V(0).Infof("mkdir %s/%s: %v", dir.FullPath(), req.Name, err) + glog.V(0).Infof("mkdir %s/%s: %v", dirFullPath, req.Name, err) return nil, fuse.EIO } @@ -320,7 +329,7 @@ func (dir *Dir) ReadDirAll(ctx context.Context) (ret []fuse.Dirent, err error) { dirPath := util.FullPath(dir.FullPath()) glog.V(4).Infof("dir ReadDirAll %s", dirPath) - processEachEntryFn := func(entry *filer.Entry, isLast bool) error { + processEachEntryFn := func(entry *filer.Entry, isLast bool) { if entry.IsDirectory() { dirent := fuse.Dirent{Name: entry.Name(), Type: fuse.DT_Dir} ret = append(ret, dirent) @@ -328,7 +337,6 @@ func (dir *Dir) ReadDirAll(ctx context.Context) (ret []fuse.Dirent, err error) { dirent := fuse.Dirent{Name: entry.Name(), Type: findFileType(uint16(entry.Attr.Mode))} ret = append(ret, dirent) } - return nil } if err = meta_cache.EnsureVisited(dir.wfs.metaCache, dir.wfs, dirPath); err != nil { @@ -382,7 +390,8 @@ func (dir *Dir) Remove(ctx context.Context, req *fuse.RemoveRequest) error { func (dir *Dir) removeOneFile(req *fuse.RemoveRequest) error { - filePath := util.NewFullPath(dir.FullPath(), req.Name) + dirFullPath := dir.FullPath() + filePath := util.NewFullPath(dirFullPath, req.Name) entry, err := filer_pb.GetEntry(dir.wfs, filePath) if err != nil { return err @@ -394,14 +403,17 @@ func (dir *Dir) removeOneFile(req *fuse.RemoveRequest) error { // first, ensure the filer store can correctly delete glog.V(3).Infof("remove file: %v", req) isDeleteData := entry.HardLinkCounter <= 1 - err = filer_pb.Remove(dir.wfs, dir.FullPath(), req.Name, isDeleteData, false, false, false, []int32{dir.wfs.signature}) + err = filer_pb.Remove(dir.wfs, dirFullPath, req.Name, isDeleteData, false, false, false, []int32{dir.wfs.signature}) if err != nil { - glog.V(3).Infof("not found remove file %s/%s: %v", dir.FullPath(), req.Name, err) + glog.V(3).Infof("not found remove file %s: %v", filePath, err) return fuse.ENOENT } // then, delete meta cache and fsNode cache - dir.wfs.metaCache.DeleteEntry(context.Background(), filePath) + if err = dir.wfs.metaCache.DeleteEntry(context.Background(), filePath); err != nil { + glog.V(3).Infof("local DeleteEntry %s: %v", filePath, err) + return fuse.ESTALE + } // clear entry inside the file fsNode := dir.wfs.fsNodeCache.GetFsNode(filePath) @@ -415,7 +427,7 @@ func (dir *Dir) removeOneFile(req *fuse.RemoveRequest) error { // remove current file handle if any dir.wfs.handlesLock.Lock() defer dir.wfs.handlesLock.Unlock() - inodeId := util.NewFullPath(dir.FullPath(), req.Name).AsInode() + inodeId := filePath.AsInode() delete(dir.wfs.handles, inodeId) return nil @@ -568,7 +580,10 @@ func (dir *Dir) saveEntry(entry *filer_pb.Entry) error { return fuse.EIO } - dir.wfs.metaCache.UpdateEntry(context.Background(), filer.FromPbEntry(request.Directory, request.Entry)) + if err := dir.wfs.metaCache.UpdateEntry(context.Background(), filer.FromPbEntry(request.Directory, request.Entry)); err != nil { + glog.Errorf("UpdateEntry dir %s/%s: %v", parentDir, name, err) + return fuse.ESTALE + } return nil }) From 217e0f906623a6d2f25f4dce7280d16bf3786a72 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 15 Apr 2021 01:51:10 -0700 Subject: [PATCH 047/128] mount: remove folder recursively --- weed/filesys/dir.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/weed/filesys/dir.go b/weed/filesys/dir.go index bf192f384..ed8ffff61 100644 --- a/weed/filesys/dir.go +++ b/weed/filesys/dir.go @@ -436,18 +436,19 @@ func (dir *Dir) removeOneFile(req *fuse.RemoveRequest) error { func (dir *Dir) removeFolder(req *fuse.RemoveRequest) error { + dirFullPath := dir.FullPath() glog.V(3).Infof("remove directory entry: %v", req) ignoreRecursiveErr := true // ignore recursion error since the OS should manage it - err := filer_pb.Remove(dir.wfs, dir.FullPath(), req.Name, true, false, ignoreRecursiveErr, false, []int32{dir.wfs.signature}) + err := filer_pb.Remove(dir.wfs, dirFullPath, req.Name, true, true, ignoreRecursiveErr, false, []int32{dir.wfs.signature}) if err != nil { - glog.V(0).Infof("remove %s/%s: %v", dir.FullPath(), req.Name, err) + glog.V(0).Infof("remove %s/%s: %v", dirFullPath, req.Name, err) if strings.Contains(err.Error(), "non-empty") { return fuse.EEXIST } return fuse.ENOENT } - t := util.NewFullPath(dir.FullPath(), req.Name) + t := util.NewFullPath(dirFullPath, req.Name) dir.wfs.metaCache.DeleteEntry(context.Background(), t) dir.wfs.fsNodeCache.DeleteFsNode(t) From 16c0304416bc0dd9fd56439e39c754c57d604608 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 15 Apr 2021 02:29:04 -0700 Subject: [PATCH 048/128] ensure to delete on filer also --- weed/filesys/dir.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/weed/filesys/dir.go b/weed/filesys/dir.go index ed8ffff61..e74c5aaac 100644 --- a/weed/filesys/dir.go +++ b/weed/filesys/dir.go @@ -396,13 +396,10 @@ func (dir *Dir) removeOneFile(req *fuse.RemoveRequest) error { if err != nil { return err } - if entry == nil { - return nil - } // first, ensure the filer store can correctly delete glog.V(3).Infof("remove file: %v", req) - isDeleteData := entry.HardLinkCounter <= 1 + isDeleteData := entry != nil && entry.HardLinkCounter <= 1 err = filer_pb.Remove(dir.wfs, dirFullPath, req.Name, isDeleteData, false, false, false, []int32{dir.wfs.signature}) if err != nil { glog.V(3).Infof("not found remove file %s: %v", filePath, err) From ba92f2e7148108c362379f266a6292401b82b90c Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 15 Apr 2021 03:19:28 -0700 Subject: [PATCH 049/128] add node.selectedVolumes fix https://github.com/chrislusf/seaweedfs/issues/1990 --- weed/shell/command_volume_server_evacuate.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/weed/shell/command_volume_server_evacuate.go b/weed/shell/command_volume_server_evacuate.go index 80c5b1d6b..f21d0334c 100644 --- a/weed/shell/command_volume_server_evacuate.go +++ b/weed/shell/command_volume_server_evacuate.go @@ -176,6 +176,11 @@ func moveAwayOneEcVolume(commandEnv *CommandEnv, ecShardInfo *master_pb.VolumeEc func moveAwayOneNormalVolume(commandEnv *CommandEnv, volumeReplicas map[uint32][]*VolumeReplica, vol *master_pb.VolumeInformationMessage, thisNode *Node, otherNodes []*Node, applyChange bool) (hasMoved bool, err error) { fn := capacityByFreeVolumeCount(types.ToDiskType(vol.DiskType)) + for _, n := range otherNodes { + n.selectVolumes(func(v *master_pb.VolumeInformationMessage) bool { + return v.DiskType == vol.DiskType + }) + } sort.Slice(otherNodes, func(i, j int) bool { return otherNodes[i].localVolumeRatio(fn) > otherNodes[j].localVolumeRatio(fn) }) From 609e228578ff0e678aea7eab3c815dd28bc6e72a Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 15 Apr 2021 10:53:04 -0700 Subject: [PATCH 050/128] avoid forward slash in file names --- weed/shell/command_fs_meta_save.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/weed/shell/command_fs_meta_save.go b/weed/shell/command_fs_meta_save.go index d154fc12b..37d94fe42 100644 --- a/weed/shell/command_fs_meta_save.go +++ b/weed/shell/command_fs_meta_save.go @@ -6,6 +6,7 @@ import ( "io" "os" "path/filepath" + "strings" "sync" "sync/atomic" "time" @@ -89,6 +90,7 @@ func (c *commandFsMetaSave) Do(args []string, commandEnv *CommandEnv, writer io. ext := filepath.Ext(entry.Entry.Name) if encrypted, encErr := util.Encrypt([]byte(entry.Entry.Name), cipherKey); encErr == nil { entry.Entry.Name = util.Base64Encode(encrypted)[:len(entry.Entry.Name)] + ext + entry.Entry.Name = strings.ReplaceAll(entry.Entry.Name, "/", "x") } } bytes, err := proto.Marshal(entry) From 283d703d50a3379f84333c5850ea117ba9f6dc30 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 15 Apr 2021 11:29:58 -0700 Subject: [PATCH 051/128] adjust text --- weed/storage/disk_location.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weed/storage/disk_location.go b/weed/storage/disk_location.go index 6de87c793..ed4e00312 100644 --- a/weed/storage/disk_location.go +++ b/weed/storage/disk_location.go @@ -131,7 +131,7 @@ func (l *DiskLocation) loadExistingVolume(fileInfo os.FileInfo, needleMapKind Ne l.SetVolume(vid, v) size, _, _ := v.FileStat() - glog.V(0).Infof("data file %s, replicaPlacement=%s v=%d size=%d ttl=%s", + glog.V(0).Infof("data file %s, replication=%s v=%d size=%d ttl=%s", l.Directory+"/"+volumeName+".dat", v.ReplicaPlacement, v.Version(), size, v.Ttl.String()) return true } From b971317a167ac806a02947047c65ba6c2a2a7bad Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 15 Apr 2021 11:41:34 -0700 Subject: [PATCH 052/128] avoid possible corrupted file names --- weed/shell/command_fs_meta_load.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/weed/shell/command_fs_meta_load.go b/weed/shell/command_fs_meta_load.go index 69ae9454c..46dc07e9a 100644 --- a/weed/shell/command_fs_meta_load.go +++ b/weed/shell/command_fs_meta_load.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "os" + "strings" "github.com/golang/protobuf/proto" @@ -72,6 +73,7 @@ func (c *commandFsMetaLoad) Do(args []string, commandEnv *CommandEnv, writer io. return err } + fullEntry.Entry.Name = strings.ReplaceAll(fullEntry.Entry.Name, "/", "x") if err := filer_pb.CreateEntry(client, &filer_pb.CreateEntryRequest{ Directory: fullEntry.Dir, Entry: fullEntry.Entry, From 3074e9b42850635b28f1ef90fc51e712210d5d45 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 15 Apr 2021 22:42:24 -0700 Subject: [PATCH 053/128] ensure consistent inode value --- weed/filesys/dir.go | 6 +++--- weed/filesys/file.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/weed/filesys/dir.go b/weed/filesys/dir.go index e74c5aaac..f177c940b 100644 --- a/weed/filesys/dir.go +++ b/weed/filesys/dir.go @@ -59,7 +59,7 @@ func (dir *Dir) Attr(ctx context.Context, attr *fuse.Attr) error { return err } - // attr.Inode = util.FullPath(dir.FullPath()).AsInode() + attr.Inode = util.FullPath(dir.FullPath()).AsInode() attr.Mode = os.FileMode(entry.Attributes.FileMode) | os.ModeDir attr.Mtime = time.Unix(entry.Attributes.Mtime, 0) attr.Crtime = time.Unix(entry.Attributes.Crtime, 0) @@ -331,10 +331,10 @@ func (dir *Dir) ReadDirAll(ctx context.Context) (ret []fuse.Dirent, err error) { processEachEntryFn := func(entry *filer.Entry, isLast bool) { if entry.IsDirectory() { - dirent := fuse.Dirent{Name: entry.Name(), Type: fuse.DT_Dir} + dirent := fuse.Dirent{Name: entry.Name(), Type: fuse.DT_Dir, Inode: dirPath.Child(entry.Name()).AsInode()} ret = append(ret, dirent) } else { - dirent := fuse.Dirent{Name: entry.Name(), Type: findFileType(uint16(entry.Attr.Mode))} + dirent := fuse.Dirent{Name: entry.Name(), Type: findFileType(uint16(entry.Attr.Mode)), Inode: dirPath.Child(entry.Name()).AsInode()} ret = append(ret, dirent) } } diff --git a/weed/filesys/file.go b/weed/filesys/file.go index a6bc734f3..00a5ac279 100644 --- a/weed/filesys/file.go +++ b/weed/filesys/file.go @@ -55,7 +55,7 @@ func (file *File) Attr(ctx context.Context, attr *fuse.Attr) (err error) { return fuse.ENOENT } - // attr.Inode = file.fullpath().AsInode() + attr.Inode = file.fullpath().AsInode() attr.Valid = time.Second attr.Mode = os.FileMode(entry.Attributes.FileMode) attr.Size = filer.FileSize(entry) From c83ab91e2e9223db2270793658a69a19e3f7e0be Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 16 Apr 2021 10:34:02 -0700 Subject: [PATCH 054/128] remove unused variable --- weed/command/mount_std.go | 1 - weed/filesys/wfs.go | 1 - 2 files changed, 2 deletions(-) diff --git a/weed/command/mount_std.go b/weed/command/mount_std.go index f0c59b331..9df43e40a 100644 --- a/weed/command/mount_std.go +++ b/weed/command/mount_std.go @@ -191,7 +191,6 @@ func RunMount(option *MountOptions, umask os.FileMode) bool { CacheDir: *option.cacheDir, CacheSizeMB: *option.cacheSizeMB, DataCenter: *option.dataCenter, - EntryCacheTtl: 3 * time.Second, MountUid: uid, MountGid: gid, MountMode: mountMode, diff --git a/weed/filesys/wfs.go b/weed/filesys/wfs.go index 4b73aaa05..7bacfb114 100644 --- a/weed/filesys/wfs.go +++ b/weed/filesys/wfs.go @@ -41,7 +41,6 @@ type Option struct { CacheDir string CacheSizeMB int64 DataCenter string - EntryCacheTtl time.Duration Umask os.FileMode ReadOnly bool From 198688c7171b7f6fe61e35a54e34c71a28348144 Mon Sep 17 00:00:00 2001 From: Konstantin Lebedev Date: Fri, 16 Apr 2021 23:22:31 +0500 Subject: [PATCH 055/128] revert volume etag --- weed/server/volume_server_handlers_write.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/weed/server/volume_server_handlers_write.go b/weed/server/volume_server_handlers_write.go index 602b147e1..3d752eda6 100644 --- a/weed/server/volume_server_handlers_write.go +++ b/weed/server/volume_server_handlers_write.go @@ -13,7 +13,6 @@ import ( "github.com/chrislusf/seaweedfs/weed/stats" "github.com/chrislusf/seaweedfs/weed/storage/needle" "github.com/chrislusf/seaweedfs/weed/topology" - "github.com/chrislusf/seaweedfs/weed/util" ) func (vs *VolumeServer) PostHandler(w http.ResponseWriter, r *http.Request) { @@ -68,7 +67,7 @@ func (vs *VolumeServer) PostHandler(w http.ResponseWriter, r *http.Request) { ret.Name = string(reqNeedle.Name) } ret.Size = uint32(originalSize) - ret.ETag = fmt.Sprintf("%x", util.Base64Md5ToBytes(contentMd5)) + ret.ETag = reqNeedle.Etag() ret.Mime = string(reqNeedle.Mime) setEtag(w, ret.ETag) w.Header().Set("Content-MD5", contentMd5) From 54410ca955cf4078684bca17b4363dc33ef433ed Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sun, 18 Apr 2021 10:02:02 -0700 Subject: [PATCH 056/128] cleaner way to set readonly --- weed/command/mount_std.go | 4 +++- weed/filesys/dir.go | 28 ---------------------------- weed/filesys/dir_link.go | 8 -------- weed/filesys/dir_rename.go | 4 ---- weed/filesys/file.go | 12 ------------ weed/filesys/filehandle.go | 4 ---- weed/filesys/wfs.go | 1 - 7 files changed, 3 insertions(+), 58 deletions(-) diff --git a/weed/command/mount_std.go b/weed/command/mount_std.go index 9df43e40a..2474cf7dd 100644 --- a/weed/command/mount_std.go +++ b/weed/command/mount_std.go @@ -167,6 +167,9 @@ func RunMount(option *MountOptions, umask os.FileMode) bool { if *option.nonempty { options = append(options, fuse.AllowNonEmptyMount()) } + if *option.readOnly { + options = append(options, fuse.ReadOnly()) + } // find mount point mountRoot := filerMountRootPath @@ -200,7 +203,6 @@ func RunMount(option *MountOptions, umask os.FileMode) bool { VolumeServerAccess: *mountOptions.volumeServerAccess, Cipher: cipher, UidGidMapper: uidGidMapper, - ReadOnly: *option.readOnly, }) // mount diff --git a/weed/filesys/dir.go b/weed/filesys/dir.go index f177c940b..9eba4066d 100644 --- a/weed/filesys/dir.go +++ b/weed/filesys/dir.go @@ -128,10 +128,6 @@ func (dir *Dir) newDirectory(fullpath util.FullPath) fs.Node { func (dir *Dir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) { - if dir.wfs.option.ReadOnly { - return nil, nil, fuse.EPERM - } - request, err := dir.doCreateEntry(req.Name, req.Mode, req.Uid, req.Gid, req.Flags&fuse.OpenExclusive != 0) if err != nil { @@ -152,10 +148,6 @@ func (dir *Dir) Create(ctx context.Context, req *fuse.CreateRequest, func (dir *Dir) Mknod(ctx context.Context, req *fuse.MknodRequest) (fs.Node, error) { - if dir.wfs.option.ReadOnly { - return nil, fuse.EPERM - } - _, err := dir.doCreateEntry(req.Name, req.Mode, req.Uid, req.Gid, false) if err != nil { @@ -214,10 +206,6 @@ func (dir *Dir) doCreateEntry(name string, mode os.FileMode, uid, gid uint32, ex func (dir *Dir) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) { - if dir.wfs.option.ReadOnly { - return nil, fuse.EPERM - } - glog.V(4).Infof("mkdir %s: %s", dir.FullPath(), req.Name) newEntry := &filer_pb.Entry{ @@ -376,10 +364,6 @@ func findFileType(mode uint16) fuse.DirentType { func (dir *Dir) Remove(ctx context.Context, req *fuse.RemoveRequest) error { - if dir.wfs.option.ReadOnly { - return fuse.EPERM - } - if !req.Dir { return dir.removeOneFile(req) } @@ -455,10 +439,6 @@ func (dir *Dir) removeFolder(req *fuse.RemoveRequest) error { func (dir *Dir) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error { - if dir.wfs.option.ReadOnly { - return fuse.EPERM - } - glog.V(4).Infof("%v dir setattr %+v", dir.FullPath(), req) entry, err := dir.maybeLoadEntry() @@ -488,10 +468,6 @@ func (dir *Dir) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fus func (dir *Dir) Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error { - if dir.wfs.option.ReadOnly { - return fuse.EPERM - } - glog.V(4).Infof("dir Setxattr %s: %s", dir.FullPath(), req.Name) entry, err := dir.maybeLoadEntry() @@ -509,10 +485,6 @@ func (dir *Dir) Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error { func (dir *Dir) Removexattr(ctx context.Context, req *fuse.RemovexattrRequest) error { - if dir.wfs.option.ReadOnly { - return fuse.EPERM - } - glog.V(4).Infof("dir Removexattr %s: %s", dir.FullPath(), req.Name) entry, err := dir.maybeLoadEntry() diff --git a/weed/filesys/dir_link.go b/weed/filesys/dir_link.go index de330e7a4..acdcd2de4 100644 --- a/weed/filesys/dir_link.go +++ b/weed/filesys/dir_link.go @@ -24,10 +24,6 @@ const ( func (dir *Dir) Link(ctx context.Context, req *fuse.LinkRequest, old fs.Node) (fs.Node, error) { - if dir.wfs.option.ReadOnly { - return nil, fuse.EPERM - } - oldFile, ok := old.(*File) if !ok { glog.Errorf("old node is not a file: %+v", old) @@ -106,10 +102,6 @@ func (dir *Dir) Link(ctx context.Context, req *fuse.LinkRequest, old fs.Node) (f func (dir *Dir) Symlink(ctx context.Context, req *fuse.SymlinkRequest) (fs.Node, error) { - if dir.wfs.option.ReadOnly { - return nil, fuse.EPERM - } - glog.V(4).Infof("Symlink: %v/%v to %v", dir.FullPath(), req.NewName, req.Target) request := &filer_pb.CreateEntryRequest{ diff --git a/weed/filesys/dir_rename.go b/weed/filesys/dir_rename.go index 28316c3bd..d2acad4b2 100644 --- a/weed/filesys/dir_rename.go +++ b/weed/filesys/dir_rename.go @@ -13,10 +13,6 @@ import ( func (dir *Dir) Rename(ctx context.Context, req *fuse.RenameRequest, newDirectory fs.Node) error { - if dir.wfs.option.ReadOnly { - return fuse.EPERM - } - newDir := newDirectory.(*Dir) newPath := util.NewFullPath(newDir.FullPath(), req.NewName) diff --git a/weed/filesys/file.go b/weed/filesys/file.go index 00a5ac279..2a991d7db 100644 --- a/weed/filesys/file.go +++ b/weed/filesys/file.go @@ -105,10 +105,6 @@ func (file *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.Op func (file *File) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error { - if file.wfs.option.ReadOnly { - return fuse.EPERM - } - glog.V(4).Infof("%v file setattr %+v", file.fullpath(), req) entry, err := file.maybeLoadEntry(ctx) @@ -197,10 +193,6 @@ func (file *File) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *f func (file *File) Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error { - if file.wfs.option.ReadOnly { - return fuse.EPERM - } - glog.V(4).Infof("file Setxattr %s: %s", file.fullpath(), req.Name) entry, err := file.maybeLoadEntry(ctx) @@ -218,10 +210,6 @@ func (file *File) Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error func (file *File) Removexattr(ctx context.Context, req *fuse.RemovexattrRequest) error { - if file.wfs.option.ReadOnly { - return fuse.EPERM - } - glog.V(4).Infof("file Removexattr %s: %s", file.fullpath(), req.Name) entry, err := file.maybeLoadEntry(ctx) diff --git a/weed/filesys/filehandle.go b/weed/filesys/filehandle.go index 240d25a3d..5b9a1748a 100644 --- a/weed/filesys/filehandle.go +++ b/weed/filesys/filehandle.go @@ -156,10 +156,6 @@ func (fh *FileHandle) readFromChunks(buff []byte, offset int64) (int64, error) { // Write to the file handle func (fh *FileHandle) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error { - if fh.f.wfs.option.ReadOnly { - return fuse.EPERM - } - fh.Lock() defer fh.Unlock() diff --git a/weed/filesys/wfs.go b/weed/filesys/wfs.go index 7bacfb114..2034354f5 100644 --- a/weed/filesys/wfs.go +++ b/weed/filesys/wfs.go @@ -42,7 +42,6 @@ type Option struct { CacheSizeMB int64 DataCenter string Umask os.FileMode - ReadOnly bool MountUid uint32 MountGid uint32 From d9a2a7f1c4593c20ec9a92b98b726af7b32baff3 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 16 Apr 2021 02:55:09 -0700 Subject: [PATCH 057/128] WIP no memory issue if some directory is removed, it may have this error $ rm -Rf ~/tmp/m2/s1 rm: fts_read: Device not configured --- go.mod | 2 +- weed/filesys/dir.go | 38 ++++++++++++-------------------------- weed/filesys/dir_rename.go | 8 +++----- weed/filesys/file.go | 6 +++++- weed/filesys/wfs.go | 31 +++++++++++++++---------------- 5 files changed, 36 insertions(+), 49 deletions(-) diff --git a/go.mod b/go.mod index bd07b3312..ed651cfdf 100644 --- a/go.mod +++ b/go.mod @@ -102,7 +102,7 @@ require ( gopkg.in/karlseguin/expect.v1 v1.0.1 // indirect ) -// replace github.com/seaweedfs/fuse => /Users/chris/go/src/github.com/seaweedfs/fuse +replace github.com/seaweedfs/fuse => /Users/chris/go/src/github.com/seaweedfs/fuse // replace github.com/chrislusf/raft => /Users/chris/go/src/github.com/chrislusf/raft replace go.etcd.io/etcd => go.etcd.io/etcd v0.5.0-alpha.5.0.20200425165423-262c93980547 diff --git a/weed/filesys/dir.go b/weed/filesys/dir.go index 9eba4066d..b3577dcf0 100644 --- a/weed/filesys/dir.go +++ b/weed/filesys/dir.go @@ -27,6 +27,7 @@ type Dir struct { } var _ = fs.Node(&Dir{}) +var _ = fs.NodeIdentifier(&Dir{}) var _ = fs.NodeCreater(&Dir{}) var _ = fs.NodeMknoder(&Dir{}) var _ = fs.NodeMkdirer(&Dir{}) @@ -42,6 +43,10 @@ var _ = fs.NodeRemovexattrer(&Dir{}) var _ = fs.NodeListxattrer(&Dir{}) var _ = fs.NodeForgetter(&Dir{}) +func (dir *Dir) Id() uint64 { + return util.FullPath(dir.FullPath()).AsInode() +} + func (dir *Dir) Attr(ctx context.Context, attr *fuse.Attr) error { // https://github.com/bazil/fuse/issues/196 @@ -105,24 +110,17 @@ func (dir *Dir) Fsync(ctx context.Context, req *fuse.FsyncRequest) error { } func (dir *Dir) newFile(name string) fs.Node { - f := dir.wfs.fsNodeCache.EnsureFsNode(util.NewFullPath(dir.FullPath(), name), func() fs.Node { - return &File{ - Name: name, - dir: dir, - wfs: dir.wfs, - } - }) - f.(*File).dir = dir // in case dir node was created later - return f + return &File{ + Name: name, + dir: dir, + wfs: dir.wfs, + } } func (dir *Dir) newDirectory(fullpath util.FullPath) fs.Node { - d := dir.wfs.fsNodeCache.EnsureFsNode(fullpath, func() fs.Node { - return &Dir{name: fullpath.Name(), wfs: dir.wfs, parent: dir} - }) - d.(*Dir).parent = dir // in case dir node was created later - return d + return &Dir{name: fullpath.Name(), wfs: dir.wfs, parent: dir} + } func (dir *Dir) Create(ctx context.Context, req *fuse.CreateRequest, @@ -396,15 +394,6 @@ func (dir *Dir) removeOneFile(req *fuse.RemoveRequest) error { return fuse.ESTALE } - // clear entry inside the file - fsNode := dir.wfs.fsNodeCache.GetFsNode(filePath) - dir.wfs.fsNodeCache.DeleteFsNode(filePath) - if fsNode != nil { - if file, ok := fsNode.(*File); ok { - file.entry = nil - } - } - // remove current file handle if any dir.wfs.handlesLock.Lock() defer dir.wfs.handlesLock.Unlock() @@ -431,7 +420,6 @@ func (dir *Dir) removeFolder(req *fuse.RemoveRequest) error { t := util.NewFullPath(dirFullPath, req.Name) dir.wfs.metaCache.DeleteEntry(context.Background(), t) - dir.wfs.fsNodeCache.DeleteFsNode(t) return nil @@ -519,8 +507,6 @@ func (dir *Dir) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp func (dir *Dir) Forget() { glog.V(4).Infof("Forget dir %s", dir.FullPath()) - - dir.wfs.fsNodeCache.DeleteFsNode(util.FullPath(dir.FullPath())) } func (dir *Dir) maybeLoadEntry() (*filer_pb.Entry, error) { diff --git a/weed/filesys/dir_rename.go b/weed/filesys/dir_rename.go index d2acad4b2..7bc705102 100644 --- a/weed/filesys/dir_rename.go +++ b/weed/filesys/dir_rename.go @@ -64,19 +64,17 @@ func (dir *Dir) Rename(ctx context.Context, req *fuse.RenameRequest, newDirector return fuse.EIO } - // fmt.Printf("rename path: %v => %v\n", oldPath, newPath) - dir.wfs.fsNodeCache.Move(oldPath, newPath) - // change file handle dir.wfs.handlesLock.Lock() defer dir.wfs.handlesLock.Unlock() inodeId := oldPath.AsInode() existingHandle, found := dir.wfs.handles[inodeId] if !found || existingHandle == nil { - return err + return nil } + glog.V(4).Infof("opened filehandle %s => %s", oldPath, newPath) delete(dir.wfs.handles, inodeId) dir.wfs.handles[newPath.AsInode()] = existingHandle - return err + return nil } diff --git a/weed/filesys/file.go b/weed/filesys/file.go index 2a991d7db..b3e5cc72d 100644 --- a/weed/filesys/file.go +++ b/weed/filesys/file.go @@ -18,6 +18,7 @@ import ( const blockSize = 512 var _ = fs.Node(&File{}) +var _ = fs.NodeIdentifier(&File{}) var _ = fs.NodeOpener(&File{}) var _ = fs.NodeFsyncer(&File{}) var _ = fs.NodeSetattrer(&File{}) @@ -40,6 +41,10 @@ func (file *File) fullpath() util.FullPath { return util.NewFullPath(file.dir.FullPath(), file.Name) } +func (file *File) Id() uint64 { + return file.fullpath().AsInode() +} + func (file *File) Attr(ctx context.Context, attr *fuse.Attr) (err error) { glog.V(4).Infof("file Attr %s, open:%v existing:%v", file.fullpath(), file.isOpen, attr) @@ -253,7 +258,6 @@ func (file *File) Fsync(ctx context.Context, req *fuse.FsyncRequest) error { func (file *File) Forget() { t := util.NewFullPath(file.dir.FullPath(), file.Name) glog.V(4).Infof("Forget file %s", t) - file.wfs.fsNodeCache.DeleteFsNode(t) file.wfs.ReleaseHandle(t, 0) } diff --git a/weed/filesys/wfs.go b/weed/filesys/wfs.go index 2034354f5..7019a464b 100644 --- a/weed/filesys/wfs.go +++ b/weed/filesys/wfs.go @@ -103,24 +103,15 @@ func NewSeaweedFileSystem(option *Option) *WFS { } wfs.metaCache = meta_cache.NewMetaCache(path.Join(cacheDir, "meta"), util.FullPath(option.FilerMountRootPath), option.UidGidMapper, func(filePath util.FullPath) { - fsNode := wfs.fsNodeCache.GetFsNode(filePath) - if fsNode != nil { - if file, ok := fsNode.(*File); ok { - if err := wfs.Server.InvalidateNodeData(file); err != nil { - glog.V(4).Infof("InvalidateNodeData %s : %v", filePath, err) - } - file.entry = nil - } + fsNode := NodeWithId(filePath.AsInode()) + if err := wfs.Server.InvalidateNodeData(fsNode); err != nil { + glog.V(4).Infof("InvalidateNodeData %s : %v", filePath, err) } + dir, name := filePath.DirAndName() - parent := wfs.root - if dir != "/" { - parent = wfs.fsNodeCache.GetFsNode(util.FullPath(dir)) - } - if parent != nil { - if err := wfs.Server.InvalidateEntry(parent, name); err != nil { - glog.V(4).Infof("InvalidateEntry %s : %v", filePath, err) - } + parent := NodeWithId(util.FullPath(dir).AsInode()) + if err := wfs.Server.InvalidateEntry(parent, name); err != nil { + glog.V(4).Infof("InvalidateEntry %s : %v", filePath, err) } }) startTime := time.Now() @@ -267,3 +258,11 @@ func (wfs *WFS) LookupFn() wdclient.LookupFileIdFunctionType { return filer.LookupFn(wfs) } + +type NodeWithId uint64 +func (n NodeWithId) Id() uint64 { + return uint64(n) +} +func (n NodeWithId) Attr(ctx context.Context, attr *fuse.Attr) error { + return nil +} From d41e6826d3a0a51750e3f9c14b33a6aa9953bb09 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sat, 17 Apr 2021 03:11:41 -0700 Subject: [PATCH 058/128] adjust logging --- weed/filer/filerstore_wrapper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weed/filer/filerstore_wrapper.go b/weed/filer/filerstore_wrapper.go index 95848e61b..cd7c0bea3 100644 --- a/weed/filer/filerstore_wrapper.go +++ b/weed/filer/filerstore_wrapper.go @@ -149,8 +149,8 @@ func (fsw *FilerStoreWrapper) FindEntry(ctx context.Context, fp util.FullPath) ( stats.FilerStoreHistogram.WithLabelValues(actualStore.GetName(), "find").Observe(time.Since(start).Seconds()) }() - glog.V(4).Infof("FindEntry %s", fp) entry, err = actualStore.FindEntry(ctx, fp) + glog.V(4).Infof("FindEntry %s: %v", fp, err) if err != nil { return nil, err } From 6cbd786db9b96833248444c42992c239f2424d95 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sat, 17 Apr 2021 10:48:22 -0700 Subject: [PATCH 059/128] correctly runs git clone --- weed/filesys/dir.go | 25 +++++++++++++++++++------ weed/filesys/dir_rename.go | 11 +++++++++++ weed/filesys/file.go | 26 +++++++++++++++----------- weed/filesys/filehandle.go | 10 +++++----- weed/filesys/wfs.go | 25 ++++++++++++++----------- 5 files changed, 64 insertions(+), 33 deletions(-) diff --git a/weed/filesys/dir.go b/weed/filesys/dir.go index b3577dcf0..3588a5951 100644 --- a/weed/filesys/dir.go +++ b/weed/filesys/dir.go @@ -24,6 +24,7 @@ type Dir struct { wfs *WFS entry *filer_pb.Entry parent *Dir + id uint64 } var _ = fs.Node(&Dir{}) @@ -44,7 +45,7 @@ var _ = fs.NodeListxattrer(&Dir{}) var _ = fs.NodeForgetter(&Dir{}) func (dir *Dir) Id() uint64 { - return util.FullPath(dir.FullPath()).AsInode() + return dir.id } func (dir *Dir) Attr(ctx context.Context, attr *fuse.Attr) error { @@ -64,7 +65,7 @@ func (dir *Dir) Attr(ctx context.Context, attr *fuse.Attr) error { return err } - attr.Inode = util.FullPath(dir.FullPath()).AsInode() + attr.Inode = dir.Id() attr.Mode = os.FileMode(entry.Attributes.FileMode) | os.ModeDir attr.Mtime = time.Unix(entry.Attributes.Mtime, 0) attr.Crtime = time.Unix(entry.Attributes.Crtime, 0) @@ -110,16 +111,28 @@ func (dir *Dir) Fsync(ctx context.Context, req *fuse.FsyncRequest) error { } func (dir *Dir) newFile(name string) fs.Node { + + fileFullPath := util.NewFullPath(dir.FullPath(), name) + fileId := fileFullPath.AsInode() + dir.wfs.handlesLock.Lock() + existingHandle, found := dir.wfs.handles[fileId] + dir.wfs.handlesLock.Unlock() + + if found { + glog.V(4).Infof("newFile found opened file handle: %+v", fileFullPath) + return existingHandle.f + } return &File{ - Name: name, - dir: dir, - wfs: dir.wfs, + Name: name, + dir: dir, + wfs: dir.wfs, + id: fileId, } } func (dir *Dir) newDirectory(fullpath util.FullPath) fs.Node { - return &Dir{name: fullpath.Name(), wfs: dir.wfs, parent: dir} + return &Dir{name: fullpath.Name(), wfs: dir.wfs, parent: dir, id: fullpath.AsInode()} } diff --git a/weed/filesys/dir_rename.go b/weed/filesys/dir_rename.go index 7bc705102..b07710d17 100644 --- a/weed/filesys/dir_rename.go +++ b/weed/filesys/dir_rename.go @@ -64,11 +64,22 @@ func (dir *Dir) Rename(ctx context.Context, req *fuse.RenameRequest, newDirector return fuse.EIO } + oldFsNode := NodeWithId(oldPath.AsInode()) + newFsNode := NodeWithId(newPath.AsInode()) + dir.wfs.Server.InvalidateInternalNode(oldFsNode, newFsNode, func(internalNode fs.Node) { + if file, ok := internalNode.(*File); ok { + glog.V(4).Infof("internal node %s", file.Name) + file.Name = req.NewName + file.id = uint64(newFsNode) + } + }) + // change file handle dir.wfs.handlesLock.Lock() defer dir.wfs.handlesLock.Unlock() inodeId := oldPath.AsInode() existingHandle, found := dir.wfs.handles[inodeId] + glog.V(4).Infof("has open filehandle %s: %v", oldPath, found) if !found || existingHandle == nil { return nil } diff --git a/weed/filesys/file.go b/weed/filesys/file.go index b3e5cc72d..2e37c6e0e 100644 --- a/weed/filesys/file.go +++ b/weed/filesys/file.go @@ -35,6 +35,7 @@ type File struct { entry *filer_pb.Entry isOpen int dirtyMetadata bool + id uint64 } func (file *File) fullpath() util.FullPath { @@ -42,25 +43,23 @@ func (file *File) fullpath() util.FullPath { } func (file *File) Id() uint64 { - return file.fullpath().AsInode() + return file.id } func (file *File) Attr(ctx context.Context, attr *fuse.Attr) (err error) { glog.V(4).Infof("file Attr %s, open:%v existing:%v", file.fullpath(), file.isOpen, attr) - entry := file.getEntry() - if file.isOpen <= 0 || entry == nil { - if entry, err = file.maybeLoadEntry(ctx); err != nil { - return err - } + entry, err := file.maybeLoadEntry(ctx) + if err != nil { + return err } if entry == nil { return fuse.ENOENT } - attr.Inode = file.fullpath().AsInode() + attr.Inode = file.Id() attr.Valid = time.Second attr.Mode = os.FileMode(entry.Attributes.FileMode) attr.Size = filer.FileSize(entry) @@ -118,7 +117,7 @@ func (file *File) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *f } if file.isOpen > 0 { file.wfs.handlesLock.Lock() - fileHandle := file.wfs.handles[file.fullpath().AsInode()] + fileHandle := file.wfs.handles[file.Id()] file.wfs.handlesLock.Unlock() if fileHandle != nil { @@ -262,10 +261,15 @@ func (file *File) Forget() { } func (file *File) maybeLoadEntry(ctx context.Context) (entry *filer_pb.Entry, err error) { - entry = file.getEntry() - if file.isOpen > 0 { - return entry, nil + + file.wfs.handlesLock.Lock() + handle, found := file.wfs.handles[file.Id()] + file.wfs.handlesLock.Unlock() + if found { + glog.V(4).Infof("maybeLoadEntry found opened file %s/%s: %v %v", file.dir.FullPath(), file.Name, handle.f.entry, entry) + entry = handle.f.entry } + if entry != nil { if len(entry.HardLinkId) == 0 { // only always reload hard link diff --git a/weed/filesys/filehandle.go b/weed/filesys/filehandle.go index 5b9a1748a..3f4ee69f4 100644 --- a/weed/filesys/filehandle.go +++ b/weed/filesys/filehandle.go @@ -193,20 +193,20 @@ func (fh *FileHandle) Write(ctx context.Context, req *fuse.WriteRequest, resp *f func (fh *FileHandle) Release(ctx context.Context, req *fuse.ReleaseRequest) error { - glog.V(4).Infof("Release %v fh %d", fh.f.fullpath(), fh.handle) + glog.V(4).Infof("Release %v fh %d open=%d", fh.f.fullpath(), fh.handle, fh.f.isOpen) fh.Lock() defer fh.Unlock() - if fh.f.isOpen <= 0 { + fh.f.isOpen-- + + if fh.f.isOpen < 0 { glog.V(0).Infof("Release reset %s open count %d => %d", fh.f.Name, fh.f.isOpen, 0) fh.f.isOpen = 0 return nil } - if fh.f.isOpen == 1 { - - fh.f.isOpen-- + if fh.f.isOpen == 0 { fh.f.entry = nil fh.entryViewCache = nil fh.reader = nil diff --git a/weed/filesys/wfs.go b/weed/filesys/wfs.go index 7019a464b..42816d23d 100644 --- a/weed/filesys/wfs.go +++ b/weed/filesys/wfs.go @@ -103,6 +103,7 @@ func NewSeaweedFileSystem(option *Option) *WFS { } wfs.metaCache = meta_cache.NewMetaCache(path.Join(cacheDir, "meta"), util.FullPath(option.FilerMountRootPath), option.UidGidMapper, func(filePath util.FullPath) { + fsNode := NodeWithId(filePath.AsInode()) if err := wfs.Server.InvalidateNodeData(fsNode); err != nil { glog.V(4).Infof("InvalidateNodeData %s : %v", filePath, err) @@ -139,16 +140,15 @@ func (wfs *WFS) AcquireHandle(file *File, uid, gid uint32) (fileHandle *FileHand fullpath := file.fullpath() glog.V(4).Infof("AcquireHandle %s uid=%d gid=%d", fullpath, uid, gid) - wfs.handlesLock.Lock() - defer wfs.handlesLock.Unlock() + inodeId := file.Id() - inodeId := file.fullpath().AsInode() - if file.isOpen > 0 { - existingHandle, found := wfs.handles[inodeId] - if found && existingHandle != nil { - file.isOpen++ - return existingHandle - } + wfs.handlesLock.Lock() + existingHandle, found := wfs.handles[inodeId] + wfs.handlesLock.Unlock() + if found && existingHandle != nil { + existingHandle.f.isOpen++ + glog.V(4).Infof("Acquired Handle %s open %d", fullpath, existingHandle.f.isOpen) + return existingHandle } entry, _ := file.maybeLoadEntry(context.Background()) @@ -156,9 +156,12 @@ func (wfs *WFS) AcquireHandle(file *File, uid, gid uint32) (fileHandle *FileHand fileHandle = newFileHandle(file, uid, gid) file.isOpen++ + wfs.handlesLock.Lock() wfs.handles[inodeId] = fileHandle + wfs.handlesLock.Unlock() fileHandle.handle = inodeId + glog.V(4).Infof("Acquired new Handle %s open %d", fullpath, file.isOpen) return } @@ -166,9 +169,9 @@ func (wfs *WFS) ReleaseHandle(fullpath util.FullPath, handleId fuse.HandleID) { wfs.handlesLock.Lock() defer wfs.handlesLock.Unlock() - glog.V(4).Infof("%s ReleaseHandle id %d current handles length %d", fullpath, handleId, len(wfs.handles)) + glog.V(4).Infof("ReleaseHandle %s id %d current handles length %d", fullpath, handleId, len(wfs.handles)) - delete(wfs.handles, fullpath.AsInode()) + delete(wfs.handles, uint64(handleId)) return } From 2acf6be24e168c4cd6bdeb64244333de17654251 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sat, 17 Apr 2021 14:32:24 -0700 Subject: [PATCH 060/128] resend the http request if connection is stale --- weed/operation/upload_content.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/weed/operation/upload_content.go b/weed/operation/upload_content.go index 7a7f8aa0c..944186eeb 100644 --- a/weed/operation/upload_content.go +++ b/weed/operation/upload_content.go @@ -11,7 +11,6 @@ import ( "net/http" "net/textproto" "path/filepath" - "runtime/debug" "strings" "time" @@ -235,10 +234,12 @@ func upload_content(uploadUrl string, fillBufferFunction func(w io.Writer) error // print("+") resp, post_err := HttpClient.Do(req) if post_err != nil { - if !strings.Contains(post_err.Error(), "connection reset by peer") { - glog.Errorf("upload %s %d bytes to %v: %v", filename, originalDataSize, uploadUrl, post_err) - debug.PrintStack() + if strings.Contains(post_err.Error(), "connection reset by peer") || + strings.Contains(post_err.Error(), "use of closed network connection") { + resp, post_err = HttpClient.Do(req) } + } + if post_err != nil { return nil, fmt.Errorf("upload %s %d bytes to %v: %v", filename, originalDataSize, uploadUrl, post_err) } // print("-") From e332da48371f28d42f71a0b33d48adb49f1f83db Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sat, 17 Apr 2021 14:32:49 -0700 Subject: [PATCH 061/128] set inode value --- weed/filesys/dir.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weed/filesys/dir.go b/weed/filesys/dir.go index 3588a5951..f6f1c2562 100644 --- a/weed/filesys/dir.go +++ b/weed/filesys/dir.go @@ -305,7 +305,7 @@ func (dir *Dir) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse. } // resp.EntryValid = time.Second - // resp.Attr.Inode = fullFilePath.AsInode() + resp.Attr.Inode = fullFilePath.AsInode() resp.Attr.Valid = time.Second resp.Attr.Mtime = localEntry.Attr.Mtime resp.Attr.Crtime = localEntry.Attr.Crtime From 372872ebbfa360e25ff63071166e9eaef6255df8 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sun, 18 Apr 2021 09:43:56 -0700 Subject: [PATCH 062/128] set root node inode number --- weed/filesys/dir.go | 1 + 1 file changed, 1 insertion(+) diff --git a/weed/filesys/dir.go b/weed/filesys/dir.go index f6f1c2562..77e6724e1 100644 --- a/weed/filesys/dir.go +++ b/weed/filesys/dir.go @@ -92,6 +92,7 @@ func (dir *Dir) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *f func (dir *Dir) setRootDirAttributes(attr *fuse.Attr) { // attr.Inode = 1 // filer2.FullPath(dir.Path).AsInode() attr.Valid = time.Second + attr.Inode = dir.Id() attr.Uid = dir.wfs.option.MountUid attr.Gid = dir.wfs.option.MountGid attr.Mode = dir.wfs.option.MountMode From d1c813c4707f988bc0688c2e780bf8075c5571af Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sun, 18 Apr 2021 11:19:33 -0700 Subject: [PATCH 063/128] let the fuse library manage directory id otherwise, on mac, during large directory deletion, if some ReaDirAll happens, the lib seems confused about the directories, and some child directories are not deleted. --- weed/filesys/dir.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/weed/filesys/dir.go b/weed/filesys/dir.go index 77e6724e1..1dc2ab939 100644 --- a/weed/filesys/dir.go +++ b/weed/filesys/dir.go @@ -28,7 +28,7 @@ type Dir struct { } var _ = fs.Node(&Dir{}) -var _ = fs.NodeIdentifier(&Dir{}) +//var _ = fs.NodeIdentifier(&Dir{}) var _ = fs.NodeCreater(&Dir{}) var _ = fs.NodeMknoder(&Dir{}) var _ = fs.NodeMkdirer(&Dir{}) @@ -44,7 +44,7 @@ var _ = fs.NodeRemovexattrer(&Dir{}) var _ = fs.NodeListxattrer(&Dir{}) var _ = fs.NodeForgetter(&Dir{}) -func (dir *Dir) Id() uint64 { +func (dir *Dir) xId() uint64 { return dir.id } @@ -65,7 +65,7 @@ func (dir *Dir) Attr(ctx context.Context, attr *fuse.Attr) error { return err } - attr.Inode = dir.Id() + // attr.Inode = dir.Id() attr.Mode = os.FileMode(entry.Attributes.FileMode) | os.ModeDir attr.Mtime = time.Unix(entry.Attributes.Mtime, 0) attr.Crtime = time.Unix(entry.Attributes.Crtime, 0) @@ -92,7 +92,7 @@ func (dir *Dir) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *f func (dir *Dir) setRootDirAttributes(attr *fuse.Attr) { // attr.Inode = 1 // filer2.FullPath(dir.Path).AsInode() attr.Valid = time.Second - attr.Inode = dir.Id() + attr.Inode = 1 // dir.Id() attr.Uid = dir.wfs.option.MountUid attr.Gid = dir.wfs.option.MountGid attr.Mode = dir.wfs.option.MountMode From 4b3cc28cab54c5cc24228cbc5f63b3aa283b0ab0 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sun, 18 Apr 2021 13:29:37 -0700 Subject: [PATCH 064/128] fuse 1.1.4 --- go.mod | 4 ++-- go.sum | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index ed651cfdf..70bc33070 100644 --- a/go.mod +++ b/go.mod @@ -62,7 +62,7 @@ require ( github.com/prometheus/client_golang v1.3.0 github.com/rakyll/statik v0.1.7 github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 // indirect - github.com/seaweedfs/fuse v1.1.3 + github.com/seaweedfs/fuse v1.1.4 github.com/seaweedfs/goexif v1.0.2 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/spaolacci/murmur3 v1.1.0 // indirect @@ -102,7 +102,7 @@ require ( gopkg.in/karlseguin/expect.v1 v1.0.1 // indirect ) -replace github.com/seaweedfs/fuse => /Users/chris/go/src/github.com/seaweedfs/fuse +// replace github.com/seaweedfs/fuse => /Users/chris/go/src/github.com/seaweedfs/fuse // replace github.com/chrislusf/raft => /Users/chris/go/src/github.com/chrislusf/raft replace go.etcd.io/etcd => go.etcd.io/etcd v0.5.0-alpha.5.0.20200425165423-262c93980547 diff --git a/go.sum b/go.sum index 4899bd7b8..0409b1ae1 100644 --- a/go.sum +++ b/go.sum @@ -693,6 +693,8 @@ github.com/seaweedfs/fuse v1.1.1 h1:WD51YFJcBViOx8I89jeqPD+vAKl4EowzBy9GUw0plb0= github.com/seaweedfs/fuse v1.1.1/go.mod h1:+PP6WlkrRUG6KPE+Th2EX5To/PjHaFsvqg/UgQ39aj8= github.com/seaweedfs/fuse v1.1.3 h1:0DddotXwSRGbYG2kynoJyr8GHCy30Z2SpdhP3vdyijY= github.com/seaweedfs/fuse v1.1.3/go.mod h1:+PP6WlkrRUG6KPE+Th2EX5To/PjHaFsvqg/UgQ39aj8= +github.com/seaweedfs/fuse v1.1.4 h1:YYqkK86agMhXRSwR+wFbRI8ikMgk3kL6PNTna1MAHyQ= +github.com/seaweedfs/fuse v1.1.4/go.mod h1:+PP6WlkrRUG6KPE+Th2EX5To/PjHaFsvqg/UgQ39aj8= github.com/seaweedfs/goexif v1.0.2 h1:p+rTXYdQ2mgxd+1JaTrQ9N8DvYuw9UH9xgYmJ+Bb29E= github.com/seaweedfs/goexif v1.0.2/go.mod h1:MrKs5LK0HXdffrdCZrW3OIMegL2xXpC6ThLyXMyjdrk= github.com/secsy/goftp v0.0.0-20190720192957-f31499d7c79a h1:C6IhVTxNkhlb0tlCB6JfHOUv1f0xHPK7V8X4HlJZEJw= From e983f91b03b6ce356a7dc9381120d941d33c9e21 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sun, 18 Apr 2021 13:58:01 -0700 Subject: [PATCH 065/128] 2.40 --- k8s/seaweedfs/Chart.yaml | 4 ++-- k8s/seaweedfs/values.yaml | 2 +- weed/util/constants.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/k8s/seaweedfs/Chart.yaml b/k8s/seaweedfs/Chart.yaml index c296ce0c6..8eddb2ca2 100644 --- a/k8s/seaweedfs/Chart.yaml +++ b/k8s/seaweedfs/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v1 description: SeaweedFS name: seaweedfs -appVersion: "2.39" -version: 2.39 +appVersion: "2.40" +version: 2.40 diff --git a/k8s/seaweedfs/values.yaml b/k8s/seaweedfs/values.yaml index e615090c1..058170bbe 100644 --- a/k8s/seaweedfs/values.yaml +++ b/k8s/seaweedfs/values.yaml @@ -4,7 +4,7 @@ global: registry: "" repository: "" imageName: chrislusf/seaweedfs - # imageTag: "2.39" - started using {.Chart.appVersion} + # imageTag: "2.40" - started using {.Chart.appVersion} imagePullPolicy: IfNotPresent imagePullSecrets: imagepullsecret restartPolicy: Always diff --git a/weed/util/constants.go b/weed/util/constants.go index fce35379d..d81138d86 100644 --- a/weed/util/constants.go +++ b/weed/util/constants.go @@ -5,7 +5,7 @@ import ( ) var ( - VERSION = fmt.Sprintf("%s %d.%02d", sizeLimit, 2, 39) + VERSION = fmt.Sprintf("%s %d.%02d", sizeLimit, 2, 40) COMMIT = "" ) From 1faafce832d2df8742f183584f9054d22606add3 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sun, 18 Apr 2021 21:46:03 -0700 Subject: [PATCH 066/128] fix docker image release --- docker/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 6309e7e2e..2165466ca 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,6 +1,7 @@ FROM alpine -ARG RELEASE=latest # 'latest' or 'dev' +# 'latest' or 'dev' +ARG RELEASE=latest RUN \ ARCH=$(if [ $(uname -m) == "x86_64" ] && [ $(getconf LONG_BIT) == "64" ]; then echo "amd64"; \ From 83cf94ad2dea74e5f9a881a617d06c42cbb7ba49 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 19 Apr 2021 10:58:25 -0700 Subject: [PATCH 067/128] delay new file creation unless file is opened exclusively --- weed/filesys/dir.go | 28 ++++++++++++++++++++++++---- weed/filesys/file.go | 1 + 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/weed/filesys/dir.go b/weed/filesys/dir.go index 1dc2ab939..6ee20974b 100644 --- a/weed/filesys/dir.go +++ b/weed/filesys/dir.go @@ -28,6 +28,7 @@ type Dir struct { } var _ = fs.Node(&Dir{}) + //var _ = fs.NodeIdentifier(&Dir{}) var _ = fs.NodeCreater(&Dir{}) var _ = fs.NodeMknoder(&Dir{}) @@ -140,19 +141,38 @@ func (dir *Dir) newDirectory(fullpath util.FullPath) fs.Node { func (dir *Dir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) { - request, err := dir.doCreateEntry(req.Name, req.Mode, req.Uid, req.Gid, req.Flags&fuse.OpenExclusive != 0) + exclusive := req.Flags&fuse.OpenExclusive != 0 + isDirectory := req.Mode&os.ModeDir > 0 - if err != nil { - return nil, nil, err + if exclusive || isDirectory { + _, err := dir.doCreateEntry(req.Name, req.Mode, req.Uid, req.Gid, exclusive) + if err != nil { + return nil, nil, err + } } var node fs.Node - if request.Entry.IsDirectory { + if isDirectory { node = dir.newDirectory(util.NewFullPath(dir.FullPath(), req.Name)) return node, nil, nil } node = dir.newFile(req.Name) file := node.(*File) + file.entry = &filer_pb.Entry{ + Name: req.Name, + IsDirectory: req.Mode&os.ModeDir > 0, + Attributes: &filer_pb.FuseAttributes{ + Mtime: time.Now().Unix(), + Crtime: time.Now().Unix(), + FileMode: uint32(req.Mode &^ dir.wfs.option.Umask), + Uid: req.Uid, + Gid: req.Gid, + Collection: dir.wfs.option.Collection, + Replication: dir.wfs.option.Replication, + TtlSec: dir.wfs.option.TtlSec, + }, + } + file.dirtyMetadata = true fh := dir.wfs.AcquireHandle(file, req.Uid, req.Gid) return file, fh, nil diff --git a/weed/filesys/file.go b/weed/filesys/file.go index 2e37c6e0e..5fb4d8d11 100644 --- a/weed/filesys/file.go +++ b/weed/filesys/file.go @@ -265,6 +265,7 @@ func (file *File) maybeLoadEntry(ctx context.Context) (entry *filer_pb.Entry, er file.wfs.handlesLock.Lock() handle, found := file.wfs.handles[file.Id()] file.wfs.handlesLock.Unlock() + entry = file.entry if found { glog.V(4).Infof("maybeLoadEntry found opened file %s/%s: %v %v", file.dir.FullPath(), file.Name, handle.f.entry, entry) entry = handle.f.entry From c31c5e829c224a73a89f7ce7a8e11d7ce0b9689a Mon Sep 17 00:00:00 2001 From: liuxiaobo <6914585+sryio@users.noreply.github.com> Date: Tue, 20 Apr 2021 10:08:58 +0800 Subject: [PATCH 068/128] fix path-specific filer store comment error --- weed/command/scaffold.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weed/command/scaffold.go b/weed/command/scaffold.go index 4794da18e..88dc94df1 100644 --- a/weed/command/scaffold.go +++ b/weed/command/scaffold.go @@ -281,7 +281,7 @@ index.max_result_window = 10000 # Make sure they are not the same if using the same store type! # 4. Set enabled to true # -# The following is just using cassandra as an example +# The following is just using redis as an example ########################## [redis2.tmp] enabled = false From 11c405fc854d17db98929771c1270300a5293929 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Tue, 20 Apr 2021 19:56:51 -0700 Subject: [PATCH 069/128] ensure file handles are released --- weed/filesys/file.go | 2 +- weed/filesys/filehandle.go | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/weed/filesys/file.go b/weed/filesys/file.go index 5fb4d8d11..bb57988cd 100644 --- a/weed/filesys/file.go +++ b/weed/filesys/file.go @@ -257,7 +257,7 @@ func (file *File) Fsync(ctx context.Context, req *fuse.FsyncRequest) error { func (file *File) Forget() { t := util.NewFullPath(file.dir.FullPath(), file.Name) glog.V(4).Infof("Forget file %s", t) - file.wfs.ReleaseHandle(t, 0) + file.wfs.ReleaseHandle(t, fuse.HandleID(t.AsInode())) } func (file *File) maybeLoadEntry(ctx context.Context) (entry *filer_pb.Entry, err error) { diff --git a/weed/filesys/filehandle.go b/weed/filesys/filehandle.go index 3f4ee69f4..27ffab6e1 100644 --- a/weed/filesys/filehandle.go +++ b/weed/filesys/filehandle.go @@ -200,13 +200,7 @@ func (fh *FileHandle) Release(ctx context.Context, req *fuse.ReleaseRequest) err fh.f.isOpen-- - if fh.f.isOpen < 0 { - glog.V(0).Infof("Release reset %s open count %d => %d", fh.f.Name, fh.f.isOpen, 0) - fh.f.isOpen = 0 - return nil - } - - if fh.f.isOpen == 0 { + if fh.f.isOpen <= 0 { fh.f.entry = nil fh.entryViewCache = nil fh.reader = nil @@ -214,6 +208,12 @@ func (fh *FileHandle) Release(ctx context.Context, req *fuse.ReleaseRequest) err fh.f.wfs.ReleaseHandle(fh.f.fullpath(), fuse.HandleID(fh.handle)) } + if fh.f.isOpen < 0 { + glog.V(0).Infof("Release reset %s open count %d => %d", fh.f.Name, fh.f.isOpen, 0) + fh.f.isOpen = 0 + return nil + } + return nil } From ecb60d5f035eed09eb7744016e0563941ba535bb Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Tue, 20 Apr 2021 20:08:42 -0700 Subject: [PATCH 070/128] Update cleanup.yml --- .github/workflows/cleanup.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml index 47a677e6d..64241512f 100644 --- a/.github/workflows/cleanup.yml +++ b/.github/workflows/cleanup.yml @@ -16,7 +16,6 @@ jobs: uses: mknejp/delete-release-assets@v1 with: token: ${{ github.token }} - tag: dev fail-if-no-assets: false assets: | weed-* From bcd400995aacca41a704248af7196586449a365d Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Tue, 20 Apr 2021 20:11:52 -0700 Subject: [PATCH 071/128] Revert "Update cleanup.yml" This reverts commit ecb60d5f035eed09eb7744016e0563941ba535bb. --- .github/workflows/cleanup.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml index 64241512f..47a677e6d 100644 --- a/.github/workflows/cleanup.yml +++ b/.github/workflows/cleanup.yml @@ -16,6 +16,7 @@ jobs: uses: mknejp/delete-release-assets@v1 with: token: ${{ github.token }} + tag: dev fail-if-no-assets: false assets: | weed-* From ae74d8f02a1eefad220bb9649db5b2c5ed032d3b Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 21 Apr 2021 01:40:16 -0700 Subject: [PATCH 072/128] fix error message related to https://github.com/chrislusf/seaweedfs/issues/2012 --- weed/storage/store_ec.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weed/storage/store_ec.go b/weed/storage/store_ec.go index a9b6a8ff3..f4587ba01 100644 --- a/weed/storage/store_ec.go +++ b/weed/storage/store_ec.go @@ -303,7 +303,7 @@ func (s *Store) doReadRemoteEcShardInterval(sourceDataNode string, needleId type break } if receiveErr != nil { - return fmt.Errorf("receiving ec shard %d.%d from %s: %v", vid, shardId, sourceDataNode, err) + return fmt.Errorf("receiving ec shard %d.%d from %s: %v", vid, shardId, sourceDataNode, receiveErr) } if resp.IsDeleted { is_deleted = true From a8114da02dddd65b0371e7a9e6a5125d085c55d4 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 21 Apr 2021 10:17:12 -0700 Subject: [PATCH 073/128] avoid thundering herd effect transient errors may cause thundering herd effect to all trying to recover from remove ec shards --- weed/storage/store_ec.go | 1 - 1 file changed, 1 deletion(-) diff --git a/weed/storage/store_ec.go b/weed/storage/store_ec.go index f4587ba01..9702fdd50 100644 --- a/weed/storage/store_ec.go +++ b/weed/storage/store_ec.go @@ -200,7 +200,6 @@ func (s *Store) readOneEcShardInterval(needleId types.NeedleId, ecVolume *erasur return } glog.V(0).Infof("clearing ec shard %d.%d locations: %v", ecVolume.VolumeId, shardId, err) - forgetShardId(ecVolume, shardId) } // try reading by recovering from other shards From cd7bf1a72a9a1bba24b9d15460782d21cb706504 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 21 Apr 2021 11:17:43 -0700 Subject: [PATCH 074/128] filer.copy copy empty folders fix https://github.com/chrislusf/seaweedfs/issues/2016 --- weed/command/filer_copy.go | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/weed/command/filer_copy.go b/weed/command/filer_copy.go index a36bb8cea..e7a9b107f 100644 --- a/weed/command/filer_copy.go +++ b/weed/command/filer_copy.go @@ -207,16 +207,6 @@ func genFileCopyTask(fileOrDir string, destPath string, fileCopyTaskChan chan Fi } mode := fi.Mode() - if mode.IsDir() { - files, _ := ioutil.ReadDir(fileOrDir) - for _, subFileOrDir := range files { - if err = genFileCopyTask(fileOrDir+"/"+subFileOrDir.Name(), destPath+fi.Name()+"/", fileCopyTaskChan); err != nil { - return err - } - } - return nil - } - uid, gid := util.GetFileUidGid(fi) fileCopyTaskChan <- FileCopyTask{ @@ -228,6 +218,16 @@ func genFileCopyTask(fileOrDir string, destPath string, fileCopyTaskChan chan Fi gid: gid, } + if mode.IsDir() { + files, _ := ioutil.ReadDir(fileOrDir) + println("checking directory", fileOrDir) + for _, subFileOrDir := range files { + if err = genFileCopyTask(fileOrDir+"/"+subFileOrDir.Name(), destPath+fi.Name()+"/", fileCopyTaskChan); err != nil { + return err + } + } + } + return nil } @@ -293,20 +293,22 @@ func (worker *FileCopyWorker) uploadFileAsOne(task FileCopyTask, f *os.File) err // upload the file content fileName := filepath.Base(f.Name()) - mimeType := detectMimeType(f) - data, err := ioutil.ReadAll(f) - if err != nil { - return err - } + var mimeType string var chunks []*filer_pb.FileChunk var assignResult *filer_pb.AssignVolumeResponse var assignError error - if task.fileSize > 0 { + if task.fileMode & os.ModeDir == 0 && task.fileSize > 0 { + + mimeType = detectMimeType(f) + data, err := ioutil.ReadAll(f) + if err != nil { + return err + } // assign a volume - err := pb.WithGrpcFilerClient(worker.filerGrpcAddress, worker.options.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error { + err = pb.WithGrpcFilerClient(worker.filerGrpcAddress, worker.options.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error { request := &filer_pb.AssignVolumeRequest{ Count: 1, From f4d77e527d90baf6059c63c51c8362baf941c613 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 21 Apr 2021 12:02:22 -0700 Subject: [PATCH 075/128] Update cleanup.yml --- .github/workflows/cleanup.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml index 47a677e6d..9fbd7dfa8 100644 --- a/.github/workflows/cleanup.yml +++ b/.github/workflows/cleanup.yml @@ -16,7 +16,7 @@ jobs: uses: mknejp/delete-release-assets@v1 with: token: ${{ github.token }} - tag: dev + tag: refs/tags/dev fail-if-no-assets: false assets: | weed-* From 7deb4b20cd08662085f5f2ac92a26a541501b847 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 21 Apr 2021 12:09:24 -0700 Subject: [PATCH 076/128] Update cleanup.yml --- .github/workflows/cleanup.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml index 9fbd7dfa8..d8a2cdc4f 100644 --- a/.github/workflows/cleanup.yml +++ b/.github/workflows/cleanup.yml @@ -16,7 +16,7 @@ jobs: uses: mknejp/delete-release-assets@v1 with: token: ${{ github.token }} - tag: refs/tags/dev + tag: master fail-if-no-assets: false assets: | weed-* From ca998328c2916fdba751fa9add6f8eec626ff0be Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 21 Apr 2021 23:11:09 -0700 Subject: [PATCH 077/128] do not add new volumes when below minFreeSpacePercent fix https://github.com/chrislusf/seaweedfs/issues/2017 --- weed/storage/store.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/weed/storage/store.go b/weed/storage/store.go index c3507c0e2..6be15a4c9 100644 --- a/weed/storage/store.go +++ b/weed/storage/store.go @@ -106,6 +106,9 @@ func (s *Store) FindFreeLocation(diskType DiskType) (ret *DiskLocation) { if diskType != location.DiskType { continue } + if location.isDiskSpaceLow { + continue + } currentFreeCount := location.MaxVolumeCount - location.VolumesLen() currentFreeCount *= erasure_coding.DataShardsCount currentFreeCount -= location.EcVolumesLen() From c60f2d333eeee7faa6fc0f0055ce43ef3a911099 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 22 Apr 2021 00:31:33 -0700 Subject: [PATCH 078/128] Update cleanup.yml --- .github/workflows/cleanup.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml index d8a2cdc4f..47a677e6d 100644 --- a/.github/workflows/cleanup.yml +++ b/.github/workflows/cleanup.yml @@ -16,7 +16,7 @@ jobs: uses: mknejp/delete-release-assets@v1 with: token: ${{ github.token }} - tag: master + tag: dev fail-if-no-assets: false assets: | weed-* From 6e5df901e478c9effd94bae2fa1de53138380d8b Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 22 Apr 2021 14:21:11 -0700 Subject: [PATCH 079/128] adjust package names --- weed/server/filer_ui/breadcrumb.go | 2 +- weed/server/filer_ui/templates.go | 2 +- weed/server/volume_server_ui/templates.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/weed/server/filer_ui/breadcrumb.go b/weed/server/filer_ui/breadcrumb.go index f21cce7d1..5016117a8 100644 --- a/weed/server/filer_ui/breadcrumb.go +++ b/weed/server/filer_ui/breadcrumb.go @@ -1,4 +1,4 @@ -package master_ui +package filer_ui import ( "strings" diff --git a/weed/server/filer_ui/templates.go b/weed/server/filer_ui/templates.go index 3f0647119..648b97f22 100644 --- a/weed/server/filer_ui/templates.go +++ b/weed/server/filer_ui/templates.go @@ -1,4 +1,4 @@ -package master_ui +package filer_ui import ( "github.com/dustin/go-humanize" diff --git a/weed/server/volume_server_ui/templates.go b/weed/server/volume_server_ui/templates.go index 6a8bb6f55..ee4c2e31d 100644 --- a/weed/server/volume_server_ui/templates.go +++ b/weed/server/volume_server_ui/templates.go @@ -1,4 +1,4 @@ -package master_ui +package volume_server_ui import ( "fmt" From 11c120c040c82a2eecc6f7f5d23b83266e22eb5e Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 22 Apr 2021 14:22:46 -0700 Subject: [PATCH 080/128] master UI adds volume size limit --- weed/server/master_server_handlers_ui.go | 12 +++++++----- weed/server/master_ui/templates.go | 10 +++++++--- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/weed/server/master_server_handlers_ui.go b/weed/server/master_server_handlers_ui.go index 9cd58158b..3822c6113 100644 --- a/weed/server/master_server_handlers_ui.go +++ b/weed/server/master_server_handlers_ui.go @@ -14,17 +14,19 @@ func (ms *MasterServer) uiStatusHandler(w http.ResponseWriter, r *http.Request) infos := make(map[string]interface{}) infos["Up Time"] = time.Now().Sub(startTime).String() args := struct { - Version string - Topology interface{} - RaftServer raft.Server - Stats map[string]interface{} - Counters *stats.ServerStats + Version string + Topology interface{} + RaftServer raft.Server + Stats map[string]interface{} + Counters *stats.ServerStats + VolumeSizeLimitMB uint }{ util.Version(), ms.Topo.ToMap(), ms.Topo.RaftServer, infos, serverStats, + ms.option.VolumeSizeLimitMB, } ui.StatusTpl.Execute(w, args) } diff --git a/weed/server/master_ui/templates.go b/weed/server/master_ui/templates.go index 60873f6aa..31b6353e9 100644 --- a/weed/server/master_ui/templates.go +++ b/weed/server/master_ui/templates.go @@ -22,8 +22,12 @@ var StatusTpl = template.Must(template.New("status").Parse(`

Cluster status

- +
+ + + + @@ -38,8 +42,8 @@ var StatusTpl = template.Must(template.New("status").Parse(` - - +
Volume Size Limit{{ .VolumeSizeLimitMB }}MB
Free {{ .Topology.Free }} {{ .Leader }}
    +
Other Masters
    {{ range $k, $p := .Peers }}
  • {{ $p.Name }}
  • {{ end }} From 46ef1811a18e4a6e704be801b55884fd1d40c9a8 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 22 Apr 2021 22:26:38 -0700 Subject: [PATCH 081/128] correct help message --- weed/shell/command_s3_clean_uploads.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weed/shell/command_s3_clean_uploads.go b/weed/shell/command_s3_clean_uploads.go index 5f674d7b6..1ba31292c 100644 --- a/weed/shell/command_s3_clean_uploads.go +++ b/weed/shell/command_s3_clean_uploads.go @@ -26,7 +26,7 @@ func (c *commandS3CleanUploads) Help() string { return `clean up stale multipart uploads Example: - s3.clean.uploads -replication 001 + s3.clean.uploads -timeAgo 1.5h ` } From 89eb9f6e7073460838699b9c3a42f2cb830f8224 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 22 Apr 2021 23:23:23 -0700 Subject: [PATCH 082/128] clean up .uploads directory in mysql and postgres tables fix https://github.com/chrislusf/seaweedfs/issues/1957 When no uploads are running, you can run this SQL to clean up. delete from where directory like '/.uploads/%' --- weed/filer/abstract_sql/abstract_sql_store.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/weed/filer/abstract_sql/abstract_sql_store.go b/weed/filer/abstract_sql/abstract_sql_store.go index 120a1d139..ab8f6bcbd 100644 --- a/weed/filer/abstract_sql/abstract_sql_store.go +++ b/weed/filer/abstract_sql/abstract_sql_store.go @@ -276,7 +276,9 @@ func (store *AbstractSqlStore) DeleteFolderChildren(ctx context.Context, fullpat } } - res, err := db.ExecContext(ctx, store.GetSqlDeleteFolderChildren(bucket), util.HashStringToLong(string(shortPath)), fullpath) + glog.V(4).Infof("delete %s SQL %s %d", string(shortPath), store.GetSqlDeleteFolderChildren(bucket), util.HashStringToLong(string(shortPath))) + + res, err := db.ExecContext(ctx, store.GetSqlDeleteFolderChildren(bucket), util.HashStringToLong(string(shortPath)), string(shortPath)) if err != nil { return fmt.Errorf("deleteFolderChildren %s: %s", fullpath, err) } From f0ad172e80e00222bb0ea9a31cb83fdd7e7844a8 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 22 Apr 2021 23:56:35 -0700 Subject: [PATCH 083/128] shell: show which server holds the lock fix https://github.com/chrislusf/seaweedfs/issues/1983 --- weed/pb/master.proto | 1 + weed/pb/master_pb/master.pb.go | 206 +++++++++--------- weed/server/master_grpc_server_admin.go | 21 +- weed/shell/command_fs_lock_unlock.go | 3 +- .../exclusive_locks/exclusive_locker.go | 6 +- 5 files changed, 128 insertions(+), 109 deletions(-) diff --git a/weed/pb/master.proto b/weed/pb/master.proto index 6a1758ccc..cdb49d1e3 100644 --- a/weed/pb/master.proto +++ b/weed/pb/master.proto @@ -288,6 +288,7 @@ message LeaseAdminTokenRequest { int64 previous_token = 1; int64 previous_lock_time = 2; string lock_name = 3; + string client_name = 4; } message LeaseAdminTokenResponse { int64 token = 1; diff --git a/weed/pb/master_pb/master.pb.go b/weed/pb/master_pb/master.pb.go index 7e1f282dd..29d8499f8 100644 --- a/weed/pb/master_pb/master.pb.go +++ b/weed/pb/master_pb/master.pb.go @@ -2468,6 +2468,7 @@ type LeaseAdminTokenRequest struct { PreviousToken int64 `protobuf:"varint,1,opt,name=previous_token,json=previousToken,proto3" json:"previous_token,omitempty"` PreviousLockTime int64 `protobuf:"varint,2,opt,name=previous_lock_time,json=previousLockTime,proto3" json:"previous_lock_time,omitempty"` LockName string `protobuf:"bytes,3,opt,name=lock_name,json=lockName,proto3" json:"lock_name,omitempty"` + ClientName string `protobuf:"bytes,4,opt,name=client_name,json=clientName,proto3" json:"client_name,omitempty"` } func (x *LeaseAdminTokenRequest) Reset() { @@ -2523,6 +2524,13 @@ func (x *LeaseAdminTokenRequest) GetLockName() string { return "" } +func (x *LeaseAdminTokenRequest) GetClientName() string { + if x != nil { + return x.ClientName + } + return "" +} + type LeaseAdminTokenResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -3268,7 +3276,7 @@ var file_master_proto_rawDesc = []byte{ 0x73, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x0d, 0x67, 0x72, 0x70, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x8a, + 0x0d, 0x67, 0x72, 0x70, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0xab, 0x01, 0x0a, 0x16, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, @@ -3277,103 +3285,105 @@ var file_master_proto_rawDesc = []byte{ 0x6b, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x4c, 0x6f, 0x63, 0x6b, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x4d, 0x0a, 0x17, 0x4c, - 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1c, 0x0a, 0x0a, - 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x73, 0x5f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x54, 0x73, 0x4e, 0x73, 0x22, 0x8c, 0x01, 0x0a, 0x18, 0x52, - 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x65, 0x76, 0x69, - 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0d, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x2c, - 0x0a, 0x12, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, - 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x70, 0x72, 0x65, 0x76, - 0x69, 0x6f, 0x75, 0x73, 0x4c, 0x6f, 0x63, 0x6b, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, - 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x1b, 0x0a, 0x19, 0x52, 0x65, 0x6c, - 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xca, 0x09, 0x0a, 0x07, 0x53, 0x65, 0x61, 0x77, 0x65, - 0x65, 0x64, 0x12, 0x49, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, - 0x65, 0x61, 0x74, 0x12, 0x14, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, - 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x73, 0x74, - 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x51, 0x0a, - 0x0d, 0x4b, 0x65, 0x65, 0x70, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x1f, - 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4b, 0x65, 0x65, 0x70, 0x43, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x19, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x56, 0x6f, 0x6c, 0x75, - 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, - 0x12, 0x51, 0x0a, 0x0c, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, - 0x12, 0x1e, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x6f, - 0x6b, 0x75, 0x70, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1f, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x6f, - 0x6b, 0x75, 0x70, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x06, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x12, 0x18, 0x2e, - 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, - 0x5f, 0x70, 0x62, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, - 0x63, 0x73, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x53, - 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x61, - 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x57, 0x0a, 0x0e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4c, - 0x69, 0x73, 0x74, 0x12, 0x20, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, - 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, - 0x62, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5d, 0x0a, 0x10, 0x43, 0x6f, - 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x22, - 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x43, - 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0a, 0x56, 0x6f, 0x6c, - 0x75, 0x6d, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, - 0x5f, 0x70, 0x62, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, - 0x62, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x0e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, - 0x45, 0x63, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x12, 0x20, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, - 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x63, 0x56, 0x6f, 0x6c, - 0x75, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6d, 0x61, 0x73, - 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x63, 0x56, - 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x51, 0x0a, 0x0c, 0x56, 0x61, 0x63, 0x75, 0x75, 0x6d, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x12, - 0x1e, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x56, 0x61, 0x63, 0x75, - 0x75, 0x6d, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1f, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x56, 0x61, 0x63, 0x75, - 0x75, 0x6d, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x6f, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x2e, 0x6d, - 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x73, 0x74, - 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, - 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x12, 0x60, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, - 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x23, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, - 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x43, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, - 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, - 0x73, 0x74, 0x65, 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x0f, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, - 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x21, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, - 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x6d, 0x61, - 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, - 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x60, 0x0a, 0x11, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, - 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, - 0x70, 0x62, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x6d, 0x61, - 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x41, - 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x63, 0x68, 0x72, 0x69, 0x73, 0x6c, 0x75, 0x73, 0x66, 0x2f, 0x73, 0x65, 0x61, 0x77, - 0x65, 0x65, 0x64, 0x66, 0x73, 0x2f, 0x77, 0x65, 0x65, 0x64, 0x2f, 0x70, 0x62, 0x2f, 0x6d, 0x61, - 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x4d, 0x0a, 0x17, + 0x4c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1c, 0x0a, + 0x0a, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x73, 0x5f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x54, 0x73, 0x4e, 0x73, 0x22, 0x8c, 0x01, 0x0a, 0x18, + 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x65, 0x76, + 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0d, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, + 0x2c, 0x0a, 0x12, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, + 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x70, 0x72, 0x65, + 0x76, 0x69, 0x6f, 0x75, 0x73, 0x4c, 0x6f, 0x63, 0x6b, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1b, 0x0a, + 0x09, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x1b, 0x0a, 0x19, 0x52, 0x65, + 0x6c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xca, 0x09, 0x0a, 0x07, 0x53, 0x65, 0x61, 0x77, + 0x65, 0x65, 0x64, 0x12, 0x49, 0x0a, 0x0d, 0x53, 0x65, 0x6e, 0x64, 0x48, 0x65, 0x61, 0x72, 0x74, + 0x62, 0x65, 0x61, 0x74, 0x12, 0x14, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, + 0x2e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x73, + 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x51, + 0x0a, 0x0d, 0x4b, 0x65, 0x65, 0x70, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, + 0x1f, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4b, 0x65, 0x65, 0x70, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x19, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x56, 0x6f, 0x6c, + 0x75, 0x6d, 0x65, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x28, 0x01, 0x30, + 0x01, 0x12, 0x51, 0x0a, 0x0c, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x56, 0x6f, 0x6c, 0x75, 0x6d, + 0x65, 0x12, 0x1e, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f, + 0x6f, 0x6b, 0x75, 0x70, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1f, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f, + 0x6f, 0x6b, 0x75, 0x70, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x06, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x12, 0x18, + 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, + 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, + 0x69, 0x63, 0x73, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, + 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x53, 0x74, + 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x12, 0x57, 0x0a, 0x0e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x4c, 0x69, 0x73, 0x74, 0x12, 0x20, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, + 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, + 0x70, 0x62, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5d, 0x0a, 0x10, 0x43, + 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, + 0x22, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, + 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0a, 0x56, 0x6f, + 0x6c, 0x75, 0x6d, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, + 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, + 0x70, 0x62, 0x2e, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x0e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, + 0x70, 0x45, 0x63, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x12, 0x20, 0x2e, 0x6d, 0x61, 0x73, 0x74, + 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x63, 0x56, 0x6f, + 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x6d, 0x61, + 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x45, 0x63, + 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x12, 0x51, 0x0a, 0x0c, 0x56, 0x61, 0x63, 0x75, 0x75, 0x6d, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, + 0x12, 0x1e, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x56, 0x61, 0x63, + 0x75, 0x75, 0x6d, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1f, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x56, 0x61, 0x63, + 0x75, 0x75, 0x6d, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x12, 0x6f, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x2e, + 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x73, + 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, + 0x5f, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x60, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x73, 0x74, + 0x65, 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x23, 0x2e, 0x6d, 0x61, 0x73, 0x74, + 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, + 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, + 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, + 0x61, 0x73, 0x74, 0x65, 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x0f, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x41, + 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x21, 0x2e, 0x6d, 0x61, 0x73, 0x74, + 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x6d, + 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, + 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x12, 0x60, 0x0a, 0x11, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, + 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x2e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, + 0x5f, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x6d, + 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, + 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x63, 0x68, 0x72, 0x69, 0x73, 0x6c, 0x75, 0x73, 0x66, 0x2f, 0x73, 0x65, 0x61, + 0x77, 0x65, 0x65, 0x64, 0x66, 0x73, 0x2f, 0x77, 0x65, 0x65, 0x64, 0x2f, 0x70, 0x62, 0x2f, 0x6d, + 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/weed/server/master_grpc_server_admin.go b/weed/server/master_grpc_server_admin.go index 7e7dcb36b..93c9e4e4e 100644 --- a/weed/server/master_grpc_server_admin.go +++ b/weed/server/master_grpc_server_admin.go @@ -3,6 +3,7 @@ package weed_server import ( "context" "fmt" + "github.com/chrislusf/seaweedfs/weed/glog" "math/rand" "sync" "time" @@ -60,6 +61,7 @@ const ( type AdminLock struct { accessSecret int64 accessLockTime time.Time + lastClient string } type AdminLocks struct { @@ -73,14 +75,15 @@ func NewAdminLocks() *AdminLocks { } } -func (locks *AdminLocks) isLocked(lockName string) bool { +func (locks *AdminLocks) isLocked(lockName string) (clientName string, isLocked bool) { locks.RLock() defer locks.RUnlock() adminLock, found := locks.locks[lockName] if !found { - return false + return "", false } - return adminLock.accessLockTime.Add(LockDuration).After(time.Now()) + glog.V(4).Infof("isLocked %v", adminLock.lastClient) + return adminLock.lastClient, adminLock.accessLockTime.Add(LockDuration).After(time.Now()) } func (locks *AdminLocks) isValidToken(lockName string, ts time.Time, token int64) bool { @@ -93,12 +96,13 @@ func (locks *AdminLocks) isValidToken(lockName string, ts time.Time, token int64 return adminLock.accessLockTime.Equal(ts) && adminLock.accessSecret == token } -func (locks *AdminLocks) generateToken(lockName string) (ts time.Time, token int64) { +func (locks *AdminLocks) generateToken(lockName string, clientName string) (ts time.Time, token int64) { locks.Lock() defer locks.Unlock() lock := &AdminLock{ accessSecret: rand.Int63(), accessLockTime: time.Now(), + lastClient: clientName, } locks.locks[lockName] = lock return lock.accessLockTime, lock.accessSecret @@ -113,18 +117,19 @@ func (locks *AdminLocks) deleteLock(lockName string) { func (ms *MasterServer) LeaseAdminToken(ctx context.Context, req *master_pb.LeaseAdminTokenRequest) (*master_pb.LeaseAdminTokenResponse, error) { resp := &master_pb.LeaseAdminTokenResponse{} - if ms.adminLocks.isLocked(req.LockName) { + if lastClient, isLocked := ms.adminLocks.isLocked(req.LockName); isLocked { + glog.V(4).Infof("LeaseAdminToken %v", lastClient) if req.PreviousToken != 0 && ms.adminLocks.isValidToken(req.LockName, time.Unix(0, req.PreviousLockTime), req.PreviousToken) { // for renew - ts, token := ms.adminLocks.generateToken(req.LockName) + ts, token := ms.adminLocks.generateToken(req.LockName, req.ClientName) resp.Token, resp.LockTsNs = token, ts.UnixNano() return resp, nil } // refuse since still locked - return resp, fmt.Errorf("already locked") + return resp, fmt.Errorf("already locked by " + lastClient) } // for fresh lease request - ts, token := ms.adminLocks.generateToken(req.LockName) + ts, token := ms.adminLocks.generateToken(req.LockName, req.ClientName) resp.Token, resp.LockTsNs = token, ts.UnixNano() return resp, nil } diff --git a/weed/shell/command_fs_lock_unlock.go b/weed/shell/command_fs_lock_unlock.go index 8a6e8f71b..33458bb6f 100644 --- a/weed/shell/command_fs_lock_unlock.go +++ b/weed/shell/command_fs_lock_unlock.go @@ -1,6 +1,7 @@ package shell import ( + "github.com/chrislusf/seaweedfs/weed/util" "io" ) @@ -26,7 +27,7 @@ func (c *commandLock) Help() string { func (c *commandLock) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) { - commandEnv.locker.RequestLock() + commandEnv.locker.RequestLock(util.DetectedHostAddress()) return nil } diff --git a/weed/wdclient/exclusive_locks/exclusive_locker.go b/weed/wdclient/exclusive_locks/exclusive_locker.go index d477a6b2d..5b5fa2704 100644 --- a/weed/wdclient/exclusive_locks/exclusive_locker.go +++ b/weed/wdclient/exclusive_locks/exclusive_locker.go @@ -41,7 +41,7 @@ func (l *ExclusiveLocker) GetToken() (token int64, lockTsNs int64) { return atomic.LoadInt64(&l.token), atomic.LoadInt64(&l.lockTsNs) } -func (l *ExclusiveLocker) RequestLock() { +func (l *ExclusiveLocker) RequestLock(clientName string) { if l.isLocking { return } @@ -56,6 +56,7 @@ func (l *ExclusiveLocker) RequestLock() { PreviousToken: atomic.LoadInt64(&l.token), PreviousLockTime: atomic.LoadInt64(&l.lockTsNs), LockName: AdminLockName, + ClientName: clientName, }) if err == nil { atomic.StoreInt64(&l.token, resp.Token) @@ -63,7 +64,7 @@ func (l *ExclusiveLocker) RequestLock() { } return err }); err != nil { - // println("leasing problem", err.Error()) + println("lock:", err.Error()) time.Sleep(InitLockInteval) } else { break @@ -83,6 +84,7 @@ func (l *ExclusiveLocker) RequestLock() { PreviousToken: atomic.LoadInt64(&l.token), PreviousLockTime: atomic.LoadInt64(&l.lockTsNs), LockName: AdminLockName, + ClientName: clientName, }) if err == nil { atomic.StoreInt64(&l.token, resp.Token) From ddc8643ee0a4bb48ba4628461a69c556d2011622 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sat, 24 Apr 2021 11:49:03 -0700 Subject: [PATCH 084/128] filer: directory listing adds namePatternExclude fix https://github.com/chrislusf/seaweedfs/issues/2023 --- weed/filer/filer_buckets.go | 2 +- weed/filer/filer_delete_entry.go | 2 +- weed/filer/filer_notify.go | 4 +-- weed/filer/filer_search.go | 32 ++++++++++++------- weed/filer/leveldb/leveldb_store_test.go | 6 ++-- weed/filer/leveldb2/leveldb2_store_test.go | 6 ++-- weed/filer/leveldb3/leveldb3_store_test.go | 6 ++-- weed/filer/rocksdb/rocksdb_store_test.go | 6 ++-- weed/server/filer_grpc_server.go | 2 +- weed/server/filer_grpc_server_rename.go | 2 +- weed/server/filer_server_handlers_read_dir.go | 3 +- 11 files changed, 40 insertions(+), 31 deletions(-) diff --git a/weed/filer/filer_buckets.go b/weed/filer/filer_buckets.go index ba170f02e..43fb000c9 100644 --- a/weed/filer/filer_buckets.go +++ b/weed/filer/filer_buckets.go @@ -29,7 +29,7 @@ func (f *Filer) LoadBuckets() { limit := int64(math.MaxInt32) - entries, _, err := f.ListDirectoryEntries(context.Background(), util.FullPath(f.DirBucketsPath), "", false, limit, "", "") + entries, _, err := f.ListDirectoryEntries(context.Background(), util.FullPath(f.DirBucketsPath), "", false, limit, "", "", "") if err != nil { glog.V(1).Infof("no buckets found: %v", err) diff --git a/weed/filer/filer_delete_entry.go b/weed/filer/filer_delete_entry.go index bedf2f4d1..3ef3cfff9 100644 --- a/weed/filer/filer_delete_entry.go +++ b/weed/filer/filer_delete_entry.go @@ -73,7 +73,7 @@ func (f *Filer) doBatchDeleteFolderMetaAndData(ctx context.Context, entry *Entry includeLastFile := false if !isDeletingBucket { for { - entries, _, err := f.ListDirectoryEntries(ctx, entry.FullPath, lastFileName, includeLastFile, PaginationSize, "", "") + entries, _, err := f.ListDirectoryEntries(ctx, entry.FullPath, lastFileName, includeLastFile, PaginationSize, "", "", "") if err != nil { glog.Errorf("list folder %s: %v", entry.FullPath, err) return nil, nil, fmt.Errorf("list folder %s: %v", entry.FullPath, err) diff --git a/weed/filer/filer_notify.go b/weed/filer/filer_notify.go index c461a82b8..7ab101102 100644 --- a/weed/filer/filer_notify.go +++ b/weed/filer/filer_notify.go @@ -116,13 +116,13 @@ func (f *Filer) ReadPersistedLogBuffer(startTime time.Time, eachLogEntryFn func( sizeBuf := make([]byte, 4) startTsNs := startTime.UnixNano() - dayEntries, _, listDayErr := f.ListDirectoryEntries(context.Background(), SystemLogDir, startDate, true, 366, "", "") + dayEntries, _, listDayErr := f.ListDirectoryEntries(context.Background(), SystemLogDir, startDate, true, 366, "", "", "") if listDayErr != nil { return lastTsNs, fmt.Errorf("fail to list log by day: %v", listDayErr) } for _, dayEntry := range dayEntries { // println("checking day", dayEntry.FullPath) - hourMinuteEntries, _, listHourMinuteErr := f.ListDirectoryEntries(context.Background(), util.NewFullPath(SystemLogDir, dayEntry.Name()), "", false, 24*60, "", "") + hourMinuteEntries, _, listHourMinuteErr := f.ListDirectoryEntries(context.Background(), util.NewFullPath(SystemLogDir, dayEntry.Name()), "", false, 24*60, "", "", "") if listHourMinuteErr != nil { return lastTsNs, fmt.Errorf("fail to list log %s by day: %v", dayEntry.Name(), listHourMinuteErr) } diff --git a/weed/filer/filer_search.go b/weed/filer/filer_search.go index 0a14d3756..dd1f8c1ab 100644 --- a/weed/filer/filer_search.go +++ b/weed/filer/filer_search.go @@ -20,9 +20,9 @@ func splitPattern(pattern string) (prefix string, restPattern string) { } // For now, prefix and namePattern are mutually exclusive -func (f *Filer) ListDirectoryEntries(ctx context.Context, p util.FullPath, startFileName string, inclusive bool, limit int64, prefix string, namePattern string) (entries []*Entry, hasMore bool, err error) { +func (f *Filer) ListDirectoryEntries(ctx context.Context, p util.FullPath, startFileName string, inclusive bool, limit int64, prefix string, namePattern string, namePatternExclude string) (entries []*Entry, hasMore bool, err error) { - _, err = f.StreamListDirectoryEntries(ctx, p, startFileName, inclusive, limit+1, prefix, namePattern, func(entry *Entry) bool { + _, err = f.StreamListDirectoryEntries(ctx, p, startFileName, inclusive, limit+1, prefix, namePattern, namePatternExclude, func(entry *Entry) bool { entries = append(entries, entry) return true }) @@ -36,7 +36,7 @@ func (f *Filer) ListDirectoryEntries(ctx context.Context, p util.FullPath, start } // For now, prefix and namePattern are mutually exclusive -func (f *Filer) StreamListDirectoryEntries(ctx context.Context, p util.FullPath, startFileName string, inclusive bool, limit int64, prefix string, namePattern string, eachEntryFunc ListEachEntryFunc) (lastFileName string, err error) { +func (f *Filer) StreamListDirectoryEntries(ctx context.Context, p util.FullPath, startFileName string, inclusive bool, limit int64, prefix string, namePattern string, namePatternExclude string, eachEntryFunc ListEachEntryFunc) (lastFileName string, err error) { if strings.HasSuffix(string(p), "/") && len(p) > 1 { p = p[0 : len(p)-1] } @@ -47,30 +47,38 @@ func (f *Filer) StreamListDirectoryEntries(ctx context.Context, p util.FullPath, } var missedCount int64 - missedCount, lastFileName, err = f.doListPatternMatchedEntries(ctx, p, startFileName, inclusive, limit, prefix, restNamePattern, eachEntryFunc) + missedCount, lastFileName, err = f.doListPatternMatchedEntries(ctx, p, startFileName, inclusive, limit, prefix, restNamePattern, namePatternExclude, eachEntryFunc) for missedCount > 0 && err == nil { - missedCount, lastFileName, err = f.doListPatternMatchedEntries(ctx, p, lastFileName, false, missedCount, prefix, restNamePattern, eachEntryFunc) + missedCount, lastFileName, err = f.doListPatternMatchedEntries(ctx, p, lastFileName, false, missedCount, prefix, restNamePattern, namePatternExclude, eachEntryFunc) } return } -func (f *Filer) doListPatternMatchedEntries(ctx context.Context, p util.FullPath, startFileName string, inclusive bool, limit int64, prefix, restNamePattern string, eachEntryFunc ListEachEntryFunc) (missedCount int64, lastFileName string, err error) { +func (f *Filer) doListPatternMatchedEntries(ctx context.Context, p util.FullPath, startFileName string, inclusive bool, limit int64, prefix, restNamePattern string, namePatternExclude string, eachEntryFunc ListEachEntryFunc) (missedCount int64, lastFileName string, err error) { - if len(restNamePattern) == 0 { + if len(restNamePattern) == 0 && len(namePatternExclude) == 0{ lastFileName, err = f.doListValidEntries(ctx, p, startFileName, inclusive, limit, prefix, eachEntryFunc) return 0, lastFileName, err } lastFileName, err = f.doListValidEntries(ctx, p, startFileName, inclusive, limit, prefix, func(entry *Entry) bool { nameToTest := strings.ToLower(entry.Name()) - if matched, matchErr := filepath.Match(restNamePattern, nameToTest[len(prefix):]); matchErr == nil && matched { - if !eachEntryFunc(entry) { - return false + if len(namePatternExclude) > 0 { + if matched, matchErr := filepath.Match(namePatternExclude, nameToTest); matchErr == nil && matched { + missedCount++ + return true } - } else { - missedCount++ + } + if len(restNamePattern) > 0 { + if matched, matchErr := filepath.Match(restNamePattern, nameToTest[len(prefix):]); matchErr == nil && !matched { + missedCount++ + return true + } + } + if !eachEntryFunc(entry) { + return false } return true }) diff --git a/weed/filer/leveldb/leveldb_store_test.go b/weed/filer/leveldb/leveldb_store_test.go index 9c342605e..d437895f5 100644 --- a/weed/filer/leveldb/leveldb_store_test.go +++ b/weed/filer/leveldb/leveldb_store_test.go @@ -51,14 +51,14 @@ func TestCreateAndFind(t *testing.T) { } // checking one upper directory - entries, _, _ := testFiler.ListDirectoryEntries(ctx, util.FullPath("/home/chris/this/is/one"), "", false, 100, "", "") + entries, _, _ := testFiler.ListDirectoryEntries(ctx, util.FullPath("/home/chris/this/is/one"), "", false, 100, "", "", "") if len(entries) != 1 { t.Errorf("list entries count: %v", len(entries)) return } // checking one upper directory - entries, _, _ = testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "") + entries, _, _ = testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "", "") if len(entries) != 1 { t.Errorf("list entries count: %v", len(entries)) return @@ -77,7 +77,7 @@ func TestEmptyRoot(t *testing.T) { ctx := context.Background() // checking one upper directory - entries, _, err := testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "") + entries, _, err := testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "", "") if err != nil { t.Errorf("list entries: %v", err) return diff --git a/weed/filer/leveldb2/leveldb2_store_test.go b/weed/filer/leveldb2/leveldb2_store_test.go index 495c73fdd..fd0ad18a3 100644 --- a/weed/filer/leveldb2/leveldb2_store_test.go +++ b/weed/filer/leveldb2/leveldb2_store_test.go @@ -49,14 +49,14 @@ func TestCreateAndFind(t *testing.T) { } // checking one upper directory - entries, _, _ := testFiler.ListDirectoryEntries(ctx, util.FullPath("/home/chris/this/is/one"), "", false, 100, "", "") + entries, _, _ := testFiler.ListDirectoryEntries(ctx, util.FullPath("/home/chris/this/is/one"), "", false, 100, "", "", "") if len(entries) != 1 { t.Errorf("list entries count: %v", len(entries)) return } // checking one upper directory - entries, _, _ = testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "") + entries, _, _ = testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "", "") if len(entries) != 1 { t.Errorf("list entries count: %v", len(entries)) return @@ -75,7 +75,7 @@ func TestEmptyRoot(t *testing.T) { ctx := context.Background() // checking one upper directory - entries, _, err := testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "") + entries, _, err := testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "", "") if err != nil { t.Errorf("list entries: %v", err) return diff --git a/weed/filer/leveldb3/leveldb3_store_test.go b/weed/filer/leveldb3/leveldb3_store_test.go index 53b0e927f..0b970a539 100644 --- a/weed/filer/leveldb3/leveldb3_store_test.go +++ b/weed/filer/leveldb3/leveldb3_store_test.go @@ -49,14 +49,14 @@ func TestCreateAndFind(t *testing.T) { } // checking one upper directory - entries, _, _ := testFiler.ListDirectoryEntries(ctx, util.FullPath("/home/chris/this/is/one"), "", false, 100, "", "") + entries, _, _ := testFiler.ListDirectoryEntries(ctx, util.FullPath("/home/chris/this/is/one"), "", false, 100, "", "", "") if len(entries) != 1 { t.Errorf("list entries count: %v", len(entries)) return } // checking one upper directory - entries, _, _ = testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "") + entries, _, _ = testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "", "") if len(entries) != 1 { t.Errorf("list entries count: %v", len(entries)) return @@ -75,7 +75,7 @@ func TestEmptyRoot(t *testing.T) { ctx := context.Background() // checking one upper directory - entries, _, err := testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "") + entries, _, err := testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "", "") if err != nil { t.Errorf("list entries: %v", err) return diff --git a/weed/filer/rocksdb/rocksdb_store_test.go b/weed/filer/rocksdb/rocksdb_store_test.go index 439663524..f6e755b4b 100644 --- a/weed/filer/rocksdb/rocksdb_store_test.go +++ b/weed/filer/rocksdb/rocksdb_store_test.go @@ -53,14 +53,14 @@ func TestCreateAndFind(t *testing.T) { } // checking one upper directory - entries, _, _ := testFiler.ListDirectoryEntries(ctx, util.FullPath("/home/chris/this/is/one"), "", false, 100, "", "") + entries, _, _ := testFiler.ListDirectoryEntries(ctx, util.FullPath("/home/chris/this/is/one"), "", false, 100, "", "", "") if len(entries) != 1 { t.Errorf("list entries count: %v", len(entries)) return } // checking one upper directory - entries, _, _ = testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "") + entries, _, _ = testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "", "") if len(entries) != 1 { t.Errorf("list entries count: %v", len(entries)) return @@ -79,7 +79,7 @@ func TestEmptyRoot(t *testing.T) { ctx := context.Background() // checking one upper directory - entries, _, err := testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "") + entries, _, err := testFiler.ListDirectoryEntries(ctx, util.FullPath("/"), "", false, 100, "", "", "") if err != nil { t.Errorf("list entries: %v", err) return diff --git a/weed/server/filer_grpc_server.go b/weed/server/filer_grpc_server.go index a4bb721ef..3821de6a9 100644 --- a/weed/server/filer_grpc_server.go +++ b/weed/server/filer_grpc_server.go @@ -63,7 +63,7 @@ func (fs *FilerServer) ListEntries(req *filer_pb.ListEntriesRequest, stream file var listErr error for limit > 0 { var hasEntries bool - lastFileName, listErr = fs.filer.StreamListDirectoryEntries(stream.Context(), util.FullPath(req.Directory), lastFileName, includeLastFile, int64(paginationLimit), req.Prefix, "", func(entry *filer.Entry) bool { + lastFileName, listErr = fs.filer.StreamListDirectoryEntries(stream.Context(), util.FullPath(req.Directory), lastFileName, includeLastFile, int64(paginationLimit), req.Prefix, "", "", func(entry *filer.Entry) bool { hasEntries = true if err = stream.Send(&filer_pb.ListEntriesResponse{ Entry: &filer_pb.Entry{ diff --git a/weed/server/filer_grpc_server_rename.go b/weed/server/filer_grpc_server_rename.go index c1e5bc789..eadb970d5 100644 --- a/weed/server/filer_grpc_server_rename.go +++ b/weed/server/filer_grpc_server_rename.go @@ -74,7 +74,7 @@ func (fs *FilerServer) moveFolderSubEntries(ctx context.Context, oldParent util. includeLastFile := false for { - entries, hasMore, err := fs.filer.ListDirectoryEntries(ctx, currentDirPath, lastFileName, includeLastFile, 1024, "", "") + entries, hasMore, err := fs.filer.ListDirectoryEntries(ctx, currentDirPath, lastFileName, includeLastFile, 1024, "", "", "") if err != nil { return err } diff --git a/weed/server/filer_server_handlers_read_dir.go b/weed/server/filer_server_handlers_read_dir.go index 9cf79ab41..307c411b6 100644 --- a/weed/server/filer_server_handlers_read_dir.go +++ b/weed/server/filer_server_handlers_read_dir.go @@ -35,8 +35,9 @@ func (fs *FilerServer) listDirectoryHandler(w http.ResponseWriter, r *http.Reque lastFileName := r.FormValue("lastFileName") namePattern := r.FormValue("namePattern") + namePatternExclude := r.FormValue("namePatternExclude") - entries, shouldDisplayLoadMore, err := fs.filer.ListDirectoryEntries(context.Background(), util.FullPath(path), lastFileName, false, int64(limit), "", namePattern) + entries, shouldDisplayLoadMore, err := fs.filer.ListDirectoryEntries(context.Background(), util.FullPath(path), lastFileName, false, int64(limit), "", namePattern, namePatternExclude) if err != nil { glog.V(0).Infof("listDirectory %s %s %d: %s", path, lastFileName, limit, err) From 79f2e780c18052b3e9bb1fa5fd28a1c04f425863 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sat, 24 Apr 2021 11:51:23 -0700 Subject: [PATCH 085/128] ensure name pattern checking is case sensitive --- weed/filer/filer_search.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weed/filer/filer_search.go b/weed/filer/filer_search.go index dd1f8c1ab..f43312cfa 100644 --- a/weed/filer/filer_search.go +++ b/weed/filer/filer_search.go @@ -64,7 +64,7 @@ func (f *Filer) doListPatternMatchedEntries(ctx context.Context, p util.FullPath } lastFileName, err = f.doListValidEntries(ctx, p, startFileName, inclusive, limit, prefix, func(entry *Entry) bool { - nameToTest := strings.ToLower(entry.Name()) + nameToTest := entry.Name() if len(namePatternExclude) > 0 { if matched, matchErr := filepath.Match(namePatternExclude, nameToTest); matchErr == nil && matched { missedCount++ From 86185262bb86e31f9a2f71e85d02df2502c7ad40 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sat, 24 Apr 2021 16:54:36 -0700 Subject: [PATCH 086/128] 2.41 --- k8s/seaweedfs/Chart.yaml | 4 ++-- k8s/seaweedfs/values.yaml | 2 +- weed/util/constants.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/k8s/seaweedfs/Chart.yaml b/k8s/seaweedfs/Chart.yaml index 8eddb2ca2..0025c9760 100644 --- a/k8s/seaweedfs/Chart.yaml +++ b/k8s/seaweedfs/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v1 description: SeaweedFS name: seaweedfs -appVersion: "2.40" -version: 2.40 +appVersion: "2.41" +version: 2.41 diff --git a/k8s/seaweedfs/values.yaml b/k8s/seaweedfs/values.yaml index 058170bbe..a4abaccf3 100644 --- a/k8s/seaweedfs/values.yaml +++ b/k8s/seaweedfs/values.yaml @@ -4,7 +4,7 @@ global: registry: "" repository: "" imageName: chrislusf/seaweedfs - # imageTag: "2.40" - started using {.Chart.appVersion} + # imageTag: "2.41" - started using {.Chart.appVersion} imagePullPolicy: IfNotPresent imagePullSecrets: imagepullsecret restartPolicy: Always diff --git a/weed/util/constants.go b/weed/util/constants.go index d81138d86..c595f0c53 100644 --- a/weed/util/constants.go +++ b/weed/util/constants.go @@ -5,7 +5,7 @@ import ( ) var ( - VERSION = fmt.Sprintf("%s %d.%02d", sizeLimit, 2, 40) + VERSION = fmt.Sprintf("%s %d.%02d", sizeLimit, 2, 41) COMMIT = "" ) From 31f1cdeac281fb88a3d03743f9796f81e1d74378 Mon Sep 17 00:00:00 2001 From: bingoohuang Date: Mon, 26 Apr 2021 18:48:34 +0800 Subject: [PATCH 087/128] minFreeSpace argument allows size like 10GiB --- weed/command/volume.go | 30 +++++----- weed/server/volume_server.go | 4 +- weed/storage/disk_location.go | 24 +++++--- weed/storage/store.go | 4 +- weed/util/bytes.go | 107 ++++++++++++++++++++++++++++++++++ weed/util/bytes_test.go | 85 +++++++++++++++++++++++++++ 6 files changed, 227 insertions(+), 27 deletions(-) create mode 100644 weed/util/bytes_test.go diff --git a/weed/command/volume.go b/weed/command/volume.go index 9df500178..93a19102d 100644 --- a/weed/command/volume.go +++ b/weed/command/volume.go @@ -57,7 +57,7 @@ type VolumeServerOptions struct { compactionMBPerSecond *int fileSizeLimitMB *int concurrentUploadLimitMB *int - minFreeSpacePercents []float32 + minFreeSpaces []float32 pprof *bool preStopSeconds *int metricsHttpPort *int @@ -105,7 +105,8 @@ var ( volumeFolders = cmdVolume.Flag.String("dir", os.TempDir(), "directories to store data files. dir[,dir]...") maxVolumeCounts = cmdVolume.Flag.String("max", "8", "maximum numbers of volumes, count[,count]... If set to zero, the limit will be auto configured.") volumeWhiteListOption = cmdVolume.Flag.String("whiteList", "", "comma separated Ip addresses having write permission. No limit if empty.") - minFreeSpacePercent = cmdVolume.Flag.String("minFreeSpacePercent", "1", "minimum free disk space (default to 1%). Low disk space will mark all volumes as ReadOnly.") + minFreeSpacePercent = cmdVolume.Flag.String("minFreeSpacePercent", "1", "minimum free disk space (default to 1%). Low disk space will mark all volumes as ReadOnly (deprecated, use minFreeSpace instead).") + minFreeSpace = cmdVolume.Flag.String("minFreeSpace", "", "min free disk space (value<=100 as percentage like 1, other as human readable bytes, like 10GiB). Low disk space will mark all volumes as ReadOnly.") ) func runVolume(cmd *Command, args []string) bool { @@ -120,12 +121,13 @@ func runVolume(cmd *Command, args []string) bool { go stats_collect.StartMetricsServer(*v.metricsHttpPort) - v.startVolumeServer(*volumeFolders, *maxVolumeCounts, *volumeWhiteListOption, *minFreeSpacePercent) + v.startVolumeServer(*volumeFolders, *maxVolumeCounts, *volumeWhiteListOption, + util.EmptyTo(*minFreeSpace, *minFreeSpacePercent)) return true } -func (v VolumeServerOptions) startVolumeServer(volumeFolders, maxVolumeCounts, volumeWhiteListOption, minFreeSpacePercent string) { +func (v VolumeServerOptions) startVolumeServer(volumeFolders, maxVolumeCounts, volumeWhiteListOption, minFreeSpace string) { // Set multiple folders and each folder's max volume count limit' v.folders = strings.Split(volumeFolders, ",") @@ -154,21 +156,21 @@ func (v VolumeServerOptions) startVolumeServer(volumeFolders, maxVolumeCounts, v } // set minFreeSpacePercent - minFreeSpacePercentStrings := strings.Split(minFreeSpacePercent, ",") - for _, freeString := range minFreeSpacePercentStrings { - if value, e := strconv.ParseFloat(freeString, 32); e == nil { - v.minFreeSpacePercents = append(v.minFreeSpacePercents, float32(value)) + minFreeSpaceStrings := strings.Split(minFreeSpace, ",") + for _, freeString := range minFreeSpaceStrings { + if vv, e := util.ParseMinFreeSpace(freeString); e == nil { + v.minFreeSpaces = append(v.minFreeSpaces, vv) } else { - glog.Fatalf("The value specified in -minFreeSpacePercent not a valid value %s", freeString) + glog.Fatalf("The value specified in -minFreeSpace not a valid value %s", freeString) } } - if len(v.minFreeSpacePercents) == 1 && len(v.folders) > 1 { + if len(v.minFreeSpaces) == 1 && len(v.folders) > 1 { for i := 0; i < len(v.folders)-1; i++ { - v.minFreeSpacePercents = append(v.minFreeSpacePercents, v.minFreeSpacePercents[0]) + v.minFreeSpaces = append(v.minFreeSpaces, v.minFreeSpaces[0]) } } - if len(v.folders) != len(v.minFreeSpacePercents) { - glog.Fatalf("%d directories by -dir, but only %d minFreeSpacePercent is set by -minFreeSpacePercent", len(v.folders), len(v.minFreeSpacePercents)) + if len(v.folders) != len(v.minFreeSpaces) { + glog.Fatalf("%d directories by -dir, but only %d minFreeSpacePercent is set by -minFreeSpacePercent", len(v.folders), len(v.minFreeSpaces)) } // set disk types @@ -231,7 +233,7 @@ func (v VolumeServerOptions) startVolumeServer(volumeFolders, maxVolumeCounts, v volumeServer := weed_server.NewVolumeServer(volumeMux, publicVolumeMux, *v.ip, *v.port, *v.publicUrl, - v.folders, v.folderMaxLimits, v.minFreeSpacePercents, diskTypes, + v.folders, v.folderMaxLimits, v.minFreeSpaces, diskTypes, *v.idxFolder, volumeNeedleMapKind, strings.Split(masters, ","), 5, *v.dataCenter, *v.rack, diff --git a/weed/server/volume_server.go b/weed/server/volume_server.go index e11d607a4..f29f12148 100644 --- a/weed/server/volume_server.go +++ b/weed/server/volume_server.go @@ -43,7 +43,7 @@ type VolumeServer struct { func NewVolumeServer(adminMux, publicMux *http.ServeMux, ip string, port int, publicUrl string, - folders []string, maxCounts []int, minFreeSpacePercents []float32, diskTypes []types.DiskType, + folders []string, maxCounts []int, minFreeSpaces []float32, diskTypes []types.DiskType, idxFolder string, needleMapKind storage.NeedleMapKind, masterNodes []string, pulseSeconds int, @@ -85,7 +85,7 @@ func NewVolumeServer(adminMux, publicMux *http.ServeMux, ip string, vs.checkWithMaster() - vs.store = storage.NewStore(vs.grpcDialOption, port, ip, publicUrl, folders, maxCounts, minFreeSpacePercents, idxFolder, vs.needleMapKind, diskTypes) + vs.store = storage.NewStore(vs.grpcDialOption, port, ip, publicUrl, folders, maxCounts, minFreeSpaces, idxFolder, vs.needleMapKind, diskTypes) vs.guard = security.NewGuard(whiteList, signingKey, expiresAfterSec, readSigningKey, readExpiresAfterSec) handleStaticResources(adminMux) diff --git a/weed/storage/disk_location.go b/weed/storage/disk_location.go index ed4e00312..3246718c8 100644 --- a/weed/storage/disk_location.go +++ b/weed/storage/disk_location.go @@ -23,9 +23,10 @@ type DiskLocation struct { DiskType types.DiskType MaxVolumeCount int OriginalMaxVolumeCount int - MinFreeSpacePercent float32 - volumes map[needle.VolumeId]*Volume - volumesLock sync.RWMutex + // MinFreeSpace limits the minimum free space (<=100 as percentage, > 100 as bytes) + MinFreeSpace float32 + volumes map[needle.VolumeId]*Volume + volumesLock sync.RWMutex // erasure coding ecVolumes map[needle.VolumeId]*erasure_coding.EcVolume @@ -34,7 +35,7 @@ type DiskLocation struct { isDiskSpaceLow bool } -func NewDiskLocation(dir string, maxVolumeCount int, minFreeSpacePercent float32, idxDir string, diskType types.DiskType) *DiskLocation { +func NewDiskLocation(dir string, maxVolumeCount int, minFreeSpace float32, idxDir string, diskType types.DiskType) *DiskLocation { dir = util.ResolvePath(dir) if idxDir == "" { idxDir = dir @@ -47,7 +48,7 @@ func NewDiskLocation(dir string, maxVolumeCount int, minFreeSpacePercent float32 DiskType: diskType, MaxVolumeCount: maxVolumeCount, OriginalMaxVolumeCount: maxVolumeCount, - MinFreeSpacePercent: minFreeSpacePercent, + MinFreeSpace: minFreeSpace, } location.volumes = make(map[needle.VolumeId]*Volume) location.ecVolumes = make(map[needle.VolumeId]*erasure_coding.EcVolume) @@ -361,14 +362,19 @@ func (l *DiskLocation) CheckDiskSpace() { stats.VolumeServerResourceGauge.WithLabelValues(l.Directory, "all").Set(float64(s.All)) stats.VolumeServerResourceGauge.WithLabelValues(l.Directory, "used").Set(float64(s.Used)) stats.VolumeServerResourceGauge.WithLabelValues(l.Directory, "free").Set(float64(s.Free)) - if (s.PercentFree < l.MinFreeSpacePercent) != l.isDiskSpaceLow { + + isLow := l.MinFreeSpace < 100 && s.PercentFree < l.MinFreeSpace || s.Free < uint64(l.MinFreeSpace) + if isLow != l.isDiskSpaceLow { l.isDiskSpaceLow = !l.isDiskSpaceLow } + + logLevel := glog.Level(4) if l.isDiskSpaceLow { - glog.V(0).Infof("dir %s freePercent %.2f%% < min %.2f%%, isLowDiskSpace: %v", dir, s.PercentFree, l.MinFreeSpacePercent, l.isDiskSpaceLow) - } else { - glog.V(4).Infof("dir %s freePercent %.2f%% < min %.2f%%, isLowDiskSpace: %v", dir, s.PercentFree, l.MinFreeSpacePercent, l.isDiskSpaceLow) + logLevel = glog.Level(0) } + + glog.V(logLevel).Infof("dir %s freePercent %.2f%% < min %.2f%%, isLowDiskSpace: %v", + dir, s.PercentFree, l.MinFreeSpace, l.isDiskSpaceLow) } time.Sleep(time.Minute) } diff --git a/weed/storage/store.go b/weed/storage/store.go index 6be15a4c9..18496890c 100644 --- a/weed/storage/store.go +++ b/weed/storage/store.go @@ -52,11 +52,11 @@ func (s *Store) String() (str string) { return } -func NewStore(grpcDialOption grpc.DialOption, port int, ip, publicUrl string, dirnames []string, maxVolumeCounts []int, minFreeSpacePercents []float32, idxFolder string, needleMapKind NeedleMapKind, diskTypes []DiskType) (s *Store) { +func NewStore(grpcDialOption grpc.DialOption, port int, ip, publicUrl string, dirnames []string, maxVolumeCounts []int, minFreeSpaces []float32, idxFolder string, needleMapKind NeedleMapKind, diskTypes []DiskType) (s *Store) { s = &Store{grpcDialOption: grpcDialOption, Port: port, Ip: ip, PublicUrl: publicUrl, NeedleMapKind: needleMapKind} s.Locations = make([]*DiskLocation, 0) for i := 0; i < len(dirnames); i++ { - location := NewDiskLocation(dirnames[i], maxVolumeCounts[i], minFreeSpacePercents[i], idxFolder, diskTypes[i]) + location := NewDiskLocation(dirnames[i], maxVolumeCounts[i], minFreeSpaces[i], idxFolder, diskTypes[i]) location.loadExistingVolumes(needleMapKind) s.Locations = append(s.Locations, location) stats.VolumeServerMaxVolumeCounter.Add(float64(maxVolumeCounts[i])) diff --git a/weed/util/bytes.go b/weed/util/bytes.go index c2a4df108..260e5067e 100644 --- a/weed/util/bytes.go +++ b/weed/util/bytes.go @@ -5,8 +5,13 @@ import ( "crypto/md5" "crypto/rand" "encoding/base64" + "errors" "fmt" "io" + "math" + "strconv" + "strings" + "unicode" ) // BytesToHumanReadable returns the converted human readable representation of the bytes. @@ -161,3 +166,105 @@ func NewBytesReader(b []byte) *BytesReader { Reader: bytes.NewReader(b), } } + +// EmptyTo returns to if s is empty. +func EmptyTo(s, to string) string { + if s == "" { + return to + } + + return s +} + +var ErrMinFreeSpaceBadValue = errors.New("minFreeSpace is invalid") + +// ParseMinFreeSpace parses min free space expression s as percentage like 1,10 or human readable size like 10G +func ParseMinFreeSpace(s string) (float32, error) { + if value, e := strconv.ParseFloat(s, 32); e == nil { + if value < 0 || value > 100 { + return 0, ErrMinFreeSpaceBadValue + } + return float32(value), nil + } else if directSize, e2 := ParseBytes(s); e2 == nil { + if directSize <= 100 { + return 0, ErrMinFreeSpaceBadValue + } + return float32(directSize), nil + } + + return 0, ErrMinFreeSpaceBadValue +} + +// ParseBytes parses a string representation of bytes into the number +// of bytes it represents. +// +// See Also: Bytes, IBytes. +// +// ParseBytes("42MB") -> 42000000, nil +// ParseBytes("42 MB") -> 42000000, nil +// ParseBytes("42 mib") -> 44040192, nil +func ParseBytes(s string) (uint64, error) { + lastDigit := 0 + hasComma := false + for _, r := range s { + if !(unicode.IsDigit(r) || r == '.' || r == ',') { + break + } + if r == ',' { + hasComma = true + } + lastDigit++ + } + + num := s[:lastDigit] + if hasComma { + num = strings.Replace(num, ",", "", -1) + } + + f, err := strconv.ParseFloat(num, 64) + if err != nil { + return 0, err + } + + extra := strings.ToLower(strings.TrimSpace(s[lastDigit:])) + if m, ok := bytesSizeTable[extra]; ok { + f *= float64(m) + if f >= math.MaxUint64 { + return 0, fmt.Errorf("too large: %v", s) + } + return uint64(f), nil + } + + return 0, fmt.Errorf("unhandled size name: %v", extra) +} + +var bytesSizeTable = map[string]uint64{ + "b": Byte, "kib": KiByte, "kb": KByte, "mib": MiByte, "mb": MByte, "gib": GiByte, "gb": GByte, + "tib": TiByte, "tb": TByte, "pib": PiByte, "pb": PByte, "eib": EiByte, "eb": EByte, + // Without suffix + "": Byte, "ki": KiByte, "k": KByte, "mi": MiByte, "m": MByte, "gi": GiByte, "g": GByte, + "ti": TiByte, "t": TByte, "pi": PiByte, "p": PByte, "ei": EiByte, "e": EByte, +} + +// IEC Sizes. +// kibis of bits +const ( + Byte = 1 << (iota * 10) + KiByte + MiByte + GiByte + TiByte + PiByte + EiByte +) + +// SI Sizes. +const ( + IByte = 1 + KByte = IByte * 1000 + MByte = KByte * 1000 + GByte = MByte * 1000 + TByte = GByte * 1000 + PByte = TByte * 1000 + EByte = PByte * 1000 +) diff --git a/weed/util/bytes_test.go b/weed/util/bytes_test.go new file mode 100644 index 000000000..4a9c25e52 --- /dev/null +++ b/weed/util/bytes_test.go @@ -0,0 +1,85 @@ +package util + +import "testing" + +func TestParseMinFreeSpace(t *testing.T) { + tests := []struct { + in string + ok bool + value float32 + }{ + {in: "42", ok: true, value: 42}, + {in: "-1", ok: false, value: 0}, + {in: "101", ok: false, value: 0}, + {in: "100B", ok: false, value: 0}, + {in: "100Ki", ok: true, value: 100 * 1024}, + {in: "100GiB", ok: true, value: 100 * 1024 * 1024 * 1024}, + {in: "42M", ok: true, value: 42 * 1000 * 1000}, + } + + for _, p := range tests { + got, err := ParseMinFreeSpace(p.in) + if p.ok != (err == nil) { + t.Errorf("failed to test %v", p.in) + } + if p.ok && err == nil && got != p.value { + t.Errorf("failed to test %v", p.in) + } + } +} + +func TestByteParsing(t *testing.T) { + tests := []struct { + in string + exp uint64 + }{ + {"42", 42}, + {"42MB", 42000000}, + {"42MiB", 44040192}, + {"42mb", 42000000}, + {"42mib", 44040192}, + {"42MIB", 44040192}, + {"42 MB", 42000000}, + {"42 MiB", 44040192}, + {"42 mb", 42000000}, + {"42 mib", 44040192}, + {"42 MIB", 44040192}, + {"42.5MB", 42500000}, + {"42.5MiB", 44564480}, + {"42.5 MB", 42500000}, + {"42.5 MiB", 44564480}, + // No need to say B + {"42M", 42000000}, + {"42Mi", 44040192}, + {"42m", 42000000}, + {"42mi", 44040192}, + {"42MI", 44040192}, + {"42 M", 42000000}, + {"42 Mi", 44040192}, + {"42 m", 42000000}, + {"42 mi", 44040192}, + {"42 MI", 44040192}, + {"42.5M", 42500000}, + {"42.5Mi", 44564480}, + {"42.5 M", 42500000}, + {"42.5 Mi", 44564480}, + // Bug #42 + {"1,005.03 MB", 1005030000}, + // Large testing, breaks when too much larger than + // this. + {"12.5 EB", uint64(12.5 * float64(EByte))}, + {"12.5 E", uint64(12.5 * float64(EByte))}, + {"12.5 EiB", uint64(12.5 * float64(EiByte))}, + } + + for _, p := range tests { + got, err := ParseBytes(p.in) + if err != nil { + t.Errorf("Couldn't parse %v: %v", p.in, err) + } + if got != p.exp { + t.Errorf("Expected %v for %v, got %v", + p.exp, p.in, got) + } + } +} From cf552417a7a422d1313c53972fd1175684e758e0 Mon Sep 17 00:00:00 2001 From: bingoohuang Date: Tue, 27 Apr 2021 10:37:24 +0800 Subject: [PATCH 088/128] minFreeSpace refactored --- .../repeated_vacuum/repeated_vacuum.go | 2 +- weed/command/server.go | 9 +- weed/command/volume.go | 26 ++---- weed/server/volume_server.go | 2 +- weed/storage/disk_location.go | 14 ++- weed/storage/store.go | 4 +- weed/util/bytes.go | 22 ++--- weed/util/bytes_test.go | 26 ------ weed/util/minfreespace.go | 90 +++++++++++++++++++ weed/util/minfreespace_test.go | 29 ++++++ 10 files changed, 147 insertions(+), 77 deletions(-) create mode 100644 weed/util/minfreespace.go create mode 100644 weed/util/minfreespace_test.go diff --git a/unmaintained/repeated_vacuum/repeated_vacuum.go b/unmaintained/repeated_vacuum/repeated_vacuum.go index bff5becc1..d85e45af0 100644 --- a/unmaintained/repeated_vacuum/repeated_vacuum.go +++ b/unmaintained/repeated_vacuum/repeated_vacuum.go @@ -52,7 +52,7 @@ func main() { } func genFile(grpcDialOption grpc.DialOption, i int) (*operation.AssignResult, string) { - assignResult, err := operation.Assign(*master, grpcDialOption, &operation.VolumeAssignRequest{ + assignResult, err := operation.Assign(func() string { return *master }, grpcDialOption, &operation.VolumeAssignRequest{ Count: 1, Replication: *replication, }) diff --git a/weed/command/server.go b/weed/command/server.go index 6eb3bf97c..d0020d33b 100644 --- a/weed/command/server.go +++ b/weed/command/server.go @@ -58,7 +58,8 @@ var ( serverDisableHttp = cmdServer.Flag.Bool("disableHttp", false, "disable http requests, only gRPC operations are allowed.") volumeDataFolders = cmdServer.Flag.String("dir", os.TempDir(), "directories to store data files. dir[,dir]...") volumeMaxDataVolumeCounts = cmdServer.Flag.String("volume.max", "8", "maximum numbers of volumes, count[,count]... If set to zero, the limit will be auto configured.") - volumeMinFreeSpacePercent = cmdServer.Flag.String("volume.minFreeSpacePercent", "1", "minimum free disk space (default to 1%). Low disk space will mark all volumes as ReadOnly.") + volumeMinFreeSpacePercent = cmdServer.Flag.String("volume.minFreeSpacePercent", "1", "minimum free disk space (default to 1%). Low disk space will mark all volumes as ReadOnly (deprecated, use minFreeSpace instead).") + volumeMinFreeSpace = cmdServer.Flag.String("volume.minFreeSpace", "", "min free disk space (value<=100 as percentage like 1, other as human readable bytes, like 10GiB). Low disk space will mark all volumes as ReadOnly.") serverMetricsHttpPort = cmdServer.Flag.Int("metricsPort", 0, "Prometheus metrics listen port") // pulseSeconds = cmdServer.Flag.Int("pulseSeconds", 5, "number of seconds between heartbeats") @@ -244,8 +245,8 @@ func runServer(cmd *Command, args []string) bool { // start volume server if *isStartingVolumeServer { - go serverOptions.v.startVolumeServer(*volumeDataFolders, *volumeMaxDataVolumeCounts, *serverWhiteListOption, *volumeMinFreeSpacePercent) - + minFreeSpaces := util.MustParseMinFreeSpace(*minFreeSpace, *minFreeSpacePercent) + go serverOptions.v.startVolumeServer(*volumeDataFolders, *volumeMaxDataVolumeCounts, *serverWhiteListOption, minFreeSpaces) } if *isStartingMasterServer { @@ -253,6 +254,4 @@ func runServer(cmd *Command, args []string) bool { } select {} - - return true } diff --git a/weed/command/volume.go b/weed/command/volume.go index 93a19102d..139a3791e 100644 --- a/weed/command/volume.go +++ b/weed/command/volume.go @@ -57,7 +57,6 @@ type VolumeServerOptions struct { compactionMBPerSecond *int fileSizeLimitMB *int concurrentUploadLimitMB *int - minFreeSpaces []float32 pprof *bool preStopSeconds *int metricsHttpPort *int @@ -121,13 +120,13 @@ func runVolume(cmd *Command, args []string) bool { go stats_collect.StartMetricsServer(*v.metricsHttpPort) - v.startVolumeServer(*volumeFolders, *maxVolumeCounts, *volumeWhiteListOption, - util.EmptyTo(*minFreeSpace, *minFreeSpacePercent)) + minFreeSpaces := util.MustParseMinFreeSpace(*minFreeSpace, *minFreeSpacePercent) + v.startVolumeServer(*volumeFolders, *maxVolumeCounts, *volumeWhiteListOption, minFreeSpaces) return true } -func (v VolumeServerOptions) startVolumeServer(volumeFolders, maxVolumeCounts, volumeWhiteListOption, minFreeSpace string) { +func (v VolumeServerOptions) startVolumeServer(volumeFolders, maxVolumeCounts, volumeWhiteListOption string, minFreeSpaces []util.MinFreeSpace) { // Set multiple folders and each folder's max volume count limit' v.folders = strings.Split(volumeFolders, ",") @@ -155,22 +154,13 @@ func (v VolumeServerOptions) startVolumeServer(volumeFolders, maxVolumeCounts, v glog.Fatalf("%d directories by -dir, but only %d max is set by -max", len(v.folders), len(v.folderMaxLimits)) } - // set minFreeSpacePercent - minFreeSpaceStrings := strings.Split(minFreeSpace, ",") - for _, freeString := range minFreeSpaceStrings { - if vv, e := util.ParseMinFreeSpace(freeString); e == nil { - v.minFreeSpaces = append(v.minFreeSpaces, vv) - } else { - glog.Fatalf("The value specified in -minFreeSpace not a valid value %s", freeString) - } - } - if len(v.minFreeSpaces) == 1 && len(v.folders) > 1 { + if len(minFreeSpaces) == 1 && len(v.folders) > 1 { for i := 0; i < len(v.folders)-1; i++ { - v.minFreeSpaces = append(v.minFreeSpaces, v.minFreeSpaces[0]) + minFreeSpaces = append(minFreeSpaces, minFreeSpaces[0]) } } - if len(v.folders) != len(v.minFreeSpaces) { - glog.Fatalf("%d directories by -dir, but only %d minFreeSpacePercent is set by -minFreeSpacePercent", len(v.folders), len(v.minFreeSpaces)) + if len(v.folders) != len(minFreeSpaces) { + glog.Fatalf("%d directories by -dir, but only %d minFreeSpacePercent is set by -minFreeSpacePercent", len(v.folders), len(minFreeSpaces)) } // set disk types @@ -233,7 +223,7 @@ func (v VolumeServerOptions) startVolumeServer(volumeFolders, maxVolumeCounts, v volumeServer := weed_server.NewVolumeServer(volumeMux, publicVolumeMux, *v.ip, *v.port, *v.publicUrl, - v.folders, v.folderMaxLimits, v.minFreeSpaces, diskTypes, + v.folders, v.folderMaxLimits, minFreeSpaces, diskTypes, *v.idxFolder, volumeNeedleMapKind, strings.Split(masters, ","), 5, *v.dataCenter, *v.rack, diff --git a/weed/server/volume_server.go b/weed/server/volume_server.go index f29f12148..f7359ea6b 100644 --- a/weed/server/volume_server.go +++ b/weed/server/volume_server.go @@ -43,7 +43,7 @@ type VolumeServer struct { func NewVolumeServer(adminMux, publicMux *http.ServeMux, ip string, port int, publicUrl string, - folders []string, maxCounts []int, minFreeSpaces []float32, diskTypes []types.DiskType, + folders []string, maxCounts []int, minFreeSpaces []util.MinFreeSpace, diskTypes []types.DiskType, idxFolder string, needleMapKind storage.NeedleMapKind, masterNodes []string, pulseSeconds int, diff --git a/weed/storage/disk_location.go b/weed/storage/disk_location.go index 3246718c8..33dd272ce 100644 --- a/weed/storage/disk_location.go +++ b/weed/storage/disk_location.go @@ -23,10 +23,9 @@ type DiskLocation struct { DiskType types.DiskType MaxVolumeCount int OriginalMaxVolumeCount int - // MinFreeSpace limits the minimum free space (<=100 as percentage, > 100 as bytes) - MinFreeSpace float32 - volumes map[needle.VolumeId]*Volume - volumesLock sync.RWMutex + MinFreeSpace util.MinFreeSpace + volumes map[needle.VolumeId]*Volume + volumesLock sync.RWMutex // erasure coding ecVolumes map[needle.VolumeId]*erasure_coding.EcVolume @@ -35,7 +34,7 @@ type DiskLocation struct { isDiskSpaceLow bool } -func NewDiskLocation(dir string, maxVolumeCount int, minFreeSpace float32, idxDir string, diskType types.DiskType) *DiskLocation { +func NewDiskLocation(dir string, maxVolumeCount int, minFreeSpace util.MinFreeSpace, idxDir string, diskType types.DiskType) *DiskLocation { dir = util.ResolvePath(dir) if idxDir == "" { idxDir = dir @@ -363,7 +362,7 @@ func (l *DiskLocation) CheckDiskSpace() { stats.VolumeServerResourceGauge.WithLabelValues(l.Directory, "used").Set(float64(s.Used)) stats.VolumeServerResourceGauge.WithLabelValues(l.Directory, "free").Set(float64(s.Free)) - isLow := l.MinFreeSpace < 100 && s.PercentFree < l.MinFreeSpace || s.Free < uint64(l.MinFreeSpace) + isLow, desc := l.MinFreeSpace.IsLow(s.Free, s.PercentFree) if isLow != l.isDiskSpaceLow { l.isDiskSpaceLow = !l.isDiskSpaceLow } @@ -373,8 +372,7 @@ func (l *DiskLocation) CheckDiskSpace() { logLevel = glog.Level(0) } - glog.V(logLevel).Infof("dir %s freePercent %.2f%% < min %.2f%%, isLowDiskSpace: %v", - dir, s.PercentFree, l.MinFreeSpace, l.isDiskSpaceLow) + glog.V(logLevel).Infof("dir %s %s", dir, desc) } time.Sleep(time.Minute) } diff --git a/weed/storage/store.go b/weed/storage/store.go index 18496890c..f27f2412f 100644 --- a/weed/storage/store.go +++ b/weed/storage/store.go @@ -2,6 +2,7 @@ package storage import ( "fmt" + "github.com/chrislusf/seaweedfs/weed/util" "path/filepath" "strings" "sync/atomic" @@ -52,7 +53,8 @@ func (s *Store) String() (str string) { return } -func NewStore(grpcDialOption grpc.DialOption, port int, ip, publicUrl string, dirnames []string, maxVolumeCounts []int, minFreeSpaces []float32, idxFolder string, needleMapKind NeedleMapKind, diskTypes []DiskType) (s *Store) { +func NewStore(grpcDialOption grpc.DialOption, port int, ip, publicUrl string, dirnames []string, maxVolumeCounts []int, + minFreeSpaces []util.MinFreeSpace, idxFolder string, needleMapKind NeedleMapKind, diskTypes []DiskType) (s *Store) { s = &Store{grpcDialOption: grpcDialOption, Port: port, Ip: ip, PublicUrl: publicUrl, NeedleMapKind: needleMapKind} s.Locations = make([]*DiskLocation, 0) for i := 0; i < len(dirnames); i++ { diff --git a/weed/util/bytes.go b/weed/util/bytes.go index 260e5067e..26da91033 100644 --- a/weed/util/bytes.go +++ b/weed/util/bytes.go @@ -5,7 +5,6 @@ import ( "crypto/md5" "crypto/rand" "encoding/base64" - "errors" "fmt" "io" "math" @@ -176,23 +175,12 @@ func EmptyTo(s, to string) string { return s } -var ErrMinFreeSpaceBadValue = errors.New("minFreeSpace is invalid") - -// ParseMinFreeSpace parses min free space expression s as percentage like 1,10 or human readable size like 10G -func ParseMinFreeSpace(s string) (float32, error) { - if value, e := strconv.ParseFloat(s, 32); e == nil { - if value < 0 || value > 100 { - return 0, ErrMinFreeSpaceBadValue - } - return float32(value), nil - } else if directSize, e2 := ParseBytes(s); e2 == nil { - if directSize <= 100 { - return 0, ErrMinFreeSpaceBadValue - } - return float32(directSize), nil +// IfElse works like b ? this : that. +func IfElse(b bool, this, that string) string { + if b { + return this } - - return 0, ErrMinFreeSpaceBadValue + return that } // ParseBytes parses a string representation of bytes into the number diff --git a/weed/util/bytes_test.go b/weed/util/bytes_test.go index 4a9c25e52..d9269cadb 100644 --- a/weed/util/bytes_test.go +++ b/weed/util/bytes_test.go @@ -2,32 +2,6 @@ package util import "testing" -func TestParseMinFreeSpace(t *testing.T) { - tests := []struct { - in string - ok bool - value float32 - }{ - {in: "42", ok: true, value: 42}, - {in: "-1", ok: false, value: 0}, - {in: "101", ok: false, value: 0}, - {in: "100B", ok: false, value: 0}, - {in: "100Ki", ok: true, value: 100 * 1024}, - {in: "100GiB", ok: true, value: 100 * 1024 * 1024 * 1024}, - {in: "42M", ok: true, value: 42 * 1000 * 1000}, - } - - for _, p := range tests { - got, err := ParseMinFreeSpace(p.in) - if p.ok != (err == nil) { - t.Errorf("failed to test %v", p.in) - } - if p.ok && err == nil && got != p.value { - t.Errorf("failed to test %v", p.in) - } - } -} - func TestByteParsing(t *testing.T) { tests := []struct { in string diff --git a/weed/util/minfreespace.go b/weed/util/minfreespace.go new file mode 100644 index 000000000..c802bf6dd --- /dev/null +++ b/weed/util/minfreespace.go @@ -0,0 +1,90 @@ +package util + +import ( + "errors" + "fmt" + "github.com/chrislusf/seaweedfs/weed/glog" + "strconv" + "strings" +) + +// MinFreeSpaceType is the type of MinFreeSpace. +type MinFreeSpaceType int + +const ( + // AsPercent set the MinFreeSpaceType to a percentage value from 0 to 100. + AsPercent MinFreeSpaceType = iota + // AsBytes set the MinFreeSpaceType to a absolute value bytes. + AsBytes +) + +// MinFreeSpace is type that defines the limit for the minimum free space. +type MinFreeSpace struct { + Type MinFreeSpaceType + Bytes uint64 + Percent float32 + Raw string +} + +// IsLow tells whether the free space is low or not. +func (s MinFreeSpace) IsLow(freeBytes uint64, freePercent float32) (yes bool, desc string) { + switch s.Type { + case AsPercent: + yes = freePercent < s.Percent + op := IfElse(yes, "<", ">=") + return yes, fmt.Sprintf("disk free %.2f%% %s required %.2f%%", freePercent, op, s.Percent) + case AsBytes: + yes = freeBytes < s.Bytes + op := IfElse(yes, "<", ">=") + return yes, fmt.Sprintf("disk free %s %s required %s", + BytesToHumanReadable(freeBytes), op, BytesToHumanReadable(s.Bytes)) + } + + return false, "" +} + +// String returns a string representation of MinFreeSpace. +func (s MinFreeSpace) String() string { + switch s.Type { + case AsPercent: + return fmt.Sprintf("%.2f%%", s.Percent) + default: + return s.Raw + } +} + +// MustParseMinFreeSpace parses comma-separated argument for min free space setting. +// minFreeSpace has the high priority than minFreeSpacePercent if it is set. +func MustParseMinFreeSpace(minFreeSpace string, minFreeSpacePercent string) (spaces []MinFreeSpace) { + ss := strings.Split(EmptyTo(minFreeSpace, minFreeSpacePercent), ",") + for _, freeString := range ss { + if vv, e := ParseMinFreeSpace(freeString); e == nil { + spaces = append(spaces, *vv) + } else { + glog.Fatalf("The value specified in -minFreeSpace not a valid value %s", freeString) + } + } + + return spaces +} + +var ErrMinFreeSpaceBadValue = errors.New("minFreeSpace is invalid") + +// ParseMinFreeSpace parses min free space expression s as percentage like 1,10 or human readable size like 10G +func ParseMinFreeSpace(s string) (*MinFreeSpace, error) { + if percent, e := strconv.ParseFloat(s, 32); e == nil { + if percent < 0 || percent > 100 { + return nil, ErrMinFreeSpaceBadValue + } + return &MinFreeSpace{Type: AsPercent, Percent: float32(percent), Raw: s}, nil + } + + if directSize, e := ParseBytes(s); e == nil { + if directSize <= 100 { + return nil, ErrMinFreeSpaceBadValue + } + return &MinFreeSpace{Type: AsBytes, Bytes: directSize, Raw: s}, nil + } + + return nil, ErrMinFreeSpaceBadValue +} diff --git a/weed/util/minfreespace_test.go b/weed/util/minfreespace_test.go new file mode 100644 index 000000000..eec1942dd --- /dev/null +++ b/weed/util/minfreespace_test.go @@ -0,0 +1,29 @@ +package util + +import "testing" + +func TestParseMinFreeSpace(t *testing.T) { + tests := []struct { + in string + ok bool + value *MinFreeSpace + }{ + {in: "42", ok: true, value: &MinFreeSpace{Type: AsPercent, Percent: 42, Raw: "42"}}, + {in: "-1", ok: false, value: nil}, + {in: "101", ok: false, value: nil}, + {in: "100B", ok: false, value: nil}, + {in: "100Ki", ok: true, value: &MinFreeSpace{Type: AsBytes, Bytes: 100 * 1024, Raw: "100Ki"}}, + {in: "100GiB", ok: true, value: &MinFreeSpace{Type: AsBytes, Bytes: 100 * 1024 * 1024 * 1024, Raw: "100GiB"}}, + {in: "42M", ok: true, value: &MinFreeSpace{Type: AsBytes, Bytes: 42 * 1000 * 1000, Raw: "42M"}}, + } + + for _, p := range tests { + got, err := ParseMinFreeSpace(p.in) + if p.ok != (err == nil) { + t.Errorf("failed to test %v", p.in) + } + if p.ok && err == nil && *got != *p.value { + t.Errorf("failed to test %v", p.in) + } + } +} From 7a9d27fce859bc3c7d95a6a98154eab1eb55aa4f Mon Sep 17 00:00:00 2001 From: bingoohuang Date: Tue, 27 Apr 2021 17:22:24 +0800 Subject: [PATCH 089/128] promote to go:embed instead of github.com/rakyll/statik --- go.mod | 4 +--- weed/server/common.go | 16 +++++++--------- weed/statik/statik.go | 13 ------------- weed/weed.go | 13 ++++++++++--- 4 files changed, 18 insertions(+), 28 deletions(-) delete mode 100644 weed/statik/statik.go diff --git a/go.mod b/go.mod index 70bc33070..e576549da 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/chrislusf/seaweedfs -go 1.12 +go 1.16 require ( cloud.google.com/go v0.58.0 // indirect @@ -60,7 +60,6 @@ require ( github.com/peterh/liner v1.1.0 github.com/pierrec/lz4 v2.2.7+incompatible // indirect github.com/prometheus/client_golang v1.3.0 - github.com/rakyll/statik v0.1.7 github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 // indirect github.com/seaweedfs/fuse v1.1.4 github.com/seaweedfs/goexif v1.0.2 @@ -76,7 +75,6 @@ require ( github.com/tidwall/match v1.0.1 github.com/tsuna/gohbase v0.0.0-20201125011725-348991136365 github.com/valyala/bytebufferpool v1.0.0 - github.com/valyala/fasthttp v1.20.0 github.com/viant/assertly v0.5.4 // indirect github.com/viant/ptrie v0.3.0 github.com/viant/toolbox v0.33.2 // indirect diff --git a/weed/server/common.go b/weed/server/common.go index 5c5f1b8eb..571944c10 100644 --- a/weed/server/common.go +++ b/weed/server/common.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "io/fs" "mime/multipart" "net/http" "path/filepath" @@ -21,19 +22,14 @@ import ( "github.com/chrislusf/seaweedfs/weed/util" "github.com/gorilla/mux" - statik "github.com/rakyll/statik/fs" - - _ "github.com/chrislusf/seaweedfs/weed/statik" ) var serverStats *stats.ServerStats var startTime = time.Now() -var statikFS http.FileSystem func init() { serverStats = stats.NewServerStats() go serverStats.Start() - statikFS, _ = statik.New() } func writeJson(w http.ResponseWriter, r *http.Request, httpStatus int, obj interface{}) (err error) { @@ -212,14 +208,16 @@ func statsMemoryHandler(w http.ResponseWriter, r *http.Request) { writeJsonQuiet(w, r, http.StatusOK, m) } +var StaticFS fs.FS + func handleStaticResources(defaultMux *http.ServeMux) { - defaultMux.Handle("/favicon.ico", http.FileServer(statikFS)) - defaultMux.Handle("/seaweedfsstatic/", http.StripPrefix("/seaweedfsstatic", http.FileServer(statikFS))) + defaultMux.Handle("/favicon.ico", http.FileServer(http.FS(StaticFS))) + defaultMux.Handle("/seaweedfsstatic/", http.StripPrefix("/seaweedfsstatic", http.FileServer(http.FS(StaticFS)))) } func handleStaticResources2(r *mux.Router) { - r.Handle("/favicon.ico", http.FileServer(statikFS)) - r.PathPrefix("/seaweedfsstatic/").Handler(http.StripPrefix("/seaweedfsstatic", http.FileServer(statikFS))) + r.Handle("/favicon.ico", http.FileServer(http.FS(StaticFS))) + r.PathPrefix("/seaweedfsstatic/").Handler(http.StripPrefix("/seaweedfsstatic", http.FileServer(http.FS(StaticFS)))) } func adjustHeaderContentDisposition(w http.ResponseWriter, r *http.Request, filename string) { diff --git a/weed/statik/statik.go b/weed/statik/statik.go deleted file mode 100644 index e3be3b214..000000000 --- a/weed/statik/statik.go +++ /dev/null @@ -1,13 +0,0 @@ -// Code generated by statik. DO NOT EDIT. - -// Package statik contains static assets. -package statik - -import ( - "github.com/rakyll/statik/fs" -) - -func init() { - data := "PK\x03\x04\x14\x00\x08\x00\x08\x00;/TL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00%\x00 \x00bootstrap/3.3.1/css/bootstrap.min.cssUT\x05\x00\x01\xe3\xb8\x8bZ\xec\xbd\xeb\x93\xe3\xb8\xb1'\xfa\xfd\xfe\x15rOtL\xb7[d\x93z\x96\xa4p]\x9f\xeb\xdd\xd8\xe3\x88\xf5\xf9\xb2\xfe\xb0\x11\xe3\xb97 \x12\x92\xe8\xa1H\x1e\x92\xaa\xaa\x1e\xad\xf6o\xbfA<\x13@\x82\xa4\xd4\xe5\xc7F\xf8t\x1cO \xf8!\x91\xc8L \x93x~\xfd\xedo\xfe\xaf\xc9o'\xffOY\xb6M[\x93j\xf22\x0f\xe7a<\xf9tj\xdbj\xfb\xf5\xeb\x91\xb6{\x99\x17&\xe5\xf9s\x87\xfeCY}\xab\xb3\xe3\xa9\x9d\xcc\xa28\x0efQ\xbc\x98\xfc\xf95k[ZO'\x7f,\x92\xb0\x03\xfd\xf7,\xa1EC\xd3\xc9\xa5Hi=\xf9\xd3\x1f\xff\xcc\x896\x1d\xd5\xac=]\xf6\x1d\xbd\xaf\xed\xeb\xbe\xf9\xaa\xaa\xf8\xba\xcf\xcb\xfd\xd73iZZ\x7f\xfd\xef\x7f\xfc\xc3\x7f\xfd\x8f\xff\xf1_\xbb*\xbf~\xfd\xedo&EY\x9fI\x9e\xfdJ\xc3\xa4i:F\xa3p6\xf9_\x8c\xb2\xa8l\xf2\xbf&\xc7\xac\x0d\xb3\xf2\xab\xc2N~\xfb\xf5\xd4\x9e\xf3\xeb\xa1,\xda\xe0@\xceY\xfem\xdb\x90\xa2 \x1aZg\x87]\xf0J\xf7\xbfdm\xd0\xd2\xb76h\xb2_i@\xd2\xbf^\x9av\x1bG\xd1\xc7]pn\xf0\x9c\xdb\xbeL\xbf]\xcf\xa4>f\xc56\xba\x91\xba\xcd\x92\x9cNI\x93\xa5t\x9a\xd2\x96dy3=d\xc7\x84TmV\x16\xdd\x9f\x97\x9aN\x0fe\xd9\xc9\xe8DI\xda\xfd\xe7X\x97\x97jz&Y1=\xd3\xe22-\xc8\xcb\xb4\xa1 +\xd1\\\xcegR\x7f\xbb\xa6YS\xe5\xe4\xdbv\x9f\x97\xc9/7rI\xb3r\x9a\x90\xe2\x854\xd3\xaa.\x8f5m\x9a\xe9K\x96\xd2R!\xb3\"\xcf\n\x1a\xb0\x02\xbb\x17\xda\xb1F\xf2\x80\xe4\xd9\xb1\xd8\xeeIC\xbb\\Nh[\x94\xed\xa7\x9f\x92\xb2h\xeb2o~\xfe\xacH\x14eAw'\xda\xa9x\x1b\xdd~:eiJ\x8b\x9f\xa7-=W9i\xa9\x81\xbb\x91\xeb\x9e$\xbftm)\xd2 )\xf3\xb2\xde\xb65)\x9a\x8a\xd4\xb4hodK\x926{\xa1S\xb2=\x95/\xb4\xbe\x96\x97\xb6c\xa1\x13\xdb~_\xff\xd4fmN\x7f\xbe\xee\xcb:\xa5u\xb0/\xdb\xb6\xa7\xe9\xcf\xb0Z\x95x\x15\x85Rz \x97\\\xb6m\xbbe*;\x94\xc9\xa5 \xb2\xa2\xa05\xe7\xc4M\xbfV$M;\xe5E;eO\x0cz\x85\x86\xcaG\xca\x1bhMr\xa2\xc9/\xfb\xf2\xcdl4I\xb3R\xb7\x10\x98\x86\xea\xb9\xae1\x81,|\xfe\x00\xfd\xb1\x17\xcd\xb2\x05\x9c\x11\xfe\x7f\x7f\xf7\xe1\x87\x0f\x02?UI\x7f%/\xa4I\xea\xacj\xb7\x1f\x1cb\x1f:\x172e\x01\xca\x7f^\xcaV\x9a\x000\xb5\x1f6\x9b\xcd\xae\"G\x1a\xeckJ~ \xb2\xa2\x8b\xac\xb6\xe4\xa5\xcc\xd2[\xdb\xc5O*\x06aF\x14\xf0\x90*`vvk\xebi\xe7D}\xe5\xbb\xbc3y\x0b^\xb3\xb4=\xb1p\x0e\xc8\xb4\x9a\x9ef\xd3\xd3\xfcZ\xd6\xd5\x89\x14\xcdv\xbe{\xcd\xd2\xf2\xb5\xd9\xceo<\x03Pe\xcd\x12D\x85g1\xc3\x89\x03 \x1c\x16\xe4eOj3v\n\xf7m\xf1\x1c&\xa4\xa6\xed4L\xeb\xb2\xbaT\xcf M\xf6\x8d\xb6\xac\x02\xcc\xf2naN\xf64G\xe4\x17E\xd1-4\xfa\x97\xd3\x9d \x19\x86\x9c\xb4\xe9T\xfeur\x83:\xbb=\\\xf0\x9c:M'\xedi\xea$\xa5\x08gi\x9a\x02*\xb7\xdf\x8bH!\xa1F\xcc\xf0\xe3\x7f\xcb\xbfU\xa7,)\x8bf\xf2\xef$?\xe4Yql~\xdc5u\xb2\xbd\xd4\xf9\xa70\xfc\xda\xa1\x9b\xafG\x05\x0bN\x12\x16\xd4\xf4x\xc9I\x1d\xd2\xb2\xfd|\x7f\x91\xff\xfb\x87\x8c\x1e\xb2\xb7\xcf\x93.6 \xed\xa7\x1f\xe9yO\xd3\x94\xa6AY\xd1\xa2\x1b\x86\x7f\xfc<\x1dO\xf1\xb5<\x1c4\xad\xee\xd7]\xc5\xdb\x16\x94n\xeb\x0b\xbd\x9b\x81\xe6\xe5\xf8\x83\x06\xfc\x7f\n \xf25\xf5\xe6\xe5\xf8\xe3\xe7[\xa8\xb0H\xbc\xdb\xc5\xadq\xf5\xb6C\xbf5F\xe8\x0f\xc4\xeb<\xee\xd8A\x9f\xb0\x88\"#\x86\x8e\xd5\xe8\xc8\xcb\x9d\xcb\xb2=uC?)\xda\x8c\xe4\x19ih\xca\xdds\xd9\xbc\xd9\x98cM\xbe5 \xc9)hQ\xc0F\xfd\xac\xf9E\x8c\xeazH\xfa\xcb\x8c|\x80\xc0*\xbf4\x08ho\x80\xe8\xa5.\xa5\x7f0\x93\x91\xa2\x11I\x8c\xc2\xe7\xac@\xab\x98\xc53\x03\x97\xe4\xe5%Ep\xab(6\x99)^h^V\x14\x81\xae\xa3\x8d\xd98Z$Y\x8e\x02\x0f\x06\xf0\x98\x93\x06\xe1\x91FV\xdd\xe7K\x93%(\xcel\x0b\x8fVP\xe0\xdc\x00\x9e(\xa9[\x14\xb74 \xb6\x04\x915\x8d\xa2\x95\x03\x0b\xe8\xb9j\xbf\xa1\xe0\xb5\x01\xbe4\x14\xa7\xf9d\xc0\x0eY~Fa\xa6\xac\xdbS\x90\x93\xfa\x88\xa8\x85FqdAQP\xec\xd0\xcb\x1aT6\x96\xe1\x94\x88\x95\xd3(6\x05]\xd3s\xf9\x823\xb70\x80\xbf\x96\xe59\xc8\n\x14\xb9t\x91\xe5\x05g\xd1\xd4Ky8\xa0(S!Mv,\x08b\xae4\x8aM\x95$\xe5\x11EY\x1a\xa9I\x83Jzf\xaa\xe3T\x9eQ\xc1\xccb\xdb\x0ep\x98\xa9\x8d6\xf3P\xb3\xf4Q\x12\xa4\xb3\xd3hfj#-_\x8b\xbc$i@rT\xce\xb3%\nG\xa1\xa6J.\x95\x17hj%+\xf6\xe5\x1b\x8a3\x95\xd2\xf9\x89 \xc9\xea\xc4#\xa6\x8de\x8f\x15%h\x93\xe6\x91\x05<\xd4\x14\xd7\xe3\xdcTP\xd7]|r\x9a\x9bJ\xea\xdc\x18\n3\x95t\xc8 jh\xf3\x85=\x88\xa5\xd5\xa9,(:\x84\xceM\x15\xbd\x94\xf9\xe5L}=b\xbe\xc2\xc0\x9dZQ\xf4\x1aC_*\x14kj\xeb?\xeb\xa4LQE\xcdME\xed\x89\x17\xb9\xb0\x865\\X\x8b\xd8F\xa1bZ\x98\x1a\xda\x97\xf8\xb0\xb6\x98;\xb03\xa9q\xa8\xa9%\xf6\xa1\x87\xe2L\x05%\xe4Lk\x82\x02M\xe5\xb0\xd9)\x0c\xb6\xb6X\xcc\xd1n\xb60\x15\xc2\xa75Q\xa05\xacu\x1f\x80\"pB\xd0\xcb\xc8E\xf3\x0f \x0cl\xea\x86M`\x069=\xe0\x94g\x088\xa1E\x8b\xbb\xd1\xe5\x1c\x81\xd7^\xb6\x17\x08\xfa\xaf\x97\xa6\xcd\x0e\xa8/_.\x9d\xbe\x8f\xc2V\xd6X\x96\xd2\xa2\xf5\xb7\xd0\x1e\xf9\x18\xda\xcf\xb3\x15(\x90\x84v\xa3\x7f\xc0\xa6\xe9\xd1\x02Vx\x96%\xed\xa5F\xbb\xd6\xca\xd4\xe2\x99TAg\xe6\xb8\xa4W\x96b\xf8\xf2\x05\x064U\xd2z:\xc4\xca\xd4\x05M3\x1cf\x85h'\xe2i\x8b\xa9\x036\xeb\x88\xe2L\xe9\xfb\xe2\x95\x95)\xf5\xa6\xa5U\xd0}\xc3\xbe\x92\x1a\xedg\xab\x8d\xa5\xa5\xa6\xed\xc5\xaf#k\xfc\xeb\x81\x9a\xdd\x87\xaf\xdf 0S?\x15\xb94h\xcb\xd6s\xabe%:\x92\xaf\x17\xd60T{\xf9[\xbaM\xef\x83\xdb\xc14\xadz\xe1\xa6\xbe\xe8_i\x82\xda\xc9\xfa\xc9\xd6\xffK]\xfa\x87\x99\xf5\x06\x85{{\xe1Sd)\xe1\xd2\xb0H\x12\xc5Z\x9f3\xdd\xa7\x99\x1fl\xaa\x8dG\xd0~\xb4\xa9\xbc\xf2\x17?\xd2\xd4\xdf\x7f^h\xd3}|\xfb\xf1KkT:\x94~\xac\xa5\xc2\xa4\xa6\xb4hN%.\xb95\xd6@\x7f\x08\xf7\xf4d7\xb1\x07kG\x11E\x0fxc\xaa\x90\xd4u\xf9\xea\xb5\x8fM\x8c\x80\xbd\xd6\xb1\x99!hS\xd4\x01\xc7\xd6T\xc0\x99\x1c\x0b\x8a\x03g\xe8X\x89\xdawl\xcd\x08H\xb0\xc7\xc2ck^\xa0\xa6\xed+\xf5pa\x07\x02eUuJH\xf0\xb9\x9d8\xb6\xe3\xe8\x9c\xcd[\xfbTl\xcd\x12\x08\xb8\xcfx\xac\xa9\x02\xd1}\xe4\x1a=Z\xc2\xfe2e%Ne\x9d\xfdZ\x16-^\xc6\x9eBH1\x0f\x19[3\x08\xfbK\x9e\x9f\xca\x1ae\xdb\x9aE\xd8S\xb4\xb7\xc7\xd6,B\xd25\xeb\x90%\xa4E%gM&\xb4\xa7\xcby\xdfx\xac\xc3\x9aI\x10X\x9fqX\x93 'R\xa4\xde18\xb6&\x14\x18\xd83\xba\xc7\xd6\xa4\x02\xc3z\x18\xde\xb8H\x1f\xbb\xd6\x9c\x02\xf7D\x03\xae#\xb6\xa6\x17\x8cB>\xf6\xady\x06\xa3\x0c\xde\x0ck\xca\xc1(\xe1m\x8e\xa9\xd7c^\xeeQ\xfd[S\x0f\xaf5-\xd0Y\xd9\xd8\x9avhI\xf3\x0b\xf6\x91\x1e[\x13\x0e\x87,\xc7?\xfebk\xb6a_g\xf4\x90\x10\xbc\x7f[\x13\x0e\x9d_\xe4q\x0b\x06\xb6\xe6\x1cR\xd2\x9c\xf6%\x1e\xa0\xc6\xd6\xccCE*Z'y\x86\xaa\xc1\x9a~`\xf3\xd2\xde\x99\xe4\xd8\x9a\x85\xc8\xb3\x02\xfb\xa2\x89\xed\x19\x88S\x89{\x1bk\x06\xa2\xba4\xa7\n\x9d\x82\x8d\xad)\x88K\x837\xdc\x94\xfeq\x8f7\xd9\x94{S\xe2\xa3\xb55\xa1\xd0\xc1\x82\xfd\xb7\x80\xe4\xd5\x89\xecq\x87`M+\xd8E~)\xaa\xb4\xa5\x1d\xc8U\xa4\xc0\x81\xf6d8_\xe6\xf5\x8e\x16\xd6\xac\x83\xc2\xe3\xe3\x915\xf3\x90\x97G|5 ^Y\xd3\xe39>\xdb\x1e\xaf\xec\xa9\xd7\xa3g\xd1 \xb6\xa6'\n\xfa\x1a\xbcfEZ\xbe\xa2`;\xa8\xd5\xbf.{\xb6\x11 \xe7\xc0\x9a d{X\x9a\x96\xd6\x1e\xd2\xb6\xbf\xbb\xb0\x881\xdf\xa3\xba\xdd\xd8n\xafC/\x83\x18\xc5\xda\xfe\xae\xc3\xae\xa8\xc0wFG\x95\xdeJ\xda\x92*8e\xc7S\xce\x14\xcdwd\xd5\xc7=\xf9\x14M\xd9\xbf\xcf\xfcH\x02\xdc\x83\xf3\xe1\xdfi\xfeB\xbb\xef\xdb\xc9\x7f\xd0\x0b\xfd0U\xbf\xa7\xffVg$\x9f\x82s\x10\xa0\xd6E\xf5f\xee\xc2 \x17\xb3\xa7\xe5:^\xcc\xc5^\xeb\x1f\xe6\xf3\xf9\x0e\xdd\x1e\xc6\xf7\xb6N\x8d\xcd\xd6z\xff6\xe4M\xee\xde\xd6\xf5\xca\x14X\xb5\xdc\xd5M\xae\xaa\xe65\xd9\xafw\xf6fG~\x0e\x81\x9f/\x98\x92-\xdb\x03-\x8b\xcc\xe6\xcb\xd9:q\x8a\x80\xfd\x91\x02/\xcf%\xb4\xa7\xac\x10\x87\x0fv2mY\xbdM\xc8\xa5-'zCRri\x82\x9aMgt\xf5HdP\x1e\x0e\x0dm\xb7\xc1\xacz\xb3v\xe7Gl\xe3\xa1u*\xe0\x9c\xa5iNoav>\x065m\xaa\xb2h\xb2\x17:\x0d\xd9wvA\xb2\xfc9;\x1f\xc1\xcf \xe1 \xa9\xcbKCs\xbe7\xfa9\xccZz\xee\xc9a\xa5\xcc\xb3$;s\x0b\xe4\x0en\x92\xe6\xect\xca\xa5r\x0b_P\x934\xbb4\xdbU\xf5\xc6\xb3\x15K\xf8\xc1\x13?u\xb5\xe9\xbb\xc7\xd0P\xebrw$\xff\x90\xa6\xe9\xce\xe4o\x01\xfbL\xe7\xcd\xf9\x0e6\x92\xe7\x93p\xd6L(ih\x90\x15]\xa8\xb9\x0b\xca!D\x7f6\x97\x03\xff2\xb7\xa4\xb4\x8c>\xdeN\xb5\xd0|\xd0\x96\xd5v\xd6uf\xf1[\x9c\xf7`Ir\xf3\xf4No\xf5\x84\x0d\xa4\x94\xde\xc2\xa6\x0e\xca\"\xff\xa6\xf7\xe3\x91}S\xe6\x97\x96\xee\x84\x84\xab7)\xe0\xeeO\xbd3[X^\xd0\xa5ZGGv\xec\x8b\xb7\xa6I\xabF\x10\xbd\x91[\xd6\xc8\xcd\x9c\xecs*\x0f\xf1 9\xbc\xf7(\xde\xba\xc0 K\x04gL\xdfP\xf7\xea\x0c\x87}B\x83\xf3\xc3\xac\xef\x14\xf3=\xb8\xd3\xd3bzZNO\xabix\x8a\xa7\xe1i6\x0dO\xf3ixZL\xc3\xd3r\x1a\x9eV\xfe\x11El(\\\xda\x1b\n\xc3\xd8:/r\x8a'l\n}z\x9a\xc9?\xe6\xf2\x8f\x85\xfcc)\xffX\x89?BU,T\xe5BU0T%CU4TeO\xf1$TU\x86\xaa\xcePU\x1a\xaaZCUm\xa8\xeb\x0du\xc5\xa1\xae9\xd4U\x87\xba\xeePW\x1e\x82\x83M\xde\xed\x96\xa2\xaf\xad\xd7\xeb\x1b\x938SD\xc8\x95\x11\x9e\xe6\x03\xf6\x1c\xb3\xa3G\xb1## \"G\xc8Zj\xb0i\x88\x88BLZ\xba\xe5\xc0m\xae\x96\x1fo\xccF\x98\xf5\x84\xd2\x82V\x90\xfb\xd8\xc7\xfd\xc2\xd1!P!b\x07\xab\x89\xad\xb7\x10Sa\x88ks\xe5r\xbf\xee\xb8g\xb2\x07\x89\xf3n\xd4\xe5\xaa\x80\xa9\x8cc\xae\x19p\xc8m\xc1\xda\xd1\xf1\x01c\x89\xa7.\x95\x89\xe3j\xfa\xfa\x9b\x90\x0eH\xed|W\xa5\xdc\xd6$\x9a0\xd9\x849%\xe9\x15\x19\xc2@\xc9\x95\xfc)Ll\xeet\xc0\xc5M\x1c\xb0\xf8t\xce\n\xe1!\xd6\xab\xa7\xea\xed\xf3\x95W\x00Z\x12Wo\xb7\x9b\x90\x95-\xa7\xa7\xe5Gv\x96o\x1a\xb2\x13}\xeaP\xcb\x8c\x9e1\xef\x91\x1c\x9e\xe8\xfc\x16\xb2( \xa7\x07q\x04\x8c\xfb\xe0\xee\xb7\xc8ba4\xccc \"\x93\xefi\x81\xb9\x18gE\xc6\x8e\xbc4g\xe0\x037\xd1\xc7\x9d\xd7\x03\x80\xe3u\xd29vf9\x01\x81\x1b\xf3\xed\xb6\x0b_\x87Km\xffR\xe3\xd0\xfa5\xe1I\xb5\xcdI\xd3\x06\xc9)\xcbSp\x9eor\xc9=\x19%\xccpz\x02\x00\x8a\x9b\x12@\n\x8f\x03@\x82\x08 \xcc\x8fZ\x10 D\x1f\x07\xa61:\xc1:U\xca\x19\x1e\xbbf$=\x84\x19j\xee\xe9\xc7\xbf\xb0\xfb0\xfe\x12E\xff\x16\xfdx\x0b5>\xa8\xe9\x0b\xad\x1bH\"\xac.y.\x82\x0e\xb3\xdb\xc5N\xbf\x8b\\\xa3\x95\xdf\x9c\xb2\xa3\x02-\x19\n\x8c06\xbc\xed\x05LY\x18\x8c\x8aG8\x90\x88\x01\xc1h\x84#\x88x\x84\x8dJX\xb2\xcd\xcf\xa4\xf6\xb6\x8cC\xfc\x0d\xeb#\x01\x11=\xcd\xea#\x01!\xc0\x82:\xdb\x990;\xfa\xf1F\xd2\xb4\xee\x9c\xbe7\xf0\x86\xa7\xe9<\xe3m\xff\xfd\x0c\x7f\xa2E^N\xffT\x16$)\xa7\x7f(\x8b\xa6\xccI3\xfd\xf0\x87\xf2Rg\xb4\x9e\xfc\x07}\xfd\xa0on`\xb4\xd4\x882\xab\xde&\x0bc\xfc\xe8\xc6$\x19h\xacg\xcb\x05\xc5\xa2\xf1\xcdavX\xb8\x137\xb7_\xf6\xe98\xd2\xbe\xb0jn\x11\x9d\x83\xd9 p\x8e:+\x1a\xdaN\xa2I\x103\x8f\x0f\xe6Q\xc3\xd9\xf2\xf3n4\xb2cx\x02\x99\x86\xb7\x8d\xb0y/\xcb\xcd\xf9\x8e{\xdb\x87\xbc\xd9\x1d\x1b\xe6\xc8&\xab\xd8\xb0\xf1\xd9\xfa8\x83\xd5\xceG\xcd\xdf\xbe\x96u\xca\x8f1o\xc5a\xe6<\xe7\x89\x9d\x97\x13i\xddoL\x7f\xcb\xee\x1f2\x1d\x97$ \xa2\xd5\xaa\xa6\x13\xc3j\"d\xda\xd7\x98\x961\xfcnUS\xc6\x93\xcb\x088QoU\x1b\xdd\xc2\xaeX\x93\xd4e\x9e\xb3S\xd0g\xf2&\x052_\xc0\xe8 \xf8\xb6\xe5\xb0[\xd8u@\x92\x81\x9b3\xbc\x83q\xacu 0`v\x8bC\xd8T\x96?\x88\xd1u\x89\xf4%\x0b\x1c\xdc\x02\x9b\xcd\x0c-\xb0Y{\n\xc4\xb3(BK\xc41/\xa23\x82C~\xc9\xd2wkmX\x97\xafW\x03\x17\xc0\xa2<.\xedR:\x16\xf2\xe0\xad \xe2)\xfb\xab9\xcb\xbf\xce\xa9\xfc+?\xca\xbf\xde\x9a`\xa6p3\x85\x9b)\xdcL\xe1\xe6\n7W\xb8\xb9\xc2\xcd\x15n\xa1p\x0b\x85[(\xdcB\xe1\x96\n\xb7T\xb8\xa5\xc2-\x15n\xa5p+\x85[)\xdcJ\xe1\xd6\n\xb7V\xb8\xb5\xc2\xad\x15\xeeI\xe1\x9e\x14\xeeI\xe1\x9e\x14n\xa3p\x1b\x85\xdb(\xdcF\xe1\xe2H\x01\xe5\x9f\x9d\xa4#\x05\x95\x7fvX\xa0\x14\xa0\x15\xa0\x16\xad\x97X+&\xd6\x9a\x89\xb5j\xe2\x19r\x8a\xbc\xb3Uw\xbe\xba\xd7\xfcl\x8b\xd16\xa1\xb5\xae\xf5\xaa5\xa7u\xa3\xa5\xaf\xe5\xab%\x08d\x04D\xc0Z\x08>/n U/n\xe8\xd4X\xf6\xcd8\\\xf1\xff[\x83\xdcH\xe4>\xcd\xc3\xb9\xf8?\x9d\xbbQ\xe3\x80N{\x12i\xab\x15Bn-2\x97O\x08\xb5\x95\xcc\x04\xdc-E\xda\x02cn!2\xe7\x18os\x919\x03\xbc)\x01`\xbcI9`\xac\xb1\xe8'\x9e]\x85\xb6\xa1\xfcxV,\xb2P!rH$ \xa8$\x19d#\x10P\x9c,\xe3Id\xa02e\x88\xb5@\xa0\x82e\x88\x95D\xd8\xbc/E\x06*b\x86X\x08\x04*g\x86\x98\x0b\xc4\xcc\xe6\\\x89\xcc\xcb\xb9\x94\x9c\x97q)7>Z\xab\x9c\xe6\xd4)\x84\xf75S\x1f]N\xccs<\xea\xe8\x10\x11Gx\xb4\xd1\x9c\x82\x0d\x07\x98\xcahN\xc1\x13O\xf7\xe8\xa29\x05k\x0e\xf0\xa8\xa29\x05+\x01\xb0\xb9^\xf2t\x8f\"\x9aS\xb0\xe0\x00\x8f\x1e\x9aS0\xe7\x80\x99\xcd\xb3\x14\x94\x97g!//\xcbBZ\x86\x0e\xf8\xaaq\xa7\x05c2\x01*CBb\x03\x82jEB#\x03\x8a\xaaG@7\x06\x12\xeaI\x00\x9e\x0c\x00\xaa0\x81\\\x1bHTs\x02\xb92\x91n[\x97\x06\x00\xd5\xa5@.\x0c$\xaaT\x81\x9c\x1b\xc8\x99\xdbRK\x05=-55\xd1\xd3\xd0h\xf4\xd4Vb\x04C:\xdc\xd1\x01\x8d\x0eYtP\xa2\xc3\x0e\x1dX\xe8\xd0A\x07\x07\xc0\xfb\x03\xe7\xce|\xb7\xe3\xe4x\xaa\xed\xe4X1\xaf\x93c\xf4\xbdN\xae\xe3\xc3vr\x1d\x97^'\xd75\xc6\xeb\xe4\xba6\xdbN\xae\x93\x88\xd7\xc9u\x82\xf3:\xb9N\xbe\xb6\x93\xeb\xa4\xefur]S}N\xae9{\x9d\x9c\xca\xf2;9\x05\xf1;9 q\x9c\x9c\xcc\xf0;9\x89\xf0;9\x89p\x9c\x9c\xcc\xf0;9\x89\xf0;9\x89p\x9c\x9c\xcc\xf0;9%\x17\x9f\x93\x93\x00\xd7\xc9\xb1\x1c\xd4\xc9\xa9\x1c\xaf\x93S\x08\xaf\x93\x93\x08\xdb\xc9\xc9t\xaf\x93\x93\x00\xaf\x93\x93\x00\xdb\xc9\xc9t\xaf\x93\x93\x00\xaf\x93\x93\x00\xdb\xc9\xc9t\xaf\x93S\xe2\xf089\x99\xef8\xb9\xe6<\xe8\xe4\x00d\xc8\xc9\x01\xe8\x90\x93\xd3P\x8f\x93\xd3\x80!'\xa7\x91CNN#=NN\x03\x86\x9c\x9cF\x0e99\x8d\xf489\x0d\x18rr@\xbe\xfdNN\x03m'\xd7;\x95\x01?\xf4\xf5\xa7\xbc\xfeX\xd7\x9f\xe3\xfa\x83[\x7fR\xeb\x8ff\xfdY\xac?|\xc1\x87-\xf8ne\x9f\xa5\x8e\x97\xe3\xa9\xb6\x97c\xc5\xbc^\x8e\xd1\xf7z\xb9\x8e\x0f\xdb\xcbu\\z\xbd\\\xd7\x18\xaf\x97\xeb\xdal{\xb9N\"^/\xd7 \xce\xeb\xe5:\xf9\xda^\xae\x93\xbe\xd7\xcbuM\xf5y\xb9s\xea\xf5r*\xcb\xef\xe5\x14\xc4\xef\xe5$\xc4\xf1r2\xc3\xef\xe5$\xc2\xef\xe5$\xc2\xf1r2\xc3\xef\xe5$\xc2\xef\xe5$\xc2\xf1r2\xc3\xef\xe5\x94\\|^N\x02\\/\xc7rP/\xa7r\xbc^N!\xbc^N\"l/'\xd3\xbd^N\x02\xbc^N\x02l/'\xd3\xbd^N\x02\xbc^N\x02l/'\xd3\xbd^N\x89\xc3\xe3\xe5d\xbe\xe3\xe5\xce\xe9\xa0\x97\x03\x90!/\x07\xa0C^NC=^N\x03\x86\xbc\x9cF\x0ey9\x8d\xf4x9\x0d\x18\xf2r\x1a9\xe4\xe54\xd2\xe3\xe54`\xc8\xcb\x01\xf9\xf6{9\x0d\x1c\xe1\xe5\xc0\xfc;\x9c\xc5\xd6\xf3\xd4z&Z\xcf5\xeb\xd9d=_\xacg\x84\xf5\x9c\xaf\x9e\xd5\x05\x93\xb6`N\x96O\xb9\xdan\x8e\xa7\xdan\x8e\x15\xf3\xba9F\xdf\xeb\xe6:>l7\xd7q\xe9us]c\xbcn\xaek\xb3\xed\xe6:\x89x\xdd\\'8\xaf\x9b\xeb\xe4k\xbb\xb9N\xfa^7\xd75\xd5\xe7\xe6\xf2\xa3\xd7\xcd\xa9,\xbf\x9bS\x10\xbf\x9b\x93\x10\xc7\xcd\xc9\x0c\xbf\x9b\x93\x08\xbf\x9b\x93\x08\xc7\xcd\xc9\x0c\xbf\x9b\x93\x08\xbf\x9b\x93\x08\xc7\xcd\xc9\x0c\xbf\x9bSr\xf1\xb99 p\xdd\x1c\xcbA\xdd\x9c\xca\xf1\xba9\x85\xf0\xba9\x89\xb0\xdd\x9cL\xf7\xba9 \xf0\xba9 \xb0\xdd\x9cL\xf7\xba9 \xf0\xba9 \xb0\xdd\x9cL\xf7\xba9%\x0e\x8f\x9b\x93\xf9\x8e\x9b\xcb\x8f\x83n\x0e@\x86\xdc\x1c\x80\x0e\xb99\x0d\xf5\xb89\x0d\x18rs\x1a9\xe4\xe64\xd2\xe3\xe64`\xc8\xcdi\xe4\x90\x9b\xd3H\x8f\x9b\xd3\x80!7\x07\xe4\xdb\xef\xe64\xd0qs\xe2*\xf1\xbe\x87^\xc4[7j5\xb9-\xab\xed\x13X\xcb\x13;W\xba$\xbd\x01kg\xef\xc3nO\xc8\xd6lV\xb9v_\xf6\xe1\"\xc1+\xdc~\xc8\xcb<\xb3\xab\xe2\x9f\xdb\xfaY\xddN\xfe\xdc\xee\xcb\xf4\x9b\x95t(\xcb\xd6JR\x05S\xb7`\xea\x16\xd4[@\x9e\xfc\xdb/\xac\xb3_mYyN\xfd\xa4i\x8a\xb4\xc0>;\xc6\xdbk\xed\x1c\x9c\xa1T\x84n\xbeHj\xdbCV\xcbmx\xa0\xd5I\x99\xb3\x1b\xf4\x87p,\xdb\xcc\xf3\x92\xec\xad9\x1dYs:\xbe\xe6\x14\\\x9a\xbf\x8dnPy_\xd8\xff\xc2|TZ\x93\xd0c\xed\xec\x88\xa3\xb8\xe4>)\x8b\x94\xbdo\x85\xd8\x18\xcct\xac\x0df:v\x87\x92M\xfb\xc8b\x99\x88U.U\x9fP\xd7\xf3\xe3w\xf3\xdb(\xacy:\xcfm\x9d\xces\x1b\x87\xd0L{h\"y\xa0e\xef\xc0\xbd\xe6\xc2|~J\x8c-3-\xb3\xa6\xad\xb3\n0\xb7-\xda\x137\xb8Oe\x9a~\xc6Le\xd3\xfd\x93\xe5\xd9\x06u]\xda\xbb\xfd\x9dm\xac\xe2\x83\xed$)\xf3\x9f\x92\x9c4\xcdo\x7f\xd7\x0d\xce?;G\xec\xcc\x170\x922\xbf\x9c\x8b\x1d\x0f\xfd\xd9\x1e2N\xa6M\x0d*S\x91z\xba\x8b6\xcdsH\xd9\x1d$Cy>\x10t7\x9b\xa2\xec\xa7\x845\x8a>u\xff0+\x11gS03\xb1\xb3\x80\x9d\xd8Y\xc0P\xbc\x04\xdd,`*\x1e\x822\x1d3\x16$K\xea\x17\xc9r\x08\xba\xf6\x82d9\x041\xe1\x8a\xa3>^\x8b1\x8e\xff\xf8Mf\x04\xcc\xc4\x0c\x1a\x8d)\xd4\x01Zh\xcb\"\xbaIV\x98\xd9d\xc5\xa1\xc4l\xc6H\x07\x06c\xa4\x03k\xc1\xe9\x9c_\x01\xfd\x94\xb4\xd4\xf0\x1amv6\x13:\x04{J-/\x13\x92\x1bY\xe7\xb2hO?\x1b\x17\xcetC\xd4\xcd\xa6\x1fr\x9d4g\xa7\"4\xc7\xaa\x11\xc5\xf0\xaaU\x96\xc9C\xe4\xe7!?\xfax0sp\x1eL\x8c\xc1C~4xX\xac\xd8\xa9o\xa6b\xb6\xc8zu?Go!sg\xd3Pz/\xe4\xd8\xae}}\xa8\xf6\xdc\x9e\xdb\xf48\xcd \x8bc4e\xfe\x1b~s\xb3 \xca8\xf2\x8b\xdc.\x189OE\x0b{\xaf\xca\x8c\xdf\xc2\xc6ks\xe3\x10\x9e!\xae=\xc2\xf2\x15g\x98\x17\xd7\xd9\x08\x05\x1d\xd7\xb8ws\x02\x01-xh\x03\xf7T\x043-\xa1/\xb6\xf0\xbfh5\x00*\x81\xd6\x93`\xc5\xe1\xad\xffF\xa7\xf1\xa2Eo\xa9\xc5\x05.j\xfe\xd2\xcf\xd8\x17\x87S\xd00\xf3\xe6\xaa\xd8\xea2\\K`\x80\xc4\xe4\x8fg\xf3\xa2\xa1\xccCK\xea\\d\x80u\x8d\xa5\x1f\xa4\xed\xc1\x1d\x8cMy\xe9jm\xc9\xf42dJ\x19\x03\xd8r\xf62\xa2\xea\xb1;\xa8\x9d\xe1e\xa3'\xdf\xea\xec\x18\x13\xd0\xe5\x04|\xd9\xf9j\x07\xb1\xd6\xde\xa1\xb5k\xba(\x1d=D\xf6\xe46g\xeb\xe2\x8b\xc8\xbap\xe9\xa6G{0x\x06\xcdy\x82\xba\xcb9\x18\xc5X`o_\xcc2s\"\xe2\xa5{]\x8d\xf8D\xd1U\x8b\xdfc\x19p\x9c\x8fr\xf1\x8a\xa2\xe9\xf4q\x9aS\xfbK\xc9f\x08\xe4\x8c`M\xdc*mj\x85\x17\xc9\x8fx\x91\xcek\xed\x8c\x9b\xcb\xf4\xed\xa2\xeaJSK\x9e\xce\xfd?+[\xa0\xf9\x11\x11h/\x0b\x8e'\xb5\x04\x9a\x1fQ\x81\xda4}\x02U\x0c\xe1\x02\xf5\xb1\xc6\x05z\"Mp\xa04\xed\xc2<\xd7a\x9b\xf9\x16\x1d\xd3\xf4\x17\xb3\x909\x17\xa3\xb7\xb8\x94\x95o\xe3\xa3\xb6\xec6\xbf\xb2'q\xdf\xb6\xb3\x1d\xf6\xd5\xc8\xbe\x14\xe1W\xa3\x1d\xa3\xed\x9c\x1bUw\xc2\xc5\x04\xf4\x85\x16m#6\x9dH\x81}\xf1\xb0)6\x8b\xaftu\xb8\x02\x95)\xf7\x13b\xdd\xa9\xb7k1\xf9\x8a\x85\xedIx\xa2y\xc5\x1d\xee\xd4\xcc\x90\xf4\xc50k\xe4 \xc7o\xe2\xc5\xd8\x89A\x95\x7fEK`\xb9f\x10\x063\xcc1\x1a)$\x03\x9e\x9e\xb2\x06\xc4\xba\xe6\xd5\xe4\xd1\xb0>\xf3c\x92\xe3\x1f\xf9\x84\xbc\xe7+\xb1\x87\x1f\xf4\x13\x97\xdfD\xfb\x08W\xec{tU\xbdM~X\xad\xf7\xf1\xea\xe9\x1e.\xed\xb2\x16\xd7\xdct\xf9\xd8@\xd2\xb4,L\x99#\x1f||\xbf\xc8\x0e\x93x\x8fDtg@J\x88\x05Y\xd7\xe4U\x06b\xf2*\x0f\x98\xbc\xc6\x1b&oB\x0d\xa3vJ`\xb9\xae\xc9\xcb\x0c\xcc\xe4\x8dB\x88\xc9\xdbeQ\x93\x177\x0c\x9b<\xf6\x98<\xc7?b\\\xf7\x18S\x0f?\x9eY\x9de\xfc\xbd&\x9fD$^\xed\xef\xe1\xd2.kq\xed5y!C\xdf>\x87\x1d&\xf1\x1e\x898&\x0fK\xd0\xba.k\xd7\xe0E2b\xee\"\x07\x18\xbb\xc4\x1a\xa6\x0ea\x86)[h7\xcf5r\x9e\x8c\x998(\x80\x18\xb8Y\x0e5oq\x056\xe4\xac\xc7\xb89\xfa\x113\xba\xc7l\xbc\xdc\xa0\xa6\xcd\xaf\xe8~\x84'`\x9e\xf4i\xf14\xbf\x87G\xbb\xac\xc1\xb3\xd7\xb0\x85\xfc|\xbb2v\x98\xb4\xbd\xd2p\xcc\x1a\xe2Up\xc8\xb4\xfd\xbf=\x05\xd9N\xf9\xa5\x0cx\xcc2\xf2A\x90\xbe\xb2\xd1\x0dt\x1c\xe7\x15\x1a5\x0d\xb0Dg\x91\xd4a\x91y\xf7\xaf\xe7\xee\x1cV\xbf0^\x10B\xfb\x16\xd0\xcc \x10\xcf\xb3<.Mi\xf1(U\xce\x10\xbb\x1c\xf1^\x82\xf2\x0b\x18\xa3k\x15\x03vc\xc3\xd9r\xe9\xa8\xba\x01\x11\xc4\x12\xa7#\xc1\xfb\xb6\x17jHL\x0b\xc7\xcf\xca3V\x84\x1f*3\x8a\x18\x03\xae\xbd\xbbeT\xfb\xc5\xc0lR\x95\x93n}&\x83\xddx~G\x95r\xe0E+\x16\x83\xae=\x0d\x81PAf1q\x8a\xfd\x93\x95\xfaJF\xe3\xb8\x97I\xeb\xe4\xfd\x8a\xb4\xbb\xb9(\x08\xaeG7\xe4\x0c\xd3\xb5\x1b\xc4\x8b(\x87\xe7-)'\xb8\xec9\xa3>==\xc0\xa01Q\xbdV\x1f\xc9\x10\n\x06\x1bQ\x1f\xff2\xf6\xdd~:0\x86\x19\\\x18\x86\xeei\xaa\xb2B\xfb\x86pL!c\xb4)\x8e\xed\xde\xc9\xab\x16\x03\x9b\xb6\xf0\xb3\x1e/\xc29~w\xed\x18\xda\xcd\xb9\x8f6_\xeb\xd8\xb7E\xefT\xb8^\xb1Fg\xc2\xf5\n\xb6=1\x9e\xe3+\xda\xee\x0c\x86\xbb\x07\xcd3\xa9\x1e\x9c\x9b\xa0-/\xc9) \xeb\x93gRd\xd5%g\xcf\xd4\xed\xfc9\xe6d\xbc\nl.\x0d\xad\x03>\xa3\xc4W\xcd\xd9z'\x92\xda\xb8\x89N\xc2\xc8ux\xff=\xcc\x8b\xae\xbf\xec\xdbB\xec\x92`\x7f\x8a\xf3#:%tS\x1cx\xe8\xc0\xc3\xbf\xd5\xee\nV\xab\xd8\xb2j\xb2\x1e\x1a\x0f\x0b\xce\xe7s\xfc!B\xc06d\xf8\x8aKs\xd4\x12\xfa\xbcz\x9b,\xad\xf82\xf6\\U\xee\xc32\xbe\xc0\xb2\xc3\xbe-\xc0\xaa 6\x89\xdf\xf5\"d&n\x87,4\x8b'\xdf\xd9S\xde\x9f\xc4\xfa\xf2\xefV`\xb3\xc4\xbe\xff\xc2s\xb5&\x1d\xae\x96\x8c\xd1 \xa5\x07r\xc9[(m\xcff\x11#\x1af\x1b|Ay\xa0I\x95\xa45*\x93B7 jP\xc1dZY\xd1\xe29L\xeb\xb2J\xcb\xd7\xce\xd7\x1c\x8f9\x1d\xcf6]u\xff\xec8>\xed\xfe\xdd\xde\x91\x07\xd4\xe0\x8c\nLs\x90\xa9\xc3f!\x91S\x94\x18\"s]^d\x0e\x10FHh\xf2\xae\xb2\x00y\x9e9D\x1e1\x01\x89DlAS G\x91GHh\xee\x11\x9d\x02\xf6E\xee\x10\xff\x98a\xa8\x06\xf4\xd6\x10\x8e\xab\x01\x19\xb4F\xf7\xb9I\xb8'\xe9\x91\x0e\xbd\x815\xe7\x85\xeex1\xcb\xaawFW)Y\x18T\xa0\xdd\xc8$\xa0\x0c\x91\x14\xbaI\x86D%l\xb8\xab\x8db\x9e?\xe1e3\x1f-\xd2\xb5\xc5\xfc\xf7\xf1\xd0\xd3\xdd%1\xb3\xbb\x8b\xd4\x11\xdd] \xa7(1D\xe6\xa3\xbb\xbb_mhww\xc9\xf7\xf7\xc7\x1e\x13\xc0\xba\xbbC~\xa0\xbb\xfb\xcd \xef\xee.\xfb\x03\x9d\xb1\xcf0\xd0\xee\xee\xb6`\\\x0d\xfe\xee>\xb6\xe7Y\x9d^\x16\xc3\xefP\xe8\xca\x89u\x87\xfe\xae\xb3L\xf6O\xcb\xc4\xaa}\x91\x10\xbaH\x0c*\xd0\x80d\x12\xd0\x8a\\\xb6r\x93\x0c\xd1J\xd8p\x9f\x1b\xc5\xfcb\xb1I\x17\x0b{\xe9e\xf3\xb4\x98on\xef\xc8CO\xbf\x97\xc4\xcc~/RG\xf4{u\xa6\x17#\x86\xc8|t\xbf\xf7\xab\x0d\xed\xf7.\xf9\xfe\x8e\xd9c\x02X\xbfw\xc8\x0f\xf4{\xbf9\xe1\xfd\xdee\x7f\xa0W\xf6\x19\x06\xda\xef\xdd\x16\x8c\xab\xc1\xdf\xef\xc7\xf6<\xab\xdf\xcbb\xfe~\x0f\x1f\xf5\xf4t\xfa}\x129\xd3\xdc\x8b\xd5\xfe)%\x9a\x044\x1d\xf6\x1b(\xa3\xfb\x1dZ\xbf\x0dY2\xc0p\x0f\x1bfu\x1e\xef\xa3ti\x8f\x8e\xab\x0d\xd9'\xb7\xef\xaf\xba\xa7g32f\xb7\xee\x92F\xf4i~:\xdb\xa1a\xcbstW\xc6\x94\x81vb\x8bj\x7f\x17CU\x8a\xf5]\x93\xea@\xc7\xc5\x0c\x03\xef\xb2\x16\xb3\x03\xbd \xd72\xdaS-~G\x10\xee\xe9\xa3c:\x8a\xddAE\x19\x7f\x07\x15\xab\xa3\xfd\x86\x7f\x88H\xba\xb0\xab\xa6\x94\xcc\xe6+\x83\n\xb4\x0c\x99\x04\x14 \x17\xd7\xdd$C\x9c\x126\xdcoF1O\x93\xcd:\xb6\xbfb\xd2\xe5\xd32\x9e\xdd\xde\x91\x87\x9e\xee+\x89\x99=X\xa4\x8e\xe8\xc4\xea\xc4n\xd3\x1e\x9ab\xf3\x18\x8fg\x82\xcdf|\xa0\xf7\xf9\xed\x00\xed\xdd\x0e\xef\xa3\xc8\xfb\xfb\xf6\xc8\xeeeumY\xca\xdf\xb5\xf3\xac\xf8\xe5\xea\x9c\x9f\x11!\xac\xf1\x81_\xab\x074e\xb9\xa9\xfa\xcb\x10K\x97\x10\xda #\xac\x99\xb1\xe2\xb0\n\x97\x90F.\x19\xa0\x1c\x02sf\xbf\x81\x99\x80\x16\x98{\x83\xe0%\xa6\x03\x94\xa4\xc4g\xf3\xe5l\x9d8\xcb?\x97\"\xa5u\x9e\x15\xc80\x8bV2\xba\xb3b\x1c\x8d\xee\x8d.\xfb\xea\xe2Ul\xe9\x8am\xd0\xef\xfe{\x14K\xb9\xcf![\x08z\x87\xad\xf8\x8cls\x86\xe4\x9b\xb3I\xfe\xf1\x83\x13\x8c\xe8[\x03\x89\xbf5\x16\xef|i\xecA\xda\xd8\xf6%\xb8CEa\xbe\x00\xb8\xb9\xbf\xc98Ny\xd9\x9f\xb3\xf6g\x8d5N.\xd1\x86\xfa\xf2\xf6\x97\xb6-\x0b\x90i\xee\x93!)\xbd\xca\xd5\xb4\x08;\xac,2\xd9Q\xdfI\xd7vR[\xc7\x8f1D\x7f6\xaf7\xcc\x8a+8C\x9b\x94yN\xaaF\x9fJc\xdd\xf8%k\xb2}\x96w\x18~\xf1\x8d\x06v\xe5M\xf9\x020\xfb3\xa7\xb7\xb6F\xf1\xe2\xee\x88\xf2\xf5\xc6n\x97\xe8\xc7p\xfbP\x15w\xc1\xba\xbb'FXD\xa4\xae\xeaQ\x17\xf582\x0d\xda\xec\x9c\x15\xc7\xe0p)\xf8R=%\x0d5\x85\x8aC\x86\xf2\x91\xaa\xd2\x8b\xe8\xb1\xe1\xdc>7n\xe5\xf9\x0b\xb9T\xab\xba\xach\xdd\xe9\x84\xb5z\xaa\x05oU\xd1\x03\x1c\x85\xba\x85 \xa9i\xdb\xb7\x81.\xd2\xa2\x87\x9bg\xba\xae\x8a\xef\xa1\x10\x1dV\x1e\xb5d\xbb\x12T/\xe6\xfe\xaeo\xb3\x02\xbf\xac\x1bC\xdcT\xc0\x87\x1d\xaf\xb1\x82A1\xc6\xaa\x05}\x008\xd3\xe2\xe29F\xc3.U\xe0\xfb\xae\xd4A\x9a8\x8a\xa2\x9d\xd1i\xf4\x8b\x0e;\xf0\xd2\xc4\xca>r\xa6\xae6\x99\x89\xfb5\xac\xed,\xd6\xd5\xda\xbb\x9d\xf7\xb6\xeb\xcd\xb37\x8c\x80\x8b\x1b@\xaa\xb1a\x81\xedm\xb0\xb6\x92`\x91\x01\xdf^\xdb\x0d\xe3fyk3q\x0f\xccR\x13x\x9e\xff*\x0f8\x81\xeb\xef\x0d\xec$L\xb3\x97,\xa5\xb5<\x96\x15\xeb\xa7\xd97L\x1d\xf6\xd0\x81\xccE\xf0\xcb^L\xc2\xcfy\xf6L<\x0f\xc0\xcf\xab\xb7 ;\xc4\x9b\xe4\x94\xd4\xdb}\xd9\x9e\xc6nSR\x11\xdf\x1c\xd9\x9f\x84\xb1 c\x0e$\xc7\x8c\x87V\xdd?4\xa6\xf0\xbe\x1fo\xd7'\x82\xc9gbW\xa72pnt\xb6\xc1Rg\xb8#\xf9\x11\x11\xb0\xaf\xe3>\xab\xd8\x1f\xe1Mgy\xb8\x03\x00;\x06\xeb\xa9g,1\xb4\x85c.\x820\x86A\xec\xcbUn\xe9\xa9\xea\xf2\x98\xa5\xdb\xff\xf2?\xff\xd8e\xfd\xb9+v(\xebs\xf8\xa7,\xa9\xcb\xa6<\xb4\xe1\xb1\xeb\xa1\xb4h?\xd1\x821\xf7\xbb\x03\xc9\x1b\xfa\xf9f\x7f2\xb3A\xd0\xbc\xba\x87C\x88w\xcc\x1c\xd9\x0f\xd9\x08\x0e^\n\xd9\xc9}\xac\nu\xa2\xa4\xeb\xa6\x03=\xaa7.\xb4{Q\x17D\xf7\xf6\xa2N\xac\xdd\x0f=\xf0\x1f\xb27\x9aZ\x87'\xd5\xceC\xcb\x07l6\xd1\x0d\x8cE\xb6\x1c=\"\xb9T\x13\xee_\xa7aA^\xf6\xa4\x0eX\x9db\x7f\xe3D\x11\x11\xa8kR\x16--\xda\xed\x87\x0f\xd0\x89\xdawS)\xdf\xa8+1\xb8\x19\xac\xcc\xe4\xbd\xab\x82\xa9Im\xf3w\xafH\xec\xddz*jc2\xb0\x89#\x82\xe9\x81\xfb,\xe7\xa6\xbf\"\xe0\x07\x85\x0c?zn\xc70\xe2\x19\xcf\xb6pE\x90}\x9e`\x15\x88\xef\x16\xa7\x16\xf8\xb2\x93I\x05~\x1e\"\xb4\x9cl\x9e\n\xbe\x8f\xb1Bv6O\x85_\xfdX)'\x9f%\x87\x03\xc5\xe4\xac\x88:M\x0cZ\xc8>d\xbfX\xc2\xd2\x89\x8e\xa2&\xfaO\xb4\x14\xc827n\x8b]\xda\xf2\x93\xaf-\xcb|Oj3wi\xe5Nl\x16T:<\xa9akN\x80\x80\x05\xc0\x14\x84\xdc\xb3A\x0er\xa4\x18\x02:*\xca\xf6\x13\xbcz\xf13O\xd1\xf7\xe6\xf1\x04;\x86\xfd|E\xe7\x81\xa0\xc5\x80\xeb\x1c\xadS\x03~\xe4\x9d\x95\xb7e\xc5{\xabb\xc3\x1c\x90\xacL\xa7f]\x91+\x07\xc3,\xed\x08\xdeAC\x8e\xbaf\xfa\x182\xf2l~<\x16`\x03F\xe8\x8c\x0f\x0b\x03*\x12\xd4\xe0\xab(\x96T\xec\xae\x89\x95\xb0D\xf3N\xaa\x115\x81\xeb\x1b\x1d\xa3\xfaN\x89O\x1c\xa5:\xe3\x0e\x8b:\x1c\x1c\x0cBL\xa6\xbf8P\xf3\xf6\x84'\xfb\xfa\xa2'\xb7C\xb2{\x0b\xfa\xc9\xb0\x98\xc3\xa0\x13\xcf\x0cB\x1e\xbe\xff\x1e\xfb\xc6\xfb\x18\xe0\xcd\xcb\x8a_0N\xbc3\xb72\x02A\xc6\x10vz\x84e\n\x95\x8b\xa7\x01\x05\x83\xb6%\x88I\x03\x1d\x9b\xf4\xd0\x88&\x82\nh\x95\xe9\x86\xbc\xfe \x0e\xc9\xdel\xdeA\xcd0S?\x9c\xb2\xf3\xbea\xe5e\x07\x92\x85/\xb0x\xe0\xb6\xb7CrG\xb5\xa3\x9f\x0e\xc0\x88\xb1\x0d^S\x15[\x87\x9e\"/\xb7#F\xbc\x9e\xc1\xce\x8eUz\xdc\x8dw\xf4ZTo\xbd\xe3\xd7\xc8A\xc7b\xa5\xcf\xff\x0c\x8eo\x83#,,\xb5\xe8\xb1\xe4w\xf3*(\xd9>q\xf7\xf8\x9b\x07hy<\xd1\xfb\xe9\xcbqJ\xae\xcc\xeewS\xae\x1aa\xfd\x7f\xbd4mv\xc8hj\xceM\xc3\x01\x82OV\xe7\xe4[yi\xc5\x97\xa3 -\xa7\xb6\xb7\x0d\xadHMZ\x8aRvF33Gt\xdc\xde\x87\x9e$;\x1f\xfd\x15\xf0D6\xe6^\xf1\xe1\x0c\xc7\x9b\x1fm\xfac\xed\xa7\x94\xb4DhZ\xact4?\xb3\x92\xc8\x99W?\x18\x0c\x9bw\x95\xc3\xaf\x00\xbc\xbb\x1e\xe4\xa8\xad\x9a\xf4e\xd3\xa35MZ\xe1d\xa3\xcf}\xd7\x1dq-\xf9?7\xb9\xd9\xf8\x0d\x03P1_\xfc\x02Z\x1ew\x0d\x9aP\x9cq@\xdb\xe5K\xdf\x05\x05\xa6\xac\xbdWU\x9b\xc4\xd9Z#\xa4?ur\x91\x93\xea}\x90}[\xf0\xc1\xedoz\xa9\x98\x87y\x0f\xc6m\xc2\x08 \xda\x90|\xe0j2\x0f_^\x94\xcb\xd9(\xa8\xe4\xcdw\xc5Y\xafxz\xd0>A\x8d,b\x8b\x0c\xdeA\xc7\x11\xcd\xb9\xcf\xdc\xba\xdc\x01s\xb3!v\x95\x7f\x8b+\x01=\xac{0\xa3\x8cmT3\xf2\x81\x8b\x05=|yQ#\x8d\xcd\xc7[\xbf-x\xc4\xd3\x83\xbe\xd3\xd8\x86D\xe6\x1a\x1bb>\xcc?\x83\x04\xcf\x15#\xda5#\x14G\x84wN\xa5\xf7\x96\xb1\xae\xd7\x19.\xed\xc6\x92\xc3\x92\x90\xd1\xc3\xc7\xd1G\xe7\x11\xaa\xd7\xa1\x1b\xe9{\x17\xca\xe0\xc5\xf4\xee\x89~d\xe9\x8e\x8e\xbe\x94\x1e\xe1U\xa4\xe8kH\xbd#\x84;\"x\xa9\xe5\xc7\xe1\x8d:\xae7s\xc9a\x17\x8c\xf4\x82t\xc4\x03\xef\xdf\xe8\x89\x1d\xcc\x17e\\\xbb\xf6f[\x81\xb8\x08s\x87\x10m\x96\x82/c+\x87\xfa\xdd\xb3\x81\x00uu\x92\x14\xd8\x84`\xecepr\xcd\x1a\xfb6B\xe0\x1b5\xee\xdc\xc6\xc08\x98\x88\x1d\x05S\xf8\x030\xa2\x928\x0b}>\xdd\xdc\x1d\"\xa8\x17\xe4%x\xbf=?R\x17\xcf\xd9\xf9x\xd5\x13\xc2\xca8\x82\x96\xec\x1b\xebu\xab\xd8z\xf2O\xc2:C\x82\xbb\xc4\x0c\xdbS&*\xa1\xcf\xc4\xec\x1c=\xdb\x1d\x9c\x08\xa7\xf7\xba\x9f\x89x\xd9\xc9\xaa\x8d\xab\xc0\xba\x93\x91R:\xe1\xffc\xb7\x04\xec\x05\xc2R\x81B\xdd<\xc3\xb8\xbaPN\xd8\x91\xb8\x9c\xa2\xf7\x16\x19K\xba\xd68\x8e\x9b\x1c\xe3\x80\xfd\xa1'\xf5\xc0\x84\x87\xa9\xbd\xc8WD\xab\xcf\xd4\xbe\x83\xd2\x9a\x13$\x97\xd8\x0d\xda^\x02\xc3\x9b2\xf4\\\\\xef\x06\x0cO\x1b\x90\xf9D=\x898\xbeQ\xd1m\x14Zo\xa2q\xe2m_\xfb]\xcb\xf2!l+\xf3\xe2\xc4X\x82Y\xd0#2|&\xbd]~L\x9f\xfb;7\xda\xec \xec\xd8 \xe7\xa5\xca\xf2\xdc\x1a\x99\xcc\x0c\xddV[w\x12\xf1%\xcf\xae\xd6.`\x13`5\xceI\x86-r3\x9d=\x83\xbe\xed\x81\xbc\xd2\xa6%\xc9/xo\xd5Y\x80evu\xaa\xbb\x1eVxF\x8b\x9b\xdb\xa1\xecj\x1e\x1d\x0b\xfe\x16C\xc0}=\x7ft\x87\x07\xa2\xf1\x8e\x9e\x8f\x0f\x08\xfd\xfdbL\x9f\x18\xec\x0f\xf7\x0f\x02\xef=\x00\xfc\x1d\x1a\x89v\xfa\x96\xec\x03\xb1\xa7\x90=\xde\x19T\xa4\x18>\xffa\x94\x12\xdf\x03\x83\xe7@\x14\xa7\xb6\x19\xdbk\xd1C_i\xd8J\x1d\xdf2\x88]\x94\xaao\x03]\xba/)\xb1]\xa4}\x01\xd3\xe0\xa6Fd8\x1c,#w\xb7\x82AV\xb6@- \xd9{M\x96\xce^\x13\xe3Q\xea7)f\xb5\xe7^e\xf1G\xa8\xbb\xaf\x0cv9%\x90\xa2'F\x1c\xb8s;\xe2;Nf\xcb\xe5T\xfe\x7f\x18{o\x02\xc7\xd1Ns\xd9I$\xfd\xc2\xf6\xf0Xf\xc8J_U\x0c[\x87\xde\xd0\x88\xefo\xb1\xb9qNB1\xab\xfeMv\xae\xca\xba%E\xbb\x03\xd3\xc6 U\x1f\xa2\x10_\x90\xea\xd3Bh\x07`\xdd\x1e\xa23\x87\xc4\x03\xbb\x94\xde\xc6\xdb\x96\xd5\xc4.\xa8v\xfa\xf2\xeb\xaa\xfb1\xe6n\xe0~\x83tV\x1d\xefa\xa6\xbf\xa2\xee\xc3J.\\,\"\xb8\xb1\x98\xbc\xc9W \xb9\xc6\x17OQ\xf5\xf6\x99?]X\xd6\x19-Z\xfe9\x9a\x93\"m\x12RQm+\xef\xc9\xd5,\x8a\xd8\xad\xb5\xdd\x10H\xb2\x82\xd6\xcff\xc7\x9e\xea\x9c\xe0\x90_\xb2\xd4\x9f\xff\xecr\xe3+\x0b81fI\xee\xbd\xb3\xf8\x9f\x81\xeb\xc8\n\xb0n\xae\x99^\x8d\xc3U\xa2[\xcbMall\x19\x1e \x005s\x98\xc6\xcc\x15\xb5\x03\xfb,\x80\xb1[\x1d\x1e\x00\x9bG\xc3\xec\x0cT5\xcc\xe2\xd5\xd8\xeco\n\xc3CR\x0cD\xf6\xd4\x96A\"\x06\xd1HGb_\x93\"\x85\x93\x13\xd0\x81\xaa)\xab\xa5\x98\xb2\xea[\x8d\xe7\x8f&B\xb2 l\xd1i\xfe\xa9-\xb30\x9bw1\xe7\xda\x86\x84\xfe\xac-sb\xd0\x9a\xba\x00n\xba&\xec\xeav-\xc5\x93\xdc\x15\xea\xd9\xf5\xcflE\xc9k#\x17\x8b@\xb8\xf3\xa4\x7f\x027o\xea\x8a\xad\x00\xdd\x7f\x1a\xe8\xbeY \xabI\xee\xc9H#{\x12fIY\x04]\xf4\x83\x9d\xae\x9e\xcd\xf4C\\\xeerX\xec\xd4\xa6\xc9}\xd1\x84\x81\x94\x16c\xba\xba\xd0\x05\x8c\\\xb5\xa2\n\xf2\"\x1f2_\x87\x9d\xdd\x06j\xaeUd\x8b\xe9Zx\xe9y\xe4\xbeg\x18\xdb\x0b\xeb3\xdb=I\xde\xd6\x9075\x8fiG\xbe\xcar\xf8@\xe5\xee\xe4b!\x0d\\\xa0\x1b0\x05\xae\xf4\xfb\x03\x1f\x1f\x87L,\xd3!\x14\xf8)\"[c\x85T\xee\x03\x9eY2\xf7\xd7x\xf5\x8e!\xbd\xc5\xac\xb1\xa5\x1f\xebL(\x83K`\x86\xcd\xad\xb3(g\xf6\x16t\x14\xb1R`M\xa3\xf8\x8dm\x89\x18\x9b1\xd6\x1c\xca\xfa|u\x96\x0bz\x07\x93\xc03\x9a8#\xda\xf0'\x81\xef\xf3\xf6\xbb?\x1b\xa6\xef\xf7M\xd1KjX\xa3\x9d\x80\xdf\xed\x1d\x1a\x97\xa6\xbdI\xe4\xcewh\xbc\x04\xfb\xdf\xa11\x8a\x81\x95\xb2\xbb\xde\xa1\xf1\x11A\xdf\xa1\x19\x07f+w~\xa8!1-\x1c?+=\xef\xd0\x18E\x92G\xde\xa11(\xd4\xfc\xf9\x11\x93\xea\xbb\xbfC\xe3V)\xdf\xa1A+\xf6\xbcC\x83Pa23\xb7\x89\xe0\x14\x01\xf0\x8ewh\x0cZc^.\x11\xef\xd0\x0c\xbaP\xa7w:\xf3\xa8X\x1faP\xb8Z\xef\xcca\x8e\x1a\x16\xe0\xfc\x02\x1c\xb5#\xf7\x83\xbf\xef+\xe7~\xf7l;\x0d\xff\xc4Y\xf4\x1d\xb3f\xf8\x87/^\xa5\xa7\x96\x859m\xf7\x8eG6\x047`_\x83\xe5\xeb\xb4c\x83`\xb6\xf4\xdf\x9ca\x19\xef\xab\xf7V\xa9\xb7\xc6(\xb5pK\x19\x013}k\x0d<\xf2*Z\xff\xf78$\xe4.\x08\xbb\xdf\x05j\xf6o\x84\xf5vX@\x14\x99ab\xf4\xaf\xe0\x83\x05LQ\xb9\xd1\x84Y\xec\x7f\x9bDL\xdb\xd7\xc6\x8b\x18$\xe4\x9eM\x81g\x91{E\x8eY\xc16^\xf4\xbe\xc4m\x9e\x95b\x8d\x9f\xa0\xdbw5=\xb7UVn\xd7_\xcd\x84\xa6\"\xfd\xb7)z\x1f\x06W'\xbc\x06\xef\xd4ew\xc8\x8epI\xf5\x88\xd0\xcd\xef\x13\xe1\xbcW\x84s\xb4n\xaf\x08\x9d\xec^\x11\xbaU\xdb\xd9\xacn\xbd\xef w\x92\xee\x15\x15\xce\x01cFe\x82\xfbK\x96!\xda\xc1\xfffL\xf79k\xae\xa6\x05\xb6\x83p\xf8\x10\xa5\xdc\xaf\xb7T\xcd\x9bX\xc3\xbcL\xe1\xfdy\xa4+e'\x93y\xe1\xb0\xa0o\xadn\x11\xff\xc9\x1a\x05\xd6#\x15\xb8\xaa\xe9KV^\x1aP@%\x81B|\xb3\x95\x00XC\x95\x99d\xb6\xc4\x19\xa0\xdc\x0cV\xcb\x03\xc3\xd6-\xe4\xfb3LU)%\x853z\x9e\x84\xab\xee\x7f\xe6\xf4\x0cz\xd4z\xf9\xd1\xb8\x00e\xed\xbb\x00E\xdd\x9doX\xd7\xf0\xbd,{\xd2P\xc6\x8a\xa9\xf2p\xb6\xa4\xe7\x1b\xe1\\\x0b)\xc9_\xce\xf7[\xdfQpqO\x9ah\xff\x96\x9e\xab\xf6\x9b\xb9E3d7\x8b\x8a\xed+N\xbc'\x8f\x04 \x02r\xc6\x15\xf9\xf0g!\xac\x01\xfa\xe9T\xd3\x83\xfc\xd2@\xb3|SF|\x05X\x92C^k\xb7\x1c\xaf\x81\xc3\xaa5\xb3|\xd5\xf2W\xe7%9\xe4\xb1h\xc9\x1e{\xb1\xd7\xc2a\xd5\x9aY\xbej\xf9\xa3\xd7\x92\x9c\xfd\x8c\xad\xac\x93=B\nAX\x85 \xdd;\x1d\xc7\xde\xdf\x95\x84\x90\x877\xa5e\xb1\xd7\x0f-\x1cV\xa7\x99\xe5\xab\x96? \xaa,\xc4y\x00P\x06\x14\xeca6\x13\x86\xda\x11\xcc\xf1\xd5\xc9\xdf0\xbc\x89\xa7\xdf\xf0\xada\xfai\x18\xb8\xb5~^\xbdM\xd6\xee\xb5B\x7f\xa7\xa1\x00\xeb[\xb6O`\xfbfX\xc3\xfc\x9d\x9a\xb7\xbb\xafS\xf3\xdd6\x12i\xec\xa8\x92O\x7f\xdd\x88\xa8F\x8eB\xfc\xd7\x83\xa3P\xe7n\x8fl\x03b\xd6\xd2\xb3\x0c\xcb9M6\xb5 \x0e\x1c\xab`\xfe\xf9\x9eG\xf2-\xf2\xb2\xac\xe1\xcbp\xcc\x17\x015v\xd0,\xed\x83\xd3\x9a\x1d\xf8\xdd\xc8\xa2\x8f\xbf^\xce\xfb\xb2\xad\xc1\x85Zs{\x8b\xb0\x98\xad`w\xc3q\xae\xb3\xe2D\xeb\x0c\xfb\xce`>Z\xd1\x9c\x9c\xe2)\xf8\x15\x9e\xe2\xabA\x00B\xed\x8dy\xd6\x01\x91Yl\xd9\xf1,\x8a@\xf1\xe7S\x0d\x830\xd9+\x97\xdd\xbf\x1b<\xc8\xa1J8\x07\x8f@\x9eu\xa0fe\x08j\xa2\x0b^\xad\xab\x9d\xc5\x04x\x93\xd4\x94\x16\xfcp\x99\xbb\xc1\xca\x95\xf8\xe2\xa9\x8b\xf2\x1e`\xd3\xda\x00o\x14y8x\x84\x8f@\xe9\xd7\xc6\xb6\x1c4 g\xcd\x84\x92\x86\x06Y\x11\x94\x97\xd6z\xd4\xce\x03\x1aD\x80\xd6?g\xe7\xe3T\xff\x9c\xc8\x8b[@/\x83',ru\x8c\x9e\xe8Bj\xe4\xd1)<<\x04)r>\x0d\xff0\xd7\xf5\x87 \xa9:\xce\xaf\xe0P\x10x\xe4\xe9\x16\x92\x9c\xd6\xed\x15\x1e\xb1\xc2\xd4\xe4H~\xe0l\x0f\xa3:9-\xcc\x0d\xa3V\x1f\xe6 \xfe\x1f>Cl\xf9\x1b\x01y\xae\xa6\xe2\x8f\x8b\xb3\x83ZA\xbe\x187\x8c/\x15\x13A\x9a5\xe7\xaca\xf1\xf4\xd4L\xca\xf6\xce\x05\xfbs\xbc\xe0$L\xf2\xb2\xc1\xca\x8b\x1c\x9f\xd3\xe9\x9c\xa8\xd8\xcc\xc8\x06\"L\x02*\xf0\x92jI\xd6\xab9\x16\xd7\xa7\x87C\x94\xda{\x17\xd3\x15\xdd$+\x8b\xd4\x04\x1d\xd5\x92\x0d\x9d\xed\xe76\x14\xca_\xc6\x85\xfb\xe5\xa2\x8b#x\x0e\x0b\xd0T0\xb5\x8e\x9e\xf0W\x9fiz\xb0\xe7\x89\xf6 }:\xc4\x90\x0e\xce\x18Y\xd1\x98\x1a\xf5\xa1\\-\x96\xb3\xd5F\xa2\xacW\xdc\x9f\xc8*\x9d\xef\xb1q#9<\xd1\xb9\xc5\xd8\x81\xd0}\x92X\xa4p\xde\x0ek\x1a\xef\x976\x14ao\xb5Z\xc6Zh\xe6\x93\xd3d\xb3X,f\x18w\xb3\x94:\xcf\xf3w\xbc\xa5\xb1I g\x8e.\xf6\x9b$\xb2\x90\x08oO\x8b\xf9r\xbe\xb8\xfd^\x0e\x8c\xbf\xd0o\x87\x9a\x9ci3\xa9\xea\xf2X\xd3\xa6 \xf6\xec\xf0j\x9dU\xb4\xb9\x1e\xea\xf2\x0c\xc3Ke\xdc\x0b6\xadpkK47\x9aD\xb7\xdb\xef\x83\xf2oJ\xfeoH;\x94\x14\xaf\xe0\x1c\x186\x1a\x96\x83\xd7w\xf9\x96^\x86\x8e,9\xaf\x0fzO$\xb9H\xcd?\xdbY\xe0\\\xae\xad\xde\xccd\x97N\xf5\\1<\xd3\x01\x9b'\xb8\xf7\xce\x13\xf74/\x00G\xa5\x8c'\x1c\xc7\"\x11\xaf\xce\x1a6 W\xdc\x1b[\xde\xdc\xca\xf4\xe6\x00\xb9q+J'\x86$\xa7!bi\xa9{\x8eO\xf2\x97\xb3\xe7n\x03\xf5\xe6\xddb\x99\xd2\xe3\x149\"\xb6\xfc<\x99-?N\x81+u~/\xa3\x8f\x9e\x92\xfe\x9c\xb5E\xc3\xfa\xfdy\xe7r^\xfe\x1f\xc8\xf4?=\xc7\xc8\x9b\xa8\xac\xbf\xb1\x91h\x11\x99S\xa4f\x8e6I\x11\xe7\xf5Y\xa4\x0c\x05e}\xa4\xc8\xce\xfc\x93\x14\x1b!'3\xf9 \xf3$+\x0eY\x91\xb5\xac\xdf\xdc_\xe8\xee\x127\xab\x1f\x0dN7\xf5wK\x8c\xc0\xbf:\xe2?\x80\xe9\x7fv\x8e-\xbb\x1b\x98o\x1c0:\xbb\xf4\xbf,\xee\x1f\xc0\xf4?;\xc7\x96\xc5\x0d\xcf9\x0f\x18\x1dB\xe0_v\xf7\x0f`\xfa\x9f\x9dc\xcb\xee\x06\x17\x1d\x06\xcc\xce-\xff/\xab\xfb\x070\xfd\xcf\xce\xf1-d\xb3\xd7\xf6\x91d\x91l\xbc&\x00g\x03E>\x9fx\x9b\xf2\x1f\xcf\xf0i}cF\x9a\xaf\xff\xf0\x12]\x82Y\x80\x1d:6g\xf2<\x05\x8c\xea\x82}\x99~\xc3n%\xb5V\xaa\xda\xb2\x92\xa4\xf8u W\xcf%\n\x92j\xdb\x96g\x1b\xc3S%\xe6DI\xc7\xac9?jN5,A\x03\xb2\xa6\xb5wz8;9\xf4BO\xff-\xf3\xfcv\x1bkY\xe8\xfe{\xe3-\xaa\xfc\x1eM\xd7\xc3\xf9\xb71Z\x1c\x8cy:o\xe0\xd0\xbfK\xd3\x7f\x11\xc3\xc0^\xc3\x81\xfd\x8f\xc4\x91\x9f\x0c#\x97K7sb'(\xf5\x83\x99x\xa7\x94\xde\x06a\x0b\xaa\xb4\xce \x8e\xdc\x07\xc3'\xa5\xdcEI\xb9\xdbd\xea\xcdQk\xd1\xbe\xfc\xc7\xf6\xd7\xb1E?\x1fM\xaf\xd0\x86\xd8|\xa4 \xe3\x7fHMj\xda~<\xcb\x9df\xee\xe7w\xa0\x94\x87Y\xe7*\x00|\xf1\xd9%\xcc\xd3}:\x16\xb9=\x9bH\xbd\xb3\x81\xf8\xe2\x14^\xc1\x1dZ\x83\xfc\xde_\xac_\xd5\xbeb\xde\x02\xcf\xcd\x99\xe4\xf9\x83L\x0e\x14\xeeg\xb5\xbf\xb0\xbfX\xf8]\x1c\x0f\x94\x1e`\x99\x97\x1e\xeaO\xbe\x16\xe0\xfd\xa2\x9f\xef\xde2#zR\xb2NS\xean\xb5\xb8s\xd1\xce\x1d\xddq\x02~\xdc\xd8\xc1\xc9K\xc0\xe7NT\xbe1\x86\xfb\xdb\x13\xb1\xf5F/\x199\xca\x0c\x01\x86\xd81\xc7\x9d\xfe\xd1F\xf0j\x8e6\\\x9e\x0e\xf9;V3\x91F\xba\xa5=\xa0\xc7\xd5\xd5\x95\xf6\n\x87e\x9a\x8a\xf2\xb5!Y\xd0\xf9\x01 +\x18\x0d\xbf\x96@n/\x17\xf7\xe8G\xb0h\xea\x87K\xcf\xa1}\xdf\xba.\xd2<\x94\x80\x1f\xf7\xb8\xa2\x04\x01\xaf\x94d\xbe!!\x7f{\xc8a\x96$\xfej\xfc\x1a3\x01C\xec\xdc\xa17\xc9\xab\xa17!O\x87\xfc]\x0b\xdeH3\xb1\xf2^\xd8\xe3J\xe3\xe5\xbdB\x12\xd9\x86p\xbcm\xa1\xfb$AU\xc6\xa9\xf85f\xe4\x0f\xf0r\x87\xbe$\xa3\x86\xbe\x84$\xbd\x02\x1b\xfa\x00\xb5\xcb\xc1\xdb\xc7\xd4\xb7\x94\xb9\x8bk~\x0b+R8\xb7\x1d\xce\xa2\xf1_\x88`~a\xdc\":_\x14\x8f\xadE\xf1\xc8\\`\xf6\x82\x04\xc3|>\x00\xee\x85\x92\x19RZ\xeeW\xb0\xf1\x9d\xd8\xdf\x82\x9e\xf3%\xd8\x01\x10\xabn\xf4Q\x16q\xc1\x81i\xf5\xa2\\\x9b\xb59\xed\xd3o\x047\x01\xac\xdc\xddI\x80\x8c:\xabne\x1e\xca\xb2\x05\x97\xef\x02\xb1x\xbe>A\x83\xfb_\xd2\x1a8\x0e\x83\x9c\xc4)h\xfe\x0c\xccu*\x938\xa7\xf2\xea\x04\x08q7\x939T\x9c\x91f\x04Y\xa7\x8c\x9c\xce\x00w\x9f[V\x8dU\x0d'D\x1c\x92\xe6\xe3\xa7\x83,\x8d\xa6\x05f^\xfc\xe7C\xc7\x19\xaeQ\xbf\x9e\x88q\xab\x87o\x83\x0e\xb7d\x1c%\xfbY\xa0\xf7\xb0.\xd9\x13\xbf\xf4\xe9\xda7\x8d%\xf6\xe0\xc0\x01\xf5\x8b\xd9\x890\xb0\x10G\xcbw/\xc2_AM\x9b\xaa,\x1a\xb6\x9d\xdc\xcc\xb7\x85\xc7r\xbd\xb6\xcer'b\x9b\xe8P\x1d\x0e\x0e\xadkbm:\x85WJ\x9a\xb3\xbaK`-\xac(j\xd763\xe6c\x9eN\xb9\x11\xe7\xf9z\xcd\xd6!\xf8\xdcv\x9a7S\xea\xf7\xe0\xf4N\xc2&\xb0sV\xa3\x8b\xde\xc3\xd3\x00\xe1\xbf\xbfx'm\xfa>-\xfb\x9ez\xee\x92\xd1\xbbq\xfc=\xf5\xdc\xd9\xf6\xd3\xfbp\xfc=\xf5\xdc\xd9\xf6w\xe2\xf8\xbezz\xcc\xff;M\x1c\xf1\x83w\xb7\xe5;\xaa\xb9S(\xef\xc3\xefwTsg\xc3O\xef\xc2\xefwTsg\xc3\xdf\x87\xdf\xbb\xaa\xe9;\x8en\xd9\xf6\x18\xde\xe0\xbb\xd8v\xa9\xeb;\xc4c.U\xd1^\x98P\x7f7\xabw\x115`]`7\xb2\xdcxfz\x89\xfeC\xc4:\xda\xd9}\x87\x94\xfb\x1d\xddh\xf9\xbc\x13\xab\x8f\xd7qO\x8bG\xba\xb7\xef\x92j\x9f3\xbe\xa3\xc5\xef\xc2\xea=u\\\xfff\x96\xac\x7f=\xda\x8e\x87\xab\xb8G\x1a\xef\xc1\xe8\xc3U\xdc\xd3\xdcq~\xec\xbb$\xda\xe3s\xefh\xee{0zG\x15\x03C\xb52a\xf6\x1f63\xf9\x85W8\xf5\xe6\x00>\xcd\x06|\x01Xo\xd3 \xe8\xea\x9d\xa63\xbb\xd6\x98\x00czo s\x12\xca\xac0\xe0Y\xd4\xaf!\x1b(\x88y \xf1\x98\xee\xb9\xad\x9fG\x8eb\x0f\x12\xd0\xf0N\x00\x83\xf0\xbe\xfaF\x11\xd0\xf0\xce(\x07\xe1}\xf5\x8d\"\x80\x88c\x9c\xaf}\x90\x00\"\x8eG\xeb\x1bE\x00\x11\xc7\xa3\xf5\xe1\x04\xa4\xd5\xe7\xe2e\xa3!\xe9\x8e\x1a\xaf\x1e+\x8f\x9a\xdaC\xb5\x8d)\x8f\x1a\xdaC\xb5\x8d)\x8f\x9a\xd9C\xb5\x8d)\x8f\x1a\xd9C\xb5\x8d)\x8f\x9a\xd8C\xb5\xa1\xe5\xd5\xf5\x16\x19\x7fog@\xb0\xe6 \x7fO\xed\xa3\x088\xa2y\xbc\xbeQ\x04\x06\xd8;\xddQ\xdf(\x02\x03\xec\xddS\x1fN\xc0^#\xf1\xeaS\x16\x87\x81\xcd#\xe2\xed+\xef\x98\xde\xc3\xb5\x8d)\xdf\xcf\xdb#\xa2\xed+\xdf\xcf\xdb=\xb5\xa1\xe5\x07\xf4\xa8\xe9y\xf6@\xab\x12\xe8\xaa(\xdf.\x0e\xf2'\xfc\x87oC5\xdc\x8e\xed\x96\xfab\x16Vwu\xb8H\xb5\xe6\x8e\xb7\x0e\xc5\xca\x10V\xafG\xf1\xdf \xee\x1dY\x0e,\x12\x8b\xfa\xbd\xa1\xb0I\xd1Y\xcc\xf3p\xccqv\xc5\x13\xc0\xb0\xd5p\xbcnu?\x1d\xc7\xca\xfdk\x0e\xe0\xd9\x12\xaa\xdc\x85\x85=\xd3c-\xd5\x8f\xa5\xd9'|\xe4\xee\x8aAz\x13\xf3&,\xc9\x14\xb2\xd9x\xee!\x84\xcb\x18\xe3K|\x84\xb9\xac\xa9\xbb\xf8\x0cY\xe8\x0bp\x01\xc6#\xe2\x07vH\xf7\x91\x1d\xd3\x1a \xe51$'\xf7\\9\x86\x12zT\xd0&w\xea@\xb7iw\xe2\x82\x1b\x03\xe33\xe7\x87\xae\xce\xe9\xa3<\xa6M\xd0\xa8G\x90\xb4\xc4-Y\xf3lk\xf5\xd0zT\xe2&\x83\xfc4\xb3!\x14yg\x8f\x06\xf8d\xfd\xd0U@^\xb2c\x9a\x02\x04=H\xcf\x96\xb2`\xca\xb39\x15#\xf4\xa8\x88M\xd6\xd4\xd9]C\x1c\xf2\x02\"\x03\xe3\x11\xf4cW\x1b\xf5Q\x1e\xd3&x\x05\xd2\x08\x92\xf6`-X\xf3\xec)\xf5\xd0zT\xe2&\x83\xf2\xd8\xaa!\x16y\xa7\x12\x84x\xe4\xfd\xd8eM=\x84\xc74\x08^\xea4L\xd1\x96\xb6`\xcc\xb3#\x14'\xf5\xa8\xb0%{\xf4\xbc\xa7)\x0c.\x87N\x10\x8a\xfd\xa1\xfa\xc2\xcd\xc8\xbeH\xc9%:qR\xc4&<\x07\x98\xb1[\xa1\x90\x0c\x96\x80\xa4\x97\xfb\xbf\xd2\xa4E2^\xb2\x94\x96\xba5d\xdf\x94\xf9\xa5\xe5\x17\xbauQ\xae\xdc\xf3\xca\xcfV\xea[\x1c\x8d\xeb\x95tdm\xd3w[\x14\xaf\xf6\xdf6W\xeba\xe7\xe5*\x9c-?\x8e)\xbe\xd8\x7f\x9b\xdb\xa5\xd7]\xd1W\x9a\xe7\xd7sV\x18\xf7:\xa9\xad\x9a\x1b\xcfM\x7f\xfd\xd1 \x0cC\xe9\xbc\xfb7n\x7f.\xbc\xbaj`\x97\xee\x00\x94\xb7k\xc2l\xea?/e\x8b\x96\xe5\x83\\o\xe6\x9d\x81\xf3\x9e5\xb8\xb9\x93\xe1\x9a\xb3qu\xa2 c\xf3\xf4\xfc\x02@p\xe7j\xdf\xed\xa3\xde[t\xa3H\\\xd7n\xecU\x8e&,~l\xcb2o3\xac\xbfj_\xba\x8e\xac\xc0\x9e\xc57\x07r\xce\xf2o\xdb\x0f\xffN\xf3\x17\xdaf \x99\xfc\x07\xbd\xd0\x0fS\xf5{\xfaouF\xf2iC\x8a&hh\x9d\x1d\xfa\x1e\x1bX\xd8aR\xb8\xd8\xbddM\xb6\xcf\xf2\xae\x9b\xb2?s\x8a\x87*\xe6 Z\xe4\xed\xfd\x1b\xd8\xfb7\x1a\xdf\x96\x95\xf1\xf4\x902nfB,\xf0\x93P\xe3\xba\x1a\xf1.\x1e\xb4P\x03\xcc\xfb\x8a\x9f\xb4\x01\x86\xf7\xda \x84\x0d6\x82\xac0o\x96\x9fE\xf6\x0b\x0fOC\xf7\xba\x8e\xbcH\xa4\x8b$\x9d\xa8_3B\xea\xba|EL\xc8\xba\x8662\xc3t\xe4\xc0\x1c\xbf\xdd\x86\x0dE\x86b&VUf\xdc\xb7\x8c>\x9aR\x02\xe3\xb4\xe8\xf3\xfc\xa5\x87\x89q\xc0 :\x1cP\x17\xa3aW\xa8\x9eK\xd8\xd9C\x9f\xf8\xfd\x9du2\xfa\x03\xadto\xe0y\xbcN\xb4>\xb66\x13}\xdc\x99\x97\x081\xf3\xf7\xd6d\xd4\xc6\xb74a\xf5a2\x95\xd5\xc9\x90`\xb0\xbeH\xd6(\xb3\xd8&@\xac:. \xac\xc2\xd1&\xe3TfN\x8a \xd5\xa1\x86\x03\xa3\x1e\xa0A\xbc\x91\x0f\xd4\xe9U\xa4k5\x8f\xd7Y\x95U\xe7$\xbdA\x9d\xf3\xf5\xb5\xb2\xbe\xbe\xc0\x18\xb5^\xc1\x88Jz\x81\xefs&\x8bag\"\xdf\x99\x00C`\xcen\xca6^\xad\xa9\xcf$\xff\xbb\x85\xf0I\x92<\x10\xc2\xfbC\x89\xc8\x8a\x12fX(\xe1\x82\x94z\x99\x07\x84\xb6\xc2\xbfxd.wz\xb0\xd3\x98\xf9\xc2\xcf\x01\x02f>smf\xf0\x07\xf3E0,-\xe3I\xbe*\xa7\xa2cK\xdf\x88\x96\xd6\xdd\xbf\x9e\x00}\xdf\xfd\xb3D\xaa\xc6\xb0\x89\xb6s\xfdy\xa7\xe7\x81\x183\n\xf1\x1c\xb2\x8e6\xb5~o\xc9\xa1E\xbb\x89\x19>}\x9f[4\xab\xbc\x1a\x9d9\x8e]&\x05S\xea\xb1\xde\x0ff\xff7\x95\xd4\x96\x95&\xcc\x9dL\xcc\x1e\xbaE\x07M\x96\xe5z\x1a\xfdm\nR\x0d\xa3[~\xb6F\x1b}\xfe\xd5aD\xf0\xaf\xf5\xb9s\x8cH\xbfD<\xf9\x80\xf0\x03^\xf5\xf4U\xc7\x8c[\xb6\xdc\xf0\x83\xbc\x91F\xb7\x00\xad6<\x1eh7L\xf7\xb4\x9c9\xaf^F\xcc\x96\xf3\x96\xf2\x8e\x877\xd9`\x064\x1a\xaf\x89\x93\x85m\xbeG\xd5\xd2\x86Q\xa7\x01\x04adX\x92\xf0\xf0\"\x9a->L\xc7k\xbb\x97%\xbe\x94\x0d\x86\"[\xd9\xdcE\x8f\xd1\xb6U\x0f\x0cB@\xc3A\xb2\xb7\xd9\x80\x0f\xd1hqnZ\xc7\x99\xde\x16\x0f\xf2\xc2Z\x9c\x90\xba\xbc4\xd8\xc3\x87:O|G\xf8\xe6\xc0\xd8\xea\x8639k\x16~\x0e\xb3\xfe+%Y\x14\x80L\x97\xca'\x19\xc4\xbbJ\x13\xe6\x93\xcdIR\x142\x90\x8f\xf3\xc7\x9fjBs\xf8\xabMF\xcc \xbf\xa5I\x9e\xf3\x07\xba\xd4\x04d0O?O?93\x9d]\xf2\x15\x17\xcc\xb8\x89\xe2U\xdfSU\xe6\\\xf1\xca\xfb`U\x0f=\x10\xc1\x1cHB\x03\xf0}\x0d\xde5\xf1d\xc9\xd2\x15\xad\x9b\x8a\xf2+j\xe2\xee\xbb\xd0N\xc0e\xcf\x1e^\xc5\x85/.\xbc\x11\x01\x86\x88%{\xe6\x91\xe7\xe9\xa7\xce&\xa7\xbeYa\x98\xefa\xa6\xaa\xe9K?3,Z\x19\xc5K0\xc4L0\xc4M'\x1aV\xa1\x87\xa5\x8e[.\x9d^\x9e\xc7\xb1\x1b\xf5\xb1\xca3o.\x9f\xf2&I;\x1d\xd7j\xc7\xb0\xf9*\x9c\x8f\xa2\xe0\xd9\xcd\xee!\xec\xfb\n\xd1\xc3\x15N\xee*\x82V,\x9f\x11\x96.\xc6G\xc0\xa3#\xad\x1eok\x1c\xa3\xf2\xd4\xe2\xf6\x04\x0b\xd79\x82\xba\xcc\xef]\xac^\xc2\xe7\x7f\x86_\xfbq\x97%\xed\xc7\x87V\x9fG\xad\x1a:\x8cs\x11\x80\xd8\xbd\xff\x82q&ps\xe6s\x12\x19 Q\x14\xc5\x9f'\x9d\x94\xc6\xdd\xfe\xfd\xbd\x14\x05\xa3\x9a\x1e\xa3?\xed\xc8N\xda\xb2\x9a\xf2\xef\xf1\xee\xafC]\x9e?\x995}\x9e\xb6\xe5'\xa7\xae\xcf#\xae\x00o\xcb \x1f\x00F\xb3.\xd4S\xd5\xe51K\xb7\xff\xe5\x7f\xfe\xb1\xa3\xfbg\xd9\xeb\xc3?eI]6\xe5\xa1\x0dU\x1dMK\xea\xf6\x0f\x9d]4m\xfd\xbb\x1f\x7fx\x8a\xf8\xff\xfd8\x9d\xd0\"\x05\x19\x91\xce\xf8o\xa2\xf0\x9f\xbfU\xf4w\xb1\xd1\x90\x9aV\x94\xb4[\xfe\x9f\xe0\x0d\xb1\x05n\xe6\xc6\x8a\x10[p{\xdc<\xb8\x00\"[J\xdfa\x1ewR\xfc\x0e\xf3\xe0\xb6`[\xc8\xf2q\xf3\xe8e\xfd\xfb\xcd#\xf2\x99\xc7\xd3\xfb\x98\x87\xda\xd8`\xa7\x8f{\x98\xd7?\xf7\xaf\xd6@\xe1*\x80]\xcb$\xcc\x92\xb2\x08\xac@\xc1\xcc\xb4|\x94\xca<\xe6\xdf\xaa\x13C$'\xfaR\x97\xfc\xe3e\x1cR,-\xa0\xc3{\xf7\x99\"\xe7\xd8\x96;\xec\xb5\xe7\xbb\xdb\x81\xb3\xca]\x8f\xfb\x0d\xc8\xa6\n\xbe[\x18\xb0\xdb\x83:\xe4\x07X_%\x83\xea\xb8\xca\x95\x10\xbd\xc6\x07\xdf\xf4S\xd3Z\xc6\xac#\x9bO\xec\xads\xbb\xa7\x87\xb2\xa6j\x12\xe5\xc7\xbf\xcc\xa2\xf9\xe6\xc7^a\xa0e\xc8\x8f\x86\xd3O\xb3\x84\xb4e\xdd \n\x97\xf3\x1d\x11\xfc$W3\xac\xcb\x9d\\~\xfc\xb8\xc3\xef\xfe\xe7*\x9bG\x1f\x11\xff\xce\xce:\xc0\x97\x04\x10\x96&y\x86?(\xae\xa7\x8d\xf4\xfe7=O\x17\xcb\xa5\xe6\x8e\xd1\xa2ek\xaf]\xb8\xe1\xdb\x0f\x04\xe6\x99'\x7f\xd9\xb8\x19`\xf0\x8a>#\x93\xa8`\x9aC\xcc\xe8Y\x06\x04Z$cN\xd1\x82\x19h\xc1\x0c\xce4\xa2\x13\xc0P\xd7\xf2\xb23Gi\xe2\xf3}\xf9q\x07w\xd8\xf1@n \xd4\xa7\xb6D2\x8b\x84\xdb\xf4\x8c\xady\xdf\x1d\xa4\xb9L\xb3\xf5\xf5+,\xcb,`\xd4{\xd4c:\xf7\x9d#\x9d\xb73\x8f\xed\xe9s`\x87s\xa7\xa7\x9b/\x83\xcf=C\xcb\xf8V(\xd6\xac\x89\xec\xe5X\xc2\xfd\x8d.\xc0U\xabr8\xb4H\x0b\xd3\xab\xc5\xd0&&)g`\x18\x10\x064\xf7u\x83+\xb0\xb0\xeeK/\xa7\xa4>dob\xb8\x9a\xea\x046#5\x0d\xd3<8\x95u\xf6kY\xb4$\x9f\xa4\xa9\x02:\x19\xa2@\"\x9f!\xd7$U\x8a\x0d\xe1/\x95\xbb@\x91.\xe0u\xf9\xaa j\xael\x1a\xb2I\x17\xc0\x02O`\xfbM\x14\xbc\x0f#\xc8\xec\xdb\xce\\\xca|O4\xc70\x0d\xc0X\xb9@\xbe\xf3\xf2\xac\xd3\x8c\x82~\x94 U\x90\x17U\xa0\xfb['C\x1e\xc4O#S\xec+\xb202\xd5\x84\xca\x1d\xde6X\xa5\x0bxE\x8e\x80\"\xff\xa5\xb2\xe4\xd6p\x90\xaf\x92\x04\x08n\xdfQ0#\x91\xcfm\x1a\xcf\xee\xc0)\xcd\xdb\xb0\xcd9\xa6\xe51$`0#\x8c\x041\x80Q\xca\x06j4U\x87\xea\xca\xa3\x15C\xfa\xae\xbc1\xe1r92am\xf7e{\xba\x85\xdc\x17\x88MR\xe6*\x931\x8e\xe0O\xd0\xc3\xc7\x97\xc0v\xe6\xdfd\xe7\xaa\xac[R\xb47\xf0\xda\x92~^\x18\xe6\x9f\xb2\x94^\xe1,/\xcclN\xe5\xab\xc9\x15\xcc\xcd\n\xb1\xab\xe7\xea\xcc6\xdeB\xe6\xa1\x18\xf1n\xfc\xdeF_\xa3 \xd9\xb9\x0bd\xb6#s}\xb7\xbb\x9c\xb6\x8d\x18\xdb)-<\x8c\xef\x1c~ \xdb\xe4p\xc8\xde\xacm\xc9\xb7\xdf\x07\xe7&x\xc9\xe8k\x07\x13\xae)\xa5/YB\xb9\x0f\xbd\x85\xa2\xad\xc1[3U\x7f7g\xfd\xf79\xd5\x7f\xe7G\xafH5\x19\xae\xf4)L\xe1q\x1a\x92dc\x9b3\x92b\x97VI6\xf6\x9c\")vi\x95dc\xf3#\x92b\x97VI\x96i[\xe2P\xdb\xdd\xd4\xfe\x82\xf5j\xcdb\x15-\x02\xaf\x01\xb2q\x08\x03\xb2\x8c[[{\xf3\x82\xba|\x85\x84NP\xbbm\xea/\x97\xd0<\x07\x05G\xf1\x8fu\xee\xbbipaZ\x01\xfd\x83T,\x86`\"J\xd1\x8a#Et\xa9\xea\xd9lb\xa3\x9e\xe6\x98\xa8\x82c\xc3'6b\xbc\xb3\x11\xc2\xf9]xs\xf3\x08\xc2\x81E\xbc\xed9\n\xcf\x8b{\x98\xb7=C4\x1aE4\x12\x81\xebq7F\x9a7\xc1\xd1w\x12\xef\xee\xb7\xc1=t\x17\xe1\x1f~L\xdf9\x8e\xefn;\x84\xf6=\x18m\xdd\x82\x97\xe7vb\xb4u+\xacm\x0dX\xb0\x0f\xe3\xd5\xc5}\x98\xbc\xb4\x1fK\xf3\x1e\xd8\xba\x9b`=\xbb\x033\xfd\xa7\x11 \x87\xe0s\n\x08\xf9\xbd\x08.\xccb\xa4e3\xec\xbd-\x18\xef\xd8\x0b\x0d\x0d\x8d?\x8d\x9aZyU\x9dG\xcbdeQ\x15\xa5\x16\x9e\xe7\xf9x\xfd_%\x8d\x91\xd8Pn\xa2\x15f\xb3\xd9,m.\x06PR\x9a\x9c\xcf0\x16\x00\xc8)\x14\xd7\x06\x83\xc1\x90G\xf3\xb9\x84\x8b\xff\x0c\xcd\x8d\xd7\x84\x10\x92I\xb3\xb2sV\xfe\x1b\x19z\x16r\x18\x1d~\x1b\xf5\xf1\x8cm\x90\xe7\xac^\xb6A\x96\xb3\xd63\xdb\xe1y\xde\x8f/OoIY*f\xac\x98\xaf\x8b\x8e1)\x03\xa7\xae\x1c\x90\xf2\xd2;\xf1\x10\x91\xe5\x10\x9c\x03\xed\x98\x13\x061vj\x1b\x02\xb33R\xb6~\x1a\xbc\x0c\xff\xc7I)7C\xdf\xbeJ\x99*f\xe7r0\x00G_+&:\x1b\xf1\xe1A\x97\x94\xd1\xf6\xdef\xb8\x87z\xe1{/\xe0\xf3\xa3\xebx}\xed\x10\x82\xbe9\xd8n\x1c\x86s\xa0\x03SW\x0fb\xc9\xeb\x82\xebI\x0f\xa6o\x1f\x93\xf2\\\xb8\xb0;\xddQ544\xfe&6\xf0\xaa\xba~\xbdY\xc5\xea\xd7\x11\xa2\xe2\x99L\xa2\xe6\xff\xa7b\x92\xd7\x15&P\xe8\xda\\\xb2\x9a\xd5\xba\x86(Q\xcc\xf3\xd5\xea\x1e\xc6*\xb1k\xc9N\xa0\xf4(\xadT}~\xf1\x1dW\x96\xcaW\x98\x92K\x13\x97\xc2\xb3e\xe5\xc9u\xbe)\x85'DW\xba\xb6-\xf7\xa4\xd0\xb8\xa6\x9d\xbc\xc8\"\xf1\\\xc5%J\xaf\xbc\xaf\x82_x\xce\xa8\xea\xf3\xf2\x15\xf3\xc9\x8b\xcc!\x84\xe4\x1a\x90\x16\x96\x10\x9a\x95^\x83%\x99\x8c\x8a\x86.C\xaf\xa6\xa1:\xfc\xef\xf23\x00\x00\xff\xffPK\x07\x08>\xd4\x17\xe7u\x02\x00\x006\x0e\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\x94\x91O \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00 \x00images/folder.gifUT\x05\x00\x01\x98w#1r\xf7t\xb3\xb0L\x14a\x10gh`\x00\x81\xff\xff\xff+\xfedad``d\xd0\x01\xf1A2\x0cL\xea=\xfd+O\xbf\xe5_<'\xa4\xb1\xfb=sO\xf9\xf4V9\xd5i\xcf,2\xa7z-\x12\x9a\xbdK\xf0\xcf)\x01\xdb\xdb\xb7O2\xb32X\x03\x02\x00\x00\xff\xffPK\x07\x08\xa9\x03\xb9JR\x00\x00\x00P\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00!z\x92E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1e\x00 \x00javascript/jquery-2.1.3.min.jsUT\x05\x00\x01\xef\xef\x92T\xcc\xfd\x7f\x93\xa36\xb68\x8c\xff\xffy\x156;\x97\xa0\xb6\xec\xb6'\xc9~\xef\xe2QS\x93L\xb2\xc9f&\xc9ff7\xd9\x8b\x99- \x04\xa6\x1b\x83\x1b\xe3\xee\x9e\x18\xf2\xda\xbf\xa5# \x04\xc6\x93\xec\xfd\xd3\x9f\xbf.\x8eyD\xab\xb4\xc8\xf1\xe4\xdb<\\L\xea\xc9\xed\xbd\xf8\xb2(\xca\xe4:KC\x9e\x1f\xf8\xe4\xea\xfa\xffL\xe3c\x1e\x8a|\x0e\xc5\x0c\x9d\xac\x82\xdd\xf2\xb0\xb2\x08\xa9>\xecy\x11OvEt\xcc\xb8m_\xf8\xb0\xe0O\xfb\xa2\xac\x0e^\xff\x95\xd0ET\x84\xc7\x1d\xcf+\x8f9\x14O\x97\xc8\xed\x1aB\xa74v\xa6]\x16Tm\xcb\xe2q\x92\xf3\xc7\xc9WeY\x94\x8e\xa5FQ\xf2\xfbcZ\xf2\xc3\x84N\x1e\xd3<*\x1e'\x8fi\xb5\x9d\xd0\x89.i\xa1u\xc9\xabc\x99O\x98CQ\xe3\xc2_\xc7:\xe6\x11\x8f\xd3\x9cG\xd6TwW\x96\xf7\xe4\x8f[m\xd3\x03\xee\x8f\xfc\x81\x96\x93\x90\xf8\x01\x8eH\xb88\x88\x19\xc2\x9c\x84\x8b\xb0\xc8CZ\xe1\x98\x84\x8b\xfd\xf1\xb0\xc5 \x17i\x1e\xf1\xa7\x1fb\xbc%\xa7\x06\xa7d\xbb\xa8\x8a\xb7U\x99\xe6 \xbe%\xdb\xc5\x96\x1e~x\xcc\x7f,\x8b=/\xab\x0f\xf8Nd\xca\x8c \xc1;b\xc1\xe2Y8'\xfd>\xa8\xb1\x88\x89\xc8\x17q\xbeH\xf3\xb4\x82/\x0d.\xc8\xf5{\x7fs\xd8\x1c\xbf\xfe\xea\xeb\xaf7O/\x97\xc1\xac\x1e\xbc?\xbbN\xf0\x9e\\\xbf\x9f\xef\x0e\xf3k|O\xae\xe7\x8e\xbf\x89\xe8\xfc\xd7\x00]').\xc7\x1bc\x8b\xaa\xf8\xc7~\xcf\xcb/\xe9\x81;\xa8Y\x8b\x96I\xbe\xd8\x97EU\x88\xc9#' 9\xee\x0e\x87E~\xa8\xcacX\x15\xa5\x9b\xe3\x03\xcf8\xa8\x1e\x95 \xc0\xdfAT\x10\xa7\xe5\xa1\xbaT\x01\xbfw\x96\xa8\xc1\x19\xfdh\x96\xf9\n5\x98\xdf\x8fL\xb9\xb1b8$3:s\xc4r2w\xd9\xce\xf7\xa0\x9f\xe1\x0dY\xda6\xbb =\x1f\x168\x0c\x02\xd7\x0fD\xf5ytq\x94\xed\x82\xd5\xf5\xd9\xda\n0Rp\xe1\xc6\xf8P\x94\x95\x1b.\xc4\x0f>\xeca\xea\xc2\x85|hp\xbe\xe0O\x15\xcf#\x02;N=\x1bm\x8a!Q,\xe6>\xc2\x1c\xc78!\xedD\xfa\xcb\xa0\xaeO\x0d\xde\x92\x15N\xbbd=\xf4[2]\xadc\x81\xceXQd\x9c\xe6\x1d\xf2Ll\xdb\xb9%I\xaf\xb2\xad\xaal6C\xf8\x0c\xdb&u\x9d/\xd2\xc3\xd7\xba_ \xaak'!\xa7\x06\xe1-!$\xb5m'\x91\x80\xbb\x9d\xcf\xd1:\xbd\xd9\xaeEEi\xec\xc8\x1d\xe5\xd0^K\x08\x89~\xb1I\x9aO(\nI\xe23\x81\xf7\xa8\xf8I\xa6\x84D\xa2{\xb6-~D\xab?f4\xcd\xe5\\;\x91h\x98\x13\x91\x0c\x1b\xdd\x89\x10B\x9e\xc3=\x87\x93\xe9J\xe0I\xdb\xee>\x86\xc8\x0b\xc5J\xbam\xbaY\x17|=5X4O\xf4\xdc;\xb78\xc6\x11B\xeeC\x91F\x93\xa5\xea\x0dd\x89P\x0b@I\xb7p\xce\x89?\xedi\x1e\x15\xae\"\x1b\xd6\xcc\xd9\xcd\xde\xd0j\xbb(E\xf2\xceAhQ\xf2}FC\xee\\o^]'\xd8\xb2\x10N\x0f?q\x1a}p\xa7K\xcc\x05\xd1\xe9\xc1\xf1\x90 Q\xd4\xe0\xbc(\xf6&06\xb8[\x8f\x91Mn\xe9$\x8b\x10\x92/\xc4:B5jj\\\xf8\xab'\n\xa7\x87\x9f%e\xba\x8c\x13m\x9b\x12B\xe8B\x920Q\xd1\xf7\xc7\x1d/\xd3p\xa4\xcc\xb4[\x02\x8al\x9b\xce\xf7\xb4<\xf0\xaf\xb3\x82V\x0eE\xb3\xd5\x0dY\x8a\n\x8c\xc5\x18\x1b\x81\x02\xc2\xa9\xd1\xff\xba\xa6\x8b\xbc\x88\xf8\xbb\x0f{.AR\xf6\xdb\xa1\xc8\x9b\xae\\j\xeeB\xdb\x9e\xdeJ\xcc\xd5K\xee(\x0b\xb6\xd2\xc3\x8f\xfa\xe5\x87\xd8\x82*\xa6\xd0\xb3\xafv\xfb\xea\xc3H\xcf\x00\xc1\xac\x0d\xe0U\xe3])\xc0\x10\xa5Em\x97\xe6\x91\x10\xea\xd1\x99e\xb9g;\x8c\xd6\xb5\xb9d:\xd5\xdb\xfa\xa9\x1a\x04\n\xeaZ\x17s\xf5\xf7\x06'Y\xc1h\xf6\xd5\x03\xcd\xce{\x8aC\xc2\x1fh\xb6\xa6b\x06\xcbt\xe7P\x84\xa9m;+XI\xc5G8\xd6\xf1\xc0'\x87\xaaL\xc3\xcaB\x9e\xc3H\xb6\x08KN+\xfeU\xc6\xc5\x8eu\xacCX\xa6\xfb\xcaB\x98-\x80&Q\x9c-\xb6\x9c\x02~\xe7y\xf4\xe56\xcd\"\x87\xa1\xc5\x9e\x96<\xaf\xbe/\"\xbe(\xf9\xaex\xe0\xfa\x0brC\x87\n\xc4\x1f\xd2\x1d\xcf\x04\xa9\x1f\x9b!\xda\xee\x93=\xb6v\x87\xb9\xd5m\x9c{\\\xc2\x1e\x88\xf8\xf7t\xc7\xc7\xe9\x9f\x04\x0d\xf1\xdd\xb6\xbb\xe7EU\xbc.\x1e5\x7fA\x08a\xfd\x94\x11\x8a*h\x9f\x98\xbf\x08s\xb2\xc41\xa1\x1a\xa5&\xe4\xe0P\xb4Nc'\x04&2A'\x01\x0b\xeb\xf8\x86\xaf\xb9\xc4w\x11a\x8a\xeaQ\x9f\x078D8\"\x84LW\x88\x95\x9c\xde5<;\xf0\x89(\xc3%\xfc\xfc\xc1\x12\x97\xdb\x92\xb0!\nr,~\xfeX{\x1f/\xa5\xb1\x1cm\xb0\x80\x9a\x8f\x01\xb3e\xb9\x8e\x00\xe8n\xa5\n\x81\xde\x04\xebq\xc7\x07\xac\x98\xc1\xe7\xb2\xba\xf6\x83\xf5\x10\xc18\x07G\xa1f\x8a\x90\xa79\xa7\x10[\x07`o\xcdm!\x980\x8a\xdcX\x8e$\xc4\x14!\x1c68\xcd\xcf\xdb48\x19\xd9k\xe6\xcdWn\xa2\x99\x1a\x8aC\xd1]\xd1\xd4\xa0\xabb\xdadwgLC@D\x96\x98\xb7\x00\xb1\x0eo\xa2u4\x9b!\xea\xf3\xd9, \xcc\x8f\xdaQ\xe9<\x84c\xb1KK\xbe?\xeb\x95n@\xc0\x99\x1f\xe0\x98,\x05E\xd6Mm\xc94\\'7\xf1:\x9e\xcdPD\xa6\xcc\xa1~\x1c\xe0\x18\xe1hJ\xc8\xd6\xb69\xb00\x90\xda\x12&>d\xfaLh>k@@3N\x89\x1f\x08\x90\xde\x02Q6Z\xd4\x0d\n\x90\x94k\x14\xd9v*\x1b\x8d\xd0\xba\x05\xadX\x82\xd6\xef\x16\xd0]T\xd0\xee\x078\x15\x9c\xf81\x8d\xdc\x15\xde\x97\xc5\xd3(\xac\x08\xaeG\x15=\x83\x03f\xdbN(\xf9\x06F(\xa6D4l\xf2)T\xf0\x06Dq\xeb-\x07\x82\x9f#\x1c\x93s\xc6\x8e\xaa\x9e1\xc9\xd2a\xae\xc4/gX\x01\x02\x1ev!\xbaN\xa8\xf9#h\x92\xf8\x9d\xcdp\xacY\x08\x81\xb6\x1e\xddW\xb4\xe2\x8b\xbcx\xc4\x87\xe3^\xc8\xa8\xee]#\xfa\n\x1c\xbb\xf5\x85d\xd0&\xdf\x1fw\x8c\x97\x13)\xcdM\xf4(&\x00\xd3\x13Q\xc3\xe4'\x9e|\xf5\xb4\x9f\xc8m\"\xb9\x03\x0bx\xc9\xca\xb1&\x16\x1a\x08\x95[\xdf\xf2%\xc5\x98X36\xb3\x02+8C\x7fh\xad\xcbL\x0e\x1d\x0fM;\x06\xba%\xbe\xeb\x11\xce\"\x1c\xa1\xc2\x92\xbah2m\xdb\xcc\x9b.]\x8b\x8aA\xa8\"K\x81\x86\xeb\xda\xcaa\xbc\xbd\xf5d7\x82\x15\x9f\xaf\x00\xa6\x1a\xd1\x99\x8a\x8c\xd05\xcd\x0d\xe3-N\xf1-\xbe\xc3\x19\xde\xe1\x1c\x17x\x8f\xefq\x89\x0f\xb8\xc2Gb\x1d\xd2_\x7f\xcd\xb85[] ^JL ~0E\xe1G\xb2\xc4Od\x89?\x90-s\x10\xfeU\xfe\xbc\x94?_\x8c\x0b\xac\x82\x0b\x12p\x97\x91\xe9\x12\xe1e\x83\xbf$\xab\x17/>]\xe1W\xe4\xd4\x0c\xa5\xef\xaf\xc4\xbe\xfe\x9a|\xb5\xd8\x17{\xfcW\xf1+\x84\xf8o\xf4\xc3\xb7\xe4+%\xeb\xff\x8d\\\xc2=K\xc1\x19k|\x13\xdd\x84\xebP\"\x7f\xea\x87\x81\xe8\x8abA&\xa1Z\x9e\xf9\xaa\xc1\xdf\x11+\xdc\xf2\xf0\x8eG\xb5\x94\x92yT\xd3\xc3\x87<\xac\xe9\xb1*\xe2\"<\x1e\xe0i\x9f\xd1\x0f\xb5\x90-\xcb\";\xd4\x11\x8fyYG\xe9\x81\xb2\x8cG\xf56\x8d\"\x9e\xd7\xe9aG\xf7uV\x14\xfbzw\xcc\xaat\x9f\xf1\xba\xd8\xf3\xbc.9\x8d\x8a<\xfbP+5IT\x1f\xc2b\xcf#\x0b\xbf&\x96\xbf\xd9<=_n6\xd5fSn6\xf9f\x13\x07\x16~C,\xc7s7\x9b\xcdfQ\xfb\x9b\xcd\xe3<\xa8\xfd\xf7\x9b\xcd\xd3r9\xdfl\x9e\xe82@3\x0b\x7fO\xde\xb4\xb4\xc4z\xb4\xb0\xf5\xf8'\x0b\xe1\x1f\x88\xb5\xd9\xf8\xd6\xec\xf5\xcc\xbar\xac\xd9\x9b\x99\x85\x1c\xcfU\xef\xfe\xd5\xfbg\xf5\xf4\xb7\xc0#H\xa5x\xee'N\xd7\xd4{\xf1\xfbI\x80\xae\xd0'\xf5\xc6\x1a~\xd8X\xe2\xcb\xc6\xaa\x1dk\xf6\xfd\xccB\xa8V\xb5l6\x81\x85\x7f$\x96\xdb5\xb8\xd98\x8e\xf3\x9fW\x8d\xea\xe1\x17\x07\xf9\x9bM\x10\xd4\xd6\xec\x87\x99\x85\xaeP\xbd\xb8B\x9b\x8dh\x1a\xff\x9d\x08`\x95\x1b\xddy=\xb3f\x16\xb6\x12\x0b\xe1\x9f\xcct\xeb=\xf4q\x06\x15\xbfW\x95\x06H\xb7\x82\xae\xe4\x18f\xcfT\xe1\xb7#\x85\xaf\xb0\xfc\xb1\x10~7\xf6\xd9\xf1of\xbf\x89.\xbe\x9eY\xa8\xcd\xfa\x8f^V\xa2\xb3\xbe\xdfl\x82O6Vp\xe5\x99\xb3\x07m\xff\xd3,\xf1#\xc2?\x0f\x1b\xfb~f=\xb3\x10\xfe\x85\x9c\xbe}\xe5\xf6\xbe\xfdIM\xbd\x85\xf0\x97\xaf_\xbe}\xdb\xff\xba\xd9,\xba\xef\xef^\xfe\xb5\xffU|\x1a@\xd2\x95\x85d\xe6\x97\xef\xde\xfd\xe4\x0ez\xf1\x03\xc2?\xbe\xfd\xea\x1f\xaf~\x18~\xf8\x11\xe1/\xbf\xf9\xf6\xf5\xa0k\xae\x03\xc0\x0f\x9a\x8c:\xa3\x87\xaa\xce\xab\xad\xf8?\x17/h\xee\x84\x82\xf7\xad\x8bx.\x90\x9b\x02\x1e5[\xfc\x81\xe7u\x11E\xb5\xe3\xf8\xb3yP#g\xb3\x89\xaeP^w\xf0\xab>\xa8\xf7\xcd&\x9a\xa1\x1a\xb5S\x0b\x80b\xa5\x82#/\x8al0n\xb1/\xbe\x9bY\xe8\x99\xca\x92s\x1e\x1d\xbe\x94\x1a\xa4\xe1\xd8Dur\x99\xdd\xaeW\xfc\xbeN\xaa:\x93#\xea\x06\xd8\x1f\x83\xe3\xb9\xf3\xcd&B\x1et\xdd\xe8\x98\xe3\x11\xff\xfd<\xa8\x9f\xa9.6\xf8_\xe4Z\xf4*\xcd\xf7\xc7J!\xa4Zt\x86\x96\x9c\xd6\xecXUE\x8e\x9e]\xa7\xf8\x7f\xc8\xf5\xfb\xed&\x12\x8f\xcf\xc8\xf5{\xff\xfd)\x98mN\x9b\xc3\xd5\xc6\xcfi\x95>\xf0\xc9\xe6\xf1\x1a\xff[\xd6\xf6'\xc7\x17\x18d\x86jg\xf38C\xf5f\xa1\x13\xd0\xb3kL\x19\xb9\xf6g\xbf\x05\xd7\x981r\xfdI\xbd\xd9\\'8d=\xc8\x83}\xe8o6\x11\x9d\xc7\xc1i\x85\xff\xdc\xc0(\xbcZ\x0e\x11\xd5\x0b\x18\x81\x00\xe1\x88\x91Q\x96\x8aX\xcb'k\xc6\xe6\x7f\xfe\xfc\xf3O\xff\xac\x19\x1c\xc1\x9eEu\x1dz\xcc]\xdeD\x9e\xa4\xe6\x8b\xb8,v_ni\xf9e\x11q'\x9aA \xe4\x8e~\xbc\xb9Y-\xeb\xcf?\x7f\xfe\x97?\xe3\xd5\xf2\xf9\xa7vT\x7f\xfe\xe7O\x9f/\x85\xa8\xc2L\xb6e\xe7\xa0f]\x95\x1fN\xdf(\xc6\xe5+\xf2\xad\xe4T\x1e\x16\x00}B\x12; \xdc\x7f\xfb\xca7\xdf\xb5j\xb3\xa5\xd7MH\xabp\xeb\xc4\x0c\x9d\xbe!'\xa8\xd7\xfdJ\xe5\xf2\xfaD\xea\xafZn\xc1\xaaY\x86P3\xca\xefS\x83\x7f^?n\xd3\x8c\x0b\xfa\xa5X\xe6\xd9,@\xeb\x96]\x0e\xe7\xab\xa6iZ\x9e$a0\xe1\x11\xe6\xb2\xae\x18o\x15\xbd/\x80\xce?\xe2'\xc1\xbc:\xccc\x8b\xe21\xe7\xe5+E\xdc\xeb\x9a\xb9\x0fhJHn\xdb;\x87!\xcc\x04\xcb\x91\xe3H\xac\x8d\x1f\xe0;\xc2\xda1\xb7\x12\xc6\xd4\x10\xc7\xa7\xb4\xaeWSB\xeel\xfb/\xf2g\x05\xaf\x9a\xe0F\xa2\xdd)\xb7\xed=\xc8\x82+\x95\xd7\x89\xc9\xbf\x17\xfc\x89\x83\xc0+\x08\xf5-\x89\xfdU\x00y\xfeBDy\xf1\xb4%l\x91\xf0JI\xd8_|\xf86rn\x11\x9en\xebz\xba5\xc4\xe8^[\xdbE*\xa4\xb4\xdb6Qr\xd6[\x84\xa3VJ\x1cL\x82mCK\xbd\xb4\xf3v\x91mW\x0e\xc3[d\xdb\xbf\xd7\x86\xe8{\xec?\x0f\xf4w\x0dy\x116\xc7s\xf8\xe2\xc3;\x9a\x08\xe9[L\x02\x86\xde\xc3<|\x1a \xdb\x0e\xfb9\xbf\xcc\xe8\xe1 \xf2\xfen\x9dmN\xd1g\x1c5B\x0e_\xdc\x1f\x84\xe48\xbd\xaf\xeb\xe9\xfd\xa2\xe2\x07\x10\x1ea\x8e\x0f\xa4$G\xfcH\x18~\"jq(\x16\xcc\xe9]w\x9e4%\n\x0c\xceU\x05\xe8T\x90D\x08HN)\x17\xebeU\x95);V\xdc\xb1\xd2\xc8B\xc8;\x90\xb2%0\x8cak\xb3yf[\xc8e\x8b\xc303> | \x96\x9fF\xe4\x13kv\x98Y\x9f\x04\x13\x0bg\xa4\xd0\x8c\x9d\xdc\x13\xd9|\x8e\n?\x0b\xc8aV2G<\xa1\xf5#\xa1L\x8f\xcb\xb6\xf7\xcca&|\xd4\xb5\x18]\xb1\xb8-\xd2\xdc\xb1\xb0\x85\xc4\xa4\x9c\xca\xbafJ\x853\x18u\xd34\xaa\xe2\xd4\xe9\xf45?a\xeb\xd9JP#\xd8\xa8\xdd\xee\x15\x8c\xb4Ta\x0b!\xb3MfN(\xf6s+v\x01\x8c\x853!\xc1\xdc\x08Y+\xdc\xf2\xd70/\xb6\x1d\xf1\x8cW|\xc2|\xba8l\xd3\xb8rP\x80\x99\x0fy\x03\xc2u_X\xd7d\xcaL\x8d\x92\x7f\x0c\xc8t\x89i\xf7\xfd\x96\x99\x873\x03EW\x94>Xh\xdd\xcd\xdetJ\x1d\x86\xd4\x04\xb5z\x84\xe9\xaa\x9d(s1l\x9b]\xd6\x80aF\x84Hl`\xb6;\xd6\xc7\x90J\x8a\xab-dj\x18$`\xf0\xf9\x1cE\x0bZU\xe574\x8f2\xee\x87>\x0f\x02b\x0c;\xeb\xd5\xc6\x04\xa8G$\xb4\xed\xa10\xb6\"\xc4@|\xb6\xed\xfc\xc6\x16\x87\xe2X\x86\xfc\xdb<\xe2Ou\xfd%\x9a;\xbf\xd1a\x9a\xd8\xc1Q\x0f\x1b\x85Hv-$\xe1\"\xe7O\xd5\xdb\x94ei\x9e\x08\x8c\x17\x1ar\xc9|\xd5*D\xbc\x95;_u=\xde\x99\x0b\xd5\x12\x8dn\x08\x17\xb6\xa5\x96B\x81\x9b\x00yR\xcc;\x9c\x14\x12B\x8d\xf9\xcd\xff\xaf\xeaw\x8c\x06\xea\xda\x92\\\n\xbc\xa1\x0b\xed\x15f{)s\xcc&5\x98\x92\x19\xc3\xe6\xa7\x10G\xb2?\x1c\xc7\x84:~\x80CM)\x19\xc2 \x89\xfb`\x90\xcc\xe7(\xf49\x89\xfd$\x08l\xdb\x11P@\xa6N$~\xc43B\x8d\xf8\xd7vi\xdf\xdb\x0b\xb6=v2MG\xf1\xb6m\xd3&$ [(u\x05958\x16\xef\xe9\xe1\x977\xaf\xcf%rP\xdf\xd1!\x05\xa6\xa8\x95\xb5U\x0b\xed\x89\xa7g}\xf3\xee\xcd\xeb>\xfeu\xa7\xab\x06\xef\xa0U^\xe9ZF\xa4\x7f\x8e\x13B\xbd\xf3\xd6\xdc\x87\xf6|F\xd2}Ao\x13\x03\xd8\x93aw<'' .\xc8\xd9\x07\xccE\x1a\x8f\xe91\xab\xfe\x99\xf2G\xccm\x9bO \x11\xc0\xb2\xb7m\x87/h\x14}\xf5\xc0\xf3\xeauz\xa8x\xceK\xef<\xc9\xb1\x8eyV\xd0\xc8\xc2\x9c\xe1\xe9\n\xb9\\la\x1an!\x97m\xf7^\x1d\xab\xc8\xbb\xec\x08\xe1=\x99\xc6N\x82p\x08\xfb\x1eP\xf0\x81\xdc\x1a\xc0c*\xceCM\x1a\x89\x95ZxJ\x07\xf4\xaa\xfdl\xa1F\xd48\xb6\xe4\x17\xeb6\x95\xfc\x89\xc2\x98_\x16;\x891-\x84Ts\xe7\xb4_\xc8\x8b\n\x80\xcf[m\x899y&\xc9[r\x89-\x90%\x05\xafr\xa1\x8bE\xaf\x8b\x14 \x1e\xe6\x88\xa7\x83\nE]u=\x96\xea\x1c\x87\xdd\x14\x8dyN\xb4\x88\xd3\xce\xacOn^\\\xd3\x9b\x17Ra\xd0%\xcf7q\xf0\xc9dw\xa0YV<\x86t_\x1dKN>\xf9\xe4\xe6E\xb1\x07\xa2\xa75\x9e\x90v-\x13o^\\\xcb\xe4\x1b\x0b\xd3\xf3\xd5\xb3\xfc~u\xef\xc9'\x9f\x04-\xee\xb2\xed{9\xdd\x96\x7f\xf5\xfeY@:\x1d\xe3'\xf5\xc6\xda\x80Bi\xb4R\xdd\x93\xae\xaa\xba\xd6Uu\xdaL\xcf\x05\xe8\xae\xa5\xd2\xe6R]i\xf4\x1b\x91\xc3\x1f\xab\xed7r\xa1\x9c\xab\xf4\xc0#e\xbaO\xa3%\xe9\x9f\xa0\xb9\xd9\xd5H\xd1\xc5\x9f\x163\x7f\xf6[\x00\xd4d\xb0\xba\x12O$C\xceZrSh=\x14\x8e\xc4N\xb4\xb0%\x95\xcd\xd0\x95\xc1\x99r?{.h\x18\xb6^]\x9a&\xf1\x9dDck\x07%\xa5\xbe\xabU\x17_\x9a4\x9e\x83\n|l\xd2\xf4'l\xb9ZS~\xa1\x96+\xec>Y\x08\xeb\x92xq\xe5\x8a\xf9Bb\xcf\xec\x84@\xc1\x0f:\xbf\xde?\x07R\xe8Ou],\x1e9\xbbK\xab7\xfd\xbc\xe2\xc3\xae\xf8u$\xb5\x18\xcby\x18$\x8a\x0d9X\xb1p\x11\xa5\x87\xb0\xc8s\x00V\xc8O\x0e\xea\xc4\x0bK\x91\x08w\xef\xfea*v\x07\x8c\xadTc\x9b\x12\x0b\xff(`\xe1\x9e\xdc\xb7\x13o\xa8\xda\xee\x95|Z\x0bn\xa1$\xe5X\x9e\xd2\xcc\xc3\xf4\x8c\x14\x8b\xb0\xd8 \xea\xa8\xd9\xbc\x1f\x8bC*:\x8epEX]\x1b\xd9\xf2\x8a\xa6\xf9\x01yc\xfa\xa7\xbf\xf4\xa4 \x8f\x0e\xd9=WHK\xac/\xc0\xb5r\x0b\x01u\xde\xd4\x99FR!\x14\x19&(S'l\x9b\xf6\xbaG'B`\x8c2\xdau\xdb^\xfd\xd9\xbe\xf8\x15,\x9a\x86T$\x8d\x1d\xa6\xe4-Fz\n\x00\xf1\x05H\x86\x12M\x97\xebVF\xc5_\x10\xe6\x9d\xd5C\xcdC\xa3L\x08\xc7\xcb\xb5\xd4eN/\xf6i>e\x97>\xb5\x04\xc8\x8b\\'\"c2\x00!d\xa8\x97\xaak\x86\xbc\xcbS\xc0\x90\xbb\xc2+[\xcc\xba\xb4\x9b{\xc5\x05\x9f\xcc#\xb1B\x97\nAC\x91'\xc6\x97\xd4\xf5\xa0\x1f\x84\x90\x07\xdb\xae\x9c\x07L\x917_\xb9L\xe6b\x97r1\xe4\xad\xdc;\xefo\xce\x1d\xa6h.~\x18r\x97\xeegv$J\xaf\xc6\x16\xe8\xd2\xc4\x86\xad\xd1@\xb7l\xc0\x07\x18\xaf[\xe2\xd3\x00\xa7\xc4g\x81\xd4*\xd6\xf54F\x06\x00&m\xa7\xbd\x95\xcb\xc5K<\xd6AQX\xf0ImY\xa5\x16X\x87\x84\xae;y\xdd\x80\x9f\xed\xe2\x98K\xc5J(r\xb1\xf1\\\xa9\x99K\xe6\xd8\xfaQ@\x08I\xfd(@\xd1l\xd6\xc1A\xc6\xe0\x1b\x86/\xae\xca\xf6 \xba\x9c\xea\xe7\x95\xbblp\x82\xdc\xbc\xc1 \xd3\x18o\xfc\xb8\x15\xf4\xbf\xf91\xcb\xe4\x1f\x86\xcc\"-\xfe<[\x8c18\xd4\xba`\n\xba\xe0\x96\x91\xfd\x07\xb6\xc8'\xcfV\x82\xe0\xe3\xa93=C\xceu=\xdd\xd7ui\xdb\xa5\xc45\x0c\xd5\xf5\xbd\xa0+\xea\x0d\x81\xb6Mn\xa1\x16M2\xa91\xa9\xeb\x11\xe4*\x803j\x15\xb4\xa07\xee\x12Z\xdc\xd2*[\x94\xf6\x89\xa3S\xd3\xcd \xc3\xb9\x9c\x10\x9f\x06\x9aJ\xdd,an4\x0e\x1a\x9d\xcf\xdf\x99\x17m\xee\x9d0\x10:\x07U|\xbc0\x00;'=5\xd5\xc0\xd8 \xc01\xe1\xb6\xfdJ\xce\x92\x99\x13\x0fr\"\x8f\x83\xde\x7f\xba\xd7\\\xa7\x06\xb0\xd6.3\xf6b\xd7\x94\x8d\xc5:y\x03Y\x87!\xd7\x89\xc9\x88\x88\xc1\x04\x1d\x8c\x17\x87=\x0f\xd38\xe5\x91\x17K\x19\xc3\x05%\x9d\x18?Xe\xf6$\xa037\x81\xb7\x1f\xf2\x8a>M '\x9e\x1c\xf3\x92\x87E\x92\xa7\xbf\xf2h\xc2\x9f\xf6%?\x1c\xd2\"w'\xd6\x8c\xca)=\xe6\xe9\xfd\x91\xbf-\xca1\xa5\x86!\"\xc06\xce\xc84\\D\xbc\xe2a\xf5\xea\xb8\xcf\xd2\x90V\xfc\x80\xef\x88\xc2\x88o+\xc1{\x08\xf1 \x0c\x08\x9c\xa5`B\xc4\x07\xe7\x0b\x843-@0B\xfdX\x08\x10@#\xfc8\xb0m\xb0\x87\x01\xb2\x1d#d\xa8\x17\xa92I\x06m\x12^!\x0dlw\xa0\xb7\xc4\xb4\xc1\x9c$ \x1c\xbc\xe3O\xa36\x19\xc4\xb2\x00\xd5\xc5\x06\xa9\x15#\x89\xe5\xf9\x88@Ku\xfd\x17\xf9\xb3\x82W)J\x9fYw\x81\x91!\x1cX\xe6U\x8b\x04\xcdD\xb0\xc0\xa4\x84.\xe0p\x12X\xc55]\x8b\x04S\x13\x19\xce\x08\xd8\xbc\xea\xb3\x91Oe\xd3\x9f\x99\xf8Q\xf6\xf4\x9fb\xe9e\xben\xde\xe0\\\n\xea\xe8\xc4\xdc\xb0\xc1\x91\xd4OI\xdcp 'C[\xed~\xbe\xc4\x92\xed\xfd\xf1\xc0\x8fQ\xe1\xa6\x0c\x032q\x7f\xc1\x1d\xa8\xbb\xa7\x06\x0b\x01M\xfc\x96<\x83\x83M\xf7d\xddX\xee)JK\xd7\xea\xd0\xae\xa5\xec\xe4\xa7\xcb\x06[\x93\x91\xef\x0d\xb6fmr\xc9\x1f\xd2\xe2xP\xa3\xef\x95\xfd\xedR\xa6\xa6\xc1\xfb\x92\x7f\x0d\x02\xbf{\x82S\xf11\x05\x82\xbf\n\x88\xf83\x10\xfe1\xf5?\x0d\x88#\xfe\xd65\xf5?\x83\xbf\x9f\x07um\xda\x03\xaa\xacBD\x01\x18|.`\x10\nZbg\xf8\x9f\x06\xa0\xf7\xc7- \xe3\xcfP\xa3\x0e\xdc?\xda\x97\x1e\xbe\xc0V^me\x03\xab\xa0\xad\xe9S\xe4\xa9\xde\xe9\x0d\xedP\x7f\x19\x88\x8e\x7f\x16\x90\x99#~<\xd1e\xf1\xf8\xe7\xa0\xaeW\xc8}~\xe5X\xfc\x81\xe7\xb2\xb2O\xc1\xe86\x8a\xf4\x1b\x12e?\x97e\xff\x7f\xc1\x8c\xfa\xff}\x96\xc1\x15?\xb6=l\xb1\xd1\xd6\x05c;g*\x9a\xb7m1;\x1a\xd4~Y\xc0\x1c\xa8\xa3\x1fQ\x87'6\xa2\x0b\x03\xf2DN\xd2\x9fr7\xb4\xed\x7f\xca\xec\xa1\x90\xba\x19I\x9c\x10O\x97H\xbe\x84\x9d\xbd/\xb2Z5\xf3\x9c\xa1\xb9~F\xb00KQ\xef\xb2\x9bCX\xe6\xe7\x81\xf6J\x82\x14s\xb5>E\xa8\x11\x00-A\xe8\xdd\xcb\xbf\x8exd\x0c\xb5F\xe3\x1a}\xa9\xfb\xe88\xd6\xf6\xb8\xa5\xa7T\xf9\x8f\xcd}\x9bF\xd9\x8e\x9c\xf7\xeb\x83O\xe1 \xa9\xd5J\xd7\xb5\xd3\xb7\x11p\xde\xb7\xf6/tfI\xc3\x80\xfa\x19\xb2\xc4\xa4~p(\x1e\xe9\x97:\xad\x1bAka\xa7|1^\xea\xfa\xf7UeC5\x99\xd2\xe8Z\x08\xf6Z\x83\x1a<\xd8\xbb=\xb3\xd76Y\x1f6\x10E\xdf\x9d\xc8pB\x92\xc6\xb1\xdc\x13\xd2\x9c\x987\x97y\x0e\x9f \xa4n\xc9\x04Op\x96\xa1\xab\xbf{|\n\xaf\xef\xd5kh\xdbKB\x08o\xe1,D\xaeu\xd5}4?\xdc\xccW\xae\xf5\xcc\xfc&\xc1\xa9\x83E\xd9\xd4o*\x8b#p\x05o\xa1\xe8\xef\x02\x1d\"\xc0\x1b\xc3Jk\xb3\xafu\xcd[8\xd55\xcfVP\xf7\xcc\x9a[\xeet\x85\x04\x82\x90ij\xdb\xd3\xad\xa0\xda\xf7@\x9cc\xcdI\xec\xd1)k\xa5\x83\x8cd\xfe>\x10\xb2\xe7\xd6\xcb.o\xbd\x12LA\xb3!K;]\xad\x0b\xb2'V\x91g`\x10Jm{Z\xd8vo$M\xbb\xf5\xd3\xd8)\x88\x9fx\xf7\x06\xb1w\xef\x17b\xe6\xe19\xc0\x89m\x1f\xd0\xe9\x8e\xdc\xfb\xc7\xa0\xae\x1d\xf1\x03\x9eI\xb7\xe4\xce\xa7\x01\x18{\xe4\xe4V 6B\x1em\xfb\xd6_\x05x\xd7Kx\x1e\xe0L\xb0\xb1\xf7\x86a\x8c\x9f\x07\xedhg\xb3\xdc\xb63\xdb\x16\xa3\xaekgGr\xb2Du],\xf6\xc5\xde\x01#\x8f\xfe@m{6\xdb\xd9v\x06\x12\xe1I\xf4\x82\xf8\x8f8\xc7\xbb`-\xcd\xf2[\x9e\xe4\x00\x9eX\x0e\x93]g\xaa\xebHp\xf5\xa2c\xb2\x8bH\xf4v\x15\xac\x0d\x06\xe5\x8f\xf4\xe9?\\\x1c\xd5i\xe8\x92\x93\xc9\x0eeF\x87\xc4\x10v\x01\xc2rT}O\x81\xdd\x9cp\xbc\x93\x8a\x92\xdd\x7fE\x84\x90\xa5m\xef\xae\xa3\x1b\xb2l\x9a\x11\xcagXw\x0bn\x14\xb8\xa5\x03,V\xb48\xf0J2$\x07\x9f\x0e\xc4\x07\x83\x8e[\xc7\\\x1dM\xf2h\"+\x90\x9cvkf\xee\x1f\x03\x0f$\x00\xae\xe5\xa3\x95\xe7\x84\xc4\xa7\x98b\xcb\xc2,\xc0f[\x03\xcb]\x87\x0e\xe5\x11\xf3\xd8\x96\x9a6\xf5 \xa8\\8\xac\x8d\xc8\xdf\x04\x91\xf0\x13\xe09\xa2\x80L\x9dP\xfc@J\x83\xc6\xc8\x9a\xa8n\x89C\xf1\x95\x0b\xf6L\xce\x8d{\xca\x8b\xcaM\xc7T\xad~\x80\x95\xe3\xf0\xf6\xdc\"\xa3; \x10\xd3\xd1\x1f\x83\xc0,\xadUUB\"-hs\xec\x07\x02\x8d\x0dl\x10\xb6\xf39rb\x92\xf8\xdb@r\n[1\x1c&~b\xd4\x1f\x0c\xe68\xee\xe8!\xb0\x148\x122\xab\xa8\x1e\xdc\x03 \x11^\xa7\xa1\x84\xd8\xa6AxK\x0f\xc31\x0eI\xd8@3\xc0\x0c\xe1\xb7AX\xcb\xbe\x17j\xa1g\x9c\x08>\xaf\xd8a\xa6\xa8Q\xd7L\x9eO\x08\xa9\xa7\xaeArli\x0e\x154G\xb4\x9b\xd1<\xb9\xd0\xe6\xcf\x8a\x83\x03J} \x80\xa1<\x80/>\xef\xe3\x00I\x9b=\x86=\xb4\x8e\x8a X]\xec=\xb6\x80\x9a\x86\xe6JO\xbb\xcc\x15\x1fD\x07\x86\xdfdzkEN\xc2As\xa1\xc0\xd0\xd2d\xbfc!\xa9 \x9fZz\x1c\xaa$\x87F&\xa8SI6\x08W\xb4\xec9_\x9b6\x82E\x081\x03\x04\xb7\xa3\x9f\xc5\xbe\xdc\xf6\xce\x02%\xa5]IW\xae4jpY\x14\xa3\xce\xdc\x94\x10R4\x18\xcc\xdd/}\xcf\x174\x14\x02\x98\xd2\x03\xdb\xb63\x85&\xbf\x06\x1b\xf9\xba{v\x04\xc77\x9d\n\xbc\x00\x8a_\xba\xd8\x96<\xae\xeb\xdf\xe8\xa2\xa2\x0c\xecd\xc0a\x18N\x04\xc6\xd9U}^\x00\x9eV\x0d\xd6\xaf\xbf\x9fy\xd9`uV3\xca[\xffA;\x19&\xfaO\x17\xda1\xa0\xb6\xe4\xd1\x98\xf1I\x9fW5X?\x8d\xf7\xcd4y2\xdf\xda\n`:pW\xa1\x1a\x04\xdf\xed\xab\x0f\xbd*\xff\x90\x1c\x9f\xc6N\xa7Px\xf1\xe71\x9fK\xd9\x87\x91\xdeN[\xea\xb2\x80\xd6\xc1\x07v\xcbi\xc4\xcb\xb1\xb1\xfd\x8f\xda\xac\xed\x9c\xa2\x06\xc3\x04\x8ee\xfe\xd7Hfi'\xf4\x7f\xb9L\x86\xb5\x91\x067#\x895\x18,\xb9\xcf\xda8\xab\xeaR\x9b\xb6m\x89\x1a\xba\xfam\xdb\x91\xdc\xbf\xc3\xc8P\xd0\x00F\x16 AC\x97\x19\xaa\xea\xb4;\x7fa\xe0?=I\xfe2\x00\xf48\xf8lh$}6_\x89<\xfc~\x98\xa3\x93`\xfc\xe5M\xe8\x853\xe6\x86\x90\xf3\x81\xe7\xe7\xb5\x19\x1e3k\x06n2\xe49\xa2\xc3sr\xda \\D\xd1\xc7\x8a\xaf~\xa7xv6\x94\x9e/\x1fi\xfb\xba\x9e\xcf\x05\x03\xb4\xd6\xd5D\xbdj\x92?\\\xcdl\x16\xbd`\xe3\xb5\x80\x99\x88\x06\xf0\xbc\xda\x12\x03\xdc\xef[?\xe5SI\xa3\xb4p\xa7K\x89FX\xf1$\x9e\xe34\xe3\xe2wO\x0f\x87\xc7\xa2\x8c\xc4s\xba\xa3\x89HlP\xc7\x95\xb1\x80\xec\x98\xc3PW\xdd\xe1\xc8vi%\xf2\x97\xfc\xc0\xab\xf3\xfc\xb9\xcc\xaf\xc66\xb9g\x0e:5\xf7\xcc\x08\xea\xa1\xadL\x0e]\x8f{\xec\x18\x08\xe1\xf7\x0c'BT\xad\x8a;\x9e\xa7\xbf\xf2\x81:\xfaA\xbb\x07vn`\xe4W-\xd1\xa7\xb1\xd3Zj3o\xe9\xde\xb5z\xd2\xf5\x96Ppz\xc4\xb7\xa2q\xad\xfe\xd2\\\x0e:9\xd3\x10\x82\x0c\xbc\x95f\xdc[\x04\x1a\x14\x0e\xb6\xd4[U\x0d\xf7\x97\x81\x16U\xebz\x8b\xb0rs\x8c\x89\x1f A4\xa7+\xecp\xf2\xae\xad\x02<\x15\xb9\xb6a\xc5\xb1\xcc~\x92J\xe7P:\x8dC\xa5\x06\x037\x81\xf3\xf5\xae\xd1V8\x96k\x91L\xd2|\xa2'\x12M\x1dN~\xf1\x93\xa0m\xb1\xaeo\xfd$\xb0m\xf1A<9\\\xa4\xfd~/\x12\xac\x0e@\\~\xa9\xf54v\xa6\xa1\xf20n\xe7x\xab\xbe\xbb[\xaf\xd3}!\xf7W\x87\xe2\x14\xb5\xb3\xdft`Q2M\x02$\x8a\\\xe2\x9e\xa3\x80e\xad\xc3\x1b\xb6f\xb3\x19\x8af\xe0\xe4)U\xf4\x9d\xc9K[\xd3\x819=\xaf\x0c\xb6\x88\xd2\x12s\x12\xda\xb6\xa9.\x15\xf2 \x8e\xc9SwZ\xc5$\xe5\xe9\xe4q\xc1\x13\xc7\x9d\xf6\x9c\xf9Q\xa0\xc5;f\x1c\xfb\xb6\xa73T\x95\x18\x08\xec\x89\xec @\xa5\x90\x9bb\x00\xc8\xe4\xbc\xe2\x91\x9am\x9b\xaa:\xda3\xdd\xbej\xfar\xa7\x84\xc4\x9e\x92\x810\x89\x9d\xad<\xa5\xb3\xedm+\xf3n\xfdU`\xea\xc1\x85\x0cL\xb6\xfes\xe8'\x9c\xd3\xddbH;\xef\x8ba\xf2Z\xf5\xecK;1\xab7\x17\xad\xd2i\xc4\xa6\x19\x021\xf9 Z\x88S\xb0\x1aX\xe2;r\xb8 \x16\x03W\xb9\xc5\xd3%\xc2\xd9\x85L\x7fs\x18\x96\xf2\xae\xca\xb8#\xbeA\x8f\x8d}>M\x04\xec\xd6u8%\xe4V\x90\x1c\x87\x91\x10u\x90v\xa7\xb2\xbb\x99z\xe8\xe2`I\x95\x00o\x82u|\x93\xaeS\xe9\xe9\x1c\xf6\xc7\x9a\xaa\xb1\xa2\x1d\xf1\x0f\xcc\xa9\x98\xb3C8DR=vR\xf9%Q4r\xabi\x96g\x99\"U\x119\x84C\xff\x18\xc8\x99\xe6d6K{\x016\xccv\xb9n\xb7\xa7\xf9zdNz\xb3\xb2m\xd9\x0dx\x14t\xad\xd5\x13\xa7\xf3\x15\xd2a\x04\x14\x9d\xb5&\xf2\xb0(\x9d?\x97Uz\xd6\x95\xe5ZVc\x04\n\xd2\x0e5!\xe67\xa9m?uU\xa6\x02\xd1\xe0\xf8\x86\xcb\xd4V\xf5\xdc\xa6\x02YE\xcdN\xf3\xae\x9aBC\x0f;\x00\xfb\xd0w\x18i\xd5(\x86\xf3\xc9\xcd\xd2\x0c\x93\xa0\xd9\xac;Y&\xc3;PC/\xf1=\xb1\x96\x16.Il\xdb~\x80\x0fbgU\xe4\x16\x1f\x05\xaa\x01[Um\xae\xeb\x08\x94s\x87\xf0\x03y\x9c\x11)pT\xde\xca\xed\xc5J\xaa\xeb\xc5\n?\x91\xa3\xde\x93b]\xeed\xb8*ij\x90\xa0\xf5\xfd\x94\x90'\xdbV\xa1\xa52r\xf4\xef\x03\xb4\xbe\x9f\xcd$^\xb0\xed\x0c\x9dv\xad\xa3aA\xa8\xbf\x9b\xcd\x80d\x16\x8e\xd8x[\x84N\x8a_\xcb\x90\xd2\xc8\x8a6\x1e\xc9\x03jBP\x7f\x92i!\xaa\xb1\xed\xfd|\x8ec\xdb.uv\xc0D\xfb\x19\xb9\xc7\xa1m\x8b\x8e\xec\xfbm1\xd9V\xe1\x94\xf8\x00Mu'\xe0\xfb\x9b\xa5\xb2\xe1\xba\x9f\xcfQ\xe9\xdf\x07u}\x80\xbf\x8e\xf8!_K\xb3\x88\x14\xa1\xf5A \x92\x03j4vH\xf1\x01\xe1;\xdb\x16H\xf9\xd0\xae\x8em\xef\xdb\xc8$\x02\xf0z&\x06N\xda\x1d\xe1\xcb\xb1\xe1[R!\\6-9\x04} rc\x9doK\x12i^\x95f\xe3\xbc\xb6\xb2Q\x80X%/\x0dN{\x1a\xa3\x93<;K\xc0\xe1\xaf\x83'mN4\x9f\xa3\x98<1\x87\xf9a\x80p\xec\x1f\x03\xaf\xb5Bp\xb9~Z\xc7\xe4\xa5C\xf1\x07A\xe1\x04\xdd\x8b\xdb\x83vB\x0d\x03\xe7\xb4;\x82\xef\xbb\xe5\x02\x1d\x15]5bB\x90\xb1\xb0J\xb6MqA\xc4l&\x0e%y\xdb\x8c\xa0-\xd2\x86\x8a\xc3\xb9\x81`\xb1\xb4\x8f\x1e\xac\xe1-)\x04\x0fU\x18'\xa4\x08kzv\xf3\xdc\xb6\xado_\x89\xdd\xed\xdc\xc1\x01\x03R\xe2u\xebT }RL\x07\xac\xbd\xd8!-\x9a\xb9\x85\x93m@3`\xf4G:'\x04\xe7N\xe3,SLPjL\x86D\x7f\x91\xbf\x0c\xf0\xb45B\xe3\xeb\x1c\x8e~{\xda9\xdc\xa1\x8c[-\x08H\xc6Z\x8f\xb3I\xc9/\x0b\xd3i\\\xbb\x1fzKw@\xba\xd3\xf9\x1c\xfa)F\x9b\x06\xd8\x18HF\xeez\xe8R\xf0\xba;\"\x07\xe3g@Yc\xb2\xfb\xe8\x98\xb4\xdf\xe3\xad&o\xe3\xfe\x8f\xca\xdb\xf3V\x13\xdc\x14\xaf\xc4 \xb56\x1e\x10\xe2-\xc2S:\xf4/\x15\xd0\x82\xb9>\x91Q\xf0\xe5\xe4u\xbdu(.\x10rb0@\xc2\x1c\xff\x8e\x07&\xc2\xbc\xc1\xa6Y\x0e9jo>\x0bi\xa3\x1ce\xde\n\xae\x17G|n\xdaC\xa6\xd3\x0c\xef\x1c\x84\xfb\x16\x8f\x17\\^V\x1f1 \x1d\xf7j\x1c1\xd5n\x19\xf7\xbe\x01\xfe\xb6\xe41\xf9\xe4O\xd2\xfc\xde\xc2\xd6\x9f\xa4\xa2\xa8\xd3\xd1\x0d4D\"\xbf\x90T\xeb\xfa\x8eI}Q\x0d\xba\xd1-O\x93mU?\xa6Q\xb5\xb5\xf0\xb8fg\x12z\xd2\xa0\xcb\x1dZna\xab=B\xed\xeb\x9b\xbc\x95\xfb\\z1u\xb6_g6\xcd\xa3C\x03\xc5\xd85\xb8\x00\x18\x83\xe9\x1b\x98\xc3N\xb0 D\x9f\xf5;\xe3\x96Y\xdb\x81\xab\x92\x97\xc6Y\xd7J17\xbd\xac\x98\xeb\xe6B\xbb\x9e\x81a\xd2\xa5\x85SA\xb8\x06\xdd\xea\xcc\xd2U\xcf\xbe;\xeb\x13\xc8M\xeb\xb3\x15\xf0Y\x00\x9aZo0\xe3\xae\x13]\xb4\xa1\x8b\x0c\x1b\xba\xc8\xb4\xa1C8a\x8dC\xd1:\x87=O*\x88\x98\xb8/I\xd5YO\xa9$\xdfr-\x19\x81q_\xb6\xda\xa0\\\xd12R\x19D\x0d\xe72\x00]\xa5\xed\xd0 \xdc\xd3/o^\xbf*BR\xc9G\x9cw&\x90U\xfb\x08\xd6\x89G\xdd\x08 \x9d\x1e\x8e\xc3\x0f\xe4\xfa\xfd\x0b\x08%\xb19\\m\xae\xbd\x1b\xc7s_l\xae7\xab\x9b\x1a=\xbb\xc6\x8f\xe4\xfa\xfd\xc2\x7f\xef\xfei\xe3o\x168\xb8zv\xdd)2\x9e\xf4\xbc\xa6\xb1\xd3\x0b?\xc5\xdas\x95|\x91\x94|\xdf3\x18\x11\x0c\xb3\x12s\xa7:\"\x1b\x8e0\x98T\x86\x0d\x90!vf\x0d:RO\x9f\x8d\xef\n\x9fG\xcb\x82\x0e>\xb6v\xabm\x95\x92YV\xb1\xd0\xd6\x8c\x98)-\x13\xf1\xb1\x96\xbb`j\xe8\x86,e\x17\x1a]\xcb\x85\xc8\x19\xcc_\x06\xc6\xb9\x8eC\x89\xe5\xe6E\xe5\x80\xa1\x0d\xb2\x10\x96\xea\x0d\x8d\xc4\xc1|\xa2\x93!$P\x0dMu\xc1\x9e\xc5\xf3\xa3\xc0\xf5\x03\xb7\x9f\xc5\xa1X\x8d\x80\x8d\x8d\xa0\xef&\x0dAn\x8d\x80\xac\xce \x0c\xf6\xccrJ*\xeb\x05\x9bU\xec\x91H2\x17\xa0\xb3\xe9\xd1S>\x0c\xc0\xebP\xa4\xa7\xbcmC\x8a$\x8c,[-\x18@W\xeba\xc0}\x16`\x08@\xdc\xa9f\x94b\xd0,$g\xc1\xa1\x18\xf2w\xca\xe3\x88\x9c\x05\xc1]yz\xcf9\x11r#\x04\nY\xc5~Af\xfd\xe6\xf5\xdef`\xc7\xe7R\x1c\xb5f`#\x13\xf5|)\xd4\xc2\xe9z\xe0\xfe\xaa\x83\x8f\xe0iX\xd7\xd3\xd0\x17\xd95K8eu\xcd\x162\xa0\xb6\xe7\xb0\xba\xfe\x80\x14| \xf7,\x8a1k\xbf\x81vG\x071a\x84M\xd2\xfcP\xd1<\x14]\xce=\xb1\x93]\x86\xcdH\xd78_@\xc8WAx\xa1$f\xda\xec\x08\xb6\xefH\xac\x96\x0cV\x13?(\x0bF\xd1\xday\xc8^&#\x07\x87\x934\x9f0\xd4\xc3\xb5*r3\xf2\xd4\x83\x145\xe4\xa8\xc0\xd6-\xc4\x90\xb26\xd6\xa0i7C6t\xd0\x0d\xfd\xe7\x01\xc2\x11\xb8Z\x1a\xc7\x9d\x8e\xb1\xdf\xc9\n\xf6\x9f\x90\x07\"\x84+#N7\xc9po\x8b\x10\x8a\xcd\xe6\x0cO$\xa7WLW'\xb3\xf7\x9bA\xee0\xb2\xe1\x98\xd9\xe0\x87E\xc9i\xf4\xc1S\xbf\x00\xddN\x8e\\\xa75\x9b\xa7m\xaf\xf4h\xba^\xb6\x8f\xfd\xd1P\xfd\x84\xc4\"\xeb\xc0\x9e\x0eU\xe8\xa7Y\xbf4Nv\xc4F\xc0\x1fH.Dk\x01\xfc_\xc8-$'\xf1P\xefK\xfe\xe0x\xee?\xf2*\xcdjp\x10\xbd\xc6_\x92\x13\xd8]\x95<\x87C*iyq\x10\xcf9\x7f\x82\x83&Q\xcc\x9d.\x9bu\x87\x92\xa3\xb4<3x\x94\xa4\x05\xb0p;\xe0P\x9b)P8D@2\x9cO\xb7\x06Z\x95n\xa4(5\x02\xe0\xe5\xf4\xe0\x84\xda\xdaI\x89\xac\x1dQ\x8c\x1a|\x90\xa7\xd4\x03\xe3\xa6\xee8\xd1\x0fF\x8e\xb4\x87Q9\xe8\x14\x0e~C]\x7f\xa7\xb1\x86(\x91\x06!\xda\xd2\xc3\xc8\xe9r\xae\x17\xc3\x14\xbdM\x04:N]\x1e 2\x8b \x16tM\xcf(\x0cld\xe6\xd3\xc0\xa40\x0d\x0e\xb3\xe2\xc0\xcd\xd0\xec\xfd\x01+o'\x03\x80q,V$!-\xd6\xad\xebs\xd2\xe8A=],u\x007w\xd9\xea\xf3a\xdb\xcb\x0d\x12\x05\xeb\xd0\xb6C1e\xeb\xa1\x9fR\xectn\xd2/V+\xdbv\x12/\x91f+\xcaBt\xe8J}\x81\x93\x80\x90\xb2\xe8\x14\xb7'\xc0\xbd\xd3\xae\x01Q\x8a\xbb\xa3\x8f\x96\x84\xc6\xc8\x8d\xc1z \xe2O\xa3f\x14\xdeHX[\xc5H\xe5\xe0\x8b#\x91\x01\xd2\xa1j%\xdd\xd3\xc8[\xd0\x06Wam \x13\xea\xc1\x0c%\xa0V\xbe<\x08\x11_\xec\xa0\x97Y\xe6h\"\xe7\xceW\x0d\xa6Q4X\xc7\x0b\x1c\x8a\x1eU\xef:\x83\x84W\x0e\xc2\xb2 \x84D\xa5Q\xf4\xc5\xf0\x1a\x04\xb3B\x1aE\x8e\x8e\x1c\\\xf5\xa3\xe8\xbb\x83w\x0d\xb1\x14\xa1\xc6\x8cQ\xfaJvs\xb8\xa5W\xfd-\xdd\x1d\x91\xab\x10\xab\xa7\x11\x0b\x11m\x98q\xee\xf2\xc9\x94[\x96I\xb1\x94g\x90\xc2ccC\xcc\x17QZ:\x14\x9bG\x8e\xa8-\x01\x18\xef\x0ca}\xa4(\xc4%\xce\x87\x96\x1e\xaa\x80\x98\x86\x9e\xc1,j$\x8e\xbc\x94wh\x18\xac\xea~\x99e\x1f\x1d\xcaH\x13\xbfW\xe4BK\x7fl\xfcf{0\x01\xa2\xb6?V\xf4\xcc\xf4Y\x14W\xd8\xf9\xc2z\xa9\xaf\x8ec\x02A]\x9f\x1adH\xfdB\x06\xc2-}\xfah=\xa6\xb2@\x14\xd2\x84ll\xfb/\xd4\xd7\x8e\x07\xd2{\xcb\x0f05#\xf65\xcd \xac\xaf \x08>\x0d:N\xd38|1.\xf3`\xdd\xd1\xa7\x05S\xd8\xb3T\xff\x1c\xc1 c\x08L\xce\x19*\x8a\xe0\xa8\xb1\x15\x04\xe1t\xd3dJn\x04j\xfd\x12,v[\xdc\xc0\x11\xfe\xa2\xd3\x93\xf1E\xc9\x1fx \x16Bx\x80N8\xd2,\xf6W\xe4z\xf3vv\x9d\xe0\xaf\xc9\xc90\x0d\xf8k\xb7?\xbf\x16#=\xb5Jk\xb5\x9f\xa9D\xd8\xceW\xa0\xf74\xe7'D'\xc1\xe7\x11A\xac0k\xf2\xc5\x974\xcb\x18\x0d\xef\x0e=\x9f5JF\xd0\xef\xd70 \xd1\xb8\xdbQ\xdc\x06+\xdf\xc6^\xd8aA\xd2R2\xa5\x8b\"\x0f9\x1c|\xdcv\xf5gZx\xa3\x8b\x1d\xdf\x15\xe5\x07\xdb\xcepH\xa6K\x9c\x10^\xd7K\xe5\xe7\xb7\xed\x04\xc7\xe9r\xbd\xb5\xed\xf8&Y'\x92\x0eo\xfdD\x9fYe\xfe2\xc0\x99`\x88\xc1r\x10|\xfe\xaab\xffC\xfe5\xcd\x0e\x1c\x9d\x18\x99\xae\x14y\x8a\xc8t\x85\xb7\xb6\xed\xa4^\xda\x8a\xce\xb7N\xaa\x15\xbd\xc8e\x9e\xe8\xba{\xa7\x0d\x0c\xc1~\xeb\x8e\x9cz\x94@\x86(\xd4gC\xba\x9b\xeb\xf6\xb2\xa6I\xe2\x00\x18\xc2R\xb0\xc1\xad1\x92\x07SQ\xa3C\xb4\xee\xc5\x8b\x8e<\xaa\xc0\xc5\xb6\xef\x16[*\x98\xab\xba\xdej*\xeb\x82\x85\xa7\xeex\xc7%\x90\xc8\xb6\x13'D\x0dj\x8c(\xdc8\xf2\xbaIt\x19\x80l\x88o!\x0c\xa5\xc9\xe5c\x19\x8a\xcd\x1c\x9f\xfa\xbc\x15\xf4_\x02T\x1b\x1c\xbc\xbf\xd7`\n4\x07\x19\x12!\xe3I\xee\x97\xe1-\x0e\x91`)\xd0V\xab\x9eC\xbc\x82\xed\xe4\xc47$\xb4\xedx>\xc7 <%\xf39j\xe46h\xf0\x90\x85k\x19\x82\xaer\x8a\xb7\xc0\xacL\x1d\x15\x12R\xab\xe6\x876\x94\xddHt\x04y\xd9\x86Z\xdd\xd1\x8c)a\x8a;\xeeg\x1e\xb9\xebf\xbampV\x98\x14\xbd\xad'\xd5u\xb0\xba6\xa0IU)\n\x8dV\x98\x82\xb5 \xff9\xad\xc6o1\x12\xe3\x0dm{\x9a\xc2q\x12\xdc\x13\x80\x19\xf1)f\x12qy\xea\xd7A.\x0bp\xe4\xa9s<\x86\\Xw\xd5\xbchb\xa4\xcfw\x0b\xdd\xf6\xf0N\"\xa3\xdcX\xaf\xa7a\xd3b\xa0;\xf3\xee\x97W<\xe6e9j\x98\xeb\xfbV\xc9\x0fE\xf6\xc0-lEE\xce-l #\xc7\x12\x88c\"\xd1\x83\x85\xb0\xce\x1bY\x01\x16\x05!:%\xb6b\x9af\xbfW\xee\x16ll\xa1\\^Ti\xfc\xc1\x12\xc4\xb0HJ~8\x0c\xca\xeabA\x80Cb\xedy\x1e\x01\xa5\x8c\xc8\xe9P\xd1jl\xca\xc2\x06\xd3\xec\x91~8\x8c|\xe3\x0b1,cC.Dw\x9d\xb3Y\xad\xb6&\xe1\x94\xf3c\\\x08\xd4\xa1v=\x9bF\x80\xbcq4\xa3\x8e\xf9\x12\xd2\x97\x89%/(~\xd6\xdc\x8f\xfdU\x108g\xcd&\xb6\x9d\x8c\xdfK\xb5\xa6R\xe1\xd0\xd5'\xc4\xda]z\xe0\xc8k\x1f\x1d$\x07\x1d.\xd4\x82\xa9A\x8bw\xb1\x10\x82\xd1\x96S\x0f\x92\x88X\x0e\xe4\x86~\xec/\x83\x99% \xcf\n\xa0]\xc0\x85aW\xab\xbc\x13.\x91\x17[\xb4]j\x04\xd6\xa02r$\xea2\x0b\xb6\x08\x1eG\xd9\x11u\x87Y\x0b\xa4\x14G\xc8\x8d\x9a\x06s\x83\x8eF\x8b}\nv\x98bq\xf0\xf9\x1c\xd3n\x8ec\xffy\x80\xb7\x10\xbdu\x1d\xc9i%\x89\xe0\xe5\x05\xa5\x81\x07s\x96C\xb2m0\xf3W\xefi\xe0?\x0f4b\xc0\xcc\x7f\x0e\xef\x021 \xccaB\x82\x91;\x18\xf8\xe8Tq/r\xc7wl??I\xda\x1d\x0e\x81\xc2\xf4\x8cq\xb8\xf7F]d\xc61\x87\x13\xbb\xc7-\x1f\xb3\xd4^\xe2\xf0\xec\xc6\x08\x04\x97\xf1\xb5\xe2\xac\x90\x0dx]\x7f\x04^\xb8+h=8\xb9{\xd45 \x1b\x89\x994\xf0\xde\x98\x9b\xa6\xa0\xe9\x82\xeb\x81\x11\x87\xe2ix\xb9\xd7\xcd\xca;\xeb\xa2\xcb\xc1W#\xf5\x12\x05w\x80\xe7D\x03\xee|\x1e\xd7u\xa2!\xb6Mo\x1ai5\x0b'\xde7+\x90\xb1S\xb0\xbc\x95T\x88#|\xdb\x7f\xbd\xeb\xbd\xae\xb9R2\x87>\x0b\x06s!\x92\xba\xe90\xdf\xf4\x0e\x12}\xb8\x13$Tn\xa0\xe4|\x03\x89\x1c\xb78E0\x00\x0d\xb8g#\x11u\xe0\xc4\xd8\x1c\x8a\xb5\xfc\x06\xae\x12\x94z02\xb6O\xe4\xa7a\xb7\xa8\x86\xac\x0e\xc9\xb7wu\xad0\x14\xf9\x99\xa6\x95\xbb\xc2\xdb\"\x8b\xe4\x87\x1ec\xe9\xa9\x8aE\xae\xd9\xccUo\xcet\x89\x1aY\xbc\x97\x1d\xc2\xc6L\x97\xde|n\x14\x03U\x1fT\x8d\xea\xdai_ \x92\xedTd\xb7\xed^\xfe\x9be];\xdf\xf4f%\xc3~\x1e(\xddQU\xa6I\xc2U\xf8\x81\xd2\xb6\x1d\xc1\xa1\x0eR\x1d\x0bj\xb3\x84<\x9f\xa1E\x11\xc7m\n\x1a\x88\xe0\xdf:\xe8\x94\xa9\xf0\xb6\x83x\x97\xaf~x\xa3\xfc\xa6^\x174\xe2\x91\x85\xbf\xc5\xd3\x15\xc2t<\xbb\x8cv)\xb3\xe8yB\xcd`a\xba\xb5\xeb\xd4\x13\xdf\x88\xf1\x92\xde\xb6\xb2\xc2b\xb7\xcfx\x05G\xc6\x99\xac\xe2\xad j\xde\x81W\xef\xd2\x1d/\x8e\x95\xa3\xaaF\xae\x93\x8dD\xeb\xbc\xdc\xfb\xf3\xbcF\xd7\x11\xc2\xdf\xb4P\xc4\xe0\xe4h\x00Y\x00\x8f\x7f\x03g\xa3\x90\x1f\x0e\x03\x04\xa0\x85\ne3 \x91n\x06j\xdf\xe2\xd0\xdf\x06x\xba\x84\x16Z\xbf\xd3\xde\xedv\xa2\x82\xfe\xbd9\x91\xbc\xdfo\xba\x043V'\xf1\x9c\xee\x84R\x872F\xaesK\x18>\x8f\x8c\xaf\xd6\xe8\xd6\xd0e\x85\x10\xf8Li\xef[3Z\x06N\x848\xc4\x89\x17\xe9+5!e\x8b\xf5'\xd4\x19\x07r\x8f\xba\xb7\x9e\xee\x07rS\x8fA\xb8\x02\x81\xe0\xe2f-G\xbd\xaf^\xd1\x8a\x8em\xf7\xbe\xcaU\x06\"1\xdf\xa73\xe3\xfc\xaf\x03\xf6\xef\x1ctR\x1a)\xa9lo=DA\xa6\x85\xe8\x1f\xe4\xd4\xe0%>\xf5\xdc\xdat\xbb\xa7\xa6Q\x8c\xffB\xdd\x13(\xcf\xa0\xc5\xd3\xec\xbb\x05\xdc\x15\xd4\xc0/Y\xe1\xef\xd4\x18\x0e\xc4\x1c\x0d\xfe\xce\xbc\x0d\xf5\x8e\xf7\x11H\x1a;\xd3\xb6\x9cC\xdbc]\x19|\x8a\x89\xbe\x85\x84\xfaf\x0f\x02e\xc0\x7f\n\x89\xea\x01\x84\xbcf\xfdLD\xbb\x054xl\x02R8Sm\x83cG\xe8\xac|\x88\x0d\x16\x84\xa1\xa6\xa7<\x85\x89\xf3\xc3\xa0\xae\x9d\xde;X\xac\x87\x0d>\xf0\xeaL\xe3\xf3\xa0\xee\xca\x82\x02w\xfc\x83\x80\xac\x98\x18\xc5yp\xe1\xe0;\xf6Y@\xc2\xb5\x86~\x01\xeb\xc6\x15\x7fN\x8cP\xdb\xd5^u\x98\x19\xf7\\E\xf2\xd4)\xf6\xa3\xfeM_\xf1\xf0:\xd9\xce\xac\xd2\xa8\xcc\xe8t[R\xeeB\x19\x04\xc1\x154\xb2\xc1r\xeb\x8e\x0f\xfd\xbcX]\xb3\x11\xe5\x0d\xb3\xed6K\xe89\xea\xa87\xe12\xba\x13\xee\xf6\xbe\xe2\xaa\xd4\xa7|\xd1\xde\xce\x07a\xad\\}\xc9\xe3\xeb\xabMSo|\xfd\x1c\xa0g\xd7\xf8\x07r\xed\xf8/\xe7\xff\x13\xa0\xeb\xa4\xc3{?\xf6!\xcf\\\xab\xb3\xa8\xf2\xf2VA+\xa2\x15\x9d[\xb3.\xb2\xda\x0f\xd8\x9a?[YC\x07\xe6ph\xb2\x14\xa1\xf3S\xfa\x10\x9d\x04N\n\x89U\x95G\xa0\xe9!\\#\x16\xd3\xec\xa0_W\xae%\x08\x91|\x83\xe08\xb3pf\xc9\xd7Y\xe8~\xaf\x83\xe1x\xea4\xfaoo\x7f\xf8\x1e\x94LFH\xb57\x06|K\xea\x18\x92~\xc0\xb1\xb0\xe9\x9d\xbe]X\xf8\xc9\x9b\x85\xfa\x08\x07\\\xaf\x8d\xb7\x06G\xfd2=2\xf9\xc6\xa4\xd6!\xfa?zS\xbd\x1a\x96A\xa77\x8a\x8b\x92\xe8\x14\xff\xfbc\xd5\xbe\xeeW\xdb\xe0\x7f_\xac\xf6u\xaf\xda\xe1\x81\xe3Y#\xe7\x1b\\\x10\xe1\x84\xc4\xb6\x1d\x1b&y=\x90\x91t\xaa\xea\xf4\xc7\xc0u\xbc\x01\xc4\x13KS\x9f\xd88\x93\x9b\xbe\x96_\xb0\xa5fQ\xc0\xcaAp\xa2\xa7\xb0\xf5e16h\xe2\x87\xd2_F<,r\xba\xe3x \xf6Bm\xc8# \x9bR\xe5m\"\x89Hi\x93>G\x08\xff(\xddx\xfc(\x10\\\xc7k\x00\x8ca\x17\xb0`\xe35C\xa2\x1e\xce\xefr\x95\x87[ R\x1b\x0c\x81\x045)\xc6\xa2\x06\xb9\x7f\x1b\xde\xb9\xdd\x99Y\x9b]\x94\xe6\x17\xb1\x81\xd3\x99\xb2\x12Q\xf3\x87\xa9\x89\x99[O\xfeu/O\xf4\x91\x07\xf74Gt\x84(#\x06\x1d+\xd4,\x8b\xb0\"a1\x1c^`\xae\xe5\x89\x04|\xd9\xa6\x91m\x03\x02\x01\xfd6(q\x1c\x81\xb4\xba\xbe\x8e\xf2WLM?\xe4\xb0\xd6-\xee\x93\x0b\x11\x02\"n\xa7<\xc4'\xa9=\xbf\xacN\x1d*\xb3\x0c\xd4\xe8\xb3v\xa9\xc3@\xde\xb31D\x95c\xab$\xfb\xf9\\\x9f\xc3\x9d\xd9=\x80\x81>\xc5\x94\x88 \xc4\x02\x99\x9d\xc1\xfc\x8b\xd0\xd3k\xab\xb1m\x1b\x8b\x1dxG\x91\xea~dw\x9a\xa5a\x83\xad\xfbP\xa0\xf6\x02VkHm\xdb\\9\xb1E\xc1\xc2\xa0\x03\x8dn\xf3\x8c\xc0\xe6\xc77\xe0h%a\xc6i\xf9\xf7\x8f\xd6\xa3`RB<\x86\xdb\xf9\xcfu\xa0\xa6\xa7\xca\n\xf3\xbe\x82@\xd2*\xcd\x85\xaa\xcd\xb15a\x7f>\x8f\xea\x9a\xf7\xd4(1\xf6\xe3@l\x83\xcb\x0b\xa7\xf1&%\xaaw\xe6e-\n)\xc4~\x12`\xda\x03V@\x12\xa1\x84wA\x96f3\xac\xde\x00\x08\x0d\x9f\xd1\xad\x8307\x15\x0cJ\xc1\xf5wr\xed\xcf\xe6\x81'\xd8\xbb\xe8j\xb3\xa8\xd1&\x9a9\x9e\xeb\xf3\xaf\x02\xf8\xb0\x89f5\xbaVW\xf9\xe0\x9f\x88o\xbd+\xf6\x16\xb6~J\x93mea\xeb\x8b\xa2\xaa\x8a\x9d\x85\xad\xd7<\xae\xac\x00\xbf\x1d\x0f\xc1K\x05\xa2\xa1\xd8\xca\x8b\x9cK\xd5C\x08\xfb\xc9\x8a\xd2\xc3>\xa3\x1f,T\xd7S\xc3nh\x10%\x16\xce\xcf\xdfI6T\xc7\x1f\xa8!\"\x01zv\x9dv\x87\x8a\xad\x86^_?\xae+\xf8\xba\xa4 82 \xccH?>\xfc\xd9M\xe5\xd2\xd5\x01\x87\xe7w\x98\xeb\x00\xf4\xe1x\x00z\xe8\x90\x05\xde\x17\xfd8{*H>\xb6\xbap\xf9\xc3<*(\xbd\xbc \xdd\xec^\x88\xf0\x9d\x0c\xb8\xf2eV\xe4\\\x88/\xe2\x17\xac\xe7\xa7K4xkC\x9a\xe9\x18-\x98\xf5\xdc\x17\xf4\x95\x897O/\xae\xdbg\x0b\xdf-\xf2\x02\xaa\xffR\x96\"`N~\xa1\xe6\x9e_\x81RL\xfd\xc3\xbc\xabc}\xb7\x80\x809i\xfe\xc5\x91\xb1\x8c\x1f\x88U\xe4*\xc5J\xf3 \x85\"\xff$\xd7\xef\xef\xf8\x87k\xfc\xb3\\\xd7]q<\xf0z_\xa4y\xc5\xcbZ\x99o\xedx~Du\x98\xa5\xe1\xdd5\xfeEfT5\xc9+h\xe1oq\xacXv,\x85$\x02\xf7D\xfa\xef\x17\xc1\x15\\5\xb9p\x163T#\xd3\xd2\xfe\x7f\xcc\xa0\x90m\xea\xb3.\xd5\xb8_\xea\xdf\x8e\x14!\x14\x14g\xfdH?J\x04\xa0\xe8\xd4\x08\xfe\xfe\x81\xe7\x159\xc9\xbb\xf4\xdd\xd3\xb9aT/\x1c\xe0\xe0\xdaau\xe9\xb0\xa6\xff\xc0\xae\x95\xe8\x14.\xb6\xad\xc25&!\x0eI\xacS0'\x9dk\x1b\\9\x03wG;\xf2\x81\xe8;\xa4\x11vRR\xca\xce\x1dP]\x1bo\xd2\xb1?!\xa5\xaaR\xea\xeb\xf4\xdb\x98\xcaT\xe1\xad|J\xc8?\xe0\xd4[\xd4\xa3\x95\xc0<\x02k\xa7J\x9a\xd6\xcbObw\x83\x9b\x84\xbe\xe4\xd18jP\x9c\x89\xd8\x90\xc0 Y\xc8\x14\xa4-+\xc0\xb7C\x17\xc0\xdb\xf9\x1cm\xc9\xbf\xa4u2\xf3o\x03i\xb9Q\x90{\xb2\xf5W\x01\xde\x13g\xeb?W\xc1c\x95\xf7\xd4B\xbbO!\\\xc0\x95\xcb\xbao\xe0sB3\xbf\x00!\x1a\x17\xc4\xe1^\xb6\x10\x1cFB+\x10\"\xdcl\xc1\xd2<\x02\xf1\xb4\xae\x0b|\xb1\xec\x1d\xe9\x08\xb8\x18\xbf[\xe0\xa2L\x13\xa8\xe3^Jn\x11V\xeb\xe6\x86\xf2\xbar\xb9NX\xaf\xa0\xcb\xfbw\xac\x82U\xe1\x05?\x13)\x93r\x84\x05\xd28\xeci\xc8\xdd\xbdr\x0d[X\xa8\xc11\xc2\xce\x8e\xa4~!\xe6G=\x11?\xc0\xbbvt_\x16\xc7\xbc\"K\x9c ,t\xdc\xdb\xb6z\xe8\\H\xf68ASB\xa6\xab\xba>W]\xdb\xf6\x88:\xbb\xc0 h\xb21\xa8\xc5\xc5T\x8b_]\xe3\x9d@cj\n4\xa8\xf6\x13\x88\x9c\x11\x840\xf7v\xda\x1ab\xd0\xe5\xd9\x0c/\xf1\x1dr\x95c\xf2\x1d\xb0\xdd\xb0\"r\xdb\x89\x81\n\xb6~T\xab\xf4\xc7\xf6_'s\xdbvo7\xda\xb6\xb9\x93\x04\xef\xfc\x9f\x80-\xdc\xd7\xf9\xbf\x87\\t\xfa\x18\xe0F\xbf\x03\xb8\x12\x04\xa0\xc5-D\xe8\xe8\xdd\x9aa9\xef\xeb\xcdf\x81\xac\x99\x86\xa2\xcdf\xe1x\xee\xe2j#\x18\x02!\x9f8\xe2\xe9\x19\xb2\x80\xd1'\xbb\xfe\xf0\xe2\xf9\x1c\xdd\x91\x9d\x1f\x07x\xca\xa5C\xf3\xddB\xc3?Xb\xc8\x95\x85t\xb9\xf4[\xdb\x9en%\x18\xdf-Z(Fu\x1d\xd9\xb6\xccg\x18\x98[WW\x96\xbc\x0ew\xda\xa5\x03dk(\x89\xf1J\x80WWf\x006\xf39\xd6\x87M\x02\xd4\xe5S\x07\x99h\x9d\xd8\xf6t\xd7\xa9\x15\xb2E\xc5i\x19\x15\x8f\xb9\xc8\xae\x9fu\x81=n1\xa6\xda!\xb9y4\xe5P\\t9\xb4\xc8\x02\xbb\xb1i\xf5\xc0\xc5$\xcd')\xd2K\xdaJ\x08\xc5L\x00\x07@\xeat)x\xec\xbe&0\x05\xed\x83\xacQ7\x81\x0d\x01\xc3\x92\xd0iA\xb67\xc1\x86\x12\xf8\xa5\xe3\xb4\xd8\xf7\xd3b\x91\x17\x92\xe1\xb3m\xd0a\xff\x9c\xe6Q\xf1\xe8DH\x9a2\xa6\xa4\xe8a\xa9\xba\xbe\xc7j\xc5\xd3\xd9\xbd\xe4>\x12\xd3\x12\x7f\x9d\xac\x07){\x89\xf7\x13\x84\xb7$Yo !N4t\x03\x82\x88\x0b*h\x8ey\xfd\"\x98\x08BU\xb2_uMQ\x13\xb7a\x17\x9c\x84\xec\xe5=\x07\xb6=\x15\x0b\xfccY\xeci\x02\x01$\xdfV\xc5~/\x04@\xa4n\xed\x8coV^\xea\x16-\x96\x15C\xd9\x11G\xd2\x8b\xa4\xdb\x86\xa0\x9e\xf7e\xa1@\xd3\x93\x04[r\xe7Z\x08\xef\x04\xae\xd2\xd1[B\x84w\xe4\xce\xb6\x13\xff.0\xbe\x08N\xa0;\xc9s\x12\x88\xc5\xae\xd7\xbfW\xbaM\x956\x9e\x0cl\xe1y^\xbd\x92\xb3\xe0\x18QR\xe48\xee\xc5:\x8b\xc1\xaa\x1c?\xca\xfcb\xa8b\xcd\xff\xad\xa6\xcf\xb6\xbbg\xd5\xde^F\\\xd5`\x00\xd2\x9b\xd1\xc9\x08\xd5\xf5\xdd\xc0\x9c#\xf2\xefar{\xa0\x01\x11\xd1\"1`\x81x\xc5\x83<\x18\xc6g(\x80\xdccQ\x833\xf6I\xed\x82\xb6\x8a-\xea&\xa3\x81\xe3\x11`C\xfb\xe7#-\xe8\xc7\xe9\x93c\xd8\xe7\xf6\xacs\xcf\xadxn\xf5:\x83*`\xb0\xd42\x16b\xa0n\x07\x1f\xee\xad\xf6\xeb\xa9\x911\xb2\xc0O\x8bv\xbbBn\\\xa8xz'V\xef\x95\xeay]\xf7^Mo\x0e\xb5\x0fO\xdb\xb69\xc5U\x1d\xcc\\\xf8V0\xda-\xb0\xc7d\xeb3\x05\xec\xf4\x12\xb0\x9f\xe8\"<\x96b\xc7\xa8\x8e\xc5\x0b\x9e\xf1\x1d\x0e\xcdM\xd3\n$\x07\xb8\x8c]W\xf8\xedn\xc7\xa3\x94V|\xb4fgJ{\x88Q0\x99\xe6\xbb\xbe\n\xb0c\x0d \\\xb0j\xea\x07vK\x121m\xb4\xa2$\x81\x1f\xcc\x89\xe3\x0c\xa7;i\xb9\x10yN\xa6\x8a\xd7u\xa2\xfb\x8c\x144\xab\x81\xa5\x86\xe6\x9aC\x83j?qm6\x0d\xf6Y\xfd=\x85\xa5)\xb51N\x07u\xc6\xc3w\x8b}q\xa8\xf4\xba\xd9v\xff\xbd\xb7\x8e\x98v\x10\xab\xe7\xf4\xf2\xa9\x89\x0e3\xc6\xfa\xac\x0e\xd8}H\x02 @lk\xdb\xa9Aa\xc5\xbc\xcb(\xa0um\x81T-\xed\xf9\x05Xj\xc3\x8a)Q\xde\xc7$\xedy4\x88D\xc1\xc6\xa6m\\Y0#\x1a\xa9\x08\xb0}$\x83J/\xd7[\x88\x849C1a~\x18\xf4\xc4\xd6\x995\xb1p\xab\x84\x8c|\x0e'0>\x0fH\xdc\x13x\xbc\xdc\xe1\xd27M\xb9a\xa5\x82\xfc+\xefl\xf5I]e\x94\xb6W\x19!,+lc\xc4\xac\xa3\x96\xc3S\x11\xd6Nb\xd5\xdd\xb4\x9b\xed\xa8i\x17n\xfb\x82\x8dg\x87\xc6\xda\x12\xda\x94x\x8b\x1a\x84\x13P(\xee\x0f\xaeE\xb3\xea;\xfea\xc2\xa4\xfec\x12\xd2<\xe4\x99\x98\xb4IX\x95\x99\xf8\xd4\xdbY\x13\x00\xa9\x1f\xb7\xf4\xc0';^Q\x91\x01\xc2\xa0\xf0He\x00\xaeF$\xcb\xb5\x9dT\xe9\x8e\xbf\xad\xe8n?yH\xf9\xe3\xe4q\x9b\x86[K\xb3B\x13\x0b\xe18}\x92:o!\x9f\xf2\x0f\xeaYu/\xdc\n@\xda\xd2\xf2\xcb\"\xe2\x93;\xfeA\xfc\x17\xcf\x83*\x06\x9e\xd8\x86BO\xc7\x8d\x80\x86a_\xc0\x93\x0es\xb7\xd0\x95{\xdd\xa3\xcb\x16\xaa\x15\x84i\xd3`P\xf8\xf4\xfb%as\"\x7f\x0e\x930Ky^\xfd\xa2~\xff5)\xe2\xf8\xc0\xab_\xd4\xef\xbf&{\x9a\xf0_\xe0\xef\xbf&\x87\xb0\xe4<\xffE\xfd\xfekR\x15JK\xf3\xfbC2\xcf\"\x99\xda\x1f\xeb\xc18\xa1)\x1d/\x8a-T\xcf \"\xa7\xdeog\x8c\x08\x9c\xa0\x9c_\n\x1d.X\x11}\xc0\xaa\xce\xae\xb2\x99\x03\xae\xc2\x87\xb0,\xb2\xec5\x8f+\x08}\xc5{ K4\x97\xb9d\x19#\x97\x99\x00\x179\xc1\xb4\xb4\xb5\xff\xabW\xfb\xbbb\xdf\xab\x1c\xde\x07uwy\x8c\xf7%\xdc\xdb\x02K]\xd7\xed\xce\x8d\xeb\xba\x05\x80\x95\x1d{+\xf7\xb9\x1d{\x9f\xba\x9f\xd9\xb1\xf7\xdc]\xca\xe5\x8e\xd3\xa7\xa1\xd1\x115x\xef6\x1a\xa6\xe9'\xa30\n\x8e \xd5Jw\x0d\xd7>\x0f\xd6\x89\xb6\xfc1\x12IB~\xd6\n\x12y\x86j\xc0\xd9?\xfb_\xba}\xd1 \x1c\x110\x00\xdd\x1f\xb4[a\xb1?hS\x11\xf5\x05\xb9\xdd'L\x89)+\xc4\x82\xbc\x0e\xec>\x18h\xef#\x9f\x05\x98\xfaa@b?l\xcdvh\xc7\xb0\xebG\x92!\xfc\xa9\x8c\xa7,\xe1\xc9\xc0\xdbm\x9e\xf6\xa3\x19\x8e)Q>V^\xd2\xba<\xe2\x18\xb9\xb4\xc1\x8a\x14\xba\xa7\xac\xa0\x91{\xd2\x0c:\\\xe9$\x83\x9a\x9f\xce\xc4\xc4\xde\xa1\xc9\x94\x90\x7f;H\xba\x86J\xad\xae\xf27\x8feDs\xb8\x93\xbc;d4t\x10\x96\xd6\xf86\x98e\xc7\xf2\xf7\x1a\"fC\xa2\x80jG<\xfe~3\xc5\xb1\xb2\x1a\x0c\x84\xe8#\x0dY\xfa\xec\xc0\"\x92\xc8\xa9\xd0Z\xf0\x08\x85\x05\xcb\xaa\xe3\xea(\x06O)\xfd\xb5\x9b\xbd\xc85\xe8\x8ff\x8d\xc7\x0e\x9e\x8c\xea\xf4\xd2a\x8bZB>g<.J.\xefkwO&[\xd0\xab\xc8\xf0\xba\x97\xec\x81mS`l\xd2\x9cf\xea\x16xg\x90\xb2\x90\xad\x83\x8a\xbe-\x87\x9a\xa6\xc1\x87tw\xcczn\x1aJ3\xd69\xff)M\xa6\x01\xdd8\xc4R\xafIqzx\xabj\x80`\xcf\xbdV\xddS\xd3\xa0u\xe4\x0d\xf8s\x87\xeb{\x17\xdd3\xbd\xb0R'p\x84\xf9\xa8\x14\x02\xfe\xec\x03\x86\xab\x91\xd6\xb1\xad\xc2\xe5\xccxt\xd4RX\xcc\xda\x98\x01\xb1\x18\xfct\x05\x16\xb7\xe7\xb5\xf5 \xb4\x17\xb1Bf\xf6\x1cp \xa8\xba0\x0c\xbd \xd1A\x18@\xd6R\xa8\x0c\x12\xce\x87J\xdaxMm\x92\x81`\xcf?\xca\xe1t\x8b,\xd8S\xef\x7f\xdcg\nE\xc9\x161\x93Z\xe4\xd6(\x113eG\xda\xb2\x0fD\x0e@\xbf\xd6\xb5\x00\xd8GG2\xc2\xf2\xac\xb8\xc3\xd1d\xbaD\xa8\xa7\"\x91VD\xea\xcd41=\x1f\xa1\xfb\x0c\x8f\xc9\x19\x90\xfc\x11i\xc1}\x86\xfb\x00\xe0\x9e\x1d\xf6\x9dO\xfc\xfa\xd2,\xff\x8f\xf4\xe1\xe8\xd7x\x9e\xe2\xa0\x06\x0f\x98\xfa\xff\xa4\xd9\xf3Q\xe8\x86\x07\x95\x8e$\xe9\xa6\xc7\xa6\xe4?\xe9\xc3G\xa6\xd4\xec\xccX\xb6\x8f}S\xdey#\xbdn\xb4\x07\xd0 \x08\xae\x98\xef\xd2\xb5\xe0\xb9x\xe0\xa5%\xf9\xbd\x8c\xd3\x07\xae\x93\x8f\x95\x85\xd5\x81\x9f\xca\xae\xded\x01\xf5\xa2\x8a\xe8O\x80\xe9\xfb\x9b\xf4L\xce\x0e\xc8\xa9G$\x18\xd6\x9a\x1a\x97)\xde\xbd\x8fc\xd5\x81?\xec\x11\x891\x0d\xe6\x1b\x82\xe6\xb6\xa2\xa7\xa2\xe0p\xc1.\x97\xd6\xef\xe6\xc9\xb5\x0e\xe2L\x95\xa6\xa8\x95A\xcd\xc3\xbbq\xe71\xac\xca0\x84C0\xf9\x1e\x9e\xa3\x8a\xdd)\xa7X\xd2\xee\x96\xc4J\nk\x92\xc2\x11\x0e\xb7g\xd1\xdeN\x99\xc2\xe6\x0e\xc3-}\xea\xebE\xc0Nj=\x9cb\x16\x90\x13\x1c\x19\x0daRY\n\x0fxa5\xb1\xad%M\x04V\xd1u\x1d\x9d\x9f#Q\x0c7\x0eb3/v\xb8\xe0=g\x02Oku\xfc\xff\xbe\xe1\xf9j\xcd\xbd^\xf5\x1c\xb9\x10\xa8\xf7\x9c7\x8c\x07G\xdc\xda7\xe2\xccLH\x82,\x8d\"\xad\x04\x8bp\xa8l\x03\x8b|\x8c\x052\xad\x85\x8c\x85\x14`W\xc4\xf1\xb8M\x1f\x86\x0bj/\x90\x95\x16k\xb4W1\x9b\xa8\x04\xe7\xceP\xf9'g#2\xce \xa2\x16\x83\xcc\xac\x8553>\xb9\xdd'#\xce\x1a\x8eZ\xed\x16\xcc\xcb%\xd8\x83h\xd5&T\xc5\xb1\xc31\xc3\xd4\xe7\xa3\x11\xae\xe0\x8e\xf8\xe9\xaa\xaeG\x82\xe12y7\x85\x01#\xe0G\x08\x1a\xb3\x90<\xfb\x9d\x05\xea\x99S\xb6+t\xc6\xc5\x0f\xf9\xb1\x8b\xf5i\xbeS\xac\x92\xc8hV\xa7\\\xd5F-\xec*i\x19\xd7\x05\xb5\x1c\xab\x10\xd0\x81f\xfd\x95\x15\x15e\xe4\xfa\x85\xe3Mi\xc9i\xcd\xca:,\xb2\x9a\xef\x18\x8f\xeamY\xa7\xbb\xa4\x06\x19\xa2\xce\xd2\xfc\xae\xde\xf1\x8a\xd6{Z\xd2\x1dr\x1c\x7f\xf3\xe8\x063\x19\x0d\x0em\xaeo\xae\x93\x143\xa8L}\xb9\xc6\xa1x\xad\xed?y\x9b\xc7\xd9\xfa\x1aG\xb2)\xf7\x10\x96\xe9\xbe\xaa\x0f\xd5\x87\x8cC\xc5\xe8:\xc5\x9c\x91ke\xf7\xb39\\9\x9e\xeb\xbf'AM6\x87+m\x0e\xb4\x10\xd9bF\xae\xdf?\xab7\xd7\x8e\xe7\xde\xd2\x07Z\xf3pG\x91\xac\xf1:\xc5\x89\xf8\\\x95G\xbe\xb9v\x16W\xe8\x1aoE\xc2\xe6p\xf5b\xeax\xee\xc6\xff\xf2\xd5\xcbw/7~=\x9f\xa3Z$\x04\x9b@<\xdfl\x0eW\xcf\xae\x13\x9c2r\x92\xd7B\xb9\xfe\n[/$\\Nv\xc7\xacJ\xf7\x19'\x9f\xe8\xa7On,l\xbd\xb8\x96\xdfo\xac\x00W[N#Y\x08B\xe0\xca\xef\xea1\xc0a\x91\xb9\xfe\xf3\xf6\xe3\x8b\xb0\xc8\x92\xb28\xeee\xb6\xf6\xcd(Q\x95\xbd\x02\x15+\xa2\x0f\xaaRx4\xb3F\xae\xff\xe90\xeb\x8b\xaaT\xd9\xcb\x9b\x912\xad\xdc\xe7/\xb1ea\xcb\n\x9au\xca\x16\xc5\xbe\x82\x9e\x10\xf9\x9c\x169N\xd9\x02J\x8b\xa4*.\x8aJ<\xe8\x1e\xc33\x85\x8c\xf0]\xcc\x02\x94\xd8\xc2k\xd4\x99.\xdd\xb2\xbe\x0e\xce\x90-\xb1\x05\xdd\xb2PO\x82=\x8f@\xc4\xcc\xa84VUZH\xdew\xae\xd4S\x87/>\xbc\xa3 \x94\xb5\xa0\xc7\x16\xf2\x97\x01\xd8\x80\x186i\x03\xeb\xbc\xa1\x99\x9c*\x88\\\xdaYR\xdd\xf5\xaf\xf9\x00>\xc7\x91\xda\xb4K\xd7E\xcd\xackk\xa6\xa4&\xa3\xa6\x8cu~\xd6 S\x91\n\xa5\xe2\xb9=\xdd\xf24'\xe5\xaf\x02W\xcb}g-\x98\xb5\xee\xd4\xd4\x1aW@\xe1\xa8\xbbn$RJli\xd1O\xfd0\xc0\x9649\xf9\xea\x81f\x16\x9e\xb2\xba\x96\xa7Bl\xf8\x0d\x19\x01\xf3s6\xa2\xc7\x97\x86(\xeb\xb3\xdbX@E\xd67Hqb\xc3\xfe\x1b\xe1\x84\xc8\xfe0\x1c#|K\xe2\xce8E\xbb\x1bi\x1b\x81\xa4\xb3\xf9Z\xb7\x88\xffVED\x13#\xbd\xf5y0\x1c\xacI@\x19\xe6X\xe4\xf1\xc3\x005o\xfa\x9d\xda\x927F\xa7Rb\xc6\xbd\xd9\"\xfcF\xf51\x15|S;\x15E\xef\xee\x80q\x18\xbc\x04\x9a\xf2\xe6\x11\xe4\xd2\x05\x042\xd3\x01\xd7^f\x99w\x9e\xd4\xe6\xf6G|\xe6\xc0\xf9\xad\xb7\x8f\x18\xf2\xda J\x14\x1cC\x8d+_\xf6\x83\x0b\x0f\xc6C0\xaf\xbb\xeb\xd3B\xdb~\xa7\xafx\x03\x10\x05}\xb8\xb4\xb1l\xef\xd1s\x1d\xa3\x80\xba$M\x90\x12x\x97\xa7\xbd\xa6\xcd%\xe9\x87vF\x86o\x11\x98l^bS\xd4\xb1&\xed\x1bv\xc2z}\xc4\xee\x16\xfc<\x9d\xa1\x81h]\xf7\xa3\xa5\xa9`g\xa63l\x17U\xd9\xa1H\x1e.%\xa4`\xce\x16\xe1X\xfcR\xa4\xc2\xfc\xb5w\x9e\xea8}{\xe6\xc4~\x14\xe0\xc4\x8f\x02\x19\xc3\x18\x82\xf2A\x151\x89\xebZ\x96NH\x02\x8f\xdb\x8b\x15\xe5\xbd\x8a\x80S\x85\x1d\xb8\xed\xee\xd2\x81\x1eaK\x12?\x0ba\xed\x19t\xb3\xb4\xed\x1ds\x12\xefz\xd8\x9d]0\x88N\x88\xc3\x14\xbe\xe5\xd2\xd6M\x90\xc68cH\xac\xf8:i\xfb\x13\x8b\xaa4\xab\xbb5Y\xddi\xab\x03\xd9\xf65-=\"\x91\xe2-('\x16\x872\xf4\xf2\xc5\xbf\xf9\x03\xcd\xfeQf\"\x8f~\x96\x1f\x05\x97\xd9\xd5\"\x9a\xea\xf0u\x8ba\xb6\x0c[\x16\x1a\x04\x85\x95n\xffp 'w\xff\xbb\xc2\xb5\xe4\x93\xa5\xb9\x1b\x91\xa4\x1e-l\xd2[\xd7\x92\x0c\x85N} \x84\xde\x02zoiD\xf12\xcb\\\xcb@\x1a#G\x9f\x838\xce\xb4O\x9c\xe1\x1e\x83\\\x8a\xcb\xbc]\x05\xbc\x85\xa9&2\x86VH\xb6p\xdfh\xb7\xd3\xe5\xea\x8b\x85\xcf\x1d\xeeo\x03\xe4\xb3\xc0 \xbb#\x9a\x08\x872r{\x8f\x98\x19\xd1\x99\xa3\xd6\xc3\xf8\x9e\xe1\x92\xf5\x023\x1f\x98\xd3\x0b\xc5\x94;\xe1@\xe2dH\xa3\xd3w\x85#\xcd\xe5\x10\x1c\xc2&\\\x1f\xae|Y\xec\xf6\xc7\x8aGo\xab\x0f\x19\x87\x90\x1c\x17\xbf\xc2E\xdb\x08y\xd1B9\x1b\xbb\xd2\xfbX$\x1b\x0e\xc8m\x8c\xb0\x85\xc4n\x0e\xc2\xf1\xf0\x9abI\x193\x1c\x92\x92\xf9\xb4;)\x80;\xb1\xe5\x0d\xd2H99\xcb\xa8\x14\xe2\xcb=#\xce=\xab\xeb\xdc\xb1^\xa4qIw|\x02\x7fYQF\xbc$\x9f,?\x99\xc0\xfdf\xf0$/<\x13\x8f\xd77\x969\x0dlhI\x880#\xf7L\xa0\x8dA\x14p\xcc\x16\x8feZ I\x1c\x9cxU\xc0\x1c\xdd\xbd{\xd6\x0e\x10a\x18\x06 \x11\x0e\x1b1\xb6##\xd7\xefw\xb4L\xd2\xfc\x1a?0b\xfa\xa8\xbcw\xac\xd9\xdfg\x16r\xbc\xe9\xfe \xf9t\xfe\xeb\x7f\x05\xb3g\x16\xb6R\x0b\xe1G6\xe6/:\xb8\xa2\xc5\xf4\xc6X\x14{\x9e\xf3rx\x8bK/K\xc2\x07\x0b\xc9\xb0\x0c\x1aG/~2\xc0\xec\x89]\xd2\x7f\xc1\xa1I\xbbv$\xac\xebGP(\x85\xb6\xed$\x04 [Gh\x03\x0d\x9b\xc3P]\x87\xe0/#\xb2Xbm\x13\xe0Z/\xab\xcd\xc0\x9b%\x97M\xc1\xbc#\xfc\xa0pY\x82l\xfb\xc8\xf4\x15Z\x00\xbb\xdb\x05@\x00\xe6d\xbb\xd8\xa5\xf9\xcf\xf0\x12\x8b\x17\xfa$_\xbat#U\x97# \x16\xfd~T9eZd\x96\xe1\xd8(\x15#\xc3\xce=\xf1\x92\x99e\xb9\xc9\xf9\xad\xaer\x82\xc6C\xd2M\xa8\xbei\xce\x8c*%\xb2:\xfa\x890t\xe1@\xb7i\x86\x0e\xfb\xa0\x8a \xd9\x88\xb1\xec\xb8w>\x8e/|\x8007r\xd6\xd1I=,\x18\x0d\xef\x92\xb28\xe6\xd1\x97Y\xba'\x96\xda/sV#_1\x9f\x07\xb3\x10\x0f\xabV\x0fQG\x16\xff\xda\xe7'\xc8K\xa5\xa7`\xdd}\x88\x1e\\C\xbe\xa3O\xce\x12G\xfe*\x98;!\xb8L\xcc\x9cH:\xf7\xef\x9f,\xe4\xb2\xae\xceo\x98i,\xa6\xf9\xf6\x98\x84\xe0\xd9\xea)Ra\xb9\x9a\xd4X\xc8\xfb\xcc\xb5\xe4m\xb8\x10\xb6i\x051\xc2\x97\xeb\xcfn\xe2u<#\xcf\x91%\xa1\\\x9d=:\xc9\xac\x0d\xb5\x13\xce~\x02\x17\xfd%\xdc\xc3\x12yN[\xa9\xce;\xef\xc2\xf2\xa8\xadj\xf5\xca\xe8\xba\xa7\xe7\xf9UG!\xfb\xcc\xfaY^\xd7+\x8b!\xd7\xec\xc5X\xcd]\xe2\xf4\xac\xd7\x1f\xad\xb9=\xc5\xebf\xf4\xdb\xc1*\x89\x9c\xc4\x9c1\xba\x90\xdeCP\x93\xab\xdf\xbe\x01\xc0\xc41yTG\x8bVG\xe3z\xf1\x8aZ\xf4n \xf9<\x06\x86cyCx]K\xa4\xae.~#\x8a\xfb\x8c\x11v\x967\xc6W8-\xd1[\\p\x94\x0f\xdda\x9c\xde\xe9\xeb\x88$\xb6\xed\xdc-\xceh\x89\x83\xea\x9a\x83\xda\xb1\xab\x80\x13\x03\xfdqT\xd7\xcb6\xb8\xe0L\xc3\x97\xe0F\xc7\xa0 G8F3\x01\x96\xdd\x0c\xfemh \xa1\x0e\x89\xe0\xfe\xb5%0\xd0j+m\xd5U3\x11\x1c\x10\xe1Hv\xca\xb6!T\x95\ne\x13a\xab\xc8\xa2V\xc8\x82\xe0\xa8\x12\xa9\xab4\xcc<\xc8_\xd7\x9d\xdcT\xd7\xce \x17i/S\x1e|\xb0\xed\xb7\xd2\x8bX\xb5\xd9\x1au\x9a\xcd\xe2\x8a9Q{X\x8f\x90\x0c\xf4\xfa\x16\x82\x86\xea\x88T\xa1m\xf3\xba\x96\xf6\x14\x83\xd2\xdc\x0b\x95\xc8\x18\x19\xf2\"R7\xa5&\xe0\xe3xa&\x98m\xb7\xc3:\xeb\xb95\x92:2t\xe6\xa9\xf9\xb1\\Y\x17\xea\xdfM\xa6\xcf\xfe\x0f\x07\xe5\xccT\x08\xac[}pO\xe7!\x85\xe10]\x99/\x00\x88Z*s+\xff\xaa\xd8\xa7\xd6\xcar\xc3\xa6i\x1a\x1c\x1e\x0e\xdf\x1fw\x8c\x97\xee),\xb2\xe3.\x07?Sw\xba\xc4q\x9ae?\xa8\xb6\xc4k\xc6\x9f\xfeZ\x16\x8f\xfa\xf9\xed\xb6L\xf3;x\xebP\xfft\x89\xb34\xe7\xdf\xb4oEW\x81\xe4\x13\xe0a\xbf\xa59\\\xde\xf8\x98F\xc5#<\xfd\xfa-\\\x83'\x9e\x8ab\x07\x1eL\xe1\x01L\xed\x0f\xee\xc9\x8a\x05\xf8\x0b\xd0>\x1c`'X\x0d\x86Y\x1c9\x9a\x97\xd6\x9a\x9f\x0e\xec\x17\xfe{\xf0N\xb5\x14a\x9c\xe2\x0fc\x03\xa7Ca\x92ID\x01\x9d\xf2\xb7\x01\x04\xde7\xde\xc9\xd7\x0c\xd4R\x02\xc1\xc0\x07\xe9\xb4\xc6\xe0\x06\xac\xf6u\x1bt>\xb1\xa1\x97\xd8\xb6\x95\xf0\xcaJ\xf3IbD\xbct8IT\x88\xc2\xe9\nG\x08y\xdcM}\x16\xb8N\xdc\x86\xbf5\"\xe2\x92\x18\x10\xcf\x17\x8af\x85H\x1al:\xdc_\x05\xb3\x15\xba\xe2\xfe\xf3`f\xe0\x11\x8d\xef\x84\xdc\x1a\x13+\x07\x00\xb0\x10\x96'!\xa1m\x87\x8ah\xe8OS\xf0\x02\x84b\x12Z\xe4\xf0\xc3\x19\x01\xa2w.> x\x96{} 6jm\x80\x8eN|\xb2 ^\x95\xcf\x02b\xa5\xf9\x96\x97)\x18h\xd8\xb6u\x18\xcc\x07\x81S\xeaD\x85\xe1\x15\x8b\xdc\x96\x0c[Y\x17IX\x1e\x01\x88\xcb+\xfc\x1f\xac\xab\x82\x84?\xb2\xba\xfd%5Wr\x89\xbb\xfe\xb6\x912\x15)\x89\x10`\xabrG3\x15E\x13x\x96W\xf2\xce\xaaW\x0c\x88\x81\xa5l\x88B\xcf\x89\xfbtA\x1a\xe4.\xa5]\xce\xf7\xc7\x1d/\xd3\xd0\x89\x91\x17\xd7\xf5\xd2\xe5\xc8\xe5\x866\xd3\xb7\xa4HjaE9\x833\xad\xa31<2\xc43\xa6)u\xe8\xfd\xaa\x08\xdcy\xc8?d\xdb\xd2\xb9\xc9\xa0\xc8\x9e\xe4\xb6\x1d\x8a\xbf4n\x92i\xab\xfbV\xcfE\x83\xdc\xf6\xb9u\xc4;\x8c\xf4\x03\x16\x97D\xb6\x0d\x94]/(0s!\x8eG\x98#w\xa9\x1d\x00\xf4,\xf4\xe4\x98\x0f\xcc\xb9[\x8c\x88t\x839\xd4\xa0\xd5\x8d\xf8\xd4r\xd2i.\x90\xe4\\3\xd4O\x0c\xfbT\xb3_2\xf2b`\x04Pk\xfdn\xa4\xc4eYXKZ\x96\x85\x95\xf4\xa58\xa7s\xf5q\xbb\x90t&\x96R\xdaH\xb8\xe6\xd1\x87f\x04\xa4\x91\xd5\xa9\x11\x08\xe1,\xd4\xb6\x17\x1an\xd6\xae\x1f\x06\xeb\xcf\x94\x11\x16\xf7\xe9\xec'?\nD\xf5\xb1\x1f\x05u\x1d\xfb\xd1\xfc9\xfc.\x0d\xf1\xa6\xc1G\xd6\xdd\xee\xea\x0cz&\xf66\xf9+CC\xc3\x8d\xe1\x8e\xbe|\xae9\xd0\x05\x8a\x81\x08\xd2\xadB\xf9\xeb\x00\xf1:x\x81\xe4\x03y\x17\xca\x8b+\x12\x1f\xfb\xccO\x82\xa0\x05\x10\xf1&Q\xb0\x1eJ\xac\xf9\xaf\x16Y\x87\x9e\xa1 \xc4p\xe0\xa0\xd1k\x83\xe9h\x88b\xd4\xe0\xc3\xb6x\x1cQ\x1e\xfcM\x19-\xc0\xd9\xda6\x8d\xc6\x14\x0c*\x0fjpU$I6\x16\x12\xd5bE\x91q\x9a\x9b!\xa7U\xd0i\xd1\xb0\xbaMj!\x1a\xd0\xcfCk\x96\xb7\xb2\x11O^\x9c\x8dt9\xfd*\x8b6\xfd\x8b_\xbe\xebI\x10\xa6}\xdehYiK\x92Gu\x13\x01x'\xcbJ\xf2\x88D\xf2\xf1\x08\x97\xb8k(V\x041\x0c<\xc1\xc2 r\xd8\xe0\xf0X\x9e\x1f\x00\xcb\xb1\xed%\xd4\xb7\x9d\xec|\xc6\x05W\x92\xf0J\x9a\xc5*{#\xb3L\x17\xa4\xa7\xfd\xde\xe0\xf28rC\x15\x0e\x7f\xaf1\x99R\x1c\x08#\xe6\\,\xa2c \x1e\x7f^\xaef\xcd7f0\xd0F$\xc3\xdcW\x14/\xf1j\xfc\x1br\xb5\xe5I\xf1H\x1c=\x95\xf3n\xca\xd1\x15\x9buo\xfdJ\x0e\x15\xdf\xab\x83x3\xa9;\x90\x94\x91]t\xfd\xeaD\x15b\xd2\x1dx\xe5\x85m\xd4\xf0K3\xd9~\x97W:5\xf8\x0cP\x0dx4\xbfa\xb3>rj\x9d\x05\x06\xd4R-\xc80\xde\x84\xe8\xb5O\xe5\x92\xd4\xb5|\xd7R\x85d\xc2\xcc4\x9d\xd3s\x98FLr\xdc2\x1d\x0b\xe9I\x88\"\xf4X\x15\x82\xebb\x1es\x97\xc8\xed73\xa4\x9fp\xb8\xf8\x04\xd3\xd9\xd6\x7f\x96\x02\xb7\xc3\xf7z\xe7\x8ct\xcf\xe0\x96T\xb9>S\xa4\x12Q\x87'{\xdd\x17|\xf9\xe3\x0cn\xfd\xac\x86\xbd\x06\xa6\xfd\xb1i\x17F\xad_\x1b\xd7\x82\x8c$\xbf\xe6q\x05\xee\x8b\xbd\xd1\xaa\x1e\x9b\"\x00$\xf4\x0c\x91\xc6\x1a\xd7\xbe\xaf\x80CN\x82r\xd3r\xcc6\x826\x18\xb0\xcb\xc8\xb7\xc5\xe7s\xd0\x1d\x85\xc5\xc1\xa1W\xf0\xf8\xe3\xb7\xe8\xfa9\xd4\x1c?\x913\xa0\xc3\xedJ\x90\x93\xba\xb5\x83\xe17\x0c\x7f\xcfd\x90\\\x89\xf7k\x81\x92k\x81\x88\xe1\x96\x8e\xa1\xe6\xcfs\x95\xf2\xafFZQ(\xcf\xf5:m\xe1\x8f\x8c\\w\x11\xa7\x9f]\xe3\xbf3\xe2\xff\x93\x05\xf8'FN\xd6\x95\xe5\xfa}\xea\xfb`\xdcY#\x8dk\x05\xe2\x96'\x8f\x11 \x15\xbe\xe4\xe4\x87V{\x86c\xc1\xcbr\xff\xd3`\x80/i\x87/qB\x06\x9f\xa42m\n\x82\xcd,B\xb6\xad+\x94\xe0\x1f*\xf8A\x08o\xc9\n\xa7\xe49P\xfa\xc4\xb6\x13\xff\xd3@\x14S\xa6\xcf\xe2\x15s\x81\xf9A\xbf1\x8b\xeaz\xb5\x8e\x8a\xc9\x96l\xeb\xdaZ|n\xe1\xe4\x9al\xb1\x86K]/Nf1R\xba\xc4\xad\x10\xc7\xb6zl\xd7\x91\xbaJ|k\xdb\xf3y\xda\xdd\xf4\xa0\x0e\x17%\x11\x99%u-\xdaZ\xe2P\x11\x0b\x1c\x02\x01\x11\x02\x99\x97\xccz\x82\x99;\x13\x7f\x11\x0e\x9b\xc08\xdf|\xcb:zo\\Of\x10\x95\xd7\xda\x1b\xafA\xf8\xb5@\x0c\xe0\xd9\xdfiz\xde\xf5\xbdQ${\xa7\x0e\xa3\xa8\xd4h3\xa9cT\xcc\x1cy>g($\x82\x9b\xc3\xdc\xd7\xea\xc0Y\x18\x10\xeew\x8a\xbd0 \xb4\x15\x9c\x84p\xb2P2>\xd1\x9a|\xc1Yu\xdd\xf8G\xab\xb2k9M\xcc\x89\xf3\x13\x03\xd1\xc9\x0f\x90\x8e\xc2\xf2\x13\xf3\xad++@pGmg\xce\xb0N@\xf99\x93W\xc8p?\x0e$\xf6\x0f\x05c\xd5j\xd5\x0cM\xee?\xc7O\x86u8Xie\xb4#\x10[U\x8bu\xfb\x9er\xe0\xad`\x0e\xef\xdb\xdb\x1a\xac\xf8Il4\x88\x0c\x0e\x9b\xa5\xae\xc1\x88\xa6\x7fM\x02\xdc\xf5\x80%\x82\xdf.\x8e9|\x8c\xc0DE\xbf\xc0\xbdm[\xe3\x02\x03l\xbe\x98q\xe6\xbb2u\x9d\n.\x0bw)\xb3\x19\xce\x16\xf2\xc6U\x13\x1c\xc6\xd2\xbaB\xf39\xeen\x7f\x80\xae\xb6\xd6`\xdb\xfe\x8d\np\x0f\xdb\x99\xf1\xa3\x16\x19\xd3|\xc2\xeaZ\xc9\x8dp\xa0a\xdbN\xb8(\x1ex\x19g\xc5#\xf1\x8b\xf6\x19w\x8f\xbf\x18\xcf\xff\n\xf0\xedH\x80x|GZe\xdd\xad\xd7N\xbd\xa9]\xac\xebJ\x08\xe2\xad\xa2\xcf\xbd\xc5J\x94\x12\x85\xee\xb4.\xce\x14\xef\xa4\xdeH\xf4\xb1\xe8t\x8d=\xf1\x0b\x0cIu\xdf _;\x96\xf6\x08dlj\x8d|]y\x7f\x19\x98\xa36\xbf\xac\xcc/\xff2\xbf<\x0f\x1a\xa5kT\xd7\x87\x81\x86\x99\x89]\xf8}\xeb\x90\x01z-u\x18\x0f\x9f\x00\xbdY\x92\x0e\x80\xc2\x00sB\x88\xb3\xf7D\xaf\xb9\xe5Z\x12d\xa1\x9c|\x967\x92N\xef\x8d\xc0#\xf7~\xf4\xffg\xee\xdb\x9e\xdc\xb6\xd1=\xdf\xcf_!\xe1xY\x84\x85V\xab\x9d\xd4\xd6Y*07q\x9c\xcb\x1c{\x92\x8c\x9d\x9d\xcc\xc8\x9c\x14\x08\x82\x12\xd5jJ\x16%u;M\xfd\xef[\xf8>\xdcH\xb1\x9d=\x0f[u\x1e\xbaE\x82 \x88;\xbe\xfb/\xa3r[\x1f\xaa\xfa\xa8\xe6;>\x9e\x9d\xef\x16E\xc6?F\xd1G\xe0\xe5\xac\x97\xa29b\xc67s\xe7Z\xc3\xdf\xe4m\xabOh&y\xa0\x14\\\xe3\xb8\xe9\xb3z\xb2v\x0c\xd0\x15\xd2F\xd7>E\x13\x06%\xbf\xb9*@\xe5R\xf1\xf5\xf4\xa0\xc9(o}m\x84\x116}\xb1\xcc\xa6\xfbc\x1d\x97\x1e\xf0#\x04\xf0\x15l\xb1f%\xd3\xa3p\xf3\xb2\x8c\xa2*\x95I\xbc\xea\x80\x94\xe8,\x19\xc5HHk\xbe\x9aZ\xa8\x10\x8cy(L\\\xc3\xd0\xb33\xa7l\xbb;\x04i\xe3\x19{4\x1eD\xaf\x81\x16N\x1e\xcfg&\xa9\x0b\x12\xe5Q\x1f\x93\xdc%\xfe\x84\\[\"\x99\xeb\x9a\xc4u\x9e\xed\x8fD\xba\xaea\xd8\xe2d\x91\xb1\x80\xc0\xf4$u\xa0\xfb3R\x83X\xb0\xb5f\x0e\x1b\x10\x07\xe0\xe5\xb4SQ 4\xcc\x03$\xe3]?\xba\x9e\x07\xef\xaa\x82\xb2\x02\xc3\xf3\x04\x1f\xb4D\xef\x8c\x15\xa8:\xb11\xc0\x01\x97\x8fg\xce:\xdd\x0d\xa3\xc4a\xbc <\x99/\x87\x89\xe5\x19MV\x06\xea\xb8\x93jXT}T\xae1J\x1d|\xeb\xefy|;\xd8n\xda%\x9d~\xc9\x1d\xed\xb4f\x82\xd9w< \xe5\x98T0\xda\xbde\xbf\xe6lM\xbb\xa8\xaf\xf63\xc0\xb7GQxk\xe3\xa7\xc3+\xe5\x03\x04\x9d\xda\xc7n\xfaT\xcc\xce5QWw\xc9\x9a!(\x90)\x01n4\xe1\x01\x0dClg\xf3\xc8\xde\x1b\xf0e\x93\xaa\xafm\xa3-\xac\xaf\x81\x896\xa9\xfa\xda\xadw\x93\x86w\xf4\\O\xbf\xae\xab;\x98p\xde\x9d\xf9\xb7\x9c=\xc2@]\xc4\x8f\xe8Bx\xd3\xd4\xc0\xfa \xb9\x9a\x08ME:\xe1\xe8<4\xcf\x9dA\xb4\xa1\x9e\xdb\xaa\xe4z\xd7c\x7f\x83\x8d\x0d\xfe\x03w\x02W\x0e\x18+G\x07\xaa\xc1\x80\x96y\xfaK\xee2\n\x9a\xfc\x92\xe3\xf4\x15\x06o\xaa\xd9)U\\\x84M\xc3E#\xa2h\x00\x8c.\\\xf8\x82&\x8f\xb6G\x13\xd9\xb6c\x19E9\xea\x15\x82>\x88\"\xe1\x97\xaf`\xb8\xac\x12\xcc\x9a\x9b(\xd0\xdf\xf95\x14Ey\x80+o_\xe40Q\xb6e\x99\xce\x12\xabir\xb5\xf2\xd9R\x7f\x99\xf8K}R \x97\xac\x9b\xdb\xa4\xc1\xf5\xc2\xe7\xca\x92 \xdd\xbb\xab\x9a :\x85\xa5\xe7\xcd\x05\xe8P\xc0f\xd3\xdc#Q_L\xb7\x9b\x82\x17n\xa21\x7f\xd9\x05F\x0b\xc3a\xebwh\x14\xc1\xaf\x97W\xe9\xc2\xa0\xe8\x0b\xb0,\x93N\xcfz\x0f\xea\x08\xc1KQ\xa8\xf7\xdb\x01\xcdV(\xcb3.Z\xef(\x90\xbeN\xef\xcbf\x96\x0e\xd2\x14\x86>\x00a\xe2\xab\xd8\xa9\x91\xf33C]\xca\x99\x99gO\xe9\xd0x\x9f\xde\x13\x08\x17\x07\x9d\x8b\xf0\x03=\xa88<,\x7f3\"\xed\xee,c%\x9d\xc7\xca\xc6U\x80\x0c\xa4\xac\xea\xaaY\x81\xea(\x87\x18d\x80\x89\xee\xec@\xa6\xf8\x9c/\x99j\xdb\xd2\x0f\xd9M\x80\xa6\xb84\xd2l\xecY\x93\x89-i\x7f_\xef\xac\x8a\x0by\x1d\xc7\x10isK\xa6 \"]\x1eKW\x9bA\xb8/\x8c\x91\x13B~\xe5Q\x04x\xec7F\x8ay\x01R6\x1c;\x07+\x01\xd6-F\xde\x16E]p0\xe8y\xd8c\x1b\x08\x14\xe1D\xc2x -\xc1oV\xff\x87\xaaGQ\x11/\xd1G\xcfbT@\x8c\x88\xe5e\xc6\x9f\x9d\xc5\x8a\x7f \x8f4\xef\x80\x7fu5\xa7\xa5~Eo\xe9&\xa24\x1a\xc1@M\xe1\x11\xd4u\xcc\xb9\xa6\xe6 A\xcf.\x1cT f}7\xac\xb4\x08\x1f\x8a\xddP:\x8fs\xbd\xdb\xd0'`\xe4p\xec\x07\x05oc\x1b\xbd\xdb\xe2\xab}\xaeW\x99\x0c{KSf\x0b\xdb\xb3$c*\xb8\xc5\x8e\xce\xba=]\xa4\x85?\xfb\x81r\xb5\xb3\x12\x10\xd2\xc3j\xc3\xf0b\x18^\xe8Y\xfc\x0d\\\x8a\xc6z\x82\xf8n\xcd\xb1[s\xecV\xe3\xd5\xaa{3\xcf\xdc\\\x17`\x0b\x93\x87\xbd\xa9Kq=\x99CO\xa2lg6_\xbe\xcc\xc1\x97\xb1X\xe4Y\x14\xe9\xff\xa6\xb2\x9d\x9b`w\xb2\x13\xde6\xca\x81_\xa2\xde\xd8\xb0\x9b\x0c\x19J\x86\xacf_{|2\x88\x82e\xbd\xc8\xb3\xb9\xf9\x0d\x8f\xa3\x8en\x08\x05\xe2m;\xa4\xb0\x92\xc3\x16\xe6\xb8\xc0\xedF\xf6\x1e\x1c\xfaf\x94a\xc1\xa1\xd7N\xb3\xa9\n\xf5\xed\xf6\xbeN\xde\xe7\x86 \xa6\x0c\x12\x7f\xddA\x12\xd4\xdf$\xbdGU\x9aN6\xcd\xa4L\xef\xbc?\xd6\xde\xe4\x06\xcb8C\xfaO\xc7C\xf0\x00J\xc2\x07\xa6 \xff\xcc\x14w\xfes\xe7\x9e\xcbm\xdd\xb62\xb7\x9b44\x0fg#_d\x96\xd4\x92\xb7\xfdm\x17\\[\x99tS\x17\xe6\x84\x17\x13\xce\xf3\xaf\xa4\x9bw\x93 \x15\\B \xe3\xd8\xf8B\xe0\xc2\x95nZ]]\xb1\x1b:\x97Nhd\xe4\xd3\x00\x12\xe1\x05\x91\x01\xe5\xd7\x0bG\x88\x95\xb0t\x8a\xfe\x8eU6\x88\xfd!\xa6\x89\xcf\xa1\x8b4\x05AD\xc8\x93\xd8\xf0\x9b/\x98\xcf\x1d\xb6\xf4m\xde\xb6\xf1\xdb\x9c7\xea\xf0\xa3\xc9\x1c\xbb.\xe9\x16Bm\xa9\xba\xd6a\x19`\xb9\xe2\xde~\x9bS\xf6\x16}qm~\xa0\x1d\xf8c\xb3\xd9\xde'\xffs6c\xa5h\x0e\xc9\x8b\xd9\xccG\x88\xfar63gv\xa16\xe2\xd3S \x89\xba\xb8\x0e\x9d\"\xb2\xb6\x15\xe0\xfbk\x80ZYpH\x04\x06\x12\xc1Q\x15\xc8\x82s&\x00\xacp\xa8=6\x8fqn\xefO\x8e'\xc1\x0fY~\xf9\x08Cx\x81u_\xfey\x1bu\xd4\xd1\x11J\xe7&B\x93\x0f\xc9la\x0e\x7f\xaa9A0\x81\x93\xd8\x1c\x15\xbb\xd5\x949\xc6\xf3Q\x05\x97&\xce\x1d \x1bZ \x02\xbd\xb3B\xb6o]\x82t\x0f\xd9g\xdab\xbe\xc1\xc9\x81\xd8\xe0\x9b\x06\xc5\x91\xddN\xe1\xe2\xff\xd8\xe7\xdc\xd5\xc8b\x1e\xfe#g\xff\xcc\xd9\xb3\x9c\x1b\xa06q8\x98\xf0r\xf3\x0eu\xa6\xd3?k\xa3P\xc3\xabO\x1b\x02\xf8\xf0UC\x87\xdb\xe0AV\x071\xaf\x823\xb2\x83S|Q\xaf\xae\x94\xc4Kw\xe7\xdeb\xaeDC\xb92\x8a^\x80\x12\xc5\xd6\xc1l\xc8\x9d@^\x9c\xf3_\xd3\x1a\xf8Pk\xf7\x00\x11\x1bJ\xc4\x85q\xb1\x81\xda6\xcey\xde\x8b!\xa3\xb9w\xe8PgC\x15w\xf0\xf0\xf4a`]\xa8\xd2\x7f\xe6\xc9?\xf2\xd0pJ\xa6\x85\xb7\xb1*\xac\xc6\x94\xc7\x8a\x17S\x0b\xb0LS\x85p\xffeU\x17\xf0-\xd4R\x19K]\xf4n\x02\xd3(\xf36\x16\xda\xb8BC[\xbc\xc2\xd9\x9e\x99\x82E\x17\xdf3grB\xf4\x021f;\x9d\xf1\x81\xda\xfc\xdb\x13\xe3\xdc\x11\xda\xf1\x19+y\xaei_\x8b9gp\xbd{`\xfa\x06\xd6\x9c\x97\x0b5\x99dTw\xa6\x1e\x86\xef\xaa\x07`&%{\xa2/!\x9a\x0b\x08\xef\xc67\x88G\xd2\x0d\x9c&5\x0f`G%\xc1\x98\xe0==\xaa\xb12\x1d\x87\xeb'\x8a\xcc\xaaBt\xe7n\xdc:\xb3\x16-Y`\x16\x99\xb3y\x18\x84Z5\xf0\xd8v\xf9\xa2\xb7\x06\xd8d\xfd3\xef+vY\x08i\x8f1,\xd3^\xf7K\x9a\xf4\xbe#\x19\xf8D\xba\x98\xc6\x97\xfd\x85\x88\xb8f\x1c\xae?\xdcO\xae\x97t\x90\xcey\x96\x1b\x13@7\xcf\xe6\x90\xd4\xe5\xbf\xbd\x19\xa2\xe3\x825Y\x8co3|A9b_\x9aw\xd2\xde\xb2Ap0S\xbfq\x05\xee\x84\x0b?i\xe1\x18\xcc\xfa\x1b\x98y\x9a<\x92r\xbb' Y\x1d\xee6\xdfm\xf7\x84\x11\xb9\x11MC\x12\xfc\xd5\x13\x8d 2\xcb\xd3\xdb\x1d[\x0eoxK\xdc\xf0\x96\xb8\xe1-\xed\x86W\xf2\x1bt\x02\x1dw\xb62\xd04\xe4\xc1j\xd3#\x9f\x03\x8a\xb4\xb7\xb5\x01\xab\x7fo\x1a\xa6\xfc\xa6\xa2\xc2M\xa5\xe0\xaa\xb3\xa9\x14\x89\x00\x83\xd6D\xf9\xadM\xf9\xadMg\xf7[\x1bf\xc6F\xdb\xc5*r\xb4\xa0\xbe0@qKm%\x9ap\xa9\x89\x1c\x8cr m\xdb\xdf\xadi\x9e\xd7\xd1\xb4\xad\x98\xae\xf6\xaaL\xc5\xd4\x15}us6\x81\xab\x833\x1b\xb6\xee\xc0\x04\xc3\x1e\xe5\xc3\x860\x80\xb5b\x0d.\x025r\xde\xb1\xc3\x08\xef\\\x89P\x07\xd8\xbf\xbb<\x89\xa9\x1dad\xafD\xf1S\xbd\xf9D\x18\xb9\x13\x0fo`\xa6\xea\xe9\xa26\x1b\xe3\xaad\xee~6\xcalF\xf6\xdb\xfbw;Q\xeb\xf4\xed\xc6\\\x1d\x1b\xf5V\xec\x08#\xe0\xc7\xfd\x0d\xbaU0\xebV\xf1\xba\xa80\x16g\xc6:'\xb2\x9d\x130\xef\xbb\xd1L\x80\x91\xb3\xc1d%\xbf^|8|\xd8\x7f\xa8?\x94\xd9\xf5\xb2GL\x14\xc5+=\xa9\x87L\xba|@\xc9K\x03M\x11E\x82U|\xc6\xd6\xfd0.=\xb9\xddg\xc2`h\x8e\xc4\xd8\xf6\xd9j\xc4\"\xe0V\x8d\x99\x9c[t\xd4\x84\xbdXQd7A\xaa\xd1\xc5K\xcd\xe6\xeb\x97\xd5\xbcB \xb4\x89\xc0[e\xac\xe0\x18\xe4\"\xd0/K_n\x1a\x93\x11\x99\x04 \x132\"\xd4\x07\x83\x91\x0c\x8cP\xf5?\xfaX\x06\xf1\xd3r\x04\xf8+\xbc\xb1\xf9\x88L\xf0\xed\xaff\x00\xa0\xce\xf1n\xbe\xd4l\xd1\xbe\xba\x8b\x0b\xca\x82\x0f\xe1N\x10\xd6\x85/\xbb!!\xcc\x9e\xf7\xff2F`\xfb\xdc\xdb6\xdb\xf6\xff\xf7\xc8\x05\xf5\xfbo=xO\x8c\x1d\xde\x0e\x8d\xe0K>\xa3\x05/\\I\xee 3#\n\x81\xdapL\x13B\xfe\x8b\xc3\x8a,z\x7fX\x03\x8b'3XV\xf8w!\xac\xc8\xc3p4\\\xa69\x8a$\xfdB\xa2a\x90\x9dn\x9a!A\xba:\x06W\x0b\xe9\x077\xa8egpeop5\x01\x95\xd3sh\x81\xda\x89\xcf\xc4\xa5\x9d\xb0\xa8\xa20\xe5\x03W\xd0\x99\x00\x06\xe8\x88\x97\x8bB\x8f\x8e\xd2G\x08~>\xa7\xa9\xea4&\xa7\x89\xf2\xcd\xcdQ_\x1bK\xcd%t\x84;\x18\xf2\xb4[\xdf(z\xe3\x0c1\x19\xf9\xfdw\xf7\xe0\xf7\xdfI\x7f\xde\xf6\xeey\xf7\xb6m\x05\x92\x80\x84$\xa1`\xb9[&\x85\xb9\x0ea\xb3L{:k\xd9\xaby\xf5$\x130\xc9.CdY\xed^\x10\xa8n!\xb3\x8e\xcd\xcc\x88Ll\xf2\xe7\xd7\x83\x9b\xef9\xccs\xa3p\x9e\xcd\xad\xe6\xd9\x12z\xb9\xe4\xd7\x1f\xf6\xfdC\xe3$6O\xedE.\xe89\xb8E\xf5\x03VYR\x94\xf7&\xdf\x90\xf4\xd4L\x195\xbf\x88\xc0\x07\xc6\x02E\xda\x9d\x90v\xca\x9e\xc4&\xa64\x11\x8e\xefR\x1c\x1c\xddz\xca\x1e\x95\xaa\x89~\xe0\x8d\x10\x8c/%\xea\"\xd5P\x88F\x1b=\x8d\x90DL\x089S\xca4mv\x12\x9b\xc0\xe8\xf9\x80\xf0\x90\xfd\xe4z0\xb2o\x06V\xb4\x86Z\xcb\x03j-\xf7\xd3S1\x02\\ x/AY\xc8\xa4\xb8\x1d\xd5\x87G\x0b+\xa3\x06j\xa2>[\x8d\xe5e5b\xc9s\x98\xd4\xbe\x124\x95I,\xb92\xd2\x94A\xcf\x0d;\xd9r\xc9\x08\xb1\xe8\x10R\xf7\x9b\xb4\x9e.\xc1Tr^\x86\xba\xbb\x9f0g\xee\xb1\xd5\xb62\xa1\x95\xf3\x18\xac\x90\xcd\x96lBh\n\x00\xaeFz\xee\xb2\xe42\x0c\\\xca\x855\xf8\x06\x15j\x97\x06,\xb9\x91I]\x19\x03#a\xe2G\xcd^*\xb6\xe4e\xaa+\x90\x00~f\x99\xaa\xc9MbE\xf6\xfa\xa0}\xa9\xd2UR\xa6*\x99\xcdW\xe1\xf9V\xe8\xc3m\x1c\x8f\xbd\x08*\x8a*\xb4\x99\x8a;\xd2\xa7\xd4\x0b\x9f\x9c\xec\xa0\x17\xe2\xdc>'\x94\xb6m'N\xa1}\xd2a\x93\xc3\x1c\x8c\xd8\x00\xf3\xc4\x06V\xe5z\xf5\xe1Jb\xa5\xf7a7\xb0\x95\xb9;\xca\x96\x17.R\x1d\xe1\x82\xefQ\x8c\x1df\xb1\xa2\xf30x\x94\xd9\xf2\x97WW\xb4\xe0j\xb1\xccX\\x\x9a\xde\x07\xc3-\xcc\x84+a\xc3\x02\xd5\xd7xF\xc3\x88I\xbdA\xe3W7\x94\x95\xe7s\x87v7\xc28/%\xec\xd1\xd4\x9dE\x9b]\xb2\xfeALD\xef\xe3\x93\xba\xc8\xdbA}\xc1\xe4\x0b\xbb\x10\xf6X\x07\xf1\xe0\xe5\x92\xc0\xc8t\xbf\x08\x81n\x9e\xdcz.\"\xdb\x9be\x90\x92mM\x12+N\xa4\xbe\xc1$\xdf\x1c\xf7#\x80\xf1\x19\x19l\x9f\x91\x05\xf5\x19m\xb6\xa2\x18\xedUS\xfd\xa1Fh\xc7>B\xf0\xb8\x11\x80\xd2\x8d\x8a|\x83\x17\x00\xaeTl\xefk\xbc:\xee\xf0W\x1f\xc5#\x87\xc74\xb2\x10L#\x0f\xd74\xf2\x10M#\xb9\x12\xf5R\x8d\x0czCs\xcc\xef\xaa\xc3\xe8V}\x82ro\xd5\xa7\xdd^5\x8d\xbe8\xeeFj\xbf\xdf\xeeG\x12\xc1Z\xefT}\xecbnv\xc6c@\xdd\xe3\xc53\x17R\x84\x19RI \xe16Q\x15\xa5\x8583\xa0\x1c9\xbd\x88\xe6\xbb\xd2\x0d\x1c\x9e\x07\xf0\xaao\xb1\x1et\xdf\xe88\x07Pp\x00\x8bzJ\x82d\xeb#\x0c\xa8\x1d\x93\xf4\xcc\x8e\xf5\xc5+\xbd\x17\xca\xd2\xbdA=\x82\xe1\x9f\xa8\xec\xa1\xd9V\xf7~\xac\x9fx\xcb\xbds3\xc0P\xa4\xc1\xe7\xc9\xf3\xe7\xc4t\x9eN\xc8\x99&\xe9\x9f?'\xba \x86~\x90\xd2\xea\x7fX\xa1I\x89\xf4z^O\xc1\x8f\xf4/\xef~\xfa\xeb\xd0L\xd7\xe9\x98#\xd6\x87,\xe8P\xe0\xf6\xb7\xb7o.\xd5\xe6LB:\x85\x13\x96\x19\x8f\xf3\xb6\xcd\x87\xc1\x04\x08\x14\xb9\x879\xec\x8c\x9eA\xc7\x0cI1\xf9\xb1>\x89MU\x8c~{\xfb&\xd1\xe4\x1ee9z\\(\xc9\xaf\xff}\xfa\xfc\xd95+%\xbf\x8e\x17i\x94\xd1\xdf\xf9\xe2_Q\xf6\xfc\x9a-%\xbf\xfeW<}\x9e\xd2d1\xfap\xc8\x9e\xc7\x8b\x7fi\x9e>{N\x9f]/\xef\xd8J\xa2HN\xe4\xdb\xe3\xa1\x15\xbb\x9d\xfe\xbbj\x0e\xdb\xbdX\xaav:\xb9\x82\xc9\xdcT\xdb\xba-\xab\x8dj\xf7\xaai\xef\xabb\xa9\x0e4yv\xcd*\xf3\xfa\xf7\xaf\xdf\xb7?\xbc\xfe\xfa[\xfa\xec\x9a\xadu\xda\x87\xeb\x0f\xd7\xd7\xec\x16\x1e/>\xdcO'W\xd9$\xa1q\x9a\xe8\x07\x00 \xf3\xe1:\xfd\xf7\xec\xf9\xffni\x8c\xd7I\xf6\\?O\xe2\x0f\xc5\x84\xb6\xb4\xa5\xd7l#\xf9\xe3\x99\xdd\xc1\xffZr\xf2\xfc\x9aX#|\xf2\x9cP\xb6\x95\\L7[ \x164 \x05b;\xc9o%\x1a#oe\x97\\AV\xc1\xce\x85\xd1G\x19L\x1f\xcf\xa2\xea\x89\xfc\x04\x1eV\xcer\xae\xbf;7\xe2:\x14\x83w?\xd2\xe5K\xfa\xdc\xb1\xa4\x96s\xb42q2\x01C\xd9\xc5,Kc\xcd6\xda\x801\x00\x16\xc1P\x08.\xc0\xc4y\x91QgL%)M\xfa\xcf\xe0\x90\x95\xa1\xb1\xed^\xf6\x0dc\xc0kVs\x1ew2\x8c\xcf\xb5\xc2\xe7\x95\xf3\x82]\xac24\x13\x80\xe3@\x80\xaf\xf9\xa2\xa3?7\xaf\xac\xf9\xca\x18\xd4\xa9\xfdA\xed\x9b\x05\xb0\x92\x13\xfd \xa3\x8f%W\xa6\xc4%\xc4&T\xf4\x8c\x0ePv\xa0\xcb4.\xc7\xd8\xf0(\xf2\x15))\x93\x8b\xd2\xbb\x9b\xbb\xee=\xf6'y\x10\xb9\xf7\xf1\xccn\xc3\xae5\xab\x0b\xfa\xe1vq\x93!\x1a\x08\x98r\x07\x15\xa6\xeb\xc5\xb2/\x18\xed4h\x99\xcdK~k\x07\xc5\x8cW\xa9;\x130na\x04\xbe\xab\xd4\xa6h\x16\xa5\x9eCr1\x90\x9e\xf1\x9c\x02\xaa\x07`\xa8\xea*~\x07\x96q \xb9\x0f\x13\x00\x12\xd16\x81\x82\xdd7\x0b>\x0f\x10\x1c0WJ=0\x0e]\x83<'\xba\x1b\x917(ap\x96|\xbd\xa8`0\xca\xacm\xd7\x0b\xf2\x1c.\xd9x\xe9\xe7\xc4\x1a\xe6\x04W!\xe5\xb4Z\xdcd&v\x87/b\xa5\xc7\xd3\x95\x02w\x94>.\xc121]\x02\x90O\xa2\xff\x01:?@\x06\xe9<\xec\xd6\x8d\xa8.\x95\x06\xd3k 9)\xba\xc7\x89\x059\xac\xf6\xdb\xfb\x86d4\xe7\xcb87\x06a\xfa<\xc6{s\xc8n\xec\x16\xfe\xd8\x1c4M\xd29F\x19\xfc$\xcbt\x93\x90\xbfnG8\x84\xfa,\x1b\x95\xfb\xed\x9d\x9e\x94\x132:lu/\x9c\xcf\xe7n9\xcd\x11|8\x08\xd3]\x9f\xe4\xe7 \xc4\x8d\x90\x87\xea\xa4\x92\x19\xdb\x88\xe6\xf0v[Te\xa5\x8a\xe4\xf1\xcc\xd4A,\xf5o\xb8\xd9$\x8f\xc7\xfd&\xd9J\x06ZJ\xf2\xfd\xeb\xf7\x84U\xcd\x9b\xad\x14\x9bd%M\xd4g\xa9\xfb\x82a\xf4\xdfd\x9e\x9dpy)\x9d\x93[\xb9\xc8/\xb1\x00x\xbex\x91\x9ds^.D\xef\xc9\xb9\xc3\x8e\xe7(\xf6\xc9\xcf\xbaN_o6\xddj5\xa14\xdc\xbc\x07\x95JU\x82\xc6^\x8dn\xc9\xc7\xa3j\x0e\x17\x0da\x01\x92Y\x17\x0b\xcc\xf2sm\x1b\x0b\xdeh\x1a\x06a\x08\x04\xdb/\x04\x9cE\x07P0h\x96u_\x15\xea\xad!,\x065\xe6 i\xb2\xa4\x07\x17\xf6]?8\xc3}\x0b\xde]/^\x1e\xa8sN\x13\xf4\xa3f\xc2\x17\x1f\xc1\xa6o\x91gHU\x8cN\xd6\x1fB,Nf\xcc{`\xa0L\xe4z\xb2_J\xfcD\xdb\x1e\x9dx'\x8a\xe4\x142\xc69e\x0f\xf1\xcc\x02\x8f\x9f\xcf\xba>[\xe7^t\xa2\xdeP~7\x15E\xc1NSs\x00\xf0\x13\xfap\x9c\x90\x0f\xe3'p\xd8\xd0\xe7\xd8~\xc3\xe3X\xb4-\\\xb6\xedVR\xcdx:)\xa6\x02)\xa6\xbb]K\x06\xdb\xfc\x84\\_C *0\xf3\xca\xa7w\xea\xb0\xda\x16\x9a~C\xd1\xe0\xadK\xc1,\xec\xd6\xd3/V\x1b\xe8\x93\x10S\xeei\x16\x84\x90\xccH\xb4o\xa7r\xbfm\x9ao\xb7w\xa2\xaa!\xba\xbe\xe5\x93\xa0\xfe=V\x89u\xb2\xf3q<^\xb5\xad\xa1\x03\xa0\x19\x9ad|a\xee^h\"g\x85\x9e\xf3du8\xec\x12M\x8e\xe8\xdc)\xf9\x8f\x19I\xc8\x97_~A(\x05\xb7O\xd9\xcf\x06\xa5u\xf2\xc1\xd7u\x03\xa3\xe8v\x1a\x9c\x84^s\xe5\x98\x0b\x9b\xcf\xf4\x08\x87\xddY\xd8\x0e\xd2\x9d\xbc\x17\x05D\x0f\x15\x1bJ\xd9^\xef\x97\xec\x96\xe5\xecD\x19\xaet3SN\xf3\xca\xc2O\xe9\xaf\xe2I\xcc*\x8c\x0cUO\xf1\xc4\x9fL\x80\xd2\xef`\xb1\x12\xd8\x11\x0fb\x7f\xf0\x83\x8a?\xddX\xa0\xec\x16tR\x06~e\\\x99\x93\x1f\xb3RVp\x18\x85N.Xe\xa6}\xe6\xf9\x84\xc7\x85\xb4tzJ\"\x92\x90\x94\xd0\x89i\xae1Z\xc1;\x18B!W\xca\x02\xe0\xe2\x8c-\xfd\xeb^GYJF\x9e\xdd\xfc\xce\xc9D\xca\xc9\x84&\xc5d\xf03\xc4\xe5\xd0\x85W\xa5%}\x00\xb9$\xa4\x85\x16E\x16E\xa7i\x7f\xa3\x8a\xc9\x8f\xe5\x95\xcds\xf5\xae\xaa\xa5\"\xec\xe2M\x90B\x1e\xc4\xf2s\x85\xfcu[\xab\xab\xb7z\x9a\x13\x9f\x9bR\x16\xfb\x89\xe3\xfbQ\xdf\x05\x84\xd3\x18\xe1\x83\xf30\x8d\x0e\x7f\xa9\xc3>\xb1N)\x94\x0d\xbd\xf05\x10X$\\\xb3\xc0\xc7\xdc\x1a\xac\xb2f\xd1}\x92\xa5O>\x99\x18\n\xbe\x9b\x9c\x126\"\x93ZN\xc8|\xf4\x91\xcf\xa6\xb3\x1b\x92\x10B\x13_\x0c\xf8z\x01G\xbb\xd6;\xec\xedt\x85\xc7\n\x1d\xa8\xef\x9a\xb9\xc7\x8b5b\x1c\xdeN\x11\x0e\xe2\x9d\xaa\x0b\x0c\xfe\xe9nQo\xb6a'vK\x0d\x063\xae!\xb7\x88\xcc^K\xe7GN\xe0\x92\xb8\x8a<\x9a\xed4\xb91D\xf9\x0ds\xce[7gzZ\xac\xb3\xf8\xd6VB\xf2\xbd&b\xccJ\xa5\x8f\xa7\xa9?\xca\xf9\x0d\x00\xd3\xf5\x16 \xc0Z,N\xec6\xd3S\x13\xe8e\xdd\xeb\x07\xb4.~9\x03\xd6e8<\x85\xad51\x99 =3\xf7&E\x10\x9f\x03\xbfar\xdah\xea\x7f\xcf\x1e,\xefq\x8ft\x02\x1ci\x14\xd8\x95\xd1\xfd\xfc!\xbe\xbaa\xf7\xf4\x8c~\xc0p\xa7y\x0fG\x9f\x91 \n\xd5\x03\xb0\xae\xa5\x13\xa6\xb0=k\xd8\x91\xdd\xb3\x07\x9e\xcf_\x8c9\xd7T\xd4\x81\xbf`\xcb(\xeaXK/\xa9CFg\nB\x84\x10\xd6\xe9$\xf1r\x96~\x99\xcc\xd8\x9a\x8b\x97\xfc\xc5l\x16E_\xccf/E\xdb~1\xfb\x92s.\xc0B\xec\xc8\x0f2\xbee'VR\xca\x8e\xfc\xa8o\x8e\xec\xc4\xd6\x94\xad\xd3\xb8\xb7\xc2\xef\xf9iH\xc2\xf0F4\x07\xb7\xa6 e\xf7C\x9b\x01\xbf\xa7\xec\x89\xf7\xf5\xdau\xaf\x99\x85\xcc\xef)e/\xb0\xa2mK~x\xfd\xf5\xb7\x108\x01\xf6\xca\xf4\x81\x93zkC\xc7&\xa6=\x98z\xb8\xb3\x15I\xe2\x07~\x04\xcaA\xb1=?\xe2\xfe\xd8\xf0#\x1e\xe2l\xcd\xc7\x0d\xa5I\xdc\xf0\x07\xa6\x8f\xf0\xf1\x03\x8d\xa2\xf8\x81\x13\xc37\xce^\x02\x10\x11\x9f\xe9\xc3\xc8R \\\xb8K\x08]\x1e\xe7m\xfb\xa0\xcf|\xb6N\xb7\x1d\xf7\xd8\x0d[\xec\xd9\x03;e4\xd9\x86\x0e\xb2\x1b=E\x1fX\x93\xf9B5\xb5\x14\x7f\xd4\xc4\xad\x19\xce\xce\xe4^\xa78\xbd\x0d/\x9a\xc0\xddk\xac\xa3\x9e\xedl\x9d\xee\x13]\xdc\x0e\xa2b\x04\x1f\xc9\x00\xbf1\xee\xad\x93Wf\xc9\xb9\xb5rue\x0f\xb7\xb6}\xe2h\xdb\xeeB\xc0\x99\x13P\xad\x9a+zJ\xfc_[\xfb?&\x19\x01^\x8a\xc2;\xef\x80\x11|J-\x86\xef\x98>\xc8=\xacOG\x0d\xb7T\x07\xc2\xc8n\xdb\x1c.CA\xf6\x959]\xef\x9d\xae\x10\x164\xf6\xaam\x0bVp\xe9V\x125lX\x0c\xbc\x9c@\x86=gv\xfbM\x14\x8a\x02$\xb3[Ya-A-\x90\xcf\xa0\n\xee\xb2L\x14\x02\xb8rm[-\x9b\x7f\xe3\x04\x017\xccJA4?I{\xee\x96\xf7{\xb1\xfbz3`R1\x1fj\xb5\xa0\xe9\x80\xb5D`\x91e\x8a\xebZcQz\xa6\x88\xa5\x01\xe7\x18\xe8x\x9f\x00\x8a\xa2S\xf51\x9e\xd1\x00\xb4\xc7f\xebZLv\xc0\xfeL\x16\xca\xf2>\x88\x1c\xd8 \x82\x01\x87a\xc0\x04\x82O\x19\xcd \x02\"\n>\x90\xea\xa3\x12;4\xb4\x03\"\x8f\x1d0\x14\x9dn\xe9\x8fu\xad\xfe\xc4\x8f\xe1IK\xa7^\xa7AQ\x17\xdd\x96\xf4\xda\x92;\xeb%\xc9\x1d\xed\xd1\xc4\xdeW)\x95~\x08h\x92\x07@n\xa6\xca\x83\xe6\x0d\x9d*\xce/\xdaP\xfaI\x7f1\xd0y\xd7\x14\x86&\xf8\xa9c\xdd\xfdX\xb7gp,\x070E\x033\x01(\x8f \xfaz\xdbz\x8b?\x07\x1fe\xcc\xaa<^\xee\xd9x\xfdZ#\x8f\xdd\xde\xf8 76\\\xca\xc0Hu\x02\xb6~\xc5gQ\xd4\x0d\xa3\xfe\x15\x07g\xa7Ni\x00\x1c\xb0Q\x03\xc5\x8d\x07\xbf\xab\xbb\x1f\x84!'\xc9\xaf\xff\xc7\x8b\xd9\xf5\x92\xddK~\xfda\xf1!{v\xcd\x1e\xc0\xcc)\xfdP_/\xd9'\xa3 C\x15\xb51fo\xab;\xb1\x04\xa5\x99:\x80\xfe\x8c>\xbb\xae\xd8\x1f\xf2sf\xf0\xb7\xea\xd3R\xd5\xf4\xba\xf2t\xc2\xd7}\x19\xf7E\xb8P\xb3Ev\\\xb1\x14}\x94m{/m4\xd3\xb4\x88\x05S4\xd1\xa5M\xc8\x82L\xe2\x0b\x81\x90JsMNNHF\x98B\x85o\x80\xd7\xdc\xb6\xf6\x85\xb1\xc3z\xce)-`\x07\xee9\xec\xe6\xd4}FAq\xf9BeX\xa2a\xd2\xf8\x80\xd8\xc2\"\x85u\x1f\xf5\xe6yN\xd3<\xb6\xe6AyJ\x88\xde\xa4\x17\xd6t&\xe3(\xfd\xfc\xf5o?\xea\xd3n[\x03\xb6\x1d\x9d\x10N&\x03Orz\xee\x80H\xe6\xc6r>\x94\xbci\xae\xaf\xa3?\n\xb9\xca \x1e\x8d\x00ct\x94s\x99\xd0\x02?oDU;/t;L\"\xb4\x1e\xc1\xf52\xad\xc5\x9dB{\xb6\x13\xc2\xa9\x07=jb\xe3|-c\xc9 \x06\x8e\x1e\\\x1f\xa6`\xbd\xad\xea\x98D\x81\xb0\xe1$\x19\x99\x90\xfe\x81\xd1\xa8}%6\xd5\x1f\x83X0\x86s\x86\x1a\xb8\x8c\xd8.J\xcf\xac\x9b\xf4\xd4\xee0\xb8\x87\x1b\xdf+\xdc\x15\x94Q|{#,\x91\x86F>\xc6\xd6\xf4Lm\xa0\x80\xc1#\x01\xe6^g\xb7\xab\xc1t\xc6\xd4\\\x8e\xad\xbb\xba\x1f\xfd\xf6\xf6\xcd\x0f\x87\xc3\xce\xb0\x8b\x86\xdf\x11\xf4\xf1\x8c\xbb\xe17\x92\xcf\xd8+\xd0\xcb\x7f+\xf9\xe3\x0c\xbcLo^\xbc\xf8\"y1\xfb\xf2\xcc^\xcb\xbe\xc6\xf5a\xb5\x8f\xe9\\L\xc5\xe1 \xe4\xea5\xcaY:\xb71\xd9\xd6hGD\xc2\xd5a\x8d\xed\x84^\x02\xaf$}%\x17\"\x83pX\x9a\x1d\xdf7|<~-\xa3\x88\xdcW\x87\xd5\xab\xbd*T}\xa8\xc4\xa6!U=z-5+\xb8\x16\x0f\xfc\xb5\x84l\xa6\x0b\x1c+\x16?I9a\xd9m\xab\x8b\x1e\x8bP\x14\x96>6\x1d\xac\xe8P\xef\xc8\x05\xb6\x93-\xf9d\xf2\x8dD\x10\xb0\xedN\x9f\"(\xca\x13 \xe3\x11\xc8\x9f\xea\xbbF\xeda\x08\xc5t'\x9a\xe6~\xbb/(\x83BP\xef\xe1\xd5r\x9d\xc4\x85\xcax\x90\xb0P\xd9\xdck\x82\xa3\xa8\x9c\xf6\x85\xb8Ci\xb1\x7fE\x7f3hb\xdb\xca\x05\xf9\xed\xca\x8c\xbd*\xae\x00\xf1\x11\xc2\xe8\x0f\xa5s\xd2\x9d,&\xb4\x8cQ\n\x97\x97R\x07\xc5$\x04k\xc8\x87\x0e\xf5`\xe8\xf5VlDZ\xaf\xe4b\x99A\x08\x82-L\x11\xb8@y,\x183\x19i\x03p\x82\xa5\x95B$\x86\x9b\x83\xd4\x02\xf0\xd84\xc7\xc5\xca\x80\x89\xa3I\x11\x7f+\x176)k\xdb\xc1l\x97\xf6\xaa\xe54T=\xa5\x06\xb9\xbd\x93x6\xbajVN\x07\x05\xfd1\x053S\xd7\xa6<\xa6\xcc\xb7+\x8fM\xf5)\xcb9\xb4_'a;\x0d\xb20\n$DG\xd4\x85\xaab\x8cqa\xe5\x14+\x03\xb8\x81\"\x8a\xd5\xf9Bx\x0e}\x9d\xebm\"\x8c\xe6\x1e\xe8z\x9c\xe2\x10Y\x15\xa3!\xf4\xea=6\xeah\x00\x9fHW\xf2n0\xfd\xe1\xca?\xe9(\n\xcd\xd7\xae\xe34\xd1e\xb6:\x1b\xc5\xc4\xeb\x9eb\x0f4t\xa6\x88!\xb2\xbe\x83\xad*(\x13g\xbf':\x85Z\xecX\xb1\xce\xce`i\x03\x81\x02U\xf0\xf8D\xd1*\xba\xa7v\x05\xeb\xd6\x81[3y\xd4}\xc3o:\x83\xdf\x00\xdb\x81\xa0\x1co\xa7f\xce\x86\xee\xae\x03\xbc\xad\xe6(\xc8WX\xdaKB\xf1\x94}\xf4\xbab\xd4\xfd&b\x8aY^\xe1=k\xf62\x81\x8d\xe8L\xa7\xdb:&`\xbai\xe4\x1e\xb2\xb3$s\x07\xb5\xcc$\xae3\x11E*\x0e\x96\x15Jd\xbe\x9c} \x07\x00\xdeb\xa8\xc2\x95\x12]\x88\xb4\\\xb3y\x033OF\x91\xd43\xcf\x9a\xfe}'5 \xf8\xbd\xe4\xd71\xa7\x1f\xd28\xe5Q\xfb\x8c\xb6\x1fR4\x02\x0c&\xe5\xba\xd9\xd6\xbb\x84H\xa3/D\xf5\xef\xce\xaa\x0f\xfb\xfc\x97\xe0\xdfI\x0c\xda\x00\x92\x0e\x08\xf0\xb7\x9d\x90\xdfQ\xa2\x1d\xd2\x12\x0b\x01\xc6Obp\x82\xe8o\x80\x86xG:a\x0f\xfa\x00\x1e\xf9\x142\xd9\x88/\xdf\x1b*#\xd7\x1dOSr\xdcoHr\xb1\xa9\xe4Fz=\x8e;\xd2i\xf4xr\xaeF\x7f\xaa\xf7\xd7\xf4\x8e\xff (\x03\xa2\x88\xe8_Ou\xad\xda\x96`3\x00\x84\xbc#c\x8e\x95\xad\xbe\xed\xcc\x1e\x01\xde}H\xd3^B\xac\xd9\xd7N\n[\xa5\xf9b\x95q\xfd\xcf\xd1)\xdf\x83\xd6\x81L\x94\xcbn{+\xef\xe9:l\xafYE\x84\xc9\x8e4=\xc0\xcbz\xe3\x1c\xb3\xbcP\x8b\x9f\xf1KB\x15 S\xd1\xa6RM\xc8\xe8^4\xa3z{\x18\xe9i\x04\x12\xcc\xe5b\x96\x9dY\xb7K8\n\xb2 \xca\xa4\xca\x98\xfe\x17\x96\xbc\xf4\xf6\xaegV\x0c\xc4n\xc4\x17\x80\x07\x82\xe6u;W\xf6:KO\xd4c\xb3\x8a\x155\x88\xd7A\xdf\x974\x8a\xcax 2\x93%/}de'1\x0b\xb7p\xb0U\x00\xf4\xca\x90re\xc6V\xeb\xcf\xcd_\x07\xfd\xc7\x8c%\xa4\xde\xfb\xc0\\`\x83\xf6\x90\xfcd\x01\xf3\x99\xe2c\x19E\x0b\x17\xc4\xbfH\x17y/\xf6E\xb1\xb8\xc9h\x96@\xb0\xcf.\x0e\xf9B \x7f\x83\x11\x89\x90\xa9\x8b\xa2:V\xd4oG\x16\x04|\x91\xb1\"\x94#\x18f\xfd\x07\x0c\xeb3\xd5[\xdb\xdc]\x0d\xf5\xc1Pt\xac\x1f\xa4\xed\x83\x1f\x9e\x08\xec3\x0f,\xf90L\xf3\x8a\x8b\xd0\x17\xd0/\xb3\x97\x1c<:\xad\xe6V\x18\x13\xb4\x15\xa5\x0c\xe2\xf1\xc1\xdd\x0c\xe0z\xfaL\xae\xe9g#\x9cL\xf2\x81\x88x\x08\xbaC~\xfe\xe9\xdd{=s\x9d\xb9\xba\xe5X;bG\x15\x88\x1c\xd1\xb8\xc7\x986\xd1^`YA\x1fK?\xa3\xd9\x12\xe1\xe4\x8bT\x1f7Eu\xd2g\x8d\x11N\x053LsI\xe0l\x13\x17(Ir\x1a\xf5XFQ\x97qZ\"\x1b,Y\xd9\xb6\x81m\x1cPZ9\x13\x99>B\x80\x0b\xf4\xa2_\xaffe^.\xcdzB\xedP<\xde\x11\x9c3\xaf%\xba\xc4\x10\xea;\x02\x0c\x19\xbf\x0fI\xa6L\xc8\xa3bX\xea\xbb\xdc\xab]\xec\xe2q\xf9\xa3\xc2\x8b\xaf`\xe3\xc5`\xaef\xe0p\xf6\xfe(\xb9p\x10\xc4},b/\x14\xfa\x8b\xec|\xafj\xfe^\xd5\xc5\xf6>\x164\x15\xc9\xff\xe2}\x18\xb1\x00\xd1\xfa\\\x1b9\x198\xaa\xfc\x04\x97\x17\xd2\xfc\xd3eL\xf2\x00=\xd1`r\x12\xca6\x08\xeb~\xc7\x1f\xcfs\xa2)\xe6J\x9a(\xd7\x16\x8bjjss\xb2W\x1bq\xa8N\x8aP\xb6\xe2\x1bS\x8b\x18\xa3\xf3\x99\xa2A\xdf\xc0*\x9f\xb0Q\xe5\x81P\xb6\xe6\xb1\x07\x00\xd5\x1fh[RV\x0f\xaa\x80\x1b\x80\xde\x9bT\xe1)y\xc1\x1b\xee\x80\xf2|\xa6\xf8#\xa0H3@\x91\x9e\x9dY\xc9\x0b\x881\x19j\x0c\x80A\xf6^\x88\xe5\x05\xeev\x00m\x0eaF\xe2\x83\x0d\xb9\xb9T\x87o\xb6\xc7\xba\xa8\xea\xe5\xabM\xa5\xea\xc3\xdf\x94<\x8c9\xff\x15\x9d<\x87\x9f\xc7T\x13\xb2\x7f\x91qI\x19TO\xc1\x80\xc8\xe9N,\xd5?\xb0]W\xf9T\xc2\x0b\xef\xb7;\xac\xbc\xc2\xf1\xc0\\\xbf\xf5r\xbdQ\xe5\xe1L\x13uf\xbb\x01H\xe6\xaat\n\x0e\xa4A\xf5\xd2r}T\xf4\xfa\xc8Z\xd0\xbbYm0$\xc2\xe5\x96\xe6\xe8\xc37\xd4\xb8$6\xc21\x1c\xab\x9f\x8d\xa4\x9e\xe5a*\x1c\x9e>\xee\x8d\xae\x07\x1e\x06\x14\x0c\xdc\x85\xcbF\x19\xac\x11\x07\x81\n9\x11\xc5\xeb\xfdv\xe7\x00Pu6\xe8\x9f\x81|\xbas|F\xd3\xe7\xb8\x14\xa0\xec+\xd7@\x84px\xaf\xb7t]$t\x88Y\x0eX|?\xab.\x1a\xf2\x9e\xcfg\x16\xb6\xf7\xbf$\x99\xbc\xe8\xad\xb6\xfdQZu\x13Hp\xc3\x08A\xd8M`\x85dw\xb9\x81\x0d\x11tRa\x99N\xce\xa9\x0b?w\xe3\xfe9\x18\x98\x84\x04\xb3\x8b0\x07\x1b\x83\xe9fn\x86\xb0iA\xa4\xb5N\x16\xce\xb9\xbc\x8ce\xe85\xa1}<\xb2\x1ca\xa9N\x00d\xfe\x97\x10F\xd8\xed\x0ce\xbaL\x97\x0b\x99%\x9a|\x05\xea2^\xa6K\x07m\x13\x17\xa9\x08\xd7FR\xb2\"-\x13\x11\xae*\n\xef\xf2\x92\x9e5iw\x11\xa5\x07\x91\xb1{\x11\x1c\xf5\xf1\x0e{\xff\xe7Q\xff\x00\xe6\xae\x03\x8a\x1ef\xf7ji\xa9\xe9)\xc40\xf4h\xb7\x92b\x04~\x7f6,\xf2\x0c\x80h\x13\xe9 \xe9`\xc4\x0c`\xa8\x03&D\xf8^\x13\xff\xfe2n\"\xbe\xe4`\xf0\x00L\x9eL\x84\x15q$9#$!\xdb\xe3\x01\x92\x83\xf7\x81\x9f\x84a,\x82a\x04\xb6\xff\x04\xd0\xcc\xfd.\xd4dy\x10\xbf\xc0\xd1\xb3\x10\xe9\x16\x90l\x0d\xee\"\xc6\x0eN-jJb\xe1x\xdd\xb8_\xce\x90@\xeftIn\xe4\x9a\xfd{\x8aZY\x10\xdc&\xc9Dd@\x93\xe4\x8e&A.\xb3\xbf\xe3\xbb \xf5\xf94\xdf\x16\x9f\x80\x9b\xdbn6\xfa}\xa6:w6\x03.5\x9b\xa1{\xe7?N\xedh\x02*\x01.[\xdd\xb0%M,<\x02F\"X\xc2\x14-\xd3\xc2 \xef\xdc\xe4\xb4\xee\x9dM\xf5\x87\x1a\xe0+a31t\x1cf\x14u\xf1NmJ\xe4EDQ|\xa3\x99;b\xdf\x0c\"H\xab\xb2\xaaU\x14\xe1\xefT\xdc\x15\xf6:&\xa8M\"l\x91\x0d\xc0P\xd6\xe6\xec\xfdOM1\xae\x7f\xd19\xd9\x1b}\xfd\xcc\x0fT\xbd}\xb5\xad\xcbM%\x03\x07\xe1\x80\x08\x9d>\xd3\xdb\x18\x10k\xcf\xf8\x1b\x89\x81\x80MY\xee\x89\xb9\xfdOIY}f\x96\xf7\xe0x\xdc\xba\xc7\xba\x84Z\xe7\xa0\xf3\x7f\xfb\xbf\x01\x00\x00\xff\xffPK\x07\x08\x01\x84\xa6\xa6\x8ct\x00\x00`I\x01\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\x9dJ\xb1L\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00:\x00 \x00javascript/jquery-sparklines/2.1.2/jquery.sparkline.min.jsUT\x05\x00\x01zI\xfdZ\xd4\xbdy{\xdb6\xb60\xfe\xff|\n\x993\xa3\x10\xd6\x91,9ifB\x19\xd6\x93fi\xb35\x99&\xbf\xdb\xf6ay\xfb\x80$\xb8\xc8\x14I\x93\x94-\xc7\xd2w\xff=\xd8H\x90\xa2lw\xb9\xf7}\xdf\xe9\xbd\xb1\x88\xf5\xe0\xe0\xe0l8\x00N\x8e\x07\xcb\xcb5-n&eN\x8a\x8b$N\xe9\xe0t2\x9b\x9c\x0e\xc6\x83\xa8\xaar\xeb\xe4$[\xa5q\x9eU4\xad&)\xadN\xba\xc5O\x06\x7f;>\x1e\xbc\x8f=\x9a\x96\xd4\x1f\xacS\x9f\x16\x83*\xa2\x83\x1f\xe8\xf5\xe0\xdb\xcf/U\xd6`<()\x1d\x107\xbb\xa2\x832\xae\xe8 \xc8\x8a\x81O+\x12'\xe5\xe0\xf8\xe4o\x7f3\x83u\xeaUq\x96\x9a\x04\\\xf0\xd0\xad\x96\x80n\xab\x9b\x9cf\xc1\xc0\xa7A\x9cR\x8c\x0d\x95g\x0c\x87\"mBV\xfeB\xfc4mC\x80i8@\x90\xb5\xfc\x0f\xfb=\x1c\x1e\x89\x1f\x93 m\xc0\x1f\x0e\x89)\x92\xd1\x0e5\x1d\xfa\xe8\xd6X\x97tPVE\xecU\xc6\xfc\x8a\x14\x03\x8aow\x10@\x08\x11\xc4\xb0\x84\x0bH`\x05)d\x90\xc3%\x14PB\x05k\xb8\x82k\xd8\xc0\x0d|\x85\xe7\xf0-\xbc\x80\x97\xf0\n^\xc3w\xf0=\xbc\x81\xb7\xf0\x0e\xde\xe3\xe9<\xc0u?\xe8\xb6\xa0\xd5\xbaHo\xbdl\xb5\xcaR\x8b\x0f\xd32\x18d\x06\xb0\x7f_dIVX\xc6\xdf\xa7\xd3\xc0\x80 N\x12\x95\xe0\xf9\x81\x01>\x0d\xc8:\xa9>\xc5\x1b\x9a\x94\x9fh\xf1_$YS\xeb1\\\xc7~\x15Y\x06YW\x99\x01\x11\x8d\xc3\xa8R_^\xb6\xca3\x86\x7f\xebh\x06\x15 y\x95\xf2yU\x15\xb1\xbb\xae\xa8e\\\xf1\x04\x83\xe5}\xcc\x19\x90\xe5\xa7\x82\x06\xf1\xc628\xd2\x0c\xa0)q\x13\xfa\xa5\xcef\x0d\x89\xb4\xef\xe30JxgGS\x88\xd4\xc7{\xf6\x0fM\xad\xd9\xe4 TY\x96Tq\xfe\xf9\"\xce\x7fX' +'\x93T'F]f\x1d\xc8\x04?.E\xeb\xbeO\xd3\x17\x11\xf5.X\x97\xe9z\xe5\xd2\xe2uV\xacHU\xd1\xa2Iz\x19\x87q\xf5]\x91\xad\xf3\x17\xd9:\xad\xac\xc7{\xe9\x9fin\x19`\xa8t\xea\xc5+\x92| \xc5\x85eL\xea\xee\xbe\x080\xf8\xf0d\xd2\x9b\xb4\xa2\x05\xe13g\x1d\xcdv|\x82\xac\xdb2\xcf*5+\xc1\xbf\xa7F3\xf2\xcfZ\xce7\xc17\x86\x8e\x93fj\x83\xd3S\x03X#?\x12?^\x97\xd6l\xf2\x0d\xac\xe2\xf4s\xb7\xd9\x15\xd9\xec\xa51\x08~\xe2\x93=\x83\x94\xa1\"\xf9\x91\xa4!\xfd\x10\xa7\x96\xd7J \x9bvBMF\x9eg\x80_\x90\xeb\x1fx\xde\xc7\xf4K\x96\xb3\x11{\x11)*\xad-\xed\x9b7\xd5\xca\xff\xb9[\x80%\xc8i\x14\x13d\xa5\xf4z\x10\x99\x8f\xce\xca\x9c\xa4\x83\xb2\xbaI(6<\x0e\xc3\xe0\xf6\x96\xff\xd8\xed\x8c\xf3\xe1\xdf\x9f=\xfd\xd7\xb3\xf9\xd9 +v>\xb8\xbd\xcd9U\xecv\xb7\xb77\xec\x9f\x92\xd3\xc4n\xf7\x08\xed\xc0%\x85u\xeb\x92B\x8d\xe4\xf1\xe3\xa7O\xd9`R\x1a~\xdb\xa4\x06O\x9e\x18PV\xc4\xbb\xa0~\x9dl7\xa5\x8d\xbf\xfb\xde\xe3g\xb3S\xf6+\x08\x9e=\x9bN\xd9\xaf\xd9\xf4\xd9\xd3\xd9\xbf\xd9\xaf\xa7O \x11i\xbe\xff\xe4\xc9\xbf\xfe\xc5~M\xa7\xcf\x9eyO\xd9/V\xfe\xd93\xc3\x81\xaf\xb4\xc8D\xdb\x1e\xa4k\xb5J=\x9e\xfe|\x13\x97\x8c\xd0]R\x88\xa9z\xc2~~\xce\x89\x17\xa7\xa15\xbb\x1b\xb5\xad\xef\x17I,&\x87\xb5\xfe\x81\xe4\x7f=\x96\xf9\xe2\xefb\xba*\xe2\xb2\"\x15\xe5\xe8\xee\x1dB\x9e\x95\x1a\xce\x9f\x06O{\xa7\x81!CK{\xf6\xec\x99\xd1\x0c\xe5v\xf7\xd7\x8c\x85\x8f\xc0Z\x91\x9c\xc1\xae\x9a\xe4L\xee}\x96]\xac\xf3\xd2\xba]\xb1\xee\x8c\xf1\xcc\xb0\x8c\xf7YY\x1a0\xb5\x8c\x97\x05\xb96`f\x19?\xc5\xa9\xb1\xdb\xed\xd8\x92\xf7\n\xca\x06\xcd\xd6\xd8\xf7-\x1eZE\x05-\xa3,\xf1\xd54\xd7 \x82\x01O\xff\xc0\x9c\xf6\x0d\xde\xb8sj\x0c\xb6\x08\xd6IB+\xeb\xb6\"EH\x1b\xd6\xf0\xf81\xe3\xdf,IL\xd7c\xc8i\x11\xb0\xa6S\x8f6\x0b&0\xa0\xa8\x99A\xc9\x96\x85\xff\xd8'\x01e\xa4M\xfe\xed>\x0d\x02\xf6\xeb_\xc1\xb3'A`8\xe0\x92\x92\x1e 9\xe3\xf66\x88i\xe2_\xd0\x1b\x8b\xff(w\xbb\xc1xPCm\x1c\x98\nQ\xd6\xba-,\x83c\xc3\x80\xdc2>5\xb0\x1aPY\xc6\x17>\x12>+yL\xad\xdb,\x08JZYS(\x93\xd8\xd3\x80\xff\x8b\xd7\xb4\x9b\x15>\x95\xf4>\x95_\x8d8\x9e\x1a\x7f!\xbd\xeev\x03\xf3\xf66\xa7\x85\xc7\xd4\xac\xd9n\xf7O\xc4Y\\\xb6\xb1n\x0br\xcd\xe8\xc3\xcd6\xef[\xfa\xc0\xd4`i\xaf\xbb*\xc1u\x14\x97\x17\x1d@\xb3u\x95\xc4\xb4\xd0\xeb?f$\"\xd3\xf56\x026\xe7+\xea\xc7$\xad\x93X\x13e\x94]\x7f\x14\xc593\x93U\xdf\xfc\xe7G.\xaf:\xe2K\xd0\x1e\xa3\x95\x16]>!\xa7\xc6\xbdK\xe3.\xf2\xaai\xcb\xea#-Q\xe55+\x92\xc4e\xf5\x8e\xdeX\x06\xaf`\xdcM|\xc9%c\x04\xd7\xb4\x18\xfcgM\x8a*N(G\x81e|\xe0x0`}i\x19\xff_\x9e\xb7\n$\x99e\xbc\xa7A5\x90X1\xa0\xc8,\xe3G\xc6&\x9a\xa4\xe4Z\x16\xfaI\xcc\x8a\x01\xc5\xb5*\xa4\x92v\xec\x7f\xf0\n?\x9a,/K \xe7\xe0v\xc0\x954\xa6h\x0c\x88[f\xc9\xba\xa2\xf3\x84\x06\x955\x98\xe6\x9by\x95\xe5\xe2\xc7U\\\xc6n\x9c\xc4\xd5\x8d5\x88\xb8~4w\x89w\x11\x16\xd9:\xf5\xadA\x11\xba\xe6\x14\x06\xec\xff\xd0\xa0*H\xcaT8\x9aVZ\xa1\xb1\xa4\xd1\"t\x899\x05\xfe\xdf\xe4)\x9a\x07q\xc2\xb4\xaa\xbc\xc8\xc2\xd8\xb7^\xfe\xfcfEB\xfa\x855\xc1\xd6\xe6\xe4C\xec\x15Y\x99\x05\xd5$,\x88\x1f\xd3\xb42\xcb\x8a\x14b\xaa\xcb\xaa\xc0|\x1d\xb1\xff\xc1\x80\xa6\xfe~2\x9a\x8fW\xe5X\xf620\xfe\xe7\xfa1\xe6r\x84\xd7Q\\\xd1y\x90\xa5\x955\x98M\xf3\xcd\x80\x141I`P\xb2\xf5J\x8b8\x98WtS\x8dI\x12\x87\xa95`\xc8\x9e\xf3*\xe32'\x1e\xb5\x06iv]\x90|\x9e\x13\xdfgRo\xf0M\xbe\x99\x0b\xae`\x0df\xf9fPfI\xec\xcb^\xbe\x8e\xe3\xd4\xa7\x1b\xd6\xd1t:\x9d\xef\xd8\xdcrr\x1b\xdc\x0e\xfe88\xbbG\x10\xea\xb6\x03\xb3J\x08\xb8saC\x0c\x88\x9eWEq9\x89\xd3\xb8\x9a\x90\x1d\x0eM\x17?/\nr\xd342\xe1Ra\xe2\x91$i\xc6\x033\x18\xcf\x10\xb8\x93uZFqP\xe9cC\xa0\x06'Q\xe5\x83\x8b\xd0!\xc0\xd8\x88\xb5\xce\xbc\xa4\xc4\x04\xc8\x0e\xfc\xc9\xe7O\x82\x01\xbdHHY\xe2\x08\x87\xe6mPP\xeb\xe4\xd7\xdb_oM\xfb\xd7\xeb\x893Z \xd32'\xa3\x05B\x8b_w\xbf\xeeNB\xc8\x0b\xea\xb1B\xe6\xaf\xd7#\xf4\xeb\xc4\xfc\xd5\x1f\xa1\x13`\xf3f\xe9\xe6\xb0\x9cO.\x16+L@|y\xbc'w\x07\x05efw\xab\x06\xf8\x82>(\xe63\x1f`\xd2X\xae\x8a`\xb46'\x05\xcd\x13\xe2QS\xa4\x15\x14\xbat\xa6*E\x1a2f\x0e\xc4\xda\xe7c\x07BL'bL\x13\xba\xa1\x9e\x19!\x08\x17\xe6\x05\x0e\xedS\x07\"\x1c\xda3\x07Y\x17\xf8h\x06K\x1c\xd8\x91\x03K\x8c\xb1\xb70\x0c+\x1e\x0e\xdd\xe1\xd0\xb5cFy\x98\xfd\x052 i\xb5`?\xd9\x0fs\x89\xb6\xdb\xa5\xc5>\xed\xa5\xb3\xdd.\x91e\xa6\xe6\x12\x0d\x87\xa6\xcf\xf3\x8d\x8eIh\xa0\xc5\x12\x1f\xca2\x97\xc8Z\xe2\xd2d\x86|\xabL\xc7|4\xd0\xa1\xec\xcf4\xdf\xcblLI\x03!\x04K\xb4C\xbb\x1d+T\xe6r\xee\xda\xb3*q*\xa4\x1cK\xd9A\x8c\xbb~\x10\xb5\xbc\xcf\xdc\x85k\x91so\xe1Yd\xc7\xf0\xd7\x94\xf3\xc4$\xf9j\x92<\x8c\xf1\xe9\xc2\xf4\xb1; \x92,+L\"\xd7\xcc\xc9)[\x92\xe2\xf7?O\x17\xc4\xf6\x1d\xcb$\xb6?\x9e9#\xf6\x81NN\x19\xe5\xd7\x05L\x1f\xd7u\x8f\xbd\x91\x87N\x9e\x80\xffO\xc6*l\xd5\xb4\x8fX\xd5\xe6\x8b-\xfb\x93SK4\x8a\xacN\x0b\xa7\xbf\xbb\x85\x1d\\`\xdd\x13\xc4\x06\xea\xce\xcb\xeb\xb8\xf2\"\xf6\xed\x91\x92\x1a\xebT\xb8~|\xc3\"\xd8\x9b\xbb\x05%\x17s\x9e\xc3\xcc1\x96\xc8\xfe\xea\xe9U\xb1\xa6,\xfdh\xaa\xa7\x06$)E\xf2L&K\x1f\x8b\xe5\xe2\x9c\x14%}\x9dd\xa42 \x02\x82\xb1;\x1c2RE;5C;H\xf6A\x05\x0f\xdb\xce<\xc8\n\xd3\xc5\n\x11sw<\x9e#\xcfv\x1d|\xc1\x10\xe1\xa0z\xe6v\xb0\xea\x10 k\xc5\x03\x1f\xa8j\xc7\xc3S\xf0\x9b\xb6\xbc3\x7f\xee\x8dF\x88\xd8\x9es\xc4\xa1\xa2\x93|]F&K\xa8\x1b\xa6;H[\xc0\x89\xf4\xa3\xb8\xfc\x81\xfc`\xb6\xc6\x86\x86\xc3\xb8|\xcd\x98\x105 \xdaA\xd9!I\xa0\x10\x08\xb0B\x88\xe6\x04\x9b.\xc6\xf8h\xb6h52\xa9\xb2\xcfU\x11\xa7\xa1\xc9\x08\xaa\xca^\xc7\x1b\xea\x9b.B\x932O\xe2\xca4\x0c\x04!6C\xecO\xe2\x94\xf3p\xd3\x98\x18@\x10:\x9b.\xd4\xd8\xac\x10\xc23\xf5\xc1\xb0m\x87\x0e\x0e\x10\xc7B\x84\xc3\xb17\x8f\xce\xa7\xf3h\x8c=Dx\xbb\x1e5#\x98\x02\xad\x87M&\xcb,NYo;\xc8\xf6\x96\x96X4\xac5\xb6T$>}67\xb7q`z\x8c\x1f\xf9\x0e\xc6\x9cx\x90\x97\xa5U\x9c\xae\xe9<\x0eL\x96~\x841A\x12\x8b3I\x03G\xd3\x1d\xe4\xfb4\x80\xa7\xe0\xc9\xa9k\xa6\x8du\xe3\x8e\xb0\xf4l\xb2\xc9\xc2X2\x12c\xc1>\xad\xa9\x1a\x05\xe3\xf1=\x937\xf0'q)pG\xd0\x82X6qvp\xd9\x14T\xc43'\x13\xaf\xa0\xa4\xa2\x9f\x99]\xf29\xa2\xb4Z\xec'\x99h\xe2\x95\xe5\x17\xba\xa9\xb0kqHE\x89W e\xfc\xdd4\xb8Uc \xf0&\\(\x1aLA9\xf1\x98\x05\xcd9\xb5,W~{\xf3\x85\x84?\x90\x155\x8d\x88\x12\xdf@L\xe2\x93\xbc7\x1c\xb6\xfc\xc0\xe7>\xddx\x95'\xd4/\xc8\xb5\x86\x00F\xaa\x10\n4D\x10\xb3\xa9\x0b\x86C3\xe2Rq\xe2\x93\x8a\x98\xc6o\xcb\xcb\xf2\xb7+\x8f\xa4W\xa4dl[ :V\xd8oy\x8b'\xa2\x10\xa7\xf6z\xe6\xef*&Il\xb9\x8fK\xd5\x1d\xab}\xb4\x9cp+)e\x08\xddn[\x9f\xa6q\xea\x1b\x88\x93\xe3\x11\x99\xa4dE\xb9:Zn\xb7G\xad\xef\xc9\x15\xaai\xa2\x0f\x96\xa3\x19\x1c\xcd\xe6\xad\x1a\xc4\xf7M\xe3\xca\x00c]\xa4V\xe9EtE\xca\xf1J)\xdcc/[YW\xab\x84\xdb\xc9\x82\x07\xfe\xfd\xbf>\xbc\xe7\"\xaf\xaf\x83\x0e\x8b\xf0[\xc2\xed\xad\\p\xbb\x1dMJz\x00\xc4;[xS\xa7\xee\x14\xbbe\\\xc7\xe3*\xa0\xcf\xd5\x164\xe1t\xc1-v\x13!\xa02\x9f\xb6\xf3\x85/\x87\x15\x88p/ \x9cj\xb8\xd6\x14\"\x88\xeb\xca\x1a\xb1\xac\"\x92\xfa S# \x1e\x0e\xe3IA\xc3\xb8\xach\xf1B\xd4\x8f\x10D\x92(\xbd\x84\x92\xa2M\x93R\x9f:L\x81s2\x1c\x92IAK\xb6$Y;\xd2Z\xce\x85fY1\xcd\xb2\xa3!\xd6\xa2\x06\xfcZ\xd8\x0c\xe2t@\x10\x99D\xa4\xfcx\x9d~*\xb2\x9c\x16\xd5\x8d\xe9\xa2\xe1P\xae=\xb7Y_\xc3\xa1;\xe1&\xcb\xc7\xc04,\x03\x9d\x8fg\xc3\xa1\xe9aWqk\x8b\xad{{\xea`\xf6\x8fda\x18\xe3\xe9b\xfc&\x0d\x1847\x96\xc6\xfeY!V~\xc6\xca\xcfZ\xe5\xfb\x8b\xcfx\xf1S\x073a\x08\xbe\x90_\x1eBs\x8e\xa5\x15\xc9\x95\xd2\xcb\x1dV\xcc\xbc\xc7\xfev{4\xdbAH{\x10\x81\xdbe\x99\xf0\x84\x80-53\xc0\xaaE\x9b8\xe8\x88-QIN<\xdfE\xbd\xc2\x80b\xc6\xf0Y\x01jO\x9d3L\x86Cj\xcf\x9c\xf3Z\x00\x0c\xa8}\xea\xecj\x19\xceu>\xde\xfdo\x0c\xf6\x1e\xce\xcdh\xba\xe2\xd2u\xdd3\x9f\xb5\xd8\xc7\xbeI$\x12\xfeA\x13\xec $db\xe7\x05\xbb\xe2\xd3[\x17\xcc\xaa\xffDB\xfa3\x9e\xee\xa5\xfd\xa2\xd2h\xa2\xb0\xc8&\xb5\xac\xb0\xed\x88O\xe9v\xe0bN\xf6pE\x0b\xc63\x04\x91\xc6e\x9e\x90\x1b\xb5#\x82\x8f\\\xa1\xf4vvJ\x0c$\x8a\xd7\xbb\x1c\xaf\xf8\xae\x90\xdf-_o\x131\x99\xac\x96\xceg\xb5\x08[\x93\xa9\x01+U\x1a\xd4\xc07\x1c\xf2\x9f\xeb\xdc'\x15})`4\xb5&\xc5j\xec!\x0e\xdf$r\xa9K\xd4J\x06$\x91\xf3\x0f\xf9\xe9\x82;Ye\xeb\x92\xd2\xb4\xa2\x85\xe93\xb3o#,i-\x9d\xd7AH\x95M(\xb9\xa2=eyzS\xd6Kb\xef\xa2\xa7\x18O\x97\xc5\xd8H\xca\x0eu\xdf1{\xc3!\x19\x0eM=eR\xd0UvEM\xd4\x9ee\x0f\xed\xa0\xe9\xac\x0f?\x93WWB\xe0\xabIy\xc1J\x1ah\xeeN\xb2\"\x0e\xe3\x94$\xbc\x04&\xe06\xec\xb3\xc4\x1at\xa0hvR\x15q\x18\xd2\xc2tU\xb7\x1cm\x96\xae\xa4\xb0 aj\x00\x9a\xacS7N}\xd3\xe0\x05\x19\xf0\x93\xe5%#\xac\xa6D_>\xec#\x92\xe5)tk\x14\xbd\xbf:~\xc6\xee$g\x7f{\xd6\x8d\xc8\xf9\xa5\x95\xf3*\xc1\xeeD\xf85\xe1\xa8\x8d\xff\xbe\xc5\xd2\x99\x12\xcc\xd6\xfd\x95H\x92\xab\xb8=;\x92\x9c?I\xf7\x9f\xa9\x80\x93\xa0\xa8\xd1\xec\x11}Cd\x96&i\xeeE\xec\\c\x97r\xe2\xbc\x9a\xf9\x01e, \x80p^\xed\xf1\x84\x06\x1b\x0d\xd3\xa8\x11\xf1\x10*\xe4*5\x97T!\x9e\xce\xc33o\x1e\x8eF(\xc0\xae\x1d:\x10\x08\xc1\xf9#\x0d\xe3,\xad\xf9\x85\x89\xb84?\x9a\xa29\x95\xd8\x16Ku\"\x1c 5\x1eX\x7f\xfb\xcb\xa65\xe9\xe4\xe0\xa4\x93\xfeI'j\xd2{\xe6\xfc\xc0\xdc\xa9>d\x8b\x87\xa6\xae\x95`\xf5\xeb rn\x1a\x0b\x12<6\x15\xc2\xbb\xa38\xd6D\xec\x8c\x98\x08\xa4\x94\xd3G<\xa6\x93\x84\x06\x15\x84{Y\xbf\x8c\xe9\xa4\xca\xf2&\xae\x81\xeb\xa4\xf5\x94K \xc7\xa7j\x89\xa7\xf3\xe5\x99;_\x8eF(\xc6\xc4^:p\x81\xe3II\xab\xeeT\xb5\xb1\xc7\x95p\xb8\xe0*\x05\x9b>f\\\xa1\xdb\xa4\x87\xd3\x88v^D|7\x08A\xf2`\xf6\x92\xf0V\xf5\xe9@\xb7\x116\x8cCpG#\x1cs=[\xc0(\xfa\x95\xab\xd6\x94rA\xcdk)\xd5\xf1\xb42#\xb4S\xcb\xbc%\xcb\xb6\xdb^z\xbc\x90\xc6\xa3$\x14MDp\xc7\xd0\x15\x93\xfde\xfcUXa\x96\xd1x\xfd\xcb\x8aT\xb178\x8aWyVT$\xad\xe6\x92\xb1X\x037\xc9\xbc\x0b=g\x7f\x17@\xcf\x0d\x98\x8a%<\xc8z\xb2\xd1\xf12\xd6\x1a\x87\x14\xd6r\xec\\\xe7dF\x83\x01F\xb3Aa Ez5\xf0\x10HY\x9a\xa5\x15\x89SZt\x1bR\xe9\x06\xdan\x05Wj-\xa5\x8f\x9cv\x7f\xee\xd4\x92\xa9\x06\xcc\xa6\xa8\xa7\xf8/\xbd\xc5\x7f1`v\xcad\x86\xf1\xf7\xe5e\xc9 \xe4 7\x8cH\xe6\xd4\x83\xe9\xb0(Y\x03\xfb\xa6q\xe6\xc7W'\xe7\x06\xdc\xc6\xbeeh\x8d\x017X-\n\x06w\xc2\x1a\x96\xb7\xeb\xf0\xb7\x9e\xca\xaa?\xbd\x924\x8c\xbfdf\x1b{\xf5\x1aV4X\xafm\xb10\xf9\xd7{\x1aT8\x10\xebZK\xfe\x92\xe58\xe0K\x9a'\n\x92`\xb2\xcf7\xaf\xe3\xd4\xcf\xae\x1bqPP6\"&\x0b\x06\xa5WdIR\xcb[U\xf2\xaerm\xb9+\xf8\xd8O\xbc\xde\xcbxU\xb6\xc4o7\xb3a|M\x9a\xce\xfb\xc4D\xf0\xae\xd8p\x1ax\xea\xb4z\xb6x\x02GE\xb7\x14Kl\x17\xe3{o\xb8So\xd4\xd4\xbb\x16\xc6\xa3\x0es\xcd\xce\x1176>\xc7_\xfbtTA\x17\x93\xa8Z%&\xb9cV\xf97\xef\x05\xb7*\xca\x8eGR\xbc\x8a\xa0\xadv\x91H\x1a\xae-\"\xadIw\x07\x0d\x97j\xc1\xc7=\x07\x12LEL^Y\x9aF\xc36\x0c0\x04\x914*\xbc$\x19\xe9\x80\x12Is@\xec\xd4R\x04\xa1\xf2<1\xc5l\x1eu\x1dHT\x8fC\xe5\xae\x0b\x8c\xb1\xc1\x96\xa2\xb1\xdd\xba\x82\xe8Wb\xfc!\xad\xea\x00O3\x90\x02s/\xf4\xd3@\\\x83Z\xb1\xaa\xdb\xedJy\xbdW8\x14\x0b\x1c\xcd)^\xd5\xfb\x84'\xe6\x7f\xffZ\x1e\x9f\x1d\x8d\xc7hk\x8e\xc7\xe7\xbf\x96\xc7\xff@\xdb_\xcb\xd1I\x08\x86Q;\xf9\xc1@b\x95P\xec\xb21\x88\xce9%\x18\x88\x01\xcc#\xaa\x16Tm\x10\xc9\x02\xbd\x81\xad\x06\xb2\xda\xf5\xb97W\xa4\x88\xe5\xa05)\xf8\x9e\xcc\xad\x83^\x99\xeeq\xe4\x0b\xaf\x1b\xc7s\xd7\xf9\xdb\xe3\xa9-s\xc2\xf8\xe2rR{\x9d\xb1A\x0c\x90XY\n\x17\xe1\xb2\xe3\\\xdcnyZ\xcd\xb2\xf9WM\xa2K\x8eZ\x81\x98\x18w\x860\x0fZ\xde\x12-\xe8\xd4@\x8b\x0b|4\xb3\xcc\x0b\xbc?\x06\xcd'y\xb1\xe8\x1b\xf7\x85r)\xb2\xfa\x8c\xfe\xd6\xa2z\x80\xe0\x8e\xd6\xe0\x02!\x1d\xd1Z\x93\xc3\xe1\x9d\xa8\xbc\xdd\xcf\xa4E\x91fU\x1c\xdc0xL\x92\xd0\xa22\x8d\xe7UEWyE\xfdA\x95\x0dHU\x11/\x1a\x90A\xdd\xcf\xa0\x89Lg\xf9\xe9\x80\x8a\x89\x19\\\xc7U4H\xb3\x01\xdd\xc4e\x15\xa7aS\xd0\xe8\x1bQ\xd37\x1cM\x91\xda\x1f\xda%\x0c\x15\x1d\xff\xbd\xad\xd6\xc8MN\x0d\xe4 \xb9$!`K\x8e\xd9$J\xbdg\x16\xcd\xc5d\xcf\x85e&h\xc7w\x05\xa4\xe7X,\x9f\xe1\xf0(\xe8\xf8\xc1\xea\x80f\x86\xcc\xdaI]\x9a\x86\xa5\xc41#W\x99.BwJ\xd3`\xaa\xb3\x81\xe4\x8a9D\xe7\xc3\xe1>\n\x98J\x12\xa7\xa1\x81\xb8\x933\xc6\xef\x94\x933\x9e\xc7\xe31zg\xc7\xe3\x99cO\x1d\xcc\xd9\xc6p\xf8Nm\xa9\xc5\xe3\x19\xcc\xd0\xfc\x9d\xf0\xc1\xd9\xbc\xc9\xc8\xe9\xc3\xb2\xea\x82\xe1X\xd0w$B1\xf8\x18v\xf5^M\xcd\xf5\xe4J/q`\x8a}r\x99\xf1\x9b4w~\x93\xfaAO\xdc\x0bx\xcd\x96(\xdfak\x06\xe4\x9eysw4B\x04\xbf\xb3]6$\xf0\x99\x82\xc2Q\xab\x14\x0eN\xbf,U!\xb6\x83\xfa\x85\xc9\xeb\xce\x1c\x19KR\x0fW5\xd9\x1d\xf1\x0c\x81\xdcxu\x11\xb2D\xdb^\x92\x95\xb4d\xab\x9b1fT\xefd\xaa\x95C\xbaS\xc3\xa3\x19\x1e\xde\x8d\x1c<\xad\x07>w\xd9L\xaa\x89\xa3\xb6;\x9e90\xdb\xc7\xbbr!\xdf\xe1{\x16\x87\x1d\x84U\xb7.i\xf1Qy\x9d\xb1\xbb\xdd\xde\xee\xa4 '\xa1\xf2\x9b\n\x91\xf2\x82x\x918*\xd1\xdd]QS\x0d!\x0e&\xe2\xc0C]\xb1u\xd4\x00\xbb\x93\xee!\x03\xa6\xac\xec\x95\xdbn\xc3\xbd4\x04Q-\xf4\xbe\x90\xf03\xad\x18oPk\x19\"\x8c1]x8\xb0]\xbe\x89\xc9\x9b\xb8\xc9\xa9cy\"\x1e\x85\xd7]\xd1\"\xa4\xbe\x1ao\x1d\xfbt\xbb\x83\x10<\x1e\xa5\xd1j\xfd\xd0\xfeC\x176\xf0%J\xa5\xb4>\x9a)I\xad6\x11\x1a\x17F\x83\xcb\xee\x06\x0eA\xc8\xc7{\x85.\xe8\xcd\x9c\xad\xb7\xdb&\xaf-\xf7\xdd\x11\xe1|\xdc\x17\xf2\xddW\xf2\xdd\xc7t.\x04Q`\xfa\x93r\xed\x96UaNa\xc6E\xa9m\xa0[\x1f\xd7\xc93\xf0U<\xd6\xa9.\xe29\x15\xb2\xf9\x96T\x18\x8c\xc7s\xe4\xdb\x81\x83/L\xf6\xa7\xa35\xa0\xad\xc9\xd5\x05\xa1*H>\xd1\xd7\xfd\xad\x81n\xc3\xfb\xbb\x07\x1f\xdf\xee$\x0ca\x0b\x86\x08\x87\xac\xfbz\x0f\x0bN\x11\xf8vdO\xef\x06\x89\xc1\x1d\xb1\x95\xff\x00\xb8}6F\xe5/jO\x08\xf6\xd5\x8e\x90\xdf\xdd\xa7R\x8b\xcc\xef#V\x82 P;\xff\xfe\x11#X\xdf\xaa7\xaet\xd2\xb4\x89\x83x\xfc\x94k\x05b\xcf\xa9\xb5\xe0~sII\xd9\n\x97\x02\xc7\xb7\x8ef\xfb\xb1ej\x8b\x9c7\xdfl\x11\xfd\x83&|\xf3I|\x89SAj\xc3I1\x0f\xaa\x9b\xb3A\xcbr\x0d\xc5\x97\xa7{\xd7\xb0\xb7\xe3\xdd\x8b\x80\xee}O\xe7\x91\xde\xf8a%h\xae\x96\x08k\x05+`\xb5\xfd\x7f\xb3\x81J\x87 \xf6\x9b\xd7\x04&\x10\x84\x16\xd2\\\xe7*\xccO\x8d\x91.\xba\x9a\xe4L\x1d\xfd\xa9iV\x94\xfb^3\xd5\xf5\x82\"\x1d \xc3W\xcd\x00>\x9a\xee\x87\xeb\xb5\x0d\x06UT\x02C\x13]\xf1\xe4\xfc\xdfb\x8d\x84\xca\xe7\xda\xa1*\xee\x07\xe8xc\xef\x88\x0cl\xcf\x10\x04wL\x82\xb6o\x07a\x1d\x99r\xdeE\xd9v\xeb\x9f\xef\xa1g\xbbu\xcf\xa6\xdb\xad\x7f6]0\xaec\x99aM\xf8\xa2g \x19\xd0#\x8c\xc3\x85\xc9\xfex\xc3a \xfd\xa6Bs\xd66\x02\xfa\xa8+\x84\xb0[\x8b\xe1Y\xafu4E\xd6\x11\x93\x86}\xdb\x0b\x87&\xa4\xd5\x0b\xebA\xce\xcc\x83\x80\xf2d\x9fj\xd6{{\x13\xf5\xb8\xcf\xbbi\x8eiP\xd0\xe9\xe3\xfeJ|p\xed\xc4\x96x\xe2\x84\xd3\xe7\xf3>\xb0\xf1 \x01\\F{L\xdd\xe2\"\xec\xc0\x01\xc8Z|\xb5q\xd0H8\xc3\x98\x07\xf5\xc4\xb7\xa0\xe0a\xfb\xa5\x89\xe0\x12\x93\x96KW\x8b\xd9d\xcd_*Yy)\xd4N\x02\x01\x9a\xb7k\xbc\x88HQ}\x89+\xa1\xe3\x99\xee\x08?:\xf3\xe3\xab\x81\x08\x975\x98\xa9\xbf\xbc,+^\xe0\xfc\xd1\xe8p\xe5\x91qv\xe2\xc7W\xe7\xbf\xa6\xde\\\xbb\xa0~\xae\x84K\x12\xa5\xa1\xb7\x08\xef\xd1h9z\xc4(\xeeB\x11\x96Q\x87\x8e*\x05~\xe1\x8e\xa8\x8cXdTg\x19\xc6\xfe\x82\x15KE_\xaf;\xf0H\xe2\xd5\x0b^\x9c\xb7\xd1x\xbe\x8at\xc4\x9et5\xb4J\xf2\xcd\x9cn\x8e<\xb5k y\xe4y\xc9u\xc7:\xc6\x8c\x87\xc2\xa0\xdb\x00\x9f\xfc\xf7\xdfM{:~F\xc6\x81\x83\xfa~\xfd\xe3$\x16\xa1\xe0\x04m\xb7z\xe9\xdb\xd3\x1d:\xfc\xa1U\xe3\xde\x07t\x1b1\xe6\x13\xd6\x1b\xa0\x18\xe3'\x8b\xd9Sk\xa6m\xf0=\xe6\x1b|\x91\xbdtpl\xba\x13~\xc0E\x84\xb6\xbeI+3\xb0\x97#f\x0c=E\xc7\xe11E0\x85\xd3o\xbeQ\x8e\x00\xa3\x08]\xd3\x18E\x12\xf9\xc0V?2vMh\xef\x0e\xc15\xbe\xbd\x8b\xb5j\x9a\x7f[\xa8z\xbar\xa06\xce\n\x9e\xf79\"9-m\xd7\x81`N\x87C\xa5\xe5 R\x93\x92\xd1\x05\xd2\xe5\x1a\xcd\x17E\x0b\xd3Sz\xeaOq\x15\x89\x16M\n\x81\x94G\x9d\x8e\xb0?Y\x91\xdc\x0c@\x07]\x8dr\x12\xfb;\x84\xac\x9e\x16\xefh0\x98\xc4\xbe\x88V\xe9\xaa5\x9a\x1c\x11\n$\xb8-Tx\xfb\xa8\x10\xda\xa8\xb0\x91\x84*\xe2%\xa5\x9a{< Y\xba8\x9bb<\x99Nb_\x98V\xfc\x93\xbb\xfc\xdcfkx\xcf$\xe7\x8e\xe4\x0d\x0e\xbb\xd1\xa4\\\x8d\x87\xd6\xd5\x00\xfbJ<\x0f\x10G\xb7\x1b\x85*~\xf4\xa7F\x144E\xa4>O\x8b*\xf6hY\x07\x11\x899\xf8@\xf2:e#u~\xf5}\xd3\xfd^\xc5\xe9\x8al\xeao\xc6:\xaa\xfa\xdc\xfb\x1b_\x0b\x0cIHY\xf1\xe9m\xa56\xa6\x80\xdcZ\xeb\xd1gke\x15\x02\xdc\x86\x93O\x16m$\x0fe\x93\xc58\x85M\x9d#\xb5\xe9\xee\x9ec\xf6mO\x9d\xe1\xd0=\x13\xbfgN\x1d\xf1\xc7>O\x1d-\xf4\xfe~V\xab\x91tk\x91\xcbFn\x85\\\xb2t\x8c\xd9D\x85\x90\xc3\xc6\xd2Qk\x13\x07n\xbaE\xc5\x91hk_\xc0\xd7\xd7A0\x9e\\\x1f\xd9\xdc/W\xe7\x19\x08\xe4QY\xb2\xbbS\xfd<4\xa6\xcej\x95\xb6\xaa\"\x1d\x06+m+\x89\x01\xa6\x02\x86\xe6<(\xd7\x9chG\xb8\xd4\xf7\x18p\x8fL7\xf7\xbd6\xd0X\x08\x9e#\xbf^\xe9\xc3a8\x1c\x9a1v'\xcc\xd4{\x11\x17^BM\x9f\xbb\xf8\xec\x19\xd3N=\x08\xbb\x81\x88\x92 \xe3I\xec\x83;\x89\xd3\x92\x16\xd5\xf3\xa0\xa2\x85`m]\x1a\x85\x18!\x88\x86Cs){a\x10\x89>\x04\x92\xb8a\xf3%\xcb\xa1'm\xa4}\n\xdb\x07\xa2.<\xac\xbd7>^>\x14\x9e%\xba\xdb\x14\xd0&P\xcc\xd5\xbco\xfc\xc3\xa1I\xa4\xd1\"\x1b6\xfb\x8a\x1d@\x1ew\x16\xf5\x8e\xe3\xdevE\xb1\x03H\xe0\xed\xee\xa0\xf4H*6\xa4\xee\x16 \xcd\x01\nE\x8fr1)Z\x94+I\xf1\x0b\xc9\xa5\xb4\xc3o\xdd\x90\xb3\x08\x13;t n\x0eb\x84N\xa3\xa6\xc2\xb2\x93\x9e\xb9K\xeaU\xc6p\xc8\xbe\xe3\xb4\xacH\xea\xb1l.J\xe0\x02\xc7\"G\x8f\x9b\x8e\x87\xc3\x8bFk9]\x982\xbe\xf9\x07~\xd6\xc3\xbc\xb0\xa7\x0e\xaaU\xc6:q\xc6\x12\x83\x9eDd-\xeb&\"\x1ehM\xd5\xc7\xcc\xa9\xab\xf0\x0fd\xa9\x82!\x021\x00\x8e\xf0\xedV~\x88\xe3Q\x0b\xd9\x00\x9f\x0b\xcblC\x12\xed\x81\x11!\xa4\"\xb3[\x9cG\xce\x84pb\xf7\x98(u\xbe$\x85\x15\xd9\xdc\xe0\xfaWV\x84\xd8e\xbf\xe41L\xb7V8Vq\xaa\xca\xc5\xa9*\x17\xa7\xfb\xe5\xc8f\xd3i\xc2o\x9a\xd8tj\xa9,%\xe7\xfc\xb6\x98\xa3m)\x17\xec /2\x8f\x96%\x8f\xc6Ww\xdf\xdcg4K;\xa1}7\x0b\xf7X\xf6\xe4\x90\x8d\x81\xe6\xee\x91<\xd5pV\x8fX\x05Cp<\xb8\x08\xa4\x7f\x85!\xad\xceb\xa8\xf4\x11R\x86I\xebT\xbd\x81d\x9b{\x99/\x928\x17\xf1X}\xb5\x1a\x08P\x0b\x84\xfe\xd2}}\xb3\x11\xfd\xfe\xbeY\xadf\x88\xa85\xc6\xfe\xd2\xfd\xe3\xfe\xf9\xee\xce\x7f>4\xf2\x9f\xb5\xa1o\xf4\xa1oz\x87\xfe\xf3\xa1\xb1\xff\xb1\xfey\xbdz\xf8\x1b}\xf8}\xfd\xf3\xe2h\xa7]\xe2\xc3\xd3u-\xcao\x9d\x82\xeaY\x98{\xe4\x19\xdfS\x88\xe1\x1c\x96\xd8\x1f)#\x8b\x8e\xe9\xb1i\xc6\xe3\x86bNB\xc4\xcc\xe6\xba\xc0\xb1\x19\x8f#\x96\xaa\\\xe5\xdcS\xca\xa0\xfe\x91z\x95I` \x01\\\x80\xd7\xe3\xa5\xed\xde[d T\xab\xda\xf7\x98\x1dj%\xd2\x96&\xa3\xc2c\x1b\xa7e\x1d\x16\xdb\x92\xdbm\x85\x07b\x85\xfe\x96z\xb3\xec(\xa6=\x8e1q/\xd8\xc1[\xc1\xb8\x86\xb3\xb9\xd7\xc8\xe1\xdd4rR\xf9\x1b{\xd8\x92\x89\xe0M[8\xbem \xc7\xc6\xb8\x92\xecMJ'\x19q$K\xc9\xc4\xb3S\x05@\x8a3<\x85\x8b\x9aeo\xc6\xcd\xc2\xc0x\xba\x98Y=9\x904,\xbe!\x8fNy-Gy\xc2\xda@\x8cgL\x8a\x9a\xc1Y|\xfcd\xbb\x0d\xd9\x1f\xc4\xd5@\x11L\x1c\xa3\xdb\xef\xd4\xe4\xf4i\x99\xc3\xa1\xf2\x13\xf5n(\xc4\x81\xf9]\xbd\x14\xf5\xbb\xb7\xb4\x15Z\xea\xad\xbd\xb5WLz6\xd4\x1e\x8e\xb1;\xf1h\x9c\x98q\xb79\xed\xda\xae\x075\xc7X\x9e\xde\x1c\xa4\xa3\xbd\xb6\xcd\xbbam\xf7\xc9\x10\xf5\x96o\xeb7\x00o\xb7\xad\x14\xd6'\xca\xb4~ 88\xa0\xb2\xaf\xc3\x87\xc0\xc1\xc1X\xed\x81\xd1\x19:\xd2{\xde\x85\xe31\x1c\x90\xa1\x82\xb5\xd6\xf3\xda\xbe\xc3\x8c\xf5\xc6\xdb\xec\xb0E3\x83\x14B\x08 Ap\xc9L\xd7\x02\xdb\x97\x0e|\xc5\xcf\x85\x81\xf6-~\xab\xbb2\xdf\xe1\xe9\xfc\xdd\xd9\xb7\xf3w\xa3\x11*\xf1\x1b\xfb\x9d\x03W\xec\xcfh\xe6\xc0\x1a\xbfe\xdf\xd78\xab\xb9\xa0Y6t\x8f\x8e\xcd\xe0\xe4\x02!\xb8\xc1\xef\xce\xbe\x1d\xcf\x16Z\xb1\xab\xfdbV\x00\xcf\xf1\xf5\xc8\xbc\x19_\xa3\x93SX\xda\xef\x1cl\x7f\xddn\xa7\xf0\x1c\xdeq\x00a-\x95\xb7\xc5;\x8e\xc9w\xe3Yc\xe8\x9ab0BM\xbbd\xe6\x8b\xa6\xd1!\xcb\\\xb7\xb4\x89\xb5F\xb9\xb0ni\x13km*\xe0\xb2\xe6\x0b\x972\xf4\xe3\x1a\xd2Q\xe8 \xc81\xff\xa9F\x14\x8e\xc3c\xd3\\\xeb\xec?A\xc8\x01Y-\xaf\xe1\xc9\x11\x9a\xbf`\xa0\xbed\xff\xbc\xc2\xc5>\xb6_ql_\xe2\x82a\xf7\xb2\x8e\x98P$\xaf\x99\xb7l\xd8\x12\xaeK\xfb\xb2\xe6\x14\x00\x0d\x80\x8d\xc5t\x84[\xb6\x10\x93\x825\xecm><\x1c\x9a\x8ci5\xdb9z=x\x8b\x1c84\xa2>>\xd6\x1d\x85\xde\xd8\x1d\xa3i\x83\xd4\x0c\x88\x9b\xc5\x87\xe4D\x1f\xd8\xc2D\xfc\x8b\xc0\x16\x8d\xdd\x05v[\x846`\xa3}\x97&w`\xbdo\x12\xea\x9d`\xe5\x13\xc2)P\xcd\xf1\xbb\x17\xa8\xe1\x92\x02\xdf\x1cr\xfc^+\xd7\xafK\x8a}\xcf\xaft\x97\xab{\x06\x9a\xcd\x0e1\x0cuc\xa3\x81\xf8 \x9a\xb4\xaf\x80\xbc\xc7Q\x16\xc9T\"\xf4\x16\x1fMa=\x1c\x9a\xa2)\x9c\x98\x1f5\xef\x0eB\xf0\x11\xaf\xcc\x8f \x1cdW\x1d/\xc3G\x04\xd7\x1d\x9f\xc4G\x04Wg\x05\xdf\xff\xbcBp}^r\x82\xbf\x96\x07\xcb\xe4e\xa5\xf8\xed\xfef\x0bVAwjV\xf1\xb2\xfe\x96\x93\x88UH]V\x91\xe4\xdb\xba\xd8H&\x8bH\x9d\xb0F\xd3\xf1rd\xd2\x9aw\xa1\xe3t\xdf3\x0f\x97\xc3\xa1\xf9=\xcex\x9cQs\xd8?\x8378\xe7i\xcd\x81~\x04_\x99T\xdf\xe0\xb7\x0b\xdb\xb1\xbe\xf2\xb3\xa5\x9fY\xca\x17\xb5\xb9r`\x92\xe2\xc0|\x8bn\xdf\x89\x99\xe28\xfe\x81U\xfb\xcc~Ma\xc3\xfe|\xe1\xbfy#\xef\xf1\x14>4\xb1\x9e\xef\xcf>\xcc\xdf\x8fF\xe8#\xfe\xc1~\xef\xe0\xcbEl\xbe\xb3\xdf;\x8c\x9a\x90\xc5\x7f}l\xd4\xa4\x8f\xe7\xd3\xe1\xd0d\x0d\x8f\xf0G\x04\xc5\xd9t8,\xcf\xa7\x8b\x8fg\xd3\xc5\x17\x9e\xeaN\x88[\x9a\x1f\x91\xb5\x11\x85\xe4_\x99<6Y\xc9\xd2*\x10\x82\xafB7\xf8\xa8\"\xc1>\xf2\xbe\xf9\x18x\xdf\xfc\x97\xa4\x99\x0b\xf3#\xd2\xe0\xa8\xab\xaa\xfb\x0d6\xf8\xbb\x0e\x95|m{\xb50\xff\x83\xdf\nP\x17\xa5\xf5\x1d|\xc2\xe6\x7f\xc6\xaf\xd0\xc9\x8b\xe3\xbd\n\xf0\xe9\x08+\x1b\xeaS\xed?jC0\xc6\xa7\xf0\xa9)\x84\x90\xf5\xa9\xc7\x03\xc2Sn\xc4.\x12\xfe\xa4oBK\xa4g\xe2\xfa^\xa3\x89\x9d\x93)\xdf\xde\xbca\x9c\x15\xef\x15\x84N1\xee\xcc\xc0\xd2\xf3\xdc\xdbD\xb3c\xd8\xad\xf5\xb0\xc6\xd5Q\xf1v\xaa\xa6\x8b\xf6C\xc4\xd5\xd3\xbe\xacZ\x07\xe0\x17|\xe0\x17\x07\xf6.\xf9~\xac8\xedS_mv\xb2\xcf\x9c\xeb\x9b\xaa\x82\xb3\xe9v\x1b\x9c\xeb\xbb\x1d*(\xc4\xb3\x82?\xb37 ..L\xadY\x9b8\x88\xdfJ\xc6`\x94\xfb\xa8nk\x1f\xd5\xc7\xaeM\x1d\xf0\x04\x9bR\x9b\x9a*f\x18\xc4%\xcb\xbe\xbeU\xe9\x91\xc4\xe3\xca\x92I\xc1\x07\xa2m=\xeaW\x9b\xd5\xa5:\x11\xa8\nQ}\xf3_\xbb\xe6\xda\xb3\x00mG&\xdf)\xd4\xa3\xf6\xa4\x1c]\xc48\x92ju\xfb\x16p\x03Y1v\xcf\xa6\x0b\x99\xad]Xm +\xaa\xb5#e\x0c\xb9\x183&\x115\\D\xe6H\xeeU\xf7\xa3e!\x08\xf9.\"\x0f\xc56]\x84\x161^Z\xc1p\xa8v\xad\xcf)\xaf\x19\xd8\xb4\x15\x18\x16\xa3El\x93\x7f\xc6\xb2\x94c\xc5\xca\xcd\xb9Ofm\xdc5\xdb\xc8\xedx5\x85-\x8d\x87A\xcc(@y/\x19)+\xd7\x9e\xc4\x94\xf2\xdcI\xa7\xe9\n\x93\xe3}\xf2U\xb1hm\xee\x02\xf2\xe4\x9dd\x1d}\x0f$\xcc\x03\xac\xc7\xb4,\x02\xcb\x0e\x1c\xb8\xae7\xf4a\x83\x03\xbe\xbb\x8a3\xee\xc5\x80\x80\xa9\x17\x99\x19A\x00\xf2\n\x81Jm\xe4\x87r\n\xd7\xb5m\xbd0KL\x17\xfb\xe9\x0d\xb1\xb6\xa3\xa5\xcc\xfd\xa2\x102\xd56?\x9f.\xf2\xf1\xcc\xca!i\x9c\xd5+\xb8l+b\xe3\x19L\xa1\x84\x12!\xcb\x9b\xaf\xb1\x08N\xb8\xc2\xd3\xf9\xd5\xd9\xf5\xfc\x8a\xa9\xa1l4W\xfc\x06\xa1\x8b\xe1p\x831\x96'w\xben\xb77\xcd\xcdr7\xf8h\xba[\x9eO\x17E\xcd3\xd2cS\xe8\x1e\x9bq\x84N\x96\x08\x8dfV\x81g\xb09\x8b\xb6[\xde\xd0p\x98sw\xaay\x89\xd7\xb0\x1e\xe1\x02Y\xe6%\xce\xc7\x05\xe4c\\ (qg\x85^\xc1\x86\xc7\xa8q\xbd\xf3\x10BJ\x08\x99E$8\xc0=c/\xc631z\x15.\x157\xdb\x9d\xb3ElO\x19\x05\xef\x9b=\xeaby\xfc\xf5^\xdbG\x15\xed\x0b}imn\xd4\xf6\x0d\xed5\x80\xe2\xbe\x02-\x03h\xfe\xf5\x8e\x10\x1a\xd1\xd5C4\xf3hO3\x8f\xfb4\xf3h$\x93\xd5&$\x0f\xcerA\xec\xb4\"]k\x0fj\x1e}\x1c\x8dLW\xd3\xdac\x8du\xd0\x87\n\xe6\xbd\x82\x7f\xa5`~X\xe3\xffc\x82\xf9\x01\x91E\xfa\xfdrj\xa9y}\xe2\xf9\xcf\x08\xde\xbe\xa0 =&\xc8\x93\x92\xb4\x9d\xd1/V;eH+\xb8\xa7_\xae\xaa\x1b=\xb4\xaa\xd0\xde\x97W\xfbb]\x91\xbb'\x89\x85\xc8\x0d!\xaa\xb5\x15~\x8b\xa2P\xc0\x08B\x8b\x10G\x16\x1d\x0e\x95iw\xee.BLm\xd7\xb1<\xdbu\xce\xa6\x8b\xb0\xbe\x89\xb7%gY\xee\xb9\x96\xab\xbd$a \xabN\xd6\x9f\x8e`\x9c\xf9\xb08\xf4Z\xe7j\xda\xa1!M\x98\x92\x98g!\xd7\xb4\x07}\xe6!\x0e\xf4\xc3\x1b\x10\xd7\xfb\x97\xe1\xc9)\x82e\xbf\x08\xf4m\xc2\x86h^\xe0\x18\"\x1c\x8fg\xc8bI\xe7EJ\xb26\xb3\xeb\x8b9e\xf2!Q'\xb3\xe4\xae\x9b\xd2\x9e\x86C39\xc8\xad\x13\xa0\xdc\xbc\xaa\xd94\x1bO\xd5a\xd3\xec\xff\x13H\xfa\xdf#^\xda\xa7\x8e\xc5\x12`i\xcf\x1c\xec\xb3\x7f\xdaY3\x07\xa2\xfd\x90\xb8x?\x80\xaeV\x94Kj\x88\xe3\xa6\x11\x8e\x98\xe1lM\xad:zW\xe46<:j\x98r\xacs\xe1x,s\xca\x1e\xae\xdf\x91\x04B<\xf8\xa4\"u\x92R\x85\xe9\x01nm<\x99L\xe9\xca\xb0\x02\x9d\xbf\x88\x99\xef\x9c\x11er\xa8VS\xebS\xa0\xa8>b\xbc\xdd\n\xfe\xa2\x9d\xdc|\xb8v\xd9=d)\xa3\x9dB*\xb6\x88\x9eW\xb2L}9\xbb\xe0\xc1\x1abD\x14\xbb\xb7h'Y\x7fI\x8cz\xfdT\x13\xd1\x0f\\\xf7\xb1\xa2\xfa\xe45r@\xcc\x86P7\xff\xc4)\x18m\xa2\xf9U\xbfs\x9f&\xb4\x92\xd7(\xc9\x81z\x8e\xba\xdf\xdf\xd5!\x94w\xfd\x17\x86%\xd9\x9ed\x8e|\xdb\xdbm`e\x8a\x94v\xa7\x7f\xde)\xaf\xbd/e\xb6KV\x9d\x92r\x96\x89\xdc\x9ai\x83\x8e\xfdI\xec\xeb\xb4l\xb3\x04G\x9d\x91\x96S\xbew\x8e\x86_a\x0d\x1a\xe4\xf7qu\x11g\xdf#\xf3\xb8x:6M\xbf\xd6h\xd0I\xb3\xd2P}\x83\\+\x96N{\xf0\xcb@6\x19k\xc7\x1f\x9a\xe3G=LR.(ugiK~7Z\xe7\x14\xa6@\xc7\xea\x8a\xd0\xb6\x0f;\x80\xa0\x1e\xb86\x05{\xc4\xd3\xb2W\x18\x97\xf2\xef\x1a\xbcwh\xf0\xb4g\xf0\xddW\xd0\x8c\xe6\x99\x00~\xb3\xe8\xa1\xc1\xd3\x07\x0e\xfeN\xc5\x04\xfc\xf1\xec\xae\x12O\xd0x\x06\x14h\x8d\xa4\xee1\xf9^\xfcL\xff\x18~\xc6\xfb\xc8\xd1\x1e\x8c3\x10\xb3vz\x89NA;C\xed(J9\xcd\xf4\xf8\xb4\xff\xa4l\xf3\x10X\x1b\xe9\xe1A\xa4\x87\x0fB\xba\x0f\xed\xc9\xe9\x19\x0b\xa3\xbd\xf1\x0cB\x08\xef\x8b\x16my\xdd\xbbg\xd5\x80\x1fz<\xfa\xf6\xde8M\xf1\xce\xc3\xe9\xdc;#\xfc]\x8e\x1ef\xe5u\x03;Z\x1c\xc4(\x8c\x91\xb7'\x16m\x9e,\xd8\xce\xbc\xbdD\x9a\xfd\xcd\x83\x8c\xee\xee\x0e\xf3\x99\xb1\xd7\xdf$\x9f\xf1\xbeZ\xba\x87==\xd4\x99\x92\x88w\xf6SM{\xfa\xa9\xa6\xb2\x1f\xf7\xae\x18\x89<\xa6\xf8\xc5=*W\x1e\xf7X\xa5^\xdbA8\x85x\xfe\xe2\xa0\xbe\xe5\xb5\xf4\xad\x07\xe8(R%\xf3\xa4\xb5\xea\xd5\xd6\xea\x01\x0dE\xd9:\xda\x15\x90\xe2v\nq)\xaer\x9fL\xe5\x15M*a\x1e\xf3\xebLF\xd8\xb3cG]N[\x91\x04G\x87L\xab\x82\xc7'\xd7\xa6)W\xf4\xf6\x18\xc4>\xa3f+\xff\xffI\xa5\xe6w\x9aX\xf2\xb1\xc5N\xb2\xe6\x00<\x9eM\xa7\x07\xcf\xe2i/O2)\xfa\xcf{J\xa8}\x9c\x96\xcb\xeeO\xe8Pb\x99|\xe6\xba\xbf8\x1d\xdc\xa7[\xf5iV\xbes\xb7v\xe2\x83\x87\xf69\x8f\xeb`\xaf\xab\xecxB\xd9Q\x1c\x95\x03\xd32szh\xa5\xa3\x91(I!H\x15\xea\xeb\x01\xb5\x977\xf9Y\x04\x99,\x90\xc7#\xeeO\x8f\xdd\xc9\xa77\xcaZ\x95\xd6\xaf\xda\xaeb\xb3\x07+\x1c/D\xa9c3>y\xfct\x8a\xaci\x13\x93?\xbf\xc4\x17zdd\x8e\xa7\xf3\xfc\xecr\x9e\x8fF\xe86\xc5+\xc8\xf0\n\x12\x1e+\x92\xe1\xd5hyl^\xd8\xb9s\x92\x88\xab\xf0\x08\xc68W{O\x85\x02\xafM\x13\xf9?\xfb\x92kB\xf0y\xf8\x8f\\}{\xa2\xaf\x80\x80\x1f\x08cR\xeeSL\xc5D\x87L\x82\x8d#>\x08\x0f\n4_\xe1lw\x8f@\xeb\x9c\xdc\xbc\xcbK '\xa1>\xac\xd9\x9e\x04\xf9v\xce\xd1\x8b{\xa5_8\x1c\x12=\xa3\xf3\xf5h\x84Vb\x83\xd5\xb7\xd7\xcey>\xce\xd4\xb5\xbb\xcd\xab\xce<\n\x9b\x8d`\xed `\xff\x9e\x15\xa3C\xc5JQl.q\xc4\x10@\xc73G\x04EJ,\x94*\xb1\xbd\x8c\xb1\xcdg\xd0i/%\xbcj/\x0f\\Bk\xd9\xaa W\xb5\x14q\x05\xd78<1/\xc6\xf1hV\xefrt\xa7\xd3LT\xb8\x9b*\xa0\x9d\xe2C\x10\x8e\xb9Vq\xb8\x80\xdeEz\xb6\xea\x8893\x1d\xc7\xe8\xf8z\x94@tr\n=\x0d@\x1by\xfa%\x07\xed\x9c\xd7}'D\xa0:/\xbb=V\xff\xa3=\" \x8d\x95]G\xd8\xe7\xb2OT{2\"\xee\x0b\xa8\x0b\x14\xe3\x1c\x1d_\xb7\xb2\xff]\xf7\xa7?bn\xe8\xa9\xfdc&\x8dK\xben\x7f\xb5\x0f\x00S)\xee\x02\x90\xe5k\x9bB\x7f\xbc\x9f'Z?=\xf9c^B\xf6\xa4\xbf\xc3~og\xe5=\x83*\xfe\xa2A\xf5\xf5\xa3\x0f\xaa'\xff\x8f\x0f\xea\xf2>R\xe9-\xf0\xac\xeeL{\x85\xbe\xd5\x97\xcc\x16B\x8e/\xec\xab;\x17v\x1fl\x9d6\xfa\xd0;\xbe\xd2@}@\xf9\xd1U\x07\xb4\x87a\xa9\xbfe\xbd\xf36)\xf4\x97\x1fu\xcb\xdf\x0d \x03E\xd7{\xbf\xeb\xbd\x06Xl\x8fj\x1a\x85\xba\x1a4\xf6k'\xf7MN\xd5\xa3t\xa4\x08K\xec\xef@t\xd2\xa3Kj-IH\x9a\xebN\x84B\xc4`\xf9\x9e\xc1\xf2[\xbe)hH7\xd6 \x7f\x11\xdb\xcc7h\xf1ky\xfc\x8f\x93\xb8O\xd7Vo\\\xe8\xe7\xa4\x85\xd6,\x01\x96w\x92\xb6<\xf3\n\xec\xbd\xbb\x89<~[\x90\xe9a\xf1\x88\xa1\xbc\x8d\xd9\xeb\xdco\xce+\xcbc\xf7\xef\xdb\xcf\xd6\xa9\xab\x96\x98\x01\xa0\x0f\xbd9pg\xdb\x04\\\x07l\x0f|\xc7\xe1\x05EC<\xb7g\x0e\xf4V~\x0b\xa9\x88\x980\x0d\xfe\xc7\x00[\x96sd+B><\x00 \xad)Q\xa7i\x8b\x15V\xed)\x0b\xb4\xb7E\x08\x0f\xb6\xa9\xea\xb5[\x85P\xb5\xcb$\xcb\xef\x83\x92\xd5\xd8\x87\xb1yz\xf6\x10\xd1\x899\xe3%\xb5cZ\x87Jk\x14\xb1\xf7\x04\x1f\xba\x95\xb7\xd6\xf3\xf4A\x9aU\x03\xbe\xa5\xc8z\xa7\xbe\x81v\xf0\x9b\xb8\xce\xc7j\xabs\xbe\xe9\"\xf5N\xca\x0e~c6\xff:!\x15\xe5/\x1c\xb4\xdf\xbe!\xcd\xd3\xc4t.mu\xb5\x1e\xc4\x95x.\x02*|hZ\xac\x12\xa6\xf6\xcc\xb1\xf6R}\xd3\xd3\x9e!\xe8m\x8e\xb4\x9b\x13\xf1*\x9d\xd6D\"oL\xbeT\xb3\x83zb:c\x15\x9b\x14\xefG\xa3\xfa\x8e\xd8\xfa\xd9}\x0fAJ\xaf\x07\xdf +\xd8\x03\xf1\xe4\xb9\xc6\x0cZn0\x89j-\xbb\x0f\xe1]\x1fV\x07\x9az\xbe\xda\xa5\xfaZ\xea^\xc4\xd4\xdfR\xb7T?L\xdamH}cj\x15\xe8k\xa1\xf1\xaa\xeeQ\x86l\xa2)\xd1\x0fA\xd7@\xac{f\x19=Uv\x08\xde\xe0\xd0\xfc\x1e:\x92\xa0\x0e\xb0}s\xc0\x99\xe1j\x8e\x0c\xb1\xd2\x0e?w\x0c\x81\xe0\xae\x01\x0f\xee\xae\xb9k\xd0\xcb]\xa1\xb5\xf3$\xdf\x17R\xcf\xa0\x19q\xca\x14\x9f1\x7f\x0d\xcd\x00\xf1\xec\x90\xab^\x1b\xa2 n\xed \xc9\xf3$\x0eS\xcb\xa8\xb2\xbc~bH.Q\xbd\xf1z\x00=K\xb3~\x0fX\x01\xa2\x7f\xe8\x8e\xfc\xfe\xfb\xa1[o9\xe9\xc1\x85\xb8u\xf1 N\x12Q\x981\xb8\x1e\xd6\xab\xb984P\xf9\xf1yy\x9f\xb3\xdc\x82\xee\x9d\x0d\xfdR\xee?\x83\xeb\x1d\xfcv@V\xfb\xda\xc3\xf8\xfb`\x8a\\\xeeI\x0d'.\x0d\xe3\xf4\x13\xe1o\x8d\x85\x13\xc6\x0b\xbed\xa6+\xae\x1f\x18M\xbe\x01\xfes\xc6~\xaa\xc7\xeeg<4G\xfaC\xa3\xb3x\x1e\x8dF(\xe4(\xe5U\xa3\xa6jTW\xf5\x05rC9\x15\xa6\xb8_\x9b\xa70l\xb7\xc3\xf0~\xd6\xf6kD\xca/\xaap\\~\xca\xe2\xb4z#@\xd6\xeb\xe8\x0d\xfc\xd2\x9c5\xeb\xc3\x1c\x97|\xfd\n\x8a\xaf\x02C!V'\x99\xf7\xf0'r\xe7\xcb\x16\xee\x96\x13Rx\x02\xb50\x05\xb9-p4{\xd8\xb8\x96\x7f\xd5\xb8\xd4\xc5\xe3\xcb\x06\xcf\x91J\x11x\x96#\xefW\xa5\x1e2\xfa\xde\xb1+\xba\x01_a\xa2i\x8c!aY\x93\x87(\xc1_#\x91\xb5\xa3=\x90\xe3\x06\xdc\xff]\xf4I\xe4\x1c\xd4\x07\xf7t\xccF\x93&`\xdb.x\x0e\xd8\xee\xc8o\xfe\x8e(\xfb\xd5\xfcuD\x1c\xe2aUCo^\xee\x11\x10\xbeG@\xda\xbcA\xbeB\xddl\x8b\xeb\xe6\x03K\x06~\x87\xee}J\x89\x16\xc7\xa1Z\x06\xb9\xa9/{w\xc5\xf6\x1a_\xfc~\xb3\x0d\xcc\x9f#\xf7l\xdf\xc1\x980#\x85\xfdr\xf9M\xb4=\x9b}\xc4\xd9\x07\xa4|\x08$\x8c%r\xbc\xcbG5\xea\x8bu\xe5\xc3\x1e\xc4\x0e\x1c\x07\x1fMe\xb6\xd7\xca\xa6\xd8\xb3\x03\x07|\x9br3J=E\x13\xc0\x0cA\x0f\x90\xd4\x81\x10\x07\xea \x11\xb7\xd5T]9\x84)\xb8v\xe04xWh\x12\x89\x0ef?\xee\xd5\xe0\xfa\xd1\xde\x87\xe28\x10\xb8\xc5\x98\xa0\xdb\x1a\x0c\x7f4c\x80\xecC!'KP\xd1\xee.\xfdO\xdb\xda\xadA\xf0d\xc8H=xO\x82\xe0\xda\x9e\x04\xc1U x0\x93!k\xbb\x033~\x87\xe6\xb8o\x88\xff\xdc6\x8a\x7fQV\xb1\xf2\x0e\x1c\x16\x84\xf7\xecA\xd4\x83k\x0d\xb6\xe7\xfa\xce\x96$\x97T\xe7\xff^y\xbew\xa9'\x15\x97z\x06\xe2\x1c++o\x1b\x9cm\x18\xa3@<\xf8#\xf7\x19*\xfe\x04\x1aw^\xc8\xc3\xf8J#S{e\x87\xb5\x04\xae3\xbf\xed\xd7\x99\xbd\xfa`\xe6\xfc\xedA\xbd\x99\x15\x02*4b\x8a\xa9\xae\x11\xd3~\x8dXL\xc7\x01%[\xbeT\xf7;\xb4\xe6\xfa\xbda\xa3\xa0 \xa9\xe2+j@vE\x8b \xc9\xae-\xf5&hW\xb7\xf6`E\x8a0N-c\x9ao\x0c\xc8\x89\xef\xc7i(\xbf~\xa7\xdaM\xefT\xbb=}\xbc\x7f\x95\xda\x1d\xe0GgWVXd\xeb|\xe0eY\xe1\x8b'\xed\xb11\x1dL\x0d\x91R\xc6_)6\x1e\x8d:\xcd\x8f\x8c\x811\xea\xb67zd<\x1a=\x1a\xf0\xf7\x80q\xf3\x803q\xcb,YWt^e\xb95\x9d\xf3'.\xa7s\x81\xc9\x9e\x86\xf3\xcd\\\xc2\xdb\xd7C\xbe\x99\x1b\xe7g'\x12\xea\xf3G\xadqJ\x9e\xe7/\x89G\xd3\xea\xfb/\x1f\xde\x9b\x86K\x83\xac\xa0\xafR\xdf\xa8\x0d\x1b^\x15wi#\x8a\x13\xbf\xa0\xa9\x89\xea\x8b\x8c\xc5\xaa\xa6~\xfd\xe6j^P\x91\x86\x0d\xe3\x81\xba\xaf\xed\xe8\x8fS\x82\x08\x89K\xf1\x14V\x0d\x8fK\xcfV\xf3\x94\xa9\xb0v\xea`\xc3\x18\xb9v\xca5X\x03\xe4\xefY\xbd\xbf\x1b\xe1P\xb1?\x1e\xa8\x1c\xe0@\x9a\x0f\x01\xc4\xd2\x94`3\xc0\xf4\x18\x1f\x1b\x01IJj\x0c\x1eY*\xed'\x89\xd9G\xa3\x80\xe1\xd2\x90\xc9\xdc-\xcaR\xfd\xd1#c\xf0\x08\x96\x98\xca\xa6\x98\x12\xd4\xb4\xc4\x1a\xaa\xaf\xedb\xe5)+\xaf\xcaT\xc5\x9au\x06\x178\x14\xe1\xf7\xa1\x1d\xd6g3\x9d\x85\xb1\x19\x18\x96a@\xc2\xa9\x8e3\x8f\xbf\x88\xea\x06\x8c\xecb\x9f?h\xc2\xdb}4\"\"9\x1e-\xef\xa2HA\x8b\xf9F\x90fMy-\xb2\x94\xbd0\xba\x144k\xec\x81\xc5\xa8R\xad}\xd6\x8ad\n\xec\xa7\x80-'U\x84\x8d\xd5\xe0\xd1\x88\x8d!\x19\x18\xa3P\xbd\xae00\x10\x1f\xd6\xc5\xe8\x11\x7f\x83\xc5\x180\xea\xe6\xa387 9h'h\xba\xb2\xa05q\xa5\xb3\xf23\x8d1\x05\x9f\xfd\x13K\ny\x18UD\xbdT\x11(\xaa\x08\x1fF\x15a?U\xb0i\xcf\xaeH2\xf8Cs\xc5\xa6\x88\x11(\x9b\x08\xfe\x84\xbbe\x8c\\\xf1\xa5\xe6\x85\x1e\x9f\x8a\x049\x8d\"\x85\x8d\x88\xb3\x0c\xd6\xf7\xf9#\xb8\xb8\xc3\x02\xd1\xec\x0fX\n\xbcv\xee0\x9d\xc7\x81\x19\xf2C\xeb\x02\xd5\x861\x8f\xc6,AX\\<\x8ew\n\x91\xfcD\x90hW\xc3\xba\x13/+\xcd\x10\x1d\x07\x08V\x98j\xe9e\x9c\xca\xf4t\xaf|\xc4\xd3\xb3\xbd\xf2<\xbd>&9\x1c\xae0\xc6\x19\xdf\x00\x88\xc6\xe1\x19\xef\xbd\x061\xc1\xac\xdd\x00V8\xc3T\x1dM\xd7\xeb\x0d\x87\xaa\xd2\xc20,\xf3\x02\xdb\xfe8\x00:\x0e\x80U\xa3\xa3@a\xc1\x81\x1c\xc7\x0f\xa6\xa8Y\x0f5\xc5\x82\x9a.\xf1\xf2a\xd4\xb4\xec\xa3\xa6\xe2\x7f\x91\x89\xe4\xa3\xcb\xff\xbb\x98\x88\xcfE\x03\x1d\x19\x83k\xc2X\x87\xceK\x1e\x0d6\x83\x1eFR\xfcE\xa6\xa52$\x1b\xd3R\x19\x9b\x9ai\xb9\xe7\xaej\xc4n\xeb\x05\xb7\xc3Fhc\x1b\xd4\xca*\xd9WV\x89TVu\xb0\x95\xd0^\xb4\xfa\xbcS/p\xe5]\x18\xb5x\x1f)S\xe0\xcf\x98\xb4\xbei\xfc]\x91\x921\"j\xc7\xa1\x1e\x8f\xbb?\x1eW\x8e\xc7\xb3\xa7\xce$[W\x12Q\xf4\xa1\xb6k\xa7Kq!>6\x0ch\xacIi\xc8\n\x03!\x10\x06\xc2\xa8\x03\x98\x1d:=\xb0\xb1\xd4~\xf0d\x8b\xb3yx\xd6\xbc;3\x1a\xa1.8\xa1\x83\xb47\xfa\x1fd\xa0\xfeY$\xf6\xcdXR(G\xc2\xb7>44#_\xfe3\xff\xea\x7f\xe9\x895\xc3\x07U\n_|G\x7fp\x1f\x08\x9f\x10\x01#\x00\xa0\n\x9a\xbf\x03\x08\x04E\xcc\x85\x10\xbf\xd7^\x8d\xcbA\x04\x10\xc0\n\x88\xb0]\xe7\x05\x11`\xa4\xec\x18,X\xde3\xc9\xb6\x9d\xf0\n\x92C\x98\x0b\x05Q\xb8l\xa8\xc8\xe7\xde8\xcag\xdd\xb0\x9f\x89j\xf4\xde\xec\x8e\x00\xd05'B\xd7\xa4.|G\x0e\xf4\xc5\x8cV\x0bL\xf4\x17\x19-Go\xcf\xea\x8a\xf0\x96\xa4\n\x08X\xc9\xcd\xcc\xe4}o\x01\xe4\x8d\xe5&\xaf/\xd4yy\xae\xc6\xc9\xb5\x041\x19\"a\x17g4\xec*+PpH1j7\x07\x91\x85\xddx\x84\xde1\xbfm\x17\x8c\x80W\xce%w\xbe-\x9d\xad\xa5\xfc\xe1\xcb\x0b\x04\x85L=\x83\x83\xc7Y[\xbf\x18\xf5\x86\xae$\xbfIc!\xcb\xd0\xd4\x83j/D\x9c\x8f\xe7\x92\xd8\x82\xb5\xa8\x0f\xbd\xd7\x1aA\xacAC\x80d\xdb<>\x80\x87\xaeb\x07\x99\xbf@\x90\\-\xaf\xa0B\xb1\xb4\xca\xc0@\x1bd\xedRv\xae\xb6\xd8\x1c,r\x881h\xe6A\xbbc\xe7\xde-k:c\xe7Rg\x0e\x80\xd4CPz\x14Y\x00v\xf6;.HB_\xff4\xce\xb5P\x95^3\xb0\x16\x90\xddop\x8f\x12\xe7\xc0\x18\xd4{\xc8\x02(\xe0L\xa7_[)\xc0\x1e@$P)\xcf\xb1\xb2z\x19\x1a\x1cH\xe8\x9a\x95X\xe1\x1d\x931\x881\xe0\x94\xae\x19\xabv\xda\xf9A\x94s\x866P\xacI\x08>\xdaqA\x1e\x85\xe4mN\xa8\xb2K.\x01\xd0\x9d1y\x07Hd\x84\xdb'\xab,\xd4S\x8e\xacl\xd2\x95\x1a\xac\xdb$.l\xd0\x0d)\xa2\x00`@\x00\xe4<7,\x17\xc6\"o\xa5~\xefF/\xd9\x01\xa2Pt\xa6\x0d\xd2\xc7\x91\xe5f\x17DP\x86\xf6\xbd\xc2\xe0\xc01\xbc/R\xabO\x82\xd1\x1e\xa7\xc4\x18Dz\x17I\xe9\x95\xbc\xcdy\xd1\x14\x1d\xa1\x95\x82W\xd0\xed \x02\xb5\xd4\xf3\xc7//\xd0L\x03\xa8\x10\x17\xd7\x18\x1c<\xca\xf8\xd8\xd3,-_\xc3\xe9\xd3\xb7\x90fU@\xbb%\x82\x8d\x1d\xc6\xc8\xae;\xa0(\xc0[\xde\x12Av\x85\x00P1\x10;\xc2f\x06\xaa \xe0\xb6'\xd0\xb9Z\n\x02`(\x97\xe7\x99:\xf4_\xcc\xce\xde\xc9\x993\xf7\x03\x80t!\x88\n\x11Qd1\x08\x82\x9e\xd3-\xba\x11N\xf4\x82)D\xce=\xda\x9d\x13\xf0\x08\x1e%\xd8\xdc\xf9\x01\xb7\xcb|\x94\x8a\xcb\x84\xe0XY\xb9\xb2\xf7fd\x0b\xc2Q\x88-\x91O\x88[\x0d4*\xa2\x85R\xef\xca\xed\xd9\xb8v\x07P\x80\xb4\x85ilP\xab\x0c\x92\x89\xa0\"\xbb\x87\xdfre\x8e\xe1\xe1\xd7Y\\\xbc\x8efs\x04\xd0\x9ep\x1bE\x8eRs\x8d\xe1\xa3\xcf`\x8c!\xf4\x8f\xb0y\xf1\x0d`,\x08\xdd\x02O{\xa2\x8dv\xc3g\x0fq\xb7O\xf2n\xcd\xbf\x1bD\xc8\xbf\x06\x14\xc1\x8b\xc1\xae/P:\xfe\x1c\xc9\xf5\x0f\xd3\xb2\x06\xcd+\xe2\x1e\x10\x11e|\xe2qF\xda\x10++W0;{\x07\xaa\xa6gA\x8d\x11\xac1\x94\x97g\xb1\x1ah\xdc\xf00\xce\x08\xb1\xb1\xd8\xf5EL\xbb_\xabC\x84\x91Id\xe5,f}\x01\xad\x0c\x12\x06\xc70\xed\xefa\xe4\x00R[\x011h\xb9\x1f\xb3\xd4\xee\x1bzsL\xea\xeb\x84}\x07\xd0\xbe!\xcc\xeaz\xa8\x03N\x1bB6V\x10\x9f\xe1U\xf1a[\x1e\xa9T\xcer\xf9\xe5\x7f\x8d\xb5 \x0b\x8b\xd7S\xaf\x8f\x83\x04PC\xb1\xb4\xc2\xdc\xdc-,-]\x07\xa2\x80\x80\x13l\xac\x98\x8de\xca\xb3\x87\x11`\xf3\xd0\xd5\x84\x89\xcb\x88\x93\x16\xf6\xe4+h\xb1\x82?x\x15vh\x0c9s\x98P\xa8\xe0\xa3b\xc7w\\\x1b&\xdb\xf2\x9fV\x03\xe33\xb2R?\x1c\xb8\nw\xecy\xe4\xd8\x0b\x84\x81}\xf8\x03W`\xdb\xf3\x9b\x13/\xa3\xa5\ni;\x90\x98\xad\xf9\x8e\xbfHV\xa8\xd0\x1a\x1c#\xf5\x8a\xf7\x01r\xc9\xd7\xfe\xda\xef\x84\xdf~\xee\xac4\x1b\xfbI\x93>\x94\x9c\xd0\xb5\x98\x98|\x94\xb9\xb3\xb7\x93t\xccJ\xbb\xd5mT\x8a(:C\xec3\x9c\x11l\x14cE\xda\x0dL\x96\x80\x08\xd8\x084 \xc1\xa3\xc6m+\xfa\x02\x88\x01\x14\xcd\xa3\x13\x00\x1a\x90,\x05\xeb\xc0X\x807wZ\xa4\xf3\xfb\x00\xf84%\x15C\xa6\xb0\xb1\x99\x91\xa6\x1e\x02|\xe1\xad\x15u\x9b\xf5 \xd6V\xaa \xddH\x0dj\xa9V\xa7\x01H\x92*\xa0=a4\xa8\x92*\xa8\x8d\xc8\x04\xac\x0f\x98\xdc\xf9D\x1c\x82 >\x80\x08\x88\x83|j%\x00\x02\x04r\xf5~61(\x90\xaf4\x12\xa1(\xea\x95\x00\x04qx\x94V\xe6I\xb3\xd0s,\xe4\x10@BO\x08\xee\x1f8\xc2\xe4\xe4c\xcc\x9e\xbd\x03\xd4\xe4\xa6&\xf9=(!\xf1d\xc6\x10\x04\x8c\x80\xa1\xf7\xb4\x06\x14\xc9\xe7\xea\x91\x82\xa2\x17T*k\xcfk\xde\x142\x0d$I\x06As\x88\x1c\x04e\x1bD`b\xe2\x89N[]\xbd\x94\xf5\xb5\x8b\xba&e\xf2\xe7u\x1fb4\xf5x\x85P\xc8C\xac\x80hOq\xff\xb6\x1f\x95i\xdeB\x16\xd0v#h\xcf\x80\xa3+a\xdf\xbe\x97\x19\x1f\x7f\x92\xf9\xf9\x9b\x99\x99\xb9\x0b\xef\x8b\x80v\x00\xc6\xdbp\x1b\xebSll\x1c\x02\xd1\xfcqV\xa1\x18\xe5\x19\\\xbb\x10\xe7%\xd1\xf3\x03\xc9y\xa1\xf3\x7f7\x87\xe0\\y\xa4\\\x99\xe7\xc0\x81\xffca\xe1&\xa6\xa7\x1f@\xd5\x00\xda\x1d\xf3>\xa6V\x9b\x00r3\x03\xd4\x07\xf0\xa1\xe7\xd1V\xf7\xba\xe4\xbb\xc0\xf5B\x044\x0d;.\xda\xee#\x08\x03\x9dg\x8e\xb8\x13n5\x98n\xb8\x15\xe3\xe9\x1f8\xc5\xf2\xd2Uh\xb0;\x82\x02h+\x83\xd8\x815 \xecMy\xaduN\x05\x05\x1f\xba\x8b\x86\xeev\x1c\xa4\x821)\xd5\xea\x0cg\xdb\xce\x9d\xb4\xf2P\xab\x06c\x93\xb6\xb9\xbdD\x14\xd5h\xe5\xfd=\xc7\x95\x92\xc3$)8\x83X\x0bF\xba\x03\x9a\xdf\x84 \xe0\xcc9nVQ\x1f \xf3t\xe7D\x90\xbc\xaa%\x04\xe8\x1e/\x9d_\xceEu\xc6\xc6\x9f&\xcb\xca,-]\xd3]\xf1\xc1\xa1#\x8c\x8d=C\x7f\xdf)NO?\x08\xe2\xc0\x91\xe7\x82s\x1c\xa8\x05E\xb3\x14L\x0e\x1a\xb4\xdb\x14 \x13p\x16\xb19\x90\xe6\xf6\x9e\xed\\eP\xc0\x1a\xa1\x10Y\\\xd1\xe1\x83\x92d\x81\xd4\x87\xddA\xfa\x06N\xb2\x7f\xf49\x8e\x1e\xf9\xa4\xee\xe1B\xa9\xbc\xc4\xd4\xd4\xbf\x11|\x81\xd9\xb9;XZ\xbb\x0e\x9c\x01\xcd\xb7\x17\xd8y\xb0\x865\x80\x81\xcc\xefn\"I\x86\xcay\x9e\x89\x01c\x0d\xc3\xfd\x05F\xfb\nT\x0b\x8e\xc8\x1aD\x95$\xf5,\xd5\x12fW\x9b4\x12\xcfN9U\xd3q\xf0Ni\x92\x9bT\xa5:\xcb\xea\xeae\x9c\x99\xb9\x1fO\x19\x11\xc0{r[\xce\x9b\x01\xcd3u\x08@\x80m\xab\xbd\x8b\xce;f\"\xcb5\x97\x0c\xa0\xe5\"\x8b\x85\x12+\xc5\x88V\x14\x11e\x9e\x81z\x93\xe1R\x93\x81j\xcc\xf1\xf9\x1a+\xb5\xa4\xf7\xb7[\xb6\xbf\xb8p= y\x03cR\x96V\xae\xc5S\xe1\xca\xe1\x02\x9fpI?\xd5\xc8\xe4fc\xe8/e\xdc:2\xcbg\\\xfe*\x97\x0d\xad\x82\x98ff\xeea}}\n\x9c\x01\x94F\x1aP\xb1|\xfeU/\xf0-7=\xc1\x93s\x1fM\xa5\x7f\x10\x1f\x0e\xd3\xf4}\x0cU=?w\xdf\xdf\xf2\xcd\xff\xf7\x91L\xaf]\xbag\x0eD!x$Sn\x1b)\xf2\xafNhe\x81\xd5\xa0|\xee\x08\xac&\xf0\xeb+\x8e\x16\x06\x1aM\xd8l\xa1\n\x18@r\x10\x95f\xe7\x00nv\xe6n\xb2\xb4\x02\x92\xc7uc8\xb3\x91\x80\x08\x7f~\xe2\x1a>\xee\xa2\xa3\xdcs\xe8i\xa6W\xcbx\x1d\xe3\xc0\xfe\"\xb5\xc2\xb5\x1c\x9e~\x9e\xd9z\x05\x00D \xec\x81$(\x84\x8c\xd5&\xfc\xef|B6\xe6\x01\xa5\x16\x84\xdf_\xef\xa3\xe1\xe9\x80!\n\x1buH3\x90\xde\xff\xf41\xebkWsf\xfa\xbeNBD\xc26\xa7\xa5\xeb\xb8'V\x07\xf8\xb1\x17\xeeauc\x9e\xa3gA\x81\xeb/\x0e\x1c\xcfn\xe2\x8b\xfe\xeb\x93xee\x1f\x88\xee\xdd\xb4\xbcB\x96\xa1>c\xad\xb5\xf5\x9er\x95\xab\xf3`\xbc\xcah\xa8!Y\x82\x84\x8c\xa2f\x8c\xfa&x\xbf-\xeb\xe7 Ys\x82\x10\xe2\x9e\x1b\xe8 \xa5\xaa\x80\xe7\x9fN]\xc27<\xfa\xf1\xd8B\xc6\xa7^\xfb\x0cqi\x04u\x03\xd4\x12G\xcb\x1b\x90\xbds\x10\x804\x80O\xc1g\x0cK\x8b;\xfd\"\x13\xabs\xdc\xd1~\xff\x8c\xf2\"\xd7D\x0d\xa2V\x83J\xd2\x84\xe0!Ka[bt\xec&\x1f\xf2l.\x10\x94@\xe0O\x0e_\xcd\xd3\x0b\xe3|\xc4\x81\xa3\xdcV\x9b\xe0tk\x1d\x1fx{\x94\x18\x883l\x96P\x0e FS\xb2V\xca\xf4\\J_\xd12\xd8\x1f\xf1\xcaR\xca\xc6R\x03\x8c@b \xdb\x9e\x86\x04\x05\x84\x9d\n\x01\x82\x805y\xf6V\x10\xcf\xb1\x95\x01~y\xe56\xe4eE\xa8\x13\x82\x01\xd5n\xe4A\x95=)\x18H#.w\x9er\xab\xc9\xe3\xae\xc2\xfe\xfe\x12U\xcd\xd8\x108\xd9PX[\x87\x90Aj\xa1ez*$\xf9\xae\xdf\xfa\xcfS?\xf4\x9f\xcb\x87\x1a\xbb\xfdQ\x8d\xb3`r\xc7\xd2\xbc\xed4#\x93\x03gy\xb9\xb2w\xe1\\\xc0\xc5\x9e\xcd\xd8B)\xc6\xc6\x8e\x8b\xab\x868Mym\xba\x86\xa6\x162\xdb\x0d\xf7%+|\xeb\xc3C\xb3\xf2\xf7\xff\xf1\xe8\xb7\xfe\xca\xe3\xeb\xdf\xf1\x0f\xaf\xd7+\x9b;`\xba{eL\xbe\xe2ta\xbac\"\x90\x97\xdco\x8b\x9d) \nF\xc1\x04\x10%6\x82Ch$\xb9\xa9+\x00\x14\x9c\xf01W\x96\x1b_z\xf7\xc0\xf7\xb9\xad?\xdaR\x90\x1b\xc7\xe3o\x98^\xcb\xc6w\x8d\x9e\x02\x18A\x8ca\xa7\xd4\xe7\xa6\xf7\x0ej\xa7\xfd\x1b\x81\xc9\x017w\xd7T\xf1gn;T\xf8\xe9\x0f\x01\xed\xf2ln\xbd\x85\x11\xa3\x00\x00\x00\x00IEND\xaeB`\x82\x01\x00\x00\xff\xffPK\x07\x08&\xd2\xc7\x1f\x1e\x0d\x00\x00\x14\x0d\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00;/TL\xb5Q\xa0\xce\xe9I\x00\x00Z\xbb\x01\x00%\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x00bootstrap/3.3.1/css/bootstrap.min.cssUT\x05\x00\x01\xe3\xb8\x8bZPK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\x89\x89\xb8H>\xd4\x17\xe7u\x02\x00\x006\x0e\x00\x00\x0b\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81EJ\x00\x00favicon.icoUT\x05\x00\x01s\x8bDWPK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\x94\x91O \xa9\x03\xb9JR\x00\x00\x00P\x00\x00\x00\x11\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xfcL\x00\x00images/folder.gifUT\x05\x00\x01\x98w#1PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00!z\x92E\x01\x84\xa6\xa6\x8ct\x00\x00`I\x01\x00\x1e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x96M\x00\x00javascript/jquery-2.1.3.min.jsUT\x05\x00\x01\xef\xef\x92TPK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\x9dJ\xb1L\x9d\x06\xc6\xc6-3\x00\x00\xef\xa8\x00\x00:\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81w\xc2\x00\x00javascript/jquery-sparklines/2.1.2/jquery.sparkline.min.jsUT\x05\x00\x01zI\xfdZPK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00j\x8dGM&\xd2\xc7\x1f\x1e\x0d\x00\x00\x14\x0d\x00\x00\x10\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x15\xf6\x00\x00seaweed50x50.pngUT\x05\x00\x01\xb9E\xba[PK\x05\x06\x00\x00\x00\x00\x06\x00\x06\x00\xf3\x01\x00\x00z\x03\x01\x00\x00\x00" - fs.Register(data) -} diff --git a/weed/weed.go b/weed/weed.go index 6e69c1480..91c17d9ff 100644 --- a/weed/weed.go +++ b/weed/weed.go @@ -1,12 +1,12 @@ -//go:generate statik -src=./static -// install this first "go get github.com/rakyll/statik" - package main import ( + "embed" "fmt" + weed_server "github.com/chrislusf/seaweedfs/weed/server" flag "github.com/chrislusf/seaweedfs/weed/util/fla9" "io" + "io/fs" "math/rand" "os" "strings" @@ -35,6 +35,13 @@ func setExitStatus(n int) { exitMu.Unlock() } +//go:embed static +var static embed.FS + +func init() { + weed_server.StaticFS, _ = fs.Sub(static, "static") +} + func main() { glog.MaxSize = 1024 * 1024 * 32 rand.Seed(time.Now().UnixNano()) From a48785c7df2914f432a75f2e27b33d0701edec49 Mon Sep 17 00:00:00 2001 From: Konstantin Lebedev Date: Tue, 27 Apr 2021 21:45:40 +0500 Subject: [PATCH 090/128] auth use bucket wild cards --- weed/s3api/auth_credentials.go | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/weed/s3api/auth_credentials.go b/weed/s3api/auth_credentials.go index b8af6381a..d9d26756f 100644 --- a/weed/s3api/auth_credentials.go +++ b/weed/s3api/auth_credentials.go @@ -3,14 +3,14 @@ package s3api import ( "fmt" "github.com/chrislusf/seaweedfs/weed/filer" - "github.com/chrislusf/seaweedfs/weed/s3api/s3_constants" - "io/ioutil" - "net/http" - "github.com/chrislusf/seaweedfs/weed/glog" "github.com/chrislusf/seaweedfs/weed/pb/iam_pb" xhttp "github.com/chrislusf/seaweedfs/weed/s3api/http" + "github.com/chrislusf/seaweedfs/weed/s3api/s3_constants" "github.com/chrislusf/seaweedfs/weed/s3api/s3err" + "io/ioutil" + "net/http" + "strings" ) type Action string @@ -255,11 +255,21 @@ func (identity *Identity) canDo(action Action, bucket string) bool { limitedByBucket := string(action) + ":" + bucket adminLimitedByBucket := s3_constants.ACTION_ADMIN + ":" + bucket for _, a := range identity.Actions { - if string(a) == limitedByBucket { - return true - } - if string(a) == adminLimitedByBucket { - return true + act := string(a) + if strings.HasSuffix(act, "*") { + if strings.HasPrefix(limitedByBucket, act[:len(act)-1]) { + return true + } + if strings.HasPrefix(adminLimitedByBucket, act[:len(act)-1]) { + return true + } + } else { + if act == limitedByBucket { + return true + } + if act == adminLimitedByBucket { + return true + } } } return false From ccbe02218a5cb22fed0fb620ea392180c9155acd Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Tue, 27 Apr 2021 12:49:26 -0700 Subject: [PATCH 091/128] 1.16 only due to https://github.com/chrislusf/seaweedfs/pull/2027 --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a5ebf415f..7934e1ead 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ sudo: false language: go go: - - 1.15.x - 1.16.x before_install: From 12a7e87007e27c57daaa010c6adece151a346aa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20M=C3=BChl?= Date: Wed, 28 Apr 2021 15:51:49 +0700 Subject: [PATCH 092/128] Do not compress brotli archives --- weed/util/compression.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weed/util/compression.go b/weed/util/compression.go index 9d52810cb..759be9bb0 100644 --- a/weed/util/compression.go +++ b/weed/util/compression.go @@ -126,7 +126,7 @@ func IsZstdContent(data []byte) bool { // by file name extension switch ext { - case ".zip", ".rar", ".gz", ".bz2", ".xz", ".zst": + case ".zip", ".rar", ".gz", ".bz2", ".xz", ".zst", ".br": return false, true case ".pdf", ".txt", ".html", ".htm", ".css", ".js", ".json": return true, true From a8864e2abdc6141a6681da0590c505e9ae7ffa5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20M=C3=BChl?= Date: Wed, 28 Apr 2021 15:54:19 +0700 Subject: [PATCH 093/128] Detect rar archives by mime type RAR archives might not have .rar extension, see [Wikipedia](https://en.wikipedia.org/wiki/RAR_(file_format)) --- weed/util/compression.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/weed/util/compression.go b/weed/util/compression.go index 9d52810cb..c41dbbeab 100644 --- a/weed/util/compression.go +++ b/weed/util/compression.go @@ -147,6 +147,9 @@ func IsZstdContent(data []byte) bool { if strings.HasSuffix(mtype, "script") { return true, true } + if strings.HasSuffix(mtype, "vnd.rar) { + return false, true + } } if strings.HasPrefix(mtype, "audio/") { From 39f636c6820ce1e8cd977f83215425b92fe60751 Mon Sep 17 00:00:00 2001 From: Konstantin Lebedev Date: Wed, 28 Apr 2021 17:15:22 +0500 Subject: [PATCH 094/128] iam ListAccessKeys filtred by username --- weed/iamapi/iamapi_management_handlers.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/weed/iamapi/iamapi_management_handlers.go b/weed/iamapi/iamapi_management_handlers.go index b00ada234..89d283138 100644 --- a/weed/iamapi/iamapi_management_handlers.go +++ b/weed/iamapi/iamapi_management_handlers.go @@ -114,7 +114,11 @@ func (iama *IamApiServer) ListUsers(s3cfg *iam_pb.S3ApiConfiguration, values url func (iama *IamApiServer) ListAccessKeys(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp ListAccessKeysResponse) { status := iam.StatusTypeActive + userName := values.Get("UserName") for _, ident := range s3cfg.Identities { + if userName != "" && userName != ident.Name { + continue + } for _, cred := range ident.Credentials { resp.ListAccessKeysResult.AccessKeyMetadata = append(resp.ListAccessKeysResult.AccessKeyMetadata, &iam.AccessKeyMetadata{UserName: &ident.Name, AccessKeyId: &cred.AccessKey, Status: &status}, From c2269123d3e8de2ea659a87712cc44dcdc4b636b Mon Sep 17 00:00:00 2001 From: Konstantin Lebedev Date: Wed, 28 Apr 2021 22:28:05 +0500 Subject: [PATCH 095/128] fix aws style Etag for chunks --- weed/filer/filechunks.go | 6 ++---- weed/operation/upload_content.go | 2 +- weed/server/filer_server_handlers_write_upload.go | 6 ++++++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/weed/filer/filechunks.go b/weed/filer/filechunks.go index 68f308a51..346eb3cfb 100644 --- a/weed/filer/filechunks.go +++ b/weed/filer/filechunks.go @@ -2,7 +2,6 @@ package filer import ( "bytes" - "encoding/hex" "fmt" "github.com/chrislusf/seaweedfs/weed/wdclient" "math" @@ -43,12 +42,11 @@ func ETagEntry(entry *Entry) (etag string) { func ETagChunks(chunks []*filer_pb.FileChunk) (etag string) { if len(chunks) == 1 { - return chunks[0].ETag + return fmt.Sprintf("%x", util.Base64Md5ToBytes(chunks[0].ETag)) } md5_digests := [][]byte{} for _, c := range chunks { - md5_decoded, _ := hex.DecodeString(c.ETag) - md5_digests = append(md5_digests, md5_decoded) + md5_digests = append(md5_digests, util.Base64Md5ToBytes(c.ETag)) } return fmt.Sprintf("%x-%d", util.Md5(bytes.Join(md5_digests, nil)), len(chunks)) } diff --git a/weed/operation/upload_content.go b/weed/operation/upload_content.go index 944186eeb..8e7c6f733 100644 --- a/weed/operation/upload_content.go +++ b/weed/operation/upload_content.go @@ -39,7 +39,7 @@ func (uploadResult *UploadResult) ToPbFileChunk(fileId string, offset int64) *fi Offset: offset, Size: uint64(uploadResult.Size), Mtime: time.Now().UnixNano(), - ETag: uploadResult.ETag, + ETag: uploadResult.ContentMd5, CipherKey: uploadResult.CipherKey, IsCompressed: uploadResult.Gzip > 0, Fid: fid, diff --git a/weed/server/filer_server_handlers_write_upload.go b/weed/server/filer_server_handlers_write_upload.go index 3ab45453e..b15deb9d1 100644 --- a/weed/server/filer_server_handlers_write_upload.go +++ b/weed/server/filer_server_handlers_write_upload.go @@ -1,6 +1,7 @@ package weed_server import ( + "bytes" "crypto/md5" "hash" "io" @@ -71,6 +72,11 @@ func (fs *FilerServer) uploadReaderToChunks(w http.ResponseWriter, r *http.Reque if uploadResult.Size == 0 { break } + uploadedMd5 := util.Base64Md5ToBytes(uploadResult.ContentMd5) + readedMd5 := md5Hash.Sum(nil) + if !bytes.Equal(uploadedMd5, readedMd5) { + glog.Errorf("md5 %x does not match %x uploaded chunk %s to the volume server", readedMd5, uploadedMd5, uploadResult.Name) + } // Save to chunk manifest structure fileChunks = append(fileChunks, uploadResult.ToPbFileChunk(fileId, chunkOffset)) From a26a37dfa32447309deb1a612d8ff3dd58863b9b Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 28 Apr 2021 13:36:53 -0700 Subject: [PATCH 096/128] fix compilation fix related to #2032 --- weed/util/compression.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weed/util/compression.go b/weed/util/compression.go index 5ba8c4930..8699a8117 100644 --- a/weed/util/compression.go +++ b/weed/util/compression.go @@ -147,7 +147,7 @@ func IsZstdContent(data []byte) bool { if strings.HasSuffix(mtype, "script") { return true, true } - if strings.HasSuffix(mtype, "vnd.rar) { + if strings.HasSuffix(mtype, "vnd.rar") { return false, true } } From 042de9359c49bb0090afbc211c28789f88709718 Mon Sep 17 00:00:00 2001 From: Nathan Hawkins Date: Wed, 28 Apr 2021 19:13:37 -0400 Subject: [PATCH 097/128] make reader_at handle random reads more efficiently for FUSE --- weed/filer/reader_at.go | 22 ++++++- weed/filer/reader_at_test.go | 5 ++ weed/util/chunk_cache/chunk_cache.go | 65 +++++++++++++++++++ .../util/chunk_cache/chunk_cache_in_memory.go | 14 ++++ weed/util/chunk_cache/chunk_cache_on_disk.go | 31 +++++++-- weed/util/chunk_cache/on_disk_cache_layer.go | 22 +++++++ 6 files changed, 152 insertions(+), 7 deletions(-) diff --git a/weed/filer/reader_at.go b/weed/filer/reader_at.go index a1e989684..b03b3bbb4 100644 --- a/weed/filer/reader_at.go +++ b/weed/filer/reader_at.go @@ -139,13 +139,15 @@ func (c *ChunkReadAt) doReadAt(p []byte, offset int64) (n int, err error) { } glog.V(4).Infof("read [%d,%d), %d/%d chunk %s [%d,%d)", chunkStart, chunkStop, i, len(c.chunkViews), chunk.FileId, chunk.LogicOffset-chunk.Offset, chunk.LogicOffset-chunk.Offset+int64(chunk.Size)) var buffer []byte - buffer, err = c.readFromWholeChunkData(chunk, nextChunk) + bufferOffset := chunkStart - chunk.LogicOffset + chunk.Offset + bufferLength := chunkStop - chunkStart + buffer, err = c.readChunkSlice(chunk, nextChunk, uint64(bufferOffset), uint64(bufferLength)) if err != nil { glog.Errorf("fetching chunk %+v: %v\n", chunk, err) return } - bufferOffset := chunkStart - chunk.LogicOffset + chunk.Offset - copied := copy(p[startOffset-offset:chunkStop-chunkStart+startOffset-offset], buffer[bufferOffset:bufferOffset+chunkStop-chunkStart]) + + copied := copy(p[startOffset-offset:chunkStop-chunkStart+startOffset-offset], buffer) n += copied startOffset, remaining = startOffset+int64(copied), remaining-int64(copied) } @@ -167,6 +169,20 @@ func (c *ChunkReadAt) doReadAt(p []byte, offset int64) (n int, err error) { } +func (c *ChunkReadAt) readChunkSlice(chunkView *ChunkView, nextChunkViews *ChunkView, offset, length uint64) ([]byte, error) { + + chunkSlice := c.chunkCache.GetChunkSlice(chunkView.FileId, offset, length) + if len(chunkSlice) > 0 { + return chunkSlice, nil + } + chunkData, err := c.readFromWholeChunkData(chunkView, nextChunkViews) + if err != nil { + return nil, err + } + wanted := min(int64(length), int64(len(chunkData))-int64(offset)) + return chunkData[offset : int64(offset)+wanted], nil +} + func (c *ChunkReadAt) readFromWholeChunkData(chunkView *ChunkView, nextChunkViews ...*ChunkView) (chunkData []byte, err error) { if c.lastChunkFileId == chunkView.FileId { diff --git a/weed/filer/reader_at_test.go b/weed/filer/reader_at_test.go index 37a34f4ea..a31319082 100644 --- a/weed/filer/reader_at_test.go +++ b/weed/filer/reader_at_test.go @@ -20,6 +20,11 @@ func (m *mockChunkCache) GetChunk(fileId string, minSize uint64) (data []byte) { } return data } + +func(m *mockChunkCache) GetChunkSlice(fileId string, offset, length uint64) []byte { + return nil +} + func (m *mockChunkCache) SetChunk(fileId string, data []byte) { } diff --git a/weed/util/chunk_cache/chunk_cache.go b/weed/util/chunk_cache/chunk_cache.go index 3615aee0e..40d24b322 100644 --- a/weed/util/chunk_cache/chunk_cache.go +++ b/weed/util/chunk_cache/chunk_cache.go @@ -1,14 +1,18 @@ package chunk_cache import ( + "errors" "sync" "github.com/chrislusf/seaweedfs/weed/glog" "github.com/chrislusf/seaweedfs/weed/storage/needle" ) +var ErrorOutOfBounds = errors.New("attempt to read out of bounds") + type ChunkCache interface { GetChunk(fileId string, minSize uint64) (data []byte) + GetChunkSlice(fileId string, offset, length uint64) []byte SetChunk(fileId string, data []byte) } @@ -22,6 +26,8 @@ type TieredChunkCache struct { onDiskCacheSizeLimit2 uint64 } +var _ ChunkCache = &TieredChunkCache{} + func NewTieredChunkCache(maxEntries int64, dir string, diskSizeInUnit int64, unitSize int64) *TieredChunkCache { c := &TieredChunkCache{ @@ -87,6 +93,58 @@ func (c *TieredChunkCache) doGetChunk(fileId string, minSize uint64) (data []byt } +func (c *TieredChunkCache) GetChunkSlice(fileId string, offset, length uint64) []byte { + if c == nil { + return nil + } + + c.RLock() + defer c.RUnlock() + + return c.doGetChunkSlice(fileId, offset, length) +} + +func (c *TieredChunkCache) doGetChunkSlice(fileId string, offset, length uint64) (data []byte) { + + minSize := offset + length + if minSize <= c.onDiskCacheSizeLimit0 { + data, err := c.memCache.getChunkSlice(fileId, offset, length) + if err != nil { + glog.Errorf("failed to read from memcache: %s", err) + } + if len(data) >= int(minSize) { + return data + } + } + + fid, err := needle.ParseFileIdFromString(fileId) + if err != nil { + glog.Errorf("failed to parse file id %s", fileId) + return nil + } + + if minSize <= c.onDiskCacheSizeLimit0 { + data = c.diskCaches[0].getChunkSlice(fid.Key, offset, length) + if len(data) >= int(minSize) { + return data + } + } + if minSize <= c.onDiskCacheSizeLimit1 { + data = c.diskCaches[1].getChunkSlice(fid.Key, offset, length) + if len(data) >= int(minSize) { + return data + } + } + { + data = c.diskCaches[2].getChunkSlice(fid.Key, offset, length) + if len(data) >= int(minSize) { + return data + } + } + + return nil +} + func (c *TieredChunkCache) SetChunk(fileId string, data []byte) { if c == nil { return @@ -131,3 +189,10 @@ func (c *TieredChunkCache) Shutdown() { diskCache.shutdown() } } + +func min(x, y int) int { + if x < y { + return x + } + return y +} diff --git a/weed/util/chunk_cache/chunk_cache_in_memory.go b/weed/util/chunk_cache/chunk_cache_in_memory.go index 5f26b8c78..d725f8a16 100644 --- a/weed/util/chunk_cache/chunk_cache_in_memory.go +++ b/weed/util/chunk_cache/chunk_cache_in_memory.go @@ -31,6 +31,20 @@ func (c *ChunkCacheInMemory) GetChunk(fileId string) []byte { return data } +func (c *ChunkCacheInMemory) getChunkSlice(fileId string, offset, length uint64) ([]byte, error) { + item := c.cache.Get(fileId) + if item == nil { + return nil, nil + } + data := item.Value().([]byte) + item.Extend(time.Hour) + wanted := min(int(length), len(data)-int(offset)) + if wanted < 0 { + return nil, ErrorOutOfBounds + } + return data[offset : int(offset)+wanted], nil +} + func (c *ChunkCacheInMemory) SetChunk(fileId string, data []byte) { localCopy := make([]byte, len(data)) copy(localCopy, data) diff --git a/weed/util/chunk_cache/chunk_cache_on_disk.go b/weed/util/chunk_cache/chunk_cache_on_disk.go index 6f87a9a06..36de5c972 100644 --- a/weed/util/chunk_cache/chunk_cache_on_disk.go +++ b/weed/util/chunk_cache/chunk_cache_on_disk.go @@ -90,11 +90,11 @@ func (v *ChunkCacheVolume) Shutdown() { func (v *ChunkCacheVolume) doReset() { v.Shutdown() - os.Truncate(v.fileName + ".dat", 0) - os.Truncate(v.fileName + ".idx", 0) - glog.V(4).Infof("cache removeAll %s ...", v.fileName + ".ldb") + os.Truncate(v.fileName+".dat", 0) + os.Truncate(v.fileName+".idx", 0) + glog.V(4).Infof("cache removeAll %s ...", v.fileName+".ldb") os.RemoveAll(v.fileName + ".ldb") - glog.V(4).Infof("cache removed %s", v.fileName + ".ldb") + glog.V(4).Infof("cache removed %s", v.fileName+".ldb") } func (v *ChunkCacheVolume) Reset() (*ChunkCacheVolume, error) { @@ -121,6 +121,29 @@ func (v *ChunkCacheVolume) GetNeedle(key types.NeedleId) ([]byte, error) { return data, nil } +func (v *ChunkCacheVolume) getNeedleSlice(key types.NeedleId, offset, length uint64) ([]byte, error) { + nv, ok := v.nm.Get(key) + if !ok { + return nil, storage.ErrorNotFound + } + wanted := min(int(length), int(nv.Size)-int(offset)) + if wanted < 0 { + // should never happen, but better than panicing + return nil, ErrorOutOfBounds + } + data := make([]byte, wanted) + if readSize, readErr := v.DataBackend.ReadAt(data, nv.Offset.ToActualOffset()+int64(offset)); readErr != nil { + return nil, fmt.Errorf("read %s.dat [%d,%d): %v", + v.fileName, nv.Offset.ToActualOffset()+int64(offset), int(nv.Offset.ToActualOffset())+int(offset)+wanted, readErr) + } else { + if readSize != wanted { + return nil, fmt.Errorf("read %d, expected %d", readSize, wanted) + } + } + + return data, nil +} + func (v *ChunkCacheVolume) WriteNeedle(key types.NeedleId, data []byte) error { offset := v.fileSize diff --git a/weed/util/chunk_cache/on_disk_cache_layer.go b/weed/util/chunk_cache/on_disk_cache_layer.go index eebd89798..a4b3b6994 100644 --- a/weed/util/chunk_cache/on_disk_cache_layer.go +++ b/weed/util/chunk_cache/on_disk_cache_layer.go @@ -82,6 +82,28 @@ func (c *OnDiskCacheLayer) getChunk(needleId types.NeedleId) (data []byte) { } +func (c *OnDiskCacheLayer) getChunkSlice(needleId types.NeedleId, offset, length uint64) (data []byte) { + + var err error + + for _, diskCache := range c.diskCaches { + data, err = diskCache.getNeedleSlice(needleId, offset, length) + if err == storage.ErrorNotFound { + continue + } + if err != nil { + glog.Errorf("failed to read cache file %s id %d", diskCache.fileName, needleId) + continue + } + if len(data) != 0 { + return + } + } + + return nil + +} + func (c *OnDiskCacheLayer) shutdown() { for _, diskCache := range c.diskCaches { From 4fc074db8139d08f2cc47802e30afbacdabec1fb Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 29 Apr 2021 15:46:45 -0700 Subject: [PATCH 098/128] Update README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e463092bc..a28b13b98 100644 --- a/README.md +++ b/README.md @@ -68,12 +68,16 @@ Table of Contents * [License](#license) -## Quick Start ## +## Quick Start with single binary ## * Download the latest binary from https://github.com/chrislusf/seaweedfs/releases and unzip a single binary file `weed` or `weed.exe` * Run `weed server -dir=/some/data/dir -s3` to start one master, one volume server, one filer, and one S3 gateway. Also, to increase capacity, just add more volume servers by running `weed volume -dir="/some/data/dir2" -mserver=":9333" -port=8081` locally, or on a different machine, or on thousands of machines. That is it! +## Quick Start for S3 API on Docker ## + +`docker run -p 8333:8333 chrislusf/seaweedfs server -s3` + ## Introduction ## SeaweedFS is a simple and highly scalable distributed file system. There are two objectives: From 10cddc44ded57b02fbe65e3840003cf416f34b9b Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 29 Apr 2021 15:50:41 -0700 Subject: [PATCH 099/128] Update release.yml --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dba704800..7b7bc9a09 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,6 +35,7 @@ jobs: - name: Go Release Binaries uses: wangyoucao577/go-release-action@v1.14 with: + goversion: 1.16 github_token: ${{ secrets.GITHUB_TOKEN }} goos: ${{ matrix.goos }} goarch: ${{ matrix.goarch }} @@ -51,6 +52,7 @@ jobs: - name: Go Release Binaries uses: wangyoucao577/go-release-action@v1.14 with: + goversion: 1.16 github_token: ${{ secrets.GITHUB_TOKEN }} goos: ${{ matrix.goos }} goarch: ${{ matrix.goarch }} From 513fd5e0f627e4f7ec4959e5cd3d8506ef63d985 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 29 Apr 2021 23:05:23 -0700 Subject: [PATCH 100/128] Update release.yml --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7b7bc9a09..ee7a23810 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: run: echo BUILD_TIME=$(date -u +%Y-%m-%d-%H-%M) >> ${GITHUB_ENV} - name: Go Release Binaries - uses: wangyoucao577/go-release-action@v1.14 + uses: wangyoucao577/go-release-action@v1.17 with: goversion: 1.16 github_token: ${{ secrets.GITHUB_TOKEN }} @@ -50,7 +50,7 @@ jobs: asset_name: "weed-large-disk-${{ env.BUILD_TIME }}-${{ matrix.goos }}-${{ matrix.goarch }}" - name: Go Release Binaries - uses: wangyoucao577/go-release-action@v1.14 + uses: wangyoucao577/go-release-action@v1.17 with: goversion: 1.16 github_token: ${{ secrets.GITHUB_TOKEN }} From 8db58450a0931267a1821f294bd3a3fe44ff1715 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 30 Apr 2021 01:08:09 -0700 Subject: [PATCH 101/128] Update ISSUE_TEMPLATE.md --- .github/{ISSUE_TEMPLATE/bug_report.md => ISSUE_TEMPLATE.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{ISSUE_TEMPLATE/bug_report.md => ISSUE_TEMPLATE.md} (100%) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE.md similarity index 100% rename from .github/ISSUE_TEMPLATE/bug_report.md rename to .github/ISSUE_TEMPLATE.md From 84312e679957a5107735136a731862c7dd297ba7 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 30 Apr 2021 03:14:07 -0700 Subject: [PATCH 102/128] 2.42 --- k8s/seaweedfs/Chart.yaml | 4 ++-- k8s/seaweedfs/values.yaml | 2 +- weed/util/constants.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/k8s/seaweedfs/Chart.yaml b/k8s/seaweedfs/Chart.yaml index 0025c9760..d9c9b05c8 100644 --- a/k8s/seaweedfs/Chart.yaml +++ b/k8s/seaweedfs/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v1 description: SeaweedFS name: seaweedfs -appVersion: "2.41" -version: 2.41 +appVersion: "2.42" +version: 2.42 diff --git a/k8s/seaweedfs/values.yaml b/k8s/seaweedfs/values.yaml index a4abaccf3..1cefd0914 100644 --- a/k8s/seaweedfs/values.yaml +++ b/k8s/seaweedfs/values.yaml @@ -4,7 +4,7 @@ global: registry: "" repository: "" imageName: chrislusf/seaweedfs - # imageTag: "2.41" - started using {.Chart.appVersion} + # imageTag: "2.42" - started using {.Chart.appVersion} imagePullPolicy: IfNotPresent imagePullSecrets: imagepullsecret restartPolicy: Always diff --git a/weed/util/constants.go b/weed/util/constants.go index c595f0c53..cdd5d41fc 100644 --- a/weed/util/constants.go +++ b/weed/util/constants.go @@ -5,7 +5,7 @@ import ( ) var ( - VERSION = fmt.Sprintf("%s %d.%02d", sizeLimit, 2, 41) + VERSION = fmt.Sprintf("%s %d.%02d", sizeLimit, 2, 42) COMMIT = "" ) From d74cdf011553ae073d524a080f65f418c76ccaa7 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 30 Apr 2021 03:36:15 -0700 Subject: [PATCH 103/128] Revert "Merge pull request #2027 from bingoohuang/master" Need to revert because docker image build failed. The docker apk package only has go 1.15. --- go.mod | 4 +++- weed/server/common.go | 16 +++++++++------- weed/statik/statik.go | 13 +++++++++++++ weed/weed.go | 13 +++---------- 4 files changed, 28 insertions(+), 18 deletions(-) create mode 100644 weed/statik/statik.go diff --git a/go.mod b/go.mod index e576549da..70bc33070 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/chrislusf/seaweedfs -go 1.16 +go 1.12 require ( cloud.google.com/go v0.58.0 // indirect @@ -60,6 +60,7 @@ require ( github.com/peterh/liner v1.1.0 github.com/pierrec/lz4 v2.2.7+incompatible // indirect github.com/prometheus/client_golang v1.3.0 + github.com/rakyll/statik v0.1.7 github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 // indirect github.com/seaweedfs/fuse v1.1.4 github.com/seaweedfs/goexif v1.0.2 @@ -75,6 +76,7 @@ require ( github.com/tidwall/match v1.0.1 github.com/tsuna/gohbase v0.0.0-20201125011725-348991136365 github.com/valyala/bytebufferpool v1.0.0 + github.com/valyala/fasthttp v1.20.0 github.com/viant/assertly v0.5.4 // indirect github.com/viant/ptrie v0.3.0 github.com/viant/toolbox v0.33.2 // indirect diff --git a/weed/server/common.go b/weed/server/common.go index 571944c10..5c5f1b8eb 100644 --- a/weed/server/common.go +++ b/weed/server/common.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io" - "io/fs" "mime/multipart" "net/http" "path/filepath" @@ -22,14 +21,19 @@ import ( "github.com/chrislusf/seaweedfs/weed/util" "github.com/gorilla/mux" + statik "github.com/rakyll/statik/fs" + + _ "github.com/chrislusf/seaweedfs/weed/statik" ) var serverStats *stats.ServerStats var startTime = time.Now() +var statikFS http.FileSystem func init() { serverStats = stats.NewServerStats() go serverStats.Start() + statikFS, _ = statik.New() } func writeJson(w http.ResponseWriter, r *http.Request, httpStatus int, obj interface{}) (err error) { @@ -208,16 +212,14 @@ func statsMemoryHandler(w http.ResponseWriter, r *http.Request) { writeJsonQuiet(w, r, http.StatusOK, m) } -var StaticFS fs.FS - func handleStaticResources(defaultMux *http.ServeMux) { - defaultMux.Handle("/favicon.ico", http.FileServer(http.FS(StaticFS))) - defaultMux.Handle("/seaweedfsstatic/", http.StripPrefix("/seaweedfsstatic", http.FileServer(http.FS(StaticFS)))) + defaultMux.Handle("/favicon.ico", http.FileServer(statikFS)) + defaultMux.Handle("/seaweedfsstatic/", http.StripPrefix("/seaweedfsstatic", http.FileServer(statikFS))) } func handleStaticResources2(r *mux.Router) { - r.Handle("/favicon.ico", http.FileServer(http.FS(StaticFS))) - r.PathPrefix("/seaweedfsstatic/").Handler(http.StripPrefix("/seaweedfsstatic", http.FileServer(http.FS(StaticFS)))) + r.Handle("/favicon.ico", http.FileServer(statikFS)) + r.PathPrefix("/seaweedfsstatic/").Handler(http.StripPrefix("/seaweedfsstatic", http.FileServer(statikFS))) } func adjustHeaderContentDisposition(w http.ResponseWriter, r *http.Request, filename string) { diff --git a/weed/statik/statik.go b/weed/statik/statik.go new file mode 100644 index 000000000..e3be3b214 --- /dev/null +++ b/weed/statik/statik.go @@ -0,0 +1,13 @@ +// Code generated by statik. DO NOT EDIT. + +// Package statik contains static assets. +package statik + +import ( + "github.com/rakyll/statik/fs" +) + +func init() { + data := "PK\x03\x04\x14\x00\x08\x00\x08\x00;/TL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00%\x00 \x00bootstrap/3.3.1/css/bootstrap.min.cssUT\x05\x00\x01\xe3\xb8\x8bZ\xec\xbd\xeb\x93\xe3\xb8\xb1'\xfa\xfd\xfe\x15rOtL\xb7[d\x93z\x96\xa4p]\x9f\xeb\xdd\xd8\xe3\x88\xf5\xf9\xb2\xfe\xb0\x11\xe3\xb97 \x12\x92\xe8\xa1H\x1e\x92\xaa\xaa\x1e\xad\xf6o\xbfA<\x13@\x82\xa4\xd4\xe5\xc7F\xf8t\x1cO \xf8!\x91\xc8L \x93x~\xfd\xedo\xfe\xaf\xc9o'\xffOY\xb6M[\x93j\xf22\x0f\xe7a<\xf9tj\xdbj\xfb\xf5\xeb\x91\xb6{\x99\x17&\xe5\xf9s\x87\xfeCY}\xab\xb3\xe3\xa9\x9d\xcc\xa28\x0efQ\xbc\x98\xfc\xf95k[ZO'\x7f,\x92\xb0\x03\xfd\xf7,\xa1EC\xd3\xc9\xa5Hi=\xf9\xd3\x1f\xff\xcc\x896\x1d\xd5\xac=]\xf6\x1d\xbd\xaf\xed\xeb\xbe\xf9\xaa\xaa\xf8\xba\xcf\xcb\xfd\xd73iZZ\x7f\xfd\xef\x7f\xfc\xc3\x7f\xfd\x8f\xff\xf1_\xbb*\xbf~\xfd\xedo&EY\x9fI\x9e\xfdJ\xc3\xa4i:F\xa3p6\xf9_\x8c\xb2\xa8l\xf2\xbf&\xc7\xac\x0d\xb3\xf2\xab\xc2N~\xfb\xf5\xd4\x9e\xf3\xeb\xa1,\xda\xe0@\xceY\xfem\xdb\x90\xa2 \x1aZg\x87]\xf0J\xf7\xbfdm\xd0\xd2\xb76h\xb2_i@\xd2\xbf^\x9av\x1bG\xd1\xc7]pn\xf0\x9c\xdb\xbeL\xbf]\xcf\xa4>f\xc56\xba\x91\xba\xcd\x92\x9cNI\x93\xa5t\x9a\xd2\x96dy3=d\xc7\x84TmV\x16\xdd\x9f\x97\x9aN\x0fe\xd9\xc9\xe8DI\xda\xfd\xe7X\x97\x97jz&Y1=\xd3\xe22-\xc8\xcb\xb4\xa1 +\xd1\\\xcegR\x7f\xbb\xa6YS\xe5\xe4\xdbv\x9f\x97\xc9/7rI\xb3r\x9a\x90\xe2\x854\xd3\xaa.\x8f5m\x9a\xe9K\x96\xd2R!\xb3\"\xcf\n\x1a\xb0\x02\xbb\x17\xda\xb1F\xf2\x80\xe4\xd9\xb1\xd8\xeeIC\xbb\\Nh[\x94\xed\xa7\x9f\x92\xb2h\xeb2o~\xfe\xacH\x14eAw'\xda\xa9x\x1b\xdd~:eiJ\x8b\x9f\xa7-=W9i\xa9\x81\xbb\x91\xeb\x9e$\xbftm)\xd2 )\xf3\xb2\xde\xb65)\x9a\x8a\xd4\xb4hodK\x926{\xa1S\xb2=\x95/\xb4\xbe\x96\x97\xb6c\xa1\x13\xdb~_\xff\xd4fmN\x7f\xbe\xee\xcb:\xa5u\xb0/\xdb\xb6\xa7\xe9\xcf\xb0Z\x95x\x15\x85Rz \x97\\\xb6m\xbbe*;\x94\xc9\xa5 \xb2\xa2\xa05\xe7\xc4M\xbfV$M;\xe5E;eO\x0cz\x85\x86\xcaG\xca\x1bhMr\xa2\xc9/\xfb\xf2\xcdl4I\xb3R\xb7\x10\x98\x86\xea\xb9\xae1\x81,|\xfe\x00\xfd\xb1\x17\xcd\xb2\x05\x9c\x11\xfe\x7f\x7f\xf7\xe1\x87\x0f\x02?UI\x7f%/\xa4I\xea\xacj\xb7\x1f\x1cb\x1f:\x172e\x01\xca\x7f^\xcaV\x9a\x000\xb5\x1f6\x9b\xcd\xae\"G\x1a\xeckJ~ \xb2\xa2\x8b\xac\xb6\xe4\xa5\xcc\xd2[\xdb\xc5O*\x06aF\x14\xf0\x90*`vvk\xebi\xe7D}\xe5\xbb\xbc3y\x0b^\xb3\xb4=\xb1p\x0e\xc8\xb4\x9a\x9ef\xd3\xd3\xfcZ\xd6\xd5\x89\x14\xcdv\xbe{\xcd\xd2\xf2\xb5\xd9\xceo<\x03Pe\xcd\x12D\x85g1\xc3\x89\x03 \x1c\x16\xe4eOj3v\n\xf7m\xf1\x1c&\xa4\xa6\xed4L\xeb\xb2\xbaT\xcf M\xf6\x8d\xb6\xac\x02\xcc\xf2naN\xf64G\xe4\x17E\xd1-4\xfa\x97\xd3\x9d \x19\x86\x9c\xb4\xe9T\xfeur\x83:\xbb=\\\xf0\x9c:M'\xedi\xea$\xa5\x08gi\x9a\x02*\xb7\xdf\x8bH!\xa1F\xcc\xf0\xe3\x7f\xcb\xbfU\xa7,)\x8bf\xf2\xef$?\xe4Yql~\xdc5u\xb2\xbd\xd4\xf9\xa70\xfc\xda\xa1\x9b\xafG\x05\x0bN\x12\x16\xd4\xf4x\xc9I\x1d\xd2\xb2\xfd|\x7f\x91\xff\xfb\x87\x8c\x1e\xb2\xb7\xcf\x93.6 \xed\xa7\x1f\xe9yO\xd3\x94\xa6AY\xd1\xa2\x1b\x86\x7f\xfc<\x1dO\xf1\xb5<\x1c4\xad\xee\xd7]\xc5\xdb\x16\x94n\xeb\x0b\xbd\x9b\x81\xe6\xe5\xf8\x83\x06\xfc\x7f\n \xf25\xf5\xe6\xe5\xf8\xe3\xe7[\xa8\xb0H\xbc\xdb\xc5\xadq\xf5\xb6C\xbf5F\xe8\x0f\xc4\xeb<\xee\xd8A\x9f\xb0\x88\"#\x86\x8e\xd5\xe8\xc8\xcb\x9d\xcb\xb2=uC?)\xda\x8c\xe4\x19ih\xca\xdds\xd9\xbc\xd9\x98cM\xbe5 \xc9)hQ\xc0F\xfd\xac\xf9E\x8c\xeazH\xfa\xcb\x8c|\x80\xc0*\xbf4\x08ho\x80\xe8\xa5.\xa5\x7f0\x93\x91\xa2\x11I\x8c\xc2\xe7\xac@\xab\x98\xc53\x03\x97\xe4\xe5%Ep\xab(6\x99)^h^V\x14\x81\xae\xa3\x8d\xd98Z$Y\x8e\x02\x0f\x06\xf0\x98\x93\x06\xe1\x91FV\xdd\xe7K\x93%(\xcel\x0b\x8fVP\xe0\xdc\x00\x9e(\xa9[\x14\xb74 \xb6\x04\x915\x8d\xa2\x95\x03\x0b\xe8\xb9j\xbf\xa1\xe0\xb5\x01\xbe4\x14\xa7\xf9d\xc0\x0eY~Fa\xa6\xac\xdbS\x90\x93\xfa\x88\xa8\x85FqdAQP\xec\xd0\xcb\x1aT6\x96\xe1\x94\x88\x95\xd3(6\x05]\xd3s\xf9\x823\xb70\x80\xbf\x96\xe59\xc8\n\x14\xb9t\x91\xe5\x05g\xd1\xd4Ky8\xa0(S!Mv,\x08b\xae4\x8aM\x95$\xe5\x11EY\x1a\xa9I\x83Jzf\xaa\xe3T\x9eQ\xc1\xccb\xdb\x0ep\x98\xa9\x8d6\xf3P\xb3\xf4Q\x12\xa4\xb3\xd3hfj#-_\x8b\xbc$i@rT\xce\xb3%\nG\xa1\xa6J.\x95\x17hj%+\xf6\xe5\x1b\x8a3\x95\xd2\xf9\x89 \xc9\xea\xc4#\xa6\x8de\x8f\x15%h\x93\xe6\x91\x05<\xd4\x14\xd7\xe3\xdcTP\xd7]|r\x9a\x9bJ\xea\xdc\x18\n3\x95t\xc8 jh\xf3\x85=\x88\xa5\xd5\xa9,(:\x84\xceM\x15\xbd\x94\xf9\xe5L}=b\xbe\xc2\xc0\x9dZQ\xf4\x1aC_*\x14kj\xeb?\xeb\xa4LQE\xcdME\xed\x89\x17\xb9\xb0\x865\\X\x8b\xd8F\xa1bZ\x98\x1a\xda\x97\xf8\xb0\xb6\x98;\xb03\xa9q\xa8\xa9%\xf6\xa1\x87\xe2L\x05%\xe4Lk\x82\x02M\xe5\xb0\xd9)\x0c\xb6\xb6X\xcc\xd1n\xb60\x15\xc2\xa75Q\xa05\xacu\x1f\x80\"pB\xd0\xcb\xc8E\xf3\x0f \x0cl\xea\x86M`\x069=\xe0\x94g\x088\xa1E\x8b\xbb\xd1\xe5\x1c\x81\xd7^\xb6\x17\x08\xfa\xaf\x97\xa6\xcd\x0e\xa8/_.\x9d\xbe\x8f\xc2V\xd6X\x96\xd2\xa2\xf5\xb7\xd0\x1e\xf9\x18\xda\xcf\xb3\x15(\x90\x84v\xa3\x7f\xc0\xa6\xe9\xd1\x02Vx\x96%\xed\xa5F\xbb\xd6\xca\xd4\xe2\x99TAg\xe6\xb8\xa4W\x96b\xf8\xf2\x05\x064U\xd2z:\xc4\xca\xd4\x05M3\x1cf\x85h'\xe2i\x8b\xa9\x036\xeb\x88\xe2L\xe9\xfb\xe2\x95\x95)\xf5\xa6\xa5U\xd0}\xc3\xbe\x92\x1a\xedg\xab\x8d\xa5\xa5\xa6\xed\xc5\xaf#k\xfc\xeb\x81\x9a\xdd\x87\xaf\xdf 0S?\x15\xb94h\xcb\xd6s\xabe%:\x92\xaf\x17\xd60T{\xf9[\xbaM\xef\x83\xdb\xc14\xadz\xe1\xa6\xbe\xe8_i\x82\xda\xc9\xfa\xc9\xd6\xffK]\xfa\x87\x99\xf5\x06\x85{{\xe1Sd)\xe1\xd2\xb0H\x12\xc5Z\x9f3\xdd\xa7\x99\x1fl\xaa\x8dG\xd0~\xb4\xa9\xbc\xf2\x17?\xd2\xd4\xdf\x7f^h\xd3}|\xfb\xf1KkT:\x94~\xac\xa5\xc2\xa4\xa6\xb4hN%.\xb95\xd6@\x7f\x08\xf7\xf4d7\xb1\x07kG\x11E\x0fxc\xaa\x90\xd4u\xf9\xea\xb5\x8fM\x8c\x80\xbd\xd6\xb1\x99!hS\xd4\x01\xc7\xd6T\xc0\x99\x1c\x0b\x8a\x03g\xe8X\x89\xdawl\xcd\x08H\xb0\xc7\xc2ck^\xa0\xa6\xed+\xf5pa\x07\x02eUuJH\xf0\xb9\x9d8\xb6\xe3\xe8\x9c\xcd[\xfbTl\xcd\x12\x08\xb8\xcfx\xac\xa9\x02\xd1}\xe4\x1a=Z\xc2\xfe2e%Ne\x9d\xfdZ\x16-^\xc6\x9eBH1\x0f\x19[3\x08\xfbK\x9e\x9f\xca\x1ae\xdb\x9aE\xd8S\xb4\xb7\xc7\xd6,B\xd25\xeb\x90%\xa4E%gM&\xb4\xa7\xcby\xdfx\xac\xc3\x9aI\x10X\x9fqX\x93 'R\xa4\xde18\xb6&\x14\x18\xd83\xba\xc7\xd6\xa4\x02\xc3z\x18\xde\xb8H\x1f\xbb\xd6\x9c\x02\xf7D\x03\xae#\xb6\xa6\x17\x8cB>\xf6\xady\x06\xa3\x0c\xde\x0ck\xca\xc1(\xe1m\x8e\xa9\xd7c^\xeeQ\xfd[S\x0f\xaf5-\xd0Y\xd9\xd8\x9avhI\xf3\x0b\xf6\x91\x1e[\x13\x0e\x87,\xc7?\xfebk\xb6a_g\xf4\x90\x10\xbc\x7f[\x13\x0e\x9d_\xe4q\x0b\x06\xb6\xe6\x1cR\xd2\x9c\xf6%\x1e\xa0\xc6\xd6\xccCE*Z'y\x86\xaa\xc1\x9a~`\xf3\xd2\xde\x99\xe4\xd8\x9a\x85\xc8\xb3\x02\xfb\xa2\x89\xed\x19\x88S\x89{\x1bk\x06\xa2\xba4\xa7\n\x9d\x82\x8d\xad)\x88K\x837\xdc\x94\xfeq\x8f7\xd9\x94{S\xe2\xa3\xb55\xa1\xd0\xc1\x82\xfd\xb7\x80\xe4\xd5\x89\xecq\x87`M+\xd8E~)\xaa\xb4\xa5\x1d\xc8U\xa4\xc0\x81\xf6d8_\xe6\xf5\x8e\x16\xd6\xac\x83\xc2\xe3\xe3\x915\xf3\x90\x97G|5 ^Y\xd3\xe39>\xdb\x1e\xaf\xec\xa9\xd7\xa3g\xd1 \xb6\xa6'\n\xfa\x1a\xbcfEZ\xbe\xa2`;\xa8\xd5\xbf.{\xb6\x11 \xe7\xc0\x9a d{X\x9a\x96\xd6\x1e\xd2\xb6\xbf\xbb\xb0\x881\xdf\xa3\xba\xdd\xd8n\xafC/\x83\x18\xc5\xda\xfe\xae\xc3\xae\xa8\xc0wFG\x95\xdeJ\xda\x92*8e\xc7S\xce\x14\xcdwd\xd5\xc7=\xf9\x14M\xd9\xbf\xcf\xfcH\x02\xdc\x83\xf3\xe1\xdfi\xfeB\xbb\xef\xdb\xc9\x7f\xd0\x0b\xfd0U\xbf\xa7\xffVg$\x9f\x82s\x10\xa0\xd6E\xf5f\xee\xc2 \x17\xb3\xa7\xe5:^\xcc\xc5^\xeb\x1f\xe6\xf3\xf9\x0e\xdd\x1e\xc6\xf7\xb6N\x8d\xcd\xd6z\xff6\xe4M\xee\xde\xd6\xf5\xca\x14X\xb5\xdc\xd5M\xae\xaa\xe65\xd9\xafw\xf6fG~\x0e\x81\x9f/\x98\x92-\xdb\x03-\x8b\xcc\xe6\xcb\xd9:q\x8a\x80\xfd\x91\x02/\xcf%\xb4\xa7\xac\x10\x87\x0fv2mY\xbdM\xc8\xa5-'zCRri\x82\x9aMgt\xf5HdP\x1e\x0e\x0dm\xb7\xc1\xacz\xb3v\xe7Gl\xe3\xa1u*\xe0\x9c\xa5iNoav>\x065m\xaa\xb2h\xb2\x17:\x0d\xd9wvA\xb2\xfc9;\x1f\xc1\xcf \xe1 \xa9\xcbKCs\xbe7\xfa9\xccZz\xee\xc9a\xa5\xcc\xb3$;s\x0b\xe4\x0en\x92\xe6\xect\xca\xa5r\x0b_P\x934\xbb4\xdbU\xf5\xc6\xb3\x15K\xf8\xc1\x13?u\xb5\xe9\xbb\xc7\xd0P\xebrw$\xff\x90\xa6\xe9\xce\xe4o\x01\xfbL\xe7\xcd\xf9\x0e6\x92\xe7\x93p\xd6L(ih\x90\x15]\xa8\xb9\x0b\xca!D\x7f6\x97\x03\xff2\xb7\xa4\xb4\x8c>\xdeN\xb5\xd0|\xd0\x96\xd5v\xd6uf\xf1[\x9c\xf7`Ir\xf3\xf4No\xf5\x84\x0d\xa4\x94\xde\xc2\xa6\x0e\xca\"\xff\xa6\xf7\xe3\x91}S\xe6\x97\x96\xee\x84\x84\xab7)\xe0\xeeO\xbd3[X^\xd0\xa5ZGGv\xec\x8b\xb7\xa6I\xabF\x10\xbd\x91[\xd6\xc8\xcd\x9c\xecs*\x0f\xf1 9\xbc\xf7(\xde\xba\xc0 K\x04gL\xdfP\xf7\xea\x0c\x87}B\x83\xf3\xc3\xac\xef\x14\xf3=\xb8\xd3\xd3bzZNO\xabix\x8a\xa7\xe1i6\x0dO\xf3ixZL\xc3\xd3r\x1a\x9eV\xfe\x11El(\\\xda\x1b\n\xc3\xd8:/r\x8a'l\n}z\x9a\xc9?\xe6\xf2\x8f\x85\xfcc)\xffX\x89?BU,T\xe5BU0T%CU4TeO\xf1$TU\x86\xaa\xcePU\x1a\xaaZCUm\xa8\xeb\x0du\xc5\xa1\xae9\xd4U\x87\xba\xeePW\x1e\x82\x83M\xde\xed\x96\xa2\xaf\xad\xd7\xeb\x1b\x938SD\xc8\x95\x11\x9e\xe6\x03\xf6\x1c\xb3\xa3G\xb1## \"G\xc8Zj\xb0i\x88\x88BLZ\xba\xe5\xc0m\xae\x96\x1fo\xccF\x98\xf5\x84\xd2\x82V\x90\xfb\xd8\xc7\xfd\xc2\xd1!P!b\x07\xab\x89\xad\xb7\x10Sa\x88ks\xe5r\xbf\xee\xb8g\xb2\x07\x89\xf3n\xd4\xe5\xaa\x80\xa9\x8cc\xae\x19p\xc8m\xc1\xda\xd1\xf1\x01c\x89\xa7.\x95\x89\xe3j\xfa\xfa\x9b\x90\x0eH\xed|W\xa5\xdc\xd6$\x9a0\xd9\x849%\xe9\x15\x19\xc2@\xc9\x95\xfc)Ll\xeet\xc0\xc5M\x1c\xb0\xf8t\xce\n\xe1!\xd6\xab\xa7\xea\xed\xf3\x95W\x00Z\x12Wo\xb7\x9b\x90\x95-\xa7\xa7\xe5Gv\x96o\x1a\xb2\x13}\xeaP\xcb\x8c\x9e1\xef\x91\x1c\x9e\xe8\xfc\x16\xb2( \xa7\x07q\x04\x8c\xfb\xe0\xee\xb7\xc8ba4\xccc \"\x93\xefi\x81\xb9\x18gE\xc6\x8e\xbc4g\xe0\x037\xd1\xc7\x9d\xd7\x03\x80\xe3u\xd29vf9\x01\x81\x1b\xf3\xed\xb6\x0b_\x87Km\xffR\xe3\xd0\xfa5\xe1I\xb5\xcdI\xd3\x06\xc9)\xcbSp\x9eor\xc9=\x19%\xccpz\x02\x00\x8a\x9b\x12@\n\x8f\x03@\x82\x08 \xcc\x8fZ\x10 D\x1f\x07\xa61:\xc1:U\xca\x19\x1e\xbbf$=\x84\x19j\xee\xe9\xc7\xbf\xb0\xfb0\xfe\x12E\xff\x16\xfdx\x0b5>\xa8\xe9\x0b\xad\x1bH\"\xac.y.\x82\x0e\xb3\xdb\xc5N\xbf\x8b\\\xa3\x95\xdf\x9c\xb2\xa3\x02-\x19\n\x8c06\xbc\xed\x05LY\x18\x8c\x8aG8\x90\x88\x01\xc1h\x84#\x88x\x84\x8dJX\xb2\xcd\xcf\xa4\xf6\xb6\x8cC\xfc\x0d\xeb#\x01\x11=\xcd\xea#\x01!\xc0\x82:\xdb\x990;\xfa\xf1F\xd2\xb4\xee\x9c\xbe7\xf0\x86\xa7\xe9<\xe3m\xff\xfd\x0c\x7f\xa2E^N\xffT\x16$)\xa7\x7f(\x8b\xa6\xccI3\xfd\xf0\x87\xf2Rg\xb4\x9e\xfc\x07}\xfd\xa0on`\xb4\xd4\x882\xab\xde&\x0bc\xfc\xe8\xc6$\x19h\xacg\xcb\x05\xc5\xa2\xf1\xcdavX\xb8\x137\xb7_\xf6\xe98\xd2\xbe\xb0jn\x11\x9d\x83\xd9 p\x8e:+\x1a\xdaN\xa2I\x103\x8f\x0f\xe6Q\xc3\xd9\xf2\xf3n4\xb2cx\x02\x99\x86\xb7\x8d\xb0y/\xcb\xcd\xf9\x8e{\xdb\x87\xbc\xd9\x1d\x1b\xe6\xc8&\xab\xd8\xb0\xf1\xd9\xfa8\x83\xd5\xceG\xcd\xdf\xbe\x96u\xca\x8f1o\xc5a\xe6<\xe7\x89\x9d\x97\x13i\xddoL\x7f\xcb\xee\x1f2\x1d\x97$ \xa2\xd5\xaa\xa6\x13\xc3j\"d\xda\xd7\x98\x961\xfcnUS\xc6\x93\xcb\x088QoU\x1b\xdd\xc2\xaeX\x93\xd4e\x9e\xb3S\xd0g\xf2&\x052_\xc0\xe8 \xf8\xb6\xe5\xb0[\xd8u@\x92\x81\x9b3\xbc\x83q\xacu 0`v\x8bC\xd8T\x96?\x88\xd1u\x89\xf4%\x0b\x1c\xdc\x02\x9b\xcd\x0c-\xb0Y{\n\xc4\xb3(BK\xc41/\xa23\x82C~\xc9\xd2wkmX\x97\xafW\x03\x17\xc0\xa2<.\xedR:\x16\xf2\xe0\xad \xe2)\xfb\xab9\xcb\xbf\xce\xa9\xfc+?\xca\xbf\xde\x9a`\xa6p3\x85\x9b)\xdcL\xe1\xe6\n7W\xb8\xb9\xc2\xcd\x15n\xa1p\x0b\x85[(\xdcB\xe1\x96\n\xb7T\xb8\xa5\xc2-\x15n\xa5p+\x85[)\xdcJ\xe1\xd6\n\xb7V\xb8\xb5\xc2\xad\x15\xeeI\xe1\x9e\x14\xeeI\xe1\x9e\x14n\xa3p\x1b\x85\xdb(\xdcF\xe1\xe2H\x01\xe5\x9f\x9d\xa4#\x05\x95\x7fvX\xa0\x14\xa0\x15\xa0\x16\xad\x97X+&\xd6\x9a\x89\xb5j\xe2\x19r\x8a\xbc\xb3Uw\xbe\xba\xd7\xfcl\x8b\xd16\xa1\xb5\xae\xf5\xaa5\xa7u\xa3\xa5\xaf\xe5\xab%\x08d\x04D\xc0Z\x08>/n U/n\xe8\xd4X\xf6\xcd8\\\xf1\xff[\x83\xdcH\xe4>\xcd\xc3\xb9\xf8?\x9d\xbbQ\xe3\x80N{\x12i\xab\x15Bn-2\x97O\x08\xb5\x95\xcc\x04\xdc-E\xda\x02cn!2\xe7\x18os\x919\x03\xbc)\x01`\xbcI9`\xac\xb1\xe8'\x9e]\x85\xb6\xa1\xfcxV,\xb2P!rH$ \xa8$\x19d#\x10P\x9c,\xe3Id\xa02e\x88\xb5@\xa0\x82e\x88\x95D\xd8\xbc/E\x06*b\x86X\x08\x04*g\x86\x98\x0b\xc4\xcc\xe6\\\x89\xcc\xcb\xb9\x94\x9c\x97q)7>Z\xab\x9c\xe6\xd4)\x84\xf75S\x1f]N\xccs<\xea\xe8\x10\x11Gx\xb4\xd1\x9c\x82\x0d\x07\x98\xcahN\xc1\x13O\xf7\xe8\xa29\x05k\x0e\xf0\xa8\xa29\x05+\x01\xb0\xb9^\xf2t\x8f\"\x9aS\xb0\xe0\x00\x8f\x1e\x9aS0\xe7\x80\x99\xcd\xb3\x14\x94\x97g!//\xcbBZ\x86\x0e\xf8\xaaq\xa7\x05c2\x01*CBb\x03\x82jEB#\x03\x8a\xaaG@7\x06\x12\xeaI\x00\x9e\x0c\x00\xaa0\x81\\\x1bHTs\x02\xb92\x91n[\x97\x06\x00\xd5\xa5@.\x0c$\xaaT\x81\x9c\x1b\xc8\x99\xdbRK\x05=-55\xd1\xd3\xd0h\xf4\xd4Vb\x04C:\xdc\xd1\x01\x8d\x0eYtP\xa2\xc3\x0e\x1dX\xe8\xd0A\x07\x07\xc0\xfb\x03\xe7\xce|\xb7\xe3\xe4x\xaa\xed\xe4X1\xaf\x93c\xf4\xbdN\xae\xe3\xc3vr\x1d\x97^'\xd75\xc6\xeb\xe4\xba6\xdbN\xae\x93\x88\xd7\xc9u\x82\xf3:\xb9N\xbe\xb6\x93\xeb\xa4\xefur]S}N\xae9{\x9d\x9c\xca\xf2;9\x05\xf1;9 q\x9c\x9c\xcc\xf0;9\x89\xf0;9\x89p\x9c\x9c\xcc\xf0;9\x89\xf0;9\x89p\x9c\x9c\xcc\xf0;9%\x17\x9f\x93\x93\x00\xd7\xc9\xb1\x1c\xd4\xc9\xa9\x1c\xaf\x93S\x08\xaf\x93\x93\x08\xdb\xc9\xc9t\xaf\x93\x93\x00\xaf\x93\x93\x00\xdb\xc9\xc9t\xaf\x93\x93\x00\xaf\x93\x93\x00\xdb\xc9\xc9t\xaf\x93S\xe2\xf089\x99\xef8\xb9\xe6<\xe8\xe4\x00d\xc8\xc9\x01\xe8\x90\x93\xd3P\x8f\x93\xd3\x80!'\xa7\x91CNN#=NN\x03\x86\x9c\x9cF\x0e99\x8d\xf489\x0d\x18rr@\xbe\xfdNN\x03m'\xd7;\x95\x01?\xf4\xf5\xa7\xbc\xfeX\xd7\x9f\xe3\xfa\x83[\x7fR\xeb\x8ff\xfdY\xac?|\xc1\x87-\xf8ne\x9f\xa5\x8e\x97\xe3\xa9\xb6\x97c\xc5\xbc^\x8e\xd1\xf7z\xb9\x8e\x0f\xdb\xcbu\\z\xbd\\\xd7\x18\xaf\x97\xeb\xdal{\xb9N\"^/\xd7 \xce\xeb\xe5:\xf9\xda^\xae\x93\xbe\xd7\xcbuM\xf5y\xb9s\xea\xf5r*\xcb\xef\xe5\x14\xc4\xef\xe5$\xc4\xf1r2\xc3\xef\xe5$\xc2\xef\xe5$\xc2\xf1r2\xc3\xef\xe5$\xc2\xef\xe5$\xc2\xf1r2\xc3\xef\xe5\x94\\|^N\x02\\/\xc7rP/\xa7r\xbc^N!\xbc^N\"l/'\xd3\xbd^N\x02\xbc^N\x02l/'\xd3\xbd^N\x02\xbc^N\x02l/'\xd3\xbd^N\x89\xc3\xe3\xe5d\xbe\xe3\xe5\xce\xe9\xa0\x97\x03\x90!/\x07\xa0C^NC=^N\x03\x86\xbc\x9cF\x0ey9\x8d\xf4x9\x0d\x18\xf2r\x1a9\xe4\xe54\xd2\xe3\xe54`\xc8\xcb\x01\xf9\xf6{9\x0d\x1c\xe1\xe5\xc0\xfc;\x9c\xc5\xd6\xf3\xd4z&Z\xcf5\xeb\xd9d=_\xacg\x84\xf5\x9c\xaf\x9e\xd5\x05\x93\xb6`N\x96O\xb9\xdan\x8e\xa7\xdan\x8e\x15\xf3\xba9F\xdf\xeb\xe6:>l7\xd7q\xe9us]c\xbcn\xaek\xb3\xed\xe6:\x89x\xdd\\'8\xaf\x9b\xeb\xe4k\xbb\xb9N\xfa^7\xd75\xd5\xe7\xe6\xf2\xa3\xd7\xcd\xa9,\xbf\x9bS\x10\xbf\x9b\x93\x10\xc7\xcd\xc9\x0c\xbf\x9b\x93\x08\xbf\x9b\x93\x08\xc7\xcd\xc9\x0c\xbf\x9b\x93\x08\xbf\x9b\x93\x08\xc7\xcd\xc9\x0c\xbf\x9bSr\xf1\xb99 p\xdd\x1c\xcbA\xdd\x9c\xca\xf1\xba9\x85\xf0\xba9\x89\xb0\xdd\x9cL\xf7\xba9 \xf0\xba9 \xb0\xdd\x9cL\xf7\xba9 \xf0\xba9 \xb0\xdd\x9cL\xf7\xba9%\x0e\x8f\x9b\x93\xf9\x8e\x9b\xcb\x8f\x83n\x0e@\x86\xdc\x1c\x80\x0e\xb99\x0d\xf5\xb89\x0d\x18rs\x1a9\xe4\xe64\xd2\xe3\xe64`\xc8\xcdi\xe4\x90\x9b\xd3H\x8f\x9b\xd3\x80!7\x07\xe4\xdb\xef\xe64\xd0qs\xe2*\xf1\xbe\x87^\xc4[7j5\xb9-\xab\xed\x13X\xcb\x13;W\xba$\xbd\x01kg\xef\xc3nO\xc8\xd6lV\xb9v_\xf6\xe1\"\xc1+\xdc~\xc8\xcb<\xb3\xab\xe2\x9f\xdb\xfaY\xddN\xfe\xdc\xee\xcb\xf4\x9b\x95t(\xcb\xd6JR\x05S\xb7`\xea\x16\xd4[@\x9e\xfc\xdb/\xac\xb3_mYyN\xfd\xa4i\x8a\xb4\xc0>;\xc6\xdbk\xed\x1c\x9c\xa1T\x84n\xbeHj\xdbCV\xcbmx\xa0\xd5I\x99\xb3\x1b\xf4\x87p,\xdb\xcc\xf3\x92\xec\xad9\x1dYs:\xbe\xe6\x14\\\x9a\xbf\x8dnPy_\xd8\xff\xc2|TZ\x93\xd0c\xed\xec\x88\xa3\xb8\xe4>)\x8b\x94\xbdo\x85\xd8\x18\xcct\xac\x0df:v\x87\x92M\xfb\xc8b\x99\x88U.U\x9fP\xd7\xf3\xe3w\xf3\xdb(\xacy:\xcfm\x9d\xces\x1b\x87\xd0L{h\"y\xa0e\xef\xc0\xbd\xe6\xc2|~J\x8c-3-\xb3\xa6\xad\xb3\n0\xb7-\xda\x137\xb8Oe\x9a~\xc6Le\xd3\xfd\x93\xe5\xd9\x06u]\xda\xbb\xfd\x9dm\xac\xe2\x83\xed$)\xf3\x9f\x92\x9c4\xcdo\x7f\xd7\x0d\xce?;G\xec\xcc\x170\x922\xbf\x9c\x8b\x1d\x0f\xfd\xd9\x1e2N\xa6M\x0d*S\x91z\xba\x8b6\xcdsH\xd9\x1d$Cy>\x10t7\x9b\xa2\xec\xa7\x845\x8a>u\xff0+\x11gS03\xb1\xb3\x80\x9d\xd8Y\xc0P\xbc\x04\xdd,`*\x1e\x822\x1d3\x16$K\xea\x17\xc9r\x08\xba\xf6\x82d9\x041\xe1\x8a\xa3>^\x8b1\x8e\xff\xf8Mf\x04\xcc\xc4\x0c\x1a\x8d)\xd4\x01Zh\xcb\"\xbaIV\x98\xd9d\xc5\xa1\xc4l\xc6H\x07\x06c\xa4\x03k\xc1\xe9\x9c_\x01\xfd\x94\xb4\xd4\xf0\x1amv6\x13:\x04{J-/\x13\x92\x1bY\xe7\xb2hO?\x1b\x17\xcetC\xd4\xcd\xa6\x1fr\x9d4g\xa7\"4\xc7\xaa\x11\xc5\xf0\xaaU\x96\xc9C\xe4\xe7!?\xfax0sp\x1eL\x8c\xc1C~4xX\xac\xd8\xa9o\xa6b\xb6\xc8zu?Go!sg\xd3Pz/\xe4\xd8\xae}}\xa8\xf6\xdc\x9e\xdb\xf48\xcd \x8bc4e\xfe\x1b~s\xb3 \xca8\xf2\x8b\xdc.\x189OE\x0b{\xaf\xca\x8c\xdf\xc2\xc6ks\xe3\x10\x9e!\xae=\xc2\xf2\x15g\x98\x17\xd7\xd9\x08\x05\x1d\xd7\xb8ws\x02\x01-xh\x03\xf7T\x043-\xa1/\xb6\xf0\xbfh5\x00*\x81\xd6\x93`\xc5\xe1\xad\xffF\xa7\xf1\xa2Eo\xa9\xc5\x05.j\xfe\xd2\xcf\xd8\x17\x87S\xd00\xf3\xe6\xaa\xd8\xea2\\K`\x80\xc4\xe4\x8fg\xf3\xa2\xa1\xccCK\xea\\d\x80u\x8d\xa5\x1f\xa4\xed\xc1\x1d\x8cMy\xe9jm\xc9\xf42dJ\x19\x03\xd8r\xf62\xa2\xea\xb1;\xa8\x9d\xe1e\xa3'\xdf\xea\xec\x18\x13\xd0\xe5\x04|\xd9\xf9j\x07\xb1\xd6\xde\xa1\xb5k\xba(\x1d=D\xf6\xe46g\xeb\xe2\x8b\xc8\xbap\xe9\xa6G{0x\x06\xcdy\x82\xba\xcb9\x18\xc5X`o_\xcc2s\"\xe2\xa5{]\x8d\xf8D\xd1U\x8b\xdfc\x19p\x9c\x8fr\xf1\x8a\xa2\xe9\xf4q\x9aS\xfbK\xc9f\x08\xe4\x8c`M\xdc*mj\x85\x17\xc9\x8fx\x91\xcek\xed\x8c\x9b\xcb\xf4\xed\xa2\xeaJSK\x9e\xce\xfd?+[\xa0\xf9\x11\x11h/\x0b\x8e'\xb5\x04\x9a\x1fQ\x81\xda4}\x02U\x0c\xe1\x02\xf5\xb1\xc6\x05z\"Mp\xa04\xed\xc2<\xd7a\x9b\xf9\x16\x1d\xd3\xf4\x17\xb3\x909\x17\xa3\xb7\xb8\x94\x95o\xe3\xa3\xb6\xec6\xbf\xb2'q\xdf\xb6\xb3\x1d\xf6\xd5\xc8\xbe\x14\xe1W\xa3\x1d\xa3\xed\x9c\x1bUw\xc2\xc5\x04\xf4\x85\x16m#6\x9dH\x81}\xf1\xb0)6\x8b\xaftu\xb8\x02\x95)\xf7\x13b\xdd\xa9\xb7k1\xf9\x8a\x85\xedIx\xa2y\xc5\x1d\xee\xd4\xcc\x90\xf4\xc50k\xe4 \xc7o\xe2\xc5\xd8\x89A\x95\x7fEK`\xb9f\x10\x063\xcc1\x1a)$\x03\x9e\x9e\xb2\x06\xc4\xba\xe6\xd5\xe4\xd1\xb0>\xf3c\x92\xe3\x1f\xf9\x84\xbc\xe7+\xb1\x87\x1f\xf4\x13\x97\xdfD\xfb\x08W\xec{tU\xbdM~X\xad\xf7\xf1\xea\xe9\x1e.\xed\xb2\x16\xd7\xdct\xf9\xd8@\xd2\xb4,L\x99#\x1f||\xbf\xc8\x0e\x93x\x8fDtg@J\x88\x05Y\xd7\xe4U\x06b\xf2*\x0f\x98\xbc\xc6\x1b&oB\x0d\xa3vJ`\xb9\xae\xc9\xcb\x0c\xcc\xe4\x8dB\x88\xc9\xdbeQ\x93\x177\x0c\x9b<\xf6\x98<\xc7?b\\\xf7\x18S\x0f?\x9eY\x9de\xfc\xbd&\x9fD$^\xed\xef\xe1\xd2.kq\xed5y!C\xdf>\x87\x1d&\xf1\x1e\x898&\x0fK\xd0\xba.k\xd7\xe0E2b\xee\"\x07\x18\xbb\xc4\x1a\xa6\x0ea\x86)[h7\xcf5r\x9e\x8c\x998(\x80\x18\xb8Y\x0e5oq\x056\xe4\xac\xc7\xb89\xfa\x113\xba\xc7l\xbc\xdc\xa0\xa6\xcd\xaf\xe8~\x84'`\x9e\xf4i\xf14\xbf\x87G\xbb\xac\xc1\xb3\xd7\xb0\x85\xfc|\xbb2v\x98\xb4\xbd\xd2p\xcc\x1a\xe2Up\xc8\xb4\xfd\xbf=\x05\xd9N\xf9\xa5\x0cx\xcc2\xf2A\x90\xbe\xb2\xd1\x0dt\x1c\xe7\x15\x1a5\x0d\xb0Dg\x91\xd4a\x91y\xf7\xaf\xe7\xee\x1cV\xbf0^\x10B\xfb\x16\xd0\xcc \x10\xcf\xb3<.Mi\xf1(U\xce\x10\xbb\x1c\xf1^\x82\xf2\x0b\x18\xa3k\x15\x03vc\xc3\xd9r\xe9\xa8\xba\x01\x11\xc4\x12\xa7#\xc1\xfb\xb6\x17jHL\x0b\xc7\xcf\xca3V\x84\x1f*3\x8a\x18\x03\xae\xbd\xbbeT\xfb\xc5\xc0lR\x95\x93n}&\x83\xddx~G\x95r\xe0E+\x16\x83\xae=\x0d\x81PAf1q\x8a\xfd\x93\x95\xfaJF\xe3\xb8\x97I\xeb\xe4\xfd\x8a\xb4\xbb\xb9(\x08\xaeG7\xe4\x0c\xd3\xb5\x1b\xc4\x8b(\x87\xe7-)'\xb8\xec9\xa3>==\xc0\xa01Q\xbdV\x1f\xc9\x10\n\x06\x1bQ\x1f\xff2\xf6\xdd~:0\x86\x19\\\x18\x86\xeei\xaa\xb2B\xfb\x86pL!c\xb4)\x8e\xed\xde\xc9\xab\x16\x03\x9b\xb6\xf0\xb3\x1e/\xc29~w\xed\x18\xda\xcd\xb9\x8f6_\xeb\xd8\xb7E\xefT\xb8^\xb1Fg\xc2\xf5\n\xb6=1\x9e\xe3+\xda\xee\x0c\x86\xbb\x07\xcd3\xa9\x1e\x9c\x9b\xa0-/\xc9) \xeb\x93gRd\xd5%g\xcf\xd4\xed\xfc9\xe6d\xbc\nl.\x0d\xad\x03>\xa3\xc4W\xcd\xd9z'\x92\xda\xb8\x89N\xc2\xc8ux\xff=\xcc\x8b\xae\xbf\xec\xdbB\xec\x92`\x7f\x8a\xf3#:%tS\x1cx\xe8\xc0\xc3\xbf\xd5\xee\nV\xab\xd8\xb2j\xb2\x1e\x1a\x0f\x0b\xce\xe7s\xfc!B\xc06d\xf8\x8aKs\xd4\x12\xfa\xbcz\x9b,\xad\xf82\xf6\\U\xee\xc32\xbe\xc0\xb2\xc3\xbe-\xc0\xaa 6\x89\xdf\xf5\"d&n\x87,4\x8b'\xdf\xd9S\xde\x9f\xc4\xfa\xf2\xefV`\xb3\xc4\xbe\xff\xc2s\xb5&\x1d\xae\x96\x8c\xd1 \xa5\x07r\xc9[(m\xcff\x11#\x1af\x1b|Ay\xa0I\x95\xa45*\x93B7 jP\xc1dZY\xd1\xe29L\xeb\xb2J\xcb\xd7\xce\xd7\x1c\x8f9\x1d\xcf6]u\xff\xec8>\xed\xfe\xdd\xde\x91\x07\xd4\xe0\x8c\nLs\x90\xa9\xc3f!\x91S\x94\x18\"s]^d\x0e\x10FHh\xf2\xae\xb2\x00y\x9e9D\x1e1\x01\x89DlAS G\x91GHh\xee\x11\x9d\x02\xf6E\xee\x10\xff\x98a\xa8\x06\xf4\xd6\x10\x8e\xab\x01\x19\xb4F\xf7\xb9I\xb8'\xe9\x91\x0e\xbd\x815\xe7\x85\xeex1\xcb\xaawFW)Y\x18T\xa0\xdd\xc8$\xa0\x0c\x91\x14\xbaI\x86D%l\xb8\xab\x8db\x9e?\xe1e3\x1f-\xd2\xb5\xc5\xfc\xf7\xf1\xd0\xd3\xdd%1\xb3\xbb\x8b\xd4\x11\xdd] \xa7(1D\xe6\xa3\xbb\xbb_mhww\xc9\xf7\xf7\xc7\x1e\x13\xc0\xba\xbbC~\xa0\xbb\xfb\xcd \xef\xee.\xfb\x03\x9d\xb1\xcf0\xd0\xee\xee\xb6`\\\x0d\xfe\xee>\xb6\xe7Y\x9d^\x16\xc3\xefP\xe8\xca\x89u\x87\xfe\xae\xb3L\xf6O\xcb\xc4\xaa}\x91\x10\xbaH\x0c*\xd0\x80d\x12\xd0\x8a\\\xb6r\x93\x0c\xd1J\xd8p\x9f\x1b\xc5\xfcb\xb1I\x17\x0b{\xe9e\xf3\xb4\x98on\xef\xc8CO\xbf\x97\xc4\xcc~/RG\xf4{u\xa6\x17#\x86\xc8|t\xbf\xf7\xab\x0d\xed\xf7.\xf9\xfe\x8e\xd9c\x02X\xbfw\xc8\x0f\xf4{\xbf9\xe1\xfd\xdee\x7f\xa0W\xf6\x19\x06\xda\xef\xdd\x16\x8c\xab\xc1\xdf\xef\xc7\xf6<\xab\xdf\xcbb\xfe~\x0f\x1f\xf5\xf4t\xfa}\x129\xd3\xdc\x8b\xd5\xfe)%\x9a\x044\x1d\xf6\x1b(\xa3\xfb\x1dZ\xbf\x0dY2\xc0p\x0f\x1bfu\x1e\xef\xa3ti\x8f\x8e\xab\x0d\xd9'\xb7\xef\xaf\xba\xa7g32f\xb7\xee\x92F\xf4i~:\xdb\xa1a\xcbstW\xc6\x94\x81vb\x8bj\x7f\x17CU\x8a\xf5]\x93\xea@\xc7\xc5\x0c\x03\xef\xb2\x16\xb3\x03\xbd \xd72\xdaS-~G\x10\xee\xe9\xa3c:\x8a\xddAE\x19\x7f\x07\x15\xab\xa3\xfd\x86\x7f\x88H\xba\xb0\xab\xa6\x94\xcc\xe6+\x83\n\xb4\x0c\x99\x04\x14 \x17\xd7\xdd$C\x9c\x126\xdcoF1O\x93\xcd:\xb6\xbfb\xd2\xe5\xd32\x9e\xdd\xde\x91\x87\x9e\xee+\x89\x99=X\xa4\x8e\xe8\xc4\xea\xc4n\xd3\x1e\x9ab\xf3\x18\x8fg\x82\xcdf|\xa0\xf7\xf9\xed\x00\xed\xdd\x0e\xef\xa3\xc8\xfb\xfb\xf6\xc8\xeeeumY\xca\xdf\xb5\xf3\xac\xf8\xe5\xea\x9c\x9f\x11!\xac\xf1\x81_\xab\x074e\xb9\xa9\xfa\xcb\x10K\x97\x10\xda #\xac\x99\xb1\xe2\xb0\n\x97\x90F.\x19\xa0\x1c\x02sf\xbf\x81\x99\x80\x16\x98{\x83\xe0%\xa6\x03\x94\xa4\xc4g\xf3\xe5l\x9d8\xcb?\x97\"\xa5u\x9e\x15\xc80\x8bV2\xba\xb3b\x1c\x8d\xee\x8d.\xfb\xea\xe2Ul\xe9\x8am\xd0\xef\xfe{\x14K\xb9\xcf![\x08z\x87\xad\xf8\x8cls\x86\xe4\x9b\xb3I\xfe\xf1\x83\x13\x8c\xe8[\x03\x89\xbf5\x16\xef|i\xecA\xda\xd8\xf6%\xb8CEa\xbe\x00\xb8\xb9\xbf\xc98Ny\xd9\x9f\xb3\xf6g\x8d5N.\xd1\x86\xfa\xf2\xf6\x97\xb6-\x0b\x90i\xee\x93!)\xbd\xca\xd5\xb4\x08;\xac,2\xd9Q\xdfI\xd7vR[\xc7\x8f1D\x7f6\xaf7\xcc\x8a+8C\x9b\x94yN\xaaF\x9fJc\xdd\xf8%k\xb2}\x96w\x18~\xf1\x8d\x06v\xe5M\xf9\x020\xfb3\xa7\xb7\xb6F\xf1\xe2\xee\x88\xf2\xf5\xc6n\x97\xe8\xc7p\xfbP\x15w\xc1\xba\xbb'FXD\xa4\xae\xeaQ\x17\xf582\x0d\xda\xec\x9c\x15\xc7\xe0p)\xf8R=%\x0d5\x85\x8aC\x86\xf2\x91\xaa\xd2\x8b\xe8\xb1\xe1\xdc>7n\xe5\xf9\x0b\xb9T\xab\xba\xach\xdd\xe9\x84\xb5z\xaa\x05oU\xd1\x03\x1c\x85\xba\x85 \xa9i\xdb\xb7\x81.\xd2\xa2\x87\x9bg\xba\xae\x8a\xef\xa1\x10\x1dV\x1e\xb5d\xbb\x12T/\xe6\xfe\xaeo\xb3\x02\xbf\xac\x1bC\xdcT\xc0\x87\x1d\xaf\xb1\x82A1\xc6\xaa\x05}\x008\xd3\xe2\xe29F\xc3.U\xe0\xfb\xae\xd4A\x9a8\x8a\xa2\x9d\xd1i\xf4\x8b\x0e;\xf0\xd2\xc4\xca>r\xa6\xae6\x99\x89\xfb5\xac\xed,\xd6\xd5\xda\xbb\x9d\xf7\xb6\xeb\xcd\xb37\x8c\x80\x8b\x1b@\xaa\xb1a\x81\xedm\xb0\xb6\x92`\x91\x01\xdf^\xdb\x0d\xe3fyk3q\x0f\xccR\x13x\x9e\xff*\x0f8\x81\xeb\xef\x0d\xec$L\xb3\x97,\xa5\xb5<\x96\x15\xeb\xa7\xd97L\x1d\xf6\xd0\x81\xccE\xf0\xcb^L\xc2\xcfy\xf6L<\x0f\xc0\xcf\xab\xb7 ;\xc4\x9b\xe4\x94\xd4\xdb}\xd9\x9e\xc6nSR\x11\xdf\x1c\xd9\x9f\x84\xb1 c\x0e$\xc7\x8c\x87V\xdd?4\xa6\xf0\xbe\x1fo\xd7'\x82\xc9gbW\xa72pnt\xb6\xc1Rg\xb8#\xf9\x11\x11\xb0\xaf\xe3>\xab\xd8\x1f\xe1Mgy\xb8\x03\x00;\x06\xeb\xa9g,1\xb4\x85c.\x820\x86A\xec\xcbUn\xe9\xa9\xea\xf2\x98\xa5\xdb\xff\xf2?\xff\xd8e\xfd\xb9+v(\xebs\xf8\xa7,\xa9\xcb\xa6<\xb4\xe1\xb1\xeb\xa1\xb4h?\xd1\x821\xf7\xbb\x03\xc9\x1b\xfa\xf9f\x7f2\xb3A\xd0\xbc\xba\x87C\x88w\xcc\x1c\xd9\x0f\xd9\x08\x0e^\n\xd9\xc9}\xac\nu\xa2\xa4\xeb\xa6\x03=\xaa7.\xb4{Q\x17D\xf7\xf6\xa2N\xac\xdd\x0f=\xf0\x1f\xb27\x9aZ\x87'\xd5\xceC\xcb\x07l6\xd1\x0d\x8cE\xb6\x1c=\"\xb9T\x13\xee_\xa7aA^\xf6\xa4\x0eX\x9db\x7f\xe3D\x11\x11\xa8kR\x16--\xda\xed\x87\x0f\xd0\x89\xdawS)\xdf\xa8+1\xb8\x19\xac\xcc\xe4\xbd\xab\x82\xa9Im\xf3w\xafH\xec\xddz*jc2\xb0\x89#\x82\xe9\x81\xfb,\xe7\xa6\xbf\"\xe0\x07\x85\x0c?zn\xc70\xe2\x19\xcf\xb6pE\x90}\x9e`\x15\x88\xef\x16\xa7\x16\xf8\xb2\x93I\x05~\x1e\"\xb4\x9cl\x9e\n\xbe\x8f\xb1Bv6O\x85_\xfdX)'\x9f%\x87\x03\xc5\xe4\xac\x88:M\x0cZ\xc8>d\xbfX\xc2\xd2\x89\x8e\xa2&\xfaO\xb4\x14\xc827n\x8b]\xda\xf2\x93\xaf-\xcb|Oj3wi\xe5Nl\x16T:<\xa9akN\x80\x80\x05\xc0\x14\x84\xdc\xb3A\x0er\xa4\x18\x02:*\xca\xf6\x13\xbcz\xf13O\xd1\xf7\xe6\xf1\x04;\x86\xfd|E\xe7\x81\xa0\xc5\x80\xeb\x1c\xadS\x03~\xe4\x9d\x95\xb7e\xc5{\xabb\xc3\x1c\x90\xacL\xa7f]\x91+\x07\xc3,\xed\x08\xdeAC\x8e\xbaf\xfa\x182\xf2l~<\x16`\x03F\xe8\x8c\x0f\x0b\x03*\x12\xd4\xe0\xab(\x96T\xec\xae\x89\x95\xb0D\xf3N\xaa\x115\x81\xeb\x1b\x1d\xa3\xfaN\x89O\x1c\xa5:\xe3\x0e\x8b:\x1c\x1c\x0cBL\xa6\xbf8P\xf3\xf6\x84'\xfb\xfa\xa2'\xb7C\xb2{\x0b\xfa\xc9\xb0\x98\xc3\xa0\x13\xcf\x0cB\x1e\xbe\xff\x1e\xfb\xc6\xfb\x18\xe0\xcd\xcb\x8a_0N\xbc3\xb72\x02A\xc6\x10vz\x84e\n\x95\x8b\xa7\x01\x05\x83\xb6%\x88I\x03\x1d\x9b\xf4\xd0\x88&\x82\nh\x95\xe9\x86\xbc\xfe \x0e\xc9\xdel\xdeA\xcd0S?\x9c\xb2\xf3\xbea\xe5e\x07\x92\x85/\xb0x\xe0\xb6\xb7CrG\xb5\xa3\x9f\x0e\xc0\x88\xb1\x0d^S\x15[\x87\x9e\"/\xb7#F\xbc\x9e\xc1\xce\x8eUz\xdc\x8dw\xf4ZTo\xbd\xe3\xd7\xc8A\xc7b\xa5\xcf\xff\x0c\x8eo\x83#,,\xb5\xe8\xb1\xe4w\xf3*(\xd9>q\xf7\xf8\x9b\x07hy<\xd1\xfb\xe9\xcbqJ\xae\xcc\xeewS\xae\x1aa\xfd\x7f\xbd4mv\xc8hj\xceM\xc3\x01\x82OV\xe7\xe4[yi\xc5\x97\xa3 -\xa7\xb6\xb7\x0d\xadHMZ\x8aRvF33Gt\xdc\xde\x87\x9e$;\x1f\xfd\x15\xf0D6\xe6^\xf1\xe1\x0c\xc7\x9b\x1fm\xfac\xed\xa7\x94\xb4DhZ\xact4?\xb3\x92\xc8\x99W?\x18\x0c\x9bw\x95\xc3\xaf\x00\xbc\xbb\x1e\xe4\xa8\xad\x9a\xf4e\xd3\xa35MZ\xe1d\xa3\xcf}\xd7\x1dq-\xf9?7\xb9\xd9\xf8\x0d\x03P1_\xfc\x02Z\x1ew\x0d\x9aP\x9cq@\xdb\xe5K\xdf\x05\x05\xa6\xac\xbdWU\x9b\xc4\xd9Z#\xa4?ur\x91\x93\xea}\x90}[\xf0\xc1\xedoz\xa9\x98\x87y\x0f\xc6m\xc2\x08 \xda\x90|\xe0j2\x0f_^\x94\xcb\xd9(\xa8\xe4\xcdw\xc5Y\xafxz\xd0>A\x8d,b\x8b\x0c\xdeA\xc7\x11\xcd\xb9\xcf\xdc\xba\xdc\x01s\xb3!v\x95\x7f\x8b+\x01=\xac{0\xa3\x8cmT3\xf2\x81\x8b\x05=|yQ#\x8d\xcd\xc7[\xbf-x\xc4\xd3\x83\xbe\xd3\xd8\x86D\xe6\x1a\x1bb>\xcc?\x83\x04\xcf\x15#\xda5#\x14G\x84wN\xa5\xf7\x96\xb1\xae\xd7\x19.\xed\xc6\x92\xc3\x92\x90\xd1\xc3\xc7\xd1G\xe7\x11\xaa\xd7\xa1\x1b\xe9{\x17\xca\xe0\xc5\xf4\xee\x89~d\xe9\x8e\x8e\xbe\x94\x1e\xe1U\xa4\xe8kH\xbd#\x84;\"x\xa9\xe5\xc7\xe1\x8d:\xae7s\xc9a\x17\x8c\xf4\x82t\xc4\x03\xef\xdf\xe8\x89\x1d\xcc\x17e\\\xbb\xf6f[\x81\xb8\x08s\x87\x10m\x96\x82/c+\x87\xfa\xdd\xb3\x81\x00uu\x92\x14\xd8\x84`\xecepr\xcd\x1a\xfb6B\xe0\x1b5\xee\xdc\xc6\xc08\x98\x88\x1d\x05S\xf8\x030\xa2\x928\x0b}>\xdd\xdc\x1d\"\xa8\x17\xe4%x\xbf=?R\x17\xcf\xd9\xf9x\xd5\x13\xc2\xca8\x82\x96\xec\x1b\xebu\xab\xd8z\xf2O\xc2:C\x82\xbb\xc4\x0c\xdbS&*\xa1\xcf\xc4\xec\x1c=\xdb\x1d\x9c\x08\xa7\xf7\xba\x9f\x89x\xd9\xc9\xaa\x8d\xab\xc0\xba\x93\x91R:\xe1\xffc\xb7\x04\xec\x05\xc2R\x81B\xdd<\xc3\xb8\xbaPN\xd8\x91\xb8\x9c\xa2\xf7\x16\x19K\xba\xd68\x8e\x9b\x1c\xe3\x80\xfd\xa1'\xf5\xc0\x84\x87\xa9\xbd\xc8WD\xab\xcf\xd4\xbe\x83\xd2\x9a\x13$\x97\xd8\x0d\xda^\x02\xc3\x9b2\xf4\\\\\xef\x06\x0cO\x1b\x90\xf9D=\x898\xbeQ\xd1m\x14Zo\xa2q\xe2m_\xfb]\xcb\xf2!l+\xf3\xe2\xc4X\x82Y\xd0#2|&\xbd]~L\x9f\xfb;7\xda\xec \xec\xd8 \xe7\xa5\xca\xf2\xdc\x1a\x99\xcc\x0c\xddV[w\x12\xf1%\xcf\xae\xd6.`\x13`5\xceI\x86-r3\x9d=\x83\xbe\xed\x81\xbc\xd2\xa6%\xc9/xo\xd5Y\x80evu\xaa\xbb\x1eVxF\x8b\x9b\xdb\xa1\xecj\x1e\x1d\x0b\xfe\x16C\xc0}=\x7ft\x87\x07\xa2\xf1\x8e\x9e\x8f\x0f\x08\xfd\xfdbL\x9f\x18\xec\x0f\xf7\x0f\x02\xef=\x00\xfc\x1d\x1a\x89v\xfa\x96\xec\x03\xb1\xa7\x90=\xde\x19T\xa4\x18>\xffa\x94\x12\xdf\x03\x83\xe7@\x14\xa7\xb6\x19\xdbk\xd1C_i\xd8J\x1d\xdf2\x88]\x94\xaao\x03]\xba/)\xb1]\xa4}\x01\xd3\xe0\xa6Fd8\x1c,#w\xb7\x82AV\xb6@- \xd9{M\x96\xce^\x13\xe3Q\xea7)f\xb5\xe7^e\xf1G\xa8\xbb\xaf\x0cv9%\x90\xa2'F\x1c\xb8s;\xe2;Nf\xcb\xe5T\xfe\x7f\x18{o\x02\xc7\xd1Ns\xd9I$\xfd\xc2\xf6\xf0Xf\xc8J_U\x0c[\x87\xde\xd0\x88\xefo\xb1\xb9qNB1\xab\xfeMv\xae\xca\xba%E\xbb\x03\xd3\xc6 U\x1f\xa2\x10_\x90\xea\xd3Bh\x07`\xdd\x1e\xa23\x87\xc4\x03\xbb\x94\xde\xc6\xdb\x96\xd5\xc4.\xa8v\xfa\xf2\xeb\xaa\xfb1\xe6n\xe0~\x83tV\x1d\xefa\xa6\xbf\xa2\xee\xc3J.\\,\"\xb8\xb1\x98\xbc\xc9W \xb9\xc6\x17OQ\xf5\xf6\x99?]X\xd6\x19-Z\xfe9\x9a\x93\"m\x12RQm+\xef\xc9\xd5,\x8a\xd8\xad\xb5\xdd\x10H\xb2\x82\xd6\xcff\xc7\x9e\xea\x9c\xe0\x90_\xb2\xd4\x9f\xff\xecr\xe3+\x0b81fI\xee\xbd\xb3\xf8\x9f\x81\xeb\xc8\n\xb0n\xae\x99^\x8d\xc3U\xa2[\xcbMall\x19\x1e \x005s\x98\xc6\xcc\x15\xb5\x03\xfb,\x80\xb1[\x1d\x1e\x00\x9bG\xc3\xec\x0cT5\xcc\xe2\xd5\xd8\xeco\n\xc3CR\x0cD\xf6\xd4\x96A\"\x06\xd1HGb_\x93\"\x85\x93\x13\xd0\x81\xaa)\xab\xa5\x98\xb2\xea[\x8d\xe7\x8f&B\xb2 l\xd1i\xfe\xa9-\xb30\x9bw1\xe7\xda\x86\x84\xfe\xac-sb\xd0\x9a\xba\x00n\xba&\xec\xeav-\xc5\x93\xdc\x15\xea\xd9\xf5\xcflE\xc9k#\x17\x8b@\xb8\xf3\xa4\x7f\x027o\xea\x8a\xad\x00\xdd\x7f\x1a\xe8\xbeY \xabI\xee\xc9H#{\x12fIY\x04]\xf4\x83\x9d\xae\x9e\xcd\xf4C\\\xeerX\xec\xd4\xa6\xc9}\xd1\x84\x81\x94\x16c\xba\xba\xd0\x05\x8c\\\xb5\xa2\n\xf2\"\x1f2_\x87\x9d\xdd\x06j\xaeUd\x8b\xe9Zx\xe9y\xe4\xbeg\x18\xdb\x0b\xeb3\xdb=I\xde\xd6\x9075\x8fiG\xbe\xcar\xf8@\xe5\xee\xe4b!\x0d\\\xa0\x1b0\x05\xae\xf4\xfb\x03\x1f\x1f\x87L,\xd3!\x14\xf8)\"[c\x85T\xee\x03\x9eY2\xf7\xd7x\xf5\x8e!\xbd\xc5\xac\xb1\xa5\x1f\xebL(\x83K`\x86\xcd\xad\xb3(g\xf6\x16t\x14\xb1R`M\xa3\xf8\x8dm\x89\x18\x9b1\xd6\x1c\xca\xfa|u\x96\x0bz\x07\x93\xc03\x9a8#\xda\xf0'\x81\xef\xf3\xf6\xbb?\x1b\xa6\xef\xf7M\xd1KjX\xa3\x9d\x80\xdf\xed\x1d\x1a\x97\xa6\xbdI\xe4\xcewh\xbc\x04\xfb\xdf\xa11\x8a\x81\x95\xb2\xbb\xde\xa1\xf1\x11A\xdf\xa1\x19\x07f+w~\xa8!1-\x1c?+=\xef\xd0\x18E\x92G\xde\xa11(\xd4\xfc\xf9\x11\x93\xea\xbb\xbfC\xe3V)\xdf\xa1A+\xf6\xbcC\x83Pa23\xb7\x89\xe0\x14\x01\xf0\x8ewh\x0cZc^.\x11\xef\xd0\x0c\xbaP\xa7w:\xf3\xa8X\x1faP\xb8Z\xef\xcca\x8e\x1a\x16\xe0\xfc\x02\x1c\xb5#\xf7\x83\xbf\xef+\xe7~\xf7l;\x0d\xff\xc4Y\xf4\x1d\xb3f\xf8\x87/^\xa5\xa7\x96\x859m\xf7\x8eG6\x047`_\x83\xe5\xeb\xb4c\x83`\xb6\xf4\xdf\x9ca\x19\xef\xab\xf7V\xa9\xb7\xc6(\xb5pK\x19\x013}k\x0d<\xf2*Z\xff\xf78$\xe4.\x08\xbb\xdf\x05j\xf6o\x84\xf5vX@\x14\x99ab\xf4\xaf\xe0\x83\x05LQ\xb9\xd1\x84Y\xec\x7f\x9bDL\xdb\xd7\xc6\x8b\x18$\xe4\x9eM\x81g\x91{E\x8eY\xc16^\xf4\xbe\xc4m\x9e\x95b\x8d\x9f\xa0\xdbw5=\xb7UVn\xd7_\xcd\x84\xa6\"\xfd\xb7)z\x1f\x06W'\xbc\x06\xef\xd4ew\xc8\x8epI\xf5\x88\xd0\xcd\xef\x13\xe1\xbcW\x84s\xb4n\xaf\x08\x9d\xec^\x11\xbaU\xdb\xd9\xacn\xbd\xef w\x92\xee\x15\x15\xce\x01cFe\x82\xfbK\x96!\xda\xc1\xfffL\xf79k\xae\xa6\x05\xb6\x83p\xf8\x10\xa5\xdc\xaf\xb7T\xcd\x9bX\xc3\xbcL\xe1\xfdy\xa4+e'\x93y\xe1\xb0\xa0o\xadn\x11\xff\xc9\x1a\x05\xd6#\x15\xb8\xaa\xe9KV^\x1aP@%\x81B|\xb3\x95\x00XC\x95\x99d\xb6\xc4\x19\xa0\xdc\x0cV\xcb\x03\xc3\xd6-\xe4\xfb3LU)%\x853z\x9e\x84\xab\xee\x7f\xe6\xf4\x0cz\xd4z\xf9\xd1\xb8\x00e\xed\xbb\x00E\xdd\x9doX\xd7\xf0\xbd,{\xd2P\xc6\x8a\xa9\xf2p\xb6\xa4\xe7\x1b\xe1\\\x0b)\xc9_\xce\xf7[\xdfQpqO\x9ah\xff\x96\x9e\xab\xf6\x9b\xb9E3d7\x8b\x8a\xed+N\xbc'\x8f\x04 \x02r\xc6\x15\xf9\xf0g!\xac\x01\xfa\xe9T\xd3\x83\xfc\xd2@\xb3|SF|\x05X\x92C^k\xb7\x1c\xaf\x81\xc3\xaa5\xb3|\xd5\xf2W\xe7%9\xe4\xb1h\xc9\x1e{\xb1\xd7\xc2a\xd5\x9aY\xbej\xf9\xa3\xd7\x92\x9c\xfd\x8c\xad\xac\x93=B\nAX\x85 \xdd;\x1d\xc7\xde\xdf\x95\x84\x90\x877\xa5e\xb1\xd7\x0f-\x1cV\xa7\x99\xe5\xab\x96? \xaa,\xc4y\x00P\x06\x14\xeca6\x13\x86\xda\x11\xcc\xf1\xd5\xc9\xdf0\xbc\x89\xa7\xdf\xf0\xada\xfai\x18\xb8\xb5~^\xbdM\xd6\xee\xb5B\x7f\xa7\xa1\x00\xeb[\xb6O`\xfbfX\xc3\xfc\x9d\x9a\xb7\xbb\xafS\xf3\xdd6\x12i\xec\xa8\x92O\x7f\xdd\x88\xa8F\x8eB\xfc\xd7\x83\xa3P\xe7n\x8fl\x03b\xd6\xd2\xb3\x0c\xcb9M6\xb5 \x0e\x1c\xab`\xfe\xf9\x9eG\xf2-\xf2\xb2\xac\xe1\xcbp\xcc\x17\x015v\xd0,\xed\x83\xd3\x9a\x1d\xf8\xdd\xc8\xa2\x8f\xbf^\xce\xfb\xb2\xad\xc1\x85Zs{\x8b\xb0\x98\xad`w\xc3q\xae\xb3\xe2D\xeb\x0c\xfb\xce`>Z\xd1\x9c\x9c\xe2)\xf8\x15\x9e\xe2\xabA\x00B\xed\x8dy\xd6\x01\x91Yl\xd9\xf1,\x8a@\xf1\xe7S\x0d\x830\xd9+\x97\xdd\xbf\x1b<\xc8\xa1J8\x07\x8f@\x9eu\xa0fe\x08j\xa2\x0b^\xad\xab\x9d\xc5\x04x\x93\xd4\x94\x16\xfcp\x99\xbb\xc1\xca\x95\xf8\xe2\xa9\x8b\xf2\x1e`\xd3\xda\x00o\x14y8x\x84\x8f@\xe9\xd7\xc6\xb6\x1c4 g\xcd\x84\x92\x86\x06Y\x11\x94\x97\xd6z\xd4\xce\x03\x1aD\x80\xd6?g\xe7\xe3T\xff\x9c\xc8\x8b[@/\x83',ru\x8c\x9e\xe8Bj\xe4\xd1)<<\x04)r>\x0d\xff0\xd7\xf5\x87 \xa9:\xce\xaf\xe0P\x10x\xe4\xe9\x16\x92\x9c\xd6\xed\x15\x1e\xb1\xc2\xd4\xe4H~\xe0l\x0f\xa3:9-\xcc\x0d\xa3V\x1f\xe6 \xfe\x1f>Cl\xf9\x1b\x01y\xae\xa6\xe2\x8f\x8b\xb3\x83ZA\xbe\x187\x8c/\x15\x13A\x9a5\xe7\xaca\xf1\xf4\xd4L\xca\xf6\xce\x05\xfbs\xbc\xe0$L\xf2\xb2\xc1\xca\x8b\x1c\x9f\xd3\xe9\x9c\xa8\xd8\xcc\xc8\x06\"L\x02*\xf0\x92jI\xd6\xab9\x16\xd7\xa7\x87C\x94\xda{\x17\xd3\x15\xdd$+\x8b\xd4\x04\x1d\xd5\x92\x0d\x9d\xed\xe76\x14\xca_\xc6\x85\xfb\xe5\xa2\x8b#x\x0e\x0b\xd0T0\xb5\x8e\x9e\xf0W\x9fiz\xb0\xe7\x89\xf6 }:\xc4\x90\x0e\xce\x18Y\xd1\x98\x1a\xf5\xa1\\-\x96\xb3\xd5F\xa2\xacW\xdc\x9f\xc8*\x9d\xef\xb1q#9<\xd1\xb9\xc5\xd8\x81\xd0}\x92X\xa4p\xde\x0ek\x1a\xef\x976\x14ao\xb5Z\xc6Zh\xe6\x93\xd3d\xb3X,f\x18w\xb3\x94:\xcf\xf3w\xbc\xa5\xb1I g\x8e.\xf6\x9b$\xb2\x90\x08oO\x8b\xf9r\xbe\xb8\xfd^\x0e\x8c\xbf\xd0o\x87\x9a\x9ci3\xa9\xea\xf2X\xd3\xa6 \xf6\xec\xf0j\x9dU\xb4\xb9\x1e\xea\xf2\x0c\xc3Ke\xdc\x0b6\xadpkK47\x9aD\xb7\xdb\xef\x83\xf2oJ\xfeoH;\x94\x14\xaf\xe0\x1c\x186\x1a\x96\x83\xd7w\xf9\x96^\x86\x8e,9\xaf\x0fzO$\xb9H\xcd?\xdbY\xe0\\\xae\xad\xde\xccd\x97N\xf5\\1<\xd3\x01\x9b'\xb8\xf7\xce\x13\xf74/\x00G\xa5\x8c'\x1c\xc7\"\x11\xaf\xce\x1a6 W\xdc\x1b[\xde\xdc\xca\xf4\xe6\x00\xb9q+J'\x86$\xa7!bi\xa9{\x8eO\xf2\x97\xb3\xe7n\x03\xf5\xe6\xddb\x99\xd2\xe3\x149\"\xb6\xfc<\x99-?N\x81+u~/\xa3\x8f\x9e\x92\xfe\x9c\xb5E\xc3\xfa\xfdy\xe7r^\xfe\x1f\xc8\xf4?=\xc7\xc8\x9b\xa8\xac\xbf\xb1\x91h\x11\x99S\xa4f\x8e6I\x11\xe7\xf5Y\xa4\x0c\x05e}\xa4\xc8\xce\xfc\x93\x14\x1b!'3\xf9 \xf3$+\x0eY\x91\xb5\xac\xdf\xdc_\xe8\xee\x127\xab\x1f\x0dN7\xf5wK\x8c\xc0\xbf:\xe2?\x80\xe9\x7fv\x8e-\xbb\x1b\x98o\x1c0:\xbb\xf4\xbf,\xee\x1f\xc0\xf4?;\xc7\x96\xc5\x0d\xcf9\x0f\x18\x1dB\xe0_v\xf7\x0f`\xfa\x9f\x9dc\xcb\xee\x06\x17\x1d\x06\xcc\xce-\xff/\xab\xfb\x070\xfd\xcf\xce\xf1-d\xb3\xd7\xf6\x91d\x91l\xbc&\x00g\x03E>\x9fx\x9b\xf2\x1f\xcf\xf0i}cF\x9a\xaf\xff\xf0\x12]\x82Y\x80\x1d:6g\xf2<\x05\x8c\xea\x82}\x99~\xc3n%\xb5V\xaa\xda\xb2\x92\xa4\xf8u W\xcf%\n\x92j\xdb\x96g\x1b\xc3S%\xe6DI\xc7\xac9?jN5,A\x03\xb2\xa6\xb5wz8;9\xf4BO\xff-\xf3\xfcv\x1bkY\xe8\xfe{\xe3-\xaa\xfc\x1eM\xd7\xc3\xf9\xb71Z\x1c\x8cy:o\xe0\xd0\xbfK\xd3\x7f\x11\xc3\xc0^\xc3\x81\xfd\x8f\xc4\x91\x9f\x0c#\x97K7sb'(\xf5\x83\x99x\xa7\x94\xde\x06a\x0b\xaa\xb4\xce \x8e\xdc\x07\xc3'\xa5\xdcEI\xb9\xdbd\xea\xcdQk\xd1\xbe\xfc\xc7\xf6\xd7\xb1E?\x1fM\xaf\xd0\x86\xd8|\xa4 \xe3\x7fHMj\xda~<\xcb\x9df\xee\xe7w\xa0\x94\x87Y\xe7*\x00|\xf1\xd9%\xcc\xd3}:\x16\xb9=\x9bH\xbd\xb3\x81\xf8\xe2\x14^\xc1\x1dZ\x83\xfc\xde_\xac_\xd5\xbeb\xde\x02\xcf\xcd\x99\xe4\xf9\x83L\x0e\x14\xeeg\xb5\xbf\xb0\xbfX\xf8]\x1c\x0f\x94\x1e`\x99\x97\x1e\xeaO\xbe\x16\xe0\xfd\xa2\x9f\xef\xde2#zR\xb2NS\xean\xb5\xb8s\xd1\xce\x1d\xddq\x02~\xdc\xd8\xc1\xc9K\xc0\xe7NT\xbe1\x86\xfb\xdb\x13\xb1\xf5F/\x199\xca\x0c\x01\x86\xd81\xc7\x9d\xfe\xd1F\xf0j\x8e6\\\x9e\x0e\xf9;V3\x91F\xba\xa5=\xa0\xc7\xd5\xd5\x95\xf6\n\x87e\x9a\x8a\xf2\xb5!Y\xd0\xf9\x01 +\x18\x0d\xbf\x96@n/\x17\xf7\xe8G\xb0h\xea\x87K\xcf\xa1}\xdf\xba.\xd2<\x94\x80\x1f\xf7\xb8\xa2\x04\x01\xaf\x94d\xbe!!\x7f{\xc8a\x96$\xfej\xfc\x1a3\x01C\xec\xdc\xa17\xc9\xab\xa17!O\x87\xfc]\x0b\xdeH3\xb1\xf2^\xd8\xe3J\xe3\xe5\xbdB\x12\xd9\x86p\xbcm\xa1\xfb$AU\xc6\xa9\xf85f\xe4\x0f\xf0r\x87\xbe$\xa3\x86\xbe\x84$\xbd\x02\x1b\xfa\x00\xb5\xcb\xc1\xdb\xc7\xd4\xb7\x94\xb9\x8bk~\x0b+R8\xb7\x1d\xce\xa2\xf1_\x88`~a\xdc\":_\x14\x8f\xadE\xf1\xc8\\`\xf6\x82\x04\xc3|>\x00\xee\x85\x92\x19RZ\xeeW\xb0\xf1\x9d\xd8\xdf\x82\x9e\xf3%\xd8\x01\x10\xabn\xf4Q\x16q\xc1\x81i\xf5\xa2\\\x9b\xb59\xed\xd3o\x047\x01\xac\xdc\xddI\x80\x8c:\xabne\x1e\xca\xb2\x05\x97\xef\x02\xb1x\xbe>A\x83\xfb_\xd2\x1a8\x0e\x83\x9c\xc4)h\xfe\x0c\xccu*\x938\xa7\xf2\xea\x04\x08q7\x939T\x9c\x91f\x04Y\xa7\x8c\x9c\xce\x00w\x9f[V\x8dU\x0d'D\x1c\x92\xe6\xe3\xa7\x83,\x8d\xa6\x05f^\xfc\xe7C\xc7\x19\xaeQ\xbf\x9e\x88q\xab\x87o\x83\x0e\xb7d\x1c%\xfbY\xa0\xf7\xb0.\xd9\x13\xbf\xf4\xe9\xda7\x8d%\xf6\xe0\xc0\x01\xf5\x8b\xd9\x890\xb0\x10G\xcbw/\xc2_AM\x9b\xaa,\x1a\xb6\x9d\xdc\xcc\xb7\x85\xc7r\xbd\xb6\xcer'b\x9b\xe8P\x1d\x0e\x0e\xadkbm:\x85WJ\x9a\xb3\xbaK`-\xac(j\xd763\xe6c\x9eN\xb9\x11\xe7\xf9z\xcd\xd6!\xf8\xdcv\x9a7S\xea\xf7\xe0\xf4N\xc2&\xb0sV\xa3\x8b\xde\xc3\xd3\x00\xe1\xbf\xbfx'm\xfa>-\xfb\x9ez\xee\x92\xd1\xbbq\xfc=\xf5\xdc\xd9\xf6\xd3\xfbp\xfc=\xf5\xdc\xd9\xf6w\xe2\xf8\xbezz\xcc\xff;M\x1c\xf1\x83w\xb7\xe5;\xaa\xb9S(\xef\xc3\xefwTsg\xc3O\xef\xc2\xefwTsg\xc3\xdf\x87\xdf\xbb\xaa\xe9;\x8en\xd9\xf6\x18\xde\xe0\xbb\xd8v\xa9\xeb;\xc4c.U\xd1^\x98P\x7f7\xabw\x115`]`7\xb2\xdcxfz\x89\xfeC\xc4:\xda\xd9}\x87\x94\xfb\x1d\xddh\xf9\xbc\x13\xab\x8f\xd7qO\x8bG\xba\xb7\xef\x92j\x9f3\xbe\xa3\xc5\xef\xc2\xea=u\\\xfff\x96\xac\x7f=\xda\x8e\x87\xab\xb8G\x1a\xef\xc1\xe8\xc3U\xdc\xd3\xdcq~\xec\xbb$\xda\xe3s\xefh\xee{0zG\x15\x03C\xb52a\xf6\x1f63\xf9\x85W8\xf5\xe6\x00>\xcd\x06|\x01Xo\xd3 \xe8\xea\x9d\xa63\xbb\xd6\x98\x00czo s\x12\xca\xac0\xe0Y\xd4\xaf!\x1b(\x88y \xf1\x98\xee\xb9\xad\x9fG\x8eb\x0f\x12\xd0\xf0N\x00\x83\xf0\xbe\xfaF\x11\xd0\xf0\xce(\x07\xe1}\xf5\x8d\"\x80\x88c\x9c\xaf}\x90\x00\"\x8eG\xeb\x1bE\x00\x11\xc7\xa3\xf5\xe1\x04\xa4\xd5\xe7\xe2e\xa3!\xe9\x8e\x1a\xaf\x1e+\x8f\x9a\xdaC\xb5\x8d)\x8f\x1a\xdaC\xb5\x8d)\x8f\x9a\xd9C\xb5\x8d)\x8f\x1a\xd9C\xb5\x8d)\x8f\x9a\xd8C\xb5\xa1\xe5\xd5\xf5\x16\x19\x7fog@\xb0\xe6 \x7fO\xed\xa3\x088\xa2y\xbc\xbeQ\x04\x06\xd8;\xddQ\xdf(\x02\x03\xec\xddS\x1fN\xc0^#\xf1\xeaS\x16\x87\x81\xcd#\xe2\xed+\xef\x98\xde\xc3\xb5\x8d)\xdf\xcf\xdb#\xa2\xed+\xdf\xcf\xdb=\xb5\xa1\xe5\x07\xf4\xa8\xe9y\xf6@\xab\x12\xe8\xaa(\xdf.\x0e\xf2'\xfc\x87oC5\xdc\x8e\xed\x96\xfab\x16Vwu\xb8H\xb5\xe6\x8e\xb7\x0e\xc5\xca\x10V\xafG\xf1\xdf \xee\x1dY\x0e,\x12\x8b\xfa\xbd\xa1\xb0I\xd1Y\xcc\xf3p\xccqv\xc5\x13\xc0\xb0\xd5p\xbcnu?\x1d\xc7\xca\xfdk\x0e\xe0\xd9\x12\xaa\xdc\x85\x85=\xd3c-\xd5\x8f\xa5\xd9'|\xe4\xee\x8aAz\x13\xf3&,\xc9\x14\xb2\xd9x\xee!\x84\xcb\x18\xe3K|\x84\xb9\xac\xa9\xbb\xf8\x0cY\xe8\x0bp\x01\xc6#\xe2\x07vH\xf7\x91\x1d\xd3\x1a \xe51$'\xf7\\9\x86\x12zT\xd0&w\xea@\xb7iw\xe2\x82\x1b\x03\xe33\xe7\x87\xae\xce\xe9\xa3<\xa6M\xd0\xa8G\x90\xb4\xc4-Y\xf3lk\xf5\xd0zT\xe2&\x83\xfc4\xb3!\x14yg\x8f\x06\xf8d\xfd\xd0U@^\xb2c\x9a\x02\x04=H\xcf\x96\xb2`\xca\xb39\x15#\xf4\xa8\x88M\xd6\xd4\xd9]C\x1c\xf2\x02\"\x03\xe3\x11\xf4cW\x1b\xf5Q\x1e\xd3&x\x05\xd2\x08\x92\xf6`-X\xf3\xec)\xf5\xd0zT\xe2&\x83\xf2\xd8\xaa!\x16y\xa7\x12\x84x\xe4\xfd\xd8eM=\x84\xc74\x08^\xea4L\xd1\x96\xb6`\xcc\xb3#\x14'\xf5\xa8\xb0%{\xf4\xbc\xa7)\x0c.\x87N\x10\x8a\xfd\xa1\xfa\xc2\xcd\xc8\xbeH\xc9%:qR\xc4&<\x07\x98\xb1[\xa1\x90\x0c\x96\x80\xa4\x97\xfb\xbf\xd2\xa4E2^\xb2\x94\x96\xba5d\xdf\x94\xf9\xa5\xe5\x17\xbauQ\xae\xdc\xf3\xca\xcfV\xea[\x1c\x8d\xeb\x95tdm\xd3w[\x14\xaf\xf6\xdf6W\xeba\xe7\xe5*\x9c-?\x8e)\xbe\xd8\x7f\x9b\xdb\xa5\xd7]\xd1W\x9a\xe7\xd7sV\x18\xf7:\xa9\xad\x9a\x1b\xcfM\x7f\xfd\xd1 \x0cC\xe9\xbc\xfb7n\x7f.\xbc\xbaj`\x97\xee\x00\x94\xb7k\xc2l\xea?/e\x8b\x96\xe5\x83\\o\xe6\x9d\x81\xf3\x9e5\xb8\xb9\x93\xe1\x9a\xb3qu\xa2 c\xf3\xf4\xfc\x02@p\xe7j\xdf\xed\xa3\xde[t\xa3H\\\xd7n\xecU\x8e&,~l\xcb2o3\xac\xbfj_\xba\x8e\xac\xc0\x9e\xc57\x07r\xce\xf2o\xdb\x0f\xffN\xf3\x17\xdaf \x99\xfc\x07\xbd\xd0\x0fS\xf5{\xfaouF\xf2iC\x8a&hh\x9d\x1d\xfa\x1e\x1bX\xd8aR\xb8\xd8\xbddM\xb6\xcf\xf2\xae\x9b\xb2?s\x8a\x87*\xe6 Z\xe4\xed\xfd\x1b\xd8\xfb7\x1a\xdf\x96\x95\xf1\xf4\x902nfB,\xf0\x93P\xe3\xba\x1a\xf1.\x1e\xb4P\x03\xcc\xfb\x8a\x9f\xb4\x01\x86\xf7\xda \x84\x0d6\x82\xac0o\x96\x9fE\xf6\x0b\x0fOC\xf7\xba\x8e\xbcH\xa4\x8b$\x9d\xa8_3B\xea\xba|EL\xc8\xba\x8662\xc3t\xe4\xc0\x1c\xbf\xdd\x86\x0dE\x86b&VUf\xdc\xb7\x8c>\x9aR\x02\xe3\xb4\xe8\xf3\xfc\xa5\x87\x89q\xc0 :\x1cP\x17\xa3aW\xa8\x9eK\xd8\xd9C\x9f\xf8\xfd\x9du2\xfa\x03\xadto\xe0y\xbcN\xb4>\xb66\x13}\xdc\x99\x97\x081\xf3\xf7\xd6d\xd4\xc6\xb74a\xf5a2\x95\xd5\xc9\x90`\xb0\xbeH\xd6(\xb3\xd8&@\xac:. \xac\xc2\xd1&\xe3TfN\x8a \xd5\xa1\x86\x03\xa3\x1e\xa0A\xbc\x91\x0f\xd4\xe9U\xa4k5\x8f\xd7Y\x95U\xe7$\xbdA\x9d\xf3\xf5\xb5\xb2\xbe\xbe\xc0\x18\xb5^\xc1\x88Jz\x81\xefs&\x8bag\"\xdf\x99\x00C`\xcen\xca6^\xad\xa9\xcf$\xff\xbb\x85\xf0I\x92<\x10\xc2\xfbC\x89\xc8\x8a\x12fX(\xe1\x82\x94z\x99\x07\x84\xb6\xc2\xbfxd.wz\xb0\xd3\x98\xf9\xc2\xcf\x01\x02f>smf\xf0\x07\xf3E0,-\xe3I\xbe*\xa7\xa2cK\xdf\x88\x96\xd6\xdd\xbf\x9e\x00}\xdf\xfd\xb3D\xaa\xc6\xb0\x89\xb6s\xfdy\xa7\xe7\x81\x183\n\xf1\x1c\xb2\x8e6\xb5~o\xc9\xa1E\xbb\x89\x19>}\x9f[4\xab\xbc\x1a\x9d9\x8e]&\x05S\xea\xb1\xde\x0ff\xff7\x95\xd4\x96\x95&\xcc\x9dL\xcc\x1e\xbaE\x07M\x96\xe5z\x1a\xfdm\nR\x0d\xa3[~\xb6F\x1b}\xfe\xd5aD\xf0\xaf\xf5\xb9s\x8cH\xbfD<\xf9\x80\xf0\x03^\xf5\xf4U\xc7\x8c[\xb6\xdc\xf0\x83\xbc\x91F\xb7\x00\xad6<\x1eh7L\xf7\xb4\x9c9\xaf^F\xcc\x96\xf3\x96\xf2\x8e\x877\xd9`\x064\x1a\xaf\x89\x93\x85m\xbeG\xd5\xd2\x86Q\xa7\x01\x04adX\x92\xf0\xf0\"\x9a->L\xc7k\xbb\x97%\xbe\x94\x0d\x86\"[\xd9\xdcE\x8f\xd1\xb6U\x0f\x0cB@\xc3A\xb2\xb7\xd9\x80\x0f\xd1hqnZ\xc7\x99\xde\x16\x0f\xf2\xc2Z\x9c\x90\xba\xbc4\xd8\xc3\x87:O|G\xf8\xe6\xc0\xd8\xea\x8639k\x16~\x0e\xb3\xfe+%Y\x14\x80L\x97\xca'\x19\xc4\xbbJ\x13\xe6\x93\xcdIR\x142\x90\x8f\xf3\xc7\x9fjBs\xf8\xabMF\xcc \xbf\xa5I\x9e\xf3\x07\xba\xd4\x04d0O?O?93\x9d]\xf2\x15\x17\xcc\xb8\x89\xe2U\xdfSU\xe6\\\xf1\xca\xfb`U\x0f=\x10\xc1\x1cHB\x03\xf0}\x0d\xde5\xf1d\xc9\xd2\x15\xad\x9b\x8a\xf2+j\xe2\xee\xbb\xd0N\xc0e\xcf\x1e^\xc5\x85/.\xbc\x11\x01\x86\x88%{\xe6\x91\xe7\xe9\xa7\xce&\xa7\xbeYa\x98\xefa\xa6\xaa\xe9K?3,Z\x19\xc5K0\xc4L0\xc4M'\x1aV\xa1\x87\xa5\x8e[.\x9d^\x9e\xc7\xb1\x1b\xf5\xb1\xca3o.\x9f\xf2&I;\x1d\xd7j\xc7\xb0\xf9*\x9c\x8f\xa2\xe0\xd9\xcd\xee!\xec\xfb\n\xd1\xc3\x15N\xee*\x82V,\x9f\x11\x96.\xc6G\xc0\xa3#\xad\x1eok\x1c\xa3\xf2\xd4\xe2\xf6\x04\x0b\xd79\x82\xba\xcc\xef]\xac^\xc2\xe7\x7f\x86_\xfbq\x97%\xed\xc7\x87V\x9fG\xad\x1a:\x8cs\x11\x80\xd8\xbd\xff\x82q&ps\xe6s\x12\x19 Q\x14\xc5\x9f'\x9d\x94\xc6\xdd\xfe\xfd\xbd\x14\x05\xa3\x9a\x1e\xa3?\xed\xc8N\xda\xb2\x9a\xf2\xef\xf1\xee\xafC]\x9e?\x995}\x9e\xb6\xe5'\xa7\xae\xcf#\xae\x00o\xcb \x1f\x00F\xb3.\xd4S\xd5\xe51K\xb7\xff\xe5\x7f\xfe\xb1\xa3\xfbg\xd9\xeb\xc3?eI]6\xe5\xa1\x0dU\x1dMK\xea\xf6\x0f\x9d]4m\xfd\xbb\x1f\x7fx\x8a\xf8\xff\xfd8\x9d\xd0\"\x05\x19\x91\xce\xf8o\xa2\xf0\x9f\xbfU\xf4w\xb1\xd1\x90\x9aV\x94\xb4[\xfe\x9f\xe0\x0d\xb1\x05n\xe6\xc6\x8a\x10[p{\xdc<\xb8\x00\"[J\xdfa\x1ewR\xfc\x0e\xf3\xe0\xb6`[\xc8\xf2q\xf3\xe8e\xfd\xfb\xcd#\xf2\x99\xc7\xd3\xfb\x98\x87\xda\xd8`\xa7\x8f{\x98\xd7?\xf7\xaf\xd6@\xe1*\x80]\xcb$\xcc\x92\xb2\x08\xac@\xc1\xcc\xb4|\x94\xca<\xe6\xdf\xaa\x13C$'\xfaR\x97\xfc\xe3e\x1cR,-\xa0\xc3{\xf7\x99\"\xe7\xd8\x96;\xec\xb5\xe7\xbb\xdb\x81\xb3\xca]\x8f\xfb\x0d\xc8\xa6\n\xbe[\x18\xb0\xdb\x83:\xe4\x07X_%\x83\xea\xb8\xca\x95\x10\xbd\xc6\x07\xdf\xf4S\xd3Z\xc6\xac#\x9bO\xec\xads\xbb\xa7\x87\xb2\xa6j\x12\xe5\xc7\xbf\xcc\xa2\xf9\xe6\xc7^a\xa0e\xc8\x8f\x86\xd3O\xb3\x84\xb4e\xdd \n\x97\xf3\x1d\x11\xfc$W3\xac\xcb\x9d\\~\xfc\xb8\xc3\xef\xfe\xe7*\x9bG\x1f\x11\xff\xce\xce:\xc0\x97\x04\x10\x96&y\x86?(\xae\xa7\x8d\xf4\xfe7=O\x17\xcb\xa5\xe6\x8e\xd1\xa2ek\xaf]\xb8\xe1\xdb\x0f\x04\xe6\x99'\x7f\xd9\xb8\x19`\xf0\x8a>#\x93\xa8`\x9aC\xcc\xe8Y\x06\x04Z$cN\xd1\x82\x19h\xc1\x0c\xce4\xa2\x13\xc0P\xd7\xf2\xb23Gi\xe2\xf3}\xf9q\x07w\xd8\xf1@n \xd4\xa7\xb6D2\x8b\x84\xdb\xf4\x8c\xady\xdf\x1d\xa4\xb9L\xb3\xf5\xf5+,\xcb,`\xd4{\xd4c:\xf7\x9d#\x9d\xb73\x8f\xed\xe9s`\x87s\xa7\xa7\x9b/\x83\xcf=C\xcb\xf8V(\xd6\xac\x89\xec\xe5X\xc2\xfd\x8d.\xc0U\xabr8\xb4H\x0b\xd3\xab\xc5\xd0&&)g`\x18\x10\x064\xf7u\x83+\xb0\xb0\xeeK/\xa7\xa4>dob\xb8\x9a\xea\x046#5\x0d\xd3<8\x95u\xf6kY\xb4$\x9f\xa4\xa9\x02:\x19\xa2@\"\x9f!\xd7$U\x8a\x0d\xe1/\x95\xbb@\x91.\xe0u\xf9\xaa j\xael\x1a\xb2I\x17\xc0\x02O`\xfbM\x14\xbc\x0f#\xc8\xec\xdb\xce\\\xca|O4\xc70\x0d\xc0X\xb9@\xbe\xf3\xf2\xac\xd3\x8c\x82~\x94 U\x90\x17U\xa0\xfb['C\x1e\xc4O#S\xec+\xb202\xd5\x84\xca\x1d\xde6X\xa5\x0bxE\x8e\x80\"\xff\xa5\xb2\xe4\xd6p\x90\xaf\x92\x04\x08n\xdfQ0#\x91\xcfm\x1a\xcf\xee\xc0)\xcd\xdb\xb0\xcd9\xa6\xe51$`0#\x8c\x041\x80Q\xca\x06j4U\x87\xea\xca\xa3\x15C\xfa\xae\xbc1\xe1r92am\xf7e{\xba\x85\xdc\x17\x88MR\xe6*\x931\x8e\xe0O\xd0\xc3\xc7\x97\xc0v\xe6\xdfd\xe7\xaa\xac[R\xb47\xf0\xda\x92~^\x18\xe6\x9f\xb2\x94^\xe1,/\xcclN\xe5\xab\xc9\x15\xcc\xcd\n\xb1\xab\xe7\xea\xcc6\xdeB\xe6\xa1\x18\xf1n\xfc\xdeF_\xa3 \xd9\xb9\x0bd\xb6#s}\xb7\xbb\x9c\xb6\x8d\x18\xdb)-<\x8c\xef\x1c~ \xdb\xe4p\xc8\xde\xacm\xc9\xb7\xdf\x07\xe7&x\xc9\xe8k\x07\x13\xae)\xa5/YB\xb9\x0f\xbd\x85\xa2\xad\xc1[3U\x7f7g\xfd\xf79\xd5\x7f\xe7G\xafH5\x19\xae\xf4)L\xe1q\x1a\x92dc\x9b3\x92b\x97VI6\xf6\x9c\")vi\x95dc\xf3#\x92b\x97VI\x96i[\xe2P\xdb\xdd\xd4\xfe\x82\xf5j\xcdb\x15-\x02\xaf\x01\xb2q\x08\x03\xb2\x8c[[{\xf3\x82\xba|\x85\x84NP\xbbm\xea/\x97\xd0<\x07\x05G\xf1\x8fu\xee\xbbipaZ\x01\xfd\x83T,\x86`\"J\xd1\x8a#Et\xa9\xea\xd9lb\xa3\x9e\xe6\x98\xa8\x82c\xc3'6b\xbc\xb3\x11\xc2\xf9]xs\xf3\x08\xc2\x81E\xbc\xed9\n\xcf\x8b{\x98\xb7=C4\x1aE4\x12\x81\xebq7F\x9a7\xc1\xd1w\x12\xef\xee\xb7\xc1=t\x17\xe1\x1f~L\xdf9\x8e\xefn;\x84\xf6=\x18m\xdd\x82\x97\xe7vb\xb4u+\xacm\x0dX\xb0\x0f\xe3\xd5\xc5}\x98\xbc\xb4\x1fK\xf3\x1e\xd8\xba\x9b`=\xbb\x033\xfd\xa7\x11 \x87\xe0s\n\x08\xf9\xbd\x08.\xccb\xa4e3\xec\xbd-\x18\xef\xd8\x0b\x0d\x0d\x8d?\x8d\x9aZyU\x9dG\xcbdeQ\x15\xa5\x16\x9e\xe7\xf9x\xfd_%\x8d\x91\xd8Pn\xa2\x15f\xb3\xd9,m.\x06PR\x9a\x9c\xcf0\x16\x00\xc8)\x14\xd7\x06\x83\xc1\x90G\xf3\xb9\x84\x8b\xff\x0c\xcd\x8d\xd7\x84\x10\x92I\xb3\xb2sV\xfe\x1b\x19z\x16r\x18\x1d~\x1b\xf5\xf1\x8cm\x90\xe7\xac^\xb6A\x96\xb3\xd63\xdb\xe1y\xde\x8f/OoIY*f\xac\x98\xaf\x8b\x8e1)\x03\xa7\xae\x1c\x90\xf2\xd2;\xf1\x10\x91\xe5\x10\x9c\x03\xed\x98\x13\x061vj\x1b\x02\xb33R\xb6~\x1a\xbc\x0c\xff\xc7I)7C\xdf\xbeJ\x99*f\xe7r0\x00G_+&:\x1b\xf1\xe1A\x97\x94\xd1\xf6\xdef\xb8\x87z\xe1{/\xe0\xf3\xa3\xebx}\xed\x10\x82\xbe9\xd8n\x1c\x86s\xa0\x03SW\x0fb\xc9\xeb\x82\xebI\x0f\xa6o\x1f\x93\xf2\\\xb8\xb0;\xddQ544\xfe&6\xf0\xaa\xba~\xbdY\xc5\xea\xd7\x11\xa2\xe2\x99L\xa2\xe6\xff\xa7b\x92\xd7\x15&P\xe8\xda\\\xb2\x9a\xd5\xba\x86(Q\xcc\xf3\xd5\xea\x1e\xc6*\xb1k\xc9N\xa0\xf4(\xadT}~\xf1\x1dW\x96\xcaW\x98\x92K\x13\x97\xc2\xb3e\xe5\xc9u\xbe)\x85'DW\xba\xb6-\xf7\xa4\xd0\xb8\xa6\x9d\xbc\xc8\"\xf1\\\xc5%J\xaf\xbc\xaf\x82_x\xce\xa8\xea\xf3\xf2\x15\xf3\xc9\x8b\xcc!\x84\xe4\x1a\x90\x16\x96\x10\x9a\x95^\x83%\x99\x8c\x8a\x86.C\xaf\xa6\xa1:\xfc\xef\xf23\x00\x00\xff\xffPK\x07\x08>\xd4\x17\xe7u\x02\x00\x006\x0e\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\x94\x91O \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00 \x00images/folder.gifUT\x05\x00\x01\x98w#1r\xf7t\xb3\xb0L\x14a\x10gh`\x00\x81\xff\xff\xff+\xfedad``d\xd0\x01\xf1A2\x0cL\xea=\xfd+O\xbf\xe5_<'\xa4\xb1\xfb=sO\xf9\xf4V9\xd5i\xcf,2\xa7z-\x12\x9a\xbdK\xf0\xcf)\x01\xdb\xdb\xb7O2\xb32X\x03\x02\x00\x00\xff\xffPK\x07\x08\xa9\x03\xb9JR\x00\x00\x00P\x00\x00\x00PK\x03\x04\x14\x00\x08\x00\x08\x00!z\x92E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1e\x00 \x00javascript/jquery-2.1.3.min.jsUT\x05\x00\x01\xef\xef\x92T\xcc\xfd\x7f\x93\xa36\xb68\x8c\xff\xffy\x156;\x97\xa0\xb6\xec\xb6'\xc9~\xef\xe2QS\x93L\xb2\xc9f&\xc9ff7\xd9\x8b\x99- \x04\xa6\x1b\x83\x1b\xe3\xee\x9e\x18\xf2\xda\xbf\xa5# \x04\xc6\x93\xec\xfd\xd3\x9f\xbf.\x8eyD\xab\xb4\xc8\xf1\xe4\xdb<\\L\xea\xc9\xed\xbd\xf8\xb2(\xca\xe4:KC\x9e\x1f\xf8\xe4\xea\xfa\xffL\xe3c\x1e\x8a|\x0e\xc5\x0c\x9d\xac\x82\xdd\xf2\xb0\xb2\x08\xa9>\xecy\x11OvEt\xcc\xb8m_\xf8\xb0\xe0O\xfb\xa2\xac\x0e^\xff\x95\xd0ET\x84\xc7\x1d\xcf+\x8f9\x14O\x97\xc8\xed\x1aB\xa74v\xa6]\x16Tm\xcb\xe2q\x92\xf3\xc7\xc9WeY\x94\x8e\xa5FQ\xf2\xfbcZ\xf2\xc3\x84N\x1e\xd3<*\x1e'\x8fi\xb5\x9d\xd0\x89.i\xa1u\xc9\xabc\x99O\x98CQ\xe3\xc2_\xc7:\xe6\x11\x8f\xd3\x9cG\xd6TwW\x96\xf7\xe4\x8f[m\xd3\x03\xee\x8f\xfc\x81\x96\x93\x90\xf8\x01\x8eH\xb88\x88\x19\xc2\x9c\x84\x8b\xb0\xc8CZ\xe1\x98\x84\x8b\xfd\xf1\xb0\xc5 \x17i\x1e\xf1\xa7\x1fb\xbc%\xa7\x06\xa7d\xbb\xa8\x8a\xb7U\x99\xe6 \xbe%\xdb\xc5\x96\x1e~x\xcc\x7f,\x8b=/\xab\x0f\xf8Nd\xca\x8c \xc1;b\xc1\xe2Y8'\xfd>\xa8\xb1\x88\x89\xc8\x17q\xbeH\xf3\xb4\x82/\x0d.\xc8\xf5{\x7fs\xd8\x1c\xbf\xfe\xea\xeb\xaf7O/\x97\xc1\xac\x1e\xbc?\xbbN\xf0\x9e\\\xbf\x9f\xef\x0e\xf3k|O\xae\xe7\x8e\xbf\x89\xe8\xfc\xd7\x00]').\xc7\x1bc\x8b\xaa\xf8\xc7~\xcf\xcb/\xe9\x81;\xa8Y\x8b\x96I\xbe\xd8\x97EU\x88\xc9#' 9\xee\x0e\x87E~\xa8\xcacX\x15\xa5\x9b\xe3\x03\xcf8\xa8\x1e\x95 \xc0\xdfAT\x10\xa7\xe5\xa1\xbaT\x01\xbfw\x96\xa8\xc1\x19\xfdh\x96\xf9\n5\x98\xdf\x8fL\xb9\xb1b8$3:s\xc4r2w\xd9\xce\xf7\xa0\x9f\xe1\x0dY\xda6\xbb =\x1f\x168\x0c\x02\xd7\x0fD\xf5ytq\x94\xed\x82\xd5\xf5\xd9\xda\n0Rp\xe1\xc6\xf8P\x94\x95\x1b.\xc4\x0f>\xeca\xea\xc2\x85|hp\xbe\xe0O\x15\xcf#\x02;N=\x1bm\x8a!Q,\xe6>\xc2\x1c\xc78!\xedD\xfa\xcb\xa0\xaeO\x0d\xde\x92\x15N\xbbd=\xf4[2]\xadc\x81\xceXQd\x9c\xe6\x1d\xf2Ll\xdb\xb9%I\xaf\xb2\xad\xaal6C\xf8\x0c\xdb&u\x9d/\xd2\xc3\xd7\xba_ \xaak'!\xa7\x06\xe1-!$\xb5m'\x91\x80\xbb\x9d\xcf\xd1:\xbd\xd9\xaeEEi\xec\xc8\x1d\xe5\xd0^K\x08\x89~\xb1I\x9aO(\nI\xe23\x81\xf7\xa8\xf8I\xa6\x84D\xa2{\xb6-~D\xab?f4\xcd\xe5\\;\x91h\x98\x13\x91\x0c\x1b\xdd\x89\x10B\x9e\xc3=\x87\x93\xe9J\xe0I\xdb\xee>\x86\xc8\x0b\xc5J\xbam\xbaY\x17|=5X4O\xf4\xdc;\xb78\xc6\x11B\xeeC\x91F\x93\xa5\xea\x0dd\x89P\x0b@I\xb7p\xce\x89?\xedi\x1e\x15\xae\"\x1b\xd6\xcc\xd9\xcd\xde\xd0j\xbb(E\xf2\xceAhQ\xf2}FC\xee\\o^]'\xd8\xb2\x10N\x0f?q\x1a}p\xa7K\xcc\x05\xd1\xe9\xc1\xf1\x90 Q\xd4\xe0\xbc(\xf6&06\xb8[\x8f\x91Mn\xe9$\x8b\x10\x92/\xc4:B5jj\\\xf8\xab'\n\xa7\x87\x9f%e\xba\x8c\x13m\x9b\x12B\xe8B\x920Q\xd1\xf7\xc7\x1d/\xd3p\xa4\xcc\xb4[\x02\x8al\x9b\xce\xf7\xb4<\xf0\xaf\xb3\x82V\x0eE\xb3\xd5\x0dY\x8a\n\x8c\xc5\x18\x1b\x81\x02\xc2\xa9\xd1\xff\xba\xa6\x8b\xbc\x88\xf8\xbb\x0f{.AR\xf6\xdb\xa1\xc8\x9b\xae\\j\xeeB\xdb\x9e\xdeJ\xcc\xd5K\xee(\x0b\xb6\xd2\xc3\x8f\xfa\xe5\x87\xd8\x82*\xa6\xd0\xb3\xafv\xfb\xea\xc3H\xcf\x00\xc1\xac\x0d\xe0U\xe3])\xc0\x10\xa5Em\x97\xe6\x91\x10\xea\xd1\x99e\xb9g;\x8c\xd6\xb5\xb9d:\xd5\xdb\xfa\xa9\x1a\x04\n\xeaZ\x17s\xf5\xf7\x06'Y\xc1h\xf6\xd5\x03\xcd\xce{\x8aC\xc2\x1fh\xb6\xa6b\x06\xcbt\xe7P\x84\xa9m;+XI\xc5G8\xd6\xf1\xc0'\x87\xaaL\xc3\xcaB\x9e\xc3H\xb6\x08KN+\xfeU\xc6\xc5\x8eu\xacCX\xa6\xfb\xcaB\x98-\x80&Q\x9c-\xb6\x9c\x02~\xe7y\xf4\xe56\xcd\"\x87\xa1\xc5\x9e\x96<\xaf\xbe/\"\xbe(\xf9\xaex\xe0\xfa\x0brC\x87\n\xc4\x1f\xd2\x1d\xcf\x04\xa9\x1f\x9b!\xda\xee\x93=\xb6v\x87\xb9\xd5m\x9c{\\\xc2\x1e\x88\xf8\xf7t\xc7\xc7\xe9\x9f\x04\x0d\xf1\xdd\xb6\xbb\xe7EU\xbc.\x1e5\x7fA\x08a\xfd\x94\x11\x8a*h\x9f\x98\xbf\x08s\xb2\xc41\xa1\x1a\xa5&\xe4\xe0P\xb4Nc'\x04&2A'\x01\x0b\xeb\xf8\x86\xaf\xb9\xc4w\x11a\x8a\xeaQ\x9f\x078D8\"\x84LW\x88\x95\x9c\xde5<;\xf0\x89(\xc3%\xfc\xfc\xc1\x12\x97\xdb\x92\xb0!\nr,~\xfeX{\x1f/\xa5\xb1\x1cm\xb0\x80\x9a\x8f\x01\xb3e\xb9\x8e\x00\xe8n\xa5\n\x81\xde\x04\xebq\xc7\x07\xac\x98\xc1\xe7\xb2\xba\xf6\x83\xf5\x10\xc18\x07G\xa1f\x8a\x90\xa79\xa7\x10[\x07`o\xcdm!\x980\x8a\xdcX\x8e$\xc4\x14!\x1c68\xcd\xcf\xdb48\x19\xd9k\xe6\xcdWn\xa2\x99\x1a\x8aC\xd1]\xd1\xd4\xa0\xabb\xdadwgLC@D\x96\x98\xb7\x00\xb1\x0eo\xa2u4\x9b!\xea\xf3\xd9, \xcc\x8f\xdaQ\xe9<\x84c\xb1KK\xbe?\xeb\x95n@\xc0\x99\x1f\xe0\x98,\x05E\xd6Mm\xc94\\'7\xf1:\x9e\xcdPD\xa6\xcc\xa1~\x1c\xe0\x18\xe1hJ\xc8\xd6\xb69\xb00\x90\xda\x12&>d\xfaLh>k@@3N\x89\x1f\x08\x90\xde\x02Q6Z\xd4\x0d\n\x90\x94k\x14\xd9v*\x1b\x8d\xd0\xba\x05\xadX\x82\xd6\xef\x16\xd0]T\xd0\xee\x078\x15\x9c\xf81\x8d\xdc\x15\xde\x97\xc5\xd3(\xac\x08\xaeG\x15=\x83\x03f\xdbN(\xf9\x06F(\xa6D4l\xf2)T\xf0\x06Dq\xeb-\x07\x82\x9f#\x1c\x93s\xc6\x8e\xaa\x9e1\xc9\xd2a\xae\xc4/gX\x01\x02\x1ev!\xbaN\xa8\xf9#h\x92\xf8\x9d\xcdp\xacY\x08\x81\xb6\x1e\xddW\xb4\xe2\x8b\xbcx\xc4\x87\xe3^\xc8\xa8\xee]#\xfa\n\x1c\xbb\xf5\x85d\xd0&\xdf\x1fw\x8c\x97\x13)\xcdM\xf4(&\x00\xd3\x13Q\xc3\xe4'\x9e|\xf5\xb4\x9f\xc8m\"\xb9\x03\x0bx\xc9\xca\xb1&\x16\x1a\x08\x95[\xdf\xf2%\xc5\x98X36\xb3\x02+8C\x7fh\xad\xcbL\x0e\x1d\x0fM;\x06\xba%\xbe\xeb\x11\xce\"\x1c\xa1\xc2\x92\xbah2m\xdb\xcc\x9b.]\x8b\x8aA\xa8\"K\x81\x86\xeb\xda\xcaa\xbc\xbd\xf5d7\x82\x15\x9f\xaf\x00\xa6\x1a\xd1\x99\x8a\x8c\xd05\xcd\x0d\xe3-N\xf1-\xbe\xc3\x19\xde\xe1\x1c\x17x\x8f\xefq\x89\x0f\xb8\xc2Gb\x1d\xd2_\x7f\xcd\xb85[] ^JL ~0E\xe1G\xb2\xc4Od\x89?\x90-s\x10\xfeU\xfe\xbc\x94?_\x8c\x0b\xac\x82\x0b\x12p\x97\x91\xe9\x12\xe1e\x83\xbf$\xab\x17/>]\xe1W\xe4\xd4\x0c\xa5\xef\xaf\xc4\xbe\xfe\x9a|\xb5\xd8\x17{\xfcW\xf1+\x84\xf8o\xf4\xc3\xb7\xe4+%\xeb\xff\x8d\\\xc2=K\xc1\x19k|\x13\xdd\x84\xebP\"\x7f\xea\x87\x81\xe8\x8abA&\xa1Z\x9e\xf9\xaa\xc1\xdf\x11+\xdc\xf2\xf0\x8eG\xb5\x94\x92yT\xd3\xc3\x87<\xac\xe9\xb1*\xe2\"<\x1e\xe0i\x9f\xd1\x0f\xb5\x90-\xcb\";\xd4\x11\x8fyYG\xe9\x81\xb2\x8cG\xf56\x8d\"\x9e\xd7\xe9aG\xf7uV\x14\xfbzw\xcc\xaat\x9f\xf1\xba\xd8\xf3\xbc.9\x8d\x8a<\xfbP+5IT\x1f\xc2b\xcf#\x0b\xbf&\x96\xbf\xd9<=_n6\xd5fSn6\xf9f\x13\x07\x16~C,\xc7s7\x9b\xcdfQ\xfb\x9b\xcd\xe3<\xa8\xfd\xf7\x9b\xcd\xd3r9\xdfl\x9e\xe82@3\x0b\x7fO\xde\xb4\xb4\xc4z\xb4\xb0\xf5\xf8'\x0b\xe1\x1f\x88\xb5\xd9\xf8\xd6\xec\xf5\xcc\xbar\xac\xd9\x9b\x99\x85\x1c\xcfU\xef\xfe\xd5\xfbg\xf5\xf4\xb7\xc0#H\xa5x\xee'N\xd7\xd4{\xf1\xfbI\x80\xae\xd0'\xf5\xc6\x1a~\xd8X\xe2\xcb\xc6\xaa\x1dk\xf6\xfd\xccB\xa8V\xb5l6\x81\x85\x7f$\x96\xdb5\xb8\xd98\x8e\xf3\x9fW\x8d\xea\xe1\x17\x07\xf9\x9bM\x10\xd4\xd6\xec\x87\x99\x85\xaeP\xbd\xb8B\x9b\x8dh\x1a\xff\x9d\x08`\x95\x1b\xddy=\xb3f\x16\xb6\x12\x0b\xe1\x9f\xcct\xeb=\xf4q\x06\x15\xbfW\x95\x06H\xb7\x82\xae\xe4\x18f\xcfT\xe1\xb7#\x85\xaf\xb0\xfc\xb1\x10~7\xf6\xd9\xf1of\xbf\x89.\xbe\x9eY\xa8\xcd\xfa\x8f^V\xa2\xb3\xbe\xdfl\x82O6Vp\xe5\x99\xb3\x07m\xff\xd3,\xf1#\xc2?\x0f\x1b\xfb~f=\xb3\x10\xfe\x85\x9c\xbe}\xe5\xf6\xbe\xfdIM\xbd\x85\xf0\x97\xaf_\xbe}\xdb\xff\xba\xd9,\xba\xef\xef^\xfe\xb5\xffU|\x1a@\xd2\x95\x85d\xe6\x97\xef\xde\xfd\xe4\x0ez\xf1\x03\xc2?\xbe\xfd\xea\x1f\xaf~\x18~\xf8\x11\xe1/\xbf\xf9\xf6\xf5\xa0k\xae\x03\xc0\x0f\x9a\x8c:\xa3\x87\xaa\xce\xab\xad\xf8?\x17/h\xee\x84\x82\xf7\xad\x8bx.\x90\x9b\x02\x1e5[\xfc\x81\xe7u\x11E\xb5\xe3\xf8\xb3yP#g\xb3\x89\xaeP^w\xf0\xab>\xa8\xf7\xcd&\x9a\xa1\x1a\xb5S\x0b\x80b\xa5\x82#/\x8al0n\xb1/\xbe\x9bY\xe8\x99\xca\x92s\x1e\x1d\xbe\x94\x1a\xa4\xe1\xd8Dur\x99\xdd\xaeW\xfc\xbeN\xaa:\x93#\xea\x06\xd8\x1f\x83\xe3\xb9\xf3\xcd&B\x1et\xdd\xe8\x98\xe3\x11\xff\xfd<\xa8\x9f\xa9.6\xf8_\xe4Z\xf4*\xcd\xf7\xc7J!\xa4Zt\x86\x96\x9c\xd6\xecXUE\x8e\x9e]\xa7\xf8\x7f\xc8\xf5\xfb\xed&\x12\x8f\xcf\xc8\xf5{\xff\xfd)\x98mN\x9b\xc3\xd5\xc6\xcfi\x95>\xf0\xc9\xe6\xf1\x1a\xff[\xd6\xf6'\xc7\x17\x18d\x86jg\xf38C\xf5f\xa1\x13\xd0\xb3kL\x19\xb9\xf6g\xbf\x05\xd7\x981r\xfdI\xbd\xd9\\'8d=\xc8\x83}\xe8o6\x11\x9d\xc7\xc1i\x85\xff\xdc\xc0(\xbcZ\x0e\x11\xd5\x0b\x18\x81\x00\xe1\x88\x91Q\x96\x8aX\xcb'k\xc6\xe6\x7f\xfe\xfc\xf3O\xff\xac\x19\x1c\xc1\x9eEu\x1dz\xcc]\xdeD\x9e\xa4\xe6\x8b\xb8,v_ni\xf9e\x11q'\x9aA \xe4\x8e~\xbc\xb9Y-\xeb\xcf?\x7f\xfe\x97?\xe3\xd5\xf2\xf9\xa7vT\x7f\xfe\xe7O\x9f/\x85\xa8\xc2L\xb6e\xe7\xa0f]\x95\x1fN\xdf(\xc6\xe5+\xf2\xad\xe4T\x1e\x16\x00}B\x12; \xdc\x7f\xfb\xca7\xdf\xb5j\xb3\xa5\xd7MH\xabp\xeb\xc4\x0c\x9d\xbe!'\xa8\xd7\xfdJ\xe5\xf2\xfaD\xea\xafZn\xc1\xaaY\x86P3\xca\xefS\x83\x7f^?n\xd3\x8c\x0b\xfa\xa5X\xe6\xd9,@\xeb\x96]\x0e\xe7\xab\xa6iZ\x9e$a0\xe1\x11\xe6\xb2\xae\x18o\x15\xbd/\x80\xce?\xe2'\xc1\xbc:\xccc\x8b\xe21\xe7\xe5+E\xdc\xeb\x9a\xb9\x0fhJHn\xdb;\x87!\xcc\x04\xcb\x91\xe3H\xac\x8d\x1f\xe0;\xc2\xda1\xb7\x12\xc6\xd4\x10\xc7\xa7\xb4\xaeWSB\xeel\xfb/\xf2g\x05\xaf\x9a\xe0F\xa2\xdd)\xb7\xed=\xc8\x82+\x95\xd7\x89\xc9\xbf\x17\xfc\x89\x83\xc0+\x08\xf5-\x89\xfdU\x00y\xfeBDy\xf1\xb4%l\x91\xf0JI\xd8_|\xf86rn\x11\x9en\xebz\xba5\xc4\xe8^[\xdbE*\xa4\xb4\xdb6Qr\xd6[\x84\xa3VJ\x1cL\x82mCK\xbd\xb4\xf3v\x91mW\x0e\xc3[d\xdb\xbf\xd7\x86\xe8{\xec?\x0f\xf4w\x0dy\x116\xc7s\xf8\xe2\xc3;\x9a\x08\xe9[L\x02\x86\xde\xc3<|\x1a \xdb\x0e\xfb9\xbf\xcc\xe8\xe1 \xf2\xfen\x9dmN\xd1g\x1c5B\x0e_\xdc\x1f\x84\xe48\xbd\xaf\xeb\xe9\xfd\xa2\xe2\x07\x10\x1ea\x8e\x0f\xa4$G\xfcH\x18~\"jq(\x16\xcc\xe9]w\x9e4%\n\x0c\xceU\x05\xe8T\x90D\x08HN)\x17\xebeU\x95);V\xdc\xb1\xd2\xc8B\xc8;\x90\xb2%0\x8cak\xb3yf[\xc8e\x8b\xc303> | \x96\x9fF\xe4\x13kv\x98Y\x9f\x04\x13\x0bg\xa4\xd0\x8c\x9d\xdc\x13\xd9|\x8e\n?\x0b\xc8aV2G<\xa1\xf5#\xa1L\x8f\xcb\xb6\xf7\xcca&|\xd4\xb5\x18]\xb1\xb8-\xd2\xdc\xb1\xb0\x85\xc4\xa4\x9c\xca\xbafJ\x853\x18u\xd34\xaa\xe2\xd4\xe9\xf45?a\xeb\xd9JP#\xd8\xa8\xdd\xee\x15\x8c\xb4Ta\x0b!\xb3MfN(\xf6s+v\x01\x8c\x853!\xc1\xdc\x08Y+\xdc\xf2\xd70/\xb6\x1d\xf1\x8cW|\xc2|\xba8l\xd3\xb8rP\x80\x99\x0fy\x03\xc2u_X\xd7d\xcaL\x8d\x92\x7f\x0c\xc8t\x89i\xf7\xfd\x96\x99\x873\x03EW\x94>Xh\xdd\xcd\xdetJ\x1d\x86\xd4\x04\xb5z\x84\xe9\xaa\x9d(s1l\x9b]\xd6\x80aF\x84Hl`\xb6;\xd6\xc7\x90J\x8a\xab-dj\x18$`\xf0\xf9\x1cE\x0bZU\xe574\x8f2\xee\x87>\x0f\x02b\x0c;\xeb\xd5\xc6\x04\xa8G$\xb4\xed\xa10\xb6\"\xc4@|\xb6\xed\xfc\xc6\x16\x87\xe2X\x86\xfc\xdb<\xe2Ou\xfd%\x9a;\xbf\xd1a\x9a\xd8\xc1Q\x0f\x1b\x85Hv-$\xe1\"\xe7O\xd5\xdb\x94ei\x9e\x08\x8c\x17\x1ar\xc9|\xd5*D\xbc\x95;_u=\xde\x99\x0b\xd5\x12\x8dn\x08\x17\xb6\xa5\x96B\x81\x9b\x00yR\xcc;\x9c\x14\x12B\x8d\xf9\xcd\xff\xaf\xeaw\x8c\x06\xea\xda\x92\\\n\xbc\xa1\x0b\xed\x15f{)s\xcc&5\x98\x92\x19\xc3\xe6\xa7\x10G\xb2?\x1c\xc7\x84:~\x80CM)\x19\xc2 \x89\xfb`\x90\xcc\xe7(\xf49\x89\xfd$\x08l\xdb\x11P@\xa6N$~\xc43B\x8d\xf8\xd7vi\xdf\xdb\x0b\xb6=v2MG\xf1\xb6m\xd3&$ [(u\x05958\x16\xef\xe9\xe1\x977\xaf\xcf%rP\xdf\xd1!\x05\xa6\xa8\x95\xb5U\x0b\xed\x89\xa7g}\xf3\xee\xcd\xeb>\xfeu\xa7\xab\x06\xef\xa0U^\xe9ZF\xa4\x7f\x8e\x13B\xbd\xf3\xd6\xdc\x87\xf6|F\xd2}Ao\x13\x03\xd8\x93aw<'' .\xc8\xd9\x07\xccE\x1a\x8f\xe91\xab\xfe\x99\xf2G\xccm\x9bO \x11\xc0\xb2\xb7m\x87/h\x14}\xf5\xc0\xf3\xeauz\xa8x\xceK\xef<\xc9\xb1\x8eyV\xd0\xc8\xc2\x9c\xe1\xe9\n\xb9\\la\x1an!\x97m\xf7^\x1d\xab\xc8\xbb\xec\x08\xe1=\x99\xc6N\x82p\x08\xfb\x1eP\xf0\x81\xdc\x1a\xc0c*\xceCM\x1a\x89\x95ZxJ\x07\xf4\xaa\xfdl\xa1F\xd48\xb6\xe4\x17\xeb6\x95\xfc\x89\xc2\x98_\x16;\x891-\x84Ts\xe7\xb4_\xc8\x8b\n\x80\xcf[m\x899y&\xc9[r\x89-\x90%\x05\xafr\xa1\x8bE\xaf\x8b\x14 \x1e\xe6\x88\xa7\x83\nE]u=\x96\xea\x1c\x87\xdd\x14\x8dyN\xb4\x88\xd3\xce\xacOn^\\\xd3\x9b\x17Ra\xd0%\xcf7q\xf0\xc9dw\xa0YV<\x86t_\x1dKN>\xf9\xe4\xe6E\xb1\x07\xa2\xa75\x9e\x90v-\x13o^\\\xcb\xe4\x1b\x0b\xd3\xf3\xd5\xb3\xfc~u\xef\xc9'\x9f\x04-\xee\xb2\xed{9\xdd\x96\x7f\xf5\xfeY@:\x1d\xe3'\xf5\xc6\xda\x80Bi\xb4R\xdd\x93\xae\xaa\xba\xd6Uu\xdaL\xcf\x05\xe8\xae\xa5\xd2\xe6R]i\xf4\x1b\x91\xc3\x1f\xab\xed7r\xa1\x9c\xab\xf4\xc0#e\xbaO\xa3%\xe9\x9f\xa0\xb9\xd9\xd5H\xd1\xc5\x9f\x163\x7f\xf6[\x00\xd4d\xb0\xba\x12O$C\xceZrSh=\x14\x8e\xc4N\xb4\xb0%\x95\xcd\xd0\x95\xc1\x99r?{.h\x18\xb6^]\x9a&\xf1\x9dDck\x07%\xa5\xbe\xabU\x17_\x9a4\x9e\x83\n|l\xd2\xf4'l\xb9ZS~\xa1\x96+\xec>Y\x08\xeb\x92xq\xe5\x8a\xf9Bb\xcf\xec\x84@\xc1\x0f:\xbf\xde?\x07R\xe8Ou],\x1e9\xbbK\xab7\xfd\xbc\xe2\xc3\xae\xf8u$\xb5\x18\xcby\x18$\x8a\x0d9X\xb1p\x11\xa5\x87\xb0\xc8s\x00V\xc8O\x0e\xea\xc4\x0bK\x91\x08w\xef\xfea*v\x07\x8c\xadTc\x9b\x12\x0b\xff(`\xe1\x9e\xdc\xb7\x13o\xa8\xda\xee\x95|Z\x0bn\xa1$\xe5X\x9e\xd2\xcc\xc3\xf4\x8c\x14\x8b\xb0\xd8 \xea\xa8\xd9\xbc\x1f\x8bC*:\x8epEX]\x1b\xd9\xf2\x8a\xa6\xf9\x01yc\xfa\xa7\xbf\xf4\xa4 \x8f\x0e\xd9=WHK\xac/\xc0\xb5r\x0b\x01u\xde\xd4\x99FR!\x14\x19&(S'l\x9b\xf6\xbaG'B`\x8c2\xdau\xdb^\xfd\xd9\xbe\xf8\x15,\x9a\x86T$\x8d\x1d\xa6\xe4-Fz\n\x00\xf1\x05H\x86\x12M\x97\xebVF\xc5_\x10\xe6\x9d\xd5C\xcdC\xa3L\x08\xc7\xcb\xb5\xd4eN/\xf6i>e\x97>\xb5\x04\xc8\x8b\\'\"c2\x00!d\xa8\x97\xaak\x86\xbc\xcbS\xc0\x90\xbb\xc2+[\xcc\xba\xb4\x9b{\xc5\x05\x9f\xcc#\xb1B\x97\nAC\x91'\xc6\x97\xd4\xf5\xa0\x1f\x84\x90\x07\xdb\xae\x9c\x07L\x917_\xb9L\xe6b\x97r1\xe4\xad\xdc;\xefo\xce\x1d\xa6h.~\x18r\x97\xeegv$J\xaf\xc6\x16\xe8\xd2\xc4\x86\xad\xd1@\xb7l\xc0\x07\x18\xaf[\xe2\xd3\x00\xa7\xc4g\x81\xd4*\xd6\xf54F\x06\x00&m\xa7\xbd\x95\xcb\xc5K<\xd6AQX\xf0ImY\xa5\x16X\x87\x84\xae;y\xdd\x80\x9f\xed\xe2\x98K\xc5J(r\xb1\xf1\\\xa9\x99K\xe6\xd8\xfaQ@\x08I\xfd(@\xd1l\xd6\xc1A\xc6\xe0\x1b\x86/\xae\xca\xf6 \xba\x9c\xea\xe7\x95\xbblp\x82\xdc\xbc\xc1 \xd3\x18o\xfc\xb8\x15\xf4\xbf\xf91\xcb\xe4\x1f\x86\xcc\"-\xfe<[\x8c18\xd4\xba`\n\xba\xe0\x96\x91\xfd\x07\xb6\xc8'\xcfV\x82\xe0\xe3\xa93=C\xceu=\xdd\xd7ui\xdb\xa5\xc45\x0c\xd5\xf5\xbd\xa0+\xea\x0d\x81\xb6Mn\xa1\x16M2\xa91\xa9\xeb\x11\xe4*\x803j\x15\xb4\xa07\xee\x12Z\xdc\xd2*[\x94\xf6\x89\xa3S\xd3\xcd \xc3\xb9\x9c\x10\x9f\x06\x9aJ\xdd,an4\x0e\x1a\x9d\xcf\xdf\x99\x17m\xee\x9d0\x10:\x07U|\xbc0\x00;'=5\xd5\xc0\xd8 \xc01\xe1\xb6\xfdJ\xce\x92\x99\x13\x0fr\"\x8f\x83\xde\x7f\xba\xd7\\\xa7\x06\xb0\xd6.3\xf6b\xd7\x94\x8d\xc5:y\x03Y\x87!\xd7\x89\xc9\x88\x88\xc1\x04\x1d\x8c\x17\x87=\x0f\xd38\xe5\x91\x17K\x19\xc3\x05%\x9d\x18?Xe\xf6$\xa037\x81\xb7\x1f\xf2\x8a>M '\x9e\x1c\xf3\x92\x87E\x92\xa7\xbf\xf2h\xc2\x9f\xf6%?\x1c\xd2\"w'\xd6\x8c\xca)=\xe6\xe9\xfd\x91\xbf-\xca1\xa5\x86!\"\xc06\xce\xc84\\D\xbc\xe2a\xf5\xea\xb8\xcf\xd2\x90V\xfc\x80\xef\x88\xc2\x88o+\xc1{\x08\xf1 \x0c\x08\x9c\xa5`B\xc4\x07\xe7\x0b\x843-@0B\xfdX\x08\x10@#\xfc8\xb0m\xb0\x87\x01\xb2\x1d#d\xa8\x17\xa92I\x06m\x12^!\x0dlw\xa0\xb7\xc4\xb4\xc1\x9c$ \x1c\xbc\xe3O\xa36\x19\xc4\xb2\x00\xd5\xc5\x06\xa9\x15#\x89\xe5\xf9\x88@Ku\xfd\x17\xf9\xb3\x82W)J\x9fYw\x81\x91!\x1cX\xe6U\x8b\x04\xcdD\xb0\xc0\xa4\x84.\xe0p\x12X\xc55]\x8b\x04S\x13\x19\xce\x08\xd8\xbc\xea\xb3\x91Oe\xd3\x9f\x99\xf8Q\xf6\xf4\x9fb\xe9e\xben\xde\xe0\\\n\xea\xe8\xc4\xdc\xb0\xc1\x91\xd4OI\xdcp 'C[\xed~\xbe\xc4\x92\xed\xfd\xf1\xc0\x8fQ\xe1\xa6\x0c\x032q\x7f\xc1\x1d\xa8\xbb\xa7\x06\x0b\x01M\xfc\x96<\x83\x83M\xf7d\xddX\xee)JK\xd7\xea\xd0\xae\xa5\xec\xe4\xa7\xcb\x06[\x93\x91\xef\x0d\xb6fmr\xc9\x1f\xd2\xe2xP\xa3\xef\x95\xfd\xedR\xa6\xa6\xc1\xfb\x92\x7f\x0d\x02\xbf{\x82S\xf11\x05\x82\xbf\n\x88\xf83\x10\xfe1\xf5?\x0d\x88#\xfe\xd65\xf5?\x83\xbf\x9f\x07um\xda\x03\xaa\xacBD\x01\x18|.`\x10\nZbg\xf8\x9f\x06\xa0\xf7\xc7- \xe3\xcfP\xa3\x0e\xdc?\xda\x97\x1e\xbe\xc0V^me\x03\xab\xa0\xad\xe9S\xe4\xa9\xde\xe9\x0d\xedP\x7f\x19\x88\x8e\x7f\x16\x90\x99#~<\xd1e\xf1\xf8\xe7\xa0\xaeW\xc8}~\xe5X\xfc\x81\xe7\xb2\xb2O\xc1\xe86\x8a\xf4\x1b\x12e?\x97e\xff\x7f\xc1\x8c\xfa\xff}\x96\xc1\x15?\xb6=l\xb1\xd1\xd6\x05c;g*\x9a\xb7m1;\x1a\xd4~Y\xc0\x1c\xa8\xa3\x1fQ\x87'6\xa2\x0b\x03\xf2DN\xd2\x9fr7\xb4\xed\x7f\xca\xec\xa1\x90\xba\x19I\x9c\x10O\x97H\xbe\x84\x9d\xbd/\xb2Z5\xf3\x9c\xa1\xb9~F\xb00KQ\xef\xb2\x9bCX\xe6\xe7\x81\xf6J\x82\x14s\xb5>E\xa8\x11\x00-A\xe8\xdd\xcb\xbf\x8exd\x0c\xb5F\xe3\x1a}\xa9\xfb\xe88\xd6\xf6\xb8\xa5\xa7T\xf9\x8f\xcd}\x9bF\xd9\x8e\x9c\xf7\xeb\x83O\xe1 \xa9\xd5J\xd7\xb5\xd3\xb7\x11p\xde\xb7\xf6/tfI\xc3\x80\xfa\x19\xb2\xc4\xa4~p(\x1e\xe9\x97:\xad\x1bAka\xa7|1^\xea\xfa\xf7UeC5\x99\xd2\xe8Z\x08\xf6Z\x83\x1a<\xd8\xbb=\xb3\xd76Y\x1f6\x10E\xdf\x9d\xc8pB\x92\xc6\xb1\xdc\x13\xd2\x9c\x987\x97y\x0e\x9f \xa4n\xc9\x04Op\x96\xa1\xab\xbf{|\n\xaf\xef\xd5kh\xdbKB\x08o\xe1,D\xaeu\xd5}4?\xdc\xccW\xae\xf5\xcc\xfc&\xc1\xa9\x83E\xd9\xd4o*\x8b#p\x05o\xa1\xe8\xef\x02\x1d\"\xc0\x1b\xc3Jk\xb3\xafu\xcd[8\xd55\xcfVP\xf7\xcc\x9a[\xeet\x85\x04\x82\x90ij\xdb\xd3\xad\xa0\xda\xf7@\x9cc\xcdI\xec\xd1)k\xa5\x83\x8cd\xfe>\x10\xb2\xe7\xd6\xcb.o\xbd\x12LA\xb3!K;]\xad\x0b\xb2'V\x91g`\x10Jm{Z\xd8vo$M\xbb\xf5\xd3\xd8)\x88\x9fx\xf7\x06\xb1w\xef\x17b\xe6\xe19\xc0\x89m\x1f\xd0\xe9\x8e\xdc\xfb\xc7\xa0\xae\x1d\xf1\x03\x9eI\xb7\xe4\xce\xa7\x01\x18{\xe4\xe4V 6B\x1em\xfb\xd6_\x05x\xd7Kx\x1e\xe0L\xb0\xb1\xf7\x86a\x8c\x9f\x07\xedhg\xb3\xdc\xb63\xdb\x16\xa3\xaekgGr\xb2Du],\xf6\xc5\xde\x01#\x8f\xfe@m{6\xdb\xd9v\x06\x12\xe1I\xf4\x82\xf8\x8f8\xc7\xbb`-\xcd\xf2[\x9e\xe4\x00\x9eX\x0e\x93]g\xaa\xebHp\xf5\xa2c\xb2\x8bH\xf4v\x15\xac\x0d\x06\xe5\x8f\xf4\xe9?\\\x1c\xd5i\xe8\x92\x93\xc9\x0eeF\x87\xc4\x10v\x01\xc2rT}O\x81\xdd\x9cp\xbc\x93\x8a\x92\xdd\x7fE\x84\x90\xa5m\xef\xae\xa3\x1b\xb2l\x9a\x11\xcagXw\x0bn\x14\xb8\xa5\x03,V\xb48\xf0J2$\x07\x9f\x0e\xc4\x07\x83\x8e[\xc7\\\x1dM\xf2h\"+\x90\x9cvkf\xee\x1f\x03\x0f$\x00\xae\xe5\xa3\x95\xe7\x84\xc4\xa7\x98b\xcb\xc2,\xc0f[\x03\xcb]\x87\x0e\xe5\x11\xf3\xd8\x96\x9a6\xf5 \xa8\\8\xac\x8d\xc8\xdf\x04\x91\xf0\x13\xe09\xa2\x80L\x9dP\xfc@J\x83\xc6\xc8\x9a\xa8n\x89C\xf1\x95\x0b\xf6L\xce\x8d{\xca\x8b\xcaM\xc7T\xad~\x80\x95\xe3\xf0\xf6\xdc\"\xa3; \x10\xd3\xd1\x1f\x83\xc0,\xadUUB\"-hs\xec\x07\x02\x8d\x0dl\x10\xb6\xf39rb\x92\xf8\xdb@r\n[1\x1c&~b\xd4\x1f\x0c\xe68\xee\xe8!\xb0\x148\x122\xab\xa8\x1e\xdc\x03 \x11^\xa7\xa1\x84\xd8\xa6AxK\x0f\xc31\x0eI\xd8@3\xc0\x0c\xe1\xb7AX\xcb\xbe\x17j\xa1g\x9c\x08>\xaf\xd8a\xa6\xa8Q\xd7L\x9eO\x08\xa9\xa7\xaeArli\x0e\x154G\xb4\x9b\xd1<\xb9\xd0\xe6\xcf\x8a\x83\x03J} \x80\xa1<\x80/>\xef\xe3\x00I\x9b=\x86=\xb4\x8e\x8a X]\xec=\xb6\x80\x9a\x86\xe6JO\xbb\xcc\x15\x1fD\x07\x86\xdfdzkEN\xc2As\xa1\xc0\xd0\xd2d\xbfc!\xa9 \x9fZz\x1c\xaa$\x87F&\xa8SI6\x08W\xb4\xec9_\x9b6\x82E\x081\x03\x04\xb7\xa3\x9f\xc5\xbe\xdc\xf6\xce\x02%\xa5]IW\xae4jpY\x14\xa3\xce\xdc\x94\x10R4\x18\xcc\xdd/}\xcf\x174\x14\x02\x98\xd2\x03\xdb\xb63\x85&\xbf\x06\x1b\xf9\xba{v\x04\xc77\x9d\n\xbc\x00\x8a_\xba\xd8\x96<\xae\xeb\xdf\xe8\xa2\xa2\x0c\xecd\xc0a\x18N\x04\xc6\xd9U}^\x00\x9eV\x0d\xd6\xaf\xbf\x9fy\xd9`uV3\xca[\xffA;\x19&\xfaO\x17\xda1\xa0\xb6\xe4\xd1\x98\xf1I\x9fW5X?\x8d\xf7\xcd4y2\xdf\xda\n`:pW\xa1\x1a\x04\xdf\xed\xab\x0f\xbd*\xff\x90\x1c\x9f\xc6N\xa7Px\xf1\xe71\x9fK\xd9\x87\x91\xdeN[\xea\xb2\x80\xd6\xc1\x07v\xcbi\xc4\xcb\xb1\xb1\xfd\x8f\xda\xac\xed\x9c\xa2\x06\xc3\x04\x8ee\xfe\xd7Hfi'\xf4\x7f\xb9L\x86\xb5\x91\x067#\x895\x18,\xb9\xcf\xda8\xab\xeaR\x9b\xb6m\x89\x1a\xba\xfam\xdb\x91\xdc\xbf\xc3\xc8P\xd0\x00F\x16 AC\x97\x19\xaa\xea\xb4;\x7fa\xe0?=I\xfe2\x00\xf48\xf8lh$}6_\x89<\xfc~\x98\xa3\x93`\xfc\xe5M\xe8\x853\xe6\x86\x90\xf3\x81\xe7\xe7\xb5\x19\x1e3k\x06n2\xe49\xa2\xc3sr\xda \\D\xd1\xc7\x8a\xaf~\xa7xv6\x94\x9e/\x1fi\xfb\xba\x9e\xcf\x05\x03\xb4\xd6\xd5D\xbdj\x92?\\\xcdl\x16\xbd`\xe3\xb5\x80\x99\x88\x06\xf0\xbc\xda\x12\x03\xdc\xef[?\xe5SI\xa3\xb4p\xa7K\x89FX\xf1$\x9e\xe34\xe3\xe2wO\x0f\x87\xc7\xa2\x8c\xc4s\xba\xa3\x89HlP\xc7\x95\xb1\x80\xec\x98\xc3PW\xdd\xe1\xc8vi%\xf2\x97\xfc\xc0\xab\xf3\xfc\xb9\xcc\xaf\xc66\xb9g\x0e:5\xf7\xcc\x08\xea\xa1\xadL\x0e]\x8f{\xec\x18\x08\xe1\xf7\x0c'BT\xad\x8a;\x9e\xa7\xbf\xf2\x81:\xfaA\xbb\x07vn`\xe4W-\xd1\xa7\xb1\xd3Zj3o\xe9\xde\xb5z\xd2\xf5\x96Ppz\xc4\xb7\xa2q\xad\xfe\xd2\\\x0e:9\xd3\x10\x82\x0c\xbc\x95f\xdc[\x04\x1a\x14\x0e\xb6\xd4[U\x0d\xf7\x97\x81\x16U\xebz\x8b\xb0rs\x8c\x89\x1f A4\xa7+\xecp\xf2\xae\xad\x02<\x15\xb9\xb6a\xc5\xb1\xcc~\x92J\xe7P:\x8dC\xa5\x06\x037\x81\xf3\xf5\xae\xd1V8\x96k\x91L\xd2|\xa2'\x12M\x1dN~\xf1\x93\xa0m\xb1\xaeo\xfd$\xb0m\xf1A<9\\\xa4\xfd~/\x12\xac\x0e@\\~\xa9\xf54v\xa6\xa1\xf20n\xe7x\xab\xbe\xbb[\xaf\xd3}!\xf7W\x87\xe2\x14\xb5\xb3\xdft`Q2M\x02$\x8a\\\xe2\x9e\xa3\x80e\xad\xc3\x1b\xb6f\xb3\x19\x8af\xe0\xe4)U\xf4\x9d\xc9K[\xd3\x819=\xaf\x0c\xb6\x88\xd2\x12s\x12\xda\xb6\xa9.\x15\xf2 \x8e\xc9SwZ\xc5$\xe5\xe9\xe4q\xc1\x13\xc7\x9d\xf6\x9c\xf9Q\xa0\xc5;f\x1c\xfb\xb6\xa73T\x95\x18\x08\xec\x89\xec @\xa5\x90\x9bb\x00\xc8\xe4\xbc\xe2\x91\x9am\x9b\xaa:\xda3\xdd\xbej\xfar\xa7\x84\xc4\x9e\x92\x810\x89\x9d\xad<\xa5\xb3\xedm+\xf3n\xfdU`\xea\xc1\x85\x0cL\xb6\xfes\xe8'\x9c\xd3\xddbH;\xef\x8ba\xf2Z\xf5\xecK;1\xab7\x17\xad\xd2i\xc4\xa6\x19\x021\xf9 Z\x88S\xb0\x1aX\xe2;r\xb8 \x16\x03W\xb9\xc5\xd3%\xc2\xd9\x85L\x7fs\x18\x96\xf2\xae\xca\xb8#\xbeA\x8f\x8d}>M\x04\xec\xd6u8%\xe4V\x90\x1c\x87\x91\x10u\x90v\xa7\xb2\xbb\x99z\xe8\xe2`I\x95\x00o\x82u|\x93\xaeS\xe9\xe9\x1c\xf6\xc7\x9a\xaa\xb1\xa2\x1d\xf1\x0f\xcc\xa9\x98\xb3C8DR=vR\xf9%Q4r\xabi\x96g\x99\"U\x119\x84C\xff\x18\xc8\x99\xe6d6K{\x016\xccv\xb9n\xb7\xa7\xf9zdNz\xb3\xb2m\xd9\x0dx\x14t\xad\xd5\x13\xa7\xf3\x15\xd2a\x04\x14\x9d\xb5&\xf2\xb0(\x9d?\x97Uz\xd6\x95\xe5ZVc\x04\n\xd2\x0e5!\xe67\xa9m?uU\xa6\x02\xd1\xe0\xf8\x86\xcb\xd4V\xf5\xdc\xa6\x02YE\xcdN\xf3\xae\x9aBC\x0f;\x00\xfb\xd0w\x18i\xd5(\x86\xf3\xc9\xcd\xd2\x0c\x93\xa0\xd9\xac;Y&\xc3;PC/\xf1=\xb1\x96\x16.Il\xdb~\x80\x0fbgU\xe4\x16\x1f\x05\xaa\x01[Um\xae\xeb\x08\x94s\x87\xf0\x03y\x9c\x11)pT\xde\xca\xed\xc5J\xaa\xeb\xc5\n?\x91\xa3\xde\x93b]\xeed\xb8*ij\x90\xa0\xf5\xfd\x94\x90'\xdbV\xa1\xa52r\xf4\xef\x03\xb4\xbe\x9f\xcd$^\xb0\xed\x0c\x9dv\xad\xa3aA\xa8\xbf\x9b\xcd\x80d\x16\x8e\xd8x[\x84N\x8a_\xcb\x90\xd2\xc8\x8a6\x1e\xc9\x03jBP\x7f\x92i!\xaa\xb1\xed\xfd|\x8ec\xdb.uv\xc0D\xfb\x19\xb9\xc7\xa1m\x8b\x8e\xec\xfbm1\xd9V\xe1\x94\xf8\x00Mu'\xe0\xfb\x9b\xa5\xb2\xe1\xba\x9f\xcfQ\xe9\xdf\x07u}\x80\xbf\x8e\xf8!_K\xb3\x88\x14\xa1\xf5A \x92\x03j4vH\xf1\x01\xe1;\xdb\x16H\xf9\xd0\xae\x8em\xef\xdb\xc8$\x02\xf0z&\x06N\xda\x1d\xe1\xcb\xb1\xe1[R!\\6-9\x04} rc\x9doK\x12i^\x95f\xe3\xbc\xb6\xb2Q\x80X%/\x0dN{\x1a\xa3\x93<;K\xc0\xe1\xaf\x83'mN4\x9f\xa3\x98<1\x87\xf9a\x80p\xec\x1f\x03\xaf\xb5Bp\xb9~Z\xc7\xe4\xa5C\xf1\x07A\xe1\x04\xdd\x8b\xdb\x83vB\x0d\x03\xe7\xb4;\x82\xef\xbb\xe5\x02\x1d\x15]5bB\x90\xb1\xb0J\xb6MqA\xc4l&\x0e%y\xdb\x8c\xa0-\xd2\x86\x8a\xc3\xb9\x81`\xb1\xb4\x8f\x1e\xac\xe1-)\x04\x0fU\x18'\xa4\x08kzv\xf3\xdc\xb6\xado_\x89\xdd\xed\xdc\xc1\x01\x03R\xe2u\xebT }RL\x07\xac\xbd\xd8!-\x9a\xb9\x85\x93m@3`\xf4G:'\x04\xe7N\xe3,SLPjL\x86D\x7f\x91\xbf\x0c\xf0\xb45B\xe3\xeb\x1c\x8e~{\xda9\xdc\xa1\x8c[-\x08H\xc6Z\x8f\xb3I\xc9/\x0b\xd3i\\\xbb\x1fzKw@\xba\xd3\xf9\x1c\xfa)F\x9b\x06\xd8\x18HF\xeez\xe8R\xf0\xba;\"\x07\xe3g@Yc\xb2\xfb\xe8\x98\xb4\xdf\xe3\xad&o\xe3\xfe\x8f\xca\xdb\xf3V\x13\xdc\x14\xaf\xc4 \xb56\x1e\x10\xe2-\xc2S:\xf4/\x15\xd0\x82\xb9>\x91Q\xf0\xe5\xe4u\xbdu(.\x10rb0@\xc2\x1c\xff\x8e\x07&\xc2\xbc\xc1\xa6Y\x0e9jo>\x0bi\xa3\x1ce\xde\n\xae\x17G|n\xdaC\xa6\xd3\x0c\xef\x1c\x84\xfb\x16\x8f\x17\\^V\x1f1 \x1d\xf7j\x1c1\xd5n\x19\xf7\xbe\x01\xfe\xb6\xe41\xf9\xe4O\xd2\xfc\xde\xc2\xd6\x9f\xa4\xa2\xa8\xd3\xd1\x0d4D\"\xbf\x90T\xeb\xfa\x8eI}Q\x0d\xba\xd1-O\x93mU?\xa6Q\xb5\xb5\xf0\xb8fg\x12z\xd2\xa0\xcb\x1dZna\xab=B\xed\xeb\x9b\xbc\x95\xfb\\z1u\xb6_g6\xcd\xa3C\x03\xc5\xd85\xb8\x00\x18\x83\xe9\x1b\x98\xc3N\xb0 D\x9f\xf5;\xe3\x96Y\xdb\x81\xab\x92\x97\xc6Y\xd7J17\xbd\xac\x98\xeb\xe6B\xbb\x9e\x81a\xd2\xa5\x85SA\xb8\x06\xdd\xea\xcc\xd2U\xcf\xbe;\xeb\x13\xc8M\xeb\xb3\x15\xf0Y\x00\x9aZo0\xe3\xae\x13]\xb4\xa1\x8b\x0c\x1b\xba\xc8\xb4\xa1C8a\x8dC\xd1:\x87=O*\x88\x98\xb8/I\xd5YO\xa9$\xdfr-\x19\x81q_\xb6\xda\xa0\\\xd12R\x19D\x0d\xe72\x00]\xa5\xed\xd0 \xdc\xd3/o^\xbf*BR\xc9G\x9cw&\x90U\xfb\x08\xd6\x89G\xdd\x08 \x9d\x1e\x8e\xc3\x0f\xe4\xfa\xfd\x0b\x08%\xb19\\m\xae\xbd\x1b\xc7s_l\xae7\xab\x9b\x1a=\xbb\xc6\x8f\xe4\xfa\xfd\xc2\x7f\xef\xfei\xe3o\x168\xb8zv\xdd)2\x9e\xf4\xbc\xa6\xb1\xd3\x0b?\xc5\xdas\x95|\x91\x94|\xdf3\x18\x11\x0c\xb3\x12s\xa7:\"\x1b\x8e0\x98T\x86\x0d\x90!vf\x0d:RO\x9f\x8d\xef\n\x9fG\xcb\x82\x0e>\xb6v\xabm\x95\x92YV\xb1\xd0\xd6\x8c\x98)-\x13\xf1\xb1\x96\xbb`j\xe8\x86,e\x17\x1a]\xcb\x85\xc8\x19\xcc_\x06\xc6\xb9\x8eC\x89\xe5\xe6E\xe5\x80\xa1\x0d\xb2\x10\x96\xea\x0d\x8d\xc4\xc1|\xa2\x93!$P\x0dMu\xc1\x9e\xc5\xf3\xa3\xc0\xf5\x03\xb7\x9f\xc5\xa1X\x8d\x80\x8d\x8d\xa0\xef&\x0dAn\x8d\x80\xac\xce \x0c\xf6\xccrJ*\xeb\x05\x9bU\xec\x91H2\x17\xa0\xb3\xe9\xd1S>\x0c\xc0\xebP\xa4\xa7\xbcmC\x8a$\x8c,[-\x18@W\xeba\xc0}\x16`\x08@\xdc\xa9f\x94b\xd0,$g\xc1\xa1\x18\xf2w\xca\xe3\x88\x9c\x05\xc1]yz\xcf9\x11r#\x04\nY\xc5~Af\xfd\xe6\xf5\xdef`\xc7\xe7R\x1c\xb5f`#\x13\xf5|)\xd4\xc2\xe9z\xe0\xfe\xaa\x83\x8f\xe0iX\xd7\xd3\xd0\x17\xd95K8eu\xcd\x162\xa0\xb6\xe7\xb0\xba\xfe\x80\x14| \xf7,\x8a1k\xbf\x81vG\x071a\x84M\xd2\xfcP\xd1<\x14]\xce=\xb1\x93]\x86\xcdH\xd78_@\xc8WAx\xa1$f\xda\xec\x08\xb6\xefH\xac\x96\x0cV\x13?(\x0bF\xd1\xday\xc8^&#\x07\x87\x934\x9f0\xd4\xc3\xb5*r3\xf2\xd4\x83\x145\xe4\xa8\xc0\xd6-\xc4\x90\xb26\xd6\xa0i7C6t\xd0\x0d\xfd\xe7\x01\xc2\x11\xb8Z\x1a\xc7\x9d\x8e\xb1\xdf\xc9\n\xf6\x9f\x90\x07\"\x84+#N7\xc9po\x8b\x10\x8a\xcd\xe6\x0cO$\xa7WLW'\xb3\xf7\x9bA\xee0\xb2\xe1\x98\xd9\xe0\x87E\xc9i\xf4\xc1S\xbf\x00\xddN\x8e\\\xa75\x9b\xa7m\xaf\xf4h\xba^\xb6\x8f\xfd\xd1P\xfd\x84\xc4\"\xeb\xc0\x9e\x0eU\xe8\xa7Y\xbf4Nv\xc4F\xc0\x1fH.Dk\x01\xfc_\xc8-$'\xf1P\xefK\xfe\xe0x\xee?\xf2*\xcdjp\x10\xbd\xc6_\x92\x13\xd8]\x95<\x87C*iyq\x10\xcf9\x7f\x82\x83&Q\xcc\x9d.\x9bu\x87\x92\xa3\xb4<3x\x94\xa4\x05\xb0p;\xe0P\x9b)P8D@2\x9cO\xb7\x06Z\x95n\xa4(5\x02\xe0\xe5\xf4\xe0\x84\xda\xdaI\x89\xac\x1dQ\x8c\x1a|\x90\xa7\xd4\x03\xe3\xa6\xee8\xd1\x0fF\x8e\xb4\x87Q9\xe8\x14\x0e~C]\x7f\xa7\xb1\x86(\x91\x06!\xda\xd2\xc3\xc8\xe9r\xae\x17\xc3\x14\xbdM\x04:N]\x1e 2\x8b \x16tM\xcf(\x0cld\xe6\xd3\xc0\xa40\x0d\x0e\xb3\xe2\xc0\xcd\xd0\xec\xfd\x01+o'\x03\x80q,V$!-\xd6\xad\xebs\xd2\xe8A=],u\x007w\xd9\xea\xf3a\xdb\xcb\x0d\x12\x05\xeb\xd0\xb6C1e\xeb\xa1\x9fR\xectn\xd2/V+\xdbv\x12/\x91f+\xcaBt\xe8J}\x81\x93\x80\x90\xb2\xe8\x14\xb7'\xc0\xbd\xd3\xae\x01Q\x8a\xbb\xa3\x8f\x96\x84\xc6\xc8\x8d\xc1z \xe2O\xa3f\x14\xdeHX[\xc5H\xe5\xe0\x8b#\x91\x01\xd2\xa1j%\xdd\xd3\xc8[\xd0\x06Wam \x13\xea\xc1\x0c%\xa0V\xbe<\x08\x11_\xec\xa0\x97Y\xe6h\"\xe7\xceW\x0d\xa6Q4X\xc7\x0b\x1c\x8a\x1eU\xef:\x83\x84W\x0e\xc2\xb2 \x84D\xa5Q\xf4\xc5\xf0\x1a\x04\xb3B\x1aE\x8e\x8e\x1c\\\xf5\xa3\xe8\xbb\x83w\x0d\xb1\x14\xa1\xc6\x8cQ\xfaJvs\xb8\xa5W\xfd-\xdd\x1d\x91\xab\x10\xab\xa7\x11\x0b\x11m\x98q\xee\xf2\xc9\x94[\x96I\xb1\x94g\x90\xc2ccC\xcc\x17QZ:\x14\x9bG\x8e\xa8-\x01\x18\xef\x0ca}\xa4(\xc4%\xce\x87\x96\x1e\xaa\x80\x98\x86\x9e\xc1,j$\x8e\xbc\x94wh\x18\xac\xea~\x99e\x1f\x1d\xcaH\x13\xbfW\xe4BK\x7fl\xfcf{0\x01\xa2\xb6?V\xf4\xcc\xf4Y\x14W\xd8\xf9\xc2z\xa9\xaf\x8ec\x02A]\x9f\x1adH\xfdB\x06\xc2-}\xfah=\xa6\xb2@\x14\xd2\x84ll\xfb/\xd4\xd7\x8e\x07\xd2{\xcb\x0f05#\xf65\xcd \xac\xaf \x08>\x0d:N\xd38|1.\xf3`\xdd\xd1\xa7\x05S\xd8\xb3T\xff\x1c\xc1 c\x08L\xce\x19*\x8a\xe0\xa8\xb1\x15\x04\xe1t\xd3dJn\x04j\xfd\x12,v[\xdc\xc0\x11\xfe\xa2\xd3\x93\xf1E\xc9\x1fx \x16Bx\x80N8\xd2,\xf6W\xe4z\xf3vv\x9d\xe0\xaf\xc9\xc90\x0d\xf8k\xb7?\xbf\x16#=\xb5Jk\xb5\x9f\xa9D\xd8\xceW\xa0\xf74\xe7'D'\xc1\xe7\x11A\xac0k\xf2\xc5\x974\xcb\x18\x0d\xef\x0e=\x9f5JF\xd0\xef\xd70 \xd1\xb8\xdbQ\xdc\x06+\xdf\xc6^\xd8aA\xd2R2\xa5\x8b\"\x0f9\x1c|\xdcv\xf5gZx\xa3\x8b\x1d\xdf\x15\xe5\x07\xdb\xcepH\xa6K\x9c\x10^\xd7K\xe5\xe7\xb7\xed\x04\xc7\xe9r\xbd\xb5\xed\xf8&Y'\x92\x0eo\xfdD\x9fYe\xfe2\xc0\x99`\x88\xc1r\x10|\xfe\xaab\xffC\xfe5\xcd\x0e\x1c\x9d\x18\x99\xae\x14y\x8a\xc8t\x85\xb7\xb6\xed\xa4^\xda\x8a\xce\xb7N\xaa\x15\xbd\xc8e\x9e\xe8\xba{\xa7\x0d\x0c\xc1~\xeb\x8e\x9cz\x94@\x86(\xd4gC\xba\x9b\xeb\xf6\xb2\xa6I\xe2\x00\x18\xc2R\xb0\xc1\xad1\x92\x07SQ\xa3C\xb4\xee\xc5\x8b\x8e<\xaa\xc0\xc5\xb6\xef\x16[*\x98\xab\xba\xdej*\xeb\x82\x85\xa7\xeex\xc7%\x90\xc8\xb6\x13'D\x0dj\x8c(\xdc8\xf2\xbaIt\x19\x80l\x88o!\x0c\xa5\xc9\xe5c\x19\x8a\xcd\x1c\x9f\xfa\xbc\x15\xf4_\x02T\x1b\x1c\xbc\xbf\xd7`\n4\x07\x19\x12!\xe3I\xee\x97\xe1-\x0e\x91`)\xd0V\xab\x9eC\xbc\x82\xed\xe4\xc47$\xb4\xedx>\xc7 <%\xf39j\xe46h\xf0\x90\x85k\x19\x82\xaer\x8a\xb7\xc0\xacL\x1d\x15\x12R\xab\xe6\x876\x94\xddHt\x04y\xd9\x86Z\xdd\xd1\x8c)a\x8a;\xeeg\x1e\xb9\xebf\xbampV\x98\x14\xbd\xad'\xd5u\xb0\xba6\xa0IU)\n\x8dV\x98\x82\xb5 \xff9\xad\xc6o1\x12\xe3\x0dm{\x9a\xc2q\x12\xdc\x13\x80\x19\xf1)f\x12qy\xea\xd7A.\x0bp\xe4\xa9s<\x86\\Xw\xd5\xbchb\xa4\xcfw\x0b\xdd\xf6\xf0N\"\xa3\xdcX\xaf\xa7a\xd3b\xa0;\xf3\xee\x97W<\xe6e9j\x98\xeb\xfbV\xc9\x0fE\xf6\xc0-lEE\xce-l #\xc7\x12\x88c\"\xd1\x83\x85\xb0\xce\x1bY\x01\x16\x05!:%\xb6b\x9af\xbfW\xee\x16ll\xa1\\^Ti\xfc\xc1\x12\xc4\xb0HJ~8\x0c\xca\xeabA\x80Cb\xedy\x1e\x01\xa5\x8c\xc8\xe9P\xd1jl\xca\xc2\x06\xd3\xec\x91~8\x8c|\xe3\x0b1,cC.Dw\x9d\xb3Y\xad\xb6&\xe1\x94\xf3c\\\x08\xd4\xa1v=\x9bF\x80\xbcq4\xa3\x8e\xf9\x12\xd2\x97\x89%/(~\xd6\xdc\x8f\xfdU\x108g\xcd&\xb6\x9d\x8c\xdfK\xb5\xa6R\xe1\xd0\xd5'\xc4\xda]z\xe0\xc8k\x1f\x1d$\x07\x1d.\xd4\x82\xa9A\x8bw\xb1\x10\x82\xd1\x96S\x0f\x92\x88X\x0e\xe4\x86~\xec/\x83\x99% \xcf\n\xa0]\xc0\x85aW\xab\xbc\x13.\x91\x17[\xb4]j\x04\xd6\xa02r$\xea2\x0b\xb6\x08\x1eG\xd9\x11u\x87Y\x0b\xa4\x14G\xc8\x8d\x9a\x06s\x83\x8eF\x8b}\nv\x98bq\xf0\xf9\x1c\xd3n\x8ec\xffy\x80\xb7\x10\xbdu\x1d\xc9i%\x89\xe0\xe5\x05\xa5\x81\x07s\x96C\xb2m0\xf3W\xefi\xe0?\x0f4b\xc0\xcc\x7f\x0e\xef\x021 \xccaB\x82\x91;\x18\xf8\xe8Tq/r\xc7wl??I\xda\x1d\x0e\x81\xc2\xf4\x8cq\xb8\xf7F]d\xc61\x87\x13\xbb\xc7-\x1f\xb3\xd4^\xe2\xf0\xec\xc6\x08\x04\x97\xf1\xb5\xe2\xac\x90\x0dx]\x7f\x04^\xb8+h=8\xb9{\xd45 \x1b\x89\x994\xf0\xde\x98\x9b\xa6\xa0\xe9\x82\xeb\x81\x11\x87\xe2ix\xb9\xd7\xcd\xca;\xeb\xa2\xcb\xc1W#\xf5\x12\x05w\x80\xe7D\x03\xee|\x1e\xd7u\xa2!\xb6Mo\x1ai5\x0b'\xde7+\x90\xb1S\xb0\xbc\x95T\x88#|\xdb\x7f\xbd\xeb\xbd\xae\xb9R2\x87>\x0b\x06s!\x92\xba\xe90\xdf\xf4\x0e\x12}\xb8\x13$Tn\xa0\xe4|\x03\x89\x1c\xb78E0\x00\x0d\xb8g#\x11u\xe0\xc4\xd8\x1c\x8a\xb5\xfc\x06\xae\x12\x94z02\xb6O\xe4\xa7a\xb7\xa8\x86\xac\x0e\xc9\xb7wu\xad0\x14\xf9\x99\xa6\x95\xbb\xc2\xdb\"\x8b\xe4\x87\x1ec\xe9\xa9\x8aE\xae\xd9\xccUo\xcet\x89\x1aY\xbc\x97\x1d\xc2\xc6L\x97\xde|n\x14\x03U\x1fT\x8d\xea\xdai_ \x92\xedTd\xb7\xed^\xfe\x9be];\xdf\xf4f%\xc3~\x1e(\xddQU\xa6I\xc2U\xf8\x81\xd2\xb6\x1d\xc1\xa1\x0eR\x1d\x0bj\xb3\x84<\x9f\xa1E\x11\xc7m\n\x1a\x88\xe0\xdf:\xe8\x94\xa9\xf0\xb6\x83x\x97\xaf~x\xa3\xfc\xa6^\x174\xe2\x91\x85\xbf\xc5\xd3\x15\xc2t<\xbb\x8cv)\xb3\xe8yB\xcd`a\xba\xb5\xeb\xd4\x13\xdf\x88\xf1\x92\xde\xb6\xb2\xc2b\xb7\xcfx\x05G\xc6\x99\xac\xe2\xad j\xde\x81W\xef\xd2\x1d/\x8e\x95\xa3\xaaF\xae\x93\x8dD\xeb\xbc\xdc\xfb\xf3\xbcF\xd7\x11\xc2\xdf\xb4P\xc4\xe0\xe4h\x00Y\x00\x8f\x7f\x03g\xa3\x90\x1f\x0e\x03\x04\xa0\x85\ne3 \x91n\x06j\xdf\xe2\xd0\xdf\x06x\xba\x84\x16Z\xbf\xd3\xde\xedv\xa2\x82\xfe\xbd9\x91\xbc\xdfo\xba\x043V'\xf1\x9c\xee\x84R\x872F\xaesK\x18>\x8f\x8c\xaf\xd6\xe8\xd6\xd0e\x85\x10\xf8Li\xef[3Z\x06N\x848\xc4\x89\x17\xe9+5!e\x8b\xf5'\xd4\x19\x07r\x8f\xba\xb7\x9e\xee\x07rS\x8fA\xb8\x02\x81\xe0\xe2f-G\xbd\xaf^\xd1\x8a\x8em\xf7\xbe\xcaU\x06\"1\xdf\xa73\xe3\xfc\xaf\x03\xf6\xef\x1ctR\x1a)\xa9lo=DA\xa6\x85\xe8\x1f\xe4\xd4\xe0%>\xf5\xdc\xdat\xbb\xa7\xa6Q\x8c\xffB\xdd\x13(\xcf\xa0\xc5\xd3\xec\xbb\x05\xdc\x15\xd4\xc0/Y\xe1\xef\xd4\x18\x0e\xc4\x1c\x0d\xfe\xce\xbc\x0d\xf5\x8e\xf7\x11H\x1a;\xd3\xb6\x9cC\xdbc]\x19|\x8a\x89\xbe\x85\x84\xfaf\x0f\x02e\xc0\x7f\n\x89\xea\x01\x84\xbcf\xfdLD\xbb\x054xl\x02R8Sm\x83cG\xe8\xac|\x88\x0d\x16\x84\xa1\xa6\xa7<\x85\x89\xf3\xc3\xa0\xae\x9d\xde;X\xac\x87\x0d>\xf0\xeaL\xe3\xf3\xa0\xee\xca\x82\x02w\xfc\x83\x80\xac\x98\x18\xc5yp\xe1\xe0;\xf6Y@\xc2\xb5\x86~\x01\xeb\xc6\x15\x7fN\x8cP\xdb\xd5^u\x98\x19\xf7\\E\xf2\xd4)\xf6\xa3\xfeM_\xf1\xf0:\xd9\xce\xac\xd2\xa8\xcc\xe8t[R\xeeB\x19\x04\xc1\x154\xb2\xc1r\xeb\x8e\x0f\xfd\xbcX]\xb3\x11\xe5\x0d\xb3\xed6K\xe89\xea\xa87\xe12\xba\x13\xee\xf6\xbe\xe2\xaa\xd4\xa7|\xd1\xde\xce\x07a\xad\\}\xc9\xe3\xeb\xabMSo|\xfd\x1c\xa0g\xd7\xf8\x07r\xed\xf8/\xe7\xff\x13\xa0\xeb\xa4\xc3{?\xf6!\xcf\\\xab\xb3\xa8\xf2\xf2VA+\xa2\x15\x9d[\xb3.\xb2\xda\x0f\xd8\x9a?[YC\x07\xe6ph\xb2\x14\xa1\xf3S\xfa\x10\x9d\x04N\n\x89U\x95G\xa0\xe9!\\#\x16\xd3\xec\xa0_W\xae%\x08\x91|\x83\xe08\xb3pf\xc9\xd7Y\xe8~\xaf\x83\xe1x\xea4\xfaoo\x7f\xf8\x1e\x94LFH\xb57\x06|K\xea\x18\x92~\xc0\xb1\xb0\xe9\x9d\xbe]X\xf8\xc9\x9b\x85\xfa\x08\x07\\\xaf\x8d\xb7\x06G\xfd2=2\xf9\xc6\xa4\xd6!\xfa?zS\xbd\x1a\x96A\xa77\x8a\x8b\x92\xe8\x14\xff\xfbc\xd5\xbe\xeeW\xdb\xe0\x7f_\xac\xf6u\xaf\xda\xe1\x81\xe3Y#\xe7\x1b\\\x10\xe1\x84\xc4\xb6\x1d\x1b&y=\x90\x91t\xaa\xea\xf4\xc7\xc0u\xbc\x01\xc4\x13KS\x9f\xd88\x93\x9b\xbe\x96_\xb0\xa5fQ\xc0\xcaAp\xa2\xa7\xb0\xf5e16h\xe2\x87\xd2_F<,r\xba\xe3x \xf6Bm\xc8# \x9bR\xe5m\"\x89Hi\x93>G\x08\xff(\xddx\xfc(\x10\\\xc7k\x00\x8ca\x17\xb0`\xe35C\xa2\x1e\xce\xefr\x95\x87[ R\x1b\x0c\x81\x045)\xc6\xa2\x06\xb9\x7f\x1b\xde\xb9\xdd\x99Y\x9b]\x94\xe6\x17\xb1\x81\xd3\x99\xb2\x12Q\xf3\x87\xa9\x89\x99[O\xfeu/O\xf4\x91\x07\xf74Gt\x84(#\x06\x1d+\xd4,\x8b\xb0\"a1\x1c^`\xae\xe5\x89\x04|\xd9\xa6\x91m\x03\x02\x01\xfd6(q\x1c\x81\xb4\xba\xbe\x8e\xf2WLM?\xe4\xb0\xd6-\xee\x93\x0b\x11\x02\"n\xa7<\xc4'\xa9=\xbf\xacN\x1d*\xb3\x0c\xd4\xe8\xb3v\xa9\xc3@\xde\xb31D\x95c\xab$\xfb\xf9\\\x9f\xc3\x9d\xd9=\x80\x81>\xc5\x94\x88 \xc4\x02\x99\x9d\xc1\xfc\x8b\xd0\xd3k\xab\xb1m\x1b\x8b\x1dxG\x91\xea~dw\x9a\xa5a\x83\xad\xfbP\xa0\xf6\x02VkHm\xdb\\9\xb1E\xc1\xc2\xa0\x03\x8dn\xf3\x8c\xc0\xe6\xc77\xe0h%a\xc6i\xf9\xf7\x8f\xd6\xa3`RB<\x86\xdb\xf9\xcfu\xa0\xa6\xa7\xca\n\xf3\xbe\x82@\xd2*\xcd\x85\xaa\xcd\xb15a\x7f>\x8f\xea\x9a\xf7\xd4(1\xf6\xe3@l\x83\xcb\x0b\xa7\xf1&%\xaaw\xe6e-\n)\xc4~\x12`\xda\x03V@\x12\xa1\x84wA\x96f3\xac\xde\x00\x08\x0d\x9f\xd1\xad\x8307\x15\x0cJ\xc1\xf5wr\xed\xcf\xe6\x81'\xd8\xbb\xe8j\xb3\xa8\xd1&\x9a9\x9e\xeb\xf3\xaf\x02\xf8\xb0\x89f5\xbaVW\xf9\xe0\x9f\x88o\xbd+\xf6\x16\xb6~J\x93mea\xeb\x8b\xa2\xaa\x8a\x9d\x85\xad\xd7<\xae\xac\x00\xbf\x1d\x0f\xc1K\x05\xa2\xa1\xd8\xca\x8b\x9cK\xd5C\x08\xfb\xc9\x8a\xd2\xc3>\xa3\x1f,T\xd7S\xc3nh\x10%\x16\xce\xcf\xdfI6T\xc7\x1f\xa8!\"\x01zv\x9dv\x87\x8a\xad\x86^_?\xae+\xf8\xba\xa4 82 \xccH?>\xfc\xd9M\xe5\xd2\xd5\x01\x87\xe7w\x98\xeb\x00\xf4\xe1x\x00z\xe8\x90\x05\xde\x17\xfd8{*H>\xb6\xbap\xf9\xc3<*(\xbd\xbc \xdd\xec^\x88\xf0\x9d\x0c\xb8\xf2eV\xe4\\\x88/\xe2\x17\xac\xe7\xa7K4xkC\x9a\xe9\x18-\x98\xf5\xdc\x17\xf4\x95\x897O/\xae\xdbg\x0b\xdf-\xf2\x02\xaa\xffR\x96\"`N~\xa1\xe6\x9e_\x81RL\xfd\xc3\xbc\xabc}\xb7\x80\x809i\xfe\xc5\x91\xb1\x8c\x1f\x88U\xe4*\xc5J\xf3 \x85\"\xff$\xd7\xef\xef\xf8\x87k\xfc\xb3\\\xd7]q<\xf0z_\xa4y\xc5\xcbZ\x99o\xedx~Du\x98\xa5\xe1\xdd5\xfeEfT5\xc9+h\xe1oq\xacXv,\x85$\x02\xf7D\xfa\xef\x17\xc1\x15\\5\xb9p\x163T#\xd3\xd2\xfe\x7f\xcc\xa0\x90m\xea\xb3.\xd5\xb8_\xea\xdf\x8e\x14!\x14\x14g\xfdH?J\x04\xa0\xe8\xd4\x08\xfe\xfe\x81\xe7\x159\xc9\xbb\xf4\xdd\xd3\xb9aT/\x1c\xe0\xe0\xdaau\xe9\xb0\xa6\xff\xc0\xae\x95\xe8\x14.\xb6\xad\xc25&!\x0eI\xacS0'\x9dk\x1b\\9\x03wG;\xf2\x81\xe8;\xa4\x11vRR\xca\xce\x1dP]\x1bo\xd2\xb1?!\xa5\xaaR\xea\xeb\xf4\xdb\x98\xcaT\xe1\xad|J\xc8?\xe0\xd4[\xd4\xa3\x95\xc0<\x02k\xa7J\x9a\xd6\xcbObw\x83\x9b\x84\xbe\xe4\xd18jP\x9c\x89\xd8\x90\xc0 Y\xc8\x14\xa4-+\xc0\xb7C\x17\xc0\xdb\xf9\x1cm\xc9\xbf\xa4u2\xf3o\x03i\xb9Q\x90{\xb2\xf5W\x01\xde\x13g\xeb?W\xc1c\x95\xf7\xd4B\xbbO!\\\xc0\x95\xcb\xbao\xe0sB3\xbf\x00!\x1a\x17\xc4\xe1^\xb6\x10\x1cFB+\x10\"\xdcl\xc1\xd2<\x02\xf1\xb4\xae\x0b|\xb1\xec\x1d\xe9\x08\xb8\x18\xbf[\xe0\xa2L\x13\xa8\xe3^Jn\x11V\xeb\xe6\x86\xf2\xbar\xb9NX\xaf\xa0\xcb\xfbw\xac\x82U\xe1\x05?\x13)\x93r\x84\x05\xd28\xeci\xc8\xdd\xbdr\x0d[X\xa8\xc11\xc2\xce\x8e\xa4~!\xe6G=\x11?\xc0\xbbvt_\x16\xc7\xbc\"K\x9c ,t\xdc\xdb\xb6z\xe8\\H\xf68ASB\xa6\xab\xba>W]\xdb\xf6\x88:\xbb\xc0 h\xb21\xa8\xc5\xc5T\x8b_]\xe3\x9d@cj\n4\xa8\xf6\x13\x88\x9c\x11\x840\xf7v\xda\x1ab\xd0\xe5\xd9\x0c/\xf1\x1dr\x95c\xf2\x1d\xb0\xdd\xb0\"r\xdb\x89\x81\n\xb6~T\xab\xf4\xc7\xf6_'s\xdbvo7\xda\xb6\xb9\x93\x04\xef\xfc\x9f\x80-\xdc\xd7\xf9\xbf\x87\\t\xfa\x18\xe0F\xbf\x03\xb8\x12\x04\xa0\xc5-D\xe8\xe8\xdd\x9aa9\xef\xeb\xcdf\x81\xac\x99\x86\xa2\xcdf\xe1x\xee\xe2j#\x18\x02!\x9f8\xe2\xe9\x19\xb2\x80\xd1'\xbb\xfe\xf0\xe2\xf9\x1c\xdd\x91\x9d\x1f\x07x\xca\xa5C\xf3\xddB\xc3?Xb\xc8\x95\x85t\xb9\xf4[\xdb\x9en%\x18\xdf-Z(Fu\x1d\xd9\xb6\xccg\x18\x98[WW\x96\xbc\x0ew\xda\xa5\x03dk(\x89\xf1J\x80WWf\x006\xf39\xd6\x87M\x02\xd4\xe5S\x07\x99h\x9d\xd8\xf6t\xd7\xa9\x15\xb2E\xc5i\x19\x15\x8f\xb9\xc8\xae\x9fu\x81=n1\xa6\xda!\xb9y4\xe5P\\t9\xb4\xc8\x02\xbb\xb1i\xf5\xc0\xc5$\xcd')\xd2K\xdaJ\x08\xc5L\x00\x07@\xeat)x\xec\xbe&0\x05\xed\x83\xacQ7\x81\x0d\x01\xc3\x92\xd0iA\xb67\xc1\x86\x12\xf8\xa5\xe3\xb4\xd8\xf7\xd3b\x91\x17\x92\xe1\xb3m\xd0a\xff\x9c\xe6Q\xf1\xe8DH\x9a2\xa6\xa4\xe8a\xa9\xba\xbe\xc7j\xc5\xd3\xd9\xbd\xe4>\x12\xd3\x12\x7f\x9d\xac\x07){\x89\xf7\x13\x84\xb7$Yo !N4t\x03\x82\x88\x0b*h\x8ey\xfd\"\x98\x08BU\xb2_uMQ\x13\xb7a\x17\x9c\x84\xec\xe5=\x07\xb6=\x15\x0b\xfccY\xeci\x02\x01$\xdfV\xc5~/\x04@\xa4n\xed\x8coV^\xea\x16-\x96\x15C\xd9\x11G\xd2\x8b\xa4\xdb\x86\xa0\x9e\xf7e\xa1@\xd3\x93\x04[r\xe7Z\x08\xef\x04\xae\xd2\xd1[B\x84w\xe4\xce\xb6\x13\xff.0\xbe\x08N\xa0;\xc9s\x12\x88\xc5\xae\xd7\xbfW\xbaM\x956\x9e\x0cl\xe1y^\xbd\x92\xb3\xe0\x18QR\xe48\xee\xc5:\x8b\xc1\xaa\x1c?\xca\xfcb\xa8b\xcd\xff\xad\xa6\xcf\xb6\xbbg\xd5\xde^F\\\xd5`\x00\xd2\x9b\xd1\xc9\x08\xd5\xf5\xdd\xc0\x9c#\xf2\xefar{\xa0\x01\x11\xd1\"1`\x81x\xc5\x83<\x18\xc6g(\x80\xdccQ\x833\xf6I\xed\x82\xb6\x8a-\xea&\xa3\x81\xe3\x11`C\xfb\xe7#-\xe8\xc7\xe9\x93c\xd8\xe7\xf6\xacs\xcf\xadxn\xf5:\x83*`\xb0\xd42\x16b\xa0n\x07\x1f\xee\xad\xf6\xeb\xa9\x911\xb2\xc0O\x8bv\xbbBn\\\xa8xz'V\xef\x95\xeay]\xf7^Mo\x0e\xb5\x0fO\xdb\xb69\xc5U\x1d\xcc\\\xf8V0\xda-\xb0\xc7d\xeb3\x05\xec\xf4\x12\xb0\x9f\xe8\"<\x96b\xc7\xa8\x8e\xc5\x0b\x9e\xf1\x1d\x0e\xcdM\xd3\n$\x07\xb8\x8c]W\xf8\xedn\xc7\xa3\x94V|\xb4fgJ{\x88Q0\x99\xe6\xbb\xbe\n\xb0c\x0d \\\xb0j\xea\x07vK\x121m\xb4\xa2$\x81\x1f\xcc\x89\xe3\x0c\xa7;i\xb9\x10yN\xa6\x8a\xd7u\xa2\xfb\x8c\x144\xab\x81\xa5\x86\xe6\x9aC\x83j?qm6\x0d\xf6Y\xfd=\x85\xa5)\xb51N\x07u\xc6\xc3w\x8b}q\xa8\xf4\xba\xd9v\xff\xbd\xb7\x8e\x98v\x10\xab\xe7\xf4\xf2\xa9\x89\x0e3\xc6\xfa\xac\x0e\xd8}H\x02 @lk\xdb\xa9Aa\xc5\xbc\xcb(\xa0um\x81T-\xed\xf9\x05Xj\xc3\x8a)Q\xde\xc7$\xedy4\x88D\xc1\xc6\xa6m\\Y0#\x1a\xa9\x08\xb0}$\x83J/\xd7[\x88\x849C1a~\x18\xf4\xc4\xd6\x995\xb1p\xab\x84\x8c|\x0e'0>\x0fH\xdc\x13x\xbc\xdc\xe1\xd27M\xb9a\xa5\x82\xfc+\xefl\xf5I]e\x94\xb6W\x19!,+lc\xc4\xac\xa3\x96\xc3S\x11\xd6Nb\xd5\xdd\xb4\x9b\xed\xa8i\x17n\xfb\x82\x8dg\x87\xc6\xda\x12\xda\x94x\x8b\x1a\x84\x13P(\xee\x0f\xaeE\xb3\xea;\xfea\xc2\xa4\xfec\x12\xd2<\xe4\x99\x98\xb4IX\x95\x99\xf8\xd4\xdbY\x13\x00\xa9\x1f\xb7\xf4\xc0';^Q\x91\x01\xc2\xa0\xf0He\x00\xaeF$\xcb\xb5\x9dT\xe9\x8e\xbf\xad\xe8n?yH\xf9\xe3\xe4q\x9b\x86[K\xb3B\x13\x0b\xe18}\x92:o!\x9f\xf2\x0f\xeaYu/\xdc\n@\xda\xd2\xf2\xcb\"\xe2\x93;\xfeA\xfc\x17\xcf\x83*\x06\x9e\xd8\x86BO\xc7\x8d\x80\x86a_\xc0\x93\x0es\xb7\xd0\x95{\xdd\xa3\xcb\x16\xaa\x15\x84i\xd3`P\xf8\xf4\xfb%as\"\x7f\x0e\x930Ky^\xfd\xa2~\xff5)\xe2\xf8\xc0\xab_\xd4\xef\xbf&{\x9a\xf0_\xe0\xef\xbf&\x87\xb0\xe4<\xffE\xfd\xfekR\x15JK\xf3\xfbC2\xcf\"\x99\xda\x1f\xeb\xc18\xa1)\x1d/\x8a-T\xcf \"\xa7\xdeog\x8c\x08\x9c\xa0\x9c_\n\x1d.X\x11}\xc0\xaa\xce\xae\xb2\x99\x03\xae\xc2\x87\xb0,\xb2\xec5\x8f+\x08}\xc5{ K4\x97\xb9d\x19#\x97\x99\x00\x179\xc1\xb4\xb4\xb5\xff\xabW\xfb\xbbb\xdf\xab\x1c\xde\x07uwy\x8c\xf7%\xdc\xdb\x02K]\xd7\xed\xce\x8d\xeb\xba\x05\x80\x95\x1d{+\xf7\xb9\x1d{\x9f\xba\x9f\xd9\xb1\xf7\xdc]\xca\xe5\x8e\xd3\xa7\xa1\xd1\x115x\xef6\x1a\xa6\xe9'\xa30\n\x8e \xd5Jw\x0d\xd7>\x0f\xd6\x89\xb6\xfc1\x12IB~\xd6\n\x12y\x86j\xc0\xd9?\xfb_\xba}\xd1 \x1c\x110\x00\xdd\x1f\xb4[a\xb1?hS\x11\xf5\x05\xb9\xdd'L\x89)+\xc4\x82\xbc\x0e\xec>\x18h\xef#\x9f\x05\x98\xfaa@b?l\xcdvh\xc7\xb0\xebG\x92!\xfc\xa9\x8c\xa7,\xe1\xc9\xc0\xdbm\x9e\xf6\xa3\x19\x8e)Q>V^\xd2\xba<\xe2\x18\xb9\xb4\xc1\x8a\x14\xba\xa7\xac\xa0\x91{\xd2\x0c:\\\xe9$\x83\x9a\x9f\xce\xc4\xc4\xde\xa1\xc9\x94\x90\x7f;H\xba\x86J\xad\xae\xf27\x8feDs\xb8\x93\xbc;d4t\x10\x96\xd6\xf86\x98e\xc7\xf2\xf7\x1a\"fC\xa2\x80jG<\xfe~3\xc5\xb1\xb2\x1a\x0c\x84\xe8#\x0dY\xfa\xec\xc0\"\x92\xc8\xa9\xd0Z\xf0\x08\x85\x05\xcb\xaa\xe3\xea(\x06O)\xfd\xb5\x9b\xbd\xc85\xe8\x8ff\x8d\xc7\x0e\x9e\x8c\xea\xf4\xd2a\x8bZB>g<.J.\xefkwO&[\xd0\xab\xc8\xf0\xba\x97\xec\x81mS`l\xd2\x9cf\xea\x16xg\x90\xb2\x90\xad\x83\x8a\xbe-\x87\x9a\xa6\xc1\x87tw\xcczn\x1aJ3\xd69\xff)M\xa6\x01\xdd8\xc4R\xafIqzx\xabj\x80`\xcf\xbdV\xddS\xd3\xa0u\xe4\x0d\xf8s\x87\xeb{\x17\xdd3\xbd\xb0R'p\x84\xf9\xa8\x14\x02\xfe\xec\x03\x86\xab\x91\xd6\xb1\xad\xc2\xe5\xccxt\xd4RX\xcc\xda\x98\x01\xb1\x18\xfct\x05\x16\xb7\xe7\xb5\xf5 \xb4\x17\xb1Bf\xf6\x1cp \xa8\xba0\x0c\xbd \xd1A\x18@\xd6R\xa8\x0c\x12\xce\x87J\xdaxMm\x92\x81`\xcf?\xca\xe1t\x8b,\xd8S\xef\x7f\xdcg\nE\xc9\x161\x93Z\xe4\xd6(\x113eG\xda\xb2\x0fD\x0e@\xbf\xd6\xb5\x00\xd8GG2\xc2\xf2\xac\xb8\xc3\xd1d\xbaD\xa8\xa7\"\x91VD\xea\xcd41=\x1f\xa1\xfb\x0c\x8f\xc9\x19\x90\xfc\x11i\xc1}\x86\xfb\x00\xe0\x9e\x1d\xf6\x9dO\xfc\xfa\xd2,\xff\x8f\xf4\xe1\xe8\xd7x\x9e\xe2\xa0\x06\x0f\x98\xfa\xff\xa4\xd9\xf3Q\xe8\x86\x07\x95\x8e$\xe9\xa6\xc7\xa6\xe4?\xe9\xc3G\xa6\xd4\xec\xccX\xb6\x8f}S\xdey#\xbdn\xb4\x07\xd0 \x08\xae\x98\xef\xd2\xb5\xe0\xb9x\xe0\xa5%\xf9\xbd\x8c\xd3\x07\xae\x93\x8f\x95\x85\xd5\x81\x9f\xca\xae\xded\x01\xf5\xa2\x8a\xe8O\x80\xe9\xfb\x9b\xf4L\xce\x0e\xc8\xa9G$\x18\xd6\x9a\x1a\x97)\xde\xbd\x8fc\xd5\x81?\xec\x11\x891\x0d\xe6\x1b\x82\xe6\xb6\xa2\xa7\xa2\xe0p\xc1.\x97\xd6\xef\xe6\xc9\xb5\x0e\xe2L\x95\xa6\xa8\x95A\xcd\xc3\xbbq\xe71\xac\xca0\x84C0\xf9\x1e\x9e\xa3\x8a\xdd)\xa7X\xd2\xee\x96\xc4J\nk\x92\xc2\x11\x0e\xb7g\xd1\xdeN\x99\xc2\xe6\x0e\xc3-}\xea\xebE\xc0Nj=\x9cb\x16\x90\x13\x1c\x19\x0daRY\n\x0fxa5\xb1\xad%M\x04V\xd1u\x1d\x9d\x9f#Q\x0c7\x0eb3/v\xb8\xe0=g\x02Oku\xfc\xff\xbe\xe1\xf9j\xcd\xbd^\xf5\x1c\xb9\x10\xa8\xf7\x9c7\x8c\x07G\xdc\xda7\xe2\xccLH\x82,\x8d\"\xad\x04\x8bp\xa8l\x03\x8b|\x8c\x052\xad\x85\x8c\x85\x14`W\xc4\xf1\xb8M\x1f\x86\x0bj/\x90\x95\x16k\xb4W1\x9b\xa8\x04\xe7\xceP\xf9'g#2\xce \xa2\x16\x83\xcc\xac\x8553>\xb9\xdd'#\xce\x1a\x8eZ\xed\x16\xcc\xcb%\xd8\x83h\xd5&T\xc5\xb1\xc31\xc3\xd4\xe7\xa3\x11\xae\xe0\x8e\xf8\xe9\xaa\xaeG\x82\xe12y7\x85\x01#\xe0G\x08\x1a\xb3\x90<\xfb\x9d\x05\xea\x99S\xb6+t\xc6\xc5\x0f\xf9\xb1\x8b\xf5i\xbeS\xac\x92\xc8hV\xa7\\\xd5F-\xec*i\x19\xd7\x05\xb5\x1c\xab\x10\xd0\x81f\xfd\x95\x15\x15e\xe4\xfa\x85\xe3Mi\xc9i\xcd\xca:,\xb2\x9a\xef\x18\x8f\xeamY\xa7\xbb\xa4\x06\x19\xa2\xce\xd2\xfc\xae\xde\xf1\x8a\xd6{Z\xd2\x1dr\x1c\x7f\xf3\xe8\x063\x19\x0d\x0em\xaeo\xae\x93\x143\xa8L}\xb9\xc6\xa1x\xad\xed?y\x9b\xc7\xd9\xfa\x1aG\xb2)\xf7\x10\x96\xe9\xbe\xaa\x0f\xd5\x87\x8cC\xc5\xe8:\xc5\x9c\x91ke\xf7\xb39\\9\x9e\xeb\xbf'AM6\x87+m\x0e\xb4\x10\xd9bF\xae\xdf?\xab7\xd7\x8e\xe7\xde\xd2\x07Z\xf3pG\x91\xac\xf1:\xc5\x89\xf8\\\x95G\xbe\xb9v\x16W\xe8\x1aoE\xc2\xe6p\xf5b\xeax\xee\xc6\xff\xf2\xd5\xcbw/7~=\x9f\xa3Z$\x04\x9b@<\xdfl\x0eW\xcf\xae\x13\x9c2r\x92\xd7B\xb9\xfe\n[/$\\Nv\xc7\xacJ\xf7\x19'\x9f\xe8\xa7On,l\xbd\xb8\x96\xdfo\xac\x00W[N#Y\x08B\xe0\xca\xef\xea1\xc0a\x91\xb9\xfe\xf3\xf6\xe3\x8b\xb0\xc8\x92\xb28\xeee\xb6\xf6\xcd(Q\x95\xbd\x02\x15+\xa2\x0f\xaaRx4\xb3F\xae\xff\xe90\xeb\x8b\xaaT\xd9\xcb\x9b\x912\xad\xdc\xe7/\xb1ea\xcb\n\x9au\xca\x16\xc5\xbe\x82\x9e\x10\xf9\x9c\x169N\xd9\x02J\x8b\xa4*.\x8aJ<\xe8\x1e\xc33\x85\x8c\xf0]\xcc\x02\x94\xd8\xc2k\xd4\x99.\xdd\xb2\xbe\x0e\xce\x90-\xb1\x05\xdd\xb2PO\x82=\x8f@\xc4\xcc\xa84VUZH\xdew\xae\xd4S\x87/>\xbc\xa3 \x94\xb5\xa0\xc7\x16\xf2\x97\x01\xd8\x80\x186i\x03\xeb\xbc\xa1\x99\x9c*\x88\\\xdaYR\xdd\xf5\xaf\xf9\x00>\xc7\x91\xda\xb4K\xd7E\xcd\xackk\xa6\xa4&\xa3\xa6\x8cu~\xd6 S\x91\n\xa5\xe2\xb9=\xdd\xf24'\xe5\xaf\x02W\xcb}g-\x98\xb5\xee\xd4\xd4\x1aW@\xe1\xa8\xbbn$RJli\xd1O\xfd0\xc0\x9649\xf9\xea\x81f\x16\x9e\xb2\xba\x96\xa7Bl\xf8\x0d\x19\x01\xf3s6\xa2\xc7\x97\x86(\xeb\xb3\xdbX@E\xd67Hqb\xc3\xfe\x1b\xe1\x84\xc8\xfe0\x1c#|K\xe2\xce8E\xbb\x1bi\x1b\x81\xa4\xb3\xf9Z\xb7\x88\xffVED\x13#\xbd\xf5y0\x1c\xacI@\x19\xe6X\xe4\xf1\xc3\x005o\xfa\x9d\xda\x927F\xa7Rb\xc6\xbd\xd9\"\xfcF\xf51\x15|S;\x15E\xef\xee\x80q\x18\xbc\x04\x9a\xf2\xe6\x11\xe4\xd2\x05\x042\xd3\x01\xd7^f\x99w\x9e\xd4\xe6\xf6G|\xe6\xc0\xf9\xad\xb7\x8f\x18\xf2\xda J\x14\x1cC\x8d+_\xf6\x83\x0b\x0f\xc6C0\xaf\xbb\xeb\xd3B\xdb~\xa7\xafx\x03\x10\x05}\xb8\xb4\xb1l\xef\xd1s\x1d\xa3\x80\xba$M\x90\x12x\x97\xa7\xbd\xa6\xcd%\xe9\x87vF\x86o\x11\x98l^bS\xd4\xb1&\xed\x1bv\xc2z}\xc4\xee\x16\xfc<\x9d\xa1\x81h]\xf7\xa3\xa5\xa9`g\xa63l\x17U\xd9\xa1H\x1e.%\xa4`\xce\x16\xe1X\xfcR\xa4\xc2\xfc\xb5w\x9e\xea8}{\xe6\xc4~\x14\xe0\xc4\x8f\x02\x19\xc3\x18\x82\xf2A\x151\x89\xebZ\x96NH\x02\x8f\xdb\x8b\x15\xe5\xbd\x8a\x80S\x85\x1d\xb8\xed\xee\xd2\x81\x1eaK\x12?\x0ba\xed\x19t\xb3\xb4\xed\x1ds\x12\xefz\xd8\x9d]0\x88N\x88\xc3\x14\xbe\xe5\xd2\xd6M\x90\xc68cH\xac\xf8:i\xfb\x13\x8b\xaa4\xab\xbb5Y\xddi\xab\x03\xd9\xf65-=\"\x91\xe2-('\x16\x872\xf4\xf2\xc5\xbf\xf9\x03\xcd\xfeQf\"\x8f~\x96\x1f\x05\x97\xd9\xd5\"\x9a\xea\xf0u\x8ba\xb6\x0c[\x16\x1a\x04\x85\x95n\xffp 'w\xff\xbb\xc2\xb5\xe4\x93\xa5\xb9\x1b\x91\xa4\x1e-l\xd2[\xd7\x92\x0c\x85N} \x84\xde\x02zoiD\xf12\xcb\\\xcb@\x1a#G\x9f\x838\xce\xb4O\x9c\xe1\x1e\x83\\\x8a\xcb\xbc]\x05\xbc\x85\xa9&2\x86VH\xb6p\xdfh\xb7\xd3\xe5\xea\x8b\x85\xcf\x1d\xeeo\x03\xe4\xb3\xc0 \xbb#\x9a\x08\x872r{\x8f\x98\x19\xd1\x99\xa3\xd6\xc3\xf8\x9e\xe1\x92\xf5\x023\x1f\x98\xd3\x0b\xc5\x94;\xe1@\xe2dH\xa3\xd3w\x85#\xcd\xe5\x10\x1c\xc2&\\\x1f\xae|Y\xec\xf6\xc7\x8aGo\xab\x0f\x19\x87\x90\x1c\x17\xbf\xc2E\xdb\x08y\xd1B9\x1b\xbb\xd2\xfbX$\x1b\x0e\xc8m\x8c\xb0\x85\xc4n\x0e\xc2\xf1\xf0\x9abI\x193\x1c\x92\x92\xf9\xb4;)\x80;\xb1\xe5\x0d\xd2H99\xcb\xa8\x14\xe2\xcb=#\xce=\xab\xeb\xdc\xb1^\xa4qIw|\x02\x7fYQF\xbc$\x9f,?\x99\xc0\xfdf\xf0$/<\x13\x8f\xd77\x969\x0dlhI\x880#\xf7L\xa0\x8dA\x14p\xcc\x16\x8feZ I\x1c\x9cxU\xc0\x1c\xdd\xbd{\xd6\x0e\x10a\x18\x06 \x11\x0e\x1b1\xb6##\xd7\xefw\xb4L\xd2\xfc\x1a?0b\xfa\xa8\xbcw\xac\xd9\xdfg\x16r\xbc\xe9\xfe \xf9t\xfe\xeb\x7f\x05\xb3g\x16\xb6R\x0b\xe1G6\xe6/:\xb8\xa2\xc5\xf4\xc6X\x14{\x9e\xf3rx\x8bK/K\xc2\x07\x0b\xc9\xb0\x0c\x1aG/~2\xc0\xec\x89]\xd2\x7f\xc1\xa1I\xbbv$\xac\xebGP(\x85\xb6\xed$\x04 [Gh\x03\x0d\x9b\xc3P]\x87\xe0/#\xb2Xbm\x13\xe0Z/\xab\xcd\xc0\x9b%\x97M\xc1\xbc#\xfc\xa0pY\x82l\xfb\xc8\xf4\x15Z\x00\xbb\xdb\x05@\x00\xe6d\xbb\xd8\xa5\xf9\xcf\xf0\x12\x8b\x17\xfa$_\xbat#U\x97# \x16\xfd~T9eZd\x96\xe1\xd8(\x15#\xc3\xce=\xf1\x92\x99e\xb9\xc9\xf9\xad\xaer\x82\xc6C\xd2M\xa8\xbei\xce\x8c*%\xb2:\xfa\x890t\xe1@\xb7i\x86\x0e\xfb\xa0\x8a \xd9\x88\xb1\xec\xb8w>\x8e/|\x8007r\xd6\xd1I=,\x18\x0d\xef\x92\xb28\xe6\xd1\x97Y\xba'\x96\xda/sV#_1\x9f\x07\xb3\x10\x0f\xabV\x0fQG\x16\xff\xda\xe7'\xc8K\xa5\xa7`\xdd}\x88\x1e\\C\xbe\xa3O\xce\x12G\xfe*\x98;!\xb8L\xcc\x9cH:\xf7\xef\x9f,\xe4\xb2\xae\xceo\x98i,\xa6\xf9\xf6\x98\x84\xe0\xd9\xea)Ra\xb9\x9a\xd4X\xc8\xfb\xcc\xb5\xe4m\xb8\x10\xb6i\x051\xc2\x97\xeb\xcfn\xe2u<#\xcf\x91%\xa1\\\x9d=:\xc9\xac\x0d\xb5\x13\xce~\x02\x17\xfd%\xdc\xc3\x12yN[\xa9\xce;\xef\xc2\xf2\xa8\xadj\xf5\xca\xe8\xba\xa7\xe7\xf9UG!\xfb\xcc\xfaY^\xd7+\x8b!\xd7\xec\xc5X\xcd]\xe2\xf4\xac\xd7\x1f\xad\xb9=\xc5\xebf\xf4\xdb\xc1*\x89\x9c\xc4\x9c1\xba\x90\xdeCP\x93\xab\xdf\xbe\x01\xc0\xc41yTG\x8bVG\xe3z\xf1\x8aZ\xf4n \xf9<\x06\x86cyCx]K\xa4\xae.~#\x8a\xfb\x8c\x11v\x967\xc6W8-\xd1[\\p\x94\x0f\xdda\x9c\xde\xe9\xeb\x88$\xb6\xed\xdc-\xceh\x89\x83\xea\x9a\x83\xda\xb1\xab\x80\x13\x03\xfdqT\xd7\xcb6\xb8\xe0L\xc3\x97\xe0F\xc7\xa0 G8F3\x01\x96\xdd\x0c\xfemh \xa1\x0e\x89\xe0\xfe\xb5%0\xd0j+m\xd5U3\x11\x1c\x10\xe1Hv\xca\xb6!T\x95\ne\x13a\xab\xc8\xa2V\xc8\x82\xe0\xa8\x12\xa9\xab4\xcc<\xc8_\xd7\x9d\xdcT\xd7\xce \x17i/S\x1e|\xb0\xed\xb7\xd2\x8bX\xb5\xd9\x1au\x9a\xcd\xe2\x8a9Q{X\x8f\x90\x0c\xf4\xfa\x16\x82\x86\xea\x88T\xa1m\xf3\xba\x96\xf6\x14\x83\xd2\xdc\x0b\x95\xc8\x18\x19\xf2\"R7\xa5&\xe0\xe3xa&\x98m\xb7\xc3:\xeb\xb95\x92:2t\xe6\xa9\xf9\xb1\\Y\x17\xea\xdfM\xa6\xcf\xfe\x0f\x07\xe5\xccT\x08\xac[}pO\xe7!\x85\xe10]\x99/\x00\x88Z*s+\xff\xaa\xd8\xa7\xd6\xcar\xc3\xa6i\x1a\x1c\x1e\x0e\xdf\x1fw\x8c\x97\xee),\xb2\xe3.\x07?Sw\xba\xc4q\x9ae?\xa8\xb6\xc4k\xc6\x9f\xfeZ\x16\x8f\xfa\xf9\xed\xb6L\xf3;x\xebP\xfft\x89\xb34\xe7\xdf\xb4oEW\x81\xe4\x13\xe0a\xbf\xa59\\\xde\xf8\x98F\xc5#<\xfd\xfa-\\\x83'\x9e\x8ab\x07\x1eL\xe1\x01L\xed\x0f\xee\xc9\x8a\x05\xf8\x0b\xd0>\x1c`'X\x0d\x86Y\x1c9\x9a\x97\xd6\x9a\x9f\x0e\xec\x17\xfe{\xf0N\xb5\x14a\x9c\xe2\x0fc\x03\xa7Ca\x92ID\x01\x9d\xf2\xb7\x01\x04\xde7\xde\xc9\xd7\x0c\xd4R\x02\xc1\xc0\x07\xe9\xb4\xc6\xe0\x06\xac\xf6u\x1bt>\xb1\xa1\x97\xd8\xb6\x95\xf0\xcaJ\xf3IbD\xbct8IT\x88\xc2\xe9\nG\x08y\xdcM}\x16\xb8N\xdc\x86\xbf5\"\xe2\x92\x18\x10\xcf\x17\x8af\x85H\x1al:\xdc_\x05\xb3\x15\xba\xe2\xfe\xf3`f\xe0\x11\x8d\xef\x84\xdc\x1a\x13+\x07\x00\xb0\x10\x96'!\xa1m\x87\x8ah\xe8OS\xf0\x02\x84b\x12Z\xe4\xf0\xc3\x19\x01\xa2w.> x\x96{} 6jm\x80\x8eN|\xb2 ^\x95\xcf\x02b\xa5\xf9\x96\x97)\x18h\xd8\xb6u\x18\xcc\x07\x81S\xeaD\x85\xe1\x15\x8b\xdc\x96\x0c[Y\x17IX\x1e\x01\x88\xcb+\xfc\x1f\xac\xab\x82\x84?\xb2\xba\xfd%5Wr\x89\xbb\xfe\xb6\x912\x15)\x89\x10`\xabrG3\x15E\x13x\x96W\xf2\xce\xaaW\x0c\x88\x81\xa5l\x88B\xcf\x89\xfbtA\x1a\xe4.\xa5]\xce\xf7\xc7\x1d/\xd3\xd0\x89\x91\x17\xd7\xf5\xd2\xe5\xc8\xe5\x866\xd3\xb7\xa4HjaE9\x833\xad\xa31<2\xc43\xa6)u\xe8\xfd\xaa\x08\xdcy\xc8?d\xdb\xd2\xb9\xc9\xa0\xc8\x9e\xe4\xb6\x1d\x8a\xbf4n\x92i\xab\xfbV\xcfE\x83\xdc\xf6\xb9u\xc4;\x8c\xf4\x03\x16\x97D\xb6\x0d\x94]/(0s!\x8eG\x98#w\xa9\x1d\x00\xf4,\xf4\xe4\x98\x0f\xcc\xb9[\x8c\x88t\x839\xd4\xa0\xd5\x8d\xf8\xd4r\xd2i.\x90\xe4\\3\xd4O\x0c\xfbT\xb3_2\xf2b`\x04Pk\xfdn\xa4\xc4eYXKZ\x96\x85\x95\xf4\xa58\xa7s\xf5q\xbb\x90t&\x96R\xdaH\xb8\xe6\xd1\x87f\x04\xa4\x91\xd5\xa9\x11\x08\xe1,\xd4\xb6\x17\x1an\xd6\xae\x1f\x06\xeb\xcf\x94\x11\x16\xf7\xe9\xec'?\nD\xf5\xb1\x1f\x05u\x1d\xfb\xd1\xfc9\xfc.\x0d\xf1\xa6\xc1G\xd6\xdd\xee\xea\x0cz&\xf66\xf9+CC\xc3\x8d\xe1\x8e\xbe|\xae9\xd0\x05\x8a\x81\x08\xd2\xadB\xf9\xeb\x00\xf1:x\x81\xe4\x03y\x17\xca\x8b+\x12\x1f\xfb\xccO\x82\xa0\x05\x10\xf1&Q\xb0\x1eJ\xac\xf9\xaf\x16Y\x87\x9e\xa1 \xc4p\xe0\xa0\xd1k\x83\xe9h\x88b\xd4\xe0\xc3\xb6x\x1cQ\x1e\xfcM\x19-\xc0\xd9\xda6\x8d\xc6\x14\x0c*\x0fjpU$I6\x16\x12\xd5bE\x91q\x9a\x9b!\xa7U\xd0i\xd1\xb0\xbaMj!\x1a\xd0\xcfCk\x96\xb7\xb2\x11O^\x9c\x8dt9\xfd*\x8b6\xfd\x8b_\xbe\xebI\x10\xa6}\xdehYiK\x92Gu\x13\x01x'\xcbJ\xf2\x88D\xf2\xf1\x08\x97\xb8k(V\x041\x0c<\xc1\xc2 r\xd8\xe0\xf0X\x9e\x1f\x00\xcb\xb1\xed%\xd4\xb7\x9d\xec|\xc6\x05W\x92\xf0J\x9a\xc5*{#\xb3L\x17\xa4\xa7\xfd\xde\xe0\xf28rC\x15\x0e\x7f\xaf1\x99R\x1c\x08#\xe6\\,\xa2c \x1e\x7f^\xaef\xcd7f0\xd0F$\xc3\xdcW\x14/\xf1j\xfc\x1br\xb5\xe5I\xf1H\x1c=\x95\xf3n\xca\xd1\x15\x9buo\xfdJ\x0e\x15\xdf\xab\x83x3\xa9;\x90\x94\x91]t\xfd\xeaD\x15b\xd2\x1dx\xe5\x85m\xd4\xf0K3\xd9~\x97W:5\xf8\x0cP\x0dx4\xbfa\xb3>rj\x9d\x05\x06\xd4R-\xc80\xde\x84\xe8\xb5O\xe5\x92\xd4\xb5|\xd7R\x85d\xc2\xcc4\x9d\xd3s\x98FLr\xdc2\x1d\x0b\xe9I\x88\"\xf4X\x15\x82\xebb\x1es\x97\xc8\xed73\xa4\x9fp\xb8\xf8\x04\xd3\xd9\xd6\x7f\x96\x02\xb7\xc3\xf7z\xe7\x8ct\xcf\xe0\x96T\xb9>S\xa4\x12Q\x87'{\xdd\x17|\xf9\xe3\x0cn\xfd\xac\x86\xbd\x06\xa6\xfd\xb1i\x17F\xad_\x1b\xd7\x82\x8c$\xbf\xe6q\x05\xee\x8b\xbd\xd1\xaa\x1e\x9b\"\x00$\xf4\x0c\x91\xc6\x1a\xd7\xbe\xaf\x80CN\x82r\xd3r\xcc6\x826\x18\xb0\xcb\xc8\xb7\xc5\xe7s\xd0\x1d\x85\xc5\xc1\xa1W\xf0\xf8\xe3\xb7\xe8\xfa9\xd4\x1c?\x913\xa0\xc3\xedJ\x90\x93\xba\xb5\x83\xe17\x0c\x7f\xcfd\x90\\\x89\xf7k\x81\x92k\x81\x88\xe1\x96\x8e\xa1\xe6\xcfs\x95\xf2\xafFZQ(\xcf\xf5:m\xe1\x8f\x8c\\w\x11\xa7\x9f]\xe3\xbf3\xe2\xff\x93\x05\xf8'FN\xd6\x95\xe5\xfa}\xea\xfb`\xdcY#\x8dk\x05\xe2\x96'\x8f\x11 \x15\xbe\xe4\xe4\x87V{\x86c\xc1\xcbr\xff\xd3`\x80/i\x87/qB\x06\x9f\xa42m\n\x82\xcd,B\xb6\xad+\x94\xe0\x1f*\xf8A\x08o\xc9\n\xa7\xe49P\xfa\xc4\xb6\x13\xff\xd3@\x14S\xa6\xcf\xe2\x15s\x81\xf9A\xbf1\x8b\xeaz\xb5\x8e\x8a\xc9\x96l\xeb\xdaZ|n\xe1\xe4\x9al\xb1\x86K]/Nf1R\xba\xc4\xad\x10\xc7\xb6zl\xd7\x91\xbaJ|k\xdb\xf3y\xda\xdd\xf4\xa0\x0e\x17%\x11\x99%u-\xdaZ\xe2P\x11\x0b\x1c\x02\x01\x11\x02\x99\x97\xccz\x82\x99;\x13\x7f\x11\x0e\x9b\xc08\xdf|\xcb:zo\\Of\x10\x95\xd7\xda\x1b\xafA\xf8\xb5@\x0c\xe0\xd9\xdfiz\xde\xf5\xbdQ${\xa7\x0e\xa3\xa8\xd4h3\xa9cT\xcc\x1cy>g($\x82\x9b\xc3\xdc\xd7\xea\xc0Y\x18\x10\xeew\x8a\xbd0 \xb4\x15\x9c\x84p\xb2P2>\xd1\x9a|\xc1Yu\xdd\xf8G\xab\xb2k9M\xcc\x89\xf3\x13\x03\xd1\xc9\x0f\x90\x8e\xc2\xf2\x13\xf3\xad++@pGmg\xce\xb0N@\xf99\x93W\xc8p?\x0e$\xf6\x0f\x05c\xd5j\xd5\x0cM\xee?\xc7O\x86u8Xie\xb4#\x10[U\x8bu\xfb\x9er\xe0\xad`\x0e\xef\xdb\xdb\x1a\xac\xf8Il4\x88\x0c\x0e\x9b\xa5\xae\xc1\x88\xa6\x7fM\x02\xdc\xf5\x80%\x82\xdf.\x8e9|\x8c\xc0DE\xbf\xc0\xbdm[\xe3\x02\x03l\xbe\x98q\xe6\xbb2u\x9d\n.\x0bw)\xb3\x19\xce\x16\xf2\xc6U\x13\x1c\xc6\xd2\xbaB\xf39\xeen\x7f\x80\xae\xb6\xd6`\xdb\xfe\x8d\np\x0f\xdb\x99\xf1\xa3\x16\x19\xd3|\xc2\xeaZ\xc9\x8dp\xa0a\xdbN\xb8(\x1ex\x19g\xc5#\xf1\x8b\xf6\x19w\x8f\xbf\x18\xcf\xff\n\xf0\xedH\x80x|GZe\xdd\xad\xd7N\xbd\xa9]\xac\xebJ\x08\xe2\xad\xa2\xcf\xbd\xc5J\x94\x12\x85\xee\xb4.\xce\x14\xef\xa4\xdeH\xf4\xb1\xe8t\x8d=\xf1\x0b\x0cIu\xdf _;\x96\xf6\x08dlj\x8d|]y\x7f\x19\x98\xa36\xbf\xac\xcc/\xff2\xbf<\x0f\x1a\xa5kT\xd7\x87\x81\x86\x99\x89]\xf8}\xeb\x90\x01z-u\x18\x0f\x9f\x00\xbdY\x92\x0e\x80\xc2\x00sB\x88\xb3\xf7D\xaf\xb9\xe5Z\x12d\xa1\x9c|\x967\x92N\xef\x8d\xc0#\xf7~\xf4\xffg\xee\xdb\x9e\xdc\xb6\xd1=\xdf\xcf_!\xe1xY\x84\x85V\xab\x9d\xd4\xd6Y*07q\x9c\xcb\x1c{\x92\x8c\x9d\x9d\xcc\xc8\x9c\x14\x08\x82\x12\xd5jJ\x16%u;M\xfd\xef[\xf8>\xdcH\xb1\x9d=\x0f[u\x1e\xbaE\x82 \x88;\xbe\xfb/\xa3r[\x1f\xaa\xfa\xa8\xe6;>\x9e\x9d\xef\x16E\xc6?F\xd1G\xe0\xe5\xac\x97\xa29b\xc67s\xe7Z\xc3\xdf\xe4m\xabOh&y\xa0\x14\\\xe3\xb8\xe9\xb3z\xb2v\x0c\xd0\x15\xd2F\xd7>E\x13\x06%\xbf\xb9*@\xe5R\xf1\xf5\xf4\xa0\xc9(o}m\x84\x116}\xb1\xcc\xa6\xfbc\x1d\x97\x1e\xf0#\x04\xf0\x15l\xb1f%\xd3\xa3p\xf3\xb2\x8c\xa2*\x95I\xbc\xea\x80\x94\xe8,\x19\xc5HHk\xbe\x9aZ\xa8\x10\x8cy(L\\\xc3\xd0\xb33\xa7l\xbb;\x04i\xe3\x19{4\x1eD\xaf\x81\x16N\x1e\xcfg&\xa9\x0b\x12\xe5Q\x1f\x93\xdc%\xfe\x84\\[\"\x99\xeb\x9a\xc4u\x9e\xed\x8fD\xba\xaea\xd8\xe2d\x91\xb1\x80\xc0\xf4$u\xa0\xfb3R\x83X\xb0\xb5f\x0e\x1b\x10\x07\xe0\xe5\xb4SQ 4\xcc\x03$\xe3]?\xba\x9e\x07\xef\xaa\x82\xb2\x02\xc3\xf3\x04\x1f\xb4D\xef\x8c\x15\xa8:\xb11\xc0\x01\x97\x8fg\xce:\xdd\x0d\xa3\xc4a\xbc <\x99/\x87\x89\xe5\x19MV\x06\xea\xb8\x93jXT}T\xae1J\x1d|\xeb\xefy|;\xd8n\xda%\x9d~\xc9\x1d\xed\xb4f\x82\xd9w< \xe5\x98T0\xda\xbde\xbf\xe6lM\xbb\xa8\xaf\xf63\xc0\xb7GQxk\xe3\xa7\xc3+\xe5\x03\x04\x9d\xda\xc7n\xfaT\xcc\xce5QWw\xc9\x9a!(\x90)\x01n4\xe1\x01\x0dClg\xf3\xc8\xde\x1b\xf0e\x93\xaa\xafm\xa3-\xac\xaf\x81\x896\xa9\xfa\xda\xadw\x93\x86w\xf4\\O\xbf\xae\xab;\x98p\xde\x9d\xf9\xb7\x9c=\xc2@]\xc4\x8f\xe8Bx\xd3\xd4\xc0\xfa \xb9\x9a\x08ME:\xe1\xe8<4\xcf\x9dA\xb4\xa1\x9e\xdb\xaa\xe4z\xd7c\x7f\x83\x8d\x0d\xfe\x03w\x02W\x0e\x18+G\x07\xaa\xc1\x80\x96y\xfaK\xee2\n\x9a\xfc\x92\xe3\xf4\x15\x06o\xaa\xd9)U\\\x84M\xc3E#\xa2h\x00\x8c.\\\xf8\x82&\x8f\xb6G\x13\xd9\xb6c\x19E9\xea\x15\x82>\x88\"\xe1\x97\xaf`\xb8\xac\x12\xcc\x9a\x9b(\xd0\xdf\xf95\x14Ey\x80+o_\xe40Q\xb6e\x99\xce\x12\xabir\xb5\xf2\xd9R\x7f\x99\xf8K}R \x97\xac\x9b\xdb\xa4\xc1\xf5\xc2\xe7\xca\x92 \xdd\xbb\xab\x9a :\x85\xa5\xe7\xcd\x05\xe8P\xc0f\xd3\xdc#Q_L\xb7\x9b\x82\x17n\xa21\x7f\xd9\x05F\x0b\xc3a\xebwh\x14\xc1\xaf\x97W\xe9\xc2\xa0\xe8\x0b\xb0,\x93N\xcfz\x0f\xea\x08\xc1KQ\xa8\xf7\xdb\x01\xcdV(\xcb3.Z\xef(\x90\xbeN\xef\xcbf\x96\x0e\xd2\x14\x86>\x00a\xe2\xab\xd8\xa9\x91\xf33C]\xca\x99\x99gO\xe9\xd0x\x9f\xde\x13\x08\x17\x07\x9d\x8b\xf0\x03=\xa88<,\x7f3\"\xed\xee,c%\x9d\xc7\xca\xc6U\x80\x0c\xa4\xac\xea\xaaY\x81\xea(\x87\x18d\x80\x89\xee\xec@\xa6\xf8\x9c/\x99j\xdb\xd2\x0f\xd9M\x80\xa6\xb84\xd2l\xecY\x93\x89-i\x7f_\xef\xac\x8a\x0by\x1d\xc7\x10isK\xa6 \"]\x1eKW\x9bA\xb8/\x8c\x91\x13B~\xe5Q\x04x\xec7F\x8ay\x01R6\x1c;\x07+\x01\xd6-F\xde\x16E]p0\xe8y\xd8c\x1b\x08\x14\xe1D\xc2x -\xc1oV\xff\x87\xaaGQ\x11/\xd1G\xcfbT@\x8c\x88\xe5e\xc6\x9f\x9d\xc5\x8a\x7f \x8f4\xef\x80\x7fu5\xa7\xa5~Eo\xe9&\xa24\x1a\xc1@M\xe1\x11\xd4u\xcc\xb9\xa6\xe6 A\xcf.\x1cT f}7\xac\xb4\x08\x1f\x8a\xddP:\x8fs\xbd\xdb\xd0'`\xe4p\xec\x07\x05oc\x1b\xbd\xdb\xe2\xab}\xaeW\x99\x0c{KSf\x0b\xdb\xb3$c*\xb8\xc5\x8e\xce\xba=]\xa4\x85?\xfb\x81r\xb5\xb3\x12\x10\xd2\xc3j\xc3\xf0b\x18^\xe8Y\xfc\x0d\\\x8a\xc6z\x82\xf8n\xcd\xb1[s\xecV\xe3\xd5\xaa{3\xcf\xdc\\\x17`\x0b\x93\x87\xbd\xa9Kq=\x99CO\xa2lg6_\xbe\xcc\xc1\x97\xb1X\xe4Y\x14\xe9\xff\xa6\xb2\x9d\x9b`w\xb2\x13\xde6\xca\x81_\xa2\xde\xd8\xb0\x9b\x0c\x19J\x86\xacf_{|2\x88\x82e\xbd\xc8\xb3\xb9\xf9\x0d\x8f\xa3\x8en\x08\x05\xe2m;\xa4\xb0\x92\xc3\x16\xe6\xb8\xc0\xedF\xf6\x1e\x1c\xfaf\x94a\xc1\xa1\xd7N\xb3\xa9\n\xf5\xed\xf6\xbeN\xde\xe7\x86 \xa6\x0c\x12\x7f\xddA\x12\xd4\xdf$\xbdGU\x9aN6\xcd\xa4L\xef\xbc?\xd6\xde\xe4\x06\xcb8C\xfaO\xc7C\xf0\x00J\xc2\x07\xa6 \xff\xcc\x14w\xfes\xe7\x9e\xcbm\xdd\xb62\xb7\x9b44\x0fg#_d\x96\xd4\x92\xb7\xfdm\x17\\[\x99tS\x17\xe6\x84\x17\x13\xce\xf3\xaf\xa4\x9bw\x93 \x15\\B \xe3\xd8\xf8B\xe0\xc2\x95nZ]]\xb1\x1b:\x97Nhd\xe4\xd3\x00\x12\xe1\x05\x91\x01\xe5\xd7\x0bG\x88\x95\xb0t\x8a\xfe\x8eU6\x88\xfd!\xa6\x89\xcf\xa1\x8b4\x05AD\xc8\x93\xd8\xf0\x9b/\x98\xcf\x1d\xb6\xf4m\xde\xb6\xf1\xdb\x9c7\xea\xf0\xa3\xc9\x1c\xbb.\xe9\x16Bm\xa9\xba\xd6a\x19`\xb9\xe2\xde~\x9bS\xf6\x16}qm~\xa0\x1d\xf8c\xb3\xd9\xde'\xffs6c\xa5h\x0e\xc9\x8b\xd9\xccG\x88\xfar63gv\xa16\xe2\xd3S \x89\xba\xb8\x0e\x9d\"\xb2\xb6\x15\xe0\xfbk\x80ZYpH\x04\x06\x12\xc1Q\x15\xc8\x82s&\x00\xacp\xa8=6\x8fqn\xefO\x8e'\xc1\x0fY~\xf9\x08Cx\x81u_\xfey\x1bu\xd4\xd1\x11J\xe7&B\x93\x0f\xc9la\x0e\x7f\xaa9A0\x81\x93\xd8\x1c\x15\xbb\xd5\x949\xc6\xf3Q\x05\x97&\xce\x1d \x1bZ \x02\xbd\xb3B\xb6o]\x82t\x0f\xd9g\xdab\xbe\xc1\xc9\x81\xd8\xe0\x9b\x06\xc5\x91\xddN\xe1\xe2\xff\xd8\xe7\xdc\xd5\xc8b\x1e\xfe#g\xff\xcc\xd9\xb3\x9c\x1b\xa06q8\x98\xf0r\xf3\x0eu\xa6\xd3?k\xa3P\xc3\xabO\x1b\x02\xf8\xf0UC\x87\xdb\xe0AV\x071\xaf\x823\xb2\x83S|Q\xaf\xae\x94\xc4Kw\xe7\xdeb\xaeDC\xb92\x8a^\x80\x12\xc5\xd6\xc1l\xc8\x9d@^\x9c\xf3_\xd3\x1a\xf8Pk\xf7\x00\x11\x1bJ\xc4\x85q\xb1\x81\xda6\xcey\xde\x8b!\xa3\xb9w\xe8PgC\x15w\xf0\xf0\xf4a`]\xa8\xd2\x7f\xe6\xc9?\xf2\xd0pJ\xa6\x85\xb7\xb1*\xac\xc6\x94\xc7\x8a\x17S\x0b\xb0LS\x85p\xffeU\x17\xf0-\xd4R\x19K]\xf4n\x02\xd3(\xf36\x16\xda\xb8BC[\xbc\xc2\xd9\x9e\x99\x82E\x17\xdf3grB\xf4\x021f;\x9d\xf1\x81\xda\xfc\xdb\x13\xe3\xdc\x11\xda\xf1\x19+y\xaei_\x8b9gp\xbd{`\xfa\x06\xd6\x9c\x97\x0b5\x99dTw\xa6\x1e\x86\xef\xaa\x07`&%{\xa2/!\x9a\x0b\x08\xef\xc67\x88G\xd2\x0d\x9c&5\x0f`G%\xc1\x98\xe0==\xaa\xb12\x1d\x87\xeb'\x8a\xcc\xaaBt\xe7n\xdc:\xb3\x16-Y`\x16\x99\xb3y\x18\x84Z5\xf0\xd8v\xf9\xa2\xb7\x06\xd8d\xfd3\xef+vY\x08i\x8f1,\xd3^\xf7K\x9a\xf4\xbe#\x19\xf8D\xba\x98\xc6\x97\xfd\x85\x88\xb8f\x1c\xae?\xdcO\xae\x97t\x90\xcey\x96\x1b\x13@7\xcf\xe6\x90\xd4\xe5\xbf\xbd\x19\xa2\xe3\x825Y\x8co3|A9b_\x9aw\xd2\xde\xb2Ap0S\xbfq\x05\xee\x84\x0b?i\xe1\x18\xcc\xfa\x1b\x98y\x9a<\x92r\xbb' Y\x1d\xee6\xdfm\xf7\x84\x11\xb9\x11MC\x12\xfc\xd5\x13\x8d 2\xcb\xd3\xdb\x1d[\x0eoxK\xdc\xf0\x96\xb8\xe1-\xed\x86W\xf2\x1bt\x02\x1dw\xb62\xd04\xe4\xc1j\xd3#\x9f\x03\x8a\xb4\xb7\xb5\x01\xab\x7fo\x1a\xa6\xfc\xa6\xa2\xc2M\xa5\xe0\xaa\xb3\xa9\x14\x89\x00\x83\xd6D\xf9\xadM\xf9\xadMg\xf7[\x1bf\xc6F\xdb\xc5*r\xb4\xa0\xbe0@qKm%\x9ap\xa9\x89\x1c\x8cr m\xdb\xdf\xadi\x9e\xd7\xd1\xb4\xad\x98\xae\xf6\xaaL\xc5\xd4\x15}us6\x81\xab\x833\x1b\xb6\xee\xc0\x04\xc3\x1e\xe5\xc3\x860\x80\xb5b\x0d.\x025r\xde\xb1\xc3\x08\xef\\\x89P\x07\xd8\xbf\xbb<\x89\xa9\x1dad\xafD\xf1S\xbd\xf9D\x18\xb9\x13\x0fo`\xa6\xea\xe9\xa26\x1b\xe3\xaad\xee~6\xcalF\xf6\xdb\xfbw;Q\xeb\xf4\xed\xc6\\\x1d\x1b\xf5V\xec\x08#\xe0\xc7\xfd\x0d\xbaU0\xebV\xf1\xba\xa80\x16g\xc6:'\xb2\x9d\x130\xef\xbb\xd1L\x80\x91\xb3\xc1d%\xbf^|8|\xd8\x7f\xa8?\x94\xd9\xf5\xb2GL\x14\xc5+=\xa9\x87L\xba|@\xc9K\x03M\x11E\x82U|\xc6\xd6\xfd0.=\xb9\xddg\xc2`h\x8e\xc4\xd8\xf6\xd9j\xc4\"\xe0V\x8d\x99\x9c[t\xd4\x84\xbdXQd7A\xaa\xd1\xc5K\xcd\xe6\xeb\x97\xd5\xbcB \xb4\x89\xc0[e\xac\xe0\x18\xe4\"\xd0/K_n\x1a\x93\x11\x99\x04 \x132\"\xd4\x07\x83\x91\x0c\x8cP\xf5?\xfaX\x06\xf1\xd3r\x04\xf8+\xbc\xb1\xf9\x88L\xf0\xed\xaff\x00\xa0\xce\xf1n\xbe\xd4l\xd1\xbe\xba\x8b\x0b\xca\x82\x0f\xe1N\x10\xd6\x85/\xbb!!\xcc\x9e\xf7\xff2F`\xfb\xdc\xdb6\xdb\xf6\xff\xf7\xc8\x05\xf5\xfbo=xO\x8c\x1d\xde\x0e\x8d\xe0K>\xa3\x05/\\I\xee 3#\n\x81\xdapL\x13B\xfe\x8b\xc3\x8a,z\x7fX\x03\x8b'3XV\xf8w!\xac\xc8\xc3p4\\\xa69\x8a$\xfdB\xa2a\x90\x9dn\x9a!A\xba:\x06W\x0b\xe9\x077\xa8egpeop5\x01\x95\xd3sh\x81\xda\x89\xcf\xc4\xa5\x9d\xb0\xa8\xa20\xe5\x03W\xd0\x99\x00\x06\xe8\x88\x97\x8bB\x8f\x8e\xd2G\x08~>\xa7\xa9\xea4&\xa7\x89\xf2\xcd\xcdQ_\x1bK\xcd%t\x84;\x18\xf2\xb4[\xdf(z\xe3\x0c1\x19\xf9\xfdw\xf7\xe0\xf7\xdfI\x7f\xde\xf6\xeey\xf7\xb6m\x05\x92\x80\x84$\xa1`\xb9[&\x85\xb9\x0ea\xb3L{:k\xd9\xaby\xf5$\x130\xc9.CdY\xed^\x10\xa8n!\xb3\x8e\xcd\xcc\x88Ll\xf2\xe7\xd7\x83\x9b\xef9\xccs\xa3p\x9e\xcd\xad\xe6\xd9\x12z\xb9\xe4\xd7\x1f\xf6\xfdC\xe3$6O\xedE.\xe89\xb8E\xf5\x03VYR\x94\xf7&\xdf\x90\xf4\xd4L\x195\xbf\x88\xc0\x07\xc6\x02E\xda\x9d\x90v\xca\x9e\xc4&\xa64\x11\x8e\xefR\x1c\x1c\xddz\xca\x1e\x95\xaa\x89~\xe0\x8d\x10\x8c/%\xea\"\xd5P\x88F\x1b=\x8d\x90DL\x089S\xca4mv\x12\x9b\xc0\xe8\xf9\x80\xf0\x90\xfd\xe4z0\xb2o\x06V\xb4\x86Z\xcb\x03j-\xf7\xd3S1\x02\\ x/AY\xc8\xa4\xb8\x1d\xd5\x87G\x0b+\xa3\x06j\xa2>[\x8d\xe5e5b\xc9s\x98\xd4\xbe\x124\x95I,\xb92\xd2\x94A\xcf\x0d;\xd9r\xc9\x08\xb1\xe8\x10R\xf7\x9b\xb4\x9e.\xc1Tr^\x86\xba\xbb\x9f0g\xee\xb1\xd5\xb62\xa1\x95\xf3\x18\xac\x90\xcd\x96lBh\n\x00\xaeFz\xee\xb2\xe42\x0c\\\xca\x855\xf8\x06\x15j\x97\x06,\xb9\x91I]\x19\x03#a\xe2G\xcd^*\xb6\xe4e\xaa+\x90\x00~f\x99\xaa\xc9MbE\xf6\xfa\xa0}\xa9\xd2UR\xa6*\x99\xcdW\xe1\xf9V\xe8\xc3m\x1c\x8f\xbd\x08*\x8a*\xb4\x99\x8a;\xd2\xa7\xd4\x0b\x9f\x9c\xec\xa0\x17\xe2\xdc>'\x94\xb6m'N\xa1}\xd2a\x93\xc3\x1c\x8c\xd8\x00\xf3\xc4\x06V\xe5z\xf5\xe1Jb\xa5\xf7a7\xb0\x95\xb9;\xca\x96\x17.R\x1d\xe1\x82\xefQ\x8c\x1df\xb1\xa2\xf30x\x94\xd9\xf2\x97WW\xb4\xe0j\xb1\xccX\\x\x9a\xde\x07\xc3-\xcc\x84+a\xc3\x02\xd5\xd7xF\xc3\x88I\xbdA\xe3W7\x94\x95\xe7s\x87v7\xc28/%\xec\xd1\xd4\x9dE\x9b]\xb2\xfeALD\xef\xe3\x93\xba\xc8\xdbA}\xc1\xe4\x0b\xbb\x10\xf6X\x07\xf1\xe0\xe5\x92\xc0\xc8t\xbf\x08\x81n\x9e\xdcz.\"\xdb\x9be\x90\x92mM\x12+N\xa4\xbe\xc1$\xdf\x1c\xf7#\x80\xf1\x19\x19l\x9f\x91\x05\xf5\x19m\xb6\xa2\x18\xedUS\xfd\xa1Fh\xc7>B\xf0\xb8\x11\x80\xd2\x8d\x8a|\x83\x17\x00\xaeTl\xefk\xbc:\xee\xf0W\x1f\xc5#\x87\xc74\xb2\x10L#\x0f\xd74\xf2\x10M#\xb9\x12\xf5R\x8d\x0czCs\xcc\xef\xaa\xc3\xe8V}\x82ro\xd5\xa7\xdd^5\x8d\xbe8\xeeFj\xbf\xdf\xeeG\x12\xc1Z\xefT}\xecbnv\xc6c@\xdd\xe3\xc53\x17R\x84\x19RI \xe16Q\x15\xa5\x8583\xa0\x1c9\xbd\x88\xe6\xbb\xd2\x0d\x1c\x9e\x07\xf0\xaao\xb1\x1et\xdf\xe88\x07Pp\x00\x8bzJ\x82d\xeb#\x0c\xa8\x1d\x93\xf4\xcc\x8e\xf5\xc5+\xbd\x17\xca\xd2\xbdA=\x82\xe1\x9f\xa8\xec\xa1\xd9V\xf7~\xac\x9fx\xcb\xbds3\xc0P\xa4\xc1\xe7\xc9\xf3\xe7\xc4t\x9eN\xc8\x99&\xe9\x9f?'\xba \x86~\x90\xd2\xea\x7fX\xa1I\x89\xf4z^O\xc1\x8f\xf4/\xef~\xfa\xeb\xd0L\xd7\xe9\x98#\xd6\x87,\xe8P\xe0\xf6\xb7\xb7o.\xd5\xe6LB:\x85\x13\x96\x19\x8f\xf3\xb6\xcd\x87\xc1\x04\x08\x14\xb9\x879\xec\x8c\x9eA\xc7\x0cI1\xf9\xb1>\x89MU\x8c~{\xfb&\xd1\xe4\x1ee9z\\(\xc9\xaf\xff}\xfa\xfc\xd95+%\xbf\x8e\x17i\x94\xd1\xdf\xf9\xe2_Q\xf6\xfc\x9a-%\xbf\xfeW<}\x9e\xd2d1\xfap\xc8\x9e\xc7\x8b\x7fi\x9e>{N\x9f]/\xef\xd8J\xa2HN\xe4\xdb\xe3\xa1\x15\xbb\x9d\xfe\xbbj\x0e\xdb\xbdX\xaav:\xb9\x82\xc9\xdcT\xdb\xba-\xab\x8dj\xf7\xaai\xef\xabb\xa9\x0e4yv\xcd*\xf3\xfa\xf7\xaf\xdf\xb7?\xbc\xfe\xfa[\xfa\xec\x9a\xadu\xda\x87\xeb\x0f\xd7\xd7\xec\x16\x1e/>\xdcO'W\xd9$\xa1q\x9a\xe8\x07\x00 \xf3\xe1:\xfd\xf7\xec\xf9\xffni\x8c\xd7I\xf6\\?O\xe2\x0f\xc5\x84\xb6\xb4\xa5\xd7l#\xf9\xe3\x99\xdd\xc1\xffZr\xf2\xfc\x9aX#|\xf2\x9cP\xb6\x95\\L7[ \x164 \x05b;\xc9o%\x1a#oe\x97\\AV\xc1\xce\x85\xd1G\x19L\x1f\xcf\xa2\xea\x89\xfc\x04\x1eV\xcer\xae\xbf;7\xe2:\x14\x83w?\xd2\xe5K\xfa\xdc\xb1\xa4\x96s\xb42q2\x01C\xd9\xc5,Kc\xcd6\xda\x801\x00\x16\xc1P\x08.\xc0\xc4y\x91QgL%)M\xfa\xcf\xe0\x90\x95\xa1\xb1\xed^\xf6\x0dc\xc0kVs\x1ew2\x8c\xcf\xb5\xc2\xe7\x95\xf3\x82]\xac24\x13\x80\xe3@\x80\xaf\xf9\xa2\xa3?7\xaf\xac\xf9\xca\x18\xd4\xa9\xfdA\xed\x9b\x05\xb0\x92\x13\xfd \xa3\x8f%W\xa6\xc4%\xc4&T\xf4\x8c\x0ePv\xa0\xcb4.\xc7\xd8\xf0(\xf2\x15))\x93\x8b\xd2\xbb\x9b\xbb\xee=\xf6'y\x10\xb9\xf7\xf1\xccn\xc3\xae5\xab\x0b\xfa\xe1vq\x93!\x1a\x08\x98r\x07\x15\xa6\xeb\xc5\xb2/\x18\xed4h\x99\xcdK~k\x07\xc5\x8cW\xa9;\x130na\x04\xbe\xab\xd4\xa6h\x16\xa5\x9eCr1\x90\x9e\xf1\x9c\x02\xaa\x07`\xa8\xea*~\x07\x96q \xb9\x0f\x13\x00\x12\xd16\x81\x82\xdd7\x0b>\x0f\x10\x1c0WJ=0\x0e]\x83<'\xba\x1b\x917(ap\x96|\xbd\xa8`0\xca\xacm\xd7\x0b\xf2\x1c.\xd9x\xe9\xe7\xc4\x1a\xe6\x04W!\xe5\xb4Z\xdcd&v\x87/b\xa5\xc7\xd3\x95\x02w\x94>.\xc121]\x02\x90O\xa2\xff\x01:?@\x06\xe9<\xec\xd6\x8d\xa8.\x95\x06\xd3k 9)\xba\xc7\x89\x059\xac\xf6\xdb\xfb\x86d4\xe7\xcb87\x06a\xfa<\xc6{s\xc8n\xec\x16\xfe\xd8\x1c4M\xd29F\x19\xfc$\xcbt\x93\x90\xbfnG8\x84\xfa,\x1b\x95\xfb\xed\x9d\x9e\x94\x132:lu/\x9c\xcf\xe7n9\xcd\x11|8\x08\xd3]\x9f\xe4\xe7 \xc4\x8d\x90\x87\xea\xa4\x92\x19\xdb\x88\xe6\xf0v[Te\xa5\x8a\xe4\xf1\xcc\xd4A,\xf5o\xb8\xd9$\x8f\xc7\xfd&\xd9J\x06ZJ\xf2\xfd\xeb\xf7\x84U\xcd\x9b\xad\x14\x9bd%M\xd4g\xa9\xfb\x82a\xf4\xdfd\x9e\x9dpy)\x9d\x93[\xb9\xc8/\xb1\x00x\xbex\x91\x9ds^.D\xef\xc9\xb9\xc3\x8e\xe7(\xf6\xc9\xcf\xbaN_o6\xddj5\xa14\xdc\xbc\x07\x95JU\x82\xc6^\x8dn\xc9\xc7\xa3j\x0e\x17\x0da\x01\x92Y\x17\x0b\xcc\xf2sm\x1b\x0b\xdeh\x1a\x06a\x08\x04\xdb/\x04\x9cE\x07P0h\x96u_\x15\xea\xad!,\x065\xe6 i\xb2\xa4\x07\x17\xf6]?8\xc3}\x0b\xde]/^\x1e\xa8sN\x13\xf4\xa3f\xc2\x17\x1f\xc1\xa6o\x91gHU\x8cN\xd6\x1fB,Nf\xcc{`\xa0L\xe4z\xb2_J\xfcD\xdb\x1e\x9dx'\x8a\xe4\x142\xc69e\x0f\xf1\xcc\x02\x8f\x9f\xcf\xba>[\xe7^t\xa2\xdeP~7\x15E\xc1NSs\x00\xf0\x13\xfap\x9c\x90\x0f\xe3'p\xd8\xd0\xe7\xd8~\xc3\xe3X\xb4-\\\xb6\xedVR\xcdx:)\xa6\x02)\xa6\xbb]K\x06\xdb\xfc\x84\\_C *0\xf3\xca\xa7w\xea\xb0\xda\x16\x9a~C\xd1\xe0\xadK\xc1,\xec\xd6\xd3/V\x1b\xe8\x93\x10S\xeei\x16\x84\x90\xccH\xb4o\xa7r\xbfm\x9ao\xb7w\xa2\xaa!\xba\xbe\xe5\x93\xa0\xfe=V\x89u\xb2\xf3q<^\xb5\xad\xa1\x03\xa0\x19\x9ad|a\xee^h\"g\x85\x9e\xf3du8\xec\x12M\x8e\xe8\xdc)\xf9\x8f\x19I\xc8\x97_~A(\x05\xb7O\xd9\xcf\x06\xa5u\xf2\xc1\xd7u\x03\xa3\xe8v\x1a\x9c\x84^s\xe5\x98\x0b\x9b\xcf\xf4\x08\x87\xddY\xd8\x0e\xd2\x9d\xbc\x17\x05D\x0f\x15\x1bJ\xd9^\xef\x97\xec\x96\xe5\xecD\x19\xaet3SN\xf3\xca\xc2O\xe9\xaf\xe2I\xcc*\x8c\x0cUO\xf1\xc4\x9fL\x80\xd2\xef`\xb1\x12\xd8\x11\x0fb\x7f\xf0\x83\x8a?\xddX\xa0\xec\x16tR\x06~e\\\x99\x93\x1f\xb3RVp\x18\x85N.Xe\xa6}\xe6\xf9\x84\xc7\x85\xb4tzJ\"\x92\x90\x94\xd0\x89i\xae1Z\xc1;\x18B!W\xca\x02\xe0\xe2\x8c-\xfd\xeb^GYJF\x9e\xdd\xfc\xce\xc9D\xca\xc9\x84&\xc5d\xf03\xc4\xe5\xd0\x85W\xa5%}\x00\xb9$\xa4\x85\x16E\x16E\xa7i\x7f\xa3\x8a\xc9\x8f\xe5\x95\xcds\xf5\xae\xaa\xa5\"\xec\xe2M\x90B\x1e\xc4\xf2s\x85\xfcu[\xab\xab\xb7z\x9a\x13\x9f\x9bR\x16\xfb\x89\xe3\xfbQ\xdf\x05\x84\xd3\x18\xe1\x83\xf30\x8d\x0e\x7f\xa9\xc3>\xb1N)\x94\x0d\xbd\xf05\x10X$\\\xb3\xc0\xc7\xdc\x1a\xac\xb2f\xd1}\x92\xa5O>\x99\x18\n\xbe\x9b\x9c\x126\"\x93ZN\xc8|\xf4\x91\xcf\xa6\xb3\x1b\x92\x10B\x13_\x0c\xf8z\x01G\xbb\xd6;\xec\xedt\x85\xc7\n\x1d\xa8\xef\x9a\xb9\xc7\x8b5b\x1c\xdeN\x11\x0e\xe2\x9d\xaa\x0b\x0c\xfe\xe9nQo\xb6a'vK\x0d\x063\xae!\xb7\x88\xcc^K\xe7GN\xe0\x92\xb8\x8a<\x9a\xed4\xb91D\xf9\x0ds\xce[7gzZ\xac\xb3\xf8\xd6VB\xf2\xbd&b\xccJ\xa5\x8f\xa7\xa9?\xca\xf9\x0d\x00\xd3\xf5\x16 \xc0Z,N\xec6\xd3S\x13\xe8e\xdd\xeb\x07\xb4.~9\x03\xd6e8<\x85\xad51\x99 =3\xf7&E\x10\x9f\x03\xbfar\xdah\xea\x7f\xcf\x1e,\xefq\x8ft\x02\x1ci\x14\xd8\x95\xd1\xfd\xfc!\xbe\xbaa\xf7\xf4\x8c~\xc0p\xa7y\x0fG\x9f\x91 \n\xd5\x03\xb0\xae\xa5\x13\xa6\xb0=k\xd8\x91\xdd\xb3\x07\x9e\xcf_\x8c9\xd7T\xd4\x81\xbf`\xcb(\xeaXK/\xa9CFg\nB\x84\x10\xd6\xe9$\xf1r\x96~\x99\xcc\xd8\x9a\x8b\x97\xfc\xc5l\x16E_\xccf/E\xdb~1\xfb\x92s.\xc0B\xec\xc8\x0f2\xbee'VR\xca\x8e\xfc\xa8o\x8e\xec\xc4\xd6\x94\xad\xd3\xb8\xb7\xc2\xef\xf9iH\xc2\xf0F4\x07\xb7\xa6 e\xf7C\x9b\x01\xbf\xa7\xec\x89\xf7\xf5\xdau\xaf\x99\x85\xcc\xef)e/\xb0\xa2mK~x\xfd\xf5\xb7\x108\x01\xf6\xca\xf4\x81\x93zkC\xc7&\xa6=\x98z\xb8\xb3\x15I\xe2\x07~\x04\xcaA\xb1=?\xe2\xfe\xd8\xf0#\x1e\xe2l\xcd\xc7\x0d\xa5I\xdc\xf0\x07\xa6\x8f\xf0\xf1\x03\x8d\xa2\xf8\x81\x13\xc37\xce^\x02\x10\x11\x9f\xe9\xc3\xc8R \\\xb8K\x08]\x1e\xe7m\xfb\xa0\xcf|\xb6N\xb7\x1d\xf7\xd8\x0d[\xec\xd9\x03;e4\xd9\x86\x0e\xb2\x1b=E\x1fX\x93\xf9B5\xb5\x14\x7f\xd4\xc4\xad\x19\xce\xce\xe4^\xa78\xbd\x0d/\x9a\xc0\xddk\xac\xa3\x9e\xedl\x9d\xee\x13]\xdc\x0e\xa2b\x04\x1f\xc9\x00\xbf1\xee\xad\x93Wf\xc9\xb9\xb5rue\x0f\xb7\xb6}\xe2h\xdb\xeeB\xc0\x99\x13P\xad\x9a+zJ\xfc_[\xfb?&\x19\x01^\x8a\xc2;\xef\x80\x11|J-\x86\xef\x98>\xc8=\xacOG\x0d\xb7T\x07\xc2\xc8n\xdb\x1c.CA\xf6\x959]\xef\x9d\xae\x10\x164\xf6\xaam\x0bVp\xe9V\x125lX\x0c\xbc\x9c@\x86=gv\xfbM\x14\x8a\x02$\xb3[Ya-A-\x90\xcf\xa0\n\xee\xb2L\x14\x02\xb8rm[-\x9b\x7f\xe3\x04\x017\xccJA4?I{\xee\x96\xf7{\xb1\xfbz3`R1\x1fj\xb5\xa0\xe9\x80\xb5D`\x91e\x8a\xebZcQz\xa6\x88\xa5\x01\xe7\x18\xe8x\x9f\x00\x8a\xa2S\xf51\x9e\xd1\x00\xb4\xc7f\xebZLv\xc0\xfeL\x16\xca\xf2>\x88\x1c\xd8 \x82\x01\x87a\xc0\x04\x82O\x19\xcd \x02\"\n>\x90\xea\xa3\x12;4\xb4\x03\"\x8f\x1d0\x14\x9dn\xe9\x8fu\xad\xfe\xc4\x8f\xe1IK\xa7^\xa7AQ\x17\xdd\x96\xf4\xda\x92;\xeb%\xc9\x1d\xed\xd1\xc4\xdeW)\x95~\x08h\x92\x07@n\xa6\xca\x83\xe6\x0d\x9d*\xce/\xdaP\xfaI\x7f1\xd0y\xd7\x14\x86&\xf8\xa9c\xdd\xfdX\xb7gp,\x070E\x033\x01(\x8f \xfaz\xdbz\x8b?\x07\x1fe\xcc\xaa<^\xee\xd9x\xfdZ#\x8f\xdd\xde\xf8 76\\\xca\xc0Hu\x02\xb6~\xc5gQ\xd4\x0d\xa3\xfe\x15\x07g\xa7Ni\x00\x1c\xb0Q\x03\xc5\x8d\x07\xbf\xab\xbb\x1f\x84!'\xc9\xaf\xff\xc7\x8b\xd9\xf5\x92\xddK~\xfda\xf1!{v\xcd\x1e\xc0\xcc)\xfdP_/\xd9'\xa3 C\x15\xb51fo\xab;\xb1\x04\xa5\x99:\x80\xfe\x8c>\xbb\xae\xd8\x1f\xf2sf\xf0\xb7\xea\xd3R\xd5\xf4\xba\xf2t\xc2\xd7}\x19\xf7E\xb8P\xb3Ev\\\xb1\x14}\x94m{/m4\xd3\xb4\x88\x05S4\xd1\xa5M\xc8\x82L\xe2\x0b\x81\x90JsMNNHF\x98B\x85o\x80\xd7\xdc\xb6\xf6\x85\xb1\xc3z\xce)-`\x07\xee9\xec\xe6\xd4}FAq\xf9BeX\xa2a\xd2\xf8\x80\xd8\xc2\"\x85u\x1f\xf5\xe6yN\xd3<\xb6\xe6AyJ\x88\xde\xa4\x17\xd6t&\xe3(\xfd\xfc\xf5o?\xea\xd3n[\x03\xb6\x1d\x9d\x10N&\x03Orz\xee\x80H\xe6\xc6r>\x94\xbci\xae\xaf\xa3?\n\xb9\xca \x1e\x8d\x00ct\x94s\x99\xd0\x02?oDU;/t;L\"\xb4\x1e\xc1\xf52\xad\xc5\x9dB{\xb6\x13\xc2\xa9\x07=jb\xe3|-c\xc9 \x06\x8e\x1e\\\x1f\xa6`\xbd\xad\xea\x98D\x81\xb0\xe1$\x19\x99\x90\xfe\x81\xd1\xa8}%6\xd5\x1f\x83X0\x86s\x86\x1a\xb8\x8c\xd8.J\xcf\xac\x9b\xf4\xd4\xee0\xb8\x87\x1b\xdf+\xdc\x15\x94Q|{#,\x91\x86F>\xc6\xd6\xf4Lm\xa0\x80\xc1#\x01\xe6^g\xb7\xab\xc1t\xc6\xd4\\\x8e\xad\xbb\xba\x1f\xfd\xf6\xf6\xcd\x0f\x87\xc3\xce\xb0\x8b\x86\xdf\x11\xf4\xf1\x8c\xbb\xe17\x92\xcf\xd8+\xd0\xcb\x7f+\xf9\xe3\x0c\xbcLo^\xbc\xf8\"y1\xfb\xf2\xcc^\xcb\xbe\xc6\xf5a\xb5\x8f\xe9\\L\xc5\xe1 \xe4\xea5\xcaY:\xb71\xd9\xd6hGD\xc2\xd5a\x8d\xed\x84^\x02\xaf$}%\x17\"\x83pX\x9a\x1d\xdf7|<~-\xa3\x88\xdcW\x87\xd5\xab\xbd*T}\xa8\xc4\xa6!U=z-5+\xb8\x16\x0f\xfc\xb5\x84l\xa6\x0b\x1c+\x16?I9a\xd9m\xab\x8b\x1e\x8bP\x14\x96>6\x1d\xac\xe8P\xef\xc8\x05\xb6\x93-\xf9d\xf2\x8dD\x10\xb0\xedN\x9f\"(\xca\x13 \xe3\x11\xc8\x9f\xea\xbbF\xeda\x08\xc5t'\x9a\xe6~\xbb/(\x83BP\xef\xe1\xd5r\x9d\xc4\x85\xcax\x90\xb0P\xd9\xdck\x82\xa3\xa8\x9c\xf6\x85\xb8Ci\xb1\x7fE\x7f3hb\xdb\xca\x05\xf9\xed\xca\x8c\xbd*\xae\x00\xf1\x11\xc2\xe8\x0f\xa5s\xd2\x9d,&\xb4\x8cQ\n\x97\x97R\x07\xc5$\x04k\xc8\x87\x0e\xf5`\xe8\xf5VlDZ\xaf\xe4b\x99A\x08\x82-L\x11\xb8@y,\x183\x19i\x03p\x82\xa5\x95B$\x86\x9b\x83\xd4\x02\xf0\xd84\xc7\xc5\xca\x80\x89\xa3I\x11\x7f+\x176)k\xdb\xc1l\x97\xf6\xaa\xe54T=\xa5\x06\xb9\xbd\x93x6\xbajVN\x07\x05\xfd1\x053S\xd7\xa6<\xa6\xcc\xb7+\x8fM\xf5)\xcb9\xb4_'a;\x0d\xb20\n$DG\xd4\x85\xaab\x8cqa\xe5\x14+\x03\xb8\x81\"\x8a\xd5\xf9Bx\x0e}\x9d\xebm\"\x8c\xe6\x1e\xe8z\x9c\xe2\x10Y\x15\xa3!\xf4\xea=6\xeah\x00\x9fHW\xf2n0\xfd\xe1\xca?\xe9(\n\xcd\xd7\xae\xe34\xd1e\xb6:\x1b\xc5\xc4\xeb\x9eb\x0f4t\xa6\x88!\xb2\xbe\x83\xad*(\x13g\xbf':\x85Z\xecX\xb1\xce\xce`i\x03\x81\x02U\xf0\xf8D\xd1*\xba\xa7v\x05\xeb\xd6\x81[3y\xd4}\xc3o:\x83\xdf\x00\xdb\x81\xa0\x1co\xa7f\xce\x86\xee\xae\x03\xbc\xad\xe6(\xc8WX\xdaKB\xf1\x94}\xf4\xbab\xd4\xfd&b\x8aY^\xe1=k\xf62\x81\x8d\xe8L\xa7\xdb:&`\xbai\xe4\x1e\xb2\xb3$s\x07\xb5\xcc$\xae3\x11E*\x0e\x96\x15Jd\xbe\x9c} \x07\x00\xdeb\xa8\xc2\x95\x12]\x88\xb4\\\xb3y\x033OF\x91\xd43\xcf\x9a\xfe}'5 \xf8\xbd\xe4\xd71\xa7\x1f\xd28\xe5Q\xfb\x8c\xb6\x1fR4\x02\x0c&\xe5\xba\xd9\xd6\xbb\x84H\xa3/D\xf5\xef\xce\xaa\x0f\xfb\xfc\x97\xe0\xdfI\x0c\xda\x00\x92\x0e\x08\xf0\xb7\x9d\x90\xdfQ\xa2\x1d\xd2\x12\x0b\x01\xc6Obp\x82\xe8o\x80\x86xG:a\x0f\xfa\x00\x1e\xf9\x142\xd9\x88/\xdf\x1b*#\xd7\x1dOSr\xdcoHr\xb1\xa9\xe4Fz=\x8e;\xd2i\xf4xr\xaeF\x7f\xaa\xf7\xd7\xf4\x8e\xff (\x03\xa2\x88\xe8_Ou\xad\xda\x96`3\x00\x84\xbc#c\x8e\x95\xad\xbe\xed\xcc\x1e\x01\xde}H\xd3^B\xac\xd9\xd7N\n[\xa5\xf9b\x95q\xfd\xcf\xd1)\xdf\x83\xd6\x81L\x94\xcbn{+\xef\xe9:l\xafYE\x84\xc9\x8e4=\xc0\xcbz\xe3\x1c\xb3\xbcP\x8b\x9f\xf1KB\x15 S\xd1\xa6RM\xc8\xe8^4\xa3z{\x18\xe9i\x04\x12\xcc\xe5b\x96\x9dY\xb7K8\n\xb2 \xca\xa4\xca\x98\xfe\x17\x96\xbc\xf4\xf6\xaegV\x0c\xc4n\xc4\x17\x80\x07\x82\xe6u;W\xf6:KO\xd4c\xb3\x8a\x155\x88\xd7A\xdf\x974\x8a\xcax 2\x93%/}de'1\x0b\xb7p\xb0U\x00\xf4\xca\x90re\xc6V\xeb\xcf\xcd_\x07\xfd\xc7\x8c%\xa4\xde\xfb\xc0\\`\x83\xf6\x90\xfcd\x01\xf3\x99\xe2c\x19E\x0b\x17\xc4\xbfH\x17y/\xf6E\xb1\xb8\xc9h\x96@\xb0\xcf.\x0e\xf9B \x7f\x83\x11\x89\x90\xa9\x8b\xa2:V\xd4oG\x16\x04|\x91\xb1\"\x94#\x18f\xfd\x07\x0c\xeb3\xd5[\xdb\xdc]\x0d\xf5\xc1Pt\xac\x1f\xa4\xed\x83\x1f\x9e\x08\xec3\x0f,\xf90L\xf3\x8a\x8b\xd0\x17\xd0/\xb3\x97\x1c<:\xad\xe6V\x18\x13\xb4\x15\xa5\x0c\xe2\xf1\xc1\xdd\x0c\xe0z\xfaL\xae\xe9g#\x9cL\xf2\x81\x88x\x08\xbaC~\xfe\xe9\xdd{=s\x9d\xb9\xba\xe5X;bG\x15\x88\x1c\xd1\xb8\xc7\x986\xd1^`YA\x1fK?\xa3\xd9\x12\xe1\xe4\x8bT\x1f7Eu\xd2g\x8d\x11N\x053LsI\xe0l\x13\x17(Ir\x1a\xf5XFQ\x97qZ\"\x1b,Y\xd9\xb6\x81m\x1cPZ9\x13\x99>B\x80\x0b\xf4\xa2_\xaffe^.\xcdzB\xedP<\xde\x11\x9c3\xaf%\xba\xc4\x10\xea;\x02\x0c\x19\xbf\x0fI\xa6L\xc8\xa3bX\xea\xbb\xdc\xab]\xec\xe2q\xf9\xa3\xc2\x8b\xaf`\xe3\xc5`\xaef\xe0p\xf6\xfe(\xb9p\x10\xc4},b/\x14\xfa\x8b\xec|\xafj\xfe^\xd5\xc5\xf6>\x164\x15\xc9\xff\xe2}\x18\xb1\x00\xd1\xfa\\\x1b9\x198\xaa\xfc\x04\x97\x17\xd2\xfc\xd3eL\xf2\x00=\xd1`r\x12\xca6\x08\xeb~\xc7\x1f\xcfs\xa2)\xe6J\x9a(\xd7\x16\x8bjjss\xb2W\x1bq\xa8N\x8aP\xb6\xe2\x1bS\x8b\x18\xa3\xf3\x99\xa2A\xdf\xc0*\x9f\xb0Q\xe5\x81P\xb6\xe6\xb1\x07\x00\xd5\x1fh[RV\x0f\xaa\x80\x1b\x80\xde\x9bT\xe1)y\xc1\x1b\xee\x80\xf2|\xa6\xf8#\xa0H3@\x91\x9e\x9dY\xc9\x0b\x881\x19j\x0c\x80A\xf6^\x88\xe5\x05\xeev\x00m\x0eaF\xe2\x83\x0d\xb9\xb9T\x87o\xb6\xc7\xba\xa8\xea\xe5\xabM\xa5\xea\xc3\xdf\x94<\x8c9\xff\x15\x9d<\x87\x9f\xc7T\x13\xb2\x7f\x91qI\x19TO\xc1\x80\xc8\xe9N,\xd5?\xb0]W\xf9T\xc2\x0b\xef\xb7;\xac\xbc\xc2\xf1\xc0\\\xbf\xf5r\xbdQ\xe5\xe1L\x13uf\xbb\x01H\xe6\xaat\n\x0e\xa4A\xf5\xd2r}T\xf4\xfa\xc8Z\xd0\xbbYm0$\xc2\xe5\x96\xe6\xe8\xc37\xd4\xb8$6\xc21\x1c\xab\x9f\x8d\xa4\x9e\xe5a*\x1c\x9e>\xee\x8d\xae\x07\x1e\x06\x14\x0c\xdc\x85\xcbF\x19\xac\x11\x07\x81\n9\x11\xc5\xeb\xfdv\xe7\x00Pu6\xe8\x9f\x81|\xbas|F\xd3\xe7\xb8\x14\xa0\xec+\xd7@\x84px\xaf\xb7t]$t\x88Y\x0eX|?\xab.\x1a\xf2\x9e\xcfg\x16\xb6\xf7\xbf$\x99\xbc\xe8\xad\xb6\xfdQZu\x13Hp\xc3\x08A\xd8M`\x85dw\xb9\x81\x0d\x11tRa\x99N\xce\xa9\x0b?w\xe3\xfe9\x18\x98\x84\x04\xb3\x8b0\x07\x1b\x83\xe9fn\x86\xb0iA\xa4\xb5N\x16\xce\xb9\xbc\x8ce\xe85\xa1}<\xb2\x1ca\xa9N\x00d\xfe\x97\x10F\xd8\xed\x0ce\xbaL\x97\x0b\x99%\x9a|\x05\xea2^\xa6K\x07m\x13\x17\xa9\x08\xd7FR\xb2\"-\x13\x11\xae*\n\xef\xf2\x92\x9e5iw\x11\xa5\x07\x91\xb1{\x11\x1c\xf5\xf1\x0e{\xff\xe7Q\xff\x00\xe6\xae\x03\x8a\x1ef\xf7ji\xa9\xe9)\xc40\xf4h\xb7\x92b\x04~\x7f6,\xf2\x0c\x80h\x13\xe9 \xe9`\xc4\x0c`\xa8\x03&D\xf8^\x13\xff\xfe2n\"\xbe\xe4`\xf0\x00L\x9eL\x84\x15q$9#$!\xdb\xe3\x01\x92\x83\xf7\x81\x9f\x84a,\x82a\x04\xb6\xff\x04\xd0\xcc\xfd.\xd4dy\x10\xbf\xc0\xd1\xb3\x10\xe9\x16\x90l\x0d\xee\"\xc6\x0eN-jJb\xe1x\xdd\xb8_\xce\x90@\xeftIn\xe4\x9a\xfd{\x8aZY\x10\xdc&\xc9Dd@\x93\xe4\x8e&A.\xb3\xbf\xe3\xbb \xf5\xf94\xdf\x16\x9f\x80\x9b\xdbn6\xfa}\xa6:w6\x03.5\x9b\xa1{\xe7?N\xedh\x02*\x01.[\xdd\xb0%M,<\x02F\"X\xc2\x14-\xd3\xc2 \xef\xdc\xe4\xb4\xee\x9dM\xf5\x87\x1a\xe0+a31t\x1cf\x14u\xf1NmJ\xe4EDQ|\xa3\x99;b\xdf\x0c\"H\xab\xb2\xaaU\x14\xe1\xefT\xdc\x15\xf6:&\xa8M\"l\x91\x0d\xc0P\xd6\xe6\xec\xfdOM1\xae\x7f\xd19\xd9\x1b}\xfd\xcc\x0fT\xbd}\xb5\xad\xcbM%\x03\x07\xe1\x80\x08\x9d>\xd3\xdb\x18\x10k\xcf\xf8\x1b\x89\x81\x80MY\xee\x89\xb9\xfdOIY}f\x96\xf7\xe0x\xdc\xba\xc7\xba\x84Z\xe7\xa0\xf3\x7f\xfb\xbf\x01\x00\x00\xff\xffPK\x07\x08\x01\x84\xa6\xa6\x8ct\x00\x00`I\x01\x00PK\x03\x04\x14\x00\x08\x00\x08\x00\x9dJ\xb1L\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00:\x00 \x00javascript/jquery-sparklines/2.1.2/jquery.sparkline.min.jsUT\x05\x00\x01zI\xfdZ\xd4\xbdy{\xdb6\xb60\xfe\xff|\n\x993\xa3\x10\xd6\x91,9ifB\x19\xd6\x93fi\xb35\x99&\xbf\xdb\xf6ay\xfb\x80$\xb8\xc8\x14I\x93\x94-\xc7\xd2w\xff=\xd8H\x90\xa2lw\xb9\xf7}\xdf\xe9\xbd\xb1\x88\xf5\xe0\xe0\xe0l8\x00N\x8e\x07\xcb\xcb5-n&eN\x8a\x8b$N\xe9\xe0t2\x9b\x9c\x0e\xc6\x83\xa8\xaar\xeb\xe4$[\xa5q\x9eU4\xad&)\xadN\xba\xc5O\x06\x7f;>\x1e\xbc\x8f=\x9a\x96\xd4\x1f\xacS\x9f\x16\x83*\xa2\x83\x1f\xe8\xf5\xe0\xdb\xcf/U\xd6`<()\x1d\x107\xbb\xa2\x832\xae\xe8 \xc8\x8a\x81O+\x12'\xe5\xe0\xf8\xe4o\x7f3\x83u\xeaUq\x96\x9a\x04\\\xf0\xd0\xad\x96\x80n\xab\x9b\x9cf\xc1\xc0\xa7A\x9cR\x8c\x0d\x95g\x0c\x87\"mBV\xfeB\xfc4mC\x80i8@\x90\xb5\xfc\x0f\xfb=\x1c\x1e\x89\x1f\x93 m\xc0\x1f\x0e\x89)\x92\xd1\x0e5\x1d\xfa\xe8\xd6X\x97tPVE\xecU\xc6\xfc\x8a\x14\x03\x8aow\x10@\x08\x11\xc4\xb0\x84\x0bH`\x05)d\x90\xc3%\x14PB\x05k\xb8\x82k\xd8\xc0\x0d|\x85\xe7\xf0-\xbc\x80\x97\xf0\n^\xc3w\xf0=\xbc\x81\xb7\xf0\x0e\xde\xe3\xe9<\xc0u?\xe8\xb6\xa0\xd5\xbaHo\xbdl\xb5\xcaR\x8b\x0f\xd32\x18d\x06\xb0\x7f_dIVX\xc6\xdf\xa7\xd3\xc0\x80 N\x12\x95\xe0\xf9\x81\x01>\x0d\xc8:\xa9>\xc5\x1b\x9a\x94\x9fh\xf1_$YS\xeb1\\\xc7~\x15Y\x06YW\x99\x01\x11\x8d\xc3\xa8R_^\xb6\xca3\x86\x7f\xebh\x06\x15 y\x95\xf2yU\x15\xb1\xbb\xae\xa8e\\\xf1\x04\x83\xe5}\xcc\x19\x90\xe5\xa7\x82\x06\xf1\xc628\xd2\x0c\xa0)q\x13\xfa\xa5\xcef\x0d\x89\xb4\xef\xe30JxgGS\x88\xd4\xc7{\xf6\x0fM\xad\xd9\xe4 TY\x96Tq\xfe\xf9\"\xce\x7fX' +'\x93T'F]f\x1d\xc8\x04?.E\xeb\xbeO\xd3\x17\x11\xf5.X\x97\xe9z\xe5\xd2\xe2uV\xacHU\xd1\xa2Iz\x19\x87q\xf5]\x91\xad\xf3\x17\xd9:\xad\xac\xc7{\xe9\x9fin\x19`\xa8t\xea\xc5+\x92| \xc5\x85eL\xea\xee\xbe\x080\xf8\xf0d\xd2\x9b\xb4\xa2\x05\xe13g\x1d\xcdv|\x82\xac\xdb2\xcf*5+\xc1\xbf\xa7F3\xf2\xcfZ\xce7\xc17\x86\x8e\x93fj\x83\xd3S\x03X#?\x12?^\x97\xd6l\xf2\x0d\xac\xe2\xf4s\xb7\xd9\x15\xd9\xec\xa51\x08~\xe2\x93=\x83\x94\xa1\"\xf9\x91\xa4!\xfd\x10\xa7\x96\xd7J \x9bvBMF\x9eg\x80_\x90\xeb\x1fx\xde\xc7\xf4K\x96\xb3\x11{\x11)*\xad-\xed\x9b7\xd5\xca\xff\xb9[\x80%\xc8i\x14\x13d\xa5\xf4z\x10\x99\x8f\xce\xca\x9c\xa4\x83\xb2\xbaI(6<\x0e\xc3\xe0\xf6\x96\xff\xd8\xed\x8c\xf3\xe1\xdf\x9f=\xfd\xd7\xb3\xf9\xd9 +v>\xb8\xbd\xcd9U\xecv\xb7\xb77\xec\x9f\x92\xd3\xc4n\xf7\x08\xed\xc0%\x85u\xeb\x92B\x8d\xe4\xf1\xe3\xa7O\xd9`R\x1a~\xdb\xa4\x06O\x9e\x18PV\xc4\xbb\xa0~\x9dl7\xa5\x8d\xbf\xfb\xde\xe3g\xb3S\xf6+\x08\x9e=\x9bN\xd9\xaf\xd9\xf4\xd9\xd3\xd9\xbf\xd9\xaf\xa7O \x11i\xbe\xff\xe4\xc9\xbf\xfe\xc5~M\xa7\xcf\x9eyO\xd9/V\xfe\xd93\xc3\x81\xaf\xb4\xc8D\xdb\x1e\xa4k\xb5J=\x9e\xfe|\x13\x97\x8c\xd0]R\x88\xa9z\xc2~~\xce\x89\x17\xa7\xa15\xbb\x1b\xb5\xad\xef\x17I,&\x87\xb5\xfe\x81\xe4\x7f=\x96\xf9\xe2\xefb\xba*\xe2\xb2\"\x15\xe5\xe8\xee\x1dB\x9e\x95\x1a\xce\x9f\x06O{\xa7\x81!CK{\xf6\xec\x99\xd1\x0c\xe5v\xf7\xd7\x8c\x85\x8f\xc0Z\x91\x9c\xc1\xae\x9a\xe4L\xee}\x96]\xac\xf3\xd2\xba]\xb1\xee\x8c\xf1\xcc\xb0\x8c\xf7YY\x1a0\xb5\x8c\x97\x05\xb96`f\x19?\xc5\xa9\xb1\xdb\xed\xd8\x92\xf7\n\xca\x06\xcd\xd6\xd8\xf7-\x1eZE\x05-\xa3,\xf1\xd54\xd7 \x82\x01O\xff\xc0\x9c\xf6\x0d\xde\xb8sj\x0c\xb6\x08\xd6IB+\xeb\xb6\"EH\x1b\xd6\xf0\xf81\xe3\xdf,IL\xd7c\xc8i\x11\xb0\xa6S\x8f6\x0b&0\xa0\xa8\x99A\xc9\x96\x85\xff\xd8'\x01e\xa4M\xfe\xed>\x0d\x02\xf6\xeb_\xc1\xb3'A`8\xe0\x92\x92\x1e 9\xe3\xf66\x88i\xe2_\xd0\x1b\x8b\xff(w\xbb\xc1xPCm\x1c\x98\nQ\xd6\xba-,\x83c\xc3\x80\xdc2>5\xb0\x1aPY\xc6\x17>\x12>+yL\xad\xdb,\x08JZYS(\x93\xd8\xd3\x80\xff\x8b\xd7\xb4\x9b\x15>\x95\xf4>\x95_\x8d8\x9e\x1a\x7f!\xbd\xeev\x03\xf3\xf66\xa7\x85\xc7\xd4\xac\xd9n\xf7O\xc4Y\\\xb6\xb1n\x0br\xcd\xe8\xc3\xcd6\xef[\xfa\xc0\xd4`i\xaf\xbb*\xc1u\x14\x97\x17\x1d@\xb3u\x95\xc4\xb4\xd0\xeb?f$\"\xd3\xf56\x026\xe7+\xea\xc7$\xad\x93X\x13e\x94]\x7f\x14\xc593\x93U\xdf\xfc\xe7G.\xaf:\xe2K\xd0\x1e\xa3\x95\x16]>!\xa7\xc6\xbdK\xe3.\xf2\xaai\xcb\xea#-Q\xe55+\x92\xc4e\xf5\x8e\xdeX\x06\xaf`\xdcM|\xc9%c\x04\xd7\xb4\x18\xfcgM\x8a*N(G\x81e|\xe0x0`}i\x19\xff_\x9e\xb7\n$\x99e\xbc\xa7A5\x90X1\xa0\xc8,\xe3G\xc6&\x9a\xa4\xe4Z\x16\xfaI\xcc\x8a\x01\xc5\xb5*\xa4\x92v\xec\x7f\xf0\n?\x9a,/K \xe7\xe0v\xc0\x954\xa6h\x0c\x88[f\xc9\xba\xa2\xf3\x84\x06\x955\x98\xe6\x9by\x95\xe5\xe2\xc7U\\\xc6n\x9c\xc4\xd5\x8d5\x88\xb8~4w\x89w\x11\x16\xd9:\xf5\xadA\x11\xba\xe6\x14\x06\xec\xff\xd0\xa0*H\xcaT8\x9aVZ\xa1\xb1\xa4\xd1\"t\x899\x05\xfe\xdf\xe4)\x9a\x07q\xc2\xb4\xaa\xbc\xc8\xc2\xd8\xb7^\xfe\xfcfEB\xfa\x855\xc1\xd6\xe6\xe4C\xec\x15Y\x99\x05\xd5$,\x88\x1f\xd3\xb42\xcb\x8a\x14b\xaa\xcb\xaa\xc0|\x1d\xb1\xff\xc1\x80\xa6\xfe~2\x9a\x8fW\xe5X\xf620\xfe\xe7\xfa1\xe6r\x84\xd7Q\\\xd1y\x90\xa5\x955\x98M\xf3\xcd\x80\x141I`P\xb2\xf5J\x8b8\x98WtS\x8dI\x12\x87\xa95`\xc8\x9e\xf3*\xe32'\x1e\xb5\x06iv]\x90|\x9e\x13\xdfgRo\xf0M\xbe\x99\x0b\xae`\x0df\xf9fPfI\xec\xcb^\xbe\x8e\xe3\xd4\xa7\x1b\xd6\xd1t:\x9d\xef\xd8\xdcrr\x1b\xdc\x0e\xfe88\xbbG\x10\xea\xb6\x03\xb3J\x08\xb8saC\x0c\x88\x9eWEq9\x89\xd3\xb8\x9a\x90\x1d\x0eM\x17?/\nr\xd342\xe1Ra\xe2\x91$i\xc6\x033\x18\xcf\x10\xb8\x93uZFqP\xe9cC\xa0\x06'Q\xe5\x83\x8b\xd0!\xc0\xd8\x88\xb5\xce\xbc\xa4\xc4\x04\xc8\x0e\xfc\xc9\xe7O\x82\x01\xbdHHY\xe2\x08\x87\xe6mPP\xeb\xe4\xd7\xdb_oM\xfb\xd7\xeb\x893Z \xd32'\xa3\x05B\x8b_w\xbf\xeeNB\xc8\x0b\xea\xb1B\xe6\xaf\xd7#\xf4\xeb\xc4\xfc\xd5\x1f\xa1\x13`\xf3f\xe9\xe6\xb0\x9cO.\x16+L@|y\xbc'w\x07\x05efw\xab\x06\xf8\x82>(\xe63\x1f`\xd2X\xae\x8a`\xb46'\x05\xcd\x13\xe2QS\xa4\x15\x14\xbat\xa6*E\x1a2f\x0e\xc4\xda\xe7c\x07BL'bL\x13\xba\xa1\x9e\x19!\x08\x17\xe6\x05\x0e\xedS\x07\"\x1c\xda3\x07Y\x17\xf8h\x06K\x1c\xd8\x91\x03K\x8c\xb1\xb70\x0c+\x1e\x0e\xdd\xe1\xd0\xb5cFy\x98\xfd\x052 i\xb5`?\xd9\x0fs\x89\xb6\xdb\xa5\xc5>\xed\xa5\xb3\xdd.\x91e\xa6\xe6\x12\x0d\x87\xa6\xcf\xf3\x8d\x8eIh\xa0\xc5\x12\x1f\xca2\x97\xc8Z\xe2\xd2d\x86|\xabL\xc7|4\xd0\xa1\xec\xcf4\xdf\xcblLI\x03!\x04K\xb4C\xbb\x1d+T\xe6r\xee\xda\xb3*q*\xa4\x1cK\xd9A\x8c\xbb~\x10\xb5\xbc\xcf\xdc\x85k\x91so\xe1Yd\xc7\xf0\xd7\x94\xf3\xc4$\xf9j\x92<\x8c\xf1\xe9\xc2\xf4\xb1; \x92,+L\"\xd7\xcc\xc9)[\x92\xe2\xf7?O\x17\xc4\xf6\x1d\xcb$\xb6?\x9e9#\xf6\x81NN\x19\xe5\xd7\x05L\x1f\xd7u\x8f\xbd\x91\x87N\x9e\x80\xffO\xc6*l\xd5\xb4\x8fX\xd5\xe6\x8b-\xfb\x93SK4\x8a\xacN\x0b\xa7\xbf\xbb\x85\x1d\\`\xdd\x13\xc4\x06\xea\xce\xcb\xeb\xb8\xf2\"\xf6\xed\x91\x92\x1a\xebT\xb8~|\xc3\"\xd8\x9b\xbb\x05%\x17s\x9e\xc3\xcc1\x96\xc8\xfe\xea\xe9U\xb1\xa6,\xfdh\xaa\xa7\x06$)E\xf2L&K\x1f\x8b\xe5\xe2\x9c\x14%}\x9dd\xa42 \x02\x82\xb1;\x1c2RE;5C;H\xf6A\x05\x0f\xdb\xce<\xc8\n\xd3\xc5\n\x11sw<\x9e#\xcfv\x1d|\xc1\x10\xe1\xa0z\xe6v\xb0\xea\x10 k\xc5\x03\x1f\xa8j\xc7\xc3S\xf0\x9b\xb6\xbc3\x7f\xee\x8dF\x88\xd8\x9es\xc4\xa1\xa2\x93|]F&K\xa8\x1b\xa6;H[\xc0\x89\xf4\xa3\xb8\xfc\x81\xfc`\xb6\xc6\x86\x86\xc3\xb8|\xcd\x98\x105 \xdaA\xd9!I\xa0\x10\x08\xb0B\x88\xe6\x04\x9b.\xc6\xf8h\xb6h52\xa9\xb2\xcfU\x11\xa7\xa1\xc9\x08\xaa\xca^\xc7\x1b\xea\x9b.B\x932O\xe2\xca4\x0c\x04!6C\xecO\xe2\x94\xf3p\xd3\x98\x18@\x10:\x9b.\xd4\xd8\xac\x10\xc23\xf5\xc1\xb0m\x87\x0e\x0e\x10\xc7B\x84\xc3\xb17\x8f\xce\xa7\xf3h\x8c=Dx\xbb\x1e5#\x98\x02\xad\x87M&\xcb,NYo;\xc8\xf6\x96\x96X4\xac5\xb6T$>}67\xb7q`z\x8c\x1f\xf9\x0e\xc6\x9cx\x90\x97\xa5U\x9c\xae\xe9<\x0eL\x96~\x841A\x12\x8b3I\x03G\xd3\x1d\xe4\xfb4\x80\xa7\xe0\xc9\xa9k\xa6\x8du\xe3\x8e\xb0\xf4l\xb2\xc9\xc2X2\x12c\xc1>\xad\xa9\x1a\x05\xe3\xf1=\x937\xf0'q)pG\xd0\x82X6qvp\xd9\x14T\xc43'\x13\xaf\xa0\xa4\xa2\x9f\x99]\xf29\xa2\xb4Z\xec'\x99h\xe2\x95\xe5\x17\xba\xa9\xb0kqHE\x89W e\xfc\xdd4\xb8Uc \xf0&\\(\x1aLA9\xf1\x98\x05\xcd9\xb5,W~{\xf3\x85\x84?\x90\x155\x8d\x88\x12\xdf@L\xe2\x93\xbc7\x1c\xb6\xfc\xc0\xe7>\xddx\x95'\xd4/\xc8\xb5\x86\x00F\xaa\x10\n4D\x10\xb3\xa9\x0b\x86C3\xe2Rq\xe2\x93\x8a\x98\xc6o\xcb\xcb\xf2\xb7+\x8f\xa4W\xa4dl[ :V\xd8oy\x8b'\xa2\x10\xa7\xf6z\xe6\xef*&Il\xb9\x8fK\xd5\x1d\xab}\xb4\x9cp+)e\x08\xddn[\x9f\xa6q\xea\x1b\x88\x93\xe3\x11\x99\xa4dE\xb9:Zn\xb7G\xad\xef\xc9\x15\xaai\xa2\x0f\x96\xa3\x19\x1c\xcd\xe6\xad\x1a\xc4\xf7M\xe3\xca\x00c]\xa4V\xe9EtE\xca\xf1J)\xdcc/[YW\xab\x84\xdb\xc9\x82\x07\xfe\xfd\xbf>\xbc\xe7\"\xaf\xaf\x83\x0e\x8b\xf0[\xc2\xed\xad\\p\xbb\x1dMJz\x00\xc4;[xS\xa7\xee\x14\xbbe\\\xc7\xe3*\xa0\xcf\xd5\x164\xe1t\xc1-v\x13!\xa02\x9f\xb6\xf3\x85/\x87\x15\x88p/ \x9cj\xb8\xd6\x14\"\x88\xeb\xca\x1a\xb1\xac\"\x92\xfa S# \x1e\x0e\xe3IA\xc3\xb8\xach\xf1B\xd4\x8f\x10D\x92(\xbd\x84\x92\xa2M\x93R\x9f:L\x81s2\x1c\x92IAK\xb6$Y;\xd2Z\xce\x85fY1\xcd\xb2\xa3!\xd6\xa2\x06\xfcZ\xd8\x0c\xe2t@\x10\x99D\xa4\xfcx\x9d~*\xb2\x9c\x16\xd5\x8d\xe9\xa2\xe1P\xae=\xb7Y_\xc3\xa1;\xe1&\xcb\xc7\xc04,\x03\x9d\x8fg\xc3\xa1\xe9aWqk\x8b\xad{{\xea`\xf6\x8fda\x18\xe3\xe9b\xfc&\x0d\x1847\x96\xc6\xfeY!V~\xc6\xca\xcfZ\xe5\xfb\x8b\xcfx\xf1S\x073a\x08\xbe\x90_\x1eBs\x8e\xa5\x15\xc9\x95\xd2\xcb\x1dV\xcc\xbc\xc7\xfev{4\xdbAH{\x10\x81\xdbe\x99\xf0\x84\x80-53\xc0\xaaE\x9b8\xe8\x88-QIN<\xdfE\xbd\xc2\x80b\xc6\xf0Y\x01jO\x9d3L\x86Cj\xcf\x9c\xf3Z\x00\x0c\xa8}\xea\xecj\x19\xceu>\xde\xfdo\x0c\xf6\x1e\xce\xcdh\xba\xe2\xd2u\xdd3\x9f\xb5\xd8\xc7\xbeI$\x12\xfeA\x13\xec $db\xe7\x05\xbb\xe2\xd3[\x17\xcc\xaa\xffDB\xfa3\x9e\xee\xa5\xfd\xa2\xd2h\xa2\xb0\xc8&\xb5\xac\xb0\xed\x88O\xe9v\xe0bN\xf6pE\x0b\xc63\x04\x91\xc6e\x9e\x90\x1b\xb5#\x82\x8f\\\xa1\xf4vvJ\x0c$\x8a\xd7\xbb\x1c\xaf\xf8\xae\x90\xdf-_o\x131\x99\xac\x96\xceg\xb5\x08[\x93\xa9\x01+U\x1a\xd4\xc07\x1c\xf2\x9f\xeb\xdc'\x15})`4\xb5&\xc5j\xec!\x0e\xdf$r\xa9K\xd4J\x06$\x91\xf3\x0f\xf9\xe9\x82;Ye\xeb\x92\xd2\xb4\xa2\x85\xe93\xb3o#,i-\x9d\xd7AH\x95M(\xb9\xa2=eyzS\xd6Kb\xef\xa2\xa7\x18O\x97\xc5\xd8H\xca\x0eu\xdf1{\xc3!\x19\x0eM=eR\xd0UvEM\xd4\x9ee\x0f\xed\xa0\xe9\xac\x0f?\x93WWB\xe0\xabIy\xc1J\x1ah\xeeN\xb2\"\x0e\xe3\x94$\xbc\x04&\xe06\xec\xb3\xc4\x1at\xa0hvR\x15q\x18\xd2\xc2tU\xb7\x1cm\x96\xae\xa4\xb0 aj\x00\x9a\xacS7N}\xd3\xe0\x05\x19\xf0\x93\xe5%#\xac\xa6D_>\xec#\x92\xe5)tk\x14\xbd\xbf:~\xc6\xee$g\x7f{\xd6\x8d\xc8\xf9\xa5\x95\xf3*\xc1\xeeD\xf85\xe1\xa8\x8d\xff\xbe\xc5\xd2\x99\x12\xcc\xd6\xfd\x95H\x92\xab\xb8=;\x92\x9c?I\xf7\x9f\xa9\x80\x93\xa0\xa8\xd1\xec\x11}Cd\x96&i\xeeE\xec\\c\x97r\xe2\xbc\x9a\xf9\x01e, \x80p^\xed\xf1\x84\x06\x1b\x0d\xd3\xa8\x11\xf1\x10*\xe4*5\x97T!\x9e\xce\xc33o\x1e\x8eF(\xc0\xae\x1d:\x10\x08\xc1\xf9#\x0d\xe3,\xad\xf9\x85\x89\xb84?\x9a\xa29\x95\xd8\x16Ku\"\x1c 5\x1eX\x7f\xfb\xcb\xa65\xe9\xe4\xe0\xa4\x93\xfeI'j\xd2{\xe6\xfc\xc0\xdc\xa9>d\x8b\x87\xa6\xae\x95`\xf5\xeb rn\x1a\x0b\x12<6\x15\xc2\xbb\xa38\xd6D\xec\x8c\x98\x08\xa4\x94\xd3G<\xa6\x93\x84\x06\x15\x84{Y\xbf\x8c\xe9\xa4\xca\xf2&\xae\x81\xeb\xa4\xf5\x94K \xc7\xa7j\x89\xa7\xf3\xe5\x99;_\x8eF(\xc6\xc4^:p\x81\xe3II\xab\xeeT\xb5\xb1\xc7\x95p\xb8\xe0*\x05\x9b>f\\\xa1\xdb\xa4\x87\xd3\x88v^D|7\x08A\xf2`\xf6\x92\xf0V\xf5\xe9@\xb7\x116\x8cCpG#\x1cs=[\xc0(\xfa\x95\xab\xd6\x94rA\xcdk)\xd5\xf1\xb42#\xb4S\xcb\xbc%\xcb\xb6\xdb^z\xbc\x90\xc6\xa3$\x14MDp\xc7\xd0\x15\x93\xfde\xfcUXa\x96\xd1x\xfd\xcb\x8aT\xb178\x8aWyVT$\xad\xe6\x92\xb1X\x037\xc9\xbc\x0b=g\x7f\x17@\xcf\x0d\x98\x8a%<\xc8z\xb2\xd1\xf12\xd6\x1a\x87\x14\xd6r\xec\\\xe7dF\x83\x01F\xb3Aa Ez5\xf0\x10HY\x9a\xa5\x15\x89SZt\x1bR\xe9\x06\xdan\x05Wj-\xa5\x8f\x9cv\x7f\xee\xd4\x92\xa9\x06\xcc\xa6\xa8\xa7\xf8/\xbd\xc5\x7f1`v\xcad\x86\xf1\xf7\xe5e\xc9 \xe4 7\x8cH\xe6\xd4\x83\xe9\xb0(Y\x03\xfb\xa6q\xe6\xc7W'\xe7\x06\xdc\xc6\xbeeh\x8d\x017X-\n\x06w\xc2\x1a\x96\xb7\xeb\xf0\xb7\x9e\xca\xaa?\xbd\x924\x8c\xbfdf\x1b{\xf5\x1aV4X\xafm\xb10\xf9\xd7{\x1aT8\x10\xebZK\xfe\x92\xe58\xe0K\x9a'\n\x92`\xb2\xcf7\xaf\xe3\xd4\xcf\xae\x1bqPP6\"&\x0b\x06\xa5WdIR\xcb[U\xf2\xaerm\xb9+\xf8\xd8O\xbc\xde\xcbxU\xb6\xc4o7\xb3a|M\x9a\xce\xfb\xc4D\xf0\xae\xd8p\x1ax\xea\xb4z\xb6x\x02GE\xb7\x14Kl\x17\xe3{o\xb8So\xd4\xd4\xbb\x16\xc6\xa3\x0es\xcd\xce\x1176>\xc7_\xfbtTA\x17\x93\xa8Z%&\xb9cV\xf97\xef\x05\xb7*\xca\x8eGR\xbc\x8a\xa0\xadv\x91H\x1a\xae-\"\xadIw\x07\x0d\x97j\xc1\xc7=\x07\x12LEL^Y\x9aF\xc36\x0c0\x04\x914*\xbc$\x19\xe9\x80\x12Is@\xec\xd4R\x04\xa1\xf2<1\xc5l\x1eu\x1dHT\x8fC\xe5\xae\x0b\x8c\xb1\xc1\x96\xa2\xb1\xdd\xba\x82\xe8Wb\xfc!\xad\xea\x00O3\x90\x02s/\xf4\xd3@\\\x83Z\xb1\xaa\xdb\xedJy\xbdW8\x14\x0b\x1c\xcd)^\xd5\xfb\x84'\xe6\x7f\xffZ\x1e\x9f\x1d\x8d\xc7hk\x8e\xc7\xe7\xbf\x96\xc7\xff@\xdb_\xcb\xd1I\x08\x86Q;\xf9\xc1@b\x95P\xec\xb21\x88\xce9%\x18\x88\x01\xcc#\xaa\x16Tm\x10\xc9\x02\xbd\x81\xad\x06\xb2\xda\xf5\xb97W\xa4\x88\xe5\xa05)\xf8\x9e\xcc\xad\x83^\x99\xeeq\xe4\x0b\xaf\x1b\xc7s\xd7\xf9\xdb\xe3\xa9-s\xc2\xf8\xe2rR{\x9d\xb1A\x0c\x90XY\n\x17\xe1\xb2\xe3\\\xdcnyZ\xcd\xb2\xf9WM\xa2K\x8eZ\x81\x98\x18w\x860\x0fZ\xde\x12-\xe8\xd4@\x8b\x0b|4\xb3\xcc\x0b\xbc?\x06\xcd'y\xb1\xe8\x1b\xf7\x85r)\xb2\xfa\x8c\xfe\xd6\xa2z\x80\xe0\x8e\xd6\xe0\x02!\x1d\xd1Z\x93\xc3\xe1\x9d\xa8\xbc\xdd\xcf\xa4E\x91fU\x1c\xdc0xL\x92\xd0\xa22\x8d\xe7UEWyE\xfdA\x95\x0dHU\x11/\x1a\x90A\xdd\xcf\xa0\x89Lg\xf9\xe9\x80\x8a\x89\x19\\\xc7U4H\xb3\x01\xdd\xc4e\x15\xa7aS\xd0\xe8\x1bQ\xd37\x1cM\x91\xda\x1f\xda%\x0c\x15\x1d\xff\xbd\xad\xd6\xc8MN\x0d\xe4 \xb9$!`K\x8e\xd9$J\xbdg\x16\xcd\xc5d\xcf\x85e&h\xc7w\x05\xa4\xe7X,\x9f\xe1\xf0(\xe8\xf8\xc1\xea\x80f\x86\xcc\xdaI]\x9a\x86\xa5\xc41#W\x99.BwJ\xd3`\xaa\xb3\x81\xe4\x8a9D\xe7\xc3\xe1>\n\x98J\x12\xa7\xa1\x81\xb8\x933\xc6\xef\x94\x933\x9e\xc7\xe31zg\xc7\xe3\x99cO\x1d\xcc\xd9\xc6p\xf8Nm\xa9\xc5\xe3\x19\xcc\xd0\xfc\x9d\xf0\xc1\xd9\xbc\xc9\xc8\xe9\xc3\xb2\xea\x82\xe1X\xd0w$B1\xf8\x18v\xf5^M\xcd\xf5\xe4J/q`\x8a}r\x99\xf1\x9b4w~\x93\xfaAO\xdc\x0bx\xcd\x96(\xdfak\x06\xe4\x9eysw4B\x04\xbf\xb3]6$\xf0\x99\x82\xc2Q\xab\x14\x0eN\xbf,U!\xb6\x83\xfa\x85\xc9\xeb\xce\x1c\x19KR\x0fW5\xd9\x1d\xf1\x0c\x81\xdcxu\x11\xb2D\xdb^\x92\x95\xb4d\xab\x9b1fT\xefd\xaa\x95C\xbaS\xc3\xa3\x19\x1e\xde\x8d\x1c<\xad\x07>w\xd9L\xaa\x89\xa3\xb6;\x9e90\xdb\xc7\xbbr!\xdf\xe1{\x16\x87\x1d\x84U\xb7.i\xf1Qy\x9d\xb1\xbb\xdd\xde\xee\xa4 '\xa1\xf2\x9b\n\x91\xf2\x82x\x918*\xd1\xdd]QS\x0d!\x0e&\xe2\xc0C]\xb1u\xd4\x00\xbb\x93\xee!\x03\xa6\xac\xec\x95\xdbn\xc3\xbd4\x04Q-\xf4\xbe\x90\xf03\xad\x18oPk\x19\"\x8c1]x8\xb0]\xbe\x89\xc9\x9b\xb8\xc9\xa9cy\"\x1e\x85\xd7]\xd1\"\xa4\xbe\x1ao\x1d\xfbt\xbb\x83\x10<\x1e\xa5\xd1j\xfd\xd0\xfeC\x176\xf0%J\xa5\xb4>\x9a)I\xad6\x11\x1a\x17F\x83\xcb\xee\x06\x0eA\xc8\xc7{\x85.\xe8\xcd\x9c\xad\xb7\xdb&\xaf-\xf7\xdd\x11\xe1|\xdc\x17\xf2\xddW\xf2\xdd\xc7t.\x04Q`\xfa\x93r\xed\x96UaNa\xc6E\xa9m\xa0[\x1f\xd7\xc93\xf0U<\xd6\xa9.\xe29\x15\xb2\xf9\x96T\x18\x8c\xc7s\xe4\xdb\x81\x83/L\xf6\xa7\xa35\xa0\xad\xc9\xd5\x05\xa1*H>\xd1\xd7\xfd\xad\x81n\xc3\xfb\xbb\x07\x1f\xdf\xee$\x0ca\x0b\x86\x08\x87\xac\xfbz\x0f\x0bN\x11\xf8vdO\xef\x06\x89\xc1\x1d\xb1\x95\xff\x00\xb8}6F\xe5/jO\x08\xf6\xd5\x8e\x90\xdf\xdd\xa7R\x8b\xcc\xef#V\x82 P;\xff\xfe\x11#X\xdf\xaa7\xaet\xd2\xb4\x89\x83x\xfc\x94k\x05b\xcf\xa9\xb5\xe0~sII\xd9\n\x97\x02\xc7\xb7\x8ef\xfb\xb1ej\x8b\x9c7\xdfl\x11\xfd\x83&|\xf3I|\x89SAj\xc3I1\x0f\xaa\x9b\xb3A\xcbr\x0d\xc5\x97\xa7{\xd7\xb0\xb7\xe3\xdd\x8b\x80\xee}O\xe7\x91\xde\xf8a%h\xae\x96\x08k\x05+`\xb5\xfd\x7f\xb3\x81J\x87 \xf6\x9b\xd7\x04&\x10\x84\x16\xd2\\\xe7*\xccO\x8d\x91.\xba\x9a\xe4L\x1d\xfd\xa9iV\x94\xfb^3\xd5\xf5\x82\"\x1d \xc3W\xcd\x00>\x9a\xee\x87\xeb\xb5\x0d\x06UT\x02C\x13]\xf1\xe4\xfc\xdfb\x8d\x84\xca\xe7\xda\xa1*\xee\x07\xe8xc\xef\x88\x0cl\xcf\x10\x04wL\x82\xb6o\x07a\x1d\x99r\xdeE\xd9v\xeb\x9f\xef\xa1g\xbbu\xcf\xa6\xdb\xad\x7f6]0\xaec\x99aM\xf8\xa2g \x19\xd0#\x8c\xc3\x85\xc9\xfex\xc3a \xfd\xa6Bs\xd66\x02\xfa\xa8+\x84\xb0[\x8b\xe1Y\xafu4E\xd6\x11\x93\x86}\xdb\x0b\x87&\xa4\xd5\x0b\xebA\xce\xcc\x83\x80\xf2d\x9fj\xd6{{\x13\xf5\xb8\xcf\xbbi\x8eiP\xd0\xe9\xe3\xfeJ|p\xed\xc4\x96x\xe2\x84\xd3\xe7\xf3>\xb0\xf1 \x01\\F{L\xdd\xe2\"\xec\xc0\x01\xc8Z|\xb5q\xd0H8\xc3\x98\x07\xf5\xc4\xb7\xa0\xe0a\xfb\xa5\x89\xe0\x12\x93\x96KW\x8b\xd9d\xcd_*Yy)\xd4N\x02\x01\x9a\xb7k\xbc\x88HQ}\x89+\xa1\xe3\x99\xee\x08?:\xf3\xe3\xab\x81\x08\x975\x98\xa9\xbf\xbc,+^\xe0\xfc\xd1\xe8p\xe5\x91qv\xe2\xc7W\xe7\xbf\xa6\xde\\\xbb\xa0~\xae\x84K\x12\xa5\xa1\xb7\x08\xef\xd1h9z\xc4(\xeeB\x11\x96Q\x87\x8e*\x05~\xe1\x8e\xa8\x8cXdTg\x19\xc6\xfe\x82\x15KE_\xaf;\xf0H\xe2\xd5\x0b^\x9c\xb7\xd1x\xbe\x8at\xc4\x9et5\xb4J\xf2\xcd\x9cn\x8e<\xb5k y\xe4y\xc9u\xc7:\xc6\x8c\x87\xc2\xa0\xdb\x00\x9f\xfc\xf7\xdfM{:~F\xc6\x81\x83\xfa~\xfd\xe3$\x16\xa1\xe0\x04m\xb7z\xe9\xdb\xd3\x1d:\xfc\xa1U\xe3\xde\x07t\x1b1\xe6\x13\xd6\x1b\xa0\x18\xe3'\x8b\xd9Sk\xa6m\xf0=\xe6\x1b|\x91\xbdtpl\xba\x13~\xc0E\x84\xb6\xbeI+3\xb0\x97#f\x0c=E\xc7\xe11E0\x85\xd3o\xbeQ\x8e\x00\xa3\x08]\xd3\x18E\x12\xf9\xc0V?2vMh\xef\x0e\xc15\xbe\xbd\x8b\xb5j\x9a\x7f[\xa8z\xbar\xa06\xce\n\x9e\xf79\"9-m\xd7\x81`N\x87C\xa5\xe5 R\x93\x92\xd1\x05\xd2\xe5\x1a\xcd\x17E\x0b\xd3Sz\xeaOq\x15\x89\x16M\n\x81\x94G\x9d\x8e\xb0?Y\x91\xdc\x0c@\x07]\x8dr\x12\xfb;\x84\xac\x9e\x16\xefh0\x98\xc4\xbe\x88V\xe9\xaa5\x9a\x1c\x11\n$\xb8-Tx\xfb\xa8\x10\xda\xa8\xb0\x91\x84*\xe2%\xa5\x9a{< Y\xba8\x9bb<\x99Nb_\x98V\xfc\x93\xbb\xfc\xdcfkx\xcf$\xe7\x8e\xe4\x0d\x0e\xbb\xd1\xa4\\\x8d\x87\xd6\xd5\x00\xfbJ<\x0f\x10G\xb7\x1b\x85*~\xf4\xa7F\x144E\xa4>O\x8b*\xf6hY\x07\x11\x899\xf8@\xf2:e#u~\xf5}\xd3\xfd^\xc5\xe9\x8al\xeao\xc6:\xaa\xfa\xdc\xfb\x1b_\x0b\x0cIHY\xf1\xe9m\xa56\xa6\x80\xdcZ\xeb\xd1gke\x15\x02\xdc\x86\x93O\x16m$\x0fe\x93\xc58\x85M\x9d#\xb5\xe9\xee\x9ec\xf6mO\x9d\xe1\xd0=\x13\xbfgN\x1d\xf1\xc7>O\x1d-\xf4\xfe~V\xab\x91tk\x91\xcbFn\x85\\\xb2t\x8c\xd9D\x85\x90\xc3\xc6\xd2Qk\x13\x07n\xbaE\xc5\x91hk_\xc0\xd7\xd7A0\x9e\\\x1f\xd9\xdc/W\xe7\x19\x08\xe4QY\xb2\xbbS\xfd<4\xa6\xcej\x95\xb6\xaa\"\x1d\x06+m+\x89\x01\xa6\x02\x86\xe6<(\xd7\x9chG\xb8\xd4\xf7\x18p\x8fL7\xf7\xbd6\xd0X\x08\x9e#\xbf^\xe9\xc3a8\x1c\x9a1v'\xcc\xd4{\x11\x17^BM\x9f\xbb\xf8\xec\x19\xd3N=\x08\xbb\x81\x88\x92 \xe3I\xec\x83;\x89\xd3\x92\x16\xd5\xf3\xa0\xa2\x85`m]\x1a\x85\x18!\x88\x86Cs){a\x10\x89>\x04\x92\xb8a\xf3%\xcb\xa1'm\xa4}\n\xdb\x07\xa2.<\xac\xbd7>^>\x14\x9e%\xba\xdb\x14\xd0&P\xcc\xd5\xbco\xfc\xc3\xa1I\xa4\xd1\"\x1b6\xfb\x8a\x1d@\x1ew\x16\xf5\x8e\xe3\xdevE\xb1\x03H\xe0\xed\xee\xa0\xf4H*6\xa4\xee\x16 \xcd\x01\nE\x8fr1)Z\x94+I\xf1\x0b\xc9\xa5\xb4\xc3o\xdd\x90\xb3\x08\x13;t n\x0eb\x84N\xa3\xa6\xc2\xb2\x93\x9e\xb9K\xeaU\xc6p\xc8\xbe\xe3\xb4\xacH\xea\xb1l.J\xe0\x02\xc7\"G\x8f\x9b\x8e\x87\xc3\x8bFk9]\x982\xbe\xf9\x07~\xd6\xc3\xbc\xb0\xa7\x0e\xaaU\xc6:q\xc6\x12\x83\x9eDd-\xeb&\"\x1ehM\xd5\xc7\xcc\xa9\xab\xf0\x0fd\xa9\x82!\x021\x00\x8e\xf0\xedV~\x88\xe3Q\x0b\xd9\x00\x9f\x0b\xcblC\x12\xed\x81\x11!\xa4\"\xb3[\x9cG\xce\x84pb\xf7\x98(u\xbe$\x85\x15\xd9\xdc\xe0\xfaWV\x84\xd8e\xbf\xe41L\xb7V8Vq\xaa\xca\xc5\xa9*\x17\xa7\xfb\xe5\xc8f\xd3i\xc2o\x9a\xd8tj\xa9,%\xe7\xfc\xb6\x98\xa3m)\x17\xec /2\x8f\x96%\x8f\xc6Ww\xdf\xdcg4K;\xa1}7\x0b\xf7X\xf6\xe4\x90\x8d\x81\xe6\xee\x91<\xd5pV\x8fX\x05Cp<\xb8\x08\xa4\x7f\x85!\xad\xceb\xa8\xf4\x11R\x86I\xebT\xbd\x81d\x9b{\x99/\x928\x17\xf1X}\xb5\x1a\x08P\x0b\x84\xfe\xd2}}\xb3\x11\xfd\xfe\xbeY\xadf\x88\xa85\xc6\xfe\xd2\xfd\xe3\xfe\xf9\xee\xce\x7f>4\xf2\x9f\xb5\xa1o\xf4\xa1oz\x87\xfe\xf3\xa1\xb1\xff\xb1\xfey\xbdz\xf8\x1b}\xf8}\xfd\xf3\xe2h\xa7]\xe2\xc3\xd3u-\xcao\x9d\x82\xeaY\x98{\xe4\x19\xdfS\x88\xe1\x1c\x96\xd8\x1f)#\x8b\x8e\xe9\xb1i\xc6\xe3\x86bNB\xc4\xcc\xe6\xba\xc0\xb1\x19\x8f#\x96\xaa\\\xe5\xdcS\xca\xa0\xfe\x91z\x95I` \x01\\\x80\xd7\xe3\xa5\xed\xde[d T\xab\xda\xf7\x98\x1dj%\xd2\x96&\xa3\xc2c\x1b\xa7e\x1d\x16\xdb\x92\xdbm\x85\x07b\x85\xfe\x96z\xb3\xec(\xa6=\x8e1q/\xd8\xc1[\xc1\xb8\x86\xb3\xb9\xd7\xc8\xe1\xdd4rR\xf9\x1b{\xd8\x92\x89\xe0M[8\xbem \xc7\xc6\xb8\x92\xecMJ'\x19q$K\xc9\xc4\xb3S\x05@\x8a3<\x85\x8b\x9aeo\xc6\xcd\xc2\xc0x\xba\x98Y=9\x904,\xbe!\x8fNy-Gy\xc2\xda@\x8cgL\x8a\x9a\xc1Y|\xfcd\xbb\x0d\xd9\x1f\xc4\xd5@\x11L\x1c\xa3\xdb\xef\xd4\xe4\xf4i\x99\xc3\xa1\xf2\x13\xf5n(\xc4\x81\xf9]\xbd\x14\xf5\xbb\xb7\xb4\x15Z\xea\xad\xbd\xb5WLz6\xd4\x1e\x8e\xb1;\xf1h\x9c\x98q\xb79\xed\xda\xae\x075\xc7X\x9e\xde\x1c\xa4\xa3\xbd\xb6\xcd\xbbam\xf7\xc9\x10\xf5\x96o\xeb7\x00o\xb7\xad\x14\xd6'\xca\xb4~ 88\xa0\xb2\xaf\xc3\x87\xc0\xc1\xc1X\xed\x81\xd1\x19:\xd2{\xde\x85\xe31\x1c\x90\xa1\x82\xb5\xd6\xf3\xda\xbe\xc3\x8c\xf5\xc6\xdb\xec\xb0E3\x83\x14B\x08 Ap\xc9L\xd7\x02\xdb\x97\x0e|\xc5\xcf\x85\x81\xf6-~\xab\xbb2\xdf\xe1\xe9\xfc\xdd\xd9\xb7\xf3w\xa3\x11*\xf1\x1b\xfb\x9d\x03W\xec\xcfh\xe6\xc0\x1a\xbfe\xdf\xd78\xab\xb9\xa0Y6t\x8f\x8e\xcd\xe0\xe4\x02!\xb8\xc1\xef\xce\xbe\x1d\xcf\x16Z\xb1\xab\xfdbV\x00\xcf\xf1\xf5\xc8\xbc\x19_\xa3\x93SX\xda\xef\x1cl\x7f\xddn\xa7\xf0\x1c\xdeq\x00a-\x95\xb7\xc5;\x8e\xc9w\xe3Yc\xe8\x9ab0BM\xbbd\xe6\x8b\xa6\xd1!\xcb\\\xb7\xb4\x89\xb5F\xb9\xb0ni\x13km*\xe0\xb2\xe6\x0b\x972\xf4\xe3\x1a\xd2Q\xe8 \xc81\xff\xa9F\x14\x8e\xc3c\xd3\\\xeb\xec?A\xc8\x01Y-\xaf\xe1\xc9\x11\x9a\xbf`\xa0\xbed\xff\xbc\xc2\xc5>\xb6_ql_\xe2\x82a\xf7\xb2\x8e\x98P$\xaf\x99\xb7l\xd8\x12\xaeK\xfb\xb2\xe6\x14\x00\x0d\x80\x8d\xc5t\x84[\xb6\x10\x93\x825\xecm><\x1c\x9a\x8ci5\xdb9z=x\x8b\x1c84\xa2>>\xd6\x1d\x85\xde\xd8\x1d\xa3i\x83\xd4\x0c\x88\x9b\xc5\x87\xe4D\x1f\xd8\xc2D\xfc\x8b\xc0\x16\x8d\xdd\x05v[\x846`\xa3}\x97&w`\xbdo\x12\xea\x9d`\xe5\x13\xc2)P\xcd\xf1\xbb\x17\xa8\xe1\x92\x02\xdf\x1cr\xfc^+\xd7\xafK\x8a}\xcf\xaft\x97\xab{\x06\x9a\xcd\x0e1\x0cuc\xa3\x81\xf8 \x9a\xb4\xaf\x80\xbc\xc7Q\x16\xc9T\"\xf4\x16\x1fMa=\x1c\x9a\xa2)\x9c\x98\x1f5\xef\x0eB\xf0\x11\xaf\xcc\x8f \x1cdW\x1d/\xc3G\x04\xd7\x1d\x9f\xc4G\x04Wg\x05\xdf\xff\xbcBp}^r\x82\xbf\x96\x07\xcb\xe4e\xa5\xf8\xed\xfef\x0bVAwjV\xf1\xb2\xfe\x96\x93\x88UH]V\x91\xe4\xdb\xba\xd8H&\x8bH\x9d\xb0F\xd3\xf1rd\xd2\x9aw\xa1\xe3t\xdf3\x0f\x97\xc3\xa1\xf9=\xcex\x9cQs\xd8?\x8378\xe7i\xcd\x81~\x04_\x99T\xdf\xe0\xb7\x0b\xdb\xb1\xbe\xf2\xb3\xa5\x9fY\xca\x17\xb5\xb9r`\x92\xe2\xc0|\x8bn\xdf\x89\x99\xe28\xfe\x81U\xfb\xcc~Ma\xc3\xfe|\xe1\xbfy#\xef\xf1\x14>4\xb1\x9e\xef\xcf>\xcc\xdf\x8fF\xe8#\xfe\xc1~\xef\xe0\xcbEl\xbe\xb3\xdf;\x8c\x9a\x90\xc5\x7f}l\xd4\xa4\x8f\xe7\xd3\xe1\xd0d\x0d\x8f\xf0G\x04\xc5\xd9t8,\xcf\xa7\x8b\x8fg\xd3\xc5\x17\x9e\xeaN\x88[\x9a\x1f\x91\xb5\x11\x85\xe4_\x99<6Y\xc9\xd2*\x10\x82\xafB7\xf8\xa8\"\xc1>\xf2\xbe\xf9\x18x\xdf\xfc\x97\xa4\x99\x0b\xf3#\xd2\xe0\xa8\xab\xaa\xfb\x0d6\xf8\xbb\x0e\x95|m{\xb50\xff\x83\xdf\nP\x17\xa5\xf5\x1d|\xc2\xe6\x7f\xc6\xaf\xd0\xc9\x8b\xe3\xbd\n\xf0\xe9\x08+\x1b\xeaS\xed?jC0\xc6\xa7\xf0\xa9)\x84\x90\xf5\xa9\xc7\x03\xc2Sn\xc4.\x12\xfe\xa4oBK\xa4g\xe2\xfa^\xa3\x89\x9d\x93)\xdf\xde\xbca\x9c\x15\xef\x15\x84N1\xee\xcc\xc0\xd2\xf3\xdc\xdbD\xb3c\xd8\xad\xf5\xb0\xc6\xd5Q\xf1v\xaa\xa6\x8b\xf6C\xc4\xd5\xd3\xbe\xacZ\x07\xe0\x17|\xe0\x17\x07\xf6.\xf9~\xac8\xedS_mv\xb2\xcf\x9c\xeb\x9b\xaa\x82\xb3\xe9v\x1b\x9c\xeb\xbb\x1d*(\xc4\xb3\x82?\xb37 ..L\xadY\x9b8\x88\xdfJ\xc6`\x94\xfb\xa8nk\x1f\xd5\xc7\xaeM\x1d\xf0\x04\x9bR\x9b\x9a*f\x18\xc4%\xcb\xbe\xbeU\xe9\x91\xc4\xe3\xca\x92I\xc1\x07\xa2m=\xeaW\x9b\xd5\xa5:\x11\xa8\nQ}\xf3_\xbb\xe6\xda\xb3\x00mG&\xdf)\xd4\xa3\xf6\xa4\x1c]\xc48\x92ju\xfb\x16p\x03Y1v\xcf\xa6\x0b\x99\xad]Xm +\xaa\xb5#e\x0c\xb9\x183&\x115\\D\xe6H\xeeU\xf7\xa3e!\x08\xf9.\"\x0f\xc56]\x84\x161^Z\xc1p\xa8v\xad\xcf)\xaf\x19\xd8\xb4\x15\x18\x16\xa3El\x93\x7f\xc6\xb2\x94c\xc5\xca\xcd\xb9Ofm\xdc5\xdb\xc8\xedx5\x85-\x8d\x87A\xcc(@y/\x19)+\xd7\x9e\xc4\x94\xf2\xdcI\xa7\xe9\n\x93\xe3}\xf2U\xb1hm\xee\x02\xf2\xe4\x9dd\x1d}\x0f$\xcc\x03\xac\xc7\xb4,\x02\xcb\x0e\x1c\xb8\xae7\xf4a\x83\x03\xbe\xbb\x8a3\xee\xc5\x80\x80\xa9\x17\x99\x19A\x00\xf2\n\x81Jm\xe4\x87r\n\xd7\xb5m\xbd0KL\x17\xfb\xe9\x0d\xb1\xb6\xa3\xa5\xcc\xfd\xa2\x102\xd56?\x9f.\xf2\xf1\xcc\xca!i\x9c\xd5+\xb8l+b\xe3\x19L\xa1\x84\x12!\xcb\x9b\xaf\xb1\x08N\xb8\xc2\xd3\xf9\xd5\xd9\xf5\xfc\x8a\xa9\xa1l4W\xfc\x06\xa1\x8b\xe1p\x831\x96'w\xben\xb77\xcd\xcdr7\xf8h\xba[\x9eO\x17E\xcd3\xd2cS\xe8\x1e\x9bq\x84N\x96\x08\x8dfV\x81g\xb09\x8b\xb6[\xde\xd0p\x98sw\xaay\x89\xd7\xb0\x1e\xe1\x02Y\xe6%\xce\xc7\x05\xe4c\\ (qg\x85^\xc1\x86\xc7\xa8q\xbd\xf3\x10BJ\x08\x99E$8\xc0=c/\xc631z\x15.\x157\xdb\x9d\xb3ElO\x19\x05\xef\x9b=\xeaby\xfc\xf5^\xdbG\x15\xed\x0b}imn\xd4\xf6\x0d\xed5\x80\xe2\xbe\x02-\x03h\xfe\xf5\x8e\x10\x1a\xd1\xd5C4\xf3hO3\x8f\xfb4\xf3h$\x93\xd5&$\x0f\xcerA\xec\xb4\"]k\x0fj\x1e}\x1c\x8dLW\xd3\xdac\x8du\xd0\x87\n\xe6\xbd\x82\x7f\xa5`~X\xe3\xffc\x82\xf9\x01\x91E\xfa\xfdrj\xa9y}\xe2\xf9\xcf\x08\xde\xbe\xa0 =&\xc8\x93\x92\xb4\x9d\xd1/V;eH+\xb8\xa7_\xae\xaa\x1b=\xb4\xaa\xd0\xde\x97W\xfbb]\x91\xbb'\x89\x85\xc8\x0d!\xaa\xb5\x15~\x8b\xa2P\xc0\x08B\x8b\x10G\x16\x1d\x0e\x95iw\xee.BLm\xd7\xb1<\xdbu\xce\xa6\x8b\xb0\xbe\x89\xb7%gY\xee\xb9\x96\xab\xbd$a \xabN\xd6\x9f\x8e`\x9c\xf9\xb08\xf4Z\xe7j\xda\xa1!M\x98\x92\x98g!\xd7\xb4\x07}\xe6!\x0e\xf4\xc3\x1b\x10\xd7\xfb\x97\xe1\xc9)\x82e\xbf\x08\xf4m\xc2\x86h^\xe0\x18\"\x1c\x8fg\xc8bI\xe7EJ\xb26\xb3\xeb\x8b9e\xf2!Q'\xb3\xe4\xae\x9b\xd2\x9e\x86C39\xc8\xad\x13\xa0\xdc\xbc\xaa\xd94\x1bO\xd5a\xd3\xec\xff\x13H\xfa\xdf#^\xda\xa7\x8e\xc5\x12`i\xcf\x1c\xec\xb3\x7f\xdaY3\x07\xa2\xfd\x90\xb8x?\x80\xaeV\x94Kj\x88\xe3\xa6\x11\x8e\x98\xe1lM\xad:zW\xe46<:j\x98r\xacs\xe1x,s\xca\x1e\xae\xdf\x91\x04B<\xf8\xa4\"u\x92R\x85\xe9\x01nm<\x99L\xe9\xca\xb0\x02\x9d\xbf\x88\x99\xef\x9c\x11er\xa8VS\xebS\xa0\xa8>b\xbc\xdd\n\xfe\xa2\x9d\xdc|\xb8v\xd9=d)\xa3\x9dB*\xb6\x88\x9eW\xb2L}9\xbb\xe0\xc1\x1abD\x14\xbb\xb7h'Y\x7fI\x8cz\xfdT\x13\xd1\x0f\\\xf7\xb1\xa2\xfa\xe45r@\xcc\x86P7\xff\xc4)\x18m\xa2\xf9U\xbfs\x9f&\xb4\x92\xd7(\xc9\x81z\x8e\xba\xdf\xdf\xd5!\x94w\xfd\x17\x86%\xd9\x9ed\x8e|\xdb\xdbm`e\x8a\x94v\xa7\x7f\xde)\xaf\xbd/e\xb6KV\x9d\x92r\x96\x89\xdc\x9ai\x83\x8e\xfdI\xec\xeb\xb4l\xb3\x04G\x9d\x91\x96S\xbew\x8e\x86_a\x0d\x1a\xe4\xf7qu\x11g\xdf#\xf3\xb8x:6M\xbf\xd6h\xd0I\xb3\xd2P}\x83\\+\x96N{\xf0\xcb@6\x19k\xc7\x1f\x9a\xe3G=LR.(ugiK~7Z\xe7\x14\xa6@\xc7\xea\x8a\xd0\xb6\x0f;\x80\xa0\x1e\xb86\x05{\xc4\xd3\xb2W\x18\x97\xf2\xef\x1a\xbcwh\xf0\xb4g\xf0\xddW\xd0\x8c\xe6\x99\x00~\xb3\xe8\xa1\xc1\xd3\x07\x0e\xfeN\xc5\x04\xfc\xf1\xec\xae\x12O\xd0x\x06\x14h\x8d\xa4\xee1\xf9^\xfcL\xff\x18~\xc6\xfb\xc8\xd1\x1e\x8c3\x10\xb3vz\x89NA;C\xed(J9\xcd\xf4\xf8\xb4\xff\xa4l\xf3\x10X\x1b\xe9\xe1A\xa4\x87\x0fB\xba\x0f\xed\xc9\xe9\x19\x0b\xa3\xbd\xf1\x0cB\x08\xef\x8b\x16my\xdd\xbbg\xd5\x80\x1fz<\xfa\xf6\xde8M\xf1\xce\xc3\xe9\xdc;#\xfc]\x8e\x1ef\xe5u\x03;Z\x1c\xc4(\x8c\x91\xb7'\x16m\x9e,\xd8\xce\xbc\xbdD\x9a\xfd\xcd\x83\x8c\xee\xee\x0e\xf3\x99\xb1\xd7\xdf$\x9f\xf1\xbeZ\xba\x87==\xd4\x99\x92\x88w\xf6SM{\xfa\xa9\xa6\xb2\x1f\xf7\xae\x18\x89<\xa6\xf8\xc5=*W\x1e\xf7X\xa5^\xdbA8\x85x\xfe\xe2\xa0\xbe\xe5\xb5\xf4\xad\x07\xe8(R%\xf3\xa4\xb5\xea\xd5\xd6\xea\x01\x0dE\xd9:\xda\x15\x90\xe2v\nq)\xaer\x9fL\xe5\x15M*a\x1e\xf3\xebLF\xd8\xb3cG]N[\x91\x04G\x87L\xab\x82\xc7'\xd7\xa6)W\xf4\xf6\x18\xc4>\xa3f+\xff\xffI\xa5\xe6w\x9aX\xf2\xb1\xc5N\xb2\xe6\x00<\x9eM\xa7\x07\xcf\xe2i/O2)\xfa\xcf{J\xa8}\x9c\x96\xcb\xeeO\xe8Pb\x99|\xe6\xba\xbf8\x1d\xdc\xa7[\xf5iV\xbes\xb7v\xe2\x83\x87\xf69\x8f\xeb`\xaf\xab\xecxB\xd9Q\x1c\x95\x03\xd32szh\xa5\xa3\x91(I!H\x15\xea\xeb\x01\xb5\x977\xf9Y\x04\x99,\x90\xc7#\xeeO\x8f\xdd\xc9\xa77\xcaZ\x95\xd6\xaf\xda\xaeb\xb3\x07+\x1c/D\xa9c3>y\xfct\x8a\xaci\x13\x93?\xbf\xc4\x17zdd\x8e\xa7\xf3\xfc\xecr\x9e\x8fF\xe86\xc5+\xc8\xf0\n\x12\x1e+\x92\xe1\xd5hyl^\xd8\xb9s\x92\x88\xab\xf0\x08\xc68W{O\x85\x02\xafM\x13\xf9?\xfb\x92kB\xf0y\xf8\x8f\\}{\xa2\xaf\x80\x80\x1f\x08cR\xeeSL\xc5D\x87L\x82\x8d#>\x08\x0f\n4_\xe1lw\x8f@\xeb\x9c\xdc\xbc\xcbK '\xa1>\xac\xd9\x9e\x04\xf9v\xce\xd1\x8b{\xa5_8\x1c\x12=\xa3\xf3\xf5h\x84Vb\x83\xd5\xb7\xd7\xcey>\xce\xd4\xb5\xbb\xcd\xab\xce<\n\x9b\x8d`\xed `\xff\x9e\x15\xa3C\xc5JQl.q\xc4\x10@\xc73G\x04EJ,\x94*\xb1\xbd\x8c\xb1\xcdg\xd0i/%\xbcj/\x0f\\Bk\xd9\xaa W\xb5\x14q\x05\xd78<1/\xc6\xf1hV\xefrt\xa7\xd3LT\xb8\x9b*\xa0\x9d\xe2C\x10\x8e\xb9Vq\xb8\x80\xdeEz\xb6\xea\x8893\x1d\xc7\xe8\xf8z\x94@tr\n=\x0d@\x1by\xfa%\x07\xed\x9c\xd7}'D\xa0:/\xbb=V\xff\xa3=\" \x8d\x95]G\xd8\xe7\xb2OT{2\"\xee\x0b\xa8\x0b\x14\xe3\x1c\x1d_\xb7\xb2\xff]\xf7\xa7?bn\xe8\xa9\xfdc&\x8dK\xben\x7f\xb5\x0f\x00S)\xee\x02\x90\xe5k\x9bB\x7f\xbc\x9f'Z?=\xf9c^B\xf6\xa4\xbf\xc3~og\xe5=\x83*\xfe\xa2A\xf5\xf5\xa3\x0f\xaa'\xff\x8f\x0f\xea\xf2>R\xe9-\xf0\xac\xeeL{\x85\xbe\xd5\x97\xcc\x16B\x8e/\xec\xab;\x17v\x1fl\x9d6\xfa\xd0;\xbe\xd2@}@\xf9\xd1U\x07\xb4\x87a\xa9\xbfe\xbd\xf36)\xf4\x97\x1fu\xcb\xdf\x0d \x03E\xd7{\xbf\xeb\xbd\x06Xl\x8fj\x1a\x85\xba\x1a4\xf6k'\xf7MN\xd5\xa3t\xa4\x08K\xec\xef@t\xd2\xa3Kj-IH\x9a\xebN\x84B\xc4`\xf9\x9e\xc1\xf2[\xbe)hH7\xd6 \x7f\x11\xdb\xcc7h\xf1ky\xfc\x8f\x93\xb8O\xd7Vo\\\xe8\xe7\xa4\x85\xd6,\x01\x96w\x92\xb6<\xf3\n\xec\xbd\xbb\x89<~[\x90\xe9a\xf1\x88\xa1\xbc\x8d\xd9\xeb\xdco\xce+\xcbc\xf7\xef\xdb\xcf\xd6\xa9\xab\x96\x98\x01\xa0\x0f\xbd9pg\xdb\x04\\\x07l\x0f|\xc7\xe1\x05EC<\xb7g\x0e\xf4V~\x0b\xa9\x88\x980\x0d\xfe\xc7\x00[\x96sd+B><\x00 \xad)Q\xa7i\x8b\x15V\xed)\x0b\xb4\xb7E\x08\x0f\xb6\xa9\xea\xb5[\x85P\xb5\xcb$\xcb\xef\x83\x92\xd5\xd8\x87\xb1yz\xf6\x10\xd1\x899\xe3%\xb5cZ\x87Jk\x14\xb1\xf7\x04\x1f\xba\x95\xb7\xd6\xf3\xf4A\x9aU\x03\xbe\xa5\xc8z\xa7\xbe\x81v\xf0\x9b\xb8\xce\xc7j\xabs\xbe\xe9\"\xf5N\xca\x0e~c6\xff:!\x15\xe5/\x1c\xb4\xdf\xbe!\xcd\xd3\xc4t.mu\xb5\x1e\xc4\x95x.\x02*|hZ\xac\x12\xa6\xf6\xcc\xb1\xf6R}\xd3\xd3\x9e!\xe8m\x8e\xb4\x9b\x13\xf1*\x9d\xd6D\"oL\xbeT\xb3\x83zb:c\x15\x9b\x14\xefG\xa3\xfa\x8e\xd8\xfa\xd9}\x0fAJ\xaf\x07\xdf +\xd8\x03\xf1\xe4\xb9\xc6\x0cZn0\x89j-\xbb\x0f\xe1]\x1fV\x07\x9az\xbe\xda\xa5\xfaZ\xea^\xc4\xd4\xdfR\xb7T?L\xdamH}cj\x15\xe8k\xa1\xf1\xaa\xeeQ\x86l\xa2)\xd1\x0fA\xd7@\xac{f\x19=Uv\x08\xde\xe0\xd0\xfc\x1e:\x92\xa0\x0e\xb0}s\xc0\x99\xe1j\x8e\x0c\xb1\xd2\x0e?w\x0c\x81\xe0\xae\x01\x0f\xee\xae\xb9k\xd0\xcb]\xa1\xb5\xf3$\xdf\x17R\xcf\xa0\x19q\xca\x14\x9f1\x7f\x0d\xcd\x00\xf1\xec\x90\xab^\x1b\xa2 n\xed \xc9\xf3$\x0eS\xcb\xa8\xb2\xbc~bH.Q\xbd\xf1z\x00=K\xb3~\x0fX\x01\xa2\x7f\xe8\x8e\xfc\xfe\xfb\xa1[o9\xe9\xc1\x85\xb8u\xf1 N\x12Q\x981\xb8\x1e\xd6\xab\xb984P\xf9\xf1yy\x9f\xb3\xdc\x82\xee\x9d\x0d\xfdR\xee?\x83\xeb\x1d\xfcv@V\xfb\xda\xc3\xf8\xfb`\x8a\\\xeeI\x0d'.\x0d\xe3\xf4\x13\xe1o\x8d\x85\x13\xc6\x0b\xbed\xa6+\xae\x1f\x18M\xbe\x01\xfes\xc6~\xaa\xc7\xeeg<4G\xfaC\xa3\xb3x\x1e\x8dF(\xe4(\xe5U\xa3\xa6jTW\xf5\x05rC9\x15\xa6\xb8_\x9b\xa70l\xb7\xc3\xf0~\xd6\xf6kD\xca/\xaap\\~\xca\xe2\xb4z#@\xd6\xeb\xe8\x0d\xfc\xd2\x9c5\xeb\xc3\x1c\x97|\xfd\n\x8a\xaf\x02C!V'\x99\xf7\xf0'r\xe7\xcb\x16\xee\x96\x13Rx\x02\xb50\x05\xb9-p4{\xd8\xb8\x96\x7f\xd5\xb8\xd4\xc5\xe3\xcb\x06\xcf\x91J\x11x\x96#\xefW\xa5\x1e2\xfa\xde\xb1+\xba\x01_a\xa2i\x8c!aY\x93\x87(\xc1_#\x91\xb5\xa3=\x90\xe3\x06\xdc\xff]\xf4I\xe4\x1c\xd4\x07\xf7t\xccF\x93&`\xdb.x\x0e\xd8\xee\xc8o\xfe\x8e(\xfb\xd5\xfcuD\x1c\xe2aUCo^\xee\x11\x10\xbeG@\xda\xbcA\xbeB\xddl\x8b\xeb\xe6\x03K\x06~\x87\xee}J\x89\x16\xc7\xa1Z\x06\xb9\xa9/{w\xc5\xf6\x1a_\xfc~\xb3\x0d\xcc\x9f#\xf7l\xdf\xc1\x980#\x85\xfdr\xf9M\xb4=\x9b}\xc4\xd9\x07\xa4|\x08$\x8c%r\xbc\xcbG5\xea\x8bu\xe5\xc3\x1e\xc4\x0e\x1c\x07\x1fMe\xb6\xd7\xca\xa6\xd8\xb3\x03\x07|\x9br3J=E\x13\xc0\x0cA\x0f\x90\xd4\x81\x10\x07\xea \x11\xb7\xd5T]9\x84)\xb8v\xe04xWh\x12\x89\x0ef?\xee\xd5\xe0\xfa\xd1\xde\x87\xe28\x10\xb8\xc5\x98\xa0\xdb\x1a\x0c\x7f4c\x80\xecC!'KP\xd1\xee.\xfdO\xdb\xda\xadA\xf0d\xc8H=xO\x82\xe0\xda\x9e\x04\xc1U x0\x93!k\xbb\x033~\x87\xe6\xb8o\x88\xff\xdc6\x8a\x7fQV\xb1\xf2\x0e\x1c\x16\x84\xf7\xecA\xd4\x83k\x0d\xb6\xe7\xfa\xce\x96$\x97T\xe7\xff^y\xbew\xa9'\x15\x97z\x06\xe2\x1c++o\x1b\x9cm\x18\xa3@<\xf8#\xf7\x19*\xfe\x04\x1aw^\xc8\xc3\xf8J#S{e\x87\xb5\x04\xae3\xbf\xed\xd7\x99\xbd\xfa`\xe6\xfc\xedA\xbd\x99\x15\x02*4b\x8a\xa9\xae\x11\xd3~\x8dXL\xc7\x01%[\xbeT\xf7;\xb4\xe6\xfa\xbda\xa3\xa0 \xa9\xe2+j@vE\x8b \xc9\xae-\xf5&hW\xb7\xf6`E\x8a0N-c\x9ao\x0c\xc8\x89\xef\xc7i(\xbf~\xa7\xdaM\xefT\xbb=}\xbc\x7f\x95\xda\x1d\xe0GgWVXd\xeb|\xe0eY\xe1\x8b'\xed\xb11\x1dL\x0d\x91R\xc6_)6\x1e\x8d:\xcd\x8f\x8c\x811\xea\xb67zd<\x1a=\x1a\xf0\xf7\x80q\xf3\x803q\xcb,YWt^e\xb95\x9d\xf3'.\xa7s\x81\xc9\x9e\x86\xf3\xcd\\\xc2\xdb\xd7C\xbe\x99\x1b\xe7g'\x12\xea\xf3G\xadqJ\x9e\xe7/\x89G\xd3\xea\xfb/\x1f\xde\x9b\x86K\x83\xac\xa0\xafR\xdf\xa8\x0d\x1b^\x15wi#\x8a\x13\xbf\xa0\xa9\x89\xea\x8b\x8c\xc5\xaa\xa6~\xfd\xe6j^P\x91\x86\x0d\xe3\x81\xba\xaf\xed\xe8\x8fS\x82\x08\x89K\xf1\x14V\x0d\x8fK\xcfV\xf3\x94\xa9\xb0v\xea`\xc3\x18\xb9v\xca5X\x03\xe4\xefY\xbd\xbf\x1b\xe1P\xb1?\x1e\xa8\x1c\xe0@\x9a\x0f\x01\xc4\xd2\x94`3\xc0\xf4\x18\x1f\x1b\x01IJj\x0c\x1eY*\xed'\x89\xd9G\xa3\x80\xe1\xd2\x90\xc9\xdc-\xcaR\xfd\xd1#c\xf0\x08\x96\x98\xca\xa6\x98\x12\xd4\xb4\xc4\x1a\xaa\xaf\xedb\xe5)+\xaf\xcaT\xc5\x9au\x06\x178\x14\xe1\xf7\xa1\x1d\xd6g3\x9d\x85\xb1\x19\x18\x96a@\xc2\xa9\x8e3\x8f\xbf\x88\xea\x06\x8c\xecb\x9f?h\xc2\xdb}4\"\"9\x1e-\xef\xa2HA\x8b\xf9F\x90fMy-\xb2\x94\xbd0\xba\x144k\xec\x81\xc5\xa8R\xad}\xd6\x8ad\n\xec\xa7\x80-'U\x84\x8d\xd5\xe0\xd1\x88\x8d!\x19\x18\xa3P\xbd\xae00\x10\x1f\xd6\xc5\xe8\x11\x7f\x83\xc5\x180\xea\xe6\xa387 9h'h\xba\xb2\xa05q\xa5\xb3\xf23\x8d1\x05\x9f\xfd\x13K\ny\x18UD\xbdT\x11(\xaa\x08\x1fF\x15a?U\xb0i\xcf\xaeH2\xf8Cs\xc5\xa6\x88\x11(\x9b\x08\xfe\x84\xbbe\x8c\\\xf1\xa5\xe6\x85\x1e\x9f\x8a\x049\x8d\"\x85\x8d\x88\xb3\x0c\xd6\xf7\xf9#\xb8\xb8\xc3\x02\xd1\xec\x0fX\n\xbcv\xee0\x9d\xc7\x81\x19\xf2C\xeb\x02\xd5\x861\x8f\xc6,AX\\<\x8ew\n\x91\xfcD\x90hW\xc3\xba\x13/+\xcd\x10\x1d\x07\x08V\x98j\xe9e\x9c\xca\xf4t\xaf|\xc4\xd3\xb3\xbd\xf2<\xbd>&9\x1c\xae0\xc6\x19\xdf\x00\x88\xc6\xe1\x19\xef\xbd\x061\xc1\xac\xdd\x00V8\xc3T\x1dM\xd7\xeb\x0d\x87\xaa\xd2\xc20,\xf3\x02\xdb\xfe8\x00:\x0e\x80U\xa3\xa3@a\xc1\x81\x1c\xc7\x0f\xa6\xa8Y\x0f5\xc5\x82\x9a.\xf1\xf2a\xd4\xb4\xec\xa3\xa6\xe2\x7f\x91\x89\xe4\xa3\xcb\xff\xbb\x98\x88\xcfE\x03\x1d\x19\x83k\xc2X\x87\xceK\x1e\x0d6\x83\x1eFR\xfcE\xa6\xa52$\x1b\xd3R\x19\x9b\x9ai\xb9\xe7\xaej\xc4n\xeb\x05\xb7\xc3Fhc\x1b\xd4\xca*\xd9WV\x89TVu\xb0\x95\xd0^\xb4\xfa\xbcS/p\xe5]\x18\xb5x\x1f)S\xe0\xcf\x98\xb4\xbei\xfc]\x91\x921\"j\xc7\xa1\x1e\x8f\xbb?\x1eW\x8e\xc7\xb3\xa7\xce$[W\x12Q\xf4\xa1\xb6k\xa7Kq!>6\x0ch\xacIi\xc8\n\x03!\x10\x06\xc2\xa8\x03\x98\x1d:=\xb0\xb1\xd4~\xf0d\x8b\xb3yx\xd6\xbc;3\x1a\xa1.8\xa1\x83\xb47\xfa\x1fd\xa0\xfeY$\xf6\xcdXR(G\xc2\xb7>44#_\xfe3\xff\xea\x7f\xe9\x895\xc3\x07U\n_|G\x7fp\x1f\x08\x9f\x10\x01#\x00\xa0\n\x9a\xbf\x03\x08\x04E\xcc\x85\x10\xbf\xd7^\x8d\xcbA\x04\x10\xc0\n\x88\xb0]\xe7\x05\x11`\xa4\xec\x18,X\xde3\xc9\xb6\x9d\xf0\n\x92C\x98\x0b\x05Q\xb8l\xa8\xc8\xe7\xde8\xcag\xdd\xb0\x9f\x89j\xf4\xde\xec\x8e\x00\xd05'B\xd7\xa4.|G\x0e\xf4\xc5\x8cV\x0bL\xf4\x17\x19-Go\xcf\xea\x8a\xf0\x96\xa4\n\x08X\xc9\xcd\xcc\xe4}o\x01\xe4\x8d\xe5&\xaf/\xd4yy\xae\xc6\xc9\xb5\x041\x19\"a\x17g4\xec*+PpH1j7\x07\x91\x85\xddx\x84\xde1\xbfm\x17\x8c\x80W\xce%w\xbe-\x9d\xad\xa5\xfc\xe1\xcb\x0b\x04\x85L=\x83\x83\xc7Y[\xbf\x18\xf5\x86\xae$\xbfIc!\xcb\xd0\xd4\x83j/D\x9c\x8f\xe7\x92\xd8\x82\xb5\xa8\x0f\xbd\xd7\x1aA\xacAC\x80d\xdb<>\x80\x87\xaeb\x07\x99\xbf@\x90\\-\xaf\xa0B\xb1\xb4\xca\xc0@\x1bd\xedRv\xae\xb6\xd8\x1c,r\x881h\xe6A\xbbc\xe7\xde-k:c\xe7Rg\x0e\x80\xd4CPz\x14Y\x00v\xf6;.HB_\xff4\xce\xb5P\x95^3\xb0\x16\x90\xddop\x8f\x12\xe7\xc0\x18\xd4{\xc8\x02(\xe0L\xa7_[)\xc0\x1e@$P)\xcf\xb1\xb2z\x19\x1a\x1cH\xe8\x9a\x95X\xe1\x1d\x931\x881\xe0\x94\xae\x19\xabv\xda\xf9A\x94s\x866P\xacI\x08>\xdaqA\x1e\x85\xe4mN\xa8\xb2K.\x01\xd0\x9d1y\x07Hd\x84\xdb'\xab,\xd4S\x8e\xacl\xd2\x95\x1a\xac\xdb$.l\xd0\x0d)\xa2\x00`@\x00\xe4<7,\x17\xc6\"o\xa5~\xefF/\xd9\x01\xa2Pt\xa6\x0d\xd2\xc7\x91\xe5f\x17DP\x86\xf6\xbd\xc2\xe0\xc01\xbc/R\xabO\x82\xd1\x1e\xa7\xc4\x18Dz\x17I\xe9\x95\xbc\xcdy\xd1\x14\x1d\xa1\x95\x82W\xd0\xed \x02\xb5\xd4\xf3\xc7//\xd0L\x03\xa8\x10\x17\xd7\x18\x1c<\xca\xf8\xd8\xd3,-_\xc3\xe9\xd3\xb7\x90fU@\xbb%\x82\x8d\x1d\xc6\xc8\xae;\xa0(\xc0[\xde\x12Av\x85\x00P1\x10;\xc2f\x06\xaa \xe0\xb6'\xd0\xb9Z\n\x02`(\x97\xe7\x99:\xf4_\xcc\xce\xde\xc9\x993\xf7\x03\x80t!\x88\n\x11Qd1\x08\x82\x9e\xd3-\xba\x11N\xf4\x82)D\xce=\xda\x9d\x13\xf0\x08\x1e%\xd8\xdc\xf9\x01\xb7\xcb|\x94\x8a\xcb\x84\xe0XY\xb9\xb2\xf7fd\x0b\xc2Q\x88-\x91O\x88[\x0d4*\xa2\x85R\xef\xca\xed\xd9\xb8v\x07P\x80\xb4\x85ilP\xab\x0c\x92\x89\xa0\"\xbb\x87\xdfre\x8e\xe1\xe1\xd7Y\\\xbc\x8efs\x04\xd0\x9ep\x1bE\x8eRs\x8d\xe1\xa3\xcf`\x8c!\xf4\x8f\xb0y\xf1\x0d`,\x08\xdd\x02O{\xa2\x8dv\xc3g\x0fq\xb7O\xf2n\xcd\xbf\x1bD\xc8\xbf\x06\x14\xc1\x8b\xc1\xae/P:\xfe\x1c\xc9\xf5\x0f\xd3\xb2\x06\xcd+\xe2\x1e\x10\x11e|\xe2qF\xda\x10++W0;{\x07\xaa\xa6gA\x8d\x11\xac1\x94\x97g\xb1\x1ah\xdc\xf00\xce\x08\xb1\xb1\xd8\xf5EL\xbb_\xabC\x84\x91Id\xe5,f}\x01\xad\x0c\x12\x06\xc70\xed\xefa\xe4\x00R[\x011h\xb9\x1f\xb3\xd4\xee\x1bzsL\xea\xeb\x84}\x07\xd0\xbe!\xcc\xeaz\xa8\x03N\x1bB6V\x10\x9f\xe1U\xf1a[\x1e\xa9T\xcer\xf9\xe5\x7f\x8d\xb5 \x0b\x8b\xd7S\xaf\x8f\x83\x04PC\xb1\xb4\xc2\xdc\xdc-,-]\x07\xa2\x80\x80\x13l\xac\x98\x8de\xca\xb3\x87\x11`\xf3\xd0\xd5\x84\x89\xcb\x88\x93\x16\xf6\xe4+h\xb1\x82?x\x15vh\x0c9s\x98P\xa8\xe0\xa3b\xc7w\\\x1b&\xdb\xf2\x9fV\x03\xe33\xb2R?\x1c\xb8\nw\xecy\xe4\xd8\x0b\x84\x81}\xf8\x03W`\xdb\xf3\x9b\x13/\xa3\xa5\ni;\x90\x98\xad\xf9\x8e\xbfHV\xa8\xd0\x1a\x1c#\xf5\x8a\xf7\x01r\xc9\xd7\xfe\xda\xef\x84\xdf~\xee\xac4\x1b\xfbI\x93>\x94\x9c\xd0\xb5\x98\x98|\x94\xb9\xb3\xb7\x93t\xccJ\xbb\xd5mT\x8a(:C\xec3\x9c\x11l\x14cE\xda\x0dL\x96\x80\x08\xd8\x084 \xc1\xa3\xc6m+\xfa\x02\x88\x01\x14\xcd\xa3\x13\x00\x1a\x90,\x05\xeb\xc0X\x807wZ\xa4\xf3\xfb\x00\xf84%\x15C\xa6\xb0\xb1\x99\x91\xa6\x1e\x02|\xe1\xad\x15u\x9b\xf5 \xd6V\xaa \xddH\x0dj\xa9V\xa7\x01H\x92*\xa0=a4\xa8\x92*\xa8\x8d\xc8\x04\xac\x0f\x98\xdc\xf9D\x1c\x82 >\x80\x08\x88\x83|j%\x00\x02\x04r\xf5~61(\x90\xaf4\x12\xa1(\xea\x95\x00\x04qx\x94V\xe6I\xb3\xd0s,\xe4\x10@BO\x08\xee\x1f8\xc2\xe4\xe4c\xcc\x9e\xbd\x03\xd4\xe4\xa6&\xf9=(!\xf1d\xc6\x10\x04\x8c\x80\xa1\xf7\xb4\x06\x14\xc9\xe7\xea\x91\x82\xa2\x17T*k\xcfk\xde\x142\x0d$I\x06As\x88\x1c\x04e\x1bD`b\xe2\x89N[]\xbd\x94\xf5\xb5\x8b\xba&e\xf2\xe7u\x1fb4\xf5x\x85P\xc8C\xac\x80hOq\xff\xb6\x1f\x95i\xdeB\x16\xd0v#h\xcf\x80\xa3+a\xdf\xbe\x97\x19\x1f\x7f\x92\xf9\xf9\x9b\x99\x99\xb9\x0b\xef\x8b\x80v\x00\xc6\xdbp\x1b\xebSll\x1c\x02\xd1\xfcqV\xa1\x18\xe5\x19\\\xbb\x10\xe7%\xd1\xf3\x03\xc9y\xa1\xf3\x7f7\x87\xe0\\y\xa4\\\x99\xe7\xc0\x81\xffca\xe1&\xa6\xa7\x1f@\xd5\x00\xda\x1d\xf3>\xa6V\x9b\x00r3\x03\xd4\x07\xf0\xa1\xe7\xd1V\xf7\xba\xe4\xbb\xc0\xf5B\x044\x0d;.\xda\xee#\x08\x03\x9dg\x8e\xb8\x13n5\x98n\xb8\x15\xe3\xe9\x1f8\xc5\xf2\xd2Uh\xb0;\x82\x02h+\x83\xd8\x815 \xecMy\xaduN\x05\x05\x1f\xba\x8b\x86\xeev\x1c\xa4\x821)\xd5\xea\x0cg\xdb\xce\x9d\xb4\xf2P\xab\x06c\x93\xb6\xb9\xbdD\x14\xd5h\xe5\xfd=\xc7\x95\x92\xc3$)8\x83X\x0bF\xba\x03\x9a\xdf\x84 \xe0\xcc9nVQ\x1f \xf3t\xe7D\x90\xbc\xaa%\x04\xe8\x1e/\x9d_\xceEu\xc6\xc6\x9f&\xcb\xca,-]\xd3]\xf1\xc1\xa1#\x8c\x8d=C\x7f\xdf)NO?\x08\xe2\xc0\x91\xe7\x82s\x1c\xa8\x05E\xb3\x14L\x0e\x1a\xb4\xdb\x14 \x13p\x16\xb19\x90\xe6\xf6\x9e\xed\\eP\xc0\x1a\xa1\x10Y\\\xd1\xe1\x83\x92d\x81\xd4\x87\xddA\xfa\x06N\xb2\x7f\xf49\x8e\x1e\xf9\xa4\xee\xe1B\xa9\xbc\xc4\xd4\xd4\xbf\x11|\x81\xd9\xb9;XZ\xbb\x0e\x9c\x01\xcd\xb7\x17\xd8y\xb0\x865\x80\x81\xcc\xefn\"I\x86\xcay\x9e\x89\x01c\x0d\xc3\xfd\x05F\xfb\nT\x0b\x8e\xc8\x1aD\x95$\xf5,\xd5\x12fW\x9b4\x12\xcfN9U\xd3q\xf0Ni\x92\x9bT\xa5:\xcb\xea\xeae\x9c\x99\xb9\x1fO\x19\x11\xc0{r[\xce\x9b\x01\xcd3u\x08@\x80m\xab\xbd\x8b\xce;f\"\xcb5\x97\x0c\xa0\xe5\"\x8b\x85\x12+\xc5\x88V\x14\x11e\x9e\x81z\x93\xe1R\x93\x81j\xcc\xf1\xf9\x1a+\xb5\xa4\xf7\xb7[\xb6\xbf\xb8p= y\x03cR\x96V\xae\xc5S\xe1\xca\xe1\x02\x9fpI?\xd5\xc8\xe4fc\xe8/e\xdc:2\xcbg\\\xfe*\x97\x0d\xad\x82\x98ff\xeea}}\n\x9c\x01\x94F\x1aP\xb1|\xfeU/\xf0-7=\xc1\x93s\x1fM\xa5\x7f\x10\x1f\x0e\xd3\xf4}\x0cU=?w\xdf\xdf\xf2\xcd\xff\xf7\x91L\xaf]\xbag\x0eD!x$Sn\x1b)\xf2\xafNhe\x81\xd5\xa0|\xee\x08\xac&\xf0\xeb+\x8e\x16\x06\x1aM\xd8l\xa1\n\x18@r\x10\x95f\xe7\x00nv\xe6n\xb2\xb4\x02\x92\xc7uc8\xb3\x91\x80\x08\x7f~\xe2\x1a>\xee\xa2\xa3\xdcs\xe8i\xa6W\xcbx\x1d\xe3\xc0\xfe\"\xb5\xc2\xb5\x1c\x9e~\x9e\xd9z\x05\x00D \xec\x81$(\x84\x8c\xd5&\xfc\xef|B6\xe6\x01\xa5\x16\x84\xdf_\xef\xa3\xe1\xe9\x80!\n\x1buH3\x90\xde\xff\xf41\xebkWsf\xfa\xbeNBD\xc26\xa7\xa5\xeb\xb8'V\x07\xf8\xb1\x17\xeeauc\x9e\xa3gA\x81\xeb/\x0e\x1c\xcfn\xe2\x8b\xfe\xeb\x93xee\x1f\x88\xee\xdd\xb4\xbcB\x96\xa1>c\xad\xb5\xf5\x9er\x95\xab\xf3`\xbc\xcah\xa8!Y\x82\x84\x8c\xa2f\x8c\xfa&x\xbf-\xeb\xe7 Ys\x82\x10\xe2\x9e\x1b\xe8 \xa5\xaa\x80\xe7\x9fN]\xc27<\xfa\xf1\xd8B\xc6\xa7^\xfb\x0cqi\x04u\x03\xd4\x12G\xcb\x1b\x90\xbds\x10\x804\x80O\xc1g\x0cK\x8b;\xfd\"\x13\xabs\xdc\xd1~\xff\x8c\xf2\"\xd7D\x0d\xa2V\x83J\xd2\x84\xe0!Ka[bt\xec&\x1f\xf2l.\x10\x94@\xe0O\x0e_\xcd\xd3\x0b\xe3|\xc4\x81\xa3\xdcV\x9b\xe0tk\x1d\x1fx{\x94\x18\x883l\x96P\x0e FS\xb2V\xca\xf4\\J_\xd12\xd8\x1f\xf1\xcaR\xca\xc6R\x03\x8c@b \xdb\x9e\x86\x04\x05\x84\x9d\n\x01\x82\x805y\xf6V\x10\xcf\xb1\x95\x01~y\xe56\xe4eE\xa8\x13\x82\x01\xd5n\xe4A\x95=)\x18H#.w\x9er\xab\xc9\xe3\xae\xc2\xfe\xfe\x12U\xcd\xd8\x108\xd9PX[\x87\x90Aj\xa1ez*$\xf9\xae\xdf\xfa\xcfS?\xf4\x9f\xcb\x87\x1a\xbb\xfdQ\x8d\xb3`r\xc7\xd2\xbc\xed4#\x93\x03gy\xb9\xb2w\xe1\\\xc0\xc5\x9e\xcd\xd8B)\xc6\xc6\x8e\x8b\xab\x868Mym\xba\x86\xa6\x162\xdb\x0d\xf7%+|\xeb\xc3C\xb3\xf2\xf7\xff\xf1\xe8\xb7\xfe\xca\xe3\xeb\xdf\xf1\x0f\xaf\xd7+\x9b;`\xba{eL\xbe\xe2ta\xbac\"\x90\x97\xdco\x8b\x9d) \nF\xc1\x04\x10%6\x82Ch$\xb9\xa9+\x00\x14\x9c\xf01W\x96\x1b_z\xf7\xc0\xf7\xb9\xad?\xdaR\x90\x1b\xc7\xe3o\x98^\xcb\xc6w\x8d\x9e\x02\x18A\x8ca\xa7\xd4\xe7\xa6\xf7\x0ej\xa7\xfd\x1b\x81\xc9\x017w\xd7T\xf1gn;T\xf8\xe9\x0f\x01\xed\xf2ln\xbd\x85\x11\xa3\x00\x00\x00\x00IEND\xaeB`\x82\x01\x00\x00\xff\xffPK\x07\x08&\xd2\xc7\x1f\x1e\x0d\x00\x00\x14\x0d\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00;/TL\xb5Q\xa0\xce\xe9I\x00\x00Z\xbb\x01\x00%\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x00bootstrap/3.3.1/css/bootstrap.min.cssUT\x05\x00\x01\xe3\xb8\x8bZPK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\x89\x89\xb8H>\xd4\x17\xe7u\x02\x00\x006\x0e\x00\x00\x0b\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81EJ\x00\x00favicon.icoUT\x05\x00\x01s\x8bDWPK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\x94\x91O \xa9\x03\xb9JR\x00\x00\x00P\x00\x00\x00\x11\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xfcL\x00\x00images/folder.gifUT\x05\x00\x01\x98w#1PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00!z\x92E\x01\x84\xa6\xa6\x8ct\x00\x00`I\x01\x00\x1e\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x96M\x00\x00javascript/jquery-2.1.3.min.jsUT\x05\x00\x01\xef\xef\x92TPK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\x9dJ\xb1L\x9d\x06\xc6\xc6-3\x00\x00\xef\xa8\x00\x00:\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81w\xc2\x00\x00javascript/jquery-sparklines/2.1.2/jquery.sparkline.min.jsUT\x05\x00\x01zI\xfdZPK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00j\x8dGM&\xd2\xc7\x1f\x1e\x0d\x00\x00\x14\x0d\x00\x00\x10\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x15\xf6\x00\x00seaweed50x50.pngUT\x05\x00\x01\xb9E\xba[PK\x05\x06\x00\x00\x00\x00\x06\x00\x06\x00\xf3\x01\x00\x00z\x03\x01\x00\x00\x00" + fs.Register(data) +} diff --git a/weed/weed.go b/weed/weed.go index 91c17d9ff..6e69c1480 100644 --- a/weed/weed.go +++ b/weed/weed.go @@ -1,12 +1,12 @@ +//go:generate statik -src=./static +// install this first "go get github.com/rakyll/statik" + package main import ( - "embed" "fmt" - weed_server "github.com/chrislusf/seaweedfs/weed/server" flag "github.com/chrislusf/seaweedfs/weed/util/fla9" "io" - "io/fs" "math/rand" "os" "strings" @@ -35,13 +35,6 @@ func setExitStatus(n int) { exitMu.Unlock() } -//go:embed static -var static embed.FS - -func init() { - weed_server.StaticFS, _ = fs.Sub(static, "static") -} - func main() { glog.MaxSize = 1024 * 1024 * 32 rand.Seed(time.Now().UnixNano()) From 3a86d4dbfded2fb34f0d9cb696a6c5ba9415be4f Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 30 Apr 2021 22:51:06 -0700 Subject: [PATCH 104/128] mount: fix directory invalidation fix https://github.com/chrislusf/seaweedfs/issues/2038 --- weed/filesys/dir.go | 12 ++++++++---- weed/filesys/meta_cache/meta_cache.go | 4 ---- weed/filesys/wfs.go | 5 ++++- weed/util/bounded_tree/bounded_tree.go | 3 --- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/weed/filesys/dir.go b/weed/filesys/dir.go index 6ee20974b..79fc10442 100644 --- a/weed/filesys/dir.go +++ b/weed/filesys/dir.go @@ -29,7 +29,7 @@ type Dir struct { var _ = fs.Node(&Dir{}) -//var _ = fs.NodeIdentifier(&Dir{}) +var _ = fs.NodeIdentifier(&Dir{}) var _ = fs.NodeCreater(&Dir{}) var _ = fs.NodeMknoder(&Dir{}) var _ = fs.NodeMkdirer(&Dir{}) @@ -45,7 +45,10 @@ var _ = fs.NodeRemovexattrer(&Dir{}) var _ = fs.NodeListxattrer(&Dir{}) var _ = fs.NodeForgetter(&Dir{}) -func (dir *Dir) xId() uint64 { +func (dir *Dir) Id() uint64 { + if dir.parent == nil { + return 1 + } return dir.id } @@ -66,7 +69,7 @@ func (dir *Dir) Attr(ctx context.Context, attr *fuse.Attr) error { return err } - // attr.Inode = dir.Id() + attr.Inode = dir.Id() attr.Mode = os.FileMode(entry.Attributes.FileMode) | os.ModeDir attr.Mtime = time.Unix(entry.Attributes.Mtime, 0) attr.Crtime = time.Unix(entry.Attributes.Crtime, 0) @@ -93,7 +96,7 @@ func (dir *Dir) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *f func (dir *Dir) setRootDirAttributes(attr *fuse.Attr) { // attr.Inode = 1 // filer2.FullPath(dir.Path).AsInode() attr.Valid = time.Second - attr.Inode = 1 // dir.Id() + attr.Inode = dir.Id() attr.Uid = dir.wfs.option.MountUid attr.Gid = dir.wfs.option.MountGid attr.Mode = dir.wfs.option.MountMode @@ -328,6 +331,7 @@ func (dir *Dir) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse. // resp.EntryValid = time.Second resp.Attr.Inode = fullFilePath.AsInode() resp.Attr.Valid = time.Second + resp.Attr.Size = localEntry.FileSize resp.Attr.Mtime = localEntry.Attr.Mtime resp.Attr.Crtime = localEntry.Attr.Crtime resp.Attr.Mode = localEntry.Attr.Mode diff --git a/weed/filesys/meta_cache/meta_cache.go b/weed/filesys/meta_cache/meta_cache.go index b9d4724c9..3a64df018 100644 --- a/weed/filesys/meta_cache/meta_cache.go +++ b/weed/filesys/meta_cache/meta_cache.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "os" - "strings" "sync" "github.com/chrislusf/seaweedfs/weed/filer" @@ -31,9 +30,6 @@ func NewMetaCache(dbFolder string, baseDir util.FullPath, uidGidMapper *UidGidMa visitedBoundary: bounded_tree.NewBoundedTree(baseDir), uidGidMapper: uidGidMapper, invalidateFunc: func(fullpath util.FullPath) { - if baseDir != "/" && strings.HasPrefix(string(fullpath), string(baseDir)) { - fullpath = fullpath[len(baseDir):] - } invalidateFunc(fullpath) }, } diff --git a/weed/filesys/wfs.go b/weed/filesys/wfs.go index 42816d23d..832925bc1 100644 --- a/weed/filesys/wfs.go +++ b/weed/filesys/wfs.go @@ -111,6 +111,9 @@ func NewSeaweedFileSystem(option *Option) *WFS { dir, name := filePath.DirAndName() parent := NodeWithId(util.FullPath(dir).AsInode()) + if dir == option.FilerMountRootPath { + parent = NodeWithId(1) + } if err := wfs.Server.InvalidateEntry(parent, name); err != nil { glog.V(4).Infof("InvalidateEntry %s : %v", filePath, err) } @@ -121,7 +124,7 @@ func NewSeaweedFileSystem(option *Option) *WFS { wfs.metaCache.Shutdown() }) - wfs.root = &Dir{name: wfs.option.FilerMountRootPath, wfs: wfs} + wfs.root = &Dir{name: wfs.option.FilerMountRootPath, wfs: wfs, id: 1} wfs.fsNodeCache = newFsCache(wfs.root) if wfs.option.ConcurrentWriters > 0 { diff --git a/weed/util/bounded_tree/bounded_tree.go b/weed/util/bounded_tree/bounded_tree.go index 3a8a22a9c..137f690b8 100644 --- a/weed/util/bounded_tree/bounded_tree.go +++ b/weed/util/bounded_tree/bounded_tree.go @@ -41,9 +41,6 @@ func (t *BoundedTree) EnsureVisited(p util.FullPath, visitFn VisitNodeFunc) (vis if t.root == nil { return } - if t.baseDir != "/" { - p = p[len(t.baseDir):] - } components := p.Split() // fmt.Printf("components %v %d\n", components, len(components)) canDelete, err := t.ensureVisited(t.root, t.baseDir, components, 0, visitFn) From f51bf61a16defa5e4a0eefe19ccf82164985afe8 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 30 Apr 2021 22:51:30 -0700 Subject: [PATCH 105/128] remove wrong parameter --- docker/compose/local-mount-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/compose/local-mount-compose.yml b/docker/compose/local-mount-compose.yml index b1c579cdf..8c4329054 100644 --- a/docker/compose/local-mount-compose.yml +++ b/docker/compose/local-mount-compose.yml @@ -38,7 +38,7 @@ services: mount_2: image: chrislusf/seaweedfs:local privileged: true - entrypoint: '/bin/sh -c "mkdir -p t2 && mkdir -p cache/t2 && weed -v=4 mount -filer=filer:8888 -cacheDir=./cache/t2 -dir=./t2 -filer.path=/c1 -volumeServerAcess=publicUrl"' + entrypoint: '/bin/sh -c "mkdir -p t2 && mkdir -p cache/t2 && weed -v=4 mount -filer=filer:8888 -cacheDir=./cache/t2 -dir=./t2 -filer.path=/c1"' depends_on: - master - volume From 53b300edd8cb6a27f1c24271b8b320de4f08f5a5 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sat, 1 May 2021 00:20:11 -0700 Subject: [PATCH 106/128] add git version for local builds --- docker/Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docker/Makefile b/docker/Makefile index a933956b7..58d494b95 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -5,7 +5,9 @@ all: gen gen: dev binary: - cd ../weed; CGO_ENABLED=0 GOOS=linux go build -ldflags "-extldflags -static"; mv weed ../docker/ + export SWCOMMIT=$(shell git rev-parse --short HEAD) + export SWLDFLAGS="-X github.com/chrislusf/seaweedfs/weed/util.COMMIT=$(SWCOMMIT)" + cd ../weed; CGO_ENABLED=0 GOOS=linux go build -ldflags "-extldflags -static $(SWLDFLAGS)"; mv weed ../docker/ build: binary docker build --no-cache -t chrislusf/seaweedfs:local -f Dockerfile.local . From c48ef786702e4c62f644631d37d31218f7447fe4 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sat, 1 May 2021 00:39:04 -0700 Subject: [PATCH 107/128] 2.43 --- k8s/seaweedfs/Chart.yaml | 4 ++-- k8s/seaweedfs/values.yaml | 2 +- weed/util/constants.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/k8s/seaweedfs/Chart.yaml b/k8s/seaweedfs/Chart.yaml index d9c9b05c8..c4f6bcc3b 100644 --- a/k8s/seaweedfs/Chart.yaml +++ b/k8s/seaweedfs/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v1 description: SeaweedFS name: seaweedfs -appVersion: "2.42" -version: 2.42 +appVersion: "2.43" +version: 2.43 diff --git a/k8s/seaweedfs/values.yaml b/k8s/seaweedfs/values.yaml index 1cefd0914..c75bb869c 100644 --- a/k8s/seaweedfs/values.yaml +++ b/k8s/seaweedfs/values.yaml @@ -4,7 +4,7 @@ global: registry: "" repository: "" imageName: chrislusf/seaweedfs - # imageTag: "2.42" - started using {.Chart.appVersion} + # imageTag: "2.43" - started using {.Chart.appVersion} imagePullPolicy: IfNotPresent imagePullSecrets: imagepullsecret restartPolicy: Always diff --git a/weed/util/constants.go b/weed/util/constants.go index cdd5d41fc..ef2159c2c 100644 --- a/weed/util/constants.go +++ b/weed/util/constants.go @@ -5,7 +5,7 @@ import ( ) var ( - VERSION = fmt.Sprintf("%s %d.%02d", sizeLimit, 2, 42) + VERSION = fmt.Sprintf("%s %d.%02d", sizeLimit, 2, 43) COMMIT = "" ) From 2e56407c6b9470c5845d67aec49233f8a98007a2 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sat, 1 May 2021 16:09:29 -0700 Subject: [PATCH 108/128] fix visited checking --- weed/filesys/meta_cache/meta_cache_init.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/weed/filesys/meta_cache/meta_cache_init.go b/weed/filesys/meta_cache/meta_cache_init.go index 1ca3b16d5..9af25ae29 100644 --- a/weed/filesys/meta_cache/meta_cache_init.go +++ b/weed/filesys/meta_cache/meta_cache_init.go @@ -17,9 +17,9 @@ func EnsureVisited(mc *MetaCache, client filer_pb.FilerClient, dirPath util.Full glog.V(4).Infof("ReadDirAllEntries %s ...", path) util.Retry("ReadDirAllEntries", func() error { - err = filer_pb.ReadDirAllEntries(client, dirPath, "", func(pbEntry *filer_pb.Entry, isLast bool) error { - entry := filer.FromPbEntry(string(dirPath), pbEntry) - if IsHiddenSystemEntry(string(dirPath), entry.Name()) { + err = filer_pb.ReadDirAllEntries(client, path, "", func(pbEntry *filer_pb.Entry, isLast bool) error { + entry := filer.FromPbEntry(string(path), pbEntry) + if IsHiddenSystemEntry(string(path), entry.Name()) { return nil } if err := mc.doInsertEntry(context.Background(), entry); err != nil { @@ -35,7 +35,7 @@ func EnsureVisited(mc *MetaCache, client filer_pb.FilerClient, dirPath util.Full }) if err != nil { - err = fmt.Errorf("list %s: %v", dirPath, err) + err = fmt.Errorf("list %s: %v", path, err) } return From a4bb37a5fee103312e2bef39bbfc071a9f9aa097 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sat, 1 May 2021 16:10:26 -0700 Subject: [PATCH 109/128] add debug info --- weed/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weed/Makefile b/weed/Makefile index 8f1257d09..edc0bf544 100644 --- a/weed/Makefile +++ b/weed/Makefile @@ -16,7 +16,7 @@ debug_shell: debug_mount: go build -gcflags="all=-N -l" - dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec weed -- mount -dir=~/tmp/mm -cacheCapacityMB=0 -filer.path=/buckets + dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec weed -- -v=4 mount -dir=~/tmp/mm -cacheCapacityMB=0 -filer.path=/buckets debug_server: go build -gcflags="all=-N -l" From e87e6ef33ceafcb804cc494540cae66b868ffbe3 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sun, 2 May 2021 21:30:37 -0700 Subject: [PATCH 110/128] s3: return 404 if bucket does not exist fix https://github.com/chrislusf/seaweedfs/issues/2039 --- weed/s3api/s3api_objects_list_handlers.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/weed/s3api/s3api_objects_list_handlers.go b/weed/s3api/s3api_objects_list_handlers.go index 739cdd8f9..66c66d280 100644 --- a/weed/s3api/s3api_objects_list_handlers.go +++ b/weed/s3api/s3api_objects_list_handlers.go @@ -63,6 +63,14 @@ func (s3a *S3ApiServer) ListObjectsV2Handler(w http.ResponseWriter, r *http.Requ writeErrorResponse(w, s3err.ErrInternalError, r.URL) return } + + if len(response.Contents) == 0 { + if exists, existErr := s3a.exists(s3a.option.BucketsPath, bucket, true); existErr == nil && !exists { + writeErrorResponse(w, s3err.ErrNoSuchBucket, r.URL) + return + } + } + responseV2 := &ListBucketResultV2{ XMLName: response.XMLName, Name: response.Name, @@ -106,6 +114,13 @@ func (s3a *S3ApiServer) ListObjectsV1Handler(w http.ResponseWriter, r *http.Requ return } + if len(response.Contents) == 0 { + if exists, existErr := s3a.exists(s3a.option.BucketsPath, bucket, true); existErr == nil && !exists { + writeErrorResponse(w, s3err.ErrNoSuchBucket, r.URL) + return + } + } + writeSuccessResponseXML(w, encodeResponse(response)) } From e24ba2aadc3c8a7fe4175eda452c6c59989a6b0c Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sun, 2 May 2021 21:53:43 -0700 Subject: [PATCH 111/128] filer: delete specific tags fix https://github.com/chrislusf/seaweedfs/issues/2041 --- weed/server/filer_server_handlers_tagging.go | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/weed/server/filer_server_handlers_tagging.go b/weed/server/filer_server_handlers_tagging.go index 50b3a2c06..70b5327d6 100644 --- a/weed/server/filer_server_handlers_tagging.go +++ b/weed/server/filer_server_handlers_tagging.go @@ -78,11 +78,27 @@ func (fs *FilerServer) DeleteTaggingHandler(w http.ResponseWriter, r *http.Reque existingEntry.Extended = make(map[string][]byte) } + // parse out tags to be deleted + toDelete := strings.Split(r.URL.Query().Get("tagging"), ",") + deletions := make(map[string]struct{}) + for _, deletion := range toDelete { + deletions[deletion] = struct{}{} + } + + // delete all tags or specific tags hasDeletion := false for header, _ := range existingEntry.Extended { if strings.HasPrefix(header, needle.PairNamePrefix) { - delete(existingEntry.Extended, header) - hasDeletion = true + if len(deletions) == 0 { + delete(existingEntry.Extended, header) + hasDeletion = true + } else { + tag := header[len(needle.PairNamePrefix):] + if _, found := deletions[tag]; found { + delete(existingEntry.Extended, header) + hasDeletion = true + } + } } } From ac26080bd2153803562838675238a349c203d3e8 Mon Sep 17 00:00:00 2001 From: qieqieplus Date: Wed, 5 May 2021 17:54:50 +0800 Subject: [PATCH 112/128] fix concurrent vacuum & delete panic --- weed/server/volume_grpc_vacuum.go | 9 ++------- weed/storage/store_vacuum.go | 6 +++--- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/weed/server/volume_grpc_vacuum.go b/weed/server/volume_grpc_vacuum.go index b87de4b5b..f8d1b7fda 100644 --- a/weed/server/volume_grpc_vacuum.go +++ b/weed/server/volume_grpc_vacuum.go @@ -44,19 +44,14 @@ func (vs *VolumeServer) VacuumVolumeCommit(ctx context.Context, req *volume_serv resp := &volume_server_pb.VacuumVolumeCommitResponse{} - err := vs.store.CommitCompactVolume(needle.VolumeId(req.VolumeId)) + readOnly, err := vs.store.CommitCompactVolume(needle.VolumeId(req.VolumeId)) if err != nil { glog.Errorf("commit volume %d: %v", req.VolumeId, err) } else { glog.V(1).Infof("commit volume %d", req.VolumeId) } - if err == nil { - if vs.store.GetVolume(needle.VolumeId(req.VolumeId)).IsReadOnly() { - resp.IsReadOnly = true - } - } - + resp.IsReadOnly = readOnly return resp, err } diff --git a/weed/storage/store_vacuum.go b/weed/storage/store_vacuum.go index 32666a417..fe2033070 100644 --- a/weed/storage/store_vacuum.go +++ b/weed/storage/store_vacuum.go @@ -25,11 +25,11 @@ func (s *Store) CompactVolume(vid needle.VolumeId, preallocate int64, compaction } return fmt.Errorf("volume id %d is not found during compact", vid) } -func (s *Store) CommitCompactVolume(vid needle.VolumeId) error { +func (s *Store) CommitCompactVolume(vid needle.VolumeId) (bool, error) { if v := s.findVolume(vid); v != nil { - return v.CommitCompact() + return v.IsReadOnly(), v.CommitCompact() } - return fmt.Errorf("volume id %d is not found during commit compact", vid) + return false, fmt.Errorf("volume id %d is not found during commit compact", vid) } func (s *Store) CommitCleanupVolume(vid needle.VolumeId) error { if v := s.findVolume(vid); v != nil { From ac71117ee67b0506ecf9fab382d6110e30c50e35 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Wed, 5 May 2021 15:11:39 -0700 Subject: [PATCH 113/128] revert PR #1903 avoid http error: superfluous response.WriteHeader --- go.mod | 4 ++-- go.sum | 2 -- weed/command/filer_cat.go | 2 +- weed/filer/filer_on_meta_event.go | 2 +- weed/filer/read_write.go | 2 +- weed/filer/stream.go | 25 +--------------------- weed/s3api/s3api_object_handlers.go | 6 +----- weed/server/common.go | 8 +++---- weed/server/filer_server_handlers_read.go | 10 ++++----- weed/server/volume_server_handlers_read.go | 7 ++++-- weed/shell/command_fs_cat.go | 2 +- 11 files changed, 22 insertions(+), 48 deletions(-) diff --git a/go.mod b/go.mod index 70bc33070..f7ae83458 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/google/uuid v1.1.1 github.com/gorilla/mux v1.7.4 github.com/gorilla/websocket v1.4.1 // indirect - github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 + github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 // indirect github.com/grpc-ecosystem/grpc-gateway v1.11.0 // indirect github.com/jcmturner/gofork v1.0.0 // indirect github.com/jinzhu/copier v0.2.8 @@ -90,7 +90,7 @@ require ( gocloud.dev/pubsub/rabbitpubsub v0.20.0 golang.org/x/image v0.0.0-20200119044424-58c23975cae1 // indirect golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c + golang.org/x/sync v0.0.0-20200930132711-30421366ff76 // indirect golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd golang.org/x/tools v0.0.0-20200608174601-1b747fd94509 google.golang.org/api v0.26.0 diff --git a/go.sum b/go.sum index 0409b1ae1..972bd72dd 100644 --- a/go.sum +++ b/go.sum @@ -969,8 +969,6 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200930132711-30421366ff76 h1:JnxiSYT3Nm0BT2a8CyvYyM6cnrWpidecD1UuSYbhKm0= golang.org/x/sync v0.0.0-20200930132711-30421366ff76/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/weed/command/filer_cat.go b/weed/command/filer_cat.go index c4281feba..a46098b04 100644 --- a/weed/command/filer_cat.go +++ b/weed/command/filer_cat.go @@ -110,7 +110,7 @@ func runFilerCat(cmd *Command, args []string) bool { filerCat.filerClient = client - return filer.StreamContent(&filerCat, writer, respLookupEntry.Entry.Chunks, 0, math.MaxInt64, false) + return filer.StreamContent(&filerCat, writer, respLookupEntry.Entry.Chunks, 0, math.MaxInt64) }) diff --git a/weed/filer/filer_on_meta_event.go b/weed/filer/filer_on_meta_event.go index a91faeb24..c9f75a5ca 100644 --- a/weed/filer/filer_on_meta_event.go +++ b/weed/filer/filer_on_meta_event.go @@ -52,7 +52,7 @@ func (f *Filer) maybeReloadFilerConfiguration(event *filer_pb.SubscribeMetadataR func (f *Filer) readEntry(chunks []*filer_pb.FileChunk) ([]byte, error) { var buf bytes.Buffer - err := StreamContent(f.MasterClient, &buf, chunks, 0, math.MaxInt64, false) + err := StreamContent(f.MasterClient, &buf, chunks, 0, math.MaxInt64) if err != nil { return nil, err } diff --git a/weed/filer/read_write.go b/weed/filer/read_write.go index d92d526d5..c4c90fb63 100644 --- a/weed/filer/read_write.go +++ b/weed/filer/read_write.go @@ -27,7 +27,7 @@ func ReadEntry(masterClient *wdclient.MasterClient, filerClient filer_pb.Seaweed return err } - return StreamContent(masterClient, byteBuffer, respLookupEntry.Entry.Chunks, 0, math.MaxInt64, false) + return StreamContent(masterClient, byteBuffer, respLookupEntry.Entry.Chunks, 0, math.MaxInt64) } diff --git a/weed/filer/stream.go b/weed/filer/stream.go index 661a210ea..f9a762187 100644 --- a/weed/filer/stream.go +++ b/weed/filer/stream.go @@ -3,7 +3,6 @@ package filer import ( "bytes" "fmt" - "golang.org/x/sync/errgroup" "io" "math" "strings" @@ -14,7 +13,7 @@ import ( "github.com/chrislusf/seaweedfs/weed/wdclient" ) -func StreamContent(masterClient wdclient.HasLookupFileIdFunction, w io.Writer, chunks []*filer_pb.FileChunk, offset int64, size int64, isCheck bool) error { +func StreamContent(masterClient wdclient.HasLookupFileIdFunction, w io.Writer, chunks []*filer_pb.FileChunk, offset int64, size int64) error { glog.V(9).Infof("start to stream content for chunks: %+v\n", chunks) chunkViews := ViewFromChunks(masterClient.GetLookupFileIdFunction(), chunks, offset, size) @@ -34,16 +33,6 @@ func StreamContent(masterClient wdclient.HasLookupFileIdFunction, w io.Writer, c fileId2Url[chunkView.FileId] = urlStrings } - if isCheck { - // Pre-check all chunkViews urls - gErr := new(errgroup.Group) - CheckAllChunkViews(chunkViews, &fileId2Url, gErr) - if err := gErr.Wait(); err != nil { - glog.Errorf("check all chunks: %v", err) - return fmt.Errorf("check all chunks: %v", err) - } - return nil - } for _, chunkView := range chunkViews { @@ -53,7 +42,6 @@ func StreamContent(masterClient wdclient.HasLookupFileIdFunction, w io.Writer, c glog.Errorf("read chunk: %v", err) return fmt.Errorf("read chunk: %v", err) } - _, err = w.Write(data) if err != nil { glog.Errorf("write chunk: %v", err) @@ -65,17 +53,6 @@ func StreamContent(masterClient wdclient.HasLookupFileIdFunction, w io.Writer, c } -func CheckAllChunkViews(chunkViews []*ChunkView, fileId2Url *map[string][]string, gErr *errgroup.Group) { - for _, chunkView := range chunkViews { - urlStrings := (*fileId2Url)[chunkView.FileId] - glog.V(9).Infof("Check chunk: %+v\n url: %v", chunkView, urlStrings) - gErr.Go(func() error { - _, err := retriedFetchChunkData(urlStrings, chunkView.CipherKey, chunkView.IsGzipped, chunkView.IsFullChunk(), chunkView.Offset, int(chunkView.Size)) - return err - }) - } -} - // ---------------- ReadAllReader ---------------------------------- func ReadAll(masterClient *wdclient.MasterClient, chunks []*filer_pb.FileChunk) ([]byte, error) { diff --git a/weed/s3api/s3api_object_handlers.go b/weed/s3api/s3api_object_handlers.go index f1a539ac5..a8dc34b54 100644 --- a/weed/s3api/s3api_object_handlers.go +++ b/weed/s3api/s3api_object_handlers.go @@ -326,11 +326,7 @@ func passThroughResponse(proxyResponse *http.Response, w http.ResponseWriter) { for k, v := range proxyResponse.Header { w.Header()[k] = v } - if proxyResponse.Header.Get("Content-Range") != "" && proxyResponse.StatusCode == 200 { - w.WriteHeader(http.StatusPartialContent) - } else { - w.WriteHeader(proxyResponse.StatusCode) - } + w.WriteHeader(proxyResponse.StatusCode) io.Copy(w, proxyResponse.Body) } diff --git a/weed/server/common.go b/weed/server/common.go index 5c5f1b8eb..9001a3b33 100644 --- a/weed/server/common.go +++ b/weed/server/common.go @@ -234,12 +234,12 @@ func adjustHeaderContentDisposition(w http.ResponseWriter, r *http.Request, file } } -func processRangeRequest(r *http.Request, w http.ResponseWriter, totalSize int64, mimeType string, writeFn func(writer io.Writer, offset int64, size int64) error) { +func processRangeRequest(r *http.Request, w http.ResponseWriter, totalSize int64, mimeType string, writeFn func(writer io.Writer, offset int64, size int64, httpStatusCode int) error) { rangeReq := r.Header.Get("Range") if rangeReq == "" { w.Header().Set("Content-Length", strconv.FormatInt(totalSize, 10)) - if err := writeFn(w, 0, totalSize); err != nil { + if err := writeFn(w, 0, totalSize, 0); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } @@ -279,7 +279,7 @@ func processRangeRequest(r *http.Request, w http.ResponseWriter, totalSize int64 w.Header().Set("Content-Length", strconv.FormatInt(ra.length, 10)) w.Header().Set("Content-Range", ra.contentRange(totalSize)) - err = writeFn(w, ra.start, ra.length) + err = writeFn(w, ra.start, ra.length, http.StatusPartialContent) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -307,7 +307,7 @@ func processRangeRequest(r *http.Request, w http.ResponseWriter, totalSize int64 pw.CloseWithError(e) return } - if e = writeFn(part, ra.start, ra.length); e != nil { + if e = writeFn(part, ra.start, ra.length, 0); e != nil { pw.CloseWithError(e) return } diff --git a/weed/server/filer_server_handlers_read.go b/weed/server/filer_server_handlers_read.go index 6bc09e953..1d90871d8 100644 --- a/weed/server/filer_server_handlers_read.go +++ b/weed/server/filer_server_handlers_read.go @@ -131,9 +131,6 @@ func (fs *FilerServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request, if r.Method == "HEAD" { w.Header().Set("Content-Length", strconv.FormatInt(totalSize, 10)) - processRangeRequest(r, w, totalSize, mimeType, func(writer io.Writer, offset int64, size int64) error { - return filer.StreamContent(fs.filer.MasterClient, writer, entry.Chunks, offset, size, true) - }) return } @@ -153,7 +150,10 @@ func (fs *FilerServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request, } } - processRangeRequest(r, w, totalSize, mimeType, func(writer io.Writer, offset int64, size int64) error { + processRangeRequest(r, w, totalSize, mimeType, func(writer io.Writer, offset int64, size int64, httpStatusCode int) error { + if httpStatusCode != 0 { + w.WriteHeader(httpStatusCode) + } if offset+size <= int64(len(entry.Content)) { _, err := writer.Write(entry.Content[offset : offset+size]) if err != nil { @@ -161,7 +161,7 @@ func (fs *FilerServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request, } return err } - return filer.StreamContent(fs.filer.MasterClient, writer, entry.Chunks, offset, size, false) + return filer.StreamContent(fs.filer.MasterClient, writer, entry.Chunks, offset, size) }) } diff --git a/weed/server/volume_server_handlers_read.go b/weed/server/volume_server_handlers_read.go index 3e977cfd4..2db46ac9b 100644 --- a/weed/server/volume_server_handlers_read.go +++ b/weed/server/volume_server_handlers_read.go @@ -27,7 +27,7 @@ var fileNameEscaper = strings.NewReplacer(`\`, `\\`, `"`, `\"`) func (vs *VolumeServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request) { - glog.V(9).Info(r.Method + " " + r.URL.Path + " " + r.Header.Get("Range")) + // println(r.Method + " " + r.URL.Path) stats.VolumeServerRequestCounter.WithLabelValues("get").Inc() start := time.Now() @@ -261,10 +261,13 @@ func writeResponseContent(filename, mimeType string, rs io.ReadSeeker, w http.Re return nil } - processRangeRequest(r, w, totalSize, mimeType, func(writer io.Writer, offset int64, size int64) error { + processRangeRequest(r, w, totalSize, mimeType, func(writer io.Writer, offset int64, size int64, httpStatusCode int) error { if _, e = rs.Seek(offset, 0); e != nil { return e } + if httpStatusCode != 0 { + w.WriteHeader(httpStatusCode) + } _, e = io.CopyN(writer, rs, size) return e }) diff --git a/weed/shell/command_fs_cat.go b/weed/shell/command_fs_cat.go index df43d93dc..3c5e13663 100644 --- a/weed/shell/command_fs_cat.go +++ b/weed/shell/command_fs_cat.go @@ -52,7 +52,7 @@ func (c *commandFsCat) Do(args []string, commandEnv *CommandEnv, writer io.Write return err } - return filer.StreamContent(commandEnv.MasterClient, writer, respLookupEntry.Entry.Chunks, 0, math.MaxInt64, false) + return filer.StreamContent(commandEnv.MasterClient, writer, respLookupEntry.Entry.Chunks, 0, math.MaxInt64) }) From 76c48ffe27e339145f140301f9a865bec40b515c Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 6 May 2021 01:53:35 -0700 Subject: [PATCH 114/128] optional parallel copy ec shards fix https://github.com/chrislusf/seaweedfs/issues/2048 --- weed/shell/command_ec_encode.go | 47 +++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/weed/shell/command_ec_encode.go b/weed/shell/command_ec_encode.go index 634cb11e2..8480bab06 100644 --- a/weed/shell/command_ec_encode.go +++ b/weed/shell/command_ec_encode.go @@ -63,6 +63,7 @@ func (c *commandEcEncode) Do(args []string, commandEnv *CommandEnv, writer io.Wr collection := encodeCommand.String("collection", "", "the collection name") fullPercentage := encodeCommand.Float64("fullPercent", 95, "the volume reaches the percentage of max volume size") quietPeriod := encodeCommand.Duration("quietFor", time.Hour, "select volumes without no writes for this period") + parallelCopy := encodeCommand.Bool("parallelCopy", true, "copy shards in parallel") if err = encodeCommand.Parse(args); err != nil { return nil } @@ -71,7 +72,7 @@ func (c *commandEcEncode) Do(args []string, commandEnv *CommandEnv, writer io.Wr // volumeId is provided if vid != 0 { - return doEcEncode(commandEnv, *collection, vid) + return doEcEncode(commandEnv, *collection, vid, *parallelCopy) } // apply to all volumes in the collection @@ -81,7 +82,7 @@ func (c *commandEcEncode) Do(args []string, commandEnv *CommandEnv, writer io.Wr } fmt.Printf("ec encode volumes: %v\n", volumeIds) for _, vid := range volumeIds { - if err = doEcEncode(commandEnv, *collection, vid); err != nil { + if err = doEcEncode(commandEnv, *collection, vid, *parallelCopy); err != nil { return err } } @@ -89,7 +90,7 @@ func (c *commandEcEncode) Do(args []string, commandEnv *CommandEnv, writer io.Wr return nil } -func doEcEncode(commandEnv *CommandEnv, collection string, vid needle.VolumeId) (err error) { +func doEcEncode(commandEnv *CommandEnv, collection string, vid needle.VolumeId, parallelCopy bool) (err error) { // find volume location locations, found := commandEnv.MasterClient.GetLocations(uint32(vid)) if !found { @@ -111,7 +112,7 @@ func doEcEncode(commandEnv *CommandEnv, collection string, vid needle.VolumeId) } // balance the ec shards to current cluster - err = spreadEcShards(commandEnv, vid, collection, locations) + err = spreadEcShards(commandEnv, vid, collection, locations, parallelCopy) if err != nil { return fmt.Errorf("spread ec shards for volume %d from %s: %v", vid, locations[0].Url, err) } @@ -157,7 +158,7 @@ func generateEcShards(grpcDialOption grpc.DialOption, volumeId needle.VolumeId, } -func spreadEcShards(commandEnv *CommandEnv, volumeId needle.VolumeId, collection string, existingLocations []wdclient.Location) (err error) { +func spreadEcShards(commandEnv *CommandEnv, volumeId needle.VolumeId, collection string, existingLocations []wdclient.Location, parallelCopy bool) (err error) { allEcNodes, totalFreeEcSlots, err := collectEcNodes(commandEnv, "") if err != nil { @@ -176,7 +177,7 @@ func spreadEcShards(commandEnv *CommandEnv, volumeId needle.VolumeId, collection allocatedEcIds := balancedEcDistribution(allocatedDataNodes) // ask the data nodes to copy from the source volume server - copiedShardIds, err := parallelCopyEcShardsFromSource(commandEnv.option.GrpcDialOption, allocatedDataNodes, allocatedEcIds, volumeId, collection, existingLocations[0]) + copiedShardIds, err := parallelCopyEcShardsFromSource(commandEnv.option.GrpcDialOption, allocatedDataNodes, allocatedEcIds, volumeId, collection, existingLocations[0], parallelCopy) if err != nil { return err } @@ -206,30 +207,36 @@ func spreadEcShards(commandEnv *CommandEnv, volumeId needle.VolumeId, collection } -func parallelCopyEcShardsFromSource(grpcDialOption grpc.DialOption, targetServers []*EcNode, allocatedEcIds [][]uint32, volumeId needle.VolumeId, collection string, existingLocation wdclient.Location) (actuallyCopied []uint32, err error) { +func parallelCopyEcShardsFromSource(grpcDialOption grpc.DialOption, targetServers []*EcNode, allocatedEcIds [][]uint32, volumeId needle.VolumeId, collection string, existingLocation wdclient.Location, parallelCopy bool) (actuallyCopied []uint32, err error) { fmt.Printf("parallelCopyEcShardsFromSource %d %s\n", volumeId, existingLocation.Url) - // parallelize - shardIdChan := make(chan []uint32, len(targetServers)) var wg sync.WaitGroup + shardIdChan := make(chan []uint32, len(targetServers)) + copyFunc := func(server *EcNode, allocatedEcShardIds []uint32) { + defer wg.Done() + copiedShardIds, copyErr := oneServerCopyAndMountEcShardsFromSource(grpcDialOption, server, + allocatedEcShardIds, volumeId, collection, existingLocation.Url) + if copyErr != nil { + err = copyErr + } else { + shardIdChan <- copiedShardIds + server.addEcVolumeShards(volumeId, collection, copiedShardIds) + } + } + + // maybe parallelize for i, server := range targetServers { if len(allocatedEcIds[i]) <= 0 { continue } wg.Add(1) - go func(server *EcNode, allocatedEcShardIds []uint32) { - defer wg.Done() - copiedShardIds, copyErr := oneServerCopyAndMountEcShardsFromSource(grpcDialOption, server, - allocatedEcShardIds, volumeId, collection, existingLocation.Url) - if copyErr != nil { - err = copyErr - } else { - shardIdChan <- copiedShardIds - server.addEcVolumeShards(volumeId, collection, copiedShardIds) - } - }(server, allocatedEcIds[i]) + if parallelCopy { + go copyFunc(server, allocatedEcIds[i]) + } else { + copyFunc(server, allocatedEcIds[i]) + } } wg.Wait() close(shardIdChan) From 3eb336e0b0aa66eb631da601f8f2315590288bc9 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 6 May 2021 01:57:54 -0700 Subject: [PATCH 115/128] report error only for the first multipart upload the glog.Errorf would always print for s3 multipart uploads --- weed/server/filer_server_handlers_write_upload.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weed/server/filer_server_handlers_write_upload.go b/weed/server/filer_server_handlers_write_upload.go index b15deb9d1..8c6f11a45 100644 --- a/weed/server/filer_server_handlers_write_upload.go +++ b/weed/server/filer_server_handlers_write_upload.go @@ -74,7 +74,7 @@ func (fs *FilerServer) uploadReaderToChunks(w http.ResponseWriter, r *http.Reque } uploadedMd5 := util.Base64Md5ToBytes(uploadResult.ContentMd5) readedMd5 := md5Hash.Sum(nil) - if !bytes.Equal(uploadedMd5, readedMd5) { + if !bytes.Equal(uploadedMd5, readedMd5) && chunkOffset == 0 { glog.Errorf("md5 %x does not match %x uploaded chunk %s to the volume server", readedMd5, uploadedMd5, uploadResult.Name) } From c899bdf0636488dc55c6788cc41ce2329f58a39f Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 6 May 2021 03:03:00 -0700 Subject: [PATCH 116/128] a little optimization --- weed/server/filer_server_handlers_write_upload.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/weed/server/filer_server_handlers_write_upload.go b/weed/server/filer_server_handlers_write_upload.go index 8c6f11a45..540def563 100644 --- a/weed/server/filer_server_handlers_write_upload.go +++ b/weed/server/filer_server_handlers_write_upload.go @@ -72,10 +72,12 @@ func (fs *FilerServer) uploadReaderToChunks(w http.ResponseWriter, r *http.Reque if uploadResult.Size == 0 { break } - uploadedMd5 := util.Base64Md5ToBytes(uploadResult.ContentMd5) - readedMd5 := md5Hash.Sum(nil) - if !bytes.Equal(uploadedMd5, readedMd5) && chunkOffset == 0 { - glog.Errorf("md5 %x does not match %x uploaded chunk %s to the volume server", readedMd5, uploadedMd5, uploadResult.Name) + if chunkOffset == 0 { + uploadedMd5 := util.Base64Md5ToBytes(uploadResult.ContentMd5) + readedMd5 := md5Hash.Sum(nil) + if !bytes.Equal(uploadedMd5, readedMd5) { + glog.Errorf("md5 %x does not match %x uploaded chunk %s to the volume server", readedMd5, uploadedMd5, uploadResult.Name) + } } // Save to chunk manifest structure From 38f411219a759709b53101d3ecd3ed05c36a6efa Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 6 May 2021 03:31:40 -0700 Subject: [PATCH 117/128] mount: skip local chunk cache if opened write only --- weed/filesys/dir.go | 2 +- weed/filesys/dirty_page.go | 3 ++- weed/filesys/file.go | 2 +- weed/filesys/filehandle.go | 5 +++-- weed/filesys/wfs.go | 6 +++++- weed/filesys/wfs_write.go | 6 ++++-- 6 files changed, 16 insertions(+), 8 deletions(-) diff --git a/weed/filesys/dir.go b/weed/filesys/dir.go index 79fc10442..33d9248ab 100644 --- a/weed/filesys/dir.go +++ b/weed/filesys/dir.go @@ -176,7 +176,7 @@ func (dir *Dir) Create(ctx context.Context, req *fuse.CreateRequest, }, } file.dirtyMetadata = true - fh := dir.wfs.AcquireHandle(file, req.Uid, req.Gid) + fh := dir.wfs.AcquireHandle(file, req.Uid, req.Gid, req.Flags & fuse.OpenWriteOnly > 0) return file, fh, nil } diff --git a/weed/filesys/dirty_page.go b/weed/filesys/dirty_page.go index 8888cff96..1719d68e6 100644 --- a/weed/filesys/dirty_page.go +++ b/weed/filesys/dirty_page.go @@ -13,6 +13,7 @@ import ( type ContinuousDirtyPages struct { intervals *ContinuousIntervals f *File + fh *FileHandle writeWaitGroup sync.WaitGroup chunkAddLock sync.Mutex lastErr error @@ -94,7 +95,7 @@ func (pages *ContinuousDirtyPages) saveToStorage(reader io.Reader, offset int64, defer pages.writeWaitGroup.Done() reader = io.LimitReader(reader, size) - chunk, collection, replication, err := pages.f.wfs.saveDataAsChunk(pages.f.fullpath())(reader, pages.f.Name, offset) + chunk, collection, replication, err := pages.f.wfs.saveDataAsChunk(pages.f.fullpath(), pages.fh.writeOnly)(reader, pages.f.Name, offset) if err != nil { glog.V(0).Infof("%s saveToStorage [%d,%d): %v", pages.f.fullpath(), offset, offset+size, err) pages.lastErr = err diff --git a/weed/filesys/file.go b/weed/filesys/file.go index bb57988cd..80905a967 100644 --- a/weed/filesys/file.go +++ b/weed/filesys/file.go @@ -97,7 +97,7 @@ func (file *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.Op glog.V(4).Infof("file %v open %+v", file.fullpath(), req) - handle := file.wfs.AcquireHandle(file, req.Uid, req.Gid) + handle := file.wfs.AcquireHandle(file, req.Uid, req.Gid, req.Flags&fuse.OpenWriteOnly > 0) resp.Handle = fuse.HandleID(handle.handle) diff --git a/weed/filesys/filehandle.go b/weed/filesys/filehandle.go index 27ffab6e1..8cbaf6fd2 100644 --- a/weed/filesys/filehandle.go +++ b/weed/filesys/filehandle.go @@ -32,7 +32,7 @@ type FileHandle struct { NodeId fuse.NodeID // file or directory the request is about Uid uint32 // user ID of process making request Gid uint32 // group ID of process making request - + writeOnly bool } func newFileHandle(file *File, uid, gid uint32) *FileHandle { @@ -42,6 +42,7 @@ func newFileHandle(file *File, uid, gid uint32) *FileHandle { Uid: uid, Gid: gid, } + fh.dirtyPages.fh = fh entry := fh.f.getEntry() if entry != nil { entry.Attributes.FileSize = filer.FileSize(entry) @@ -289,7 +290,7 @@ func (fh *FileHandle) doFlush(ctx context.Context, header fuse.Header) error { manifestChunks, nonManifestChunks := filer.SeparateManifestChunks(entry.Chunks) chunks, _ := filer.CompactFileChunks(fh.f.wfs.LookupFn(), nonManifestChunks) - chunks, manifestErr := filer.MaybeManifestize(fh.f.wfs.saveDataAsChunk(fh.f.fullpath()), chunks) + chunks, manifestErr := filer.MaybeManifestize(fh.f.wfs.saveDataAsChunk(fh.f.fullpath(), fh.writeOnly), chunks) if manifestErr != nil { // not good, but should be ok glog.V(0).Infof("MaybeManifestize: %v", manifestErr) diff --git a/weed/filesys/wfs.go b/weed/filesys/wfs.go index 832925bc1..7757e4285 100644 --- a/weed/filesys/wfs.go +++ b/weed/filesys/wfs.go @@ -138,7 +138,7 @@ func (wfs *WFS) Root() (fs.Node, error) { return wfs.root, nil } -func (wfs *WFS) AcquireHandle(file *File, uid, gid uint32) (fileHandle *FileHandle) { +func (wfs *WFS) AcquireHandle(file *File, uid, gid uint32, writeOnly bool) (fileHandle *FileHandle) { fullpath := file.fullpath() glog.V(4).Infof("AcquireHandle %s uid=%d gid=%d", fullpath, uid, gid) @@ -150,6 +150,9 @@ func (wfs *WFS) AcquireHandle(file *File, uid, gid uint32) (fileHandle *FileHand wfs.handlesLock.Unlock() if found && existingHandle != nil { existingHandle.f.isOpen++ + if existingHandle.writeOnly { + existingHandle.writeOnly = writeOnly + } glog.V(4).Infof("Acquired Handle %s open %d", fullpath, existingHandle.f.isOpen) return existingHandle } @@ -157,6 +160,7 @@ func (wfs *WFS) AcquireHandle(file *File, uid, gid uint32) (fileHandle *FileHand entry, _ := file.maybeLoadEntry(context.Background()) file.entry = entry fileHandle = newFileHandle(file, uid, gid) + fileHandle.writeOnly = writeOnly file.isOpen++ wfs.handlesLock.Lock() diff --git a/weed/filesys/wfs_write.go b/weed/filesys/wfs_write.go index dbec3bebc..9d2ce26ec 100644 --- a/weed/filesys/wfs_write.go +++ b/weed/filesys/wfs_write.go @@ -13,7 +13,7 @@ import ( "github.com/chrislusf/seaweedfs/weed/util" ) -func (wfs *WFS) saveDataAsChunk(fullPath util.FullPath) filer.SaveDataAsChunkFunctionType { +func (wfs *WFS) saveDataAsChunk(fullPath util.FullPath, writeOnly bool) filer.SaveDataAsChunkFunctionType { return func(reader io.Reader, filename string, offset int64) (chunk *filer_pb.FileChunk, collection, replication string, err error) { var fileId, host string @@ -67,7 +67,9 @@ func (wfs *WFS) saveDataAsChunk(fullPath util.FullPath) filer.SaveDataAsChunkFun return nil, "", "", fmt.Errorf("upload result: %v", uploadResult.Error) } - wfs.chunkCache.SetChunk(fileId, data) + if !writeOnly { + wfs.chunkCache.SetChunk(fileId, data) + } chunk = uploadResult.ToPbFileChunk(fileId, offset) return chunk, collection, replication, nil From 5753749c9083ca90c201803f7a9e83086a8e6d8b Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 6 May 2021 03:34:34 -0700 Subject: [PATCH 118/128] remove verbose logs --- weed/filesys/file.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weed/filesys/file.go b/weed/filesys/file.go index 80905a967..122aeeef4 100644 --- a/weed/filesys/file.go +++ b/weed/filesys/file.go @@ -267,7 +267,7 @@ func (file *File) maybeLoadEntry(ctx context.Context) (entry *filer_pb.Entry, er file.wfs.handlesLock.Unlock() entry = file.entry if found { - glog.V(4).Infof("maybeLoadEntry found opened file %s/%s: %v %v", file.dir.FullPath(), file.Name, handle.f.entry, entry) + glog.V(4).Infof("maybeLoadEntry found opened file %s/%s", file.dir.FullPath(), file.Name) entry = handle.f.entry } From 55a8f57381aaeeb0cfc1326646024a7052928965 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 6 May 2021 03:37:51 -0700 Subject: [PATCH 119/128] go fmt --- weed/command/filer_copy.go | 2 +- weed/filer/filer_search.go | 2 +- weed/filer/reader_at_test.go | 2 +- weed/filer/stream.go | 1 - weed/filesys/dir.go | 2 +- weed/filesys/wfs.go | 1 + 6 files changed, 5 insertions(+), 5 deletions(-) diff --git a/weed/command/filer_copy.go b/weed/command/filer_copy.go index e7a9b107f..a9b715f90 100644 --- a/weed/command/filer_copy.go +++ b/weed/command/filer_copy.go @@ -299,7 +299,7 @@ func (worker *FileCopyWorker) uploadFileAsOne(task FileCopyTask, f *os.File) err var assignResult *filer_pb.AssignVolumeResponse var assignError error - if task.fileMode & os.ModeDir == 0 && task.fileSize > 0 { + if task.fileMode&os.ModeDir == 0 && task.fileSize > 0 { mimeType = detectMimeType(f) data, err := ioutil.ReadAll(f) diff --git a/weed/filer/filer_search.go b/weed/filer/filer_search.go index f43312cfa..2ee29be25 100644 --- a/weed/filer/filer_search.go +++ b/weed/filer/filer_search.go @@ -58,7 +58,7 @@ func (f *Filer) StreamListDirectoryEntries(ctx context.Context, p util.FullPath, func (f *Filer) doListPatternMatchedEntries(ctx context.Context, p util.FullPath, startFileName string, inclusive bool, limit int64, prefix, restNamePattern string, namePatternExclude string, eachEntryFunc ListEachEntryFunc) (missedCount int64, lastFileName string, err error) { - if len(restNamePattern) == 0 && len(namePatternExclude) == 0{ + if len(restNamePattern) == 0 && len(namePatternExclude) == 0 { lastFileName, err = f.doListValidEntries(ctx, p, startFileName, inclusive, limit, prefix, eachEntryFunc) return 0, lastFileName, err } diff --git a/weed/filer/reader_at_test.go b/weed/filer/reader_at_test.go index a31319082..f8e4727ce 100644 --- a/weed/filer/reader_at_test.go +++ b/weed/filer/reader_at_test.go @@ -21,7 +21,7 @@ func (m *mockChunkCache) GetChunk(fileId string, minSize uint64) (data []byte) { return data } -func(m *mockChunkCache) GetChunkSlice(fileId string, offset, length uint64) []byte { +func (m *mockChunkCache) GetChunkSlice(fileId string, offset, length uint64) []byte { return nil } diff --git a/weed/filer/stream.go b/weed/filer/stream.go index f9a762187..2c25b8722 100644 --- a/weed/filer/stream.go +++ b/weed/filer/stream.go @@ -33,7 +33,6 @@ func StreamContent(masterClient wdclient.HasLookupFileIdFunction, w io.Writer, c fileId2Url[chunkView.FileId] = urlStrings } - for _, chunkView := range chunkViews { urlStrings := fileId2Url[chunkView.FileId] diff --git a/weed/filesys/dir.go b/weed/filesys/dir.go index 33d9248ab..09d5fd449 100644 --- a/weed/filesys/dir.go +++ b/weed/filesys/dir.go @@ -176,7 +176,7 @@ func (dir *Dir) Create(ctx context.Context, req *fuse.CreateRequest, }, } file.dirtyMetadata = true - fh := dir.wfs.AcquireHandle(file, req.Uid, req.Gid, req.Flags & fuse.OpenWriteOnly > 0) + fh := dir.wfs.AcquireHandle(file, req.Uid, req.Gid, req.Flags&fuse.OpenWriteOnly > 0) return file, fh, nil } diff --git a/weed/filesys/wfs.go b/weed/filesys/wfs.go index 7757e4285..fcce7d9cc 100644 --- a/weed/filesys/wfs.go +++ b/weed/filesys/wfs.go @@ -270,6 +270,7 @@ func (wfs *WFS) LookupFn() wdclient.LookupFileIdFunctionType { } type NodeWithId uint64 + func (n NodeWithId) Id() uint64 { return uint64(n) } From c4d32f6937236a83377e3937e311222fc04bd084 Mon Sep 17 00:00:00 2001 From: qieqieplus Date: Thu, 6 May 2021 18:46:14 +0800 Subject: [PATCH 120/128] ahead of time volume assignment --- weed/server/master_grpc_server_volume.go | 102 ++++++++++++++++---- weed/server/master_server.go | 22 ++++- weed/server/master_server_handlers.go | 20 ++-- weed/server/master_server_handlers_admin.go | 10 +- weed/topology/allocate_volume.go | 2 +- weed/topology/node.go | 12 ++- weed/topology/topology.go | 12 ++- weed/topology/topology_event_handling.go | 19 +++- weed/topology/volume_growth.go | 26 +++-- weed/topology/volume_layout.go | 47 +++++++-- 10 files changed, 202 insertions(+), 70 deletions(-) diff --git a/weed/server/master_grpc_server_volume.go b/weed/server/master_grpc_server_volume.go index 156afd4a1..3a4951cc5 100644 --- a/weed/server/master_grpc_server_volume.go +++ b/weed/server/master_grpc_server_volume.go @@ -4,15 +4,68 @@ import ( "context" "fmt" "github.com/chrislusf/raft" - "github.com/chrislusf/seaweedfs/weed/storage/types" + "reflect" + "sync" + "time" + "github.com/chrislusf/seaweedfs/weed/glog" "github.com/chrislusf/seaweedfs/weed/pb/master_pb" "github.com/chrislusf/seaweedfs/weed/security" "github.com/chrislusf/seaweedfs/weed/storage/needle" "github.com/chrislusf/seaweedfs/weed/storage/super_block" + "github.com/chrislusf/seaweedfs/weed/storage/types" "github.com/chrislusf/seaweedfs/weed/topology" ) +func (ms *MasterServer) ProcessGrowRequest() { + go func() { + filter := sync.Map{} + for { + req, ok := <-ms.vgCh + if !ok { + break + } + + if !ms.Topo.IsLeader() { + //discard buffered requests + time.Sleep(time.Second * 1) + continue + } + + // filter out identical requests being processed + found := false + filter.Range(func(k, v interface{}) bool { + if reflect.DeepEqual(k, req) { + found = true + } + return !found + }) + + // not atomic but it's okay + if !found && ms.shouldVolumeGrow(req.Option) { + filter.Store(req, nil) + // we have lock called inside vg + go func() { + glog.V(1).Infoln("starting automatic volume grow") + start := time.Now() + _, err := ms.vg.AutomaticGrowByType(req.Option, ms.grpcDialOption, ms.Topo, req.Count) + glog.V(1).Infoln("finished automatic volume grow, cost ", time.Now().Sub(start)) + + if req.ErrCh != nil { + req.ErrCh <- err + close(req.ErrCh) + } + + filter.Delete(req) + }() + + } else { + glog.V(4).Infoln("discard volume grow request") + } + } + }() +} + func (ms *MasterServer) LookupVolume(ctx context.Context, req *master_pb.LookupVolumeRequest) (*master_pb.LookupVolumeResponse, error) { if !ms.Topo.IsLeader() { @@ -68,38 +121,45 @@ func (ms *MasterServer) Assign(ctx context.Context, req *master_pb.AssignRequest ReplicaPlacement: replicaPlacement, Ttl: ttl, DiskType: diskType, - Prealloacte: ms.preallocateSize, + Preallocate: ms.preallocateSize, DataCenter: req.DataCenter, Rack: req.Rack, DataNode: req.DataNode, MemoryMapMaxSizeMb: req.MemoryMapMaxSizeMb, } - if !ms.Topo.HasWritableVolume(option) { + if ms.shouldVolumeGrow(option) { if ms.Topo.AvailableSpaceFor(option) <= 0 { return nil, fmt.Errorf("no free volumes left for " + option.String()) } - ms.vgLock.Lock() - if !ms.Topo.HasWritableVolume(option) { - if _, err = ms.vg.AutomaticGrowByType(option, ms.grpcDialOption, ms.Topo, int(req.WritableVolumeCount)); err != nil { - ms.vgLock.Unlock() - return nil, fmt.Errorf("Cannot grow volume group! %v", err) - } + ms.vgCh <- &topology.VolumeGrowRequest{ + Option: option, + Count: int(req.WritableVolumeCount), } - ms.vgLock.Unlock() - } - fid, count, dn, err := ms.Topo.PickForWrite(req.Count, option) - if err != nil { - return nil, fmt.Errorf("%v", err) } - return &master_pb.AssignResponse{ - Fid: fid, - Url: dn.Url(), - PublicUrl: dn.PublicUrl, - Count: count, - Auth: string(security.GenJwt(ms.guard.SigningKey, ms.guard.ExpiresAfterSec, fid)), - }, nil + var ( + lastErr error + maxTimeout = time.Second * 10 + startTime = time.Now() + ) + + for time.Now().Sub(startTime) < maxTimeout { + fid, count, dn, err := ms.Topo.PickForWrite(req.Count, option) + if err == nil { + return &master_pb.AssignResponse{ + Fid: fid, + Url: dn.Url(), + PublicUrl: dn.PublicUrl, + Count: count, + Auth: string(security.GenJwt(ms.guard.SigningKey, ms.guard.ExpiresAfterSec, fid)), + }, nil + } + //glog.V(4).Infoln("waiting for volume growing...") + lastErr = err + time.Sleep(200 * time.Millisecond) + } + return nil, lastErr } func (ms *MasterServer) Statistics(ctx context.Context, req *master_pb.StatisticsRequest) (*master_pb.StatisticsResponse, error) { diff --git a/weed/server/master_server.go b/weed/server/master_server.go index e2b2df18d..838803908 100644 --- a/weed/server/master_server.go +++ b/weed/server/master_server.go @@ -51,9 +51,9 @@ type MasterServer struct { preallocateSize int64 - Topo *topology.Topology - vg *topology.VolumeGrowth - vgLock sync.Mutex + Topo *topology.Topology + vg *topology.VolumeGrowth + vgCh chan *topology.VolumeGrowRequest boundedLeaderChan chan int @@ -82,6 +82,12 @@ func NewMasterServer(r *mux.Router, option *MasterOption, peers []string) *Maste v.SetDefault("master.replication.treat_replication_as_minimums", false) replicationAsMin := v.GetBool("master.replication.treat_replication_as_minimums") + v.SetDefault("master.volume_growth.copy_1", 7) + v.SetDefault("master.volume_growth.copy_2", 6) + v.SetDefault("master.volume_growth.copy_3", 3) + v.SetDefault("master.volume_growth.copy_other", 1) + v.SetDefault("master.volume_growth.threshold", 0.9) + var preallocateSize int64 if option.VolumePreallocate { preallocateSize = int64(option.VolumeSizeLimitMB) * (1 << 20) @@ -91,6 +97,7 @@ func NewMasterServer(r *mux.Router, option *MasterOption, peers []string) *Maste ms := &MasterServer{ option: option, preallocateSize: preallocateSize, + vgCh: make(chan *topology.VolumeGrowRequest, 1 << 6), clientChans: make(map[string]chan *master_pb.VolumeLocation), grpcDialOption: grpcDialOption, MasterClient: wdclient.NewMasterClient(grpcDialOption, "master", option.Host, 0, "", peers), @@ -128,7 +135,14 @@ func NewMasterServer(r *mux.Router, option *MasterOption, peers []string) *Maste r.HandleFunc("/{fileId}", ms.redirectHandler) } - ms.Topo.StartRefreshWritableVolumes(ms.grpcDialOption, ms.option.GarbageThreshold, ms.preallocateSize) + ms.Topo.StartRefreshWritableVolumes( + ms.grpcDialOption, + ms.option.GarbageThreshold, + v.GetFloat64("master.volume_growth.threshold"), + ms.preallocateSize, + ) + + ms.ProcessGrowRequest() ms.startAdminScripts() diff --git a/weed/server/master_server_handlers.go b/weed/server/master_server_handlers.go index a9fecc5bd..974b3308f 100644 --- a/weed/server/master_server_handlers.go +++ b/weed/server/master_server_handlers.go @@ -10,6 +10,7 @@ import ( "github.com/chrislusf/seaweedfs/weed/security" "github.com/chrislusf/seaweedfs/weed/stats" "github.com/chrislusf/seaweedfs/weed/storage/needle" + "github.com/chrislusf/seaweedfs/weed/topology" ) func (ms *MasterServer) lookupVolumeId(vids []string, collection string) (volumeLocations map[string]operation.LookupResult) { @@ -111,19 +112,20 @@ func (ms *MasterServer) dirAssignHandler(w http.ResponseWriter, r *http.Request) return } - if !ms.Topo.HasWritableVolume(option) { + if ms.shouldVolumeGrow(option) { if ms.Topo.AvailableSpaceFor(option) <= 0 { writeJsonQuiet(w, r, http.StatusNotFound, operation.AssignResult{Error: "No free volumes left for " + option.String()}) return } - ms.vgLock.Lock() - defer ms.vgLock.Unlock() - if !ms.Topo.HasWritableVolume(option) { - if _, err = ms.vg.AutomaticGrowByType(option, ms.grpcDialOption, ms.Topo, writableVolumeCount); err != nil { - writeJsonError(w, r, http.StatusInternalServerError, - fmt.Errorf("Cannot grow volume group! %v", err)) - return - } + errCh := make(chan error, 1) + ms.vgCh <- &topology.VolumeGrowRequest{ + Option: option, + Count: writableVolumeCount, + ErrCh: errCh, + } + if err := <- errCh; err != nil { + writeJsonError(w, r, http.StatusInternalServerError, fmt.Errorf("cannot grow volume group! %v", err)) + return } } fid, count, dn, err := ms.Topo.PickForWrite(requestedCount, option) diff --git a/weed/server/master_server_handlers_admin.go b/weed/server/master_server_handlers_admin.go index f24d4e924..fb16ef78c 100644 --- a/weed/server/master_server_handlers_admin.go +++ b/weed/server/master_server_handlers_admin.go @@ -3,7 +3,6 @@ package weed_server import ( "context" "fmt" - "github.com/chrislusf/seaweedfs/weed/storage/types" "math/rand" "net/http" "strconv" @@ -14,6 +13,7 @@ import ( "github.com/chrislusf/seaweedfs/weed/storage/backend/memory_map" "github.com/chrislusf/seaweedfs/weed/storage/needle" "github.com/chrislusf/seaweedfs/weed/storage/super_block" + "github.com/chrislusf/seaweedfs/weed/storage/types" "github.com/chrislusf/seaweedfs/weed/topology" "github.com/chrislusf/seaweedfs/weed/util" ) @@ -136,9 +136,11 @@ func (ms *MasterServer) submitFromMasterServerHandler(w http.ResponseWriter, r * } } -func (ms *MasterServer) HasWritableVolume(option *topology.VolumeGrowOption) bool { +func (ms *MasterServer) shouldVolumeGrow(option *topology.VolumeGrowOption) bool { vl := ms.Topo.GetVolumeLayout(option.Collection, option.ReplicaPlacement, option.Ttl, option.DiskType) - return vl.GetActiveVolumeCount(option) > 0 + active, high := vl.GetActiveVolumeCount(option) + //glog.V(0).Infof("active volume: %d, high usage volume: %d\n", active, high) + return active <= high } func (ms *MasterServer) getVolumeGrowOption(r *http.Request) (*topology.VolumeGrowOption, error) { @@ -172,7 +174,7 @@ func (ms *MasterServer) getVolumeGrowOption(r *http.Request) (*topology.VolumeGr ReplicaPlacement: replicaPlacement, Ttl: ttl, DiskType: diskType, - Prealloacte: preallocate, + Preallocate: preallocate, DataCenter: r.FormValue("dataCenter"), Rack: r.FormValue("rack"), DataNode: r.FormValue("dataNode"), diff --git a/weed/topology/allocate_volume.go b/weed/topology/allocate_volume.go index 39c24ab04..7c7fae683 100644 --- a/weed/topology/allocate_volume.go +++ b/weed/topology/allocate_volume.go @@ -22,7 +22,7 @@ func AllocateVolume(dn *DataNode, grpcDialOption grpc.DialOption, vid needle.Vol Collection: option.Collection, Replication: option.ReplicaPlacement.String(), Ttl: option.Ttl.String(), - Preallocate: option.Prealloacte, + Preallocate: option.Preallocate, MemoryMapMaxSizeMb: option.MemoryMapMaxSizeMb, DiskType: string(option.DiskType), }) diff --git a/weed/topology/node.go b/weed/topology/node.go index 95d63972e..a23729dd3 100644 --- a/weed/topology/node.go +++ b/weed/topology/node.go @@ -25,7 +25,7 @@ type Node interface { SetParent(Node) LinkChildNode(node Node) UnlinkChildNode(nodeId NodeId) - CollectDeadNodeAndFullVolumes(freshThreshHold int64, volumeSizeLimit uint64) + CollectDeadNodeAndFullVolumes(freshThreshHold int64, volumeSizeLimit uint64, growThreshold float64) IsDataNode() bool IsRack() bool @@ -235,20 +235,22 @@ func (n *NodeImpl) UnlinkChildNode(nodeId NodeId) { } } -func (n *NodeImpl) CollectDeadNodeAndFullVolumes(freshThreshHold int64, volumeSizeLimit uint64) { +func (n *NodeImpl) CollectDeadNodeAndFullVolumes(freshThreshHold int64, volumeSizeLimit uint64, growThreshold float64) { if n.IsRack() { for _, c := range n.Children() { dn := c.(*DataNode) //can not cast n to DataNode for _, v := range dn.GetVolumes() { - if uint64(v.Size) >= volumeSizeLimit { + if v.Size >= volumeSizeLimit { //fmt.Println("volume",v.Id,"size",v.Size,">",volumeSizeLimit) - n.GetTopology().chanFullVolumes <- v + n.GetTopology().chanFullVolumes <- &v + }else if float64(v.Size) > float64(volumeSizeLimit) * growThreshold { + n.GetTopology().chanCrowdedVolumes <- &v } } } } else { for _, c := range n.Children() { - c.CollectDeadNodeAndFullVolumes(freshThreshHold, volumeSizeLimit) + c.CollectDeadNodeAndFullVolumes(freshThreshHold, volumeSizeLimit, growThreshold) } } } diff --git a/weed/topology/topology.go b/weed/topology/topology.go index 08ebd24fd..3932e3fbb 100644 --- a/weed/topology/topology.go +++ b/weed/topology/topology.go @@ -34,9 +34,10 @@ type Topology struct { Sequence sequence.Sequencer - chanFullVolumes chan storage.VolumeInfo + chanFullVolumes chan *storage.VolumeInfo + chanCrowdedVolumes chan *storage.VolumeInfo - Configuration *Configuration + Configuration *Configuration RaftServer raft.Server } @@ -56,7 +57,8 @@ func NewTopology(id string, seq sequence.Sequencer, volumeSizeLimit uint64, puls t.Sequence = seq - t.chanFullVolumes = make(chan storage.VolumeInfo) + t.chanFullVolumes = make(chan *storage.VolumeInfo) + t.chanCrowdedVolumes = make(chan *storage.VolumeInfo) t.Configuration = &Configuration{} @@ -122,9 +124,11 @@ func (t *Topology) NextVolumeId() (needle.VolumeId, error) { return next, nil } +// deprecated func (t *Topology) HasWritableVolume(option *VolumeGrowOption) bool { vl := t.GetVolumeLayout(option.Collection, option.ReplicaPlacement, option.Ttl, option.DiskType) - return vl.GetActiveVolumeCount(option) > 0 + active, _ := vl.GetActiveVolumeCount(option) + return active > 0 } func (t *Topology) PickForWrite(count uint64, option *VolumeGrowOption) (string, uint64, *DataNode, error) { diff --git a/weed/topology/topology_event_handling.go b/weed/topology/topology_event_handling.go index 543dacf29..e4eb430fe 100644 --- a/weed/topology/topology_event_handling.go +++ b/weed/topology/topology_event_handling.go @@ -10,12 +10,12 @@ import ( "github.com/chrislusf/seaweedfs/weed/storage" ) -func (t *Topology) StartRefreshWritableVolumes(grpcDialOption grpc.DialOption, garbageThreshold float64, preallocate int64) { +func (t *Topology) StartRefreshWritableVolumes(grpcDialOption grpc.DialOption, garbageThreshold float64, growThreshold float64, preallocate int64) { go func() { for { if t.IsLeader() { freshThreshHold := time.Now().Unix() - 3*t.pulse //3 times of sleep interval - t.CollectDeadNodeAndFullVolumes(freshThreshHold, t.volumeSizeLimit) + t.CollectDeadNodeAndFullVolumes(freshThreshHold, t.volumeSizeLimit, growThreshold) } time.Sleep(time.Duration(float32(t.pulse*1e3)*(1+rand.Float32())) * time.Millisecond) } @@ -31,13 +31,15 @@ func (t *Topology) StartRefreshWritableVolumes(grpcDialOption grpc.DialOption, g go func() { for { select { - case v := <-t.chanFullVolumes: - t.SetVolumeCapacityFull(v) + case fv := <-t.chanFullVolumes: + t.SetVolumeCapacityFull(fv) + case cv := <-t.chanCrowdedVolumes: + t.SetVolumeCrowded(cv) } } }() } -func (t *Topology) SetVolumeCapacityFull(volumeInfo storage.VolumeInfo) bool { +func (t *Topology) SetVolumeCapacityFull(volumeInfo *storage.VolumeInfo) bool { diskType := types.ToDiskType(volumeInfo.DiskType) vl := t.GetVolumeLayout(volumeInfo.Collection, volumeInfo.ReplicaPlacement, volumeInfo.Ttl, diskType) if !vl.SetVolumeCapacityFull(volumeInfo.Id) { @@ -60,6 +62,13 @@ func (t *Topology) SetVolumeCapacityFull(volumeInfo storage.VolumeInfo) bool { } return true } + +func (t *Topology) SetVolumeCrowded(volumeInfo *storage.VolumeInfo) { + diskType := types.ToDiskType(volumeInfo.DiskType) + vl := t.GetVolumeLayout(volumeInfo.Collection, volumeInfo.ReplicaPlacement, volumeInfo.Ttl, diskType) + vl.SetVolumeCrowded(volumeInfo.Id) +} + func (t *Topology) UnRegisterDataNode(dn *DataNode) { for _, v := range dn.GetVolumes() { glog.V(0).Infoln("Removing Volume", v.Id, "from the dead volume server", dn.Id()) diff --git a/weed/topology/volume_growth.go b/weed/topology/volume_growth.go index 8941a049b..ae0b11c81 100644 --- a/weed/topology/volume_growth.go +++ b/weed/topology/volume_growth.go @@ -3,18 +3,17 @@ package topology import ( "encoding/json" "fmt" - "github.com/chrislusf/seaweedfs/weed/storage/types" "math/rand" "sync" - "github.com/chrislusf/seaweedfs/weed/storage/needle" - "github.com/chrislusf/seaweedfs/weed/storage/super_block" - "github.com/chrislusf/seaweedfs/weed/util" - "google.golang.org/grpc" "github.com/chrislusf/seaweedfs/weed/glog" "github.com/chrislusf/seaweedfs/weed/storage" + "github.com/chrislusf/seaweedfs/weed/storage/needle" + "github.com/chrislusf/seaweedfs/weed/storage/super_block" + "github.com/chrislusf/seaweedfs/weed/storage/types" + "github.com/chrislusf/seaweedfs/weed/util" ) /* @@ -25,12 +24,18 @@ This package is created to resolve these replica placement issues: 4. volume allocation for each bucket */ +type VolumeGrowRequest struct { + Option *VolumeGrowOption + Count int + ErrCh chan error +} + type VolumeGrowOption struct { Collection string `json:"collection,omitempty"` ReplicaPlacement *super_block.ReplicaPlacement `json:"replication,omitempty"` Ttl *needle.TTL `json:"ttl,omitempty"` DiskType types.DiskType `json:"disk,omitempty"` - Prealloacte int64 `json:"prealloacte,omitempty"` + Preallocate int64 `json:"preallocate,omitempty"` DataCenter string `json:"dataCenter,omitempty"` Rack string `json:"rack,omitempty"` DataNode string `json:"dataNode,omitempty"` @@ -46,6 +51,11 @@ func (o *VolumeGrowOption) String() string { return string(blob) } +func (o *VolumeGrowOption) Threshold() float64 { + v := util.GetViper() + return v.GetFloat64("master.volume_growth.threshold") +} + func NewDefaultVolumeGrowth() *VolumeGrowth { return &VolumeGrowth{} } @@ -54,10 +64,6 @@ func NewDefaultVolumeGrowth() *VolumeGrowth { // given copyCount, how many logical volumes to create func (vg *VolumeGrowth) findVolumeCount(copyCount int) (count int) { v := util.GetViper() - v.SetDefault("master.volume_growth.copy_1", 7) - v.SetDefault("master.volume_growth.copy_2", 6) - v.SetDefault("master.volume_growth.copy_3", 3) - v.SetDefault("master.volume_growth.copy_other", 1) switch copyCount { case 1: count = v.GetInt("master.volume_growth.copy_1") diff --git a/weed/topology/volume_layout.go b/weed/topology/volume_layout.go index c7e171248..57e511fa0 100644 --- a/weed/topology/volume_layout.go +++ b/weed/topology/volume_layout.go @@ -27,6 +27,7 @@ type volumeState string const ( readOnlyState volumeState = "ReadOnly" oversizedState = "Oversized" + crowdedState = "Crowded" ) type stateIndicator func(copyState) bool @@ -106,7 +107,8 @@ type VolumeLayout struct { ttl *needle.TTL diskType types.DiskType vid2location map[needle.VolumeId]*VolumeLocationList - writables []needle.VolumeId // transient array of writable volume id + writables []needle.VolumeId // transient array of writable volume id + crowded map[needle.VolumeId]interface{} readonlyVolumes *volumesBinaryState // readonly volumes oversizedVolumes *volumesBinaryState // oversized volumes volumeSizeLimit uint64 @@ -127,6 +129,7 @@ func NewVolumeLayout(rp *super_block.ReplicaPlacement, ttl *needle.TTL, diskType diskType: diskType, vid2location: make(map[needle.VolumeId]*VolumeLocationList), writables: *new([]needle.VolumeId), + crowded: make(map[needle.VolumeId]interface{}), readonlyVolumes: NewVolumesBinaryState(readOnlyState, rp, ExistCopies()), oversizedVolumes: NewVolumesBinaryState(oversizedState, rp, ExistCopies()), volumeSizeLimit: volumeSizeLimit, @@ -273,7 +276,7 @@ func (vl *VolumeLayout) PickForWrite(count uint64, option *VolumeGrowOption) (*n lenWriters := len(vl.writables) if lenWriters <= 0 { - glog.V(0).Infoln("No more writable volumes!") + //glog.V(0).Infoln("No more writable volumes!") return nil, 0, nil, errors.New("No more writable volumes!") } if option.DataCenter == "" { @@ -307,14 +310,13 @@ func (vl *VolumeLayout) PickForWrite(count uint64, option *VolumeGrowOption) (*n return &vid, count, locationList, nil } -func (vl *VolumeLayout) GetActiveVolumeCount(option *VolumeGrowOption) int { +func (vl *VolumeLayout) GetActiveVolumeCount(option *VolumeGrowOption) (active, crowded int) { vl.accessLock.RLock() defer vl.accessLock.RUnlock() if option.DataCenter == "" { - return len(vl.writables) + return len(vl.writables), len(vl.crowded) } - counter := 0 for _, v := range vl.writables { for _, dn := range vl.vid2location[v].list { if dn.GetDataCenter().Id() == NodeId(option.DataCenter) { @@ -324,11 +326,15 @@ func (vl *VolumeLayout) GetActiveVolumeCount(option *VolumeGrowOption) int { if option.DataNode != "" && dn.Id() != NodeId(option.DataNode) { continue } - counter++ + active++ + info, _ := dn.GetVolumesById(v) + if float64(info.Size) > float64(vl.volumeSizeLimit)*option.Threshold() { + crowded++ + } } } } - return counter + return } func (vl *VolumeLayout) removeFromWritable(vid needle.VolumeId) bool { @@ -342,6 +348,7 @@ func (vl *VolumeLayout) removeFromWritable(vid needle.VolumeId) bool { if toDeleteIndex >= 0 { glog.V(0).Infoln("Volume", vid, "becomes unwritable") vl.writables = append(vl.writables[0:toDeleteIndex], vl.writables[toDeleteIndex+1:]...) + vl.removeFromCrowded(vid) return true } return false @@ -408,6 +415,32 @@ func (vl *VolumeLayout) SetVolumeCapacityFull(vid needle.VolumeId) bool { return vl.removeFromWritable(vid) } +func (vl *VolumeLayout) removeFromCrowded(vid needle.VolumeId) { + delete(vl.crowded, vid) +} + +func (vl *VolumeLayout) setVolumeCrowded(vid needle.VolumeId) { + if _, ok := vl.crowded[vid]; !ok { + vl.crowded[vid] = nil + glog.V(0).Infoln("Volume", vid, "becomes crowded") + } +} + +func (vl *VolumeLayout) SetVolumeCrowded(vid needle.VolumeId) { + // since delete is guarded by accessLock.Lock(), + // and is always called in sequential order, + // RLock() should be safe enough + vl.accessLock.RLock() + defer vl.accessLock.RUnlock() + + for _, v := range vl.writables { + if v == vid { + vl.setVolumeCrowded(vid) + break + } + } +} + func (vl *VolumeLayout) ToMap() map[string]interface{} { m := make(map[string]interface{}) m["replication"] = vl.rp.String() From 9c97b172913f112425fad70593202f71f86e14eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Barotin?= Date: Thu, 6 May 2021 19:21:23 +0200 Subject: [PATCH 121/128] make SeaweedInputStream throw FileNotFoundException --- .../src/main/java/seaweedfs/client/SeaweedInputStream.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/other/java/client/src/main/java/seaweedfs/client/SeaweedInputStream.java b/other/java/client/src/main/java/seaweedfs/client/SeaweedInputStream.java index 4e40ce1b6..6097b8d56 100644 --- a/other/java/client/src/main/java/seaweedfs/client/SeaweedInputStream.java +++ b/other/java/client/src/main/java/seaweedfs/client/SeaweedInputStream.java @@ -6,6 +6,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.EOFException; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; @@ -34,6 +35,10 @@ public class SeaweedInputStream extends InputStream { this.entry = filerClient.lookupEntry( SeaweedOutputStream.getParentDirectory(fullpath), SeaweedOutputStream.getFileName(fullpath)); + if(entry == null){ + throw new FileNotFoundException(); + } + this.contentLength = SeaweedRead.fileSize(entry); this.visibleIntervalList = SeaweedRead.nonOverlappingVisibleIntervals(filerClient, entry.getChunksList()); From 28d58bac62a2721261dca9e0e90525eba272b02c Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 6 May 2021 11:25:36 -0700 Subject: [PATCH 122/128] another simple example --- .../seaweedfs/examples/ExampleWriteFile2.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 other/java/examples/src/main/java/com/seaweedfs/examples/ExampleWriteFile2.java diff --git a/other/java/examples/src/main/java/com/seaweedfs/examples/ExampleWriteFile2.java b/other/java/examples/src/main/java/com/seaweedfs/examples/ExampleWriteFile2.java new file mode 100644 index 000000000..61d8c290f --- /dev/null +++ b/other/java/examples/src/main/java/com/seaweedfs/examples/ExampleWriteFile2.java @@ -0,0 +1,22 @@ +package com.seaweedfs.examples; + +import com.google.common.io.Files; +import seaweedfs.client.FilerClient; +import seaweedfs.client.SeaweedOutputStream; + +import java.io.File; +import java.io.IOException; + +public class ExampleWriteFile2 { + + public static void main(String[] args) throws IOException { + + FilerClient filerClient = new FilerClient("localhost", 18888); + + SeaweedOutputStream seaweedOutputStream = new SeaweedOutputStream(filerClient, "/test/1"); + Files.copy(new File("/etc/resolv.conf"), seaweedOutputStream); + seaweedOutputStream.close(); + + } + +} From 9b5f54e36733854fc8d6897808ce53349cf7b785 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 6 May 2021 11:38:23 -0700 Subject: [PATCH 123/128] java: filer client add modified time to touch() function --- .../java/seaweedfs/client/FilerClient.java | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/other/java/client/src/main/java/seaweedfs/client/FilerClient.java b/other/java/client/src/main/java/seaweedfs/client/FilerClient.java index 257a9873d..91e7cba57 100644 --- a/other/java/client/src/main/java/seaweedfs/client/FilerClient.java +++ b/other/java/client/src/main/java/seaweedfs/client/FilerClient.java @@ -142,10 +142,12 @@ public class FilerClient extends FilerGrpcClient { public boolean touch(String path, int mode) { String currentUser = System.getProperty("user.name"); - return touch(path, mode, 0, 0, currentUser, new String[]{}); + + long now = System.currentTimeMillis() / 1000L; + return touch(path, now, mode, 0, 0, currentUser, new String[]{}); } - public boolean touch(String path, int mode, int uid, int gid, String userName, String[] groupNames) { + public boolean touch(String path, long modifiedTimeSecond, int mode, int uid, int gid, String userName, String[] groupNames) { File pathFile = new File(path); String parent = pathFile.getParent().replace('\\','/'); @@ -155,17 +157,25 @@ public class FilerClient extends FilerGrpcClient { if (entry == null) { return createEntry( parent, - newFileEntry(name, mode, uid, gid, userName, groupNames).build() + newFileEntry(name, modifiedTimeSecond, mode, uid, gid, userName, groupNames).build() ); } - long now = System.currentTimeMillis() / 1000L; - FilerProto.FuseAttributes.Builder attr = entry.getAttributes().toBuilder() - .setMtime(now) - .setUid(uid) - .setGid(gid) - .setUserName(userName) - .clearGroupName() - .addAllGroupName(Arrays.asList(groupNames)); + FilerProto.FuseAttributes.Builder attr = entry.getAttributes().toBuilder(); + if (modifiedTimeSecond>0) { + attr.setMtime(modifiedTimeSecond); + } + if (uid>0) { + attr.setUid(uid); + } + if (gid>0) { + attr.setGid(gid); + } + if (userName!=null) { + attr.setUserName(userName); + } + if (groupNames!=null) { + attr.clearGroupName().addAllGroupName(Arrays.asList(groupNames)); + } return updateEntry(parent, entry.toBuilder().setAttributes(attr).build()); } @@ -188,17 +198,15 @@ public class FilerClient extends FilerGrpcClient { .addAllGroupName(Arrays.asList(groupNames))); } - public FilerProto.Entry.Builder newFileEntry(String name, int mode, + public FilerProto.Entry.Builder newFileEntry(String name, long modifiedTimeSecond, int mode, int uid, int gid, String userName, String[] groupNames) { - long now = System.currentTimeMillis() / 1000L; - return FilerProto.Entry.newBuilder() .setName(name) .setIsDirectory(false) .setAttributes(FilerProto.FuseAttributes.newBuilder() - .setMtime(now) - .setCrtime(now) + .setMtime(modifiedTimeSecond) + .setCrtime(modifiedTimeSecond) .setUid(uid) .setGid(gid) .setFileMode(mode) From a46be0ca5607d47daf64f63b6558bf70e6084951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Barotin?= Date: Fri, 7 May 2021 09:24:24 +0200 Subject: [PATCH 124/128] Add exists() to java client --- .../src/main/java/seaweedfs/client/FilerClient.java | 5 +++++ .../src/test/java/seaweedfs/client/SeaweedFilerTest.java | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/other/java/client/src/main/java/seaweedfs/client/FilerClient.java b/other/java/client/src/main/java/seaweedfs/client/FilerClient.java index 91e7cba57..de03efbb4 100644 --- a/other/java/client/src/main/java/seaweedfs/client/FilerClient.java +++ b/other/java/client/src/main/java/seaweedfs/client/FilerClient.java @@ -126,6 +126,11 @@ public class FilerClient extends FilerGrpcClient { } + public boolean exists(String path){ + File pathFile = new File(path); + return lookupEntry(pathFile.getParent(), pathFile.getName()) != null; + } + public boolean rm(String path, boolean isRecursive, boolean ignoreRecusiveError) { File pathFile = new File(path); diff --git a/other/java/client/src/test/java/seaweedfs/client/SeaweedFilerTest.java b/other/java/client/src/test/java/seaweedfs/client/SeaweedFilerTest.java index eaf17e5c6..c45ce7a9d 100644 --- a/other/java/client/src/test/java/seaweedfs/client/SeaweedFilerTest.java +++ b/other/java/client/src/test/java/seaweedfs/client/SeaweedFilerTest.java @@ -16,8 +16,14 @@ public class SeaweedFilerTest { filerClient.mkdirs("/new_folder", 0755); filerClient.touch("/new_folder/new_empty_file", 0755); filerClient.touch("/new_folder/new_empty_file2", 0755); + if(!filerClient.exists("/new_folder/new_empty_file")){ + System.out.println("/new_folder/new_empty_file should exists"); + } + filerClient.rm("/new_folder/new_empty_file", false, true); filerClient.rm("/new_folder", true, true); - + if(filerClient.exists("/new_folder/new_empty_file")){ + System.out.println("/new_folder/new_empty_file should not exists"); + } } } From 89b2ef8d055866933f72c8b4d4480585df0339ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Barotin?= Date: Fri, 7 May 2021 10:05:48 +0200 Subject: [PATCH 125/128] handle "/" in exist --- .../src/main/java/seaweedfs/client/FilerClient.java | 9 ++++++++- .../src/test/java/seaweedfs/client/SeaweedFilerTest.java | 3 +++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/other/java/client/src/main/java/seaweedfs/client/FilerClient.java b/other/java/client/src/main/java/seaweedfs/client/FilerClient.java index de03efbb4..0a8356258 100644 --- a/other/java/client/src/main/java/seaweedfs/client/FilerClient.java +++ b/other/java/client/src/main/java/seaweedfs/client/FilerClient.java @@ -128,7 +128,14 @@ public class FilerClient extends FilerGrpcClient { public boolean exists(String path){ File pathFile = new File(path); - return lookupEntry(pathFile.getParent(), pathFile.getName()) != null; + String parent = pathFile.getParent(); + String entryName = pathFile.getName(); + if(parent == null) { + parent = path; + entryName =""; + } + return lookupEntry(parent, entryName) != null; + } public boolean rm(String path, boolean isRecursive, boolean ignoreRecusiveError) { diff --git a/other/java/client/src/test/java/seaweedfs/client/SeaweedFilerTest.java b/other/java/client/src/test/java/seaweedfs/client/SeaweedFilerTest.java index c45ce7a9d..f9a2c3f76 100644 --- a/other/java/client/src/test/java/seaweedfs/client/SeaweedFilerTest.java +++ b/other/java/client/src/test/java/seaweedfs/client/SeaweedFilerTest.java @@ -25,5 +25,8 @@ public class SeaweedFilerTest { if(filerClient.exists("/new_folder/new_empty_file")){ System.out.println("/new_folder/new_empty_file should not exists"); } + if(!filerClient.exists("/")){ + System.out.println("/ should exists"); + } } } From 930581cedc00c86cfbc711efe0b99d7e65f09d11 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 7 May 2021 07:09:02 -0700 Subject: [PATCH 126/128] fuse: avoid nil when invalidating entry fix https://github.com/chrislusf/seaweedfs/issues/2055 --- go.mod | 2 +- go.sum | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index f7ae83458..f54c82994 100644 --- a/go.mod +++ b/go.mod @@ -62,7 +62,7 @@ require ( github.com/prometheus/client_golang v1.3.0 github.com/rakyll/statik v0.1.7 github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 // indirect - github.com/seaweedfs/fuse v1.1.4 + github.com/seaweedfs/fuse v1.1.6 github.com/seaweedfs/goexif v1.0.2 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/spaolacci/murmur3 v1.1.0 // indirect diff --git a/go.sum b/go.sum index 972bd72dd..f2cc6d26d 100644 --- a/go.sum +++ b/go.sum @@ -695,6 +695,10 @@ github.com/seaweedfs/fuse v1.1.3 h1:0DddotXwSRGbYG2kynoJyr8GHCy30Z2SpdhP3vdyijY= github.com/seaweedfs/fuse v1.1.3/go.mod h1:+PP6WlkrRUG6KPE+Th2EX5To/PjHaFsvqg/UgQ39aj8= github.com/seaweedfs/fuse v1.1.4 h1:YYqkK86agMhXRSwR+wFbRI8ikMgk3kL6PNTna1MAHyQ= github.com/seaweedfs/fuse v1.1.4/go.mod h1:+PP6WlkrRUG6KPE+Th2EX5To/PjHaFsvqg/UgQ39aj8= +github.com/seaweedfs/fuse v1.1.5 h1:wyuRh/mDvrvt8ZLDS7YdPSe6nczniSx4sQFs/Jonveo= +github.com/seaweedfs/fuse v1.1.5/go.mod h1:+PP6WlkrRUG6KPE+Th2EX5To/PjHaFsvqg/UgQ39aj8= +github.com/seaweedfs/fuse v1.1.6 h1:kvCqaIsCEaYOBw5r8kJPUs9GcbwlIKcScnkPLT7HLuQ= +github.com/seaweedfs/fuse v1.1.6/go.mod h1:+PP6WlkrRUG6KPE+Th2EX5To/PjHaFsvqg/UgQ39aj8= github.com/seaweedfs/goexif v1.0.2 h1:p+rTXYdQ2mgxd+1JaTrQ9N8DvYuw9UH9xgYmJ+Bb29E= github.com/seaweedfs/goexif v1.0.2/go.mod h1:MrKs5LK0HXdffrdCZrW3OIMegL2xXpC6ThLyXMyjdrk= github.com/secsy/goftp v0.0.0-20190720192957-f31499d7c79a h1:C6IhVTxNkhlb0tlCB6JfHOUv1f0xHPK7V8X4HlJZEJw= From 007401f3a0b87f0a3d07f16ea731d6eec63d6975 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 7 May 2021 07:14:24 -0700 Subject: [PATCH 127/128] remove duplicated code --- weed/command/filer_copy.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/weed/command/filer_copy.go b/weed/command/filer_copy.go index a9b715f90..ad3500915 100644 --- a/weed/command/filer_copy.go +++ b/weed/command/filer_copy.go @@ -426,9 +426,6 @@ func (worker *FileCopyWorker) uploadFileInChunks(task FileCopyTask, f *os.File, if err != nil { fmt.Printf("Failed to assign from %v: %v\n", worker.options.masters, err) } - if err != nil { - fmt.Printf("Failed to assign from %v: %v\n", worker.options.masters, err) - } targetUrl := "http://" + assignResult.Url + "/" + assignResult.FileId if collection == "" { From 8f8738867f10c98d59b11eaf165e1c6028d7b1d0 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 7 May 2021 07:29:26 -0700 Subject: [PATCH 128/128] add retry to assign volume fix https://github.com/chrislusf/seaweedfs/issues/2056 --- weed/command/filer_copy.go | 76 ++++++++++--------- weed/filesys/wfs_write.go | 53 ++++++------- weed/messaging/broker/broker_append.go | 45 ++++++----- .../replication/sink/filersink/fetch_write.go | 45 +++++------ weed/server/webdav_server.go | 41 +++++----- 5 files changed, 140 insertions(+), 120 deletions(-) diff --git a/weed/command/filer_copy.go b/weed/command/filer_copy.go index ad3500915..dc729ed33 100644 --- a/weed/command/filer_copy.go +++ b/weed/command/filer_copy.go @@ -308,25 +308,27 @@ func (worker *FileCopyWorker) uploadFileAsOne(task FileCopyTask, f *os.File) err } // assign a volume - err = pb.WithGrpcFilerClient(worker.filerGrpcAddress, worker.options.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error { - - request := &filer_pb.AssignVolumeRequest{ - Count: 1, - Replication: *worker.options.replication, - Collection: *worker.options.collection, - TtlSec: worker.options.ttlSec, - DiskType: *worker.options.diskType, - Path: task.destinationUrlPath, - } + err = util.Retry("assignVolume", func() error { + return pb.WithGrpcFilerClient(worker.filerGrpcAddress, worker.options.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error { - assignResult, assignError = client.AssignVolume(context.Background(), request) - if assignError != nil { - return fmt.Errorf("assign volume failure %v: %v", request, assignError) - } - if assignResult.Error != "" { - return fmt.Errorf("assign volume failure %v: %v", request, assignResult.Error) - } - return nil + request := &filer_pb.AssignVolumeRequest{ + Count: 1, + Replication: *worker.options.replication, + Collection: *worker.options.collection, + TtlSec: worker.options.ttlSec, + DiskType: *worker.options.diskType, + Path: task.destinationUrlPath, + } + + assignResult, assignError = client.AssignVolume(context.Background(), request) + if assignError != nil { + return fmt.Errorf("assign volume failure %v: %v", request, assignError) + } + if assignResult.Error != "" { + return fmt.Errorf("assign volume failure %v: %v", request, assignResult.Error) + } + return nil + }) }) if err != nil { return fmt.Errorf("Failed to assign from %v: %v\n", worker.options.masters, err) @@ -404,24 +406,26 @@ func (worker *FileCopyWorker) uploadFileInChunks(task FileCopyTask, f *os.File, // assign a volume var assignResult *filer_pb.AssignVolumeResponse var assignError error - err := pb.WithGrpcFilerClient(worker.filerGrpcAddress, worker.options.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error { - request := &filer_pb.AssignVolumeRequest{ - Count: 1, - Replication: *worker.options.replication, - Collection: *worker.options.collection, - TtlSec: worker.options.ttlSec, - DiskType: *worker.options.diskType, - Path: task.destinationUrlPath + fileName, - } - - assignResult, assignError = client.AssignVolume(context.Background(), request) - if assignError != nil { - return fmt.Errorf("assign volume failure %v: %v", request, assignError) - } - if assignResult.Error != "" { - return fmt.Errorf("assign volume failure %v: %v", request, assignResult.Error) - } - return nil + err := util.Retry("assignVolume", func() error { + return pb.WithGrpcFilerClient(worker.filerGrpcAddress, worker.options.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error { + request := &filer_pb.AssignVolumeRequest{ + Count: 1, + Replication: *worker.options.replication, + Collection: *worker.options.collection, + TtlSec: worker.options.ttlSec, + DiskType: *worker.options.diskType, + Path: task.destinationUrlPath + fileName, + } + + assignResult, assignError = client.AssignVolume(context.Background(), request) + if assignError != nil { + return fmt.Errorf("assign volume failure %v: %v", request, assignError) + } + if assignResult.Error != "" { + return fmt.Errorf("assign volume failure %v: %v", request, assignResult.Error) + } + return nil + }) }) if err != nil { fmt.Printf("Failed to assign from %v: %v\n", worker.options.masters, err) diff --git a/weed/filesys/wfs_write.go b/weed/filesys/wfs_write.go index 9d2ce26ec..730578202 100644 --- a/weed/filesys/wfs_write.go +++ b/weed/filesys/wfs_write.go @@ -20,35 +20,36 @@ func (wfs *WFS) saveDataAsChunk(fullPath util.FullPath, writeOnly bool) filer.Sa var auth security.EncodedJwt if err := wfs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error { + return util.Retry("assignVolume", func() error { + request := &filer_pb.AssignVolumeRequest{ + Count: 1, + Replication: wfs.option.Replication, + Collection: wfs.option.Collection, + TtlSec: wfs.option.TtlSec, + DiskType: string(wfs.option.DiskType), + DataCenter: wfs.option.DataCenter, + Path: string(fullPath), + } - request := &filer_pb.AssignVolumeRequest{ - Count: 1, - Replication: wfs.option.Replication, - Collection: wfs.option.Collection, - TtlSec: wfs.option.TtlSec, - DiskType: string(wfs.option.DiskType), - DataCenter: wfs.option.DataCenter, - Path: string(fullPath), - } + resp, err := client.AssignVolume(context.Background(), request) + if err != nil { + glog.V(0).Infof("assign volume failure %v: %v", request, err) + return err + } + if resp.Error != "" { + return fmt.Errorf("assign volume failure %v: %v", request, resp.Error) + } - resp, err := client.AssignVolume(context.Background(), request) - if err != nil { - glog.V(0).Infof("assign volume failure %v: %v", request, err) - return err - } - if resp.Error != "" { - return fmt.Errorf("assign volume failure %v: %v", request, resp.Error) - } + fileId, auth = resp.FileId, security.EncodedJwt(resp.Auth) + loc := &filer_pb.Location{ + Url: resp.Url, + PublicUrl: resp.PublicUrl, + } + host = wfs.AdjustedUrl(loc) + collection, replication = resp.Collection, resp.Replication - fileId, auth = resp.FileId, security.EncodedJwt(resp.Auth) - loc := &filer_pb.Location{ - Url: resp.Url, - PublicUrl: resp.PublicUrl, - } - host = wfs.AdjustedUrl(loc) - collection, replication = resp.Collection, resp.Replication - - return nil + return nil + }) }); err != nil { return nil, "", "", fmt.Errorf("filerGrpcAddress assign volume: %v", err) } diff --git a/weed/messaging/broker/broker_append.go b/weed/messaging/broker/broker_append.go index 8e5b56fd0..40c807164 100644 --- a/weed/messaging/broker/broker_append.go +++ b/weed/messaging/broker/broker_append.go @@ -3,6 +3,7 @@ package broker import ( "context" "fmt" + "github.com/chrislusf/seaweedfs/weed/security" "io" "github.com/chrislusf/seaweedfs/weed/glog" @@ -10,7 +11,6 @@ import ( "github.com/chrislusf/seaweedfs/weed/pb" "github.com/chrislusf/seaweedfs/weed/pb/filer_pb" "github.com/chrislusf/seaweedfs/weed/pb/messaging_pb" - "github.com/chrislusf/seaweedfs/weed/security" "github.com/chrislusf/seaweedfs/weed/util" ) @@ -53,26 +53,33 @@ func (broker *MessageBroker) assignAndUpload(topicConfig *messaging_pb.TopicConf // assign a volume location if err := broker.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error { - request := &filer_pb.AssignVolumeRequest{ - Count: 1, - Replication: topicConfig.Replication, - Collection: topicConfig.Collection, - } + assignErr := util.Retry("assignVolume", func() error { + request := &filer_pb.AssignVolumeRequest{ + Count: 1, + Replication: topicConfig.Replication, + Collection: topicConfig.Collection, + } - resp, err := client.AssignVolume(context.Background(), request) - if err != nil { - glog.V(0).Infof("assign volume failure %v: %v", request, err) - return err - } - if resp.Error != "" { - return fmt.Errorf("assign volume failure %v: %v", request, resp.Error) - } + resp, err := client.AssignVolume(context.Background(), request) + if err != nil { + glog.V(0).Infof("assign volume failure %v: %v", request, err) + return err + } + if resp.Error != "" { + return fmt.Errorf("assign volume failure %v: %v", request, resp.Error) + } + + assignResult.Auth = security.EncodedJwt(resp.Auth) + assignResult.Fid = resp.FileId + assignResult.Url = resp.Url + assignResult.PublicUrl = resp.PublicUrl + assignResult.Count = uint64(resp.Count) - assignResult.Auth = security.EncodedJwt(resp.Auth) - assignResult.Fid = resp.FileId - assignResult.Url = resp.Url - assignResult.PublicUrl = resp.PublicUrl - assignResult.Count = uint64(resp.Count) + return nil + }) + if assignErr != nil { + return assignErr + } return nil }); err != nil { diff --git a/weed/replication/sink/filersink/fetch_write.go b/weed/replication/sink/filersink/fetch_write.go index a7392d856..b5ea3e2cb 100644 --- a/weed/replication/sink/filersink/fetch_write.go +++ b/weed/replication/sink/filersink/fetch_write.go @@ -71,29 +71,30 @@ func (fs *FilerSink) fetchAndWrite(sourceChunk *filer_pb.FileChunk, path string) var auth security.EncodedJwt if err := fs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error { + return util.Retry("assignVolume", func() error { + request := &filer_pb.AssignVolumeRequest{ + Count: 1, + Replication: fs.replication, + Collection: fs.collection, + TtlSec: fs.ttlSec, + DataCenter: fs.dataCenter, + DiskType: fs.diskType, + Path: path, + } + + resp, err := client.AssignVolume(context.Background(), request) + if err != nil { + glog.V(0).Infof("assign volume failure %v: %v", request, err) + return err + } + if resp.Error != "" { + return fmt.Errorf("assign volume failure %v: %v", request, resp.Error) + } + + fileId, host, auth = resp.FileId, resp.Url, security.EncodedJwt(resp.Auth) - request := &filer_pb.AssignVolumeRequest{ - Count: 1, - Replication: fs.replication, - Collection: fs.collection, - TtlSec: fs.ttlSec, - DataCenter: fs.dataCenter, - DiskType: fs.diskType, - Path: path, - } - - resp, err := client.AssignVolume(context.Background(), request) - if err != nil { - glog.V(0).Infof("assign volume failure %v: %v", request, err) - return err - } - if resp.Error != "" { - return fmt.Errorf("assign volume failure %v: %v", request, resp.Error) - } - - fileId, host, auth = resp.FileId, resp.Url, security.EncodedJwt(resp.Auth) - - return nil + return nil + }) }); err != nil { return "", fmt.Errorf("filerGrpcAddress assign volume: %v", err) } diff --git a/weed/server/webdav_server.go b/weed/server/webdav_server.go index c3f68fdee..c6550a36f 100644 --- a/weed/server/webdav_server.go +++ b/weed/server/webdav_server.go @@ -380,25 +380,32 @@ func (f *WebDavFile) saveDataAsChunk(reader io.Reader, name string, offset int64 ctx := context.Background() - request := &filer_pb.AssignVolumeRequest{ - Count: 1, - Replication: f.fs.option.Replication, - Collection: f.fs.option.Collection, - DiskType: f.fs.option.DiskType, - Path: name, - } + assignErr := util.Retry("assignVolume", func() error { + request := &filer_pb.AssignVolumeRequest{ + Count: 1, + Replication: f.fs.option.Replication, + Collection: f.fs.option.Collection, + DiskType: f.fs.option.DiskType, + Path: name, + } - resp, err := client.AssignVolume(ctx, request) - if err != nil { - glog.V(0).Infof("assign volume failure %v: %v", request, err) - return err - } - if resp.Error != "" { - return fmt.Errorf("assign volume failure %v: %v", request, resp.Error) - } + resp, err := client.AssignVolume(ctx, request) + if err != nil { + glog.V(0).Infof("assign volume failure %v: %v", request, err) + return err + } + if resp.Error != "" { + return fmt.Errorf("assign volume failure %v: %v", request, resp.Error) + } + + fileId, host, auth = resp.FileId, resp.Url, security.EncodedJwt(resp.Auth) + f.collection, f.replication = resp.Collection, resp.Replication - fileId, host, auth = resp.FileId, resp.Url, security.EncodedJwt(resp.Auth) - f.collection, f.replication = resp.Collection, resp.Replication + return nil + }) + if assignErr != nil { + return assignErr + } return nil }); flushErr != nil {