about 11 years ago
Sample Photo Sharing App using CFS Python SDK
Now we almost one week away from the submission date, we would like to show you one sample application here to help you warp up the integration with CFS.
CloudFS PhotoShare for PythonPhotoShare is a photo sharing app built upon the Bitcasa CloudFS platform SDK that is designed to simplify development of cloud based data storage applications. This PhotoShare app allows you to upload new photos, manage friends and share your uploaded photos with your friends and “like” your favorite photos. The source code for PhotoShare app is open sourced and available to download on Github.
SOURCE CODEThe code examples discussed in this document will cover major features available in the SDK and CloudFS services. This document intends to help you get started in developing your own applications using Bitcasa SDK and CloudFS API services that impersonate typical file system with files and folders.
PHOTOSHARE APP INSTALLATION GUIDEIn order to get this sample app to work in your environment first you will have to sign up for a Bitcasa account via here. Please choose appropriate account type to utilize the CloudFS services to the maximum. To obtain the CloudFS services for your PhotoShare app, you have to create an application in the Bitcasa account.
SETTING UP A NEW CLOUDFS APPFollow the instructions given below to create a new application and connect with CloudFS to manage application data.
- Login to Bitcasa Account using the credentials you used to sign up.
- Click on “New App” button under “Your Apps” panel to create a new application.
- In “New Application” window, enter your application name, select a client type and click “Create”.
- Under “Your Apps” panel, click on the newly created application to obtain Client ID and Client Secret Key.
- Obtain the API Endpoint displayed on the top of the page.
- Details of API Endpoint, Client ID and Client Secret Key are required to use Bitcasa SDK.
You’ll have to create a Bitcasa end-user account in order to login to the PhotoShare application. Application features depend on separate file system belonging to each PhotoShare app end-user. Also you need a separate end-user account to enable and maintain the sharing features of this application. From here onwards you are going to refer to this account as App Account If you are a prototype account holder you are allowed to create maximum of five end-user accounts and follow the steps given below to do so.
- Click on “New test account” under “Add Test Users” panel to create a test account.
- In “New Test Account” window enter username and password. Note them down for future use.
If you are a paid account user, you can create unlimited number of end-user accounts directly using the “Register” feature available in the PhotoShare app. To set this up, you have to obtain the Admin ID and the Admin Secret data from the Bitcasa account and configure the User signup REST API accordingly. Follow the below given steps to obtain these data.
- Click on “Admin” button under “Your Apps” panel.
- Obtain Admin ID and Admin Secret data displayed in the “Admin” window.
To get the PhotoShare app fully running, you have to setup the User signup REST API. Please refer to the next section to setup the API.
CONFIGURING THE USER SIGNUP REST APIUser signup REST API facilitates user creations feature for paid account users and authentication for all users. Follow the instructions given in the below link to setup the User signup REST API. Use the API endpoints, Client ID, Client secret, Admin ID and Admin Secret data obtained from the instructions given above to configure the API. https://github.com/bitcasa/CloudFS-Python-User-Signup Once you have this API up and running you can proceed to setting up the PhotoShare app. Next section will guide you to step-by-step to setup and run the PhotoShare app.
CONFIGURING THE PHOTOSHARE APPIn order to setup and configure PhotoShare-Python app you will need the CloudFS API Endpoint, Client ID, Client secret, App Account Username and App Account Password data. User Authentication End Point and User Registration End Point can be obtained by running the User signup REST API and by retrieving the host name of the API which can be used to configure the URLs of http://<hostname>/api/authenticate/ and http://<hostname>/api/user/ respectively. You can follow the instructions given in the above section to setup User signup REST API Once you have the User signup REST API up and running, follow the instructions in README file that can be found in the following location to setup the PhotoShare app. https://github.com/bitcasa/PhotoShare-Python If you are a prototype account user you can create a test account in Bitcasa web console and update App Account Username and App Account Password directly in the configuration file. However if you are a paid account user follow the steps given below to obtain the App account username and App accountpassword Enter all values except the App account username and App account password (Leave them empty) in configuration file and start the app.
- Once the app is launched, click on the “Register” button in the login screen and create a login. Note down username and password.
- Exit from the app
- Go to the configuration file and enter the newly created App account username and App account password
This app account creation only needs to be done once before being published to the other users due to the fact that the App account handles the app file system operations and file sharing functions. All data shared among the users will be handled by this account.
LOGIN TO THE PHOTOSHARE APPAfter the initial configurations you are now ready to use the PhotoShare app. Launch and login to the app using your end-user account credentials. Again, if you are a Prototype account user, you can create up to five end-user test accounts through the Bitcasa Admin account. You can create unlimited end-user accounts through the “Register” feature if you are a paid account user. When you login as an end-user, your authentication information which comprises of the username and password are posted to the User signup REST API. This API handles the authentication of the credentials submitted which in turn returns the CloudFS authentication token that can be used in the PhotoShare app to carry out CloudFS related operations. Note: It is required that you set up the python django REST API for user management to enable the user creation and user authentication of this app; Failing which the app users will not be able to proceed past the login screen. Please refer to section “Configuring the User Signup REST API” to configure User Signup API correctly. This document will now take you through the design and implementation of this PhotoShare app. This will give you a better understanding of how Bitcasa SDK and CloudFS services can be used in any app you intend to build.
DESIGNThe folder structure of the PhotoShare app that will be created on CloudFS is depicted in the below illustration. We are using an App account to store shared data that is used by the application in addition to the individual end-user accounts. User accounts will have a root level folder named photoshare with three subfolders named public, private and shared. Public folder contains the users shared photos while the Private folder holds user’s photos which are not shared. We will generate a shared key for public folder using the CloudFS share feature. The shared folders will therefore contain the folders named after the user’s friends username with the respective friends public folder mapped using the CloudFS share feature. Therefore we can illustrate the typical shared folder structure of a user named user1 who has two friends named user2 and user3 as follows. There is a metadata file for each user that is stored in the app accounts file system under the users folder. This file contains the sharing key for the public folder, friends list and other user details. This is done so that the app can access this information if the user logs in from a different device. There is another file called “meta” at the root of the app account that contains the number of likes for each photo and the users who liked the photos.
IMPLEMENTATIONWe will be keeping the CloudFS account details and app account credentials we configured above in a common configuration file which can be changed according to your account details by editing “/photoshare/settings/common.py” to appropriate values as given below.
- CLOUD_FS_SETTINGS = {
- ‘CLOUD_FS_ID’: ‘xxxxxxx’,
- ‘CLOUD_FS_SECRET’:‘xxxxx’,
- ‘CLOUD_FS_BASE’: ‘xxxxx.cloudfs.io’,
- ‘USER_API_BASEURL’:‘http://xxx.xx.x.xx:xxxx’,
- }
- CLOUD_FS_COMMON_SETTINGS = {
- ‘USERNAME’: ‘xxxxxx’,
- ‘PASSWORD’: ‘xxxxxx’
- }
The user signup REST API should be configured and running for the authentication request to be successful. When the client app makes a request to the user signup API, the API will in turn make a request to the Bitcasa CloudFS API to get the security token which we can use for CloudFS related operations. Client app should send the username and password in the request as shown below.
- headers = {‘content-type': ‘application/json’}
- user_data = {“username”: username,
- “password”: password}
- common_user_data = {“username”: CLOUD_FS_COMMON_SETTINGS[‘USERNAME’],
- “password”: CLOUD_FS_COMMON_SETTINGS[‘PASSWORD’]}
- url = CLOUD_FS_SETTINGS[‘USER_API_BASEURL’] + “/api/authenticate/”
- try:
- user_status = requests.post(url, data=json.dumps(user_data),
- headers=headers)
- common_user_status = requests.post(
- url, data=json.dumps(common_user_data), headers=headers)
- if user_status.status_code == 200:
- user_info = json.loads(user_status._content)
- request.session[USER_AUTH_TOKEN_KEY] = user_info[‘auth_token’]
- else:
- csrfContext = RequestContext(request)
- data = {‘login_error': True}
- return render_to_response(‘photoshare/login.html’,
- data, csrfContext)
The response string contains a json object with the auth_token and message. If the authentication is successful server returns the auth token. Our app stores this auth token in shared preferences for future usage which we can use to interact with the CloudFS. Before we can call any of the SDK methods we need to create a Bitcasa session.
- cloud_fs_session = UserSession(CLOUD_FS_SETTINGS[‘CLOUD_FS_BASE’],
- CLOUD_FS_SETTINGS[‘CLOUD_FS_ID’],
- CLOUD_FS_SETTINGS[‘CLOUD_FS_SECRET’],
- auth_key)
SDK will use the stored auth token when we call API methods. When the auth token is set for our App Account all the operations are carried out in the app account file system. We need to make sure the required folder structure for our app exists in our app account file system for our app to function correctly. We do this by checking if all the required folders are present in the filesystem using the SDK as described below and create the folders if they are missing. Firstly we list the items in the root folder.
- file_system = cloudfs_session.get_filesystem()
- root_folder = file_system.root_container()
- root_list = root_folder.list()
If we want to list the items in a subfolder returned from above request, we call the list method for that folder.
- for rootItem in root_list:
- if rootItem.name == PHOTO_SHARE_FOLDER_NAME:
- folder_present = True
- photo_share_folders = rootItem.list()
- break
If a required folder is not found, we need to create them using the CloudFS SDK methods as shown below.
- photoshare_folder = root_folder.create_folder(PHOTO_SHARE_FOLDER_NAME)
- public_folder = photoshare_folder.create_folder(PUBLIC_FOLDER_NAME)
If you do not have an account to log in to the app you can click on the registration link and create a login.
USER REGISTRATIONThis section applies only if you are using a bitcasa paid account. When you use a paid account it enables you to create users through the app itself. Source code for python django REST server can be found in our code repository here. First, you need to host it in a server and set the <hostname> in REST endpoint mentioned above. This REST server contains an endpoint which you can use to create an user. Below are the values that you need to post to create a user for the PhotoShare app. username – The username for the new user password – The password for the new user first_name – The first name for the new user last_name – The last name for the new user The source code for creating a user is illustrated below.
- class UserRegister(TemplateView):
- “””
- used for registering users
- “””
- template_name = “photoshare/register.html”
- def post(self, request):
- username = request.POST.get(‘username’)
- password = request.POST.get(‘password’)
- first_name = request.POST.get(‘firstname’)
- last_name = request.POST.get(‘lastname’)
- headers = {‘content-type': ‘application/json’}
- data = {“username”: username,
- “password”: password,
- “first_name”: first_name,
- “last_name”: last_name}
- url = CLOUD_FS_SETTINGS[‘USER_API_BASEURL’] + “/api/user/”
- status = requests.post(url, data=json.dumps(data), headers=headers)
- if status.status_code == 201:
- return HttpResponseRedirect(reverse(‘login’))
- elif status.status_code == 400:
- csrfContext = RequestContext(request)
- r = json.loads(status.content)
- data = {‘user_exists': True, ‘message’ : str(r[‘message’])}
- return render_to_response(‘photoshare/register.html’, data,
- csrfContext)
- else:
- csrfContext = RequestContext(request)
- data = {‘user_creation_error': True}
- return render_to_response(‘photoshare/register.html’, data,
- csrfContext)
The response string contains a json object with the success flag and message. When a user enters their credentials to login we capture the username and password and post it to the user signup REST API for authentication. This is implemented in the same way as we have done when authenticating the app account. If the user authentication is successful we also save the photoshare user auth token in the app shared preferences. We save this photoshare user auth token in the credential object as discussed above so that we can carry out CloudFS operations on the user file system. We need to make sure that the required folders exists in user file system and if not create them for the user to use the PhotoShare app without any issues, as we did for the app account. Photoshare users shared photos will be saved in the users public folder and in order for the other users to access the public folder, it has to be shared. When we create the public folder for a user for the first time we need to share it and obtain the shared key using the CloudFS Share functionality. It is done as shown below.
- file_system = user_session.get_filesystem()
- public_share_folder = file_system.create_share(public_folder)
- public_share_folder_auth_key = public_share_folder.data[‘share_key’]
For other users to access the shared items of the current user they need to know the shared key of the current user. Therefore we will be saving a user file with the current users login name in the app accounts file system. We will be storing the shared key and the friends list which will be discussing later on. Since we are trying to upload a JSON file into the app account file system, we first need to update the credential object with the app account auth token. Frequently accessed data such as access tokens absolute parent path ids of important folders are stored in the web session.
- user_status = requests.post(url, data=json.dumps(user_data),
- headers=headers)
- if user_status.status_code == 200:
- user_info = json.loads(user_status._content)
- request.session[USER_AUTH_TOKEN_KEY] = user_info[‘auth_token’]
Next we create the json file in the local file system which contains the shared key for the users public folder and the friend related information which we will discuss later on.
- {
- “friends”: [{
- “email”: “dhanushka@calcey.com”
- },
- {
- “email”: “lahiru@calcey.com”
- }],
- “sharedKey”: “xxxxxxxx”
- }
After we create the JSON file according to the above format we upload it to the CloudFS so that it can be used for share and friends management in the future even if the user logs in from a different device.
- if users_folder is not None:
- data_dict = {‘friends': [], ‘sharedKey': public_share_folder_auth_key}
- jason_dict = json.dumps(data_dict, ensure_ascii=False)
- new_file_name = username
- new_file_content = ‘{}’.format(jason_dict)
- users_folder.upload(new_file_content, custom_name=new_file_name,
- custom_mime=’test/mime’, data_inline=True)
When a photoshare user logs in to the application for the first time we create a file with the users username in app account file system as mentioned above. These file names represents all current photoshare users and you can add remove or search for friends in Friends screen. Friends of a photo share users are saved in the user file inside the app account file system.
- {
- “friends”: [{
- “email”: “dhanushka@calcey.com”
- },
- {
- “email”: “lahiru@calcey.com”
- }],
- “sharedKey”: “tT-FcSRJTUwzm4N6dohtYidjcUjxL49EpwGHX8xvFT4-“
- }
To download the friends file we first list the user files in app account file system. When creating the initial folders in the app account file system we have stored the parent path id of the users folder in shared preferences which we will use to list the app users.
- def _get_user_file_from_list(user_items_list, username):
- content = {}
- user_file = None
- for user_item in user_items_list:
- if user_item.data[‘name’] == username:
- user_file = user_item
- break
- file_path = MEDIA_ROOT + ‘/’ + ‘downloads’
- if not os.path.exists(file_path):
- os.mkdir(file_path)
- if user_file is not None:
- try:
- user_file.download(file_path, username)
- except:
- e = sys.exc_info()[0]
- with open(file_path + ‘/’ + username, ‘r’) as file:
- read_data = file.read()
- if read_data is not None and read_data != ”:
- content = json.loads(str(read_data))
The user file will be available in our local file path once the download function completes. When adding or removing friends you need to modify the above mention JSON object and reupload it. We have discussed the file upload implementation in the above Share section. It is required to change auth token to app account when accessing the app accounts file system and to the photoshare users auth token when accessing the photoshare users file system. The below image shows the current friend list, add and remove buttons.
UPLOADING IMAGESWe give the users the ability to either take photos using the device camera or access the image gallery to upload photos to the PhotoShare app. We enable the user to set the share options of their photos while uploading or later on in the history screen. Users shared photos are saved in the public folder where as the private photos are saved in the private folder. The uploading process can be illustrated using app screenshots as follows.
HISTORYThe History tab displays all of the current users private and public images ordered by their created timestamp and we have enabled the user to change the shared status of these pics individually. First we have to retrieve all the current logged in users uploaded photos and for this we get all the items in private folder and public folder using the list method available in the CloudFS SDK.
- for rootItem in root_list:
- if rootItem.name == PHOTO_SHARE_FOLDER_NAME:
- folder_present = True
- photo_share_folders = rootItem.list()
- break
- photo_share_images = []
- if not folder_present:
- _create_folder_structure(root_folder)
- else:
- for photo_share_folder in photo_share_folders:
- if photo_share_folder.name == PUBLIC_FOLDER_NAME:
- public_files = photo_share_folder.list()
- elif photo_share_folder.name == PRIVATE_FOLDER_NAME:
- private_files = photo_share_folder.list()
In the History screen we enable the users to change the share settings of existing photos by making them public or private. When the shared status of a photo changes we need to move the file to relevant folder in the users CloudFS file system. For this purpose we use the move functionality available in the SDK.
- if status is True:
- private_filelist = private_folder.list()
- for private_file in private_filelist:
- if private_file.id == photo_id:
- file_system.move(private_file, public_folder)
- else:
- public_filelist = public_folder.list()
- for public_file in public_filelist:
- if public_file.id == photo_id:
- file_system.move(public_file, private_folder)
The Timeline screen of the app displays all the private and public images of the currently logged in user and all the public images of the users friends according to the upload time. The users are also able to ‘like’ photos they find interesting using the Timeline. How the timeline is represented in the app is given in the screenshot below. To get the friend’s public images, first we need to download current photoshare user file from app accounts file system. This file contains all the friends for this user. Next we download all the user files for friends of the currently logged in user. When a user logs in to the app for the first time, their initial folders are created, the public folder is shared and the shared key is stored in their user file in the app account. So when we download a user file it contains the shared key for their public folder. To access the items in a shared folder we first need to receive the shared folder to our file system.
- if shared_folder is not None:
- for shared_friend in shared_friends_list:
- share_folder = shared_folder.create_folder(
- shared_friend[‘email’], exists=ExistValues.overwrite)
- share_object = user_file_system.share_from_share_key(
- shared_friend[‘shared_key’])
- share_object.receive(share_folder,
- exists=ExistValues.overwrite)
This API call copies the shared folder and shared folder contents to the current users file system. Then we can use the list methods to retrieve the files.
LIKESYou can like a photo to show your approval of the uploaded content using the Like button under each photo in the photostream/timeline. The behavior of the like button is illustrated below. Likes for each item is saved in a JSON file in the root folder of the app account. This JSON file contains the number of likes and the users who liked it as given in the example below.
- {
- “likes”: [{
- “users”: [“dilshan@calcey.com”],
- “itemId”: “FjHKpcQ8SC-Mtd7ge0tDkw”,
- “total”: 1
- },
- {
- “users”: [“dilshan@calcey.com”],
- “itemId”: “tbwIKpvVSFWOfQgM_UTKvA”,
- “total”: 1
- }]
- }
Looking for more information? Here are few places that should help:
Need a CloudFS account? Sign up today.
And as always, if you have any questions or issues, we're more than happy to help. Feel free to post a question within the Discussions section and we'll get back to you as soon as we can. If you'd like to talk to our Product Manager for more information regarding CloudFS or application ideas, please email directly at:
- Product Manager: kwang@bitcasa.com
