From f057a4303c571d5db2093e09925623dfd1238971 Mon Sep 17 00:00:00 2001
From: Dmitry Kolesnikov
Date: Sun, 12 May 2024 21:53:53 +0300
Subject: [PATCH 01/12] Golang file system (fs.FS) abstraction tailored for AWS
S3
---
.github/workflows/check-code.yml | 8 +-
.github/workflows/check-test.yml | 6 +-
Makefile | 18 --
README.md | 401 ++++++++++++++++++---------
codec.go | 146 ++++++++++
dir.go | 101 +++++++
doc/stream.png | Bin 0 -> 968980 bytes
examples/go.mod | 18 +-
examples/go.sum | 26 +-
examples/s3fs-metadata/s3fs.go | 128 +++++++++
examples/s3fs-presignurl/s3fs.go | 66 +++++
examples/s3fs-read-write/s3fs.go | 73 +++++
examples/s3fs-stat/s3fs.go | 82 ++++++
examples/s3fs-walk/s3fs.go | 76 ++++++
examples/s3url/s3url.go | 271 ------------------
examples/stream/stream.go | 212 --------------
file.go | 282 +++++++++++++++++++
filesystem.go | 399 +++++++++++++++++++++++++++
filesystem_test.go | 456 +++++++++++++++++++++++++++++++
go.mod | 10 +-
go.sum | 17 +-
internal/codec/codec.go | 322 ----------------------
internal/codec/codec_test.go | 159 -----------
internal/mocks/s3.go | 155 +++++++++++
internal/s3ts/errors.go | 53 ----
internal/s3ts/opCopy.go | 35 ---
internal/s3ts/opHas.go | 41 ---
internal/s3ts/opMatch.go | 74 -----
internal/s3ts/opRemove.go | 32 ---
internal/s3ts/opVisit.go | 40 ---
internal/s3ts/opWait.go | 32 ---
internal/s3ts/store.go | 46 ----
options.go | 41 +++
service/s3/options.go | 52 ----
service/s3/s3.go | 121 --------
service/s3url/options.go | 52 ----
service/s3url/s3url.go | 126 ---------
types.go | 143 ++++------
38 files changed, 2355 insertions(+), 1965 deletions(-)
delete mode 100644 Makefile
create mode 100644 codec.go
create mode 100644 dir.go
create mode 100644 doc/stream.png
create mode 100644 examples/s3fs-metadata/s3fs.go
create mode 100644 examples/s3fs-presignurl/s3fs.go
create mode 100644 examples/s3fs-read-write/s3fs.go
create mode 100644 examples/s3fs-stat/s3fs.go
create mode 100644 examples/s3fs-walk/s3fs.go
delete mode 100644 examples/s3url/s3url.go
delete mode 100644 examples/stream/stream.go
create mode 100644 file.go
create mode 100644 filesystem.go
create mode 100644 filesystem_test.go
delete mode 100644 internal/codec/codec.go
delete mode 100644 internal/codec/codec_test.go
create mode 100644 internal/mocks/s3.go
delete mode 100644 internal/s3ts/errors.go
delete mode 100644 internal/s3ts/opCopy.go
delete mode 100644 internal/s3ts/opHas.go
delete mode 100644 internal/s3ts/opMatch.go
delete mode 100644 internal/s3ts/opRemove.go
delete mode 100644 internal/s3ts/opVisit.go
delete mode 100644 internal/s3ts/opWait.go
delete mode 100644 internal/s3ts/store.go
create mode 100644 options.go
delete mode 100644 service/s3/options.go
delete mode 100644 service/s3/s3.go
delete mode 100644 service/s3url/options.go
delete mode 100644 service/s3url/s3url.go
diff --git a/.github/workflows/check-code.yml b/.github/workflows/check-code.yml
index b3227c8..d02f718 100644
--- a/.github/workflows/check-code.yml
+++ b/.github/workflows/check-code.yml
@@ -14,12 +14,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/setup-go@v4
+ - uses: actions/setup-go@v5
with:
- go-version: "1.20"
+ go-version: "1.22"
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v4.1.1
- - uses: dominikh/staticcheck-action@v1.2.0
+ - uses: dominikh/staticcheck-action@v1.3.0
with:
install-go: false
diff --git a/.github/workflows/check-test.yml b/.github/workflows/check-test.yml
index 56d1413..fda306b 100644
--- a/.github/workflows/check-test.yml
+++ b/.github/workflows/check-test.yml
@@ -19,11 +19,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/setup-go@v4
+ - uses: actions/setup-go@v5
with:
- go-version: "1.20"
+ go-version: "1.22"
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v4.1.1
- name: go build
run: |
diff --git a/Makefile b/Makefile
deleted file mode 100644
index bf3adcf..0000000
--- a/Makefile
+++ /dev/null
@@ -1,18 +0,0 @@
-.PHONY: all deps ask check
-
-##
-##
-all:
- @go test ./...
-
-##
-##
-deps: check ask
- $(shell $(shell go list -u -f "{{if (and (not (or .Main .Indirect)) .Update)}}go get -d {{.Path}}@{{.Update.Version}} ; {{end}}" -m all))
- go mod tidy
-
-check:
- @go list -u -f '{{if (and (not (or .Main .Indirect)) .Update)}}{{.Path}}: {{.Version}} -> {{.Update.Version}}{{end}}' -m all
-
-ask:
- @echo "\nUpdate go.mod? [y/N] " && read ans && [ $${ans:-N} = y ]
diff --git a/README.md b/README.md
index 2c32175..e88d978 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,81 @@
-# stream
+
+
+
stream
+ Golang file system abstraction tailored for AWS S3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+---
+
+The library provides a Golang file system abstraction tailored for AWS S3, enabling seamless streaming of binary objects along with their corresponding metadata.
-The library implements a simple streaming abstraction to store binary objects and its associated metadata at AWS storage services. The library support AWS S3, AWS S3 PreSigned URLs.
+## Inspiration
+
+The streaming is convenient paradigm for handling large binary objects like images, videos, and more. Applications can effectively manage data consumption by leveraging `io.Reader` and `io.Writer` for seamless abstraction. This library employs the [AWS Golang SDK v2](https://github.com/aws/aws-sdk-go-v2) under the hood to facilitate access to AWS S3 through streams.
+
+On the other hand, a file system is a method used by computers to organize and store data on storage devices. It provides a structured way to access and manage binary objects. File systems handle tasks such as creating, reading, writing, and deleting binary objects, as well as managing permissions and metadata associated with each file or directory.
+
+The library implements [Golang File System](https://pkg.go.dev/io/fs) and enhances it by adding support for writable files and type-safe metadata. The file system api is following:
+
+```go
+type FileSystem interface {
+ Create(path string) (File, error)
+ Open(path string) (fs.File, error)
+ Stat(path string) (fs.FileInfo, error)
+ ReadDir(path string) ([]fs.DirEntry, error)
+ Glob(pattern string) ([]string, error)
+}
+```
+
+Notably, the interface supports reading and writing [metadata associated with AWS objects](https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingMetadata.html) using `fs.FileInfo`.
+
+
+
+
## Getting started
The library requires Go **1.18** or later due to usage of [generics](https://go.dev/blog/intro-generics).
@@ -31,194 +103,264 @@ Use `go get` to retrieve the library and add it as dependency to your applicatio
go get -u github.com/fogfish/stream
```
-- [stream](#stream)
- - [Inspiration](#inspiration)
- - [Getting started](#getting-started)
- - [Data types definition](#data-types-definition)
- - [Stream I/O](#stream-io)
- - [Working with streams metadata](#working-with-streams-metadata)
- - [Error Handling](#error-handling)
- - [Streaming presigned url](#streaming-presigned-url)
- - [How To Contribute](#how-to-contribute)
- - [commit message](#commit-message)
- - [bugs](#bugs)
- - [License](#license)
+- [Inspiration](#inspiration)
+- [Getting started](#getting-started)
+ - [Quick Start](#quick-start)
+ - [Mounting S3](#mounting-s3)
+ - [Reading objects](#reading-objects)
+ - [Writing objects](#writing-objects)
+ - [Walking through objects](#walking-through-objects)
+ - [Supported File System Operations](#supported-file-system-operations)
+ - [Objects metadata](#objects-metadata)
+ - [Type-safe objects metadata](#type-safe-objects-metadata)
+ - [Presigned Urls](#presigned-urls)
+ - [Error handling](#error-handling)
+- [How To Contribute](#how-to-contribute)
+ - [commit message](#commit-message)
+ - [bugs](#bugs)
+- [License](#license)
-### Data types definition
-Data types definition is an essential part of development with `stream` library. Golang structs declares metadata of your binary objects. Public fields are serialized into S3 metadata attributes, the field tag `metadata` controls marshal/unmarshal process.
+### Quick Start
-The library demands from each structure implementation of `Stream` interface. This type acts as struct annotation -- Golang compiler raises an error at compile time if other data type is supplied for Stream I/O. Secondly, each structure defines unique "primary key" using `curie.IRI`.
+Check out the [examples](./examples/). They cover all fundamental use cases with runnable code snippets. Below is a simplest "Hello World"-like application for reading the object from AWS S3.
```go
-type Note struct {
- ID curie.IRI `metadata:"Id"`
- ContentType string `metadata:"Content-Type"`
- ContentLanguage string `metadata:"Content-Language"`
+import (
+ "io"
+ "os"
+
+ "github.com/fogfish/stream"
+)
+
+// mount s3 bucket as file system
+s3fs, err := stream.NewFS(/* name of S3 bucket */)
+if err != nil {
+ return err
+}
+
+// open stream `io.Reader` to an object on S3
+fd, err := s3fs.Open("/the/example/key.txt")
+if err != nil {
+ return err
+}
+
+// stream data using io.Reader interface
+buf, err := io.ReadAll(fd)
+if err != nil {
+ return err
}
-//
-// Identity implements stream interface
-func (n Note) HashKey() string { return n.ID }
-
-//
-// this data type is a normal Golang struct
-// just create an instance, fill required fields
-// The struct define the path to the object on the bucket as
-// composition of Has & Sort keys together with object's metadata
-var note := Note{
- ID: "haskell:8980789222",
- ContentType: "text/plain",
- ContentLanguage: "en",
+// close stream
+err = fd.Close()
+if err != nil {
+ return err
}
```
-This is it! Your application is ready to stream data to/form AWS S3 Buckets.
+[See and try examples](./examples/). Its cover all basic use-cases of the library.
-### Stream I/O
-Please [see and try examples](./examples/). Its cover all basic use-cases with runnable code snippets.
+### Mounting S3
-```bash
-go run examples/stream/stream.go my-bucket
-```
+The library serves as a user-side implementation of Golang's file system abstractions defined by [io/fs](https://pkg.go.dev/io/fs). It implements `fs.FS`, `fs.StatFS`, `fs.ReadDirFS` and `fs.GlobFS`. Additionally, it offers [extensions](./types.go) for file writing: `stream.CreateFS`, `stream.RemoveFS` and `stream.CopyFS`.
-The following code snippet shows a typical I/O patterns
+To create a file system instance, utilize `stream.NewFS` or `stream.New`. The file system is configurable using [options pattern](/options.go).
```go
-import (
- "github.com/fogfish/stream"
- "github.com/fogfish/stream/service/s3"
+s3fs, err := stream.NewFS(
+ /* name of S3 bucket */,
+ stream.WithIOTimeout(5 * time.Second),
+ stream.WithListingLimit(25),
)
+```
+
+
+### Reading objects
-//
-// Create client and bind it with the bucket
-// Use URI notation to specify the diver (s3://) and the bucket (/my-bucket)
-db, err := s3.New[Note](s3.WithBucket("my-bucket"))
+To open the file for reading use `Open` function giving the absolute path starting with `/`, the returned file descriptor is a composite of `io.Reader`, `io.Closer` and `stream.Stat`. Utilize Golang's convenient streaming methods to consume S3 object seamlessly.
-//
-// Write the stream with Put
-stream := io.NopCloser(strings.NewReader("..."))
-if err := db.Put(context.TODO(), note, stream); err != nil {
+```go
+r, err := s3fs.Open("/the/example/key")
+if err != nil {
+ return err
}
+defer r.Close()
-//
-// Lookup the stream using Get. This function takes input structure as key
-// and return a new copy upon the completion. The only requirement - ID has to
-// be defined.
-note, stream, err := db.Get(context.TODO(),
- Note{ID: "haskell:8980789222"},
-)
+// utilize Golang's convenient streaming methods
+io.ReadAll(r)
+```
+
+
+### Writing objects
+
+To open the file for writing use `Create` function giving the absolute path starting with `/`, the returned file descriptor is a composite of `io.Writer`, `io.Closer` and `stream.Stat`. Utilize Golang's convenient streaming methods to update S3 object seamlessly. Once all bytes are written, it's crucial to close the stream. Failure to do so would cause data loss. The object is considered successfully created on S3 only if all `Write` operations and subsequent `Close` actions are successful.
-switch {
-case nil:
- // success
-case recoverNotFound(err):
- // not found
-default:
- // other i/o error
+```go
+w, err := s3fs.Create("/the/example/key", nil)
+if err != nil {
+ return err
}
-//
-// Remove the stream using Remove
-err := db.Remove(
- Note{
- Author: "haskell",
- ID: "8980789222",
- },
-)
+// utilize Golang's convenient streaming methods
+io.WriteString(w, "Hello World!\n")
-if err != nil { /* ... */ }
+// close stream and handle error to prevent data loss.
+err = w.Close()
+if err != nil {
+ return err
+}
```
-### Working with streams metadata
-Please see the original AWS post about [Working with object metadata](https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingMetadata.html). The library support both system and user-defined metadata. System define metadata is few well-known attributes: `Cache-Control`, `Content-Encoding`, `Content-Language`, `Content-Type` and `Expires`.
+### Walking through objects
+
+The file system implements interfaces `fs.ReadDirFS` and `fs.GlobFS` for traversal through objects. The classical file system organize data hierarchically into directories as opposed to the flat storage structure of general purpose AWS S3 ([the directory bucket is not supported yet](https://docs.aws.amazon.com/AmazonS3/latest/userguide/directory-buckets-overview.html)). The flat structure implies a limitations into the implementation
+1. it assumes a directory if the path ends with `/` (e.g. `/the/example/key` points to the object, `/the/example/key/` points to the directory).
+2. it return path relative to pattern for all found object.
+
```go
-type Note struct {
- // User-defined metadata
- ID string `metadata:"Id"`
- Custom string `metadata:"Custom"`
- Attribute string `metadata:"Attribute"`
- // System metadata
- CacheControl *string `metadata:"Cache-Control"`
- ContentEncoding *string `metadata:"Content-Encoding"`
- ContentLanguage *string `metadata:"Content-Language"`
- ContentType *string `metadata:"Content-Type"`
- Expires *time.Time `metadata:"Expires"`
- LastModified *time.Time `metadata:"Last-Modified"`
+err := fs.WalkDir(s3fs, dir, func(path string, d fs.DirEntry, err error) error {
+ if err != nil {
+ return err
+ }
+
+ if d.IsDir() {
+ return nil
+ }
+
+ // do something with file
+ // path is absolute path to the file but entry is relative path
+ // path == dir + d.Name()
+
+ return nil
+})
+```
+
+
+### Supported File System Operations
+
+For added convenience, the file system is enhanced with `stream.RemoveFS` and `stream.CopyFS`, enabling the removal of S3 objects and the copying of objects across buckets, respectively.
+
+
+### Objects metadata
+
+`fs.FileInfo` is a primary container for S3 objects metadata. The file system provides access to metadata either from open streams (file descriptors) or for any key.
+
+```go
+fi, err := s3fs.Stat("/the/example/key")
+if err != nil {
+ return err
}
```
-### Error Handling
+### Type-safe objects metadata
+
+AWS S3 support [object metadata](https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingMetadata.html) as a set of name-value pairs and allows to define the metadata at the time you upload the object and read it late. This library support both system and user-controlled metadata attributes.
-The library enforces for "assert errors for behavior, not type" as the error handling strategy, see [the post](https://tech.fog.fish/posts/2022/2022-07-05-assert-golang-errors-for-behavior) for details.
+What sets this library apart is its encouragement for developers to utilize the Golang type system in defining object metadata. Rather than working with loosely typed name-value pairs, metadata is structured as Golang structs, promoting correctness and maintainability. This approach is facilitated through generic programming style within the library.
-Use following behaviors to recover from errors
+A Golang struct type serves as the metadata container, where each public field is transformed into name-value pairs before being written to S3. Example below defines the container build with two user controlled attributes `Author` and `Chapter` and two system attributes `ContentType` and `ContentLanguage`.
```go
-type ErrorCode interface{ ErrorCode() string }
+type Note struct {
+ Author string
+ Chapter string
+ ContentType string
+ ContentLanguage string
+}
+```
+
+The file system interface has been expanded to handle user-defined metadata in a type-safe manner. Firstly, `stream.New()` create type annotated client. Secondly, the `Create()` function accepts a pointer to the metadata container, which is then written alongside the data. Lastly, the `fs.FileInfo` container retains an instance of associated metadata, which is accessible through either a `Sys()` call or the `StatSys()` helper.
+
+```go
+// create client and define metadata type
+s3fs, err := stream.New[Note](/* name of S3 bucket */)
-type NotFound interface { NotFound() string }
+// create stream and annotate it with metadata
+fd, err := s3fs.Create("/the/example/key",
+ &Note{/* defined metadata values */},
+)
+
+// fs.FileInfo carries previously written metadata, use Sys() function to access.
+fi, err := s3fs.Stat("/the/example/key")
+
+note := s3fs.StatSys(fi)
```
-### Streaming presigned url
+AWS S3 defined collection of well-known system attributes. This library supports only subset of those: `Cache-Control`, `Content-Encoding`, `Content-Language`, `Content-Type`, `Expires`, `ETag`, `Last-Modified` and `Storage-Class`. Open Pull Request or raise an issue if subset needs to be enhanced.
-Usage of `io.Reader` interface is sufficient for majority cloud applications. However, sometime is required to delegate read/write responsibility to mobile client. For example, uploading images or video files from mobile client to S3 bucket directly is scalable and way more efficient than doing this thought backend system. The library supports a special case for streaming binary objects using pre-signed urls, where `Put` & `Get` methods returns pre-signed URL instead of actual stream:
+The library define type `stream.SystemMetadata` that incorporates all supported attributes. You might annotate your own types.
```go
-type Streamer[T stream.Thing] interface {
- Put(T) (string, error)
- Get(T) (string, error)
- Has(T) (T, error)
- Remove(T) error
- Match(T) []T
+type Note struct {
+ stream.SystemMetadata
+ Author string
+ Chapter string
}
```
-Write object using the library:
+
+### Presigned Urls
+
+Usage of `io.Reader` and `io.Writer` interfaces is sufficient for majority cloud applications. However, there are instances where delegating read/write responsibilities to a mobile client becomes necessary. For example, directly uploading images or video files from a mobile client to an S3 bucket is both scalable and considerably more efficient than routing through a backend system. The library accommodates this scenario with a special case for streaming binary objects using pre-signed URLs. The file system return pre-signed URL for the stream within the metadata. It only requires definition of attribute `PreSignedUrl` of `string` type.
```go
-import (
- "github.com/fogfish/stream"
- "github.com/fogfish/stream/service/s3url"
-)
+type PreSignedUrl struct {
+ PreSignedUrl string
+}
+```
-db, err := s3url.New[Note](s3url.WithBucket("my-bucket"))
+Use `fs.FileInfo` container and metadata api depicted above to obtain pre-signed URLs.
+
+```go
+// Mount the S3 bucket with metadata containing the `PreSignedUrl` attribute
+s3fs, err := stream.New[stream.PreSignedUrl](/* name of S3 bucket */)
+
+// Open file for read or write
+fd, err := s3fs.Create("/the/example/key", nil)
+if err != nil {
+ return err
+}
+defer fd.Close()
+
+// read files metadata
+fi, err := fd.Stat()
+if err != nil {
+return err
+}
-url, err := db.Put(context.TODO(),
- Note{
- ID: "haskell:8980789222",
+if meta := s3fs.StatSys(fi); meta != nil {
+ // Use meta.PreSignedUrl
+}
+```
+
+Note: Utilizing a pre-signed URL necessitates passing all headers that were provided to the Create function.
+
+```go
+fd, err := s3fs.Create("/the/example/key",
+ &Note{
+ Author: "fogfish",
ContentType: "text/plain",
ContentLanguage: "en",
- },
- stream.AccessExpiredIn[Note](5 * time.Minute),
+ }
)
```
-Note: the usage of pre-signed url requires passing of all headers that has been passed to `Put` function
-
```bash
curl -XPUT https://pre-signed-url-goes-here \
-H 'Content-Type: text/plain' \
-H 'Content-Language: en' \
- -H 'X-Amz-Meta-Id: haskell:8980789222' \
+ -H 'X-Amz-Meta-Author: fogfish' \
-d 'some content'
```
-Read object using the library:
-```go
-url, err := db.Get(context.TODO(),
- Note{ID: "haskell:8980789222"},
- stream.AccessExpiredIn[Note](5 * time.Minute),
-)
-```
+### Error handling
+
+The library consistently returns `fs.PathError`, except in cases where the object is not found, in which `fs.ErrNotExist` is returned. Additionally, it refrains from wrapping stream I/O errors.
-```bash
-curl -XGET https://pre-signed-url-goes-here
-```
## How To Contribute
@@ -238,8 +380,7 @@ The build and testing process requires [Go](https://golang.org) version 1.13 or
```bash
git clone https://github.com/fogfish/stream
cd stream
-make # build and test
-make deps # update deps
+go test
```
### commit message
diff --git a/codec.go b/codec.go
new file mode 100644
index 0000000..73dbe04
--- /dev/null
+++ b/codec.go
@@ -0,0 +1,146 @@
+package stream
+
+import (
+ "strings"
+ "time"
+
+ "github.com/aws/aws-sdk-go-v2/aws"
+ "github.com/aws/aws-sdk-go-v2/service/s3"
+ "github.com/aws/aws-sdk-go-v2/service/s3/types"
+ "github.com/fogfish/golem/hseq"
+ "github.com/fogfish/golem/optics"
+)
+
+type codec[T any] struct {
+ h optics.Isomorphism[T, s3.HeadObjectOutput]
+ w optics.Isomorphism[T, s3.PutObjectInput]
+ r optics.Isomorphism[T, s3.GetObjectOutput]
+ s optics.Lens[T, string]
+}
+
+func newCodec[T any]() *codec[T] {
+ c := &codec[T]{
+ h: isomorphism[T, s3.HeadObjectOutput](),
+ w: isomorphism[T, s3.PutObjectInput](),
+ r: isomorphism[T, s3.GetObjectOutput](),
+ }
+
+ ts := hseq.New[T]()
+ if t, has := hseq.ForNameMaybe(ts, "PreSignedUrl"); has {
+ c.s = optics.NewLens[T, string](t)
+ }
+
+ return c
+}
+
+func (c *codec[T]) DecodeHeadOutput(s *s3.HeadObjectOutput, t *T) { c.h.Inverse(s, t) }
+func (c *codec[T]) EncodePutInput(t *T, s *s3.PutObjectInput) { c.w.Forward(t, s) }
+func (c *codec[T]) DecodeGetOutput(s *s3.GetObjectOutput, t *T) { c.r.Inverse(s, t) }
+
+// codec for category S to T
+func isomorphism[T, S any]() optics.Isomorphism[T, S] {
+ ts := hseq.New[T]()
+ sq := hseq.New[S]()
+
+ iso := []optics.Isomorphism[T, S]{}
+ for _, t := range ts {
+ switch t.FieldKey() {
+ case "CacheControl":
+ iso = append(iso, codecString(ts, sq, "CacheControl"))
+ case "ContentEncoding":
+ iso = append(iso, codecString(ts, sq, "ContentEncoding"))
+ case "ContentLanguage":
+ iso = append(iso, codecString(ts, sq, "ContentLanguage"))
+ case "ContentType":
+ iso = append(iso, codecString(ts, sq, "ContentType"))
+ case "Expires":
+ iso = append(iso, codecTime(ts, sq, "Expires"))
+ case "ETag":
+ iso = append(iso, codecString(ts, sq, "ETag"))
+ case "LastModified":
+ iso = append(iso, codecTime(ts, sq, "LastModified"))
+ case "StorageClass":
+ iso = append(iso, codecStorageClass(ts, sq, "StorageClass"))
+ case "PreSignedUrl":
+ default:
+ iso = append(iso, codecMetadata(t, sq))
+ }
+ }
+
+ return optics.Morphism(iso...)
+}
+
+func codecString[T, S any](ts hseq.Seq[T], sq hseq.Seq[S], attr string) optics.Isomorphism[T, S] {
+ t, has := hseq.ForNameMaybe(ts, attr)
+ if !has {
+ return nil
+ }
+
+ s, has := hseq.ForNameMaybe(sq, attr)
+ if !has {
+ return nil
+ }
+
+ dec := optics.BiMap(
+ optics.NewLens[S, *string](s),
+ aws.ToString,
+ aws.String,
+ )
+ enc := optics.NewLens[T, string](t)
+ return optics.Iso(enc, dec)
+}
+
+func codecStorageClass[T, S any](ts hseq.Seq[T], sq hseq.Seq[S], attr string) optics.Isomorphism[T, S] {
+ t, has := hseq.ForNameMaybe(ts, attr)
+ if !has {
+ return nil
+ }
+
+ s, has := hseq.ForNameMaybe(sq, attr)
+ if !has {
+ return nil
+ }
+
+ dec := optics.BiMap(
+ optics.NewLens[S, types.StorageClass](s),
+ func(x types.StorageClass) string { return string(x) },
+ func(x string) types.StorageClass { return types.StorageClass(x) },
+ )
+ enc := optics.NewLens[T, string](t)
+ return optics.Iso(enc, dec)
+}
+
+func codecTime[T, S any](ts hseq.Seq[T], sq hseq.Seq[S], attr string) optics.Isomorphism[T, S] {
+ t, has := hseq.ForNameMaybe(ts, attr)
+ if !has {
+ return nil
+ }
+
+ s, has := hseq.ForNameMaybe(sq, attr)
+ if !has {
+ return nil
+ }
+
+ dec := optics.NewLens[S, *time.Time](s)
+ enc := optics.NewLens[T, *time.Time](t)
+ return optics.Iso(enc, dec)
+}
+
+func codecMetadata[T, S any](t hseq.Type[T], sq hseq.Seq[S]) optics.Isomorphism[T, S] {
+ attr := strings.Split(t.StructField.Tag.Get("hseq"), ",")[0]
+ if attr == "" {
+ attr = t.Name
+ }
+
+ s, has := hseq.ForNameMaybe(sq, "Metadata")
+ if !has {
+ return nil
+ }
+
+ dec := optics.Join(
+ optics.NewLens[S, map[string]string](s),
+ optics.NewLensM[map[string]string](strings.ToLower(attr)),
+ )
+ enc := optics.NewLens[T, string](t)
+ return optics.Iso(enc, dec)
+}
diff --git a/dir.go b/dir.go
new file mode 100644
index 0000000..312c180
--- /dev/null
+++ b/dir.go
@@ -0,0 +1,101 @@
+package stream
+
+import (
+ "context"
+ "errors"
+
+ "io/fs"
+
+ "github.com/aws/aws-sdk-go-v2/aws"
+ "github.com/aws/aws-sdk-go-v2/service/s3"
+ "github.com/aws/aws-sdk-go-v2/service/s3/types"
+)
+
+//------------------------------------------------------------------------------
+
+// directory descriptor
+type dd[T any] struct {
+ info[T]
+ fs *FileSystem[T]
+}
+
+var (
+ _ fs.ReadDirFile = (*dd[any])(nil)
+)
+
+// open directory descriptor
+func openDir[T any](fsys *FileSystem[T], path string) (*dd[T], error) {
+ return &dd[T]{
+ info: info[T]{
+ path: path,
+ mode: fs.ModeDir,
+ },
+ fs: fsys,
+ }, nil
+}
+
+func (dd *dd[T]) Stat() (fs.FileInfo, error) { return dd.info, nil }
+
+func (dd *dd[T]) Read([]byte) (int, error) {
+ return 0, &fs.PathError{
+ Op: "read",
+ Path: dd.path,
+ Err: errors.New("is a directory"),
+ }
+}
+
+func (dd *dd[T]) Close() error { return nil }
+
+func (dd *dd[T]) ReadDir(n int) ([]fs.DirEntry, error) {
+ return dd.readAll()
+}
+
+func (dd *dd[T]) readAll() ([]fs.DirEntry, error) {
+ seq := make([]fs.DirEntry, 0)
+ req := &s3.ListObjectsV2Input{
+ Bucket: aws.String(dd.fs.bucket),
+ MaxKeys: aws.Int32(dd.fs.lslimit),
+ Prefix: dd.s3Key(),
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), dd.fs.timeout)
+ defer cancel()
+
+ for {
+ val, err := dd.fs.api.ListObjectsV2(ctx, req)
+ if err != nil {
+ return nil, &fs.PathError{
+ Op: "readdir",
+ Path: dd.path,
+ Err: err,
+ }
+ }
+
+ for _, el := range val.Contents {
+ seq = append(seq, dd.objectToDirEntry(el))
+ }
+
+ cnt := int(aws.ToInt32(val.KeyCount))
+ if cnt == 0 || val.NextContinuationToken == nil {
+ return seq, nil
+ }
+
+ req.StartAfter = val.Contents[cnt-1].Key
+ }
+}
+
+func (dd *dd[T]) objectToDirEntry(t types.Object) fs.DirEntry {
+ // Note: file system requires a strict hierarchical division on files and dirs.
+ // It is assumed by fs.FS implementations (e.g. WalkDir) and also requires
+ // Name to be basename. It is not convenient for S3 where file system is flat.
+ path := aws.ToString(t.Key)
+ path = path[len(dd.path)-1:]
+
+ // ETag
+ // ObjectStorageClass
+ return info[T]{
+ path: path,
+ size: aws.ToInt64(t.Size),
+ time: aws.ToTime(t.LastModified),
+ }
+}
diff --git a/doc/stream.png b/doc/stream.png
new file mode 100644
index 0000000000000000000000000000000000000000..3d3fa84fd6608c891561f49f304f86b7d0384b2f
GIT binary patch
literal 968980
zcmd43by$?$(*V3Q(nxnIDAJt^0;1A_fYKo40t?a&(v3m4D5!Mjl2Q^&ch}NOF0sV7
z=<|Eu_j(`Rf4)Dy>x1Ru-h0kDbI#11nK?6a-{Bf+N`!c{cmM!^@Yz#^7XSbz>Jk%x
zi;eo{$G{bM^UX?5RSp0sjm5t(#X@~&Hh=m;6#($O4*>WF0RZQyTmI_+fD0c0uxSDS
zNF)IOR1Rr%no=l$hK1fUOI20CL)0}c03Gcv00VV}hWZ7B+Wf(Vx&oj^Lj7Q5q5U^B
z3;jP(Oh6XKe_!7;Yytd2je;&@qpjzxr>Y`h4z}YleFHYL;BmKexM={8a+g3|+F3Z8
zGP~Q^+B-?OOSArIA%VKSxy{SU{HKXCNSakoRfAa`{LX?|golrZk5vYbnVDJY-5X1Z
z7Ya}Q>5lp)&1&uJ>>$C*>*nUh<0i-herLtYFD@?5%O}7qAi#}k!R_Q>?`-PMZSTbP
zcaZ;#qhR4={?5k1*#>OSd^4`88Q8^Hnw9ki(SQB?ou`Gn&3|LEclu{ps0H%gKzR9i
z_;_#Y=4@m6e{{Qn{O$H-AKY0EH`tO7Bc+*VEZ4p{zvPZnMkjRwj;1%9v?^bRH`qUZ3RjSz!K1Ow`{estg5h~cn-t^Y*HRWL)Sy7&
zM7^mbTp3OqlNL>I%UE+&<}Vj^fg}z_Hzn2&Ee*aYZZX|2V*I-Fi4#P(W!HB1>L$zw
zt%X3IkH#`RcH?#i`)NMjk-WStU3|ud;CM}`PM@#Vh9C39sjLd*-nccHsUlsJ=bdg&
zusdHLf_5V6P|oYiHANQLe*YyjEp44p7*sZ1pCO`-ptUBU*{hd%K)D=(YRX9N*&aQQ}{wXR7xJXpT+*&K-B!Uq@Giw_$E7
zr@ISc7ajKV?$A!LSY8lc|8b|?d=iY~lllBSNzk-dPtt0v?)4i1;k7gC*`&_EmeMWJ
zxwB$`l&KX7EuXAcG&V`>78*h2XSi*UpI|Mh83
zS7657&m_V`uP)F%RePUZ7ja7??RzA>-QD{$h`hICqdcS}X#e^Im>3&pdR`yX--d&n
zgQT~wFCM3vys7(b#=qv^kBjN$>A8ZI1(U^8_}3>_EWpIbD1!i?b#Xu6gOwTme}DLX
z&(x!{{p&WWAzwT$rYab&{Wf-r^)`51u)h?bOjh&bEmKkvMi&66R7(Ct+_EBBN_0H*
zB&>*!|DLXb$xqTaoM!F%oVT&J4Eu(KL>ssHk@jcyTPCCxg_=+91mR1kTgGLk3&`5o
z=uRjS`WNw0>}bIMy_KNMuK{eL)%j#A!+Gx!Fl
zzh>EOIPcxCWN~OF&LR@G
z!L&kT`G7#6w8iV3kX!Kj~R9d6t-
zi5N;42spm@+$O#6Z-n8!=u6eRw`{0I6p&g?0ePD+VBNvQKR6uLDlvR}o2Cns#l8{2
z=7HQ>*rxLGX1|R}=DUXfTXmp1(f|ZZthH`q%R?09we6Cp?%lE_Sovg848`EAV7UdT
zm5AE@S|@WqEpA(kEJ}>f$fs^IK|hpll!_O4RNxl!Q8P1RGz0MLZjprMk$WiE2&DRL
z@+}k~Ai+(sSH%>Qm8ZLHK8klxS}tNvPgwaDA_&|;?X+j|)3{Hq=nahA;1EAQk^@
zfU#~iSi7AL+buB38yJ!-3-NEuqJn=TI+4=_PyX#vsJPw0PB>(DsQxvZBubPPulIT_
zq#b?hR8?-KdbXRt^KYsShoD4fcugli`xaR84ebBwCtFgI#Vl?2IzQy!lMQPYx>4^o
zp39@Q|B|eL4MmjHZMZazzWz7N<~a)X|C@fy6T*Siyd?K#%EB>whm|Xu7noAi)@W&rvG+dhgjc)%W?@
zcUSZ7c6>$eej`AQ%O2QbBCuE}pG|@wdq$r5sK5IedzkN%@~17z!Ra#%GCyj3~>YjfH&
zpoQ%=H~Ju3)6>(%rKNT8kw~p?v$K%V(z(;^qTPNh2E+88I*Ix*#L>~Cot+)R4m18h
zyEYe;*KFuCeJ$%xHy;8O29lh(?&QUe(}rCJ2qcLfO4+x`n5Z--nJ`dwa`#pYU;S2w)Tkjix=;Yqt<8yEh|`!tvo!k
z^1gpBg4e<_l$+h-Xp0*qc>bey9n4X7qlVvCLjNC4!kLD_+jweL!5RM>yb0#rdApUr
zyRq{V1On?%O^!mUAcZ?~F^Cx2Fu&o+VB#^7v(uv_A&E7gMFw1Kf}nDWk#!f*Y=^Fh
zT?bBE=C7*kv@V?`u|{6o^}?}&eq!&Pn7cfBecWY)q%a$%JBi4phie8N_%k33+8#91
zl6-tdOPa16R;}cU2sEP3K@%y5NhIS7Q4gyy#EJA<(?jfRDqsrjvg+e}){e~p;c5ir}
zE{1xU24#5#{+H_+u*Htj|1R%`|8b>;BLNDYr^n;QyqoLm-)-&e(#I#~%6>u$FXs@5
zn3IaCiV^0%ipokF`f4V`RQY%sYsylZ^ve3N$l2MMt0a0UBhj}@Bd633DXAEBvV)9J
zu}12??jN15=4xv8=0c)*;}I76
z>+P=RyBlXKBvZ1tYHTlDppoP4%hl}&Q;}p&FvTM9fis2
zQ(KF9?@aAmt2Kz_Rp#f{R5#@pK*}MxP1Q|R)p=HE1oxGZ=jQo!b#-gQ?n`ueJ(&tDckFV&mFybJ>@SEWC
zd7eiPBwoFI;amFs3%d02;I3jMWw!2BKh;t7xyK$Mb|Q~q#L{aIyXfd|rElg7a=COV
znblhHW+Dk|sP?L?p*bJ?pCqYK->E~c8xDZ$>g!d&>P#WD8nEH5_l%gDT3Ut{5z3ZH
z5fKMh5eL7S`Z-fe3(G1lWPKg?f25?OrR#~
zFv95c`4Jy47z~D!fvZ;?tF+rvbUL!vdQ39zy|Iyz<=8xxj_
z68e93j8)a9r!y$0(x&bzo#U8Lb6SHyN>RZ?)L5P>f>I*q4t_Sw>+slDZL(;)ApTvD
zre=7ubiRm@p7urC=Fi_$`Bwci7_k9@@$vD6`c<}$=A<3pBZ>ZTbeq$kqPQrzU-YB@
zuQT!LPQzX+zIgW=T^B8_>4=UHE_joH@wJx5rm$|20snZBCIMoybk#o1C*uK6Z?$rrTIxWjM9bf<{cw)g&-8qdwBWj$-V|;v0Yqo+QL~r@a
z_?Or71yir4E&Ebj-n6{OQl%5I4V(An)`|YdJej<^IXTetuKqoV*Qnw;G|1%sD6V<;
z%|%sSzHM2TmKGRpbSZ$?Hs6-%h;qvjZNem7i=>w7uiFvKGI;rNG+?r_a=xgjXolZP
ztuOrd@7nQ&pG;cnS9b5r!J5KD8;}odN(T`sDLj2|cdQL3$7as=CIu5SH+MIS-j(7^
z@o(qMnml|0HD~TWBUO2}Paq{ofgKWX=mZ<{t+emIVCqi>!f??aSH0bA)pst}F8JdR
zH*>R*$W|=jHBcMYTj*-E{NvL(kl}87BPJW#c64EejoxQhkyO0CKY#3RVjLgq+6H+%
ziM863j_Ryts(I%$D_L&227LK4p-H|y-qC9QJ%^B4Qta*xJRsUkU?%)|AB;-3Z~%}PmHuR=+VnLZrR$n(WNzfal(91Lv=
zc=Is6%<06Gp;IbA$o1E7xHGHGM}C7I-z3Tok#UDlB{t$4&aCtgqc~W@-Lv__HJ;t!
zAyYO>Qn-tD_Ay_l@OkUPM;VllL2EYA_rRTO_c*p3*OPF2x*Ci<7rObnSh&dUZ3Ck5
zJeNzoqOyXr!p%e{6rnaeHTKqoldserm}?V7i%BCi`cl~PcW_ZvHIEFXQ9a(t=N&Qj++GRS%4~F4
ze5|MsqFei_PM|Q6S1%F_oHQH{4)
zn|gKXsX5e2*6%Zw5DVQn5L5RIU`a%1>d*{-iUl{O5(0N?U3J;_t5x{bG6XVhT=lH8&uo;h7C#YFgc^D
z?R|=Bva_8=+YhO(+xN*jui=L!Tc|CN6-wetUC@#y9_mMGINltKRP|xrsg}GhDzQMj~RD#
zpVO4py{5&Dt46=HEukwyV?pEJwB&k31D3Th!pzLl(cVdrdhUmhq>O}eRunXS2PYf8
zG~ipbdmJx1XzGK(U}&tQ9#iIUNQjRIhq{Qac~bYswS4Z>A|Vl9+;Xy+&Ifp<9gv+e
zc{R(vj9G16(Uau5x)eX}qrtplwOZan+P`sC2c^4lXbUk7c0Vg_RLRUFCg)cp@BQ{K
zRaN<1Jce}_gJhkterqVPRwTc0&=PfCv?_ksv6U+B^tK)bO}!QsemOe-BSjdt_dA}e
z7(!PxR&bbK+?ax)yMH&VA=z-pFtS)kv|!5?KIL6_HkCV;P}NX{(Ag50n;bRGfof5=
zQ83w=<%yj4KG>cNR8u9CqsN1}x2C*ixaN%84?W{N-PkdSmGyU;r@GRKW4o6eG{tD{
zxa4(2V9sO(QL6r-F{Y1!xha>ej)6var-0XoXu=5esT3Ri^zI(X!8aS_X2&9}AHJaN
z1)Fa2SN!R)FPHwmK0Y>er&3^Dw~1|@W@8G;GW+GqDcl|g)6mqs%gdd-Z|PyNT69;X
zP0t|C@5}e^M|YzX3vm}FLIpaogATwQOF+7$;H--JdOKJ1%lLcIs?l8f(^KOwb#!_&
zg`LJ)rfSRm;6L(YeDAuj-r@d78SfRLVi605Ek%(xK40?@Tl29=SLZSRqo_za3U`~n`r4dK)$3$8T1Zqh
zv8wu{*T%M?q5@Ls%V4m!YR8DI`a*Y~x^<&KPhw
zunqDl6gb60$-9cgS#Z6d8XE7JtF{4x`R|oc{LY|WYb0y8@42dMP4Q4NwuK?rLkqhY
zcS`E7+$VfW`(JQQ&88fi*ViDX(LNAd!c{QCG)!WZScSs$ssSs91VnmT7h0EsxpD&u
z%XGLiB%vl@X&Cp%#_b3(O{L_8$X8nQgN#^5M@MtXLGHu`kGP86X*#~n^4)DnrwTXl
zNBiM1*Whsp?6->0uJ|YZ)Vy;O$eLc{`A&30Nj|JQCAvrLu#9}M`H`cHav1b-s@BPh
z=`nAU4G2`edr?_gnOo&b>2;pE22DNDja=isGV)OED`iTQysomFZY?bA{x~<6ARaxQ
zGFD*#IiCyR(#>eLMHi$k7D;jK<(LmXTOQtS>1>LFeN-R!78RMBD1|rr69+RRq|wrz
z{<4F6;65I`W5V&)BMnL3+-yEX4OA}$A?rMNVqRt2Vl~B&uw@W1o=|y`&UBdtBRH`p
z^4}PM-iSna6$j2%v9KvU(ZA4c8lxn=TlfF~bE?7ES`@22ctqCP`WS;0Xt2((6op$<
z!7TMCZIRhR?0zQG+Txkeqdh$m*pO^37g}28p&zuhKkudpmwR(b;klZ-hAp3+
zebZ(1Soy%1wxv62q~-S?mj)GUMl*Rht6KRdBs^zEIxwRjx`qf&_o`Xk6R~ZX`yc+h2%x9QD5!v3{e7T^b7#qtK`@V|Y^{<&HZbER!6y92mtSrf8C_@y&X5Dbl#e
zqo`?OxgiRYmHGRi$Zx+mxiW}!oHiL$ql1VhyP5*FRXxh_;b91&cxOu7)Y(T%?qEoT
z5x`GuO`T=coV91=3q$i?K4OP*SAGc%35Gqw1!M)gxi{3;B34m(rAn6q0|O9K(l
zN0>=_+Z{ZQWaR@xST5vA&My*M2OJR><|)$Dm|sNFTR*)YMU1nZG7fe$qaW&H_`78QgZwuNI1VQ#aBGNX
ztUUH*dP5c>eGEQ40T2%GJGEzKD=}&6xO?3n8PGul6JI2%L#~88ooEOO3qI?*7aR+r
z$p~mJ&EjvLWwMN2Tbb|{LNAe`1-Sbdg|4pXVPHiIG~Bn6mStFAY4#85*J2L1@WxJa
z;oud;!47ziIu1S3diiQn+D&c}s2#8CTkNT`+9Jyg|D6*n^ZN%!<$rXq-A$rk8Zq|W
z>_+!GAW39-{cwlV`hbp0UBetPnHM|wRQ*&eOSJ*1LODmNQ{ql4I|Y;1H4fcNygaX+
z$9-#5+-A3@X2i;*
zKpMe)?m`;KeWfVE0IA334Sq23(kT>-zn{Ilh
z4`*bz7zTccXIjglUgfYMM9(PcJf86C3m(n-s9IJu)3LPK)nGqORkyodf9Td6+gAKl
zm#})vb7>M#53eNTcBn1+;)V&w(CP}#48S@o+-h`-+UQjx^5u!yIib4tW~}bOz_$41
z*#A|Q7tL#4^Zn}jOO~hRv2Lo@QW>DbER&2jt8iUQnzYSW5M#9wV+*wz{)(PD6>Z~N
z*vJ?(S?~nBD8A>TW_mhd9$U#$Y;4%{^YuT8fady}u(2BmyB=OuPVhEgzSNJAU!NHAYJo4|=8Io5g_QvkyCpq);7|YuiC=7w^Qfglu=mped2QEllmCjb-K?m2cG)b
zUyEd~j7n(CBvdQL-$aQ6+(P}yF8zSlh1EpY+aKCOfnG~1Kb{)dGtOji_0H5f^|dPE
z^YXkZTL%^zc#OL>o%Q*>^?%BAWZKm7aCs^K?0b^B3Y!;iT3p!}zCJkk7NTxY0hZrk|B|P!(ojbQ3rBj!kdyN4IVN|ig
z0PF~zMpZ^VXm(t3KLHn%alEtb-73G@XI~;%@KD&smEqd2IVACi13)-zFA1P|{AXt#
zX*;bWy#)eJu-?fk6W`hS(J`H-s}HdkEB6eRb7}kU5Am%2P1GheVxn)v#2m;vIvyS=
zW+mKkzxn8Xa|U9v+-f`>=yhO4#3X(&4eL+u$=>pQ`7%CT-DmNOqSAO_J@O=hTff%j
z>f+2!G?EhQ+6gMH>sgv@I{Ra}6X>{kw4u(DAp123(xV1@;z%8?fTO?~xHp#0#zvuy=#iovy*Vij^b&
z2@VmR#z5q1a}9uJH%Rb_i;LUD13ssB1_pTjc%-39_~w!WI&l%-?0jWl!NI|nYrVAP
z?%l0mZsW#_9w!)d_OON6w}gUVcOmr63$ggWDgA}c8;_O=
zZhSd#<%4s5x<1I}VtVwVEHBgioQnIk_q*dw;Y@vR-5ui1O`YnwC<*xmtR_Td@o3{E
zVwy?H<>HqWuTfn?4Foc**3u#CVT0IgoA&?G4KF-VWbwMN!*s1fctOqrkx4xaPyDak
z(_GQ1(qdfw2&WDACHAdHrMH2NY5RmC2Dg6drour>F
z6f-xiqFiP{GC46d^6;SVOFRA)U-TMP0#DlY4q>Wj=1hqx2BZ6bS@+AEAgQmb#GfSj
zE-t$9`A{OOp{4k<6;qH&-+
z^TRt;&oJ?-a%eyglQ5;@S+uuQ@}KA&sd{DeqK_G
z>+`?&kMfN(GW|}^I|u<8uj!5U7|NJxvR?t*n>vWXfO9_d@T*zQj_?`3>)LWqVDf{3
zdU$RI0kgYBv5)T=g}DTw@x|Q%v1XVQZC9tG3UN3waZtk#{6N#h;-)6zsJ(PqUmpA1
zfUgf8aPtf1gNh3DYS)0g1~uzvV`Cx8r;IUns-^eV{~?q!2P$sl?1w($dsFA7xG-sz
z$t~r$w64BAUwxt;%P9R?M_c<%&Izwk19hw4#<*^g(SGOF_`Dl+>t@KW)|=yDUJU&!
z@4boAObL&Jqb6~mHEkm*&-3gJRW|BdztbYIK}d4#IZE<`Hn5l*YyS
zWz-rW>1pG_VK7h{J+0ZyB^~!Sxq&I}{jR58#L4}^(&a`vAfb}*oQs>iW??CrK2D?F
z80*r=Jq+jRukjxzn8rTnY6t;I5_yF8Zh@`HKM3=e(*uj4(;w+-O8lG$Y6Rj03S^?7
zd>YvO)jJlZsC<)*gMC7@Ew+Dn8jMPK?(*=^5boK4?6gX4ougW}jrAj%CRI@JcYj3G
zl37)BP2c)a<$l_CVv`AFS~(t@6*B@f?T_UK15fD1e7h*8@naZ&lXwbUW~kCpFm$dX)J6A&408tcRa!)9
zP+NGlf4wJ*+Ih)WL5+Z_=EFVN0O5|M$lAfOD43+2e1~-;#mW`ayVAP@TF|lYErCbg
z08`r+ndQ|JYe)S#%HnWY%f{YiUthPGj5p8|Ug0ty@_o)0u6bJB1|KV?u7XT+*okZT
zBRsPNyLR4Rww6eoqITAr32%F+tlGI<1zmU*n#mjK&^dSj%q<8KP2ymzW$Ml3;MQf<
zMSH>^o`gT2fhJ|o#K!y;deKta*2yU5-~b0RNqR12=l7}JpJNhz
z7Dl+$YM{Neu9d8ZW`3I8%t(-ot+6w`l}FPQa@_WoLc9-Sd1paX;x@*D=AW?y0et{Uibw;idY3b
zcDCS~-euS5W$oDB&Pq3idc@_hChF1C?|roIL2ebbIN=cD?`qsHM?;fdnQ38|4r-ZAZH#}FxE=gSeNDjGFY5}?_-3t5
zqzrrL=onf>z-^^KH0L)%)jMv}4%~Xiiz&n8NQ+@Y{pF;$H+j_ZI3<4Gu->vSlR2X!
zHS_8yPkgq6`_prBAK^zUMdl5Ht*M7yL_)~mSLxl2P
z3~+?f|CBz$DbWxJXo@iCIKap2dT;MT+ci~S5dZt@29LwukN1>)Yj$^C$ggmirHb@v
zoqozF>@-~uOo#ei|LR^oadSf?6=kS#iM=~qm?Nh0Lx|kJYtw74&i{N7P1;;IlI-V9
zwuUo$SEBFLR9@&IA3-xuj`sK+%{A&ddLshZIc)mz{c9@~48ZgotiAPB%VC$~+EDzi
zUY{y%@-C8*1Qv6~63nYRQ_Szk&&d%gRRn!)8((DDXRF5J+YTSFzlsew&?7p5c;A^bpTjYtnWLuPZ1IoYZ500iMgH;uJ@W4i
zmTsdP&QO3?HW7qb08P7BY0K=%uI&J8S+C#uG`SpTLa1QBDMzby
zI>Q&nyc=Xi@-#Ig!=6roL>{wusGic#YBIvY@2D$52~nt5$|{oFqsq=}MKg(9+rOTf
z7&+?UlRYZdYg{_9?B}`utpq&(ksF>?EOm1pMIDwhP->?-qqOMHH&GS;-4pQk%-P^9
zc=rnBfA#lqK6t>7PsJmIGKSl8&d^-Hdi&X2a6){1r7=91M3NbPan^IP11!0o&y;d}
zt2|tJpKL2K8Gvc#Yu^~Y_9PpPKSP{G@Byx0?l4A{C{aLIhS76b^<&4jEAx+ncoR7@
z?X7mcoa{D$g_P8}sU5-#7wM@PhWix_j61RsUtrjj>S0FUJ2~23Sz^YrM8<*`ijHEK
zXaq$%HAn*i#AH5QP^#$R$8XUovCFdkcWvRk!F9X?J1HlDVcp|TCQsb0K1()dc7EtT
z+`cdFb*5*JtGRu-{0It!77n(>N|^42O3#l5norKk#GW3zoFs+-=J|fP46tt>%6~$i
z_P;i0f8|lX<5Yg<)m6!6^*frAa};v|eybFi3GEudTe(d7380(`Irx_MWDEQqs2SP#
zDlj(!&;nJ!VA1zD9XpVPOA`YUPrTX6qaxT?vGFxWxs@(c%_KiY75LhI12K-c3MJ!E
zyTF6UE~Hn0M>mC=5Q4x%r+Q7OTt4(z#0d5`M{FR9s(ve?OIN$8Qwz{nUjP{+r+R(7
z=1vQ%i5bo-Gcp)4PF}ZdAa{-YPTX`0Uths>3SaxpqFpv#tn$LD+)ls}HP_8el6zw<
z#8n2MfV8KNL*=AoW%-hf*>M6oO>oyk^U&BnEAfA$>1606?Rpe$bH0#8gnKIMJ;BYj
zP}6?TBu~O=`pTHM2@LS(Xo<5(`od@fD2X7;(;&>r=-#5zK-5)F02c6H8$W%w11=~0
z;P3<~9kTKys+TI(2^TOb12s3HScfWo1(%nW(TxLaERzv-Y1a{q{>^r5*3O954wqQI13AAYN|!8Pbxz
zhtad(3tte3D}=pw+qm3y)=JdA0S<>lPUd2^c=R~_mZJPGPRRq%IZv(p5W&ar2A@-2
z{^0p1jlaf6MyQX*+ToKG8}ohoCF|fdXeLy=nFErQ2OODjw>g^zLoW3>)YGq4l9k6*
zbiEQOuG*=gnHW^pLY}8I!BVtxLGN%6=5~$9lz*xCsx>~~{B6}#va!C&C;c>bZPmlY
zVQFZ`eOia_d}DHqJe~$UiG!FLu-#3PX>TVDuqC~cIWQAU`r6!ZWuf8|Y}QF;jWJ61
z4e#o#@%!r3yk+WRIYe)rhy~-}uzRC1w&C`Jub1ZJ-t@4(cA%SsofZye1y`FW!70eN
zORzX|ZLAksCbpBt-L1nP2vJoul1imdS=ePG5E#o^3X&NX|7FrFA0SWTFno~}Lf*Yz
zj`*!KiD*)hp=J#@=zQH#?`NJa`N3RfYw#e(3P6qYj>~3*)}79?Tzh@GN0u4vUfkW+
zQ`sSo=j-dvo4)6E64w!6Od9i#HB>D!TfXQfbFmnk
z-gYQ*v3Q_9`ucAo^**Z1XyHD_OUD~^=>WlW-SrlM$Lq$(K>X&fctu1+C|6#nh}GBZ
z>sLdNW5x7ghNaC`+m9B#HRL7~?Jcjy)la4YbM9b4}HxVqVYpw^e{3_X~
zjB;m)fVE_KE`$cNW}5N5&v1^o>?I!d$%cWEpo~s|T4?a}q>R+=cqk~Mz1Iitd2Xpv
zGLt9$UfT}&!AVPHnsoOa&ma>iU|#w1VcYkkC^hHK_+Z5o)+QL?JA>}s-3wA$CP$GN1m%P
zh3@z>WRewqqRv@tz*vCD2Qu!`z==ce3;^OnH~`U7yN&h8)YMe-wXspNu)&6N;jOU?
zx?j|1bDz{@CdC^<#><=B;B#GPf*efqr6AaRnv}K24b-u@;D!5(4O69yV1!*8bv7D-lG_>!I)I5#n4fLj*V-Q#rF
zCdf_vn1`5=!b2*=o7}N>0+v#Aq?r-wOw<-8+p8FtK}=;ohqD%Lf+yBb0jI-vH(X<3
zXd-kKLwg7U*{1&XMEQrH3%U1Rj6*sTPN_?CaMQJvR+#`;4jrU>1|ECtgRiOkYX3VGGD**7p=)_
zzzHgt?F4g;UkL?0iiRvUeCz5=Z3Wg~q8yqqpS49ljI$Cs`(8;0p$e?_9-Z=gU;3$t
z;PpR2TQrpYwnx*ZcyQx&k+JMGHSI^&rAt;_k85$LUq7n9V}%U1`OU|qOJ46a2UPEZZEefmV+}PUCbSw(needHrhVD)FED2|
z+9Y<-8<+Nn0x{6L>@=>lMvg*j;h4njG*ono8CqkecjS8YOhxvnIxovSjGB+%zSBWZ
zXr1XNOn)EOI_g2KVLK=}`&@4Ov}lvym68i~H>kMNa7>y|3EwDGbML`-bZdWZV$Jbn
zrhDd$t|P>&NXV{EkoCW>L8TSk`{})?LDY2&8=l%bb1>6THh2?mNu8YiYXU)=FWInCy?$XsmM)b
z^ocv~qUr=^j#KFQ{+)%;&Vs>iB1w}Jvc(dMJxX&qT9J=(37zm?%+sI8L2Bl>`ZOV$
z_sAodiK8im-cJtg9Fm9Ngqrt)mY4)e8AWQCs?yN_*qHF24P{LwjHK{Vx|+z0bg1$Z
zhS{-Z$Llv>hP_6PwvRZjg77|BvkvU;8jFPp45U<|tq1V5LHBcFd9_TXZ(9g&fgxl!
zGFIwf;ob^O;alGp^P(1>y{WN6F1PKOD^ztJVs?CP>=}l&cUwnvQ>p86N7HnJhZ8&r
zYd!04h1%Df7?#Z&3Ar1?RFnxYc5ga}FD{s@w;I2mL9We3E0TocMp=x3XC^8<4t;=4
z`_jaau*A)0^=CjN#0rFfPaum=;K04waw{l~c)uvMY%!l_MBz}W^j@0NER_U#D2b5h
z(Yc!G^0P1kh6PN@AeNprrVH56h443^qI2`F0yKcdglCP!WQ4~t!Q&u;3wah_@hTPE
zLK0802fruSR7-b@?ku*JZ^zWjv`cy7F4#Fdr3>6O|=88=H*
zPBD^fk$ke~>dM8YYj3*cFex^Y;j!$>20GW?2KNFTj)a6Hp;r*f&Sulj$eVjAmlXnfv2*x^T9t(4yRd5Gjd-!`0
zq0tQc`y@O?A8mCX4SODB^TQ8*
z1;M#SOzbZ^oti-)S)fH=ND$SGl%V
zg8D>of`ML-9$anwhKim#M=bT;5vo@Y2(oxVRbfSN`RVI(>Z%&@=lllU<77sRsg~6b
zY}3$~{p-%~+rKm^?VAC`y56P5Ib%&3d4A>i#1p$SSj{mMW+Hfq8Rl9ySI=lfi+0!9
zIPVm3P!c*8!JdXM;c$mokLvQG)w^aKmb65pn1sy_B~K(zJ}48=GJ0kIlk`zxMZ*ZW
z{0LN5-S?Se_=DsCKm^H?5lx+oFH&`dhcbfpkMW-18FkjBvBlCUsl(s5Py?hAwY^(Z
z?pN8*Gz*x~onauZ>Xs+3EED2)+%1zQV~Ex1J~Hh*)66=^AoL>=m|7YSSeh3F@I^z-
zAI5JvvT%9+5UaivGsBIe2^MV^2^Iqi-L0sqOabW@Cs)O|4hLLTjfn($z*MoDNfCaB{KJ+_87FmI|*4g=t3M~MASEH*Ld*eF~h+UQVF$7&-=c2ZYCaEi0?$gn>)p)LMdGTfSyY<)
z@F5phU#=?Merv;iFa=lQ<>nVfE)L;FCf@k~Sf*bir5483W+MjjdM8{fZ;);Zr1(P4
zH-JH@{|a1ZDhNpO$?KG<3>O5H5YA-JvQp@JKLqWlp-p^mE
zfhmz$E;R&!_ugG#dYSB07CN|~e?$+r8Ka>8lDPcS5GwFG%9$4H)Kq$CgVB4!?7&2F
zpNh%8{F1(izsCKClz(vkzNf#Phlm>jxP0eVZ#lhj;u2UZ>43IDwEz23boQX`CT4HPVgs{SZNr;QM{{BsqWg}#NIv$!)xl3}A4v#9!>^12J7tyhB?&ML+^C64ZR~6WclPeDZ?0E`yl>2q8IIoIHdH+;;Mc*BmM^#t
zvl@|2*I>Gq$x1=cl*yxM;AnSC&gYlU9>Zxe)$f;7*$yn2HK9-RrD1)vxoRDrY2;!)
zOv()1+9v=cdz9NDg&CyQUM2}Ysb>q*T8!ew-mlUKRbNC&Y6ABLfrdG%t{k=!H@A3U4g{xlE?ax(
z^&63(v4U%ingJ2jwILyPdQ`#oCF<{#wFALxYi(V^yGGg&1%pxO$!&BP3A!eD73S&f
zzxe_oCj{^WgRB5g)%!P^GvL?R+D~J&Nz^vqa8h~+*F(9Vvs7C$Y*W80ctBwIlOvva
zZcoth6!Ott(PEbE2zOAger535^*k-iQjqZk@cA3r8;xL#bG?uBReV+Z
zpqNwin*%gcy1oOkJ~^KnRzs3liPz-9p&+VOj8Jo(FS
z%X<%Hi7S~v%~U7k>IuZ2(ej)eK^NqKjV#TyxN(+Isg}fa7?HfcyTc7G?xiN>X)}QK
z4fj!H48|bV&dY|Ysf<985~BvSs`Inea~L1-w4yKhXg9mpAdTMLjfuRtiVixVNDmrJ
z5-#tV39sBiPLcrAfGx}nV~}-At*ku_am=Ms6hNUpw)1W-P8$~IMXLU56?vb)AK>qz
z+Gu+FRhAMioc(+4bxnSTY`dw0*`h-@3q|_AY&OEAp=UY6~%(
zINCzzv~A(>lcTsUC0l&j$#uf!3xks*=V@xnFbe^0FP5qG@W$x|SOC(`tu3V8&*)|47m$E1QpOV3
z9l0=leXdjtL7cHE#b6;xRHcE{&5i!Um1#>KA(PI=yIZlZ9PhT*oQfHBczK=Mjkt42
zFpDFAZ28T3Ra28+{=CT5*(Ep01;ioaEp7oz
z3LPjx_Tp$_fgSaq183rXoO2G)3^0=uP4S=IyN6qYQwpu^dvL^6T2%D23^^MA(qXl@
zL>k#1Tx(=EQI7us9Q;V(-tRnXRrapW(xczGey-LZmaFM>vEs50Pu@i^hVljZ7cL9A
zA2pj0i?y@GB1`ey)8x@OO;}6|V<_(R*^Fjn98W
zv=0@a*$a>ZgrE`Sn=;FHjF_m4JtSJj$a}N0O%}$|0-L2}($f_+%IK}>+``%p#4`^N
zHgfC>?md%Q7Izil))!`U?8F&YS#Di;KbgQcE!^?Z)bDM?>^`3z1LoAo%OdqHX5Z*
z^NyL~2h4!MWg~xGFGC-?+2wnD%{|ZR!O3Pg5iW%=&?B(
zT3lSPhE*&yltQo{6sby002VSlZLGEmrn%_kyFgxLRqtH|O2zD>z7l=ao~G__S){|;
z@u_1>i9^$nNuX>$Gu9*p@e!A@1ARZ3ugK
zoy|Ox@I}m~?;g3AU~kQv^qnDhLyV|R4ZgALUYq>QWq$d-%n|?ohtA(wks3MWU+ul|
z&-t4a8^N=`7|^h&gKL`W`}!$FzzF6|BEwgdJjT2kpqYoyGR;QpL
z5-(ZZ%_QM{f$SXncY(!PNo&;7H7rETOm44f1AEkYHmzM$-kwe|b}lg99*=Qwzv6oT
z(xxJK3l-n(@3-s^e|j{Z9kl=)M?OPsV{v-Zsjcnnmn
z|LcAjHA}r=$7g7K)70|%2GV>qVdQ*zS1#!fn?aGwP~yD(KzR~db>h9S$lHOo%fL*j
z__tKDh)Fb9**D6x&6y)YLVk2nEL=>Y2WrW_Fh?gp5L_Kgw#~BRY{{G*9{HVp9jy}#
zqiNuXqCtd>*P~Nyc3yA)W&hWVZJqYlIuM*a}{9)F?+b|KgAyz=XJX$pQ?E2`TM=X2A$|xg37>u6Cv+@gJ
zEDn;a3SO)co+B$2d|4o+Zu%s|p7Ms%l*6sb+Gt@(xd#x&4I5}PK>so-P;m|*+VUlT
z`(FRTu;iR!l4EFJxib^9A{(*}Oi0Pyij+J;cTW7+088B2&F`^CvE@JjCF$(z9k8K3
z)PX>>g(YlY<&`YP%ua**&4BCa7Q>N4{Bxhzwsq!=ud(5miV(1xapKE>9^7z_zEZN=
zm-+M*)Ok;R8hqpC3IJH<8I)N3@oR}85Av6!7ua%w_=SP{8@rcsdL+dcOP@!Y@;Pc5
z=m96Z_;sDM>-=q)JUPi@z!F9(BR$0iswSyM0qFQusn;KpC`Do-uMz6M3}#ScvxesEC76#a&j?BmdYG-8^s?yvoXJ~y@R@$o{fK+stjG$;s~7YGSBTbK2(
zCIy?~5TfPxySZ&stNf2F$cTd5uKsg~>huH_=&$|SnlV|14Z)B>un_~U*BKxaQt)jd
zj`ZbiibfCysGbGKJJBsyc-5x}9&fY+4tzKgM?8u{+bmczB6~7v=_T);ZJmB5SQ)F7
zh^Q!#@sNynB9u1Sz9ary4{16fMen2mU|*74bv~35ef)@~^gX1HG9)OAydLkWlYa#{
zAJ>Mhi!VivEstb`gbyu4&HvLoHhIg44KehMJ6OP84OpU%*4$3t*cu(WKQ;J-wpyc{YGW%tI
ze(*Qp#B+%*6S!j)rw|TSTfPPV5mQFiNVE$C3>wft2u(zVxI{FgiPP=ei
zn{V(WT#8#YOVRRFJSZ@tt-zzsHtD*OHmO
z4~2QDT15T2aFqztqB>;u$Wr=vXH=qxPW0quMvo^|X)t=WfVT(+_L00s67HlOW6`V5
znI);NG7#i#Kzh-e8|nq<3jsghyWFcps|GQ5|MfdMQi5La=;Lw^A(4dIz)nI>zMcZ=j$AoT5*LDLd=;qWJl{*)ra=oJ(ag_P5kc?618kEiQDmqAR4PYx^$z~|5EA6KRoS(0S)b+K#nlP
zaj793WGM3ZXQkB(3BumZ<|`U`X;@Qs)1wh|yT?$OcV}qYgIzX<#~tDb>d}o_iSm*5d2F*yYDIqXDCEFgWrTs~Qs{`+c
zb#uxBcFDB;9-MYEkSEb_gJM79Jx+_5wN9pl
z;4Q4AxISWicb*6E!V&TBi;fSDH@suAp8v}Ir4cf^>hJfYW^cvMgE9U9_ffmM`aOWL4R5y4PnTI^4QF@sCz<)+alm>8Uw~xhl%wy!
z2^1&V${&$6Fs8%~nEveb6e?~H*4h7o7GdP5$7flZj;7rTZQbiI`S9D}Ph*3_0>4=`
zC&bH;LDG*rVtNkg@o=;x-M6OG@8q+cq=c}$e56HOr)VJFSy&tYm*qM=*npkLnjsYx
zl@BEXnDs{gfYyGN;4(y^zVjZLxHlAm?RRi_ad|1)FYSr+KSkbU3G4+0e(S0{yP&;=
zHA32*298=0dtDefbNN#KiycrSL(S~Z^6sL^ECRgh!}pnYw+hWy278a-$#ps+#i
z^Jbs_#ON&G8I2L(OahlwgV9Dj*A8YCNQfcxL^32UeN2oGUpkCjdwLQ}nw{OeFUb)w
z^!pSEMCXj6Y{A!=CO4x$f6|1nr>FOk64C7GJ`#F&`4)R|ffCe(o-g
zrEv_pRi;Go)0WH7SggX>65P)D@=d%)edu2nrAmB};C0>Qi#3jK&Ya
zL5yShRn0rxPK6mTQZXj38R7d&gnChg7{`I3;p@`=rxKwAv$WWi;$Q@ND4~#9j9f2e
zXB@^A&~#mf=FyFJnw_v~j0q!drR%pXa6dfCidWe`5L>?2J6=AZ$r&u*pjL$(3r6b%
znJ4D~>}a0X^1uVTcv&%bEG6&EvL!qk5Vzf5(fb9voUymC&V;Y4zP}xLg!+y#wCzb|
zBbRpnW>+7rTpcg;yaFN5luZ1tm8AVPQFCCg_o4CGLO};fNn)v%(2L&gvX#hZrUCz=EB+$pMVGx)=Qq`(?f5N!_Q=0GoMr4o$b?5`Paoar6
zAVaBbDw!W_VQ&q2Z(og|L&qBZ)HZ2r6k>tv^ge;_Sk#5on#4@Hm(PEy`Rb-nZ<*|k
zAmHqKe)k%;dJeDSK7Rmsf1*ng-=gh{zqt)>;C^~*3E69Mln|tvy350t98pD?(jBe?
z{gsh=LDkj8Xv>iznl73^8Cd3oxx@8a>yEn4`TSe67aWn?7pq3Md~aiF2qwlPExL0*
zlZm^m>tPAyLt7ScC_xwv!WSmjv=&H0I<&JUK~MxHTuVp
zk5c-j<@RD)dKP$dsqKo0!1n!tk)cx^Vxc4{N7el{&Z-p@K+M))iMAie?&EAHV;)Bo
zqlauAVB!kMyPF}9Np=5~nE8mYMV1G~_w*yFp@cBFa@da8+uMgL$%KizUKEQ4!7(@S
zrn9;9vej%_FM<05ngFk~DaLhky3c2D78n*GnxNP?fU0)wRUOfcwF7W8cpbFRk&pOw
zY(IG!Su;OWWaxbI;qw$c5P}I_t31dRw4|QjafMU(U;_q79rTR?gzYM}w4)j(!$KjP
zg1BxpT^xjwT0OTX&ChtZ9Zo*D8!yu3CM$7Xm!2D>q8nKh@eEerCXKTWi$2g8pm%=cR
zrP%|`x`4L@zV{XLGf$WkA9s|oB2PeC!`I#y#7LqgVZi*$RfcQ<0kioAe>?B!<%mK0
zs72|Q<&EO$Xq~9`LDK(L7LHHmV3fxe+ZWzbCF@SV#35rRxFb?PpI3;0C
z&>-X-7Y-wAO0#S(;z96zWOmbP*K8uJ5*Vhf&*URsE(@lwg7h~{bBkU%&8;O?Nndr`
z-V8fmtV`bLV9#QN&b7{A7&JpCIV$b~T=SWiqrr1riOx)U{W@ePj7}wgC+%I*n#_Xa{gjOxb`5E`c(}y3=>Tg(MDtj1$!*Kf@haq5XO?}$ko_|i~
zKVPe+6;$cr(z?bpqk)1X2h`Wa*X~B)VK^{}MwDa3HJuNsNxNTKv-9OHvcYa@cMWn>
z)hfoWT^LL0y&DJ>cScSgiF?8SZ1gCpJ??OMse*%yrIzPcVtsj*gGd)w@k2UR={*oy
zGXAocXWz!?dzp>X9g!+I+8o2RG11{cgF;;Lgv;xU`To=P>s`$l#Dvsu=u2S(=^DZJLK}$$aJMNRHH3fU1TL1DFCf;o*8l;fZg>?zF
zV6t%Nn~?w3$1P>P&t`P3{1Qs@n~CWKUF|r|3lQ^Wg4l-1aZ>mDtDpX#APU^Z;b`L%gJ
z+sc&ybYNXz^kEycJi2@{<;qr4IMU)10ayxyQb)h00^E+Ukrt4QaVWlk)E)oWyP2aQ
z_MuPZ8MwL>^)!Z_QhPjU`)1V9f{9?=2!bEFD?qiTnV!q7j#8FeNHL8=SsW
z_EdGOU%b}vnUXJjb+_2fLDDGqjuy9?3GipYBj1zlUnbKCIL6FNJQkjH9<8}XP2|Xy+wStXL4n9V8`0vU!x^@)g^#0BZ99eR(vJsY
z&uIRC3&i@b1!8qBr$HHK@=~L-YvheAbe!yrA*Q_ZkO4Y6EX<=IAzGn|wO@CVHv#5T
zC6R2~UibG$)s!dVXZ0pI{o>8*-P
zaLwLg0;r!roU(&(39R3$=Lh$sg<0i;=HUb3-QB2`-oYB$KHpKm(x@k-SVub?Cx}l%
z1)P7d?h_&;j`KRj6d4^5Rkd+7blng!QlA?*(&?n!Vh!w%qmJ5^TzYBnd;VqQ@ag3D
z*oi6WrItr_rZ4lLL&F=0g`p
zF$u}Hw>d2~A?*%l&@a&i#q(3CeKYo`$As{s*>r3pG4Be5$5XY%?r&
zB0Fl${{5@0U=wu6+#B~38D`3cKeIS%nn92=Gdep#7snlEGJ)UZL2Z3P*h_EZY{@eA
z&*|HY;6az-LlPsK9Z}ke)5t5yzu}6TOkpTr0OS4W?K;%
zs}CK2^Y8bSq-O-X%v06=HCLx^{#8v$jcREqJAfu?%(Ev{@Y%Tz7#V6NvM7uiBLg|*
zdLJVjBz%H~aaM1pQMwEtAZVD{xqYD;*LyN~&inLnMX#a#VnIG~_17sJb!CB9jhrq=Ijl>NE^Z0&Ska4mnHZ5>$hfEo$NFBF75Tgs_nW)3|cvZJC%rV5ft(tv(`M^klo^``TqzpQLW
zmS}nZNS5SM#Rq;8i%*ntx1h-EQ-_p9N-xMwlBZ>G^#tX+m+_r8|0rO;pA-nAQ9>f~
zEw&EuTsC`M-A|XX+;0b2-4C?eo0rD{uFLk2N3^&j62=nj(!K3!w8&=@J&d?T=p8UB
zA(^7W%Xg+<{^Cw@4v`5_F_`;yx|rIm)IccVX%|v(*)6c)5(5W9O!1uB5WdDO;2?xn
z(rWVDVgAC0SQ03K(QQh~(s0|l>(76-2oKzFemY$KgGZ27*m3c^P>KHjfIZLW{x~XS
zK)K-jW*rd*K@2ryAomA1dW3HGSB~b883XhWX99ELbQ>ZW(`Nq(Pv=l)$63`Hy6!*Q
zh5j%v-&oJgZ=r
zF$RY~-??g+Q9E_0uHid}n3z~cM)A=VHBxm-^TyjFG-YR#uv%&Ww(&RDO&O=^XD0Kt
zsvUI$@`3=LW(Rs3z`Cn(QhzX`KhF6R$9O>AnRk0&oCtlWf8|k{jk5_=e=u(U*(H{E
zxCiW6$;N6y|wCu(rXV3;?i
z{1^BdeNf}cO*TY0q)~sLgAmeAF%Hb`KQ(QJYKIBNo6W
zY@Adte)FG@I%FyRpXB4DQmYTOE}gJKDnB;9hx!EG2;ZD8)PARPcU^1uqAt1ezt!6t
zF}a!}i^dH+-|TCv{0KpcgC5r-^HB{={=tEKbxSVBay#!-ZW?wtmEqjO`um|DEz6t6
zV4PmDCwDXw%Ye>K{j6bBv;Nj&AS?2M0d&bkMdOnYSn73vC>L5yt@JTX1XD2w^K_5S
z%r(O<6{VlKfi7qL0sUyN*%SNjE4ZA{`d4S)>1DkLNC$oVG+Sw8kD`AI
zU7v1yX3QSkge!UG5oNKjRq4B5hy%GBd|9#H|e_NA-Vwvu~60ky?I~zMOmudK4|e?tN!u)Ufw5-LltTSXWO;
z3CB1mB?>my86=3_3uI*=Z9Un9?BCn30eCsS2=qbnhfQjux+v{RTIQVlfu=12IA^-^
zAKNl^aQz;Wqr}(!;7O#QOTia4!hCTVj~#jH!cv(w7e+Lai14U&6TL{8AcDQ@*12kd
z%iS@7*cu~r5}cN+n*l#F5!|Ibq~N#Pr3_ap>IxBAQcf;)k=|g77@SX^S$Zft5asTU
zB2Q0Z0eEMv@@RRS_b9?Yg!=ZYS%3ukCuWkqK&m_uwBBdyf}g!!hX~k=pgdxPY{v40
z2Y4+DIuQ7ue`l*PDrzN~p!c(%$9u!oBhUS1Sy(O7dd&dqW9Sy7w`;BsWFgW)APF0x
z!4BTN@jit5FNwUwS6g3+YrTmjyOu7i`;2CLstJg`qQsn)xG|YwKVFN5%5)q4R^Pj8wifZGsJdb9m%
z*YB1%N;&@%ub)!=%Xxf_Do;_0xF&k^fUJke>Y(eukB#l{T$C(-2W5hlg~Q-;)`>$O
zaK^$V8NMIrcKG>2DusX^>EgR?0pv;^OP-upu$LFl&HaXhj7|_J=Bgwac
z{3XNN3;F?a6DMlU&v!97S1q?VeeJ_1tp0l~8M`5LtsB&Ud4hq^Tl07@Puc^I1dYz|
zd{CRn6jiXElXFEi8S#&IBQOB}{Cn>Hv$b$Q5XwN1pG&Wjm*DVF)xekYv&8cKg#6$-
z*ibW-$R$0(--Vd@p*9!pVm4NI%|EzbS&Q=fjpr2p{#gUZpMp6q#R%3P2wu#(dQu
zb1xA0%CAZoxqcJkL@f54(VAj*RP`2NcfRkzIbnvJ+%}NT=rdXkZryNtReZv>4ZJ(D
z007b_CnZkahDIjnSdk0^$p9%O5`4Ci1v?@tQ`pp+&-=48@P7zNG<3+xBJ%tWf8dqtRr~r)
z*=M`2F16}CK@!fj`4XhKWMuV7klxCrC&h%tg+_&?`ZMceioCwoWv*%=-Qj1Ac2<7#
zw5o0lQra95q4z4QR67@g^0{62R;5_hW&h-6pa~)F(=Tihia(1HecI<^!(#%iLC;{G
zCzBJN4r{B|>0}VJN1P_EhpY5Qd;2t&{t$=I1N0AQJ}7if_zwh*IZgvSd7`{@1_C67
z&bbQ`@p8W*6Tsxga7d%YkB)EsXv^V$#S_{|NrMjDT;6PQttdH?%g~m?C9nkOYO4VspWl<3dh=~d(e%^
z%sw{D#C>NARZ&x;jOO7tx+z)E!E%Ji|JnnNuBuvq;71?U+E{SJa6@E2a49dd5CYKk
zs^i!HlDepWFid4nDTak=g$6L>@V>V4dY|4^@diPpVnu_t!9!LusU;kLuQHH8XCh{@
z8HhyGQ@_pVxP$Dr^Fwj`i*+Y{xbLwPDJ9b1ob@0BKN<=YE&YouGcJ5WhOzKs`%gL^
z_lKORyIHVcdkhVYEMsVA1`T@JFjorFYD*efKP`+zL=~H0U=z9&IklWCO_iu2rRBcT
zUWu_k7ciTxFbK;48HqjA&k5BpH6nwjRi~bxXOMCK7^E@h4j|VBzq9pVv@!#lfP8PmKgX)5NnG#D?~l@%1s
z%sO(=V}gNX!HAGXcU5ZGp!Ivk@96jHCOwfhQFv0aw9IwQu$f_U`+?VE|Gq@c?7T)>
zGqF8SgLp5lxfijy%C`My53jbe*%#`FFPqsI+N`0Z!M$|d^z8W+e^5fn7Lvq(eIW><
zq$iql5iPP#?;Brmv9uNqT7V0E*>;sGVF>>T!wzucnD-OG#Y68rc3(qpso1#K34!5e
zm?B4Vz&&NFAi}ZzCI}Qhv7b^i&^gVKCaPU){dcAjlaKFW+xz<)YrQ{Ssf;D00j9GQ
zvD9DE^LDE`ey$~<>;*M^*F&nE-ci5GMj_3JzS+u;;t>A59qRJ;)%2Kz?x^XLgw^6c
z4x8%j(8259ud7*b6@XEUXU(B9y?|RoBBklfe+%F%en@H1!xCDpiLcc}VGqtnRI2#9
zB5}VP-V+huq#=YHpyB!mdo;>;X$o}t7+Z;ShKY&w)7@MCIe)F1qv0-jcbX?|DFC(=>E~;s>trYylLm8I|mX=u5W{7Tl_g3NA^Jdt|V$*Q(<{EO*{>0tR
z$$Rd%Z^I-LQ|jO9wwSPY*^9rZu+qe&;=I_89Gn*8BVB+dxc(uEB5fbo|71h+A(OU~
z+UmhLuAW$HYnRv;ZP)z>*u-1V?d#X7gNOjYz=
zhx+>>$++M+2D49l_@hjg1GcezFf8IRSi+X*ETOZKkJW<&BWb>vEgxO`K&9BKH!qb9
zo6X-+nNLb3+2grg3GBCmMesM#jhpXoQ!X@84)gXsN`zTKW0E&*)z&%gn$&o)6o08-
z+3y}Vi-`%bsHu}Xw;>{M9t=JIrtFm)xQwQ~#{0i0wuaKb}pF7CVy?O^Xl=|a4tTxA!SQ>Ge9KQ^u=0Yy25RKun6q2{A{{F0@87*zk
z?qp5bqsG0{fSt@VTF#rAGh*^fXS~orZw%
zi>uh+L#|IZyy!ziwiuH^g)uYc3X&yzzBDU2;)x8J@80Z#jHR`&uo|^~
zoq8#L)7sX1uc+32%N<-@{?5rM$t{N=Cv9=9{Uw{J*K9EZbwU7t!
z&?^0(?=NOMgt!rvPnURJFT=`%b?FG&%r&?)8Uis-g4^KejW*B+r>Wgar^{JKCuj$T
z6ZC;0>c@T~b|xCH^f^JN)cY=LT#R?tZ-Dpjoc-yb*CRO6m?f_iJ@0E@equRFkbJ2n
z^@avNzH)34>R8$cg4pP~Cwr-e4?^5h8f`~AS#~gaug4ydiCizKapBsx9u6$
zCb`$&pF4>>#=p0#`3UxB%I8*cqkn0X6jO=>7)1rhkJO4<1pYg6
zvE%{KBM{^^dn30|!!hfR2Xu=191dz>UPuU?@I$9#rhQKNTnG~s61MNe5o{Yu9+u%w!mq+PLl~?nBUX`9{gW#4K0ph1|nsCo+v9$GBfJ@gpt2PkUW#vUtMjVbDY2ZDv06L
zyfTT;ksJYJL$h8jPM#2BCLh*|%ED3p>Aa|APM&HnE&GvCYgg`=C8%*C$o%hmtmNwx
z=g%P@vO7yVXqfstFWYr~n2Api+2@mKjtI&f%==4jkzc+X!BOd57CEVExxmkgioZt`
z$g&`GlyHPbCMva>-BA_FX@kd~P%Jm{`D{Ilf%u2>?LSlWq8Th!_D`QmzD6CKs9huw
z_V4H%k)hKi{}(KFq7YH{-8zG&^|S?)eKkXb(F1qm@^AC2OxH%?l2ekY^&1T-(NA5Q
zeFDPm4X`G`R3&K=U
zv4XK!Z;Tp2o47wVGz)vReb>QUb&Vu`)*z9UwXir)Cv6^M`9P`wX~J2QPjMJi(Gq>L
zKEY^elH^Q2674aN-n*#kl&NI@VA+0B`izfmbTR*3$ce@;$w`>!iu+5hIK2eB?^?Fn
zRyOQS@{uH*JTviBe4Dr1c#Y~!59ND%3^I>z;E&LJ9~pIYaNyOx)*Om7pE4KUE{?ANo|7u(962{An_A2w+}T-k#{J$rQlYBcpl$KF?)XzUro3rJdQ2zr
zlb4cVH4)wIanJC4qwsViY`}d7c~AN-p0Iz*E4~kn)->jM_}~va_!6_gLWnj*QR49P
z_qR^96N-4iBWu>DN-8q#JLlP#1D<0ir`ROV_9F=C;s)%Q3M+dBzr=JLBgxJPI<L1iRDtW!I
zr6T}}%j8f1-dcnQXH*JA8Wfz*&9^ufTkL!YF1zTlR85Nsh=xf#f8@MC#J8OHm$GRzZ*^_-L=LlY
z;s2}8P_F}*`Ipv$`k6l||JR2A&=&5!jelDrCWRXyuD&+8A3@zn80SBqO6H$z_5}&C$wP5pi-r7EZa%M9a2dmST@rIQp)b65VbLj3IaB
z+_WUPm8F?v3WNUrXroQ3tGMb^=)L7ND$W^)!_O1PNbHEUNxDW~Nve%DeQwW_>Pa%X
zt1|FPIGX+ycC=7iE^$ZVD!j})d3`*i(?RgrW0*7j<{ku{llv6EPWk%S&gk&Vwwlnd
zX9tnRBoT5Fg@hLld*GBO(zq=j7>$Jy)C$HYu~Y!Qb%hMn0>(hEuz@kcVqIqNtw{AM
z^(LoD^#>=%QHq9n62@hw5m8v;g}KC=6M5k1B=LBMFMNy4Y54se-h5r3qmN_u!+uZV
z@_7n^m-E?q7w_2z9rJIWNGem)gHPB~E49t4J~X#z{4ANi_VBXJL6d=4$cE1?$6F;A
z2t^jX`FmIR!WwLozt`NvXz~bhrxU9aAS-{wk>V{4dAv8;i7*o0R%%@JLn#v&JcCY(
z@K87CEE~mAatBzWrfXFwTkG$QarBZ0UD9`#iid9{F-XOLM*mlzEc_QpA(w?>^P1Lo!BlEwl%F9kL
zFAp{7fhKn0Do+x6O_Xeu@3A)HrWuselz;rh_0o8r-D_w?#aGQ=^R1E^U5>^@Qo}SX
zzQ-~+DsmqCp!bidxt#UiW4>(TF~IuMC;khI!>&&U?h1c?&Ca$be!PdBY&kUr71+=G
zPP3nt82ECeO+~44v~zhF3xxgF_F=Il@=*#|tupy68Az_Jj!Os_4+`D(_QJxZcO-Hq
zRPZacdDIhP?y8lex$P#qE2Wl$9F`aHp%--oXa!(~jNxGFONX41%zfo1pt$XwHFR8@
z(te?Kk(d#nE^1b-{Pyp7xB7Tck%jcrq!+E=I?Nc)Wz%oCHf={eK#{=1xDj-()_7VI
zZ`xUBmj~kG3|Mex;^N%=67PGt`$R8YmlFIQQf$D^Z%P{W0&91BiC1k>^@+0=A;32d
z`nB6u&Pvz$fBEvzI*mu%oA2MW>@G^%X@n$5s;Q~*IsPDHPiRK%y1Xn2tIc8*8WfVI
zLMe1j<5+Pd%Z^#6UuSwcf^z*@bvc~o6!sY}*dcSu<-ZQdC7RXsBBw;Q6#97WlQ
zn0>COyY-wR!QR+n)5f(u37we3XYhv)5xC}1Y8r{UZ~XQ6XxP4LUls#eMq?w+xTmuw
zqmxs~+Is1WXSaeG;|r!jG#eCPPrq(Y>GtLrx*B^W5261lc7PM65{Rp-5B(hd~PG)|}Zz
zM4_G8$qB!BFqfBUjrrPX+`DO+6shra;tQ-4|K28rNS9~@-r+N0^5XgzDzD{aEd=1qAYUfb}WF5)R6(I7M;Hv0b
zD(w3r0^g#!tf|xUbXm)l^EB-7p{|sonO?HBRW=N#n)=nJt5oF_w%l5S;KMhW0yh2I
z3$@nma1^bT-D`im(0u;vs06-_1Lo6v{7I90??>6E^uJPR>c1|tj*&Kv_Fv>vm`t^i
zjcf4OSLJ$0_vqq9l(Na+aVQm!44c?tUda8X&DRd(xN@K9zB~F5i%R;#OSM30rWymc
z53CO3J9|&5&@;2LSc|9M#>n8hnuZI!EIOI-8(&tTm!Y_}L7Jxi0=T`<7MHjeeE!o9
zI^6k(!b&FiW85j;Yg;`ZY>Y|YJXw*X^&VcCCo@%QZ!`RV05unGNT}x5uBRnO0M8$W
zHiuNyX_?iM>KNQ;7_fQKDp-EDmT6k4;J>~rJ6qIMll`hEizKg5tkb#yVgxKHo}CFx
z85!P?S9+`UT4ZNR&I%bF;^j+WbTaB`oR~!H&$r$3dtAHED;)r5R$g$wq@{+`r1g(Y
zEf(qKaYU9f4>7@#qR3p}N*CmGcYBGCQyn2CUp9w|n|uB97If-SH?pXEpln#(%Dj8w
zm=y+%T{$@PcV}g!@ESqB($HlPuzQS)LEWv@p@Z>1j&LD!q)1`7}BtwhY
z$#b=EhugrYqpZar6c2)n5RME)7o(N)+d|_LPhL#zl&Ru+&Ek%Sz@~SL+6H2Ztdf$7
zivX;KM2e?7*)ZK|W62dq9d>Zz+fp7#rc~g)sJoQ+$zqxSGA#?VUmW}kOYD>DU=0%i
z8&T`VsMb{JULOsE(Z3XpYRt0a{w3*>^@JDy^`3@I!Ij8v-`aF%@a13Q3?r{BTIr2+
z6g?LDaV*y7I@5s;i^oie{4g`Tw<=XCV!uHv~56$g9vgd^d>5wjuP#xpYn<L=d)s^WLLDP4rh^P4Rg${fh!F2dKYFitxnzai)R0Py4WNcPDbL=)
zLd-s>e={vqrX6wku$TW6vY;ox&mX7kdVghSmt@siC?9#6&F>4(6hFN49H>1b@Cm*&-3GP+i_D_t6TJ8>CUB*r$?&
z-r+Z6$wNQc&rYbMnZg+pyZe474b~A;%${bY~efTroPCz4=dY|Ncq$_iU1V6NxG&z4Ua8@JM`m}eCx
z+J?LJUli8a*=%jdoR7DP*q3K3mzKZ2I`VMqRQKK{b^PbatiEyFQ-f$J2=JgV;0X@uj1FS&LQMX
zo#CZpROoSQA;-rry{*=UR|b!inmWYvYR8ZMzZbwzwbih7m1Fj-+|TfBHjc!feS)1C
zjvwk?P~hnC3ckbY`ucS74dxBLE+GI(^<}$605BS%S+-$VMY)6{W8vtKiZ-r~Ht~Rq
zp3jE)FMKr_r3An}TYgvsDx?zp5fx_Z@_5~;ECy-zWxF>FBU
zLWi%zBpMj{pVVYL=@!#V3_#wZO_U^DK|Zi_+3OTe^x^b7AnpAq5@>~8mxWOl=ek6X
z>U}?JW7pHpM!=632-e|r1T3Bo`lj_LW>Y!Ex%FUF9BM$D&(o5Ks)bR&LMc|rzp_DA
z=*53st2?S|od6-$AS=I2C%)|^p%YPwn6CDsY5{sa^WLloe1j$zv(pi3cZzDG+LD1J
z5^3_^>2`y~zPS_^(Z#Q+zDcES>Tdf2_^>uG76{bu10-K1$!`)?UxBtd%1PDagRc95
zZ%FrG*cGozJ~*o*70!AXJ4Xez4j&^FiulXlkOTQ^`Hiw?E4QOtvhb6+D<3aW7az<|@>mckKGd2Jq1~uLCTW$VE
zh=ROWa0Qc4LF2L)0KgGX-2UVl6JH%?N}dQD*rMAybEmTb)Yaon2eKIbtBUtO3zS~2
zjOMm3qv#ubxbwTK0XF$3Q@6B(cC)GvAAb+eO$5Aw>v*Vg`CDa0fmOdsV01F*?q?D9
z&REObKaSB->8I6qI26V8^+g3PNd-zg5-`jLwIsa4#FiBg*+Vh#KO*382|aC(?t0P`
zSU;_^A+Pnwerqw2OUwK4YpK)uU%z^|+$pOKe_JY7%h_Qp3+*(lB`~aQEd@TPlXwwb
z@5USdOM0jthV$Q^QwuRp{V!;2@-hkmv&&f^NS))->@Ph-SH-^dt}ynX%cV5~j_q=%
z#gDH#P;D^hk#H3v(k4G}1G!Y|5BV`0R{V+#J=y}Ill(JZG{xrU4=cXVrXf!9R1v1v
zz%8Zw?ocJc@13CMpt4K9TNs>YfpL}
zrziZ8YpcEi-g0uj=^5G%`8eS8Lv^&Q1GZ?t;>i-xx*F)$n*R6qUspX>*TUcA9Emq&
zbc?cJnF73CgWEbP059WL;1hFoV78`0n1TJ1mqcupmsU^VhXJ&TuYpa2K#^Dg;&{=*
zULF%7$}oe7g^(s_?`#96cLXecRDPONY+)zTKk6I9NLutLD!3AtW9yO6|3313tTYq-MZ=eYWFk0
z+em*ejw8e-+x?;qO+n1d;=u4v{ndt#?@MX2lcb(GknBHK0k$
z$Unl%Ri+-3!qCm^}h0JG9NMv|i4Bf^Mb>rmo-k03HJEW|wr`#Nwrl)##~N0&>Rg
zaqXE57jzWzmsUT0RW=7O{!+5alf;bxAT+vvpgj|hQ6|qOYtaUt4qDM7>TFx+?o`J<_OjE5ecoKBirq!
zT2=*&^P2tcE_NzSQH1jWIuZtPuS02+_435x3*z&IZ>YrZFCiKN{{tQrCm*F&;ufB>#D>zpvi_6v4G{K7R7mi~Pt_PzfTnld<
zXX$_!L7UkT8%V|D^Sz%M`J@=Z?eSna_}4E9DWu)JFBV!y_M)kBcK6>spl-Muj5w4?
z4vezOX?2S$P`)4j^$X<5z#zV{y4XBbxmGjL4!gH*+vd(4k}PL;${3w!K0XFts``b19lFX)&dwM<_Mh^oScO0nDR^K-dl{y|Tu
zbQ>shCEs7kAt|Lm1!x_yl_OZBUw!H&Nnx$hSN)*rja*S
zs6vydm6c`b^NQXQCcT|Y0_SiWahNw7Mj3qtD!qHdjcfLq4V$c7?#e?$EHud#I10m5
z!9(Z3)WW47q;DvZB}(0T;26kx-#58F4Kh061ZA{9fQxZ)n
zX%Z4+WvMhlgKow=3{3@Q4h&@xc1*p=ug%O0V0S4v`)U}NKNpF=`FElD;PZ4A=A;l`
zMcz0)2S3w|dOoJgI6k7;`ZFp@zrE(I2u?@`wt~d+lpYZ3b}h-cRVNU19MFnkZsHB%
zNObX%>er{k#h}lS+>RcjWT4D=x8t|83)E}PYq`jM`&H98a8A?dDKbaA3@=|V}MNW&x6>xJD_w!7A&$qP2ZQxCF
zL6mt(F-Lbsu{eRwX@D+Av<~MWB
zbz*Xvsr^SZs~lH0+(wAvB!yLwR#RPZ&Q?DM~=q#I@EPcEL~!QPV{*SdT~
z(f1)iTv&dqvQoq}^Q^kJy7x(g1zl!#fyIRk#;+L_z(82U#_F$c4D;aZbK7iYpDUX$
zcCBw-4Nw}DUp?Mdd7M((o{uX&L&$jy*~|{xX-6{`x){eXRsUOCo6|z7kMI`|?%uxv
zZ!?ROG>GoXj^t@d-`2~fSIAh5$M&`P{i?+G`7$>6`Y>JjKA)jDDBD8odnq&mDuv*ET9_40wWmm(
z2*ll4HI`37v%}x!KYmcm;oYl&D#}fO{qwNlyOFI*xQ|jM%AN?vhDd7=M~w;%Jg*kI
zd?nk&8fL+Tu?|+Yb+D+#cx5KM
z5EifI86S=#l5I~Qk8Og7p~{gzC}&88gI<2GltrVB$SX>q
zAwzhap>Wa{jmoUo7%!G(W1_L()19?I{y7G0$N@p>1$qDp0g|fLD@Z)TTnOt_H
zFD<25%v~#-fd9L4|L)%%JI_sj1pTSDhY#(NGG;=j>+P*er%Ox>+AZIY0P~a4T&s_d
zs0EU~uA5!{R|mv7*)0FR=JEX7zh}Y3K`z5&ZAxb$1|jrp7N>hw_sDUYq#2qxPBoGEhQL`7ajK)XBiJynPihhkUwA(rYYd4?uW*2P=#o-Af_}r6ZPQ+AXm_`XKd9u$FY-m`$Zd_eLb-y`-
zx?aX-U#qg$dkM|^H;d%v+Uu6D5^BlJUFp@;e{vw|8t33LSF8_i^eqtiD*ef)MPaP%
zE*-|6R>zq4sktSp7(BlHYmu4!>4j=ujI)nU^vLR=yI0`%c!OY?@vF#ct(U<-cPWTy
zlzIU1Gd&6*M~t91`8S?Ul2G4N3XKi`mXQ@E?-_UETYYiF_}$g$ClYq2G~-H(c3GLp
zbs8a>QJj19toI^Iq1l*YWQkFdr^C{fI_FmH4zFMf_&iV?O#rR})6+zvXDUjj+^B%@
zqUHN{#-8s3r+xy#5m5@Iyxx~L)h!COhrEoa6H2^$93x+!k`NMc@kuV2XYtbj5aocY
zv1F{C+sqi(1|tb0x2pwv>3JWHzSq5!lj#KbMYq-EFNMM4PtS{Xr=iK81!(LUJhJ~f
zF{DMqlg25#Z;M9;r7)0V`9b2G+9Upz%pTC?qVc;!JwG86CQBT*>22Bu3M67j$VNJX
z1)`#dJh6TY{Wh5DsqXyzNg4R(^pr5sFEt_$_Ro!(qUYaEq?90OeAUlMBM2CDq64I?
z(9k%_os)@qc%&8TkpcE_Dj%`S%zWX}#jf>1NM;T)KWEkDHCqu^L$AF)
zzBO{)=Y)!5sLB@B0{M-Z@7x)dH{}V$w30N{iwD>#ldAArQi{m5*xcA$gwa)@ZjnS>aU1a_d~C@IJH7TgXkM9UluIWQSIT_mODzxoKr
z3^K{?Ln2>Dj8SJl+%qqn$oV0`?gMVo8K@gl5kDLK==m5t^*2Xfm41xL!u#*(FSp^O
z70ETor|{&MuHNN!I9cvx!qa5^IGTXVw83_5S)uhUh7Tgx=}4Uoef(AI^81kIf6v6b
ze+%x}4;hCN|BlcK{(fzE)DY6p?tzs?wfiOHap}YrOU&kSJj;i}p|*cBG1B{<#WS){
zrJMVOj0`&fXJU%fANWoQ87!m|jyvt8ETEIp)$qtPc0;7Dq5WZ^LE=o@PZiOh;aa%3
z|H6DK1pJM!M3beUz%>-hT7VTEWBdC>C1g`~x;>hJ#j)_&wZ65|44wR3t_U05kSb|1
zPVAb3%A*I#13g)hJd6S7q`^{P=FJmMY
zYTYg(P{j8qE(%Q5`4O@A?S;Sm9NF(2z4Jg&3dPJ`vhLLW0-3n-rp^XP9?z^YTK=bSV2h+Sj9M
zmzHw&a#b$|Pm)}!wgddlPwSAW@V%ovk6D9Os1W_avIGGeHF)`GXXYBMCzw}MTa56^
zD=uFnS;X_!?1DXXs*fhMobAHDBRU+BZ@sq}$yh4;XN39W}H=f_FFz9_Eiz1u!2T{KTGu}c11KB2d7*|XUYMc2g^aH`+s$7T!a
z$y&uU;v#rI^SrB;t;${TL#MvEzM+spGCXE6AS*v$9@n$DdZT)7QNz%gLn}~PUg_Rx
zcu*e&1@K%#bJm|GR)l|*YuyQDF7qiod}Tl(jrsn-_vLWIN9pM`7T?c;l#s`f!fO09
z_26GN%`RRxDVtAE-#fm5$7vJ3Q8AaY&Mob~PbCx2zZY(*7qs|VEa|0%@Mck}-f^?r
zR2yoY#RaeFXc|$IV$0EUhj%DRbv2_R+*b&WZbHv(E7-2{<(zoow+$cBMyjE-&Y!r?
zc4CZW5o!{N5?_(o*pDE9FfzlaN8o%>#SqY38)OVywHu4Rwb%*7QEL_eBg1rIl}Jw0
zFiG7w2U8HBhW`>KSs8mDo6D_MF(;Xfij#sd%^|V@8Ifo>G
zxyq$7y8c;nqWq)aK;ruvZ?(a?2I11ZC7#Tb#Ddx^c%1Zi;Cyp0VCX#GLg5XsK}bOvktrAOf2R`S)K`
zIY8e%K_LFs)zuLVonX)p5UP~tQ(npoZTa!#IY
z0)hecVfdK~jnkoCR~Dbi`a-RAYB`2cpSG)`IK=s2Q6F@k`VgiQL!N>^%UR-g;Ma|=
zx&&@zrM8(F!*|Bdb&P%@`CnC)8MqG
z;mvJP2HM)Z_R4Cw&%Tv^pz@>r;Y5w&C=n|xmm_~tA1UcBha
zXwgUnUrFlgwfT9ypKmkNG<4t~gY1g&{VAN4zW3dn%cf$PBPjxAGEMcQgkwI1>_QyJ
zX}`*axFboJ5>Of^QTTI(R7cRx=lKq|4O|#u8q%_}SsHRh=N}}}B=`W#<;Wq7e9Nx|
z+Y(WYmUBI*hZ`HwM4piZ{N{cqiz1k=GMUJfOESQ^)Px)(6ab<|VwapOewb+%f+$L+
z8j6wJka0$pNb
zzUoZIQN|`@4RU^G%S9~I3EH+b>VkyxBS_g~S)HCBxtXDJ
zhZHk;YMxjMR39M1WsdBycM=PW83{#yn%fTxHkLw<4Hn}-CWLspa5nwrh+p6gAo
zpC2Psh$0XdpwQlRNq>qX8>hv)6%-`vP9yJi-R0H;i@>WiD{NTzCi2y{ac()3r?
z9#%QF&RaVfyD^3<*
zfSFH>wv0_Ah$=|V005ip3#@+>{v!%!EDofwjM`M;{qaHBg6Sj1ihKz=N?UO!0hdb@
z0q>8}lf{ngIMwT6>`!Fs(K5;3*;ypQh{JNXxs8CWmR6O^a+%TYB1>f8#9+i?8V2__
zDNMC)s7_eY+A>kMk)Xc)1IPlnuk4HaIajB9&sY}A3NC;292a(;jaydYki_+(y-8{x$*y^=b;B9r
z!eS?ZOG4j1p?nZs;jk5stZ1ol{cK5cTC+
zH0AS3lm2MX$KiXD=TGM_+v6O!$q!c#966r@!|eC;KuKR71ft?_VZyuB!{a6d|0e*1|7?tjt~3Jl=0JtU$%ayP5;BP%L@2!Q84WjktCxhNJnCzi
z)Xp;xR$5li6Jza56*aUjUM!LK5DqjUT1aQ+w^NE-C|YVm07T8{Qtd5XMUZ&K_WL~Y
zWmjkV3i$+X>lefLlAOaTg#<3!-86(9G|~
zT<=`(D6@G4D}+uq_a~`7HQOTyCk4E`c$H`y&)LQEw7cBtYcr6=Q$3L;V732s{%{C`
z+y{&!^g7jB-h`22>vdA_-v+&-5_s9Us6W3h^%nE*J%;7qmBlmRn!{myeli&2y=r)S
zn3=j^zmq1TsqgLFo)d$^q__I|bdaOf=D2q(Lan$*P#Z@(=N#7?0PX9gtBNVb`xBmt
z&t68M3r8ehxU!8Mj!awCgawAa?@}?2c^Zh~PLAHEh;npGjp$~V;_!=bA4ZxYgv9VDs#pjo)==n;+T;agH~A6rM+J8N9f)q7rTTq&vcs5b;igW|)O0F8
zSj}TD6tkOG=v{tJhX%_yaC(G8D&U5<>)3w;_0C-Zj({-7947onh4
z&G2+k>ah9VNArCi{?P1H>>LmVr}yniz-xsI(Pr1imwd@j%bW0v#C10|fU-Gv8F
z;t6wuVbOW$YGDvwM$3TJAoZG4?5&Nj_1o_6ZHdk})C`AaYopx}Zv){zg=H^Q%8drkUTubu~Dz8Qm;taELZ2c
zT9nO#;rgZGiG=s2*e1a}3>6BJk?AGhRI!a2TWSWE?@`{`g}41_$@@czF$uW4SB_o`
zMqjrTjp^g+03-_P4Facmn|=2_+Dq*uFR3-2uhQjH%W%ERSm99q{(qA@WCMrl$24Ui
zr5jCpDI+F~DBS~b+&z?4XjXZJ*FjE{*Ov{g=kMqsapIk_f*$?%=O!+pn_IR>i1hZC
zIO4&uIFCgH98g5GdA_45wXtJZU|x?-W5_tn|0_%37n2IFGz3x?=>%ck+O1ulend*M
zAjfd)2e!Vr!`a$)0s)VtR`*#{TZ4D2=tp`+ZsQs~@MQbVIG^bT#IATu
z?DN=aJ3K2RnqyK&iW)>FGxw*u6t-+W96&azl*?;j-^H>qC?%8vODyF{*d6Q=jOf6E
zSK3rs+Qs&rLOtNI_Z>Q>v5qzKgNsG+e5Z^{ljH2+Kj{XMz*B8^Qiq4L^FUky_y>Cy
zgZE73cM3
zN!@Wtyp*_+t8sAx$J(Q}4JxG+;KZT5alDk*r_L5Ql>irx90n(pjB*H))l|hO$Abf>
z+c_84S$O?0P}Z+``dD5~hoQyc>Qzeqb4)k}Jbp6J+TH9HeaA$dNsUufr#vm8v)w|K$J
zZ|_m%Z=>agU9W1jPyKp|H5L1=CR+ZK1|){>Gzm9d?Ve~uI+nj0p!A%yn=b6(f{l!*
z3REb4DK&6!2ySRCaEk2fA{EKv-0?b*PW_Xzvo_PSJsSLqwRA!ASuJ|%{Y#Z9F2krK
zdduz4UCDPr7m234vM$RFpM^*S_CqeB@i~V1xXWnE&NCHGG*Q#o?7%I_6&fWCb{jt-
z_xKBZkC(HiP7T*%8qWVMQI>u4jR6G~|GU5m1DM_`H0Yn^meuJx*Q+`e%w2@%V3_
zr&dnrK4AGTa9VqKfdhi|2l?2>GlYL%Q?ygz{(|Ud>=1=R=L;9
zmpxGbMM^Hr&c|Whtwf*mIOTSrE!`GKH%y)(>Brlb?mhg#^iiC9Q8y}1nO3u^Am8h9
z7f+nTn~H!P(G*s?PgqeURKWQ->1Dz9O%v2i=ggm9xMN~*4xy>VWV)TWS$T?*6(bZe
zQd5idvDS1UssEJ2^pE}iTX@5vf>r7KrnJKGrm4NJjk
zw@mjavxfo~Ea7>+QJ{3rS(2a(NEI
z_e)nu6#WEM$y-T-9&70_CKSs1)d|Erjzo5wR&hHxpRLwoOu4wI8QZ!4g+^%L`{ksy
z(TkWd(4be-Met8h)iRb*eL)KmYOJqU
zX74vN&12D-D2|kPe=3!tQ9MD7cFM@a9CXtEUXpfpjdGy;Rx
z0Zttx=iqSF#0=P;Nz206We%ZB*epSnkmYi}
zrT`g`>v-~T(qV}Kr2J%h3TVd+^eoGhN>W5ivE?EGM##LQ#~(#)%O_<&7#gaofQmze
zHB=}Ql(qFG{M6l#O5QtwautJlp8#7?TpkqnZt>tVJKqDJ_m_=Gm8owQb*lxP(r`N2QZ|Hae)GS+
zr5ke({4p8PuyifAz?~$C=@uRKeZKu{%uJa0aBf_;CBQHMI||PZ#SQYS!(C`=n_*<*
z(-lNJheV&n#@GMRu(YdffR&9(Pb3rX;O&DaqG(5+@9X!y{d%X!4~tP_IG*kc9j)bs
zNX`AoZGXh=wQS4Nata+d<_}r9=cP4Q&8DxImTw{&lf&*8ruUhT#`*P91PUu*zD{Lz
z@xzeGv;kPW?_0C^Tp$nAhefY}E&Wu+Y?J({5$BU>i8uyUv+OK)v{%wTRaG7$QuR?d
z3x9KB3uYU}P=`y1F7h^<1yCM#Jt^laKp=Pu1l6F2$-#p{zO^jtf3E-786#j^O`c1
ztbpRrw~@zsDsWkzo43LWxo$bGA#3?!YODpBtR?h#iO)MC(QCBt%sGJ2!Cas^@q
z0y!7J%0?0QS%D6e?E%l3G9h{7ju*Zvs{F86}EoF?A3
zTC~{Aetk5`<#wOmCGp(@BY;DJHI~O@X!N3Pm_E-JLT~%^69VrQO&dNdjdmMTh;50~
zN_f}FRND#U7X-*qOBGD0D1ev9Ei)aI_)q}O8cZ#kh?S}d#to^CbjAqfnn)4hC;OP~
zlj+9#R*Tta$eCVSr#eRLSVfmu&xEp1fBozfFkaVtr4~qYMl4(Bt;-XxZhEbPS3G
zuxrhP7r-=6aRHRW2O6<&b^&*})DvouLGvRi?Z&Ds5=nx^zNhv?UV0HTF%P_{#mag{~UJ^W%ZAB?|
za0sB?Q6_qID#E4uUMf0n2%ur}g$;~c3uYp*OIFsy;sBJd5Dg1;o-D!I>iBHtJy|-*
zD%^dEf7;WwyHPElc+B-fq-A8BQ!%m8O(-ly*m&>yrB$!EKBxcb=0L$!d7S7lE8-n8n$yJ=d`ar^M}r
zp=Rqhl-VVFCc8okHcs
z(x3g$C5vA!);$CrzK;VVX+DRpmc~_!%Jw_r`01zuI_}rS*UVnx-jVVRjyov$vvf9Q
z@tfTPxZ=tN+ee$}W$BN`Hrstyof-V@*RJFJ6vBH{5V6U0tDYM;*bKr$EKPK)TH$k;
z{sl7_O@Q+RSBjr}NcC^V3WEZU{pT_tM}m$Od^R__$%-O@?0KIrc&<-=fBs^d!)$n6
z_slwx$;D)|)dt7J__g^`;|tfqpTSatVU@h4Dl-|~M~LUhG*e3-uTK*sdq)uf1&Fs%
zb=MU+1fADvh8%YNBoP-fC$K!$`}Jk0J&rYO<32lMWWKFa<}DP3m>xcua4}pu_;64h
zB!y`cmMIG|F0~*7N!5jm=lm{`UKTf$l*YXZ`-pMWDL8yVnME4=92UoXO$Yqgpeg=o
zZ&50Y+80(r%MYb2ZeBd2@bE}gU*P^qg-c2L9~{{fCAseUdt-H1%v=GH^val4j#^D*
zVZH*Nh&i9@`RPCm!Nqyoi)}g^O*HO@GSMv1GNRTowf$%+1#%Ccwmcl->s^ILD|OE6
zFHg?}+p>GJ%pWL;fLYtfKG?&R>d=#!rdR?_ujbb0a-a2+)@A=79;jw*sl!~6aHbPF
z=K#VIS(vScQT%mZ-&{}c8PkX5lT5V*z5$A^2f+P7;Ru!!xkMjWK>rUUL&Liz%dF86
zk{+?xD1U?a2g5SJ7R9=jcNTtA?~~CE(32AjB{~^&BzgJ78Bb|Gt})T6>_`(8Zz!L+
z4?~)yu`$>5tD|YIrjkjb#v-H)Qy=>(DqI?646jVsszz>CnpNxf^vgZTj}PpFrei++
z{N?pAb^W7%XKQ`ZE_P3%?l_lWs@jWlU+$aE=LrwZ@L8Fi>zuxu{c3djwoLPw+?5s7
zlEy@wLe^q%8FtlWo@Aw2Ear9m}Pz_q#j!
zCk)j!i5S)Sv5aiWQk3wT2!H1*pD?M$B2}sJ#d_5St(2I+x3L>H%tJ5~@-^&43pYl~
zAc0QTPnHow&L>d-C?P`%5@&mK%yb5g+ozAT^+h0(xrXVHwuF{2#h8B;&N#OE
z9vmii?Iy!l7B_ifd_PFWEK!r20K*@%I+DVU|LMmp!%Cyg!L#1d;2NuKAp!6Au#>rp
zP@t6c*+`PUvd8>&%B
zSS(~{P6$g)<*`{n<)LucW1(pcTqq*mA3uEW&3w;8^nDAqzu!cRb+7}u&j6yRArk)b
zD0REJP*j7z$Fx_CV};JqPK63top}R?MZ8IZ8nv6wtCVur&-b-MT75M2&v<1S+T^1W
zp;^dDSK)wrpMT%mPox1+sPJ7cvh~DqP=M`X1cma^@R&b7xh20%sP?rBRQ{H!|e#AY&vWyB}ADSk94$
zQ+&8iT3S3V34O&1+G~+aZ4@L}R)y{k!;Ka*^V45YDVa66Exx1HN>W92j0@aSZ@6gc
zdMA(YIg9SlOQxrp_fx`gR@e2hlTzvO(@wC2ZEHN%-@!&2Xtq_p^H5>-_OidXtu(ANNv8$T%Qi7<#p7+0)6THk~OTF#uN=0Lv5&9OnmbpGELZM
zHDqK~!Z!v4{ht0+lhR9!wQUa${qwOH5d2N^b-nN}5`$c)Xr~Zmk%Y^~Gs?BrKYVtb
z#@D_tBb`}d>jOOylNf6*giJswmqJev;y1ukohn@GkF!DAC9C`fT{XOCZ^5S&
z4VH7q6`e0{7aGjN24;6WPt6P;q#E@E`aL
zK`fs_=MKh+s;KP~Z6H$Q=1be>qYaajs`q0M4BneWf+j1qJ{Vg!SS<_`J`!ez%6{I5
zc2AhiUsqVE**i#2#9{0_MJ`*$Nn3X90QM#hDy6Y+s2K*&?z7t+-M7`RK#b}at5((R
zRo9tivB|7L+?3&7bt7YA&O3z)mvb){P77#<~;lA!Ayx&25q{A++3`M2~XD5fTUpiNA!kG0!C7Bjw=_q$-F
zc2l>&-EF)V4`N7PidrhMsPa!C+AF^uBbhyeMv@0aN?M+XFk!7aPR}tiZo=&m2m7
zALBd?`kud^jy;i%G6^78R{zO|WH!0Hed
z9*eZS$Ry7vOt}e{(*{hLqddMhLYPo#Tm1DXe;oVnUGt{e2oFv03saD@3HOVFqcM;f
z9Z&9FEA#O&@2`F8A@_nHtEOxUJOE!`O6YlLG2bhxuWL68_KvbCyIS-?2EmBuHXxYqs54+rhf$xy9QC_{CTMw&w#=
zkm;<}o089U;xxqWC|CwA>3CRgC2z7_;RJVezj+h8@7kHYp1XD$zdJzWY*dI?=In
zd&JLUWkuI|h9ZOEj9ni#nWt(28zy@>6eonWv`jWST(G#6ZIrA?quQ>(K_P3Wl1q=H
z9^)Z&z!!)}75NC?JuxE0u+625d1(al(XxH4*|L3f7K2Fd&cySQwy#=zPq{JO
zL9NG57>b{gOG-=oo}sTgoJTY;_KO^m7joWrBx7P8gFeskaZ)GygAHfbmq
zStguISN(%;-A&9CzO%vQ#baiSLG$kWzU$7L;X@H4-KCo&$?~nUg&_Us?0AB{
z=Nuc0r={62vrM7L&Ju@EEFPE=pR6AnGV1pkWFX_zf+N3jz^>#9!_~V_*GYMdP-11U-G8B+g4@Xq
z`>22i$q)Zsz51RU&x%x=&0B1mR5J2&Hj2?JH3zR6Wihy#E*qQzSC3-)EdgD#Yz1s~T;9LW&mhGZoF(Rc)
zV+o%Y+i$Psxm?HTio5;FpF??h0C%ew-WP_h7rHx@Z_I9o2%0gJ;>p-b8!om2!4T2?
zaRHGAQvog0V?S%GbYc){nW0060i_X_dame`jB>?L9(`?vZ#meO45n+M@p#*EV=nEb
zQExigJ`C0>vQE^Ua7W6Q)QHq8Lxzq*>o=G(ffZk*kSB;!&Ostyo-w6)<8>dYX=f~(
z>~*D?y}LSJXOIVZ-pn42CIoX2I(6P=#hh?F%>Opt>a$H^Lm#s1?j&fT%nIRw8eO1t
z2LF7To7*CpFnK^S6%
zN>U_4w(aZ#H2%JDsfTf0g6n>R#Zwo+Kp;aVqRO0Za}_9wltGPm-5F1DaWzUr4*p{)
z%fT*)vs!&KWS($#BmFKz-M;vCz@FR^KwobOYka!cA;%6QB{>!;o=uzYebN4~J|hD)
zvP)zCXXR#HFq7oS;yRLSn7m=e
zc$3vA3baUJprWHLW*(fmBS0WW$AW5ssQ)s1?7VI|M9>9p5BSPiIqbRDk4jqotUpnujJ0NXwZ9_QaQ2oE=ru