Sfoglia il codice sorgente

add & clean up documentation

also a small improvement for URL error logging, variable naming, and argument joining
jomo 10 anni fa
parent
commit
abaf3f77aa
7 ha cambiato i file con 81 aggiunte e 56 eliminazioni
  1. 3 3
      lib/cache.js
  2. 2 2
      lib/cleaner.js
  3. 37 23
      lib/helpers.js
  4. 10 10
      lib/logging.js
  5. 21 12
      lib/networking.js
  6. 2 2
      lib/renders.js
  7. 6 4
      lib/skins.js

+ 3 - 3
lib/cache.js

@@ -64,7 +64,7 @@ exp.get_redis = function() {
 
 
 
 
 // updates the redis instance's server_info object
 // updates the redis instance's server_info object
-// callback contains error, info object
+// callback: error, info object
 exp.info = function(callback) {
 exp.info = function(callback) {
   redis.info(function (err, res) {
   redis.info(function (err, res) {
 
 
@@ -95,7 +95,7 @@ exp.info = function(callback) {
 // sets the timestamp for +userId+ and its face file's (+hash+) date to the current time
 // sets the timestamp for +userId+ and its face file's (+hash+) date to the current time
 // if +temp+ is true, the timestamp is set so that the record will be outdated after 60 seconds
 // if +temp+ is true, the timestamp is set so that the record will be outdated after 60 seconds
 // these 60 seconds match the duration of Mojang's rate limit ban
 // these 60 seconds match the duration of Mojang's rate limit ban
-// +callback+ contains error
+// callback: error
 exp.update_timestamp = function(rid, userId, hash, temp, callback) {
 exp.update_timestamp = function(rid, userId, hash, temp, callback) {
   logging.log(rid + "cache: updating timestamp");
   logging.log(rid + "cache: updating timestamp");
   sub = temp ? (config.local_cache_time - 60) : 0;
   sub = temp ? (config.local_cache_time - 60) : 0;
@@ -143,7 +143,7 @@ exp.remove_hash = function(rid, userId) {
 
 
 // get a details object for +userId+
 // get a details object for +userId+
 // {skin: "0123456789abcdef", cape: "gs1gds1g5d1g5ds1", time: 1414881524512}
 // {skin: "0123456789abcdef", cape: "gs1gds1g5d1g5ds1", time: 1414881524512}
-// +callback+ contains error, details
+// callback: error, details
 // details is null when userId not cached
 // details is null when userId not cached
 exp.get_details = function(userId, callback) {
 exp.get_details = function(userId, callback) {
   // get userId in lower case if not null
   // get userId in lower case if not null

+ 2 - 2
lib/cleaner.js

@@ -8,7 +8,7 @@ var redis = cache.get_redis();
 var exp = {};
 var exp = {};
 
 
 // compares redis' used_memory with cleaning_redis_limit
 // compares redis' used_memory with cleaning_redis_limit
-// callback contains error, true|false
+// callback: error, true|false
 function should_clean_redis(callback) {
 function should_clean_redis(callback) {
   cache.info(function(err, info) {
   cache.info(function(err, info) {
     if (err) {
     if (err) {
@@ -28,7 +28,7 @@ function should_clean_redis(callback) {
 }
 }
 
 
 // uses `df` to get the available fisk space
 // uses `df` to get the available fisk space
-// callback contains error, true|false
+// callback: error, true|false
 function should_clean_disk(callback) {
 function should_clean_disk(callback) {
   df({
   df({
     file: __dirname + "/../" + config.faces_dir,
     file: __dirname + "/../" + config.faces_dir,

+ 37 - 23
lib/helpers.js

@@ -15,11 +15,15 @@ function get_hash(url) {
   return hash_pattern.exec(url)[0].toLowerCase();
   return hash_pattern.exec(url)[0].toLowerCase();
 }
 }
 
 
-function store_skin(rid, userId, profile, details, callback) {
+// gets the skin for +userId+ with +profile+
+// uses +cache_details+ to determine if the skin needs to be downloaded or can be taken from cache
+// face and face+helm images are extracted and stored to files
+// callback: error, skin hash
+function store_skin(rid, userId, profile, cache_details, callback) {
   networking.get_skin_url(rid, userId, profile, function(err, url) {
   networking.get_skin_url(rid, userId, profile, function(err, url) {
     if (!err && url) {
     if (!err && url) {
       var skin_hash = get_hash(url);
       var skin_hash = get_hash(url);
-      if (details && details.skin === skin_hash) {
+      if (cache_details && cache_details.skin === skin_hash) {
         cache.update_timestamp(rid, userId, skin_hash, false, function(err) {
         cache.update_timestamp(rid, userId, skin_hash, false, function(err) {
           callback(err, skin_hash);
           callback(err, skin_hash);
         });
         });
@@ -60,11 +64,15 @@ function store_skin(rid, userId, profile, details, callback) {
   });
   });
 }
 }
 
 
-function store_cape(rid, userId, profile, details, callback) {
+// gets the cape for +userId+ with +profile+
+// uses +cache_details+ to determine if the cape needs to be downloaded or can be taken from cache
+// the cape - if downloaded - is stored to file
+// callback: error, cape hash
+function store_cape(rid, userId, profile, cache_details, callback) {
   networking.get_cape_url(rid, userId, profile, function(err, url) {
   networking.get_cape_url(rid, userId, profile, function(err, url) {
     if (!err && url) {
     if (!err && url) {
       var cape_hash = get_hash(url);
       var cape_hash = get_hash(url);
-      if (details && details.cape === cape_hash) {
+      if (cache_details && cache_details.cape === cape_hash) {
         cache.update_timestamp(rid, userId, cape_hash, false, function(err) {
         cache.update_timestamp(rid, userId, cape_hash, false, function(err) {
           callback(err, cape_hash);
           callback(err, cape_hash);
         });
         });
@@ -120,7 +128,10 @@ function callback_for(userId, type, err, hash) {
   }
   }
 }
 }
 
 
-// returns true if any object in +arr+ has +value+ as +property+
+// returns true if any object in +arr+ has a +property+ that matches +value+
+//
+//    deep_property_check([{foo: "bar"}, {foo: "baz"}], "foo", "baz");
+//
 function deep_property_check(arr, property, value) {
 function deep_property_check(arr, property, value) {
   for (var i = 0; i < arr.length; i++) {
   for (var i = 0; i < arr.length; i++) {
     if (arr[i][property] === value) {
     if (arr[i][property] === value) {
@@ -131,10 +142,10 @@ function deep_property_check(arr, property, value) {
 }
 }
 
 
 // downloads the images for +userId+ while checking the cache
 // downloads the images for +userId+ while checking the cache
-// status based on +details+. +type+ specifies which
+// status based on +cache_details+. +type+ specifies which
 // image type should be called back on
 // image type should be called back on
-// +callback+ contains error, image hash
-function store_images(rid, userId, details, type, callback) {
+// callback: error, image hash
+function store_images(rid, userId, cache_details, type, callback) {
   var is_uuid = userId.length > 16;
   var is_uuid = userId.length > 16;
   var new_hash = {
   var new_hash = {
     rid: rid,
     rid: rid,
@@ -160,7 +171,7 @@ function store_images(rid, userId, details, type, callback) {
         }
         }
       } else {
       } else {
         // no error and we have a profile (if it's a uuid)
         // no error and we have a profile (if it's a uuid)
-        store_skin(rid, userId, profile, details, function(err, skin_hash) {
+        store_skin(rid, userId, profile, cache_details, function(err, skin_hash) {
           if (err && !skin_hash) {
           if (err && !skin_hash) {
             // an error occured, not caching. we can try in 60 seconds
             // an error occured, not caching. we can try in 60 seconds
             callback_for(userId, "skin", err, null);
             callback_for(userId, "skin", err, null);
@@ -170,7 +181,7 @@ function store_images(rid, userId, details, type, callback) {
             });
             });
           }
           }
         });
         });
-        store_cape(rid, userId, profile, details, function(err, cape_hash) {
+        store_cape(rid, userId, profile, cache_details, function(err, cape_hash) {
           if (err && !cape_hash) {
           if (err && !cape_hash) {
             // an error occured, not caching. we can try in 60 seconds
             // an error occured, not caching. we can try in 60 seconds
             callback_for(userId, "cape", (err || cache_err), cape_hash);
             callback_for(userId, "cape", (err || cache_err), cape_hash);
@@ -197,7 +208,7 @@ exp.id_valid = function(userId) {
 };
 };
 
 
 // decides whether to get a +type+ image for +userId+ from disk or to download it
 // decides whether to get a +type+ image for +userId+ from disk or to download it
-// callback contains error, status, hash
+// callback: error, status, hash
 // the status gives information about how the image was received
 // the status gives information about how the image was received
 //  -1: "error"
 //  -1: "error"
 //   0: "none" - cached as null
 //   0: "none" - cached as null
@@ -205,32 +216,32 @@ exp.id_valid = function(userId) {
 //   2: "downloaded" - profile downloaded, skin downloaded from mojang servers
 //   2: "downloaded" - profile downloaded, skin downloaded from mojang servers
 //   3: "checked" - profile re-downloaded (was too old), but it has either not changed or has no skin
 //   3: "checked" - profile re-downloaded (was too old), but it has either not changed or has no skin
 exp.get_image_hash = function(rid, userId, type, callback) {
 exp.get_image_hash = function(rid, userId, type, callback) {
-  cache.get_details(userId, function(err, details) {
-    var cached_hash = (details !== null) ? (type === "skin" ? details.skin : details.cape) : null;
+  cache.get_details(userId, function(err, cache_details) {
+    var cached_hash = (cache_details !== null) ? (type === "skin" ? cache_details.skin : cache_details.cape) : null;
     if (err) {
     if (err) {
       callback(err, -1, null);
       callback(err, -1, null);
     } else {
     } else {
-      if (details && details[type] !== undefined && details.time + config.local_cache_time * 1000 >= new Date().getTime()) {
+      if (cache_details && cache_details[type] !== undefined && cache_details.time + config.local_cache_time * 1000 >= new Date().getTime()) {
         // use cached image
         // use cached image
         logging.log(rid + "userId cached & recently updated");
         logging.log(rid + "userId cached & recently updated");
         callback(null, (cached_hash ? 1 : 0), cached_hash);
         callback(null, (cached_hash ? 1 : 0), cached_hash);
       } else {
       } else {
         // download image
         // download image
-        if (details) {
+        if (cache_details) {
           logging.log(rid + "userId cached, but too old");
           logging.log(rid + "userId cached, but too old");
         } else {
         } else {
           logging.log(rid + "userId not cached");
           logging.log(rid + "userId not cached");
         }
         }
-        store_images(rid, userId, details, type, function(err, new_hash) {
+        store_images(rid, userId, cache_details, type, function(err, new_hash) {
           if (err) {
           if (err) {
             // we might have a cached hash although an error occured
             // we might have a cached hash although an error occured
             // (e.g. Mojang servers not reachable, using outdated hash)
             // (e.g. Mojang servers not reachable, using outdated hash)
             cache.update_timestamp(rid, userId, cached_hash, true, function(err2) {
             cache.update_timestamp(rid, userId, cached_hash, true, function(err2) {
-              callback(err2 || err, -1, details && cached_hash);
+              callback(err2 || err, -1, cache_details && cached_hash);
             });
             });
           } else {
           } else {
-            var status = details && (cached_hash === new_hash) ? 3 : 2;
-            logging.debug(rid + "cached hash: " + (details && cached_hash));
+            var status = cache_details && (cached_hash === new_hash) ? 3 : 2;
+            logging.debug(rid + "cached hash: " + (cache_details && cached_hash));
             logging.log(rid + "new hash: " + new_hash);
             logging.log(rid + "new hash: " + new_hash);
             callback(null, status, new_hash);
             callback(null, status, new_hash);
           }
           }
@@ -242,7 +253,7 @@ exp.get_image_hash = function(rid, userId, type, callback) {
 
 
 
 
 // handles requests for +userId+ avatars with +size+
 // handles requests for +userId+ avatars with +size+
-// callback contains error, status, image buffer, skin hash
+// callback: error, status, image buffer, skin hash
 // image is the user's face+helm when helm is true, or the face otherwise
 // image is the user's face+helm when helm is true, or the face otherwise
 // for status, see get_image_hash
 // for status, see get_image_hash
 exp.get_avatar = function(rid, userId, helm, size, callback) {
 exp.get_avatar = function(rid, userId, helm, size, callback) {
@@ -271,7 +282,7 @@ exp.get_avatar = function(rid, userId, helm, size, callback) {
 };
 };
 
 
 // handles requests for +userId+ skins
 // handles requests for +userId+ skins
-// callback contains error, skin hash, image buffer
+// callback: error, skin hash, image buffer
 exp.get_skin = function(rid, userId, callback) {
 exp.get_skin = function(rid, userId, callback) {
   exp.get_image_hash(rid, userId, "skin", function(err, status, skin_hash) {
   exp.get_image_hash(rid, userId, "skin", function(err, status, skin_hash) {
     var skinpath = __dirname + "/../" + config.skins_dir + skin_hash + ".png";
     var skinpath = __dirname + "/../" + config.skins_dir + skin_hash + ".png";
@@ -290,13 +301,16 @@ exp.get_skin = function(rid, userId, callback) {
   });
   });
 };
 };
 
 
+// helper method used for file names
+// possible returned names based on +helm+ and +body+ are:
+// body, bodyhelm, head, headhelm
 function get_type(helm, body) {
 function get_type(helm, body) {
   var text = body ? "body" : "head";
   var text = body ? "body" : "head";
   return helm ? text + "helm" : text;
   return helm ? text + "helm" : text;
 }
 }
 
 
 // handles creations of 3D renders
 // handles creations of 3D renders
-// callback contains error, skin hash, image buffer
+// callback: error, skin hash, image buffer
 exp.get_render = function(rid, userId, scale, helm, body, callback) {
 exp.get_render = function(rid, userId, scale, helm, body, callback) {
   exp.get_skin(rid, userId, function(err, skin_hash, img) {
   exp.get_skin(rid, userId, function(err, skin_hash, img) {
     if (!skin_hash) {
     if (!skin_hash) {
@@ -335,7 +349,7 @@ exp.get_render = function(rid, userId, scale, helm, body, callback) {
 };
 };
 
 
 // handles requests for +userId+ capes
 // handles requests for +userId+ capes
-// callback contains error, cape hash, image buffer
+// callback: error, cape hash, image buffer
 exp.get_cape = function(rid, userId, callback) {
 exp.get_cape = function(rid, userId, callback) {
   exp.get_image_hash(rid, userId, "cape", function(err, status, cape_hash) {
   exp.get_image_hash(rid, userId, "cape", function(err, status, cape_hash) {
     if (!cape_hash) {
     if (!cape_hash) {

+ 10 - 10
lib/logging.js

@@ -3,23 +3,23 @@ var config = require("./config");
 
 
 var exp = {};
 var exp = {};
 
 
-function split_args(args) {
-  var text = "";
+// returns all values in the +args+ object separated by " "
+function join_args(args) {
+  var values = [];
   for (var i = 0, l = args.length; i < l; i++) {
   for (var i = 0, l = args.length; i < l; i++) {
-    if (i > 0) {
-      text += " " + args[i];
-    } else {
-      text += args[i];
-    }
+    values.push(args[i]);
   }
   }
-  return text;
+  return values.join(" ");
 }
 }
 
 
+// prints +args+ to +logger+ (defaults to `console.log`)
+// the +level+ and a timestamp is prepended to each line of log
+// the timestamp can be disabled in the config
 function log(level, args, logger) {
 function log(level, args, logger) {
   logger = logger || console.log;
   logger = logger || console.log;
   var time = config.log_time ? new Date().toISOString() + " " : "";
   var time = config.log_time ? new Date().toISOString() + " " : "";
   var clid = (cluster.worker && cluster.worker.id || "M");
   var clid = (cluster.worker && cluster.worker.id || "M");
-  var lines = split_args(args).split("\n");
+  var lines = join_args(args).split("\n");
   for (var i = 0, l = lines.length; i < l; i++) {
   for (var i = 0, l = lines.length; i < l; i++) {
     logger(time + clid + " " + level + ": " + lines[i]);
     logger(time + clid + " " + level + ": " + lines[i]);
   }
   }
@@ -42,4 +42,4 @@ if (config.debug_enabled || process.env.DEBUG === "true") {
   exp.debug = function(){};
   exp.debug = function(){};
 }
 }
 
 
-module.exports = exp;
+module.exports = exp;

+ 21 - 12
lib/networking.js

@@ -12,36 +12,39 @@ var mojang_urls = [skins_url, capes_url];
 
 
 var exp = {};
 var exp = {};
 
 
-function extract_url(profile, property) {
+// extracts the +type+ [SKIN|CAPE] URL
+// from the nested & encoded +profile+ object
+// returns the URL or null if not present
+function extract_url(profile, type) {
   var url = null;
   var url = null;
   if (profile && profile.properties) {
   if (profile && profile.properties) {
     profile.properties.forEach(function(prop) {
     profile.properties.forEach(function(prop) {
       if (prop.name === "textures") {
       if (prop.name === "textures") {
         var json = new Buffer(prop.value, "base64").toString();
         var json = new Buffer(prop.value, "base64").toString();
         var props = JSON.parse(json);
         var props = JSON.parse(json);
-        url = props && props.textures && props.textures[property] && props.textures[property].url || null;
+        url = props && props.textures && props.textures[type] && props.textures[type].url || null;
       }
       }
     });
     });
   }
   }
   return url;
   return url;
 }
 }
 
 
-// exracts the skin url of a +profile+ object
-// returns null when no url found (user has no skin)
+// exracts the skin URL of a +profile+ object
+// returns null when no URL found (user has no skin)
 exp.extract_skin_url = function(profile) {
 exp.extract_skin_url = function(profile) {
   return extract_url(profile, 'SKIN');
   return extract_url(profile, 'SKIN');
 };
 };
 
 
-// exracts the cape url of a +profile+ object
-// returns null when no url found (user has no cape)
+// exracts the cape URL of a +profile+ object
+// returns null when no URL found (user has no cape)
 exp.extract_cape_url = function(profile) {
 exp.extract_cape_url = function(profile) {
   return extract_url(profile, 'CAPE');
   return extract_url(profile, 'CAPE');
 };
 };
 
 
-// makes a GET request to the +url+
-// +options+ hash includes these options:
+// performs a GET request to the +url+
+// +options+ object includes these options:
 //   encoding (string), default is to return a buffer
 //   encoding (string), default is to return a buffer
-// +callback+ contains the body, response,
+// callback: the body, response,
 // and error buffer. get_from helper method is available
 // and error buffer. get_from helper method is available
 exp.get_from_options = function(rid, url, options, callback) {
 exp.get_from_options = function(rid, url, options, callback) {
   request.get({
   request.get({
@@ -55,7 +58,9 @@ exp.get_from_options = function(rid, url, options, callback) {
   }, function(error, response, body) {
   }, function(error, response, body) {
     // log url + code + description
     // log url + code + description
     var code = response && response.statusCode;
     var code = response && response.statusCode;
-    if (!error) {
+    if (error) {
+      logging.error(url, error);
+    } else {
       var logfunc = code && code < 405 ? logging.log : logging.warn;
       var logfunc = code && code < 405 ? logging.log : logging.warn;
       logfunc(rid + url + " " + code + " " + http_code[code]);
       logfunc(rid + url + " " + code + " " + http_code[code]);
     }
     }
@@ -65,7 +70,6 @@ exp.get_from_options = function(rid, url, options, callback) {
       // response received successfully
       // response received successfully
       callback(body, response, null);
       callback(body, response, null);
     } else if (error) {
     } else if (error) {
-      logging.error(error);
       callback(body || null, response, error);
       callback(body || null, response, error);
     } else if (code === 404 || code === 204) {
     } else if (code === 404 || code === 204) {
       // page does not exist
       // page does not exist
@@ -115,7 +119,7 @@ exp.get_uuid_url = function(profile, type, callback) {
 };
 };
 
 
 // make a request to sessionserver for +uuid+
 // make a request to sessionserver for +uuid+
-// +callback+ contains error, profile
+// callback: error, profile
 exp.get_profile = function(rid, uuid, callback) {
 exp.get_profile = function(rid, uuid, callback) {
   if (!uuid) {
   if (!uuid) {
     callback(null, null);
     callback(null, null);
@@ -142,6 +146,8 @@ exp.get_cape_url = function(rid, userId, profile, callback) {
   });
   });
 };
 };
 
 
+// helper method that calls `get_username_url` or `get_uuid_url` based on the +usedId+
+// +userId+ is used for usernames, while +profile+ is used for UUIDs
 function get_url(rid, userId, profile, type, callback) {
 function get_url(rid, userId, profile, type, callback) {
   if (userId.length <= 16) {
   if (userId.length <= 16) {
     //username
     //username
@@ -155,6 +161,9 @@ function get_url(rid, userId, profile, type, callback) {
   }
   }
 }
 }
 
 
+// download the +tex_hash+ image from the texture server
+// and save it in the +outpath+ file
+// callback: error, response, image buffer
 exp.save_texture = function(rid, tex_hash, outpath, callback) {
 exp.save_texture = function(rid, tex_hash, outpath, callback) {
   if (tex_hash) {
   if (tex_hash) {
     var textureurl = textures_url + tex_hash;
     var textureurl = textures_url + tex_hash;

+ 2 - 2
lib/renders.js

@@ -128,7 +128,7 @@ exp.draw_body = function(rid, skin_canvas, model_ctx, scale) {
 // sets up the necessary components to draw the skin model
 // sets up the necessary components to draw the skin model
 // uses the +img+ skin with options of drawing
 // uses the +img+ skin with options of drawing
 // the +helm+ and the +body+
 // the +helm+ and the +body+
-// callback contains error, image buffer
+// callback: error, image buffer
 exp.draw_model = function(rid, img, scale, helm, body, callback) {
 exp.draw_model = function(rid, img, scale, helm, body, callback) {
   var image = new Image();
   var image = new Image();
 
 
@@ -169,7 +169,7 @@ exp.draw_model = function(rid, img, scale, helm, body, callback) {
 };
 };
 
 
 // helper method to open a render from +renderpath+
 // helper method to open a render from +renderpath+
-// callback contains error, image buffer
+// callback: error, image buffer
 exp.open_render = function(rid, renderpath, callback) {
 exp.open_render = function(rid, renderpath, callback) {
   fs.readFile(renderpath, function (err, buf) {
   fs.readFile(renderpath, function (err, buf) {
     if (err) {
     if (err) {

+ 6 - 4
lib/skins.js

@@ -6,7 +6,7 @@ var exp = {};
 
 
 // extracts the face from an image +buffer+
 // extracts the face from an image +buffer+
 // result is saved to a file called +outname+
 // result is saved to a file called +outname+
-// +callback+ contains error
+// callback: error
 exp.extract_face = function(buffer, outname, callback) {
 exp.extract_face = function(buffer, outname, callback) {
   lwip.open(buffer, "png", function(err, image) {
   lwip.open(buffer, "png", function(err, image) {
     if (err) {
     if (err) {
@@ -28,7 +28,7 @@ exp.extract_face = function(buffer, outname, callback) {
 // extracts the helm from an image +buffer+ and lays it over a +facefile+
 // extracts the helm from an image +buffer+ and lays it over a +facefile+
 // +facefile+ is the filename of an image produced by extract_face
 // +facefile+ is the filename of an image produced by extract_face
 // result is saved to a file called +outname+
 // result is saved to a file called +outname+
-// +callback+ contains error
+// callback: error
 exp.extract_helm = function(rid, facefile, buffer, outname, callback) {
 exp.extract_helm = function(rid, facefile, buffer, outname, callback) {
   lwip.open(buffer, "png", function(err, skin_img) {
   lwip.open(buffer, "png", function(err, skin_img) {
     if (err) {
     if (err) {
@@ -69,7 +69,7 @@ exp.extract_helm = function(rid, facefile, buffer, outname, callback) {
 };
 };
 
 
 // resizes the image file +inname+ to +size+ by +size+ pixels
 // resizes the image file +inname+ to +size+ by +size+ pixels
-// +callback+ contains error, image buffer
+// callback: error, image buffer
 exp.resize_img = function(inname, size, callback) {
 exp.resize_img = function(inname, size, callback) {
   lwip.open(inname, function(err, image) {
   lwip.open(inname, function(err, image) {
     if (err) {
     if (err) {
@@ -105,7 +105,7 @@ exp.default_skin = function(uuid) {
 };
 };
 
 
 // helper method for opening a skin file from +skinpath+
 // helper method for opening a skin file from +skinpath+
-// callback contains error, image buffer
+// callback: error, image buffer
 exp.open_skin = function(rid, skinpath, callback) {
 exp.open_skin = function(rid, skinpath, callback) {
   fs.readFile(skinpath, function(err, buf) {
   fs.readFile(skinpath, function(err, buf) {
     if (err) {
     if (err) {
@@ -117,6 +117,8 @@ exp.open_skin = function(rid, skinpath, callback) {
   });
   });
 };
 };
 
 
+// write the image +buffer+ to the +outpath+ file
+// callback: error
 exp.save_image = function(buffer, outpath, callback) {
 exp.save_image = function(buffer, outpath, callback) {
   lwip.open(buffer, "png", function(err, image) {
   lwip.open(buffer, "png", function(err, image) {
     if (err) {
     if (err) {