Posts Tagged ‘Photo Album’
iTraveller–A photo uploader for Flickr and Facebook
Today I released my new version of iTraveller. I was working on this version for last 2 weeks, it’s a complete rewrite of old iTraveller, with several added usability features. Now iTraveller can upload photos to Facebook as well.
What is iTraveller
iTraveller is a desktop application to organize your photos and upload them to Flickr and Facebook. iTraveller ease the organizing of photos. We can mark which photos should go to Flickr or Facebook and add to appropriate set/album in offline. Once you decide to upload, click the Synchronize button leave the rest to iTraveller. While iTraveller synching with Flickr/Facebook, you go for a walk or grab a cup of coffee or you can continue creating your local category or adding more photos to Flickr Set/Facebook Album. Another useful feature of iTraveller is, you can see the photo comments posted by other users in offline mode.
I used Calburn micro to separate my View and View Model. Also I extensively used Async CTP to make a very responsive UI. I will give more details of my architecture in a different post.
Technologies used
Let’s see some screen shots of iTraveller
Home Screen
First step in using iTraveller is creating a Local set. You can create a local set by dragging and dropping your photos from Windows explorer to the Local Set box at the left top pane. When you drag and drop photos to Local Set pane, it will ask whether you need to create a new set or add to an existing category. You can see the photos inside a category by clicking on category and all the photos inside the category will show in the bottom pane of the application.
Humans are prone to errors, if you accidentally create any Local Set, don’t worry select the Local Category and just press delete key in your keyboard, you are done. The local set is deleted (only from iTraveller). In some scenarios you may need to delete only couple of photos from a local set. You can do that as well, select the photo from the thumbnail view and press delete key in your keyboard.
Mark for Flickr Uploading
You need to authorize iTraveller to connect to Flickr. Clicking on the synchronize button for the first time will popup a dialog box and follow the step to authorize it. After the authorization iTraveller will download the Flickr sets from Flickr.
Now let’s see how we can upload photo(s) to Flickr. Select a Flickr set in the right hand side of the application. Click on the Flickr set you want to upload your photo. It will open a tab as shown below.
Drag the required photo from the thumbnail to the Queue box and drop it. You also can give the Title and Description for the photo. By default visible to public is unchecked, if you want your photo to be visible to public then check the ‘Is Visible to Public’. You can put as many photo to queue. Once you are connected to internet, click the synchronize button just below the Flickr set to upload it to Flickr.
Mark for Facebook Uploading
You can upload the photo to Facebook just like the way we did for Flickr, instead of Flickr set you should select a Facebook Album on the right hand side. First time synching with Facebook needs your authorization. After you allow iTraveller to communicate with Facebook account, it will download all your Facebook Albums. iTraveller will take some time to get the photo albums from Facebook.
Once the synchronization is over and iTraveller got any new comments from Facebook or Flickr then it will open an Updates tab for you, so that you wont miss any updates.
You can provide different title and description for Facebook and Flickr Photos. These text is also searchable.
Create Flickr Set Through iTraveller
You can also create Flickr Set through iTraveller. Click on the ‘+’ icon, just above the Flickr Set Thumbnails. iTraveller will popup a window to enter new Flickr Set and Set description, click save once you done. While synchronizing with Flickr, the set will get created in Flickr. To create a Flickr Set successfully, at least one photo should be their in the upload queue of the newly created Set. The thumbnail of the newly created Set will be displayed only after the synchronization.
Search
iTraveller have built in searching feature. It enables the user to search for photos that satisfy the search text. The search will only look for photos added to iTraveller. User can search for the photo name, Title, Description, Flickr Title, Flickr Description, Facebook Description and Photo Comments. Enter the Search Text at the text box located at the top right corner of the application.
As a photographer you will have a lot of photos in your hardisk. Always our big problem is how to find a photo with some attributes. iTraveller can help you out, you can add a title and description and just save it. When ever you want to find it just enter the search text and click search. Another reason for search is, you may wanted to find the photos that got comments from your friends (either from Facebook or Flickr), let’s say Kevin or Sony. Go ahead and enter Kevin;Sony in the search text and hit Search button. iTraveller will give you the list of photos commented by Kevin or Sony.
You can see the preview of the Searched photo by double clicking on the thumbnail.
Comments
In the preview if any photo have any comments then a View comments button will shown in the preview tab as shown below.
Clicking on the comments button will open a new comment tab as shown below. You can leave this tab open and the comments will get changed when you navigate from one photo to another through the thumbnail.
Preview Tab
Preview tab will display the image in preview mode. Below the preview image, you can see below buttons
More feature will get added in next release, but needs motivation. Use my iTraveller and Motivate me .
Upgrade to New Versions
In coming days I will release iTraveller with bug fixes and new enhancements.
If you have already installed iTraveller 2.0, then you can upgrade to latest version. Please take a backup of the following files and folders before you update to new version, it will help you keep your data safe. In case of any issue after the update like not showing your Local set or any thing, you always can put the backuped up files back to the iTraveller folder.
You can find the the below files/folders here ‘Program Files\iTraveller\iTraveller’
Files/Folders to Backup
- ITravellerData.data (File)
- FacebookThumbnails (Folder)
- FlickrThumbnails (Folder)
- SearchIndex (Folder)
Download
I uploaded the application here. Install it and let me know your feed back.
Photo Album using Silverlight, Caliburn Micro and Mongo DB
In this post I am going to explain a small Photo Album I created using Silverlight. With this app I also joined the band wagon of NoSQL movement. I used MongoDB as my backend for this application. This application is still in the development mode. I will add more functionality to this application like Public comments, admin module, etc. Currently this app shows the functionality of admin module, like upload photo, delete photo, etc. The application uses MVVM pattern and as usual I used Caliburn Micro here. I used several framework in the app as shown below
- Silverlight
- Caliburn Micro
- Reactive Extension
- MongoDB
- WCF
In this application I used Reactive Extension (Rx) to perform Async service calls. I felt the Rx is a very powerful tool to perform Async calls. The one good advantage of Rx is you can place Async calls in Thread Pool and push it to dispatcher once the call completed. All these can be done in one or two lines of code. This way the UI will always responsive.
This is my first experiment with Mongo db, I can say it’s very easy to use and fast. There are several Mongo drivers for .NET available in the market and is free. I used the one from here. The advantage of Mongodb is, I don’t want to write code to map my POCO to tables. You can directly persist your entity to Mongodb. You can find a lot of article about Mongo db in internet
I don’t think I need to explain Caliburn Micro in detail here. I wrote a post that gives a brief introduction to Caliburn Micro.
Running MongoDB
Below are the steps to run MongoDB
- Download the latest version of Mongo db from Mongodb.org
- Extract the zip file to C:\Mongodb
- Create a Folder called data in C drive
- Create a subfolder called db inside data
- Goto command prompt and change directory to C:\Mongodb
- Type Mongod in the command prompt and hit enter
Now the Mongodb will listen to the default port and our app can make request. There are different ways of hosting Mongodb, you can get more details from Mongodb site.
The App
Below is the screen shot of my application.
This page will display the thumbnails of uploaded images. We can see the bigger image by clicking on the thumbnail as show below.
The preview will display in a Windowless popup. We can delete the photo by clicking the Trash icon bottom right side of the image. We can upload images by clicking on the upload icon at the top right of the window. The upload page will get displayed as shown below.
When the user upload the image, the application will create a thumbnail version of the image and store along with the original image in the server. This helps to transfer and load the thumbnail page.
Let’s see the entity used in this application.
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.IO; using System.Drawing; using System.Drawing.Imaging; namespace PicturePortfolio.Domain { [DataContract] public class Photo { private const int THUMBWIDTH = 100; private const int THUMBHEIGHT = 100; public Photo() { } public Photo(PhotoPersistanceObject persistanceObject) { this.Name = persistanceObject.Name; this.Description = persistanceObject.Description; this.ID = persistanceObject.ID; this.PublicComments = persistanceObject.PublicComments; this.GUID = persistanceObject.GUID; this.LoadThumbNail(); } [DataMember] public int ID { get; set; } [DataMember] public string GUID { get; set; } [DataMember] public byte[] ImageFile { get; set; } [DataMember] public byte[] ImageThumbNail { get; set; } [DataMember] public string Description { get; set; } [DataMember] public string Name { get; set; } [DataMember] public List<PublicComment> PublicComments { get; set; } public PhotoPersistanceObject GetDataForPersistance() { PhotoPersistanceObject photoPersistance = new PhotoPersistanceObject(); photoPersistance.ID = this.ID; photoPersistance.Name = this.Name; photoPersistance.ImagePath = "/test/" + this.Name; photoPersistance.Description = this.Description; photoPersistance.PublicComments = this.PublicComments; if (string.IsNullOrEmpty(this.GUID) == true) photoPersistance.GUID = Guid.NewGuid().ToString(); else photoPersistance.GUID = this.GUID; return photoPersistance; } public void SaveImage() { if (this.ImageFile == null) throw new ArgumentNullException(); try { byte[] buffer = this.ImageFile.ToArray(); MemoryStream memStream = new MemoryStream(); memStream.Write(buffer, 0, buffer.Length); this.SaveOrginalImage(memStream); this.SaveImageThumbNail(memStream); } catch (Exception ex) { throw; } } public void LoadImage() { string fullFileName = this.GetFileFullPath(); this.ImageFile = this.GetImageAsByteArray(fullFileName); } public void LoadThumbNail() { string fullFileName = this.GetFilePath() + this.ThumbNailImageName; this.ImageThumbNail = this.GetImageAsByteArray(fullFileName); } #region Private Methods private void SaveOrginalImage(MemoryStream memStream) { System.Drawing.Image imgToSave = System.Drawing.Image.FromStream(memStream); imgToSave.Save(this.GetFileFullPath(), ImageFormat.Jpeg); } private void SaveImageThumbNail(MemoryStream memStream) { Image img = Image.FromStream(memStream); Image.GetThumbnailImageAbort myCallback = new Image.GetThumbnailImageAbort(ThumbnailCallback); Image imageToSave = img.GetThumbnailImage (THUMBWIDTH, THUMBHEIGHT, myCallback, IntPtr.Zero); imageToSave.Save(this.GetFilePath() + ThumbNailImageName, System.Drawing.Imaging.ImageFormat.Jpeg); } private static bool ThumbnailCallback() { return false; } private byte[] GetImageAsByteArray(string fullFileName) { FileInfo fil = new FileInfo(fullFileName); if (fil.Exists == true) { FileStream fileStream = new FileStream(fullFileName, FileMode.Open, FileAccess.Read); byte[] buffer = new byte[fileStream.Length]; fileStream.Read(buffer, 0, (int)fileStream.Length); fileStream.Close(); return buffer; } else return null; } private string GetFilePath() { string filePath = AppDomain.CurrentDomain.BaseDirectory + "Images\\"; return filePath; } private string GetFileFullPath() { string fullPath = this.GetFilePath() + this.Name; return fullPath; } private string ThumbNailImageName { get { string fileName = this.Name.Substring(0, Name.LastIndexOf(".")); return fileName + "_thumb.jpg"; } } #endregion } }
The entity class is very simple. I may need to give a brief explanation about PhotoPersistanceObject used in the entity class. The PhotoPersistanceObject is used for persisting meta data of the image like Name, description etc. You might think that why cant we save Photo object directly, yes I also thought the same way but their is some issue with Mongodb driver I used here. It’s not supporting byte array neither binary, It allows to store binary in Mongodb but throws error while retrieving. So I used a trimmed down version of Photo entity to persist the data.
Async Calls using Thread Pool
As I told before one of the cool feature of Reactive Extension is it’s advantage of making async calls in thread pool. Even if we place async calls in separate thread in Silverlight still it will block the UI to load if we do it in page load. Because the our thread will work in UI. We can achieve the placing calls in thread pool using Rx. See the below code.
private void GetAllThumbnails() { var func=Observable.FromEvent<GetAllPhotosCompletedEventArgs>(_service,"GetAllPhotosCompleted") .ObserveOn(Scheduler.ThreadPool);
_service.GetAllPhotosAsync();
func.ObserveOn(Scheduler.Dispatcher)
.Select(result => result.EventArgs.Result)
.Subscribe(s => this.ParseThumbnails(s.ToList()));
}
As you can see the service calls are doing in ThreadPool. Once the call is done we will change the ObserveOn to Dispatcher, other wise cross thread expection will throw. I call the GetAllThumbnails in the constructor of a ViewModel. As we are doing the call in Threadpool my UI will loaded without any delay, once the call is completed the thumbnails will start display in the listbox.
Mongodb Repository
I wrote a Generic repository similar to the one I wrote for EF. You can find the generic repository for EF in one of my post. Below is the Generic repository class for Mongodb.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using MongoDB; using MongoDB.Linq; using MongoDB.Connections; namespace PicturePortfolio.Persistance { public class GenericRepository:IRepository { public GenericRepository() { } Mongo _mongoDb=null; public IMongoCollection<TEntity> GetQuery<TEntity>() where TEntity : class { if (_mongoDb == null) { _mongoDb = new Mongo(); _mongoDb.Connect(); } return DataBase.GetCollection<TEntity>(typeof(TEntity).Name + "s"); } IMongoDatabase db = null; private IMongoDatabase DataBase { get { if (db == null) { db = _mongoDb.GetDatabase("Protfolio"); } return db; } } public IList<TEntity> GetAll<TEntity>() where TEntity : class { try { return GetQuery<TEntity>().Linq().ToList<TEntity>(); } catch (Exception ex) { throw; } } public IList<TEntity> Find<TEntity>(System.Linq.Expressions.Expression<Func<TEntity, bool>> criteria) where TEntity : class { return this.GetQuery<TEntity>().Linq<TEntity>().Where(criteria).ToList<TEntity>(); } public TEntity Single<TEntity>(System.Linq.Expressions.Expression<Func<TEntity, bool>> criteria) where TEntity : class { return this.GetQuery<TEntity>().Linq<TEntity>().Single(criteria); } public TEntity First<TEntity>(System.Linq.Expressions.Expression<Func<TEntity, bool>> criteria) where TEntity : class { return this.GetQuery<TEntity>().Linq<TEntity>().First(criteria); } public void Add<TEntity>(TEntity entity) where TEntity : class { this.GetQuery<TEntity>().Save(entity); } public void Delete<TEntity>(TEntity entity) where TEntity : class { this.GetQuery<TEntity>().Remove(entity); } public void Delete<TEntity>(IEnumerable<TEntity> entites) where TEntity : class { throw new NotImplementedException(); } public void Update<TEntity>(TEntity entity, TEntity old) where TEntity : class { this.GetQuery<TEntity>().Update(entity, old, UpdateFlags.None, true); } } }
This app is in very primitive stage. you may need to do lot of refactoring to make use in real scenario. I will work on this app and upload it to codeplex some times later.
Download the current source here.