[download] Add exception handling
authorDeqing Huang <deqing.huang@intel.com>
Fri, 13 Sep 2013 01:45:30 +0000 (09:45 +0800)
committerDeqing Huang <deqing.huang@intel.com>
Thu, 26 Sep 2013 03:46:01 +0000 (11:46 +0800)
This commit added exception handling and bug fixing according to
tct-download-tizen-tests.

download/download_api.js
download/download_context.cc
download/download_context.h
download/download_context_desktop.cc
download/download_context_mobile.cc
download/download_utils.h
examples/download.html

index 8402d8d..289837d 100644 (file)
@@ -14,6 +14,32 @@ var postMessage = function(msg) {
   extension.postMessage(JSON.stringify(msg));
 };
 
+var asValidString = function(o) {
+  return (typeof o == "string") ? o : "";
+}
+
+var ensureType = function(o, expected) {
+  if (typeof o != expected) {
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+  }
+}
+
+var ensureHas = function(o) {
+  if (typeof o == "undefined") {
+    throw new tizen.WebAPIException(tizen.WebAPIException.NOT_FOUND_ERR);
+  }
+}
+
+var getNetworkTypeThrowsError = function(networkType) {
+  if (typeof networkType == "undefined") {
+    return "ALL";
+  } else if (networkType in AllowDownloadOnNetworkType) {
+    return networkType;
+  } else {
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+  }
+}
+
 var errorMap = {
   "DOWNLOAD_ERROR_NONE" : {
     code : tizen.WebAPIException.NOT_SUPPORTED_ERR,
@@ -137,50 +163,84 @@ var errorMap = {
   },
 };
 
+var AllowDownloadOnNetworkType = {
+  "ALL": 0,
+  "CELLULAR": 1,
+  "WIFI": 2,
+};
+
 extension.setMessageListener(function(msg) {
   var m = JSON.parse(msg);
-  if (m.cmd == "DownloadReplyProgress") {
-    startListeners[m.uid].onprogress(m.uid, m.receivedSize, m.totalSize);
+  var id = parseInt(m.uid);
+  if (isNaN(id) || typeof startListeners[id] === "undefined") {
+    return;
+  } else if (m.cmd == "DownloadReplyProgress") {
+    if (typeof startListeners[id].onprogress !== "undefined") {
+      var receivedSize = parseInt(m.receivedSize);
+      var totalSize = parseInt(m.totalSize);
+      startListeners[id].onprogress(id, receivedSize, totalSize);
+    }
   } else if (m.cmd == "DownloadReplyComplete") {
-    startListeners[m.uid].oncompleted(m.uid, m.fullPath);
+    if (typeof startListeners[id].oncompleted !== "undefined") {
+      startListeners[id].oncompleted(id, m.fullPath);
+    }
   } else if (m.cmd == "DownloadReplyPause") {
-    startListeners[m.uid].onpaused(m.uid);
+    if (typeof startListeners[id].onpaused !== "undefined") {
+      startListeners[id].onpaused(id);
+    }
   } else if (m.cmd == "DownloadReplyCancel") {
-    startListeners[m.uid].oncanceled(m.uid);
+    if (typeof startListeners[id].oncanceled !== "undefined") {
+      startListeners[id].oncanceled(id);
+    }
   } else if (m.cmd == "DownloadReplyNetworkType") {
-    networkTypeCallbacks[m.uid](m.networkType);
+    networkTypeCallbacks[id](m.networkType);
   } else if (m.cmd == "DownloadReplyMIMEType") {
-    mimeTypeCallbacks[m.uid](m.mimeType);
+    mimeTypeCallbacks[id](m.mimeType);
   } else if (m.cmd == "DownloadReplyFail") {
-    startListeners[m.uid].onfailed(m.uid,
+    startListeners[id].onfailed(id,
         new tizen.WebAPIError(errorMap[m.errorCode].code,
                               errorMap[m.errorCode].message,
                               errorMap[m.errorCode].name));
   }
 });
 
-tizen.DownloadRequest = function(url, destination, filename, networkType) {
+tizen.DownloadRequest = function(url, destination, fileName, networkType) {
+  Object.defineProperty(this, "networkType", {
+    get: function() { return this.networkTypeValue; },
+    set: function(type) {
+      if (type in AllowDownloadOnNetworkType) {
+        this.networkTypeValue = type;
+      } else {
+        throw new tizen.WebAPIException(tizen.WebAPIException.INVALID_VALUES_ERR);
+      }
+    }
+  });
+
+  if (!(this instanceof tizen.DownloadRequest)) {
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
+  }
   this.url = url;
-  this.uid = (++currentUID).toString();
-  typeof destination != "undefined" && (this.destination = destination);
-  typeof filename != "undefined" && (this.filename = filename);
-  typeof networkType != "undefined" && (this.networkType = networkType);
+  this.uid = ++currentUID;
+  this.destination = asValidString(destination);
+  this.fileName = asValidString(fileName);
+  this.networkType = getNetworkTypeThrowsError(networkType);
   this.httpHeader = {};
 }
 
 exports.start = function(request, listener) {
   if (!(request instanceof tizen.DownloadRequest)) {
-    console.log("tizen.download.start(): argument of invalid type " + typeof(request));
-    return;
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
   }
   requests[request.uid] = request;
-  startListeners[request.uid] = listener;
+  if (typeof listener != "undefined") {
+    exports.setListener(request.uid, listener);
+  }
   postMessage({
     "cmd": "DownloadStart",
     "url": request.url,
     "uid": request.uid,
     "destination": request.destination,
-    "filename": request.filename,
+    "fileName": request.fileName,
     "networkType": request.networkType,
     "httpHeader": request.httpHeader,
   });
@@ -188,16 +248,21 @@ exports.start = function(request, listener) {
 }
 
 exports.setListener = function(downloadId, listener) {
-  if (typeof requests[downloadId] == "undefined") {
-    throw new tizen.WebAPIException(tizen.WebAPIException.NOT_FOUND_ERR);
+  ensureType(downloadId, "number");
+  ensureType(listener, "object");
+  if (listener === null) { // null is also an object, so we need double check
+    throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
   }
-  startListeners[request.uid] = listener;
+  for (var property in listener) {
+    ensureType(listener[property], "function");
+  }
+  ensureHas(requests[downloadId]);
+  startListeners[downloadId] = listener;
 }
 
 exports.pause = function(downloadId) {
-  if (typeof requests[downloadId] == "undefined") {
-    throw new tizen.WebAPIException(tizen.WebAPIException.NOT_FOUND_ERR);
-  }
+  ensureType(downloadId, "number");
+  ensureHas(requests[downloadId]);
   postMessage({
     "cmd": "DownloadPause",
     "uid": downloadId,
@@ -205,9 +270,8 @@ exports.pause = function(downloadId) {
 }
 
 exports.resume = function(downloadId) {
-  if (typeof requests[downloadId] == "undefined") {
-    throw new tizen.WebAPIException(tizen.WebAPIException.NOT_FOUND_ERR);
-  }
+  ensureType(downloadId, "number");
+  ensureHas(requests[downloadId]);
   postMessage({
     "cmd": "DownloadResume",
     "uid": downloadId,
@@ -215,9 +279,8 @@ exports.resume = function(downloadId) {
 }
 
 exports.cancel = function(downloadId) {
-  if (typeof requests[downloadId] == "undefined") {
-    throw new tizen.WebAPIException(tizen.WebAPIException.NOT_FOUND_ERR);
-  }
+  ensureType(downloadId, "number");
+  ensureHas(requests[downloadId]);
   postMessage({
     "cmd": "DownloadCancel",
     "uid": downloadId,
@@ -225,16 +288,15 @@ exports.cancel = function(downloadId) {
 }
 
 exports.getDownloadRequest = function(downloadId) {
-  if (typeof requests[downloadId] == "undefined") {
-    throw new tizen.WebAPIException(tizen.WebAPIException.NOT_FOUND_ERR);
-  }
+  ensureType(downloadId, "number");
+  ensureHas(requests[downloadId]);
   return requests[downloadId];
 }
 
 exports.getNetworkType = function(downloadId, callback) {
-  if (typeof requests[downloadId] == "undefined") {
-    throw new tizen.WebAPIException(tizen.WebAPIException.NOT_FOUND_ERR);
-  }
+  ensureType(downloadId, "number");
+  ensureType(callback, "function");
+  ensureHas(requests[downloadId]);
   networkTypeCallbacks[downloadId] = callback;
   postMessage({
     "cmd": "DownloadGetNetworkType",
@@ -242,21 +304,33 @@ exports.getNetworkType = function(downloadId, callback) {
   });
 }
 
-exports.getMIMEType = function(downloadId, callback) {
-  if (typeof requests[downloadId] == "undefined") {
-    throw new tizen.WebAPIException(tizen.WebAPIException.NOT_FOUND_ERR);
-  }
-  mimeTypeCallbacks[downloadId] = callback;
-  postMessage({
+exports.getMIMEType = function(downloadId) {
+  ensureType(downloadId, "number");
+  ensureHas(requests[downloadId]);
+  var reply = JSON.parse(_sendSyncMessage({
     "cmd": "DownloadGetMIMEType",
     "uid": downloadId,
-  });
+  }));
+  if (reply["error"] != "DOWNLOAD_ERROR_NONE") {
+    switch (reply["error"]) {
+    case "DOWNLOAD_ERROR_INVALID_PARAMETER":
+      throw new tizen.WebAPIException(tizen.WebAPIException.SYNTAX_ERR);
+      break;
+    case "DOWNLOAD_ERROR_ID_NOT_FOUND":
+      throw new tizen.WebAPIException(tizen.WebAPIException.NOT_FOUND_ERR);
+      break;
+    default:
+      throw new tizen.WebAPIException(tizen.WebAPIException.NOT_SUPPORTED_ERR);
+    }
+  } else {
+    delete reply["error"];
+    return reply["mimeType"];
+  }
 }
 
 exports.getState = function(downloadId) {
-  if (typeof requests[downloadId] == "undefined") {
-    throw new tizen.WebAPIException(tizen.WebAPIException.NOT_FOUND_ERR);
-  }
+  ensureType(downloadId, "number");
+  ensureHas(requests[downloadId]);
   var reply = JSON.parse(_sendSyncMessage({
     "cmd": "DownloadGetState",
     "uid": downloadId,
index 75442c0..8142697 100644 (file)
@@ -74,8 +74,6 @@ void DownloadContext::HandleMessage(const char* message) {
     HandleGeneral(v, download_cancel, "HandleCancel");
   else if (cmd == "DownloadGetNetworkType")
     HandleGetNetworkType(v);
-  else if (cmd == "DownloadGetMIMEType")
-    HandleGetMIMEType(v);
   else
     fprintf(stderr, "Not supported async command %s\n", cmd.c_str());
 }
@@ -93,6 +91,8 @@ void DownloadContext::HandleSyncMessage(const char* message) {
   std::string cmd = v.get("cmd").to_str();
   if (cmd == "DownloadGetState")
     HandleGetState(v);
+  else if (cmd == "DownloadGetMIMEType")
+    HandleGetMIMEType(v);
   else
     fprintf(stderr, "Not supported sync command %s\n", cmd.c_str());
 }
@@ -208,8 +208,8 @@ void DownloadContext::HandleStart(const picojson::value& msg) {
 
   d->url = msg.get("url").to_str();
   d->destination = GetFullDestinationPath(msg.get("destination").to_str());
-  std::string filename = msg.get("filename").to_str();
-  d->filename = (filename == "null") ? std::string() : filename;
+  std::string fileName = msg.get("fileName").to_str();
+  d->fileName = fileName;
 
   std::string network_type = msg.get("networkType").to_str();
   if (network_type == "CELLULAR")
@@ -230,8 +230,8 @@ void DownloadContext::HandleStart(const picojson::value& msg) {
                                  static_cast<void*>(args)), args);
   CHECK(download_set_url(d->downloadID, d->url.c_str()), args);
   CHECK(download_set_destination(d->downloadID, d->destination.c_str()), args);
-  if (!d->filename.empty()) {
-    CHECK(download_set_file_name(d->downloadID, d->filename.c_str()), args);
+  if (!d->fileName.empty()) {
+    CHECK(download_set_file_name(d->downloadID, d->fileName.c_str()), args);
   }
   CHECK(download_set_network_type(d->downloadID, d->networkType), args);
 
@@ -300,20 +300,25 @@ void DownloadContext::HandleGetNetworkType(const picojson::value& msg) {
 }
 
 void DownloadContext::HandleGetMIMEType(const picojson::value& msg) {
-  int downloadID;
-  DownloadArgs* args;
-  if (!GetDownloadID(msg, downloadID, &args))
-    return;
-
+  std::string uid;
+  int downloadID = -1;
+  std::string retStr("DOWNLOAD_ERROR_NONE");
   char* mimeType = 0;
-  CHECK(download_get_mime_type(downloadID, &mimeType), args);
 
   picojson::value::object o;
-  o["cmd"] = picojson::value("DownloadReplyMIMEType");
-  o["uid"] = picojson::value(args->download_uid);
-  o["mimeType"] = picojson::value(mimeType);
+  if (!GetID(msg, uid, downloadID)) {
+    retStr = "DOWNLOAD_ERROR_ID_NOT_FOUND";
+  } else {
+    int ret = download_get_mime_type(downloadID, &mimeType);
+    if (ret != DOWNLOAD_ERROR_NONE) {
+      retStr = EnumToPChar(ret);
+    } else {
+      o["mimeType"] = picojson::value(mimeType);
+    }
+  }
+  o["error"] = picojson::value(retStr);
   picojson::value v(o);
-  args->context->api_->PostMessage(v.serialize().c_str());
+  api_->SetSyncReply(v.serialize().c_str());
 
   if (mimeType)
     free(mimeType);
index 5a1cee4..517d271 100644 (file)
@@ -66,7 +66,7 @@ class DownloadContext {
     std::string uid;
     std::string url;
     std::string destination;
-    std::string filename;
+    std::string fileName;
     download_network_type_e networkType;
     std::string httpHeader;
 
@@ -92,7 +92,8 @@ class DownloadContext {
 
   // FullDestPath = HomePath + DestPath
   // TODO(hdq): This depends on filesystem api?
-  std::string GetFullDestinationPath(const std::string destination) const;
+  const std::string GetFullDestinationPath(const std::string destination) const;
+  const std::string GetRealLocation(const std::string& destination) const;
 
   bool GetDownloadID(const picojson::value& msg,
                      int& downloadID, DownloadArgs** args);
index 1e4b36f..35781f9 100644 (file)
 
 std::string DownloadContext::GetFullDestinationPath(
     const std::string destination) const {
-  std::string folder = (destination == "null") ? std::string() : destination;
-
   std::string path(getenv("HOME"));
   if (path.empty()) {
     struct passwd password_entry;
-    struct passwd *password_entry_ptr;
+    struct passwdpassword_entry_ptr;
     char buf[1024];
     if (!getpwuid_r(getuid(), &password_entry,
                     buf, sizeof buf, &password_entry_ptr)) {
       path = password_entry.pw_dir;
     }
   }
-  return (path + '/' + folder);
+  return (path + '/' + destination);
 }
index 2fd9095..5544a4a 100644 (file)
@@ -2,17 +2,49 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
 #include "download/download_context.h"
 
-std::string DownloadContext::GetFullDestinationPath(
-    const std::string destination) const {
-  std::string folder = (destination == "null") ? std::string() : destination;
+const std::string DownloadContext::GetRealLocation(
+    const std::string& destination) const {
+  typedef std::map<std::string, std::string> LocationMap;
+  static const LocationMap::value_type data[] = {
+     LocationMap::value_type("documents", "Documents"),
+     LocationMap::value_type("downloads", "Downloads"),
+     LocationMap::value_type("images", "Images"),
+     LocationMap::value_type("music", "Sounds"),
+     LocationMap::value_type("videos", "Videos"),
+  };
+  static const LocationMap locations(data, data + sizeof data / sizeof data[0]);
+  LocationMap::const_iterator location = locations.find(destination);
+  if (location == locations.end()) {
+    return destination;
+  } else {
+    return location->second;
+  }
+}
 
+const std::string DownloadContext::GetFullDestinationPath(
+    const std::string destination) const {
   // TODO(hdq): User should be able to choose store to external storage
   //            i.e. /opt/storage/sdcard/Downloads
   std::string path("/opt/usr/media/");
-  if (folder.empty()) {
-    return (path + "Downloads/");
+  std::string location = destination;
+  if (destination.empty()) {
+    location = "Downloads";
+  }
+  std::string default_path = path+location;
+  path += GetRealLocation(location);
+
+  // Create path if not exist
+  struct stat path_stat;
+  if (stat(path.c_str(), &path_stat) != -1
+      || mkdir(path.c_str(), 0777) != 0) {
+    path = default_path;
   }
-  return (path + folder);
+
+  return path;
 }
index 0e3a9c8..523b5e7 100644 (file)
@@ -75,23 +75,23 @@ const char* EnumToPChar(int error) {  // enum download_error_e
 const char* EnumToPChar(download_state_e state) {
   switch (state) {
   case DOWNLOAD_STATE_NONE:
-    return "DOWNLOAD_STATE_NONE";
+    return "NONE";
   case DOWNLOAD_STATE_READY:
-    return "DOWNLOAD_STATE_READY";
+    return "READY";
   case DOWNLOAD_STATE_QUEUED:
-    return "DOWNLOAD_STATE_QUEUED";
+    return "QUEUED";
   case DOWNLOAD_STATE_DOWNLOADING:
-    return "DOWNLOAD_STATE_DOWNLOADING";
+    return "DOWNLOADING";
   case DOWNLOAD_STATE_PAUSED:
-    return "DOWNLOAD_STATE_PAUSED";
+    return "PAUSED";
   case DOWNLOAD_STATE_COMPLETED:
-    return "DOWNLOAD_STATE_COMPLETED";
+    return "COMPLETED";
   case DOWNLOAD_STATE_FAILED:
-    return "DOWNLOAD_STATE_FAILED";
+    return "FAILED";
   case DOWNLOAD_STATE_CANCELED:
-    return "DOWNLOAD_STATE_CANCELED";
+    return "CANCELED";
   default:
-    return "DOWNLOAD_UNKNOWN_STATE";
+    return "UNKNOWN_STATE";
   }
 }
 
index 3e48baf..11b09a0 100644 (file)
@@ -70,7 +70,7 @@ var url1 = "http://ipv4.download.thinkbroadband.com/200MB.zip";
 var url2 = "https://developer.tizen.org/sites/all/themes/tizen_theme/logo.png";
 // Test downloading with specific HTTP header
 var url_httpheader = "http://download.tizen.org/tools/README.txt";
-var urls = { "1" : url1, "2" : url2, "3" : url_httpheader }
+var urls = { 1 : url1, 2 : url2, 3 : url_httpheader }
 
 var r1, id1, r2, id2;
 var paused1=false;
@@ -78,7 +78,8 @@ var paused2=false;
 
 handle("bnstart1", "Start Download1", function() {
   r1 = new tizen.DownloadRequest(url1); // Test saving to default folder
-  id1 = tizen.download.start(r1, listener);
+  id1 = tizen.download.start(r1);
+  tizen.download.setListener(id1, listener); // Test setListener()
   urls[id1] = url1;
   output.value += "> asked to start download " + url1 + " to default location.\n";
 });
@@ -205,10 +206,6 @@ handle("bnnetworktype2", "GetNetworkType of Download2", function() {
   tizen.download.getNetworkType(id2, networkTypeCallback);
 });
 
-var mimeCallback = function(mimeType) {
-  output.value += "> received mimeType: " + mimeType + "\n";
-}
-
 handle("bnmimetype1", "GetMIMEType of Download1", function() {
   if (typeof id1 == "undefined") {
     output.value += "> Download1 is not started yet.\n";
@@ -216,7 +213,11 @@ handle("bnmimetype1", "GetMIMEType of Download1", function() {
   }
 
   output.value += "> asked to get MIME type of download1, id:" + id1 + "\n";
-  tizen.download.getMIMEType(id1, mimeCallback);
+  try {
+    output.value += "> return: " + tizen.download.getMIMEType(id1) + "\n";
+  } catch (err) {
+    output.value += "> received exception: " + err.name + "\n";
+  }
 });
 
 handle("bnmimetype2", "GetMIMEType of Download2", function() {
@@ -226,7 +227,11 @@ handle("bnmimetype2", "GetMIMEType of Download2", function() {
   }
 
   output.value += "> asked to get MIME type of download2, id:" + id2 + "\n";
-  tizen.download.getMIMEType(id2, mimeCallback);
+  try {
+    output.value += "> return: " + tizen.download.getMIMEType(id2) + "\n";
+  } catch (err) {
+    output.value += "> received exception: " + err.name + "\n";
+  }
 });
 
 handle("bngetrequest1", "Get download request 1", function() {