Updates in test documentation (#175)

* Updates in test documentation

* Update README.md

* Update StateClient.java

* Update README.md

* Update StateClient.java

Co-authored-by: Artur Souza <artursouza.ms@outlook.com>
This commit is contained in:
Marcos Reyes 2020-01-30 15:31:29 -06:00 committed by GitHub
parent b7d6b1cc58
commit 1af0bee418
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 259 additions and 365 deletions

View File

@ -0,0 +1,135 @@
# Dapr Actors Sample
In this example, we'll use Dapr to test the actor pattern capabilities such as concurrency, state, life-cycle management for actor activation/deactivation and timers and reminders to wake-up actors.
Visit [this](https://github.com/dapr/docs/blob/master/concepts/actor/actor_overview.md) link for more information about the Actor pattern.
This example contains the follow classes:
* DemoActor: The interface for the actor. Exposes the different actor features.
* DemoActorImpl: The implementation for the DemoActor interface. Handles the logic behind the different actor features.
* DemoActorService: A Spring Boot application service that registers the actor into the Dapr actor runtime.
* DemoActorClient: This class will create and execute actors and its capabilities by using Dapr.
## Pre-requisites
* [Dapr and Dapr Cli](https://github.com/dapr/docs/blob/master/getting-started/environment-setup.md#environment-setup).
* Java JDK 11 (or greater): [Oracle JDK](https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK11) or [OpenJDK](https://jdk.java.net/13/).
* [Apache Maven](https://maven.apache.org/install.html) version 3.x.
### Checking out the code
Clone this repository:
```sh
git clone https://github.com/dapr/java-sdk.git
cd java-sdk
```
Then build the Maven project:
```sh
# make sure you are in the `java-sdk` directory.
mvn install
```
### Running the Demo actor service
The first element is to run is `DemoActorService`. Its job is registering the `DemoActor` implementation in the Dapr's Actor runtime. In `DemoActorService.java` file, you will find the `DemoActorService` class and the `main` method. See the code snippet below:
```java
@SpringBootApplication
public class DemoActorService {
public static void main(String[] args) throws Exception {
///...
// Register the Actor class.
ActorRuntime.getInstance().registerActor(
DemoActorImpl.class, new DefaultObjectSerializer(), new DefaultObjectSerializer());
// Start Dapr's callback endpoint.
DaprApplication.start(port);
// Start application's endpoint.
SpringApplication.run(DemoActorService.class);
}
}
```
This application uses `ActorRuntime.getInstance().registerActor()` in order to register `DemoActorImpl` as an actor in the Dapr Actor runtime. Notice that this call passes in two serializer implementations: one is for Dapr's sent and received object and the other is for objects to be persisted.
`DaprApplication.start()` method will run the Spring Boot [DaprApplication](../../../springboot/DaprApplication.java), which registers the Dapr Spring Boot controller [DaprController](../../springboot/DaprController.java). This controller contains all Actor methods implemented as endpoints. The Dapr's sidecar will call into the controller. At the end of the main method, this class uses `SpringApplication.run()` to boostrap itself a an Spring application.
Execute the follow script in order to run the DemoActorService:
```sh
cd to [repo-root]
dapr run --app-id demoactorservice --app-port 3000 --port 3005 -- mvn exec:java -pl=examples -D exec.mainClass=io.dapr.examples.actors.http.DemoActorService -D exec.args="-p 3000"
```
### Running the Actor client
The actor client is a simple java class with a main method that uses the Dapr Actor capabilities in order to create the actors and execute the different methods based on the Actor pattern.
The `DemoActorClient.java` file contains the `DemoActorClient` class. See the code snippet below:
```java
public class DemoActorClient {
private static final int NUM_ACTORS = 3;
private static final int NUM_MESSAGES_PER_ACTOR = 10;
private static final ExecutorService POOL = Executors.newFixedThreadPool(NUM_ACTORS);
public static void main(String[] args) throws Exception {
///...
for (int i = 0; i < NUM_ACTORS; i++) {
ActorProxy actor = builder.build(ActorId.createRandom());
futures.add(callActorNTimes(actor));
}
///...
private static final CompletableFuture<Void> callActorNTimes(ActorProxy actor) {
return CompletableFuture.runAsync(() -> {
actor.invokeActorMethod("registerReminder").block();
for (int i = 0; i < NUM_MESSAGES_PER_ACTOR; i++) {
//Invoking the "incrementAndGet" method:
actor.invokeActorMethod("incrementAndGet", 1).block();
//Invoking "say" method
String result = actor.invokeActorMethod("say",
String.format("Actor %s said message #%d", actor.getActorId().toString(), i), String.class).block();
System.out.println(String.format("Actor %s got a reply: %s", actor.getActorId().toString(), result));
///...
}
System.out.println(
"Messages sent: " + actor.invokeActorMethod("incrementAndGet", 0, int.class).block());
}, POOL);
}
}
}
```
First, The client defines how many actors it is going to create, as well as how many invocation calls it will perform per actor. Then the main method declares a `ActorProxyBuilder` for the `DemoActor` class for creating `ActorProxy` instances, which are the actor representation provided by the SDK. The code executes the `callActorNTimes` private method once per actor. This method executes functionality for the DemoActor implementation using `actor.invokeActorMethod()` in the follow order: `registerReminder()` which sets the due time and period for the reminder, `incrementAndGet()` which increments a counter, persists it and sends it back as response, and finally `say` method wich will print a message containing the received string along with the formatted server time. See [DemoActorImpl](DemoActorImpl.java) for details on the implementation of these methods.
Use the follow command to execute the DemoActorClient:
```sh
cd to [repo-root]
dapr run --app-id demoactorclient --port 3006 -- mvn exec:java -pl=examples -D exec.mainClass=io.dapr.examples.actors.http.DemoActorClient
```
Once running, the `DemoActorClient` logs will start displaying the different steps:
First, we can see actors being activated:
![actordemo1](../../../../../../resources/img/demo-actor-client1.png)
Then we can see the `registerReminder` in action. `DemoActorClient` console displays the actors handling reminders:
![actordemo2](../../../../../../resources/img/demo-actor-client2.png)
After invoking `incrementAndGet`, the code invokes `say` method (you'll see these messages 10 times per each of the 3 actors):
![actordemo2](../../../../../../resources/img/demo-actor-client3.png)
On the other hand, the console for `DemoActorService` is also responding to the remote invocations:
![actordemo2](../../../../../../resources/img/demo-actor-service.png)
For more details on Dapr SpringBoot integration, please refer to [Dapr Spring Boot](../../springboot/DaprApplication.java) Application implementation.

View File

@ -3,7 +3,7 @@
* Licensed under the MIT License.
*/
package io.dapr.examples.bindings;
package io.dapr.examples.bindings.http;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

View File

@ -3,7 +3,7 @@
* Licensed under the MIT License.
*/
package io.dapr.examples.bindings;
package io.dapr.examples.bindings.http;
import io.dapr.springboot.DaprApplication;
import org.apache.commons.cli.CommandLine;

View File

@ -3,7 +3,7 @@
* Licensed under the MIT License.
*/
package io.dapr.examples.bindings;
package io.dapr.examples.bindings.http;
import io.dapr.client.DaprClient;
import io.dapr.client.DaprClientBuilder;

View File

@ -40,7 +40,7 @@ mvn install
Before getting into the application code, follow these steps in order to setup a local instance of Kafka. This is needed for the local instances. Steps are:
1. navigate to the [repo-root]/examples/src/main/java/io/dapr/examples/bindings
1. navigate to the [repo-root]/examples/src/main/java/io/dapr/examples/bindings/http
2. Run `docker-compose -f ./docker-compose-single-kafka.yml up -d` to run the container locally
3. Run `docker ps` to see the container running locally:
@ -89,7 +89,7 @@ public class InputBindingController {
Execute the follow script in order to run the Input Binding example:
```sh
cd to [repo-root]/examples
dapr run --app-id inputbinding --app-port 3000 --port 3005 -- mvn exec:java -D exec.mainClass=io.dapr.examples.bindings.InputBindingExample -D exec.args="-p 3000"
dapr run --app-id inputbinding --app-port 3000 --port 3005 -- mvn exec:java -D exec.mainClass=io.dapr.examples.bindings.http.InputBindingExample -D exec.args="-p 3000"
```
### Running the Output binding sample
@ -124,19 +124,19 @@ Use the follow command to execute the Output Binding example:
```sh
cd to [repo-root]/examples
dapr run --app-id outputbinding --port 3006 -- mvn exec:java -D exec.mainClass=io.dapr.examples.bindings.OutputBindingExample
dapr run --app-id outputbinding --port 3006 -- mvn exec:java -D exec.mainClass=io.dapr.examples.bindings.http.OutputBindingExample
```
Once running, the OutputBindingExample should print the output as follows:
![publisheroutput](../../../../../resources/img/outputbinding.png)
![publisheroutput](../../../../../../resources/img/outputbinding.png)
Events have been sent.
Once running, the InputBindingExample should print the output as follows:
![publisheroutput](../../../../../resources/img/inputbinding.png)
![publisherinput](../../../../../../resources/img/inputbinding.png)
Events have been retrieved from the binding.
For more details on Dapr Spring Boot integration, please refer to [Dapr Spring Boot](../../springboot/DaprApplication.java) Application implementation.
For more details on Dapr Spring Boot integration, please refer to [Dapr Spring Boot](../../../springboot/DaprApplication.java) Application implementation.

View File

@ -0,0 +1,67 @@
## State management sample
This sample illustrates the capabilities provided by Dapr Java SDK for state management. For further information about State management please refer to [this link](https://github.com/dapr/docs/blob/master/concepts/state-management/state-management.md)
## Pre-requisites
* [Dapr and Dapr Cli](https://github.com/dapr/docs/blob/master/getting-started/environment-setup.md#environment-setup).
* Java JDK 11 (or greater): [Oracle JDK](https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK11) or [OpenJDK](https://jdk.java.net/13/).
* [Apache Maven](https://maven.apache.org/install.html) version 3.x.
### Checking out the code
Clone this repository:
```sh
git clone https://github.com/dapr/java-sdk.git
cd java-sdk
```
Then build the Maven project:
```sh
# make sure you are in the `java-sdk` directory.
mvn install
```
### Running the StateClient
This example uses the Java SDK Dapr client in order to save, retrieve and delete a state, in this case, an instance of a class. See the code snippet bellow:
```
public class StateClient {
///...
private static final String KEY_NAME = "mykey";
///...
public static void main(String[] args) {
DaprClient client = new DaprClientBuilder().build();
String message = args.length == 0 ? " " : args[0];
MyClass myClass = new MyClass();
myClass.message = message;
client.saveState(KEY_NAME, myClass).block();
System.out.println("Saving class with message: " + message);
Mono<State<MyClass>> retrievedMessageMono = client.getState(KEY_NAME, MyClass.class);
System.out.println("Retrieved class message from state: " + (retrievedMessageMono.block().getValue()).message);
System.out.println("Deleting state...");
Mono<Void> mono = client.deleteState(KEY_NAME);
mono.block();
Mono<State<MyClass>> retrievedDeletedMessageMono = client.getState(KEY_NAME, MyClass.class);
System.out.println("Trying to retrieve deleted state: " + retrievedDeletedMessageMono.block().getValue());
}
}
```
The code uses the `DaprClient` created by the `DaprClientBuilder`. Notice that this builder uses default settings. Internally, it is using `DefaultObjectSerializer` for two properties: `objectSerializer` is for Dapr's sent and recieved objects, and `stateSerializer` is for objects to be persisted. This client performs three operations: `client.saveState(...)` for persisting an instance of `MyClass`, then uses the `client.getState(...)` operation in order to retrieve back the persisted state using the same key. `client.deleteState(...)` operation is used to remove the persisted state. Finally, the code tries to retrieve the deleted state, which should not be found.
### Running the example
Run this example with the following command:
```sh
dapr run --port 3006 -- mvn exec:java -pl=examples -D exec.mainClass=io.dapr.examples.state.StateClient -D exec.args="'my message'"
```
Once running, the OutputBindingExample should print the output as follows:
![stateouput](../../../../../resources/img/state.png)

View File

@ -0,0 +1,48 @@
package io.dapr.examples.state;
import io.dapr.client.DaprClient;
import io.dapr.client.DaprClientBuilder;
import io.dapr.client.domain.State;
import reactor.core.publisher.Mono;
/**
* 1. Build and install jars:
* mvn clean install
* 2. send a message to be saved as state:
* dapr run --port 3006 -- \
* mvn exec:java -pl=examples -D exec.mainClass=io.dapr.examples.state.StateClient -D exec.args="'my message'"
*/
public class StateClient {
public static class MyClass {
public String message;
}
private static final String KEY_NAME = "myKey";
/**
* Executes the sate actions.
* @param args messages to be sent as state value.
*/
public static void main(String[] args) {
DaprClient client = new DaprClientBuilder().build();
String message = args.length == 0 ? " " : args[0];
MyClass myClass = new MyClass();
myClass.message = message;
client.saveState(KEY_NAME, myClass).block();
System.out.println("Saving class with message: " + message);
Mono<State<MyClass>> retrievedMessageMono = client.getState(KEY_NAME, MyClass.class);
System.out.println("Retrieved class message from state: " + (retrievedMessageMono.block().getValue()).message);
System.out.println("Deleting state...");
Mono<Void> mono = client.deleteState(KEY_NAME);
mono.block();
Mono<State<MyClass>> retrievedDeletedMessageMono = client.getState(KEY_NAME, MyClass.class);
System.out.println("Trying to retrieve deleted state: " + retrievedDeletedMessageMono.block().getValue());
}
}

View File

@ -1,69 +0,0 @@
package io.dapr.examples.state.grpc;
import com.google.protobuf.Any;
import com.google.protobuf.ByteString;
import io.dapr.DaprGrpc;
import io.dapr.DaprGrpc.DaprBlockingStub;
import io.dapr.DaprProtos.DeleteStateEnvelope;
import io.dapr.DaprProtos.GetStateEnvelope;
import io.dapr.DaprProtos.GetStateResponseEnvelope;
import io.dapr.DaprProtos.SaveStateEnvelope;
import io.dapr.DaprProtos.StateRequest;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import java.util.UUID;
/**
* Simple example, to run:
* mvn clean install
* dapr run --grpc-port 50001 -- mvn exec:java -pl=examples -Dexec.mainClass=io.dapr.examples.Example
*/
public class Example {
/**
* The main method of this app.
* @param args Not used.
*/
public static void main(String[] args) {
ManagedChannel channel =
ManagedChannelBuilder.forAddress("localhost", 50001).usePlaintext().build();
DaprBlockingStub client = DaprGrpc.newBlockingStub(channel);
String key = "mykey";
// First, write key-value pair.
{
String value = UUID.randomUUID().toString();
StateRequest req = StateRequest
.newBuilder()
.setKey(key)
.setValue(Any.newBuilder().setValue(ByteString.copyFromUtf8(value)).build())
.build();
SaveStateEnvelope state = SaveStateEnvelope.newBuilder()
.addRequests(req)
.build();
client.saveState(state);
System.out.println("Saved!");
}
// Now, read it back.
{
GetStateEnvelope req = GetStateEnvelope
.newBuilder()
.setKey(key)
.build();
GetStateResponseEnvelope response = client.getState(req);
String value = response.getData().getValue().toStringUtf8();
System.out.println("Got: " + value);
}
// Then, delete it.
{
DeleteStateEnvelope req = DeleteStateEnvelope
.newBuilder()
.setKey(key)
.build();
client.deleteState(req);
System.out.println("Deleted!");
}
}
}

View File

@ -1,79 +0,0 @@
## State management via Dapr's Grpc endpoint using the Java-SDK
This example shows how to write and read data on Dapr via Grpc.
## Pre-requisites
* [Dapr and Dapr Cli](https://github.com/dapr/docs/blob/master/getting-started/environment-setup.md#environment-setup).
* Java JDK 11 (or greater): [Oracle JDK](https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK11) or [OpenJDK](https://jdk.java.net/13/).
* [Apache Maven](https://maven.apache.org/install.html) version 3.x.
### Checking out the code
Clone this repository:
```sh
git clone https://github.com/dapr/java-sdk.git
cd java-sdk
```
Then build the Maven project:
```sh
# make sure you are in the `java-sdk` directory.
mvn install
```
### Understanding the code
Open the file `Example.java`, it contains the client to communicate to Dapr's runtime. In this example, we will be using port 50001 and use the blocking (instead of asynchronous) client:
```
ManagedChannel channel =
ManagedChannelBuilder.forAddress("localhost", 50001).usePlaintext().build();
DaprBlockingStub client = DaprGrpc.newBlockingStub(channel);
```
The code has 3 parts: save, read and delete a key-value pair.
First, save a key-value pair to the state store using the `saveState` method.
```
StateRequest req = StateRequest
.newBuilder()
.setKey(key)
.setValue(Any.newBuilder().setValue(ByteString.copyFromUtf8(value)).build())
.build();
SaveStateEnvelope state = SaveStateEnvelope.newBuilder()
.addRequests(req)
.build();
client.saveState(state);
```
Then, read it:
```
GetStateEnvelope req = GetStateEnvelope
.newBuilder()
.setKey(key)
.build();
GetStateResponseEnvelope response = client.getState(req);
String value = response.getData().getValue().toStringUtf8();
```
In the end, delete it:
```
DeleteStateEnvelope req = DeleteStateEnvelope
.newBuilder()
.setKey(key)
.build();
client.deleteState(req);
```
### Running the example
Finally, run this example with the following command:
```sh
dapr run --grpc-port 50001 -- mvn exec:java -pl=examples -Dexec.mainClass=io.dapr.examples.state.grpc.Example
```
To find more features available in the Dapr's Grpc API, see [dapr.proto](../../../../../../../../../proto/dapr/dapr.proto).
Thanks for playing.

View File

@ -1,110 +0,0 @@
package io.dapr.examples.state.http;
import static java.lang.System.out;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import io.dapr.client.DaprClient;
import io.dapr.client.DaprClientBuilder;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
/**
* OrderManager web app.
*
* <p>Based on the helloworld Node.js example in https://github.com/dapr/samples/blob/master/1.hello-world/app.js
*
* <p>To install jars into your local maven repo:
* mvn clean install
*
* <p>To run (after step above):
* dapr run --app-id orderapp --app-port 3000 --port 3500 -- mvn exec:java -pl=examples \
* -Dexec.mainClass=io.dapr.examples.state.http.OrderManager
*
* <p>If this class changes, run this before running it again:
* mvn compile
*/
public class OrderManager {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
/**
* The main method of this app.
* @param args Unused.
* @throws IOException An exception.
*/
public static void main(String[] args) throws IOException {
int httpPort = 3001;
HttpServer httpServer = HttpServer.create(new InetSocketAddress(httpPort), 0);
DaprClient daprClient =
(new DaprClientBuilder()).build();
httpServer.createContext("/order").setHandler(e -> {
out.println("Fetching order!");
try {
byte[] data = daprClient.getState("order", String.class).block().getValue().getBytes();
e.getResponseHeaders().set("content-type", "application/json");
e.sendResponseHeaders(200, data.length);
e.getResponseBody().write(data);
e.getResponseBody().close();
} catch (IOException ioerror) {
out.println(ioerror);
e.sendResponseHeaders(500, ioerror.getMessage().getBytes().length);
e.getResponseBody().write(ioerror.getMessage().getBytes());
e.getResponseBody().close();
}
});
httpServer.createContext("/neworder").setHandler(e -> {
try {
out.println("Received new order ...");
String json = readBody(e);
JsonNode jsonObject = OBJECT_MAPPER.readTree(json);
JsonNode data = jsonObject.get("data");
String orderId = data.get("orderId").asText();
out.printf("Got a new order! Order ID: %s\n", orderId);
daprClient.saveState("order", data.toString()).block();
out.printf("Saved state: %s\n", data.toString());
e.sendResponseHeaders(200, 0);
e.getResponseBody().write(new byte[0]);
e.getResponseBody().close();
} catch (IOException ioerror) {
out.println(ioerror);
e.sendResponseHeaders(500, ioerror.getMessage().getBytes().length);
e.getResponseBody().write(ioerror.getMessage().getBytes());
e.getResponseBody().close();
}
});
httpServer.start();
out.printf("Java App listening on port %s.", httpPort);
}
private static String readBody(HttpExchange t) throws IOException {
// retrieve the request json data
InputStream is = t.getRequestBody();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
try {
while ((len = is.read(buffer)) > 0) {
bos.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
bos.close();
}
return new String(bos.toByteArray(), StandardCharsets.UTF_8);
}
}

View File

@ -1,98 +0,0 @@
## State management via Dapr's HTTP endpoint using the Java-SDK
This example shows how to write and read data on Dapr via HTTP.
## Pre-requisites
* [Dapr and Dapr Cli](https://github.com/dapr/docs/blob/master/getting-started/environment-setup.md#environment-setup).
* Java JDK 11 (or greater): [Oracle JDK](https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK11) or [OpenJDK](https://jdk.java.net/13/).
* [Apache Maven](https://maven.apache.org/install.html) version 3.x.
### Checking out the code
Clone this repository:
```sh
git clone https://github.com/dapr/java-sdk.git
cd java-sdk
```
Then build the Maven project:
```sh
# make sure you are in the `java-sdk` directory.
mvn install
```
### Understanding the code
This example implements a service listening on port 3000, while using Dapr's state store via port 3500. Its API also offers two methods: `/order` and `/neworder`. Calls to '/order' will fetch the state from Dapr's state store:
```
DaprClient daprClient =
(new DaprClientBuilder()).build();
httpServer.createContext("/order").setHandler(e -> {
out.println("Fetching order!");
try {
byte[] data = daprClient.getState("order", String.class).block().getValue().getBytes();
e.getResponseHeaders().set("content-type", "application/json");
e.sendResponseHeaders(200, data.length);
e.getResponseBody().write(data);
e.getResponseBody().close();
} catch (IOException ioerror) {
out.println(ioerror);
e.sendResponseHeaders(500, ioerror.getMessage().getBytes().length);
e.getResponseBody().write(ioerror.getMessage().getBytes());
e.getResponseBody().close();
}
});
```
Calls to `/neworder` will persist a new state do Dapr's state store:
```
httpServer.createContext("/neworder").setHandler(e -> {
try {
out.println("Received new order ...");
String json = readBody(e);
JsonNode jsonObject = OBJECT_MAPPER.readTree(json);
JsonNode data = jsonObject.get("data");
String orderId = data.get("orderId").asText();
out.printf("Got a new order! Order ID: %s\n", orderId);
daprClient.saveState("order", data.toString()).block();
out.printf("Saved state: %s\n", data.toString());
e.sendResponseHeaders(200, 0);
e.getResponseBody().write(new byte[0]);
e.getResponseBody().close();
} catch (IOException ioerror) {
out.println(ioerror);
e.sendResponseHeaders(500, ioerror.getMessage().getBytes().length);
e.getResponseBody().write(ioerror.getMessage().getBytes());
e.getResponseBody().close();
}
});
```
### Running the example
Now, run this example with the following command:
```sh
dapr run --app-id orderapp --app-port 3000 --port 3500 -- mvn exec:java -pl=examples -Dexec.mainClass=io.dapr.examples.state.http.OrderManager
```
Use Dapr cli to invoke the APIs for this service. Initially, save a new order:
```sh
dapr invoke --app-id orderapp --method neworder --payload "{\"data\": { \"orderId\": \"41\" } }"
```
See the last order by invoking the `/order` method:
```sh
dapr invoke --app-id orderapp --method order
```
Finally, change the values for `orderId` and see if it gets updated by invoking `/order` again.
Thanks for playing.

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB