I have just found out that Amazon has released "Kindle Cloud Reader" this month (August 2011), which is a web-based e-Book application for iPad and other mobile devices.
Amazon Kindle Cloud Reader
Release by amazon.com Inc. in August 2011
Web-based e-Book reader application for iPad and other devices
Amazon has acquired cloudreader.com in July 2011 (reference)
This is a little bit annoying to me, because I have an iPad application called "CloudReaders pdf,cbz,cbr", which is also an e-Book reader application. I have released this application when Apple came up with the first iPad (April 2010), and have more than three quarter million users world-wide.
CloudReader pdf, cbz, cbr
Release by Satoshi Nakajima (me) in April 2010
Native e-Book reader application for IPad and iPhone
I have acquired cloudreaders.com in March 2010.
Even though there is a difference (web-based vs. native), both apps are e-Book readers mostly targeting iPad. Names and logos are too similar, and I see a conflict.
Since I am not a big fan of legal battle, I have decided to just post this entry for now to find out how other people perceive this event (not necessarily from legal point of view, but as a regular business practice as well).
August 13, 2011 | Permalink | Comments (38)
In my previous post, I talked about the app-id/secret-id pair you get when you register your app to Facebook. The documentation provided by Facebook, however, is a little bit unclear about the usage model of this pair under various SDKs. In this post, I am going to write some tips for App Engine developers who are building Facebook applications using this app-id/secret-id pair.
In a nut shell,
(1) It is possible to build your application only with JavaScript SDK. In this case, you don't need a secret key to access Facebook API.
(2) This JavaScript-only above approach is fine for client-only application (such as a small widget application that presents Facebook news feed for the user), but not appropriate if you want to use Facebook API as the authentication/identification mechanism to access your database (such as per-user storage).
(3) The best approach is a hybrid application, which uses JavaScript API for log-in and various non-critical API calls (such as fetching the list of friends to present it to the user), but uses Python SDK for authentication and some critical API calls.
I built fruence.com with this hybrid architecture. Here are some code from it:
main.js (client-side):
window.fbAsyncInit = function() {
SNBinder.get('/api/appid', null, true, function(result, data) {
FB.init({appId: result.appId, status: true, cookie: true,
xfbml: true});
FB.Event.subscribe('auth.login', function(response) {
main.init();
});
FB.Event.subscribe('auth.logout', function(response) {
main.login();
});
main.init();
});
};
SNBinder.get is a wrapper of HTTP-GET and fetches the app-id by accessing /api/appid, then calls FB.init() to initialize the API. Notice that you don't need the secret-key in this case, and you don't want to (otherwise, it is no longer a secret).
When the user clicks the log-in button (provided by Facebook) and authorizes the access to the app (this UI will be provided by Facebook as well), the application receives the 'auth.login' event.
Then (within the main.init()), the application accesses '/api/user' with a cookie attached by Facebook API, which will execute the following code on the server side.
gdispatch.route(lambda: ('/api/user', ApiUserHandler))
class ApiUserHandler(webapp.RequestHandler):
@login_required
def post(self):
self.response.out.write('{"success":true, "result":%s}' % self.current_user.json)
The login-required decorator performs the server-side authentication.
def login_required(original_func):
def decorated_func(rh, **kw):
if User.current_user(rh):
return original_func(rh, **kw)
else:
return rh.response.out.write('{"success":false, "login_required":true}')
return decorated_func
User.current_user is defined as follows.
class User(db.Model):
....
@classmethod
def current_user(cls, rh):
if not hasattr(rh, "current_user"):
rh.current_user = None
(id, secret) = cls.facebook_ids(rh)
cookie = facebook.get_user_from_cookie(rh.request.cookies, id, secret)
if cookie:
key_name = "fb:"+cookie["uid"]
user = cls.get_by_id(key_name)
if not user:
graph = facebook.GraphAPI(cookie["access_token"])
profile = graph.get_object("me")
user = cls(key_name=str(key_name),
name=profile["name"],
profile_url=profile["link"],
access_token=cookie["access_token"],
cached_logs=[])
user.put()
else:
if user.access_token != cookie["access_token"]:
user.access_token = cookie["access_token"]
user.put()
rh.current_user = user
return rh.current_user
Notice that you need to give the secret-id to Facebook API (facebook.get_user_from_cookie) in this case, but your secret-id is secure because this code is on the server side.
January 07, 2011 | Permalink | Comments (2)
There is a good discussion around "how to use Facebook log-in from Google App Engine", but there is one tricky issue dealing with Facebook's app-id/secret-key pair, which is generated for a specific URL.
If you hard-code the id/key pair for your applicatin's URL in your source code, you won't be able to debug it until you deploy.
I've encountered this issue when I was developing fruence.com (groupware for Facebook users), and came up with a simple work around.
Here is the code (the actual app-id and secret-code are replaced by "{app-id}","{secret-code}" to hide them):
@memoize
def _facebook_ids(domain):
dict = {
"www.fruence.com": ("{app-id}","{secret-code}"),
"un-url.appspot.com": ("{app-id}", "{secret-code}"),
"staging.latest.un-url.appspot.com": ("{app-id}", "{secret-code}"),
"localhost:8104": ("{app-id}", "{secret-code}"),
"10.0.1.100:8104": ("{app-id}","{secret-code}"),
}
return dict[domain]
class User(db.Model):
...
@classmethod
def facebook_ids(cls, rh):
url = rh.request.url
(a, domain, b, c, d, e) = urlparse.urlparse(url)
return _facebook_ids(domain)
@classmethod
def current_user(cls, rh):
if not hasattr(rh, "current_user"):
rh.current_user = None
(id, secret) = cls.facebook_ids(rh)
cookie = facebook.get_user_from_cookie(rh.request.cookies, id, secret)
if cookie:
...
Notice that I have generated five app-id/secret-key pairs, including staging environment and local environment (localhost as well as LAN), which allows me to debug my application in any environment.
To see this mechanism in action, access my app from both www.fruence.com and un-url.appspot.com. Each URL has an unique app-id/secret-key pair, but both are connected to the same version of the application. As a Facebook user, you need to "approve" each app separately because they are two different apps from Facebook's point of view (and cookies are associated with each domain), but my application recognize you correctly because the "current_user" is associated with facebook id.
Notice that Facebook allows you to register local URLs like "http://localhost:8104". As far as I can tell, Facebook generates a unique app-id/secret-key pair for each registration, therefore, you don't need to worry about collision.
Viva Google App Engine!
January 06, 2011 | Permalink | Comments (3)
Unlike relational databases, the bigtable is a schema-less database. Therefore, adding new properties is quite easy. You just need to add new property definition(s) like this:
class Foo(db.Model):
existingProperty = db.StringProperty()
newProperty = db.StringProperty()
When you db.Model.get() one of existing entities (which are created before you define this new property), the newProperty will be automatically set to None (because the default value of db.StringProperty is None).
This is because the bigtable is schema-less database, and it does not require all entities to have the same set of properties. When you see existing entities using the Datastore Viewer, you see <missing> as the value of this new property.
It's very easy, isn't it?
There is one caveat, though. You can't query against those missing properties. The following query will NOT find those entities with <missing> property value.
query = Foo.all().filter('newProperty', None)
This is a little bit counter-intuitive because you actually see "None" as the property value when you db.Model.get() those entities. You need to understand that this "None" is not coming from the database, but simply coming from the default value of the property definition.
Then, how can I find those entities with missing properties? This is a common questions among app-engine newbies, you see a lot of discussions around this topic, such as:
AppEngine: Query database for records with <missing> value
There is even an article about this topic written by one of Google engineers.
This article is, however, a bit outdated (written in 2008 before the introduction of taskqueue) and not generalized.
After creating quite a few appengine aplications, I came to the conclusion that it is much easier to use schema-versioning technique instead.
Here is the quick summary of this technique.
1. Define the "version" property when you define a model object
class Foo(db.Model):
VER = 1
version = db.IntegerProperty(default = VER)
existingProperty = db.StringProperty()
2. Increment the version number when you change the schema
class Foo(db.Model):
VER = 2
version = db.IntegerProperty(default = VER)
existingProperty = db.StringProperty()
newProperty = db.StringProperty()
3. Define a migration method, which typically looks like this (notice that we don't need to explicitly set the values of new properties as long as the default is appropriate - None in this case)
class Foo(db.Model):
...
@classmethod
def migrate(cls):
query = cls.all().filter("version <", cls.VER)
items = query.fetch(1)
if items:
item = items[0]
item.version = cls.VER
item.put()
deferred.defer(cls.migrate)
4. Execute this method from your own admin screen (via admin url)
gdispatch.route(lambda: ('/admin/api/migrate', AdminMigrateHandler, 'admin')
class AdminMigrateHandler(webapp.RequestHandler):
def get(self):
Foo.migrate()
return self.responce.out.write('{"success:true"})
Once you run this migration code, the property values of existing entities will become None (or the default value you specified in the property definition), and you can query against those property values.
December 23, 2010 | Permalink | Comments (3)
Last week was a quite exciting week for me because of various discussion around Google App Engine, such as "Goodbye Goole App Engine" and "Why we're really happy with AppEngine". I also have some opinions about it, but I much prefer to write code than participating in this kind of debate.
Instead, I am going to share my solution to deal with one of those AppEngine specific issues - an occasional DeadlineExceededError. This solution may not apply to everybody, because I am just solving this issue for my own application - but I hope sharing this kind of idea helps the community as well as myself.
To deal with this issue, I came up with a following decorator (yes, I am writing my code in Python, and decorator is my favorite feature of this language).
@memoize
def catch_dee(ratio):
def decorator(original_func):
def decorated_func(rh, *args, **kwargs):
try:
if os.environ['SERVER_SOFTWARE'].startswith('Development/') and random.random() < ratio:
raise DeadlineExceededError
return original_func(rh, *args, **kwargs)
except DeadlineExceededError:
user_agent = rh.request.headers['User-Agent']
rh.response.set_status(500)
return rh.response.out.write('{"success":false, "retry":true, "errors":["deadline_exceeded_error"]}')
return decorated_func
return decorator
And this is a piece of code from my application (sorry for additional decorators unrelated to this post -- all my code looks like this).
gdispatch.route(lambda: ('/blob/delete', DeleteHandler))
class DeleteHandler(webapp.RequestHandler):
@mysession.require_login
@config.catch_dee(0.333)
@gdispatch.kwargs
def post(self, ids):
...
This decorator does two things.
(1) It catches the DeadlineExceededError exception, and reports this error in the format my application (iPhone application) understands.
(2) It randomly (33.3%) generates this exception when I run this application under SDK, which helps me to test my client-side of code, which performs some retries.
I hope this post helps somebody. Feedback is welcome especially if you notice something I am missing.
Cheer to Google App Engine developers!
December 03, 2010 | Permalink | Comments (5)
One of our users has pointed our that the "support page" of CloudReaders(TM) is not in English, and it's hard to give me feedback. It was my mistake, and here is the place for all of CloudReaders(TM) to give me feedback, encouragements and feature requests (no refund-request please - CloudReaders(TM) is free!).
April 28, 2010 | Permalink | Comments (380)
Adobe made a couple of interesting announcements today.
October 05, 2009 | Permalink | Comments (11)
One of Seattle start-ups, iLike, was acquired by MySpace for $20.5 million ($13.5 to buy the company, and $6 million to retain talents). While I am glad to see an exit for Hadi (we used to work together in Microsoft building Internet Explorer), this price tag bothers me a lot (I run Big Canvas Inc., which offers "Big Canvas PhotoShare" - a real-time photo sharing social networking service application to iPhone users).
August 20, 2009 | Permalink | Comments (0)
August 14, 2009 | Permalink | Comments (3)