April 2009 - Posts

Geneva Framework – Quick Intro

This new stuff will make me busy for the next 7 weeks as we will have the 1st POC for Geneva. For you how are new to Geneva, you can download it at connect.microsoft.com including all of its tutorials and guidance around claim-based identity. In claim-based paradigm, user presents an identity to application as a set of claims. One claim could be the user ID, emails, or another The main idea is that an external identity system is configured to give your application everything it needs to know about the user with each request, along with cryptographic assurance that the identity data you receive comes from a trusted source.

Let see simple scenario. Let say, you are creating a weather web services with smart client model. Diagram below shows a claims-aware weather Web service (the Relying Party application) and a smart client that wants to use that weather service. The RP exposes a policy that describes its addresses, bindings, and contracts. But the policy also includes a list of claims that the RP needs, such as user name, e-mail address, and role memberships. The policy also tells the smart client the address of the STS from which it should retrieve these claims. After retrieving this policy (1), the client now knows where to go to authenticate: the STS. The user presents credentials (2) and the smart client makes a web service request (3) to the STS, requesting the claims that the RP asked for in its policy. The job of the STS is to authenticate the user and return a security token that gives the RP all of the claims it needs. The smart client then makes its request to the weather web service as the relying party (4), and sending the security token along in the security SOAP header. The weather service as RP now receives claims with each request, and simply rejects any requests that don’t include a security token from the issuing authority that it trusts.

We already know this concept in general. But what about if you are required to reuse existing user identity, for example Windows Live passport or you have list of website users in SQL Database? Yes.. Here is where Geneva Framework can help you. You can make your custom STS that can read claims store in database (maybe in legacy system) and also directory services. Geneva is not only about SSO or Federation, but more than that because you will be able to create custom STS that match to specific requirement.

What about interoperability? Several WS-* standards are used in the previous scenario. Policy is retrieved using WS-MetadataExchange, and the policy itself is structured according to the WS-Policy specification. The STS exposes endpoints that implement the WS-Trust specification, which describes how to request and receive security tokens. Most STSs today issue tokens formatted with Security Assertion Markup Langauge (SAML). SAML is an industry-recognized XML vocabulary that can be used to represent claims in an interoperable way.

01

See you in other Geneva story.

Cheers – RAM 

Share this post: | | | |

Weather Info for Indonesia

Simplest Scenario. Put list of cities in Indonesia in string collection and display it in dropdown list.

private string[] Cities = new string[]
       {
           "Banda Aceh , ID",
           "Medan , ID",
           "Pakanbaru , ID",
           "Batam , ID",
           "Padang , ID",
           "Jambi , ID",
           "Palembang , ID",
           "Pangkal Pinang , ID",
           "Bengkulu , ID",
           "Bandar Lampung , ID",
           "Pontianak , ID",
           "Samarinda , ID",
           "Palangkaraya , ID",
           "Banjarmasin , ID",
           "Menado , ID",
           "Gorontalo , ID",
           "Palu , ID",
           "Kendari , ID",
           "Makassar , ID",
           "Majene , ID",
           "Ternate , ID",
           "Ambon , ID",
           "Jayapura , ID",
           "Sorong , ID",
           "Biak , ID",
           "Manokwari , ID",
           "Merauke , ID",
           "Kupang , ID",
           "Sumbawa Besar , ID",
           "Mataram , ID",
           "Denpasar , ID",
           "Jakarta , ID",
           "Serang , ID",
           "Bandung , ID",
           "Semarang , ID",
           "Yogyakarta , ID",
           "Surabaya , ID"
       };


Then you can use the Animaonline DLL (Windows Mobile version is available) to query Google Maps API. Very easy, just add reference to the downloaded animaonline DLL in your project then put the following codes snippet for Show Data event handler method:

Animaonline.GeoServices.GeoCode.ApiKey =
@"ABQIAAAAYLumHTv11x4C0SeD1dju_hR1L4k4lZLEfduCoVr7aBMzMPP6bRT1dqi20tlAWBkMGPUkXEc1qTd8bQ";
Animaonline.WeatherAPI.WeatherData wD = new WeatherData(LanguageCode.en_US, cityNameSelected, true, true);


Once you get your wD object created, you can use its properties to display weather condition including its 3 days forecast.

this.lblCityVal.Text = wD.city;
this.lblDay1Val.Text = wD.day_of_weekDAY2 + " " + wD.conditionDAY2;
this.lblDay2Val.Text = wD.day_of_weekDAY3 + " " + wD.conditionDAY3;
this.lblHumidityVal.Text = wD.humidity;
this.lblTempVal.Text = wD.temp_c.ToString();
this.lblTimeVal.Text = wD.current_date_time.ToString();
this.lblTodayVal.Text = wD.conditionTODAY;
this.lblTomorrowVal.Text = wD.conditionTOMORROW;
this.lblWindVal.Text = wD.wind_condition;


You can get Google Maps API key from this link and the result will be like this:

01

If you have nice icons, WeatherData (wD) object also can help you to locate the image icons. Weather condition is common for all Weather services, for example cloudy, partly_cloudy, etc condition from Yahoo Weather API. Also, if you dont want to use Yahoo Weather API, you can get many free weather icons in Internet or make your own Indonesian version.

free_weather_icons

What else? Hmmm the map. Look on the code below:


Animaonline.GeoServices.GeoCode.ApiKey =
@"ABQIAAAAYLumHTv11x4C0SeD1dju_hR1L4k4lZLEfduCoVr7aBMzMPP6bRT1dqi20tlAWBkMGPUkXEc1qTd8bQ";

Animaonline.WeatherAPI.WeatherData wD = new WeatherData(LanguageCode.en_US, cityNameSelected, true, true);

string weatherString = string.Format("City:{0} Condition:{1} Temperature:{2}°F {3}°C", wD.city, wD.condition, wD.temp_f, wD.temp_c);

Animaonline.Maps.GoogleMapGenerator.Map myMap = new Animaonline.Maps.GoogleMapGenerator.Map(wD.latitude_e6, wD.longitude_e6, weatherString, 640, 480, 11);


Once you have your myMap object, you can save an HTML file for example (Makassar.html) using this following code:

myMap.SaveMap(“Makassar.html”);


The generated HTML will give you the Google Map HTML Script viewer:

02

If you want to combine your your viewer with WebBrowser control, let say, your control name is myWebBrowser, then you can use the other method to bind the generated HTML to your WebBrowser control”


FileStream source = new FileStream("Makassar.html", FileMode.Open, FileAccess.Read);
myWebBrowser.DocumentStream = source;
myWebBrowser.Refresh();


If you prefer NOT to generate HTML, but directly bind the mapHTML stream to your WebBrowser control, you can do handler codes like below:



Animaonline.GeoServices.GeoCode.ApiKey =
@"ABQIAAAAYLumHTv11x4C0SeD1dju_hR1L4k4lZLEfduCoVr7aBMzMPP6bRT1dqi20tlAWBkMGPUkXEc1qTd8bQ";

Animaonline.WeatherAPI.WeatherData wD = new WeatherData(LanguageCode.en_US, cityNameSelected, true, true);

string weatherString = string.Format("City:{0} Condition:{1} Temperature:{2}°F {3}°C", wD.city, wD.condition, wD.temp_f, wD.temp_c);

Animaonline.Maps.GoogleMapGenerator.Map myMap = new Animaonline.Maps.GoogleMapGenerator.Map(wD.latitude_e6, wD.longitude_e6, weatherString, 640, 480, 11);

myWebBrowser.DocumentText = myMap.MapHtml;
myWebBrowser.Refresh();

 

Other API that you may use is Yahoo Weather API. I am thinking to make simple IE8 Web Slice ready scripts using Yahoo Feed. Let see if I have my insomnia time this weekend, so I can take REST with weather, kidding…. :) Remembering weather, we have a lot of urgency in Indonesia to start doing something about it. I believe many developers here can do something, for example:

- Create Indonesian weather services as wrapper from other weather APIs
- Create cool reusable weather viewer using Silverlight, WPF, Compact Framework, Popfly, IE8 etc.

Have nice insomnia weekend and hope this helps!

 

Cheers – RAM

Share this post: | | | |

.NET RIA Services – Is Good Enough?

I am working to help some of physicists who run KucingFisika, to design their future multimedia e-learning site. After looking around for design practices, I found that Microsoft .NET RIA Services is good as our base line. This is now available and you can download from here and later explore it like I do. In this post, I would like to encourage you to see that we can add additional scenarios, making RIA Services really applicable in low bandwidth condition like us in Asia – adding offline feature. From marketing point of view:

Microsoft .NET RIA Services simplifies the traditional n-tier application pattern by bringing together the ASP.NET and Silverlight platforms. RIA Services provides a pattern to write application logic that runs on the mid-tier and controls access to data for queries, changes and custom operations. It also provides end-to-end support for common tasks such as data validation, authentication and roles by integrating with Silverlight components on the client and ASP.NET on the mid-tier.


From technical point of view (you can read more from its PDF documentation). It is a framework that provides patterns for writing LOB application logic and validation (by design). LOB applications use data that needs to flow across tiers. It may be created and used through basic CRUD operations or it may be accessed through domain-specific custom operations in encapsulated form such as an approval operation. In an internet application, there is a trust boundary between the client and the mid-tier server. Hence, it is important to have a stylized way to express what resources are available to the client tier and what operations are permitted. In addition to defining the resources and operations, a developer needs to validate the data as it is created or updated. The validation needs to run on the presentation tier for giving immediate feedback to the user and on the mid-tier for ensuring that the rules defined for the application are enforced. Thus, there may be common validation across the tiers as well as tier-specific validations that need to be integrated with the resources and operations.

v01

Lets look at the anatomy of a common Web application compared to RIA. With a RIA, most of the presentation logic moves to the client to improve the UX and to make effective use of local state. The result is additional work for the developer: defining a full-fledged services layer, new types and contracts to share etc. and continually evolving them as the app evolves on either end. A RIA isn't just a client app, but an "Internet" app that includes a server component. This is where RIA Service guidance will give you values – end-to-end pattern for this additional work. But remember, there is no perfect design/pattern, we can always improve it based on our requirements.

 

v02     v03

The first part of RIA Services pattern is to write a DomainService class. This represents your application domain, your application logic, or business logic, a set of operations – CRUD based or custom domain-specific, DAL specific, authorization and validation. A DomainService is optimized to be stateless - to respond to query requests and to change set processing requests.

v05 v06

The second part of the pattern is what we generate - the client-side data model - in a DomainContext class. This class represents the view of what the client can see. It contains lists of objects, one for each type exposed to the client, and it contains a set of load methods that roughly correspond to the queries exposed on the service. These load methods can be used to load objects into the corresponding lists. The individual objects and lists track changes and raise change notifications. The DomainContext can extract all the changes, create a change set and submit it to the service for processing and committing. A DomainContext is optimized for taking advantage of the stateful environment and to be a well-behaved citizen in a binding-centric presentation technology such as Silverlight or WPF.

Nikhil Kothari describe .NET RIA Services vision as RAD for RIA, which is I totally agreed and have the same sense.But there is one requirement here in Asia that we must consider – low bandwidth condition. This will rise new scenario - offline-enabled RIA application, means that local storage scenario on the DomainContext. The design challenges of all the offline-enabled applications are:

How to isolate client data layer

What is actually the data layer in RIA? It that the API that server exposes to AJAX? Gears provides a good vision on Server Data Layer and Local Data Layer, controlled by a Data Switch. Gears propose JavaScript DB API with SQLite to enable local data storage. It is a good project to start with SQL Compact Edition, however, but an ActiveXObject should be created for SQL Compact Edition/SQL Express/ESENT. Anyone interested here?

v07

Offline strategy – what features need offline scenario

This is the harder part for me, as closely related to our application scenarios. Let say, I want to design Physics e-learning application that contains RIA/Silverlight based simulation. Some numerical calculation should be done at client side and its results should be store locally in order to feed parameters for responsive and dynamic GUI animation. You might think that you would always want to use the local store since it is faster. However, there are many practical reasons why you may want or need to access the data on server instead. For instance:

- Data may be so transient in nature that it makes no sense to cache it. We will use Project Velocity for cache.
- Some Simulation's data makes sense only while online.
- The App may choose to store only the most frequently accessed data.
- Numerical computational and/or disk space requirements make it unfeasible to recreate the feature offline.

Typically, the optimal solution is to use the local store as much as possible, since it's usually faster than a remote connection. However, the more work an application does locally, the more code you need to write to implement the feature locally and to synchronize the corresponding data. There is a cost/benefit tradeoff to consider, and some features may not be worthwhile to support locally.


In fact, .NET RIA Service provide ready to extend set of capability that I like. But, a lot of work should be done to make it work like I want. That is my problem though :).

 

Application Modality

What is modality – feature to switch from online to offline states or vice versa. Modal applications usually indicated through some change in the user interface. The user is made aware of the state and participates in switching states in some manner. Modeless applications attempt to transition seamlessly between online and offline states, without significant UI changes. The user does not need to participate in switching states, which the application does automatically.

In a modal application, when the application is online it communicates with the server. When it's offline, it uses the local store. Data must be synchronized when the app switches between modes. The advantage of making a modal application is that it's “relatively simple” to implement and therefore a reasonable way to bootstrap the application to function offline. But some disadvantages are: The user must decide and remember to switch modes. Since the local store is not always up-to-date, it can't be used to improve the application's responsiveness when connected to the server.

 

Data Synchronization

What is the sync strategy for this kind of application? No matter which connection and modality strategy we use, the data in the local database will get out of sync with the server data. For example, local data and server data get out of sync when:

- The user makes changes while offline
- Data is shared and can be changed by external parties
- Data comes from an external source

There are many approaches to synchronization and none are perfect for all situations. We have Microsoft Sync Framework and Astroria Offline as candidates for reusable framework here (where I am still arguing). The most important thing for engineer to decide is manual or background synchronization. In manual sync user decides when to synchronize. It can be implemented simply by uploading all the old local data to the server, and then downloading a fresh copy from the server before going offline. In background sync, the application continuously synchronizes the data between the local data store and the server. This can be implemented by pinging the server every once in a while or better yet, letting the server push or stream data to the client. With Silverlight supports on WCF, this should be easy to implement.

v08

Well, the next step for me is to gather our detail requirements and see how current frameworks can help. I am still hacking the MSF, Astoria Offline, RIA Services, and Velocity. For sure, I will share with you if I found interesting things.

 

Have nice weekend without insomnia!.

 

Cheers – RAM

Share this post: | | | |

Project Velocity – Answer to Memcached?

Again, Zeddy is responsible for this, weekend insomnia! Regarding my previous post about Memcached, Zeddy pointed me to Project Velocity. This is exactly what I am looking for!. A highly scalable in-memory, distributed cache cluster in farm deployment, high availability and of course, concurrency ready and x64 support!. Applications can store any serializable CLR object without worrying about where the object gets stored. Scalability can be achieved by simply adding more cache servers on demand. “Velocity” also allows for copies of data to be stored across the cluster, thus protecting data against failures. “Velocity” can be configured to run as a service accessed over the network or can be run embedded with the distributed application.

 

Key Features 

"Velocity" consists of a cluster of servers that communicate with each other to form a single, unified application cache system. A client application can communicate with one or more of the servers in the cluster to perform cache operations such as Get, Put, or Remove. Multiple client applications can work with the same cached objects that use any of the servers in the cache cluster. The primary components of the physical model consist of the cache server, the cache host Windows service, the cache cluster, the PowerShell-based cache administration tool, and the cluster configuration storage location. The following diagram shows how all these elements relate.

 
v01

- Caches any serializable CLR object and provides access through simple cache APIs.
- Supports enterprise scale: tens to hundreds of computers – with unified cache view from clients
- Configurable to run as a service accessed over the network or run embedded with the application.
- Supports common cache configurations.
- Supports dynamic scaling by adding new nodes.
- Configurable number of backup copies to provide high availability.
- Automatic load balancing.
- Integration with administration and monitoring tools such as ETW, System Center, etc.
- Provides tight integration with ASP.NET to be able to cache ASP.NET session data
- Follows the cache-aside architecture (also known as Explicit Caching) for V1.
- Support for multiple client languages (for example, PHP, C#, C++, etc.).


Key Scenarios : Social Networking Site and Enterprise LOB

Not only for social networking scenarios, Velocity was designed to support enterprise application.

v02 v03

High Availability Model

The logical model of the cache cluster consists of named caches, regions, and cached objects. In the diagram, named caches span all the cache hosts in the cluster, but regions are limited to just one cache host in the cluster. The high availability feature supports continuous availability of your cached data by storing copies of cached data on separate cache hosts, as illustrated in the next diagram.

x01 x03

With high availability enabled on a multi-server cluster, your application can still retrieve its cached data if a cache server fails. A copy of each cached object or region is maintained on a separate cache host. The cache cluster manages maintenance of these copies and supplies them to your application if the primary copies are not available. No code changes are required to make your cache-enabled applications highly available.

All "Velocity" cache hosts use TCP/IP to communicate with each other and support the cache cluster. To function correctly, each cache server needs firewall exceptions to be configured for the cache host service. Three separate ports are used: the cluster port, the arbitration port, and the cache port.

 

Client Types

In order to store data in the cache on a cache cluster, the System.Data.Caching.CacheFactory.GetCache(System.String) method is used to return a Cache object for you to work with in application code. Once instantiated, this Cache object is referred to as the cache client.

There are two types of cache clients: routing and simple. Each cache client also has the option to store cached data locally, which is named local cache. These cache client options are specified in the application configuration settings.

 

x04

 

Deployment Models

Velocity” can be deployed as a service or embedded within the application. In cache service, client applications access the Velocity cache through client APIs (TCP/IP or pipes), just like Memcached.  In embedded mode, application (like ASP.NET) provides host for Velocity. The distributed cache runs as part of the application (or container) and the memory for the cache is shared with that of the application.

v05

Microsoft project code named "Velocity" offers many options for specifying and storing the configuration details that are used by the cache cluster and the client application. Cache-based applications have the option of specifying configuration information programmatically or by using an application configuration file. There are three options available for storing cluster configuration settings: in XML, in a SQL Server Compact data file, or in a SQL Server 2005 or 2008.

x05

 

Performance Test

Below is the test result for Velocity. The application consisted of simulating lots of users on many distributed client computers. Each user thread performs operations like Get, Put, and Remove on a 20K payload. It is a plot of  throughput as the load gradually increased and also as new servers were added. The throughput increases as the load increases until it reaches saturation and stabilizes. At this point, if we add more servers, load balancing kicks in and the throughput increases again and the response time goes down.

v06

Sources:

- Microsoft Project Code Named “Velocity”
- Microsoft Project Code Named "Velocity" Team Blog
- Velocity MSDN Documentation
- Download Velocity CTP 2 and Sample Codes 

 

Cheers – Risman Adnan

Share this post: | | | |

Memcached for Windows

Zeddy brought this to my table for a review (thanks for him!). Simply said, memcached is a proven distributed memory object caching system, very generic in nature, run in many platforms. People use that to speed up data driven web applications by alleviating database workload, Facebook, Flickr, Youtube, and many big giant social network sites are using memcached to serve million users. That’s why I think it is important to share here how you can take advantage from this technique as well. You can read from memcached documentation about its low level nature. In this post, I will show you how to start quickly.

 

Build memcached on Windows

First, you can download the latest libevent and memcached source codes from here. I am using version 1.2.6 and compile it using VC++ 2008. There are some *.txt documentations inside the source codes that explain clearly the  low level nature of its protocol and implementation. Launch your VC++ (you can use VC++ Express) then open the libevent project and then add memcached project in the same solution.

01

Make sure you compile libevent before and the location of compiled libevent.lib file. Locate that file in the linker property of memcached project. Looks like below:

02

Then you can build your memcached project. If you found compile time error on winsock extension (ws2tcpip.h), just delete the following codes then compile again. I hope you have no problem anymore.

INT
WSAAPI inet_ntop( __in INT Family, __in PVOID pAddr, __out_ecount(StringBufSize) PSTR pStringBuf );

Once you are done with compilation, you can launch memcached.exe for installation:

memcached –d install

Check on your Windows Service if the memcached service already installed. After that you can start memcached with some options (-help), for example if you want to allocate 2GB RAM and use port 11211:

memcached –d start –m 2024 –p 11211

Make sure to check memcached server if it is running well or not (TCP and UDP).

03

Using Memcached Manager

using this tool, you can manage your memcached server easily on Windows. You can also use this tool to generate client configuration file for enyim.com Memcached Client that you can use from client.

0405

 

How to use in ASP.NET

I am using Memcached Provider 1.2  from CodePlex. To shorten this post, I will write another post to guide you how to use Memcached Provider 1.2 with ASP.NET 3.5. However, you can use the two walkthrough articles from CodePlex if you really want to try earlier.

 

Have nice weekend!.

 

Ciao – RAM

Share this post: | | | |

Working with Qt Visual Studio 2008 Add-In

After built and configured your Qt 4.5 SDK, you can download and install qt-vs-addin-1.0.0-rc1.exe. This is a productivity tool that already integrated to Visual Studio 2008. Before you start using it, make sure you configure the location of your Qt build.

- Go to Qt menu in VS 2008 then choose Qt Options
- Set the location of your Qt build – directory where you can find bin-include-lib sub folders
01   02

- Test your Qt Designer and Linguist tools for form builder (*.ui) and Localization

03

Its a common form builder. Qt uses *.ui file to define form. You can add UI handler later in your C++ codes. Enjoy!

- Explore capabilities of Qt as your GUI toolkit from the samples

04 


Hope this helps!

 

Cheers – RAM

Share this post: | | | |

Qt 4.5 with Visual Studio 2008 (VC++ Express)

Qt is a cross platform GUI toolkit from Nokia. Its SDK is available under an LPGL license, it means we can use it free of charge to develop proprietary, commercial, closed-source software (thanks to Nokia).  However, the current LPGL release leaves some gaps in using Qt from within Visual Studio 2008. This is somewhat understandable given that Visual Studio integration was a commercial-only offering and Nokia has Qt Creator (and not Visual Studio).  My problem with Qt Creator is it uses MingGW (gcc) compiler which is nowhere near as good as VC++ 2008.  Also for Visual Studio background like me, who is not familiar with Qt creator, learning Qt Creator will take time. Thanks to Wiria and AK who brought Qt to my table for a review.

Qt GUI Toolkit is very useful to develop portable application (single source codes) for *NIX, MAC and Windows (including Windows 98 but not being tested for Windows 7).  In this post, I will show you how to start using Qt SDK only with Visual C++ 2008, you can use FREE Visual C++ 2008 express too.

Build VC++ Version of Qt

- Download and install qt-sdk-win-opensource-2009.01.1.
- Install the SDK to default directory, in my case C:\qt\2009.01. I didn;t install MingGW and Qt Creator as I don’t need.  
01

- Once QT and VisualStudio 2008 both are installed and ready, open the VisualStudio 2008 Command Prompt.
- Change directory to the Qt SDK installation folder in the Command Prompt. Usually it would be of the form "C:\Qt\2009.01\qt"
- Run Configure.exe to target platform win32-msvc2008. You can simply run by typing configure or using other options. For example:

   C:\Qt\2009.01\configure -no-sql-sqlite -no-qt3support -no-opengl -platform win32-msvc2008 
   -no-libtiff -no-dbus -no-phonon -no-phonon-backend -no-webkit

- Build Qt for use with Visual Studio 2008 by typing nmake
- Configure and nmake will take time, be patient enough. In my case, I started to build 19.17 PM and completed on 23.47 (4:30 hours)!
- Once completed, Qt SDK is ready for Visual Studio 2008.

03 09

Build and Run the Qt SDK Samples

Once your Qt SDK is ready to be used you can see samples directory in your Qt SDK installation (usually, C:\Qt\2009.01\qt\examples) and then open the examples.sln file from your Visual Studio 2008. If double-clicking .sln file does not work, open it manually from your Visual Studio. There are a lot of samples from Qt SDK.

12

If you build and debug – calculator project of instance – you will notification that the application because QtGuid4.dll was not found.

10  11

To let Visual Studio knows the include and library files of Qt, add their paths to the:  

Visual Studio Tools|Options|Projects and Solutions|VC++ Directories

section. Typically you would want to add C:\Qt\2009.01\qt\include to the Include, C:\Qt\2009.01\qt\lib to the libraries and C:\Qt\2009.01\qt\bin to the Executables sections. Also you can add C:\Qt\2009.01\qt\bin to the Path System variable so that the Qt dlls get probed correctly when loaded from your application.

13

A lot of Qt samples inside the SDK. Try to build and run those sample applications to see how powerful Qt as GUI Toolkit.

14

Using Qt’s Visual Studio 2008 Plug-in

You can try Visual Studio add-in RC version (this will be a commercial product) to get integrated Qt templates and visual IDE features in Visual Studio 2008. I will not cover in this post, but this tools is promising. I will show you in my other Qt-VC++ post.

04 

Hope this helps!

 

Ciao – RAM

Share this post: | | | |