revisionstorage.js 32.4 KB
Newer Older
1 2 3
/*jslint indent: 2, maxlen: 80, nomen: true */
/*global jIO, hex_sha256, setTimeout, define */

4
/**
5
 * JIO Revision Storage.
6
 * It manages document version and can generate conflicts.
7 8 9
 * Description:
 * {
 *     "type": "revision",
10
 *     "sub_storage": <sub storage description>
11
 * }
12
 */
13 14
// define([module_name], [dependencies], module);
(function (dependencies, module) {
Sven Franck's avatar
Sven Franck committed
15
  "use strict";
16 17 18
  if (typeof define === 'function' && define.amd) {
    return define(dependencies, module);
  }
19 20
  module(jIO, {hex_sha256: hex_sha256});
}(['jio', 'sha256'], function (jIO, sha256) {
21 22
  "use strict";

23
  jIO.addStorage("revision", function (spec) {
24

25
    var that = this, priv = {};
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
    spec = spec || {};
    // ATTRIBUTES //
    priv.doc_tree_suffix = ".revision_tree.json";
    priv.sub_storage = spec.sub_storage;
    // METHODS //
    /**
     * Clones an object in deep (without functions)
     * @method clone
     * @param  {any} object The object to clone
     * @return {any} The cloned object
     */
    priv.clone = function (object) {
      var tmp = JSON.stringify(object);
      if (tmp === undefined) {
        return undefined;
41
      }
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
      return JSON.parse(tmp);
    };

    /**
     * Generate a new uuid
     * @method generateUuid
     * @return {string} The new uuid
     */
    priv.generateUuid = function () {
      var S4 = function () {
        /* 65536 */
        var i, string = Math.floor(
          Math.random() * 0x10000
        ).toString(16);
        for (i = string.length; i < 4; i += 1) {
          string = '0' + string;
58
        }
59 60 61 62
        return string;
      };
      return S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() +
        S4() + S4();
63
    };
64 65 66 67 68 69 70 71

    /**
     * Generates a hash code of a string
     * @method hashCode
     * @param  {string} string The string to hash
     * @return {string} The string hash code
     */
    priv.hashCode = function (string) {
72
      return sha256.hex_sha256(string);
73 74 75 76 77 78 79 80 81 82 83
    };

    /**
     * Checks a revision format
     * @method checkDocumentRevisionFormat
     * @param  {object} doc The document object
     * @return {object} null if ok, else error object
     */
    priv.checkDocumentRevisionFormat = function (doc) {
      var send_error = function (message) {
        return {
84
          "status": 409,
85
          "message": message,
86
          "reason": "Wrong revision"
87 88 89 90 91 92
        };
      };
      if (typeof doc._rev === "string") {
        if (/^[0-9]+-[0-9a-zA-Z]+$/.test(doc._rev) === false) {
          return send_error("The document revision does not match " +
                            "^[0-9]+-[0-9a-zA-Z]+$");
93
        }
Sven Franck's avatar
Sven Franck committed
94
      }
95 96 97 98 99 100 101
      if (typeof doc._revs === "object") {
        if (typeof doc._revs.start !== "number" ||
            typeof doc._revs.ids !== "object" ||
            typeof doc._revs.ids.length !== "number") {
          return send_error(
            "The document revision history is not well formated"
          );
102
        }
Sven Franck's avatar
Sven Franck committed
103
      }
104 105 106 107 108
      if (typeof doc._revs_info === "object") {
        if (typeof doc._revs_info.length !== "number") {
          return send_error("The document revision information " +
                            "is not well formated");
        }
109
      }
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
    };

    /**
     * Creates a new document tree
     * @method newDocTree
     * @return {object} The new document tree
     */
    priv.newDocTree = function () {
      return {"children": []};
    };

    /**
     * Convert revs_info to a simple revisions history
     * @method revsInfoToHistory
     * @param  {array} revs_info The revs info
     * @return {object} The revisions history
     */
    priv.revsInfoToHistory = function (revs_info) {
      var i, revisions = {
        "start": 0,
        "ids": []
      };
      revs_info = revs_info || [];
      if (revs_info.length > 0) {
        revisions.start = parseInt(revs_info[0].rev.split('-')[0], 10);
Sven Franck's avatar
Sven Franck committed
135
      }
136 137
      for (i = 0; i < revs_info.length; i += 1) {
        revisions.ids.push(revs_info[i].rev.split('-')[1]);
Sven Franck's avatar
Sven Franck committed
138
      }
139 140 141 142 143 144 145 146 147 148 149 150 151
      return revisions;
    };

    /**
     * Convert the revision history object to an array of revisions.
     * @method revisionHistoryToList
     * @param  {object} revs The revision history
     * @return {array} The revision array
     */
    priv.revisionHistoryToList = function (revs) {
      var i, start = revs.start, new_list = [];
      for (i = 0; i < revs.ids.length; i += 1, start -= 1) {
        new_list.push(start + "-" + revs.ids[i]);
Sven Franck's avatar
Sven Franck committed
152
      }
153 154 155 156 157 158 159 160 161 162 163 164 165 166
      return new_list;
    };

    /**
     * Convert revision list to revs info.
     * @method revisionListToRevsInfo
     * @param  {array} revision_list The revision list
     * @param  {object} doc_tree The document tree
     * @return {array} The document revs info
     */
    priv.revisionListToRevsInfo = function (revision_list, doc_tree) {
      var revisionListToRevsInfoRec, revs_info = [], j;
      for (j = 0; j < revision_list.length; j += 1) {
        revs_info.push({"rev": revision_list[j], "status": "missing"});
Sven Franck's avatar
Sven Franck committed
167
      }
168 169 170 171 172 173 174 175 176 177 178
      revisionListToRevsInfoRec = function (index, doc_tree) {
        var child, i;
        if (index < 0) {
          return;
        }
        for (i = 0; i < doc_tree.children.length; i += 1) {
          child = doc_tree.children[i];
          if (child.rev === revision_list[index]) {
            revs_info[index].status = child.status;
            revisionListToRevsInfoRec(index - 1, child);
          }
179
        }
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
      };
      revisionListToRevsInfoRec(revision_list.length - 1, doc_tree);
      return revs_info;
    };

    /**
     * Update a document metadata revision properties
     * @method fillDocumentRevisionProperties
     * @param  {object} doc The document object
     * @param  {object} doc_tree The document tree
     */
    priv.fillDocumentRevisionProperties = function (doc, doc_tree) {
      if (doc._revs_info) {
        doc._revs = priv.revsInfoToHistory(doc._revs_info);
      } else if (doc._revs) {
        doc._revs_info = priv.revisionListToRevsInfo(
          priv.revisionHistoryToList(doc._revs),
          doc_tree
        );
      } else if (doc._rev) {
        doc._revs_info = priv.getRevisionInfo(doc._rev, doc_tree);
        doc._revs = priv.revsInfoToHistory(doc._revs_info);
      } else {
        doc._revs_info = [];
        doc._revs = {"start": 0, "ids": []};
Sven Franck's avatar
Sven Franck committed
205
      }
206 207 208 209
      if (doc._revs.start > 0) {
        doc._rev = doc._revs.start + "-" + doc._revs.ids[0];
      } else {
        delete doc._rev;
Sven Franck's avatar
Sven Franck committed
210
      }
211
    };
212 213 214 215 216 217 218 219 220

    /**
     * Generates the next revision of a document.
     * @methode generateNextRevision
     * @param  {object} doc The document metadata
     * @param  {boolean} deleted_flag The deleted flag
     * @return {array} 0:The next revision number and 1:the hash code
     */
    priv.generateNextRevision = function (doc, deleted_flag) {
221
      var string, revision_history, revs_info;
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
      doc = priv.clone(doc) || {};
      revision_history = doc._revs;
      revs_info = doc._revs_info;
      delete doc._rev;
      delete doc._revs;
      delete doc._revs_info;
      string = JSON.stringify(doc) + JSON.stringify(revision_history) +
        JSON.stringify(deleted_flag ? true : false);
      revision_history.start += 1;
      revision_history.ids.unshift(priv.hashCode(string));
      doc._revs = revision_history;
      doc._rev = revision_history.start + "-" + revision_history.ids[0];
      revs_info.unshift({
        "rev": doc._rev,
        "status": deleted_flag ? "deleted" : "available"
      });
      doc._revs_info = revs_info;
      return doc;
    };

    /**
     * Gets the revs info from the document tree
     * @method getRevisionInfo
     * @param  {string} revision The revision to search for
     * @param  {object} doc_tree The document tree
     * @return {array} The revs info
     */
    priv.getRevisionInfo = function (revision, doc_tree) {
      var getRevisionInfoRec;
      getRevisionInfoRec = function (doc_tree) {
        var i, child, revs_info;
        for (i = 0; i < doc_tree.children.length; i += 1) {
          child = doc_tree.children[i];
          if (child.rev === revision) {
            return [{"rev": child.rev, "status": child.status}];
          }
          revs_info = getRevisionInfoRec(child);
          if (revs_info.length > 0 || revision === undefined) {
            revs_info.push({"rev": child.rev, "status": child.status});
            return revs_info;
          }
        }
        return [];
      };
      return getRevisionInfoRec(doc_tree);
    };

    priv.updateDocumentTree = function (doc, doc_tree) {
270
      var revs_info, updateDocumentTreeRec;
271 272 273 274 275
      doc = priv.clone(doc);
      revs_info = doc._revs_info;
      updateDocumentTreeRec = function (doc_tree, revs_info) {
        var i, child, info;
        if (revs_info.length === 0) {
Sven Franck's avatar
Sven Franck committed
276
          return;
277
        }
278 279 280 281 282
        info = revs_info.pop();
        for (i = 0; i < doc_tree.children.length; i += 1) {
          child = doc_tree.children[i];
          if (child.rev === info.rev) {
            return updateDocumentTreeRec(child, revs_info);
283
          }
284
        }
285 286 287 288
        doc_tree.children.unshift({
          "rev": info.rev,
          "status": info.status,
          "children": []
289
        });
290 291 292 293 294
        updateDocumentTreeRec(doc_tree.children[0], revs_info);
      };
      updateDocumentTreeRec(doc_tree, priv.clone(revs_info));
    };

295 296 297 298 299 300 301 302 303 304 305 306 307 308
    priv.send = function (command, method, doc, option, callback) {
      function onSuccess(success) {
        callback(undefined, success);
      }
      function onError(err) {
        callback(err, undefined);
      }
      if (method === 'allDocs') {
        command.storage(priv.sub_storage).allDocs(option).
          then(onSuccess, onError);
      } else {
        command.storage(priv.sub_storage)[method](doc, option).
          then(onSuccess, onError);
      }
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
    };

    priv.getWinnerRevsInfo = function (doc_tree) {
      var revs_info = [], getWinnerRevsInfoRec;
      getWinnerRevsInfoRec = function (doc_tree, tmp_revs_info) {
        var i;
        if (doc_tree.rev) {
          tmp_revs_info.unshift({
            "rev": doc_tree.rev,
            "status": doc_tree.status
          });
        }
        if (doc_tree.children.length === 0) {
          if (revs_info.length === 0 ||
              (revs_info[0].status !== "available" &&
               tmp_revs_info[0].status === "available") ||
              (tmp_revs_info[0].status === "available" &&
               revs_info.length < tmp_revs_info.length)) {
            revs_info = priv.clone(tmp_revs_info);
          }
        }
        for (i = 0; i < doc_tree.children.length; i += 1) {
          getWinnerRevsInfoRec(doc_tree.children[i], tmp_revs_info);
        }
        tmp_revs_info.shift();
334
      };
335 336
      getWinnerRevsInfoRec(doc_tree, []);
      return revs_info;
337
    };
338 339 340 341 342 343

    priv.getConflicts = function (revision, doc_tree) {
      var conflicts = [], getConflictsRec;
      getConflictsRec = function (doc_tree) {
        var i;
        if (doc_tree.rev === revision) {
344 345
          return;
        }
346 347 348 349
        if (doc_tree.children.length === 0) {
          if (doc_tree.status !== "deleted") {
            conflicts.push(doc_tree.rev);
          }
350
        }
351 352
        for (i = 0; i < doc_tree.children.length; i += 1) {
          getConflictsRec(doc_tree.children[i]);
353 354
        }
      };
355 356
      getConflictsRec(doc_tree);
      return conflicts.length === 0 ? undefined : conflicts;
357
    };
358

359 360
    priv.get = function (command, doc, option, callback) {
      priv.send(command, "get", doc, option, callback);
361
    };
362 363
    priv.put = function (command, doc, option, callback) {
      priv.send(command, "put", doc, option, callback);
364
    };
365 366
    priv.remove = function (command, doc, option, callback) {
      priv.send(command, "remove", doc, option, callback);
367
    };
368 369
    priv.getAttachment = function (command, attachment, option, callback) {
      priv.send(command, "getAttachment", attachment, option, callback);
370
    };
371 372
    priv.putAttachment = function (command, attachment, option, callback) {
      priv.send(command, "putAttachment", attachment, option, callback);
373
    };
374 375
    priv.removeAttachment = function (command, attachment, option, callback) {
      priv.send(command, "removeAttachment", attachment, option, callback);
376 377
    };

378
    priv.getDocument = function (command, doc, option, callback) {
379 380 381 382 383 384
      doc = priv.clone(doc);
      doc._id = doc._id + "." + doc._rev;
      delete doc._attachment;
      delete doc._rev;
      delete doc._revs;
      delete doc._revs_info;
385
      priv.get(command, doc, option, callback);
386
    };
387
    priv.putDocument = function (command, doc, option, callback) {
388 389 390 391 392 393 394 395
      doc = priv.clone(doc);
      doc._id = doc._id + "." + doc._rev;
      delete doc._attachment;
      delete doc._data;
      delete doc._mimetype;
      delete doc._rev;
      delete doc._revs;
      delete doc._revs_info;
396
      priv.put(command, doc, option, callback);
397 398
    };

399
    priv.getRevisionTree = function (command, doc, option, callback) {
400 401
      doc = priv.clone(doc);
      doc._id = doc._id + priv.doc_tree_suffix;
402
      priv.get(command, doc, option, callback);
403 404
    };

405
    priv.getAttachmentList = function (command, doc, option, callback) {
406 407
      var attachment_id, dealResults, state = "ok", result_list = [], count = 0;
      dealResults = function (attachment_id, attachment_meta) {
408
        return function (err, response) {
409 410
          if (state !== "ok") {
            return;
Sven Franck's avatar
Sven Franck committed
411
          }
412 413 414 415 416 417 418 419
          count -= 1;
          if (err) {
            if (err.status === 404) {
              result_list.push(undefined);
            } else {
              state = "error";
              return callback(err, undefined);
            }
420
          }
421 422
          result_list.push({
            "_attachment": attachment_id,
423
            "_data": response.data,
424 425 426 427
            "_mimetype": attachment_meta.content_type
          });
          if (count === 0) {
            state = "finished";
428
            callback(undefined, {"data": result_list});
429 430 431 432 433 434 435
          }
        };
      };
      for (attachment_id in doc._attachments) {
        if (doc._attachments.hasOwnProperty(attachment_id)) {
          count += 1;
          priv.getAttachment(
436
            command,
437 438 439 440
            {"_id": doc._id, "_attachment": attachment_id},
            option,
            dealResults(attachment_id, doc._attachments[attachment_id])
          );
441 442
        }
      }
443 444
      if (count === 0) {
        callback(undefined, []);
445
      }
446 447
    };

448 449
    priv.putAttachmentList = function (command, doc, option,
                                       attachment_list, callback) {
450 451
      var i, dealResults, state = "ok", count = 0, attachment;
      attachment_list = attachment_list || [];
452 453
      dealResults = function () {
        return function (err) {
454 455 456 457 458 459 460 461 462 463
          if (state !== "ok") {
            return;
          }
          count -= 1;
          if (err) {
            state = "error";
            return callback(err, undefined);
          }
          if (count === 0) {
            state = "finished";
464
            callback(undefined, {});
465
          }
466
        };
467 468 469 470 471 472
      };
      for (i = 0; i < attachment_list.length; i += 1) {
        attachment = attachment_list[i];
        if (attachment !== undefined) {
          count += 1;
          attachment._id = doc._id + "." + doc._rev;
473
          priv.putAttachment(command, attachment, option, dealResults(i));
474
        }
475
      }
476
      if (count === 0) {
477
        return callback(undefined, {});
478 479 480
      }
    };

481
    priv.putDocumentTree = function (command, doc, option, doc_tree, callback) {
482 483
      doc_tree = priv.clone(doc_tree);
      doc_tree._id = doc._id + priv.doc_tree_suffix;
484
      priv.put(command, doc_tree, option, callback);
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506
    };

    priv.notFoundError = function (message, reason) {
      return {
        "status": 404,
        "statusText": "Not Found",
        "error": "not_found",
        "message": message,
        "reason": reason
      };
    };

    priv.conflictError = function (message, reason) {
      return {
        "status": 409,
        "statusText": "Conflict",
        "error": "conflict",
        "message": message,
        "reason": reason
      };
    };

507
    priv.revisionGenericRequest = function (command, doc, option,
508 509 510 511
                                            specific_parameter, onEnd) {
      var prev_doc, doc_tree, attachment_list, callback = {};
      if (specific_parameter.doc_id) {
        doc._id = specific_parameter.doc_id;
512
      }
513 514
      if (specific_parameter.attachment_id) {
        doc._attachment = specific_parameter.attachment_id;
515
      }
516 517
      callback.begin = function () {
        var check_error;
518
        doc._id = doc._id || priv.generateUuid(); // XXX should not generate id
519 520 521 522 523
        if (specific_parameter.revision_needed && !doc._rev) {
          return onEnd(priv.conflictError(
            "Document update conflict",
            "No document revision was provided"
          ), undefined);
524
        }
525 526 527 528 529
        // check revision format
        check_error = priv.checkDocumentRevisionFormat(doc);
        if (check_error !== undefined) {
          return onEnd(check_error, undefined);
        }
530
        priv.getRevisionTree(command, doc, option, callback.getRevisionTree);
531 532 533
      };
      callback.getRevisionTree = function (err, response) {
        var winner_info, previous_revision, generate_new_revision;
534
        previous_revision = doc._rev;
535 536 537 538 539
        generate_new_revision = doc._revs || doc._revs_info ? false : true;
        if (err) {
          if (err.status !== 404) {
            err.message = "Cannot get document revision tree";
            return onEnd(err, undefined);
540
          }
541
        }
542
        doc_tree = response.data || priv.newDocTree();
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560
        if (specific_parameter.get || specific_parameter.getAttachment) {
          if (!doc._rev) {
            winner_info = priv.getWinnerRevsInfo(doc_tree);
            if (winner_info.length === 0) {
              return onEnd(priv.notFoundError(
                "Document not found",
                "missing"
              ), undefined);
            }
            if (winner_info[0].status === "deleted") {
              return onEnd(priv.notFoundError(
                "Document not found",
                "deleted"
              ), undefined);
            }
            doc._rev = winner_info[0].rev;
          }
          priv.fillDocumentRevisionProperties(doc, doc_tree);
561
          return priv.getDocument(command, doc, option, callback.getDocument);
562
        }
563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578
        priv.fillDocumentRevisionProperties(doc, doc_tree);
        if (generate_new_revision) {
          if (previous_revision && doc._revs_info.length === 0) {
            // the document history has changed, it means that the document
            // revision was wrong. Add a pseudo history to the document
            doc._rev = previous_revision;
            doc._revs = {
              "start": parseInt(previous_revision.split("-")[0], 10),
              "ids": [previous_revision.split("-")[1]]
            };
            doc._revs_info = [{"rev": previous_revision, "status": "missing"}];
          }
          doc = priv.generateNextRevision(
            doc,
            specific_parameter.remove
          );
579
        }
580 581 582 583 584 585 586
        if (doc._revs_info.length > 1) {
          prev_doc = {
            "_id": doc._id,
            "_rev": doc._revs_info[1].rev
          };
          if (!generate_new_revision && specific_parameter.putAttachment) {
            prev_doc._rev = doc._revs_info[0].rev;
587 588
          }
        }
589 590 591 592 593
        // force revs_info status
        doc._revs_info[0].status = (specific_parameter.remove ?
                                    "deleted" : "available");
        priv.updateDocumentTree(doc, doc_tree);
        if (prev_doc) {
594 595
          return priv.getDocument(command, prev_doc,
                                  option, callback.getDocument);
596 597
        }
        if (specific_parameter.remove || specific_parameter.removeAttachment) {
598
          return onEnd(priv.notFoundError(
599
            "Unable to remove an inexistent document",
600 601
            "missing"
          ), undefined);
602
        }
603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625
        priv.putDocument(doc, option, callback.putDocument);
      };
      callback.getDocument = function (err, res_doc) {
        var k, conflicts;
        if (err) {
          if (err.status === 404) {
            if (specific_parameter.remove ||
                specific_parameter.removeAttachment) {
              return onEnd(priv.conflictError(
                "Document update conflict",
                "Document is missing"
              ), undefined);
            }
            if (specific_parameter.get) {
              return onEnd(priv.notFoundError(
                "Unable to find the document",
                "missing"
              ), undefined);
            }
            res_doc = {};
          } else {
            err.message = "Cannot get document";
            return onEnd(err, undefined);
626 627
          }
        }
628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654
        if (specific_parameter.get) {
          res_doc._id = doc._id;
          res_doc._rev = doc._rev;
          if (option.conflicts === true) {
            conflicts = priv.getConflicts(doc._rev, doc_tree);
            if (conflicts) {
              res_doc._conflicts = conflicts;
            }
          }
          if (option.revs === true) {
            res_doc._revisions = doc._revs;
          }
          if (option.revs_info === true) {
            res_doc._revs_info = doc._revs_info;
          }
          return onEnd(undefined, res_doc);
        }
        if (specific_parameter.putAttachment ||
            specific_parameter.removeAttachment) {
          // copy metadata (not beginning by "_" to document
          for (k in res_doc) {
            if (res_doc.hasOwnProperty(k) && !k.match("^_")) {
              doc[k] = res_doc[k];
            }
          }
        }
        if (specific_parameter.remove) {
655 656
          priv.putDocumentTree(command, doc, option,
                               doc_tree, callback.putDocumentTree);
657
        } else {
658 659
          priv.getAttachmentList(command, res_doc, option,
                                 callback.getAttachmentList);
660
        }
661
      };
662 663
      callback.getAttachmentList = function (err, res_list) {
        var i, attachment_found = false;
664
        if (err) {
665 666
          err.message = "Cannot get attachment";
          return onEnd(err, undefined);
Sven Franck's avatar
Sven Franck committed
667
        }
668 669 670 671 672 673 674 675 676 677 678 679 680 681
        attachment_list = res_list || [];
        if (specific_parameter.getAttachment) {
          // getting specific attachment
          for (i = 0; i < attachment_list.length; i += 1) {
            if (attachment_list[i] &&
                doc._attachment ===
                attachment_list[i]._attachment) {
              return onEnd(undefined, attachment_list[i]._data);
            }
          }
          return onEnd(priv.notFoundError(
            "Unable to get an inexistent attachment",
            "missing"
          ), undefined);
682
        }
683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699
        if (specific_parameter.remove_from_attachment_list) {
          // removing specific attachment
          for (i = 0; i < attachment_list.length; i += 1) {
            if (attachment_list[i] &&
                specific_parameter.remove_from_attachment_list._attachment ===
                attachment_list[i]._attachment) {
              attachment_found = true;
              attachment_list[i] = undefined;
              break;
            }
          }
          if (!attachment_found) {
            return onEnd(priv.notFoundError(
              "Unable to remove an inexistent attachment",
              "missing"
            ), undefined);
          }
Sven Franck's avatar
Sven Franck committed
700
        }
701
        priv.putDocument(command, doc, option, callback.putDocument);
702
      };
703
      callback.putDocument = function (err) {
704
        var i, attachment_found = false;
705
        if (err) {
706 707
          err.message = "Cannot post the document";
          return onEnd(err, undefined);
708
        }
709 710 711 712 713 714 715 716 717 718 719 720 721 722 723
        if (specific_parameter.add_to_attachment_list) {
          // adding specific attachment
          attachment_list = attachment_list || [];
          for (i = 0; i < attachment_list.length; i += 1) {
            if (attachment_list[i] &&
                specific_parameter.add_to_attachment_list._attachment ===
                attachment_list[i]._attachment) {
              attachment_found = true;
              attachment_list[i] = specific_parameter.add_to_attachment_list;
              break;
            }
          }
          if (!attachment_found) {
            attachment_list.unshift(specific_parameter.add_to_attachment_list);
          }
Sven Franck's avatar
Sven Franck committed
724
        }
725
        priv.putAttachmentList(
726
          command,
727 728 729 730 731 732
          doc,
          option,
          attachment_list,
          callback.putAttachmentList
        );
      };
733
      callback.putAttachmentList = function (err) {
734
        if (err) {
735 736
          err.message = "Cannot copy attacments to the document";
          return onEnd(err, undefined);
737
        }
738 739
        priv.putDocumentTree(command, doc, option,
                             doc_tree, callback.putDocumentTree);
740
      };
741
      callback.putDocumentTree = function (err) {
742
        var response_object;
743
        if (err) {
744 745
          err.message = "Cannot update the document history";
          return onEnd(err, undefined);
746
        }
747 748 749 750 751 752 753 754 755
        response_object = {
          "ok": true,
          "id": doc._id,
          "rev": doc._rev
        };
        if (specific_parameter.putAttachment ||
            specific_parameter.removeAttachment ||
            specific_parameter.getAttachment) {
          response_object.attachment = doc._attachment;
756
        }
757 758
        onEnd(undefined, response_object);
        // if (option.keep_revision_history !== true) {
759
        //   // priv.remove(command, prev_doc, option, function () {
760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776
        //   //   - change "available" status to "deleted"
        //   //   - remove attachments
        //   //   - done, no callback
        //   // });
        // }
      };
      callback.begin();
    };

    /**
     * Post the document metadata and create or update a document tree.
     * Options:
     * - {boolean} keep_revision_history To keep the previous revisions
     *                                   (false by default) (NYI).
     * @method post
     * @param  {object} command The JIO command
     */
777
    that.post = function (command, metadata, option) {
778
      priv.revisionGenericRequest(
779 780 781
        command,
        metadata,
        option,
782 783 784
        {},
        function (err, response) {
          if (err) {
785
            return command.error(err);
786
          }
787
          command.success({"id": response.id});
788
        }
789 790 791 792 793 794 795 796 797 798 799
      );
    };

    /**
     * Put the document metadata and create or update a document tree.
     * Options:
     * - {boolean} keep_revision_history To keep the previous revisions
     *                                   (false by default) (NYI).
     * @method put
     * @param  {object} command The JIO command
     */
800
    that.put = function (command, metadata, option) {
801
      priv.revisionGenericRequest(
802 803 804
        command,
        metadata,
        option,
805
        {},
806
        function (err) {
807
          if (err) {
808
            return command.error(err);
809
          }
810
          command.success();
811 812 813 814 815
        }
      );
    };


816
    that.putAttachment = function (command, param, option) {
817
      priv.revisionGenericRequest(
818 819 820
        command,
        param,
        option,
821
        {
822 823
          "doc_id": param._id,
          "attachment_id": param._attachment,
824
          "add_to_attachment_list": {
825 826 827
            "_attachment": param._attachment,
            "_mimetype": param._blob.type,
            "_data": param._blob
828 829 830
          },
          "putAttachment": true
        },
831
        function (err) {
832
          if (err) {
833
            return command.error(err);
834
          }
835
          command.success();
836 837
        }
      );
838
    };
839

840
    that.remove = function (command, param, option) {
841
      priv.revisionGenericRequest(
842 843 844
        command,
        param,
        option,
845 846 847 848
        {
          "revision_needed": true,
          "remove": true
        },
849
        function (err) {
850
          if (err) {
851
            return command.error(err);
852
          }
853
          command.success();
854 855 856 857
        }
      );
    };

858
    that.removeAttachment = function (command, param, option) {
859
      priv.revisionGenericRequest(
860 861 862
        command,
        param,
        option,
863
        {
864 865
          "doc_id": param._id,
          "attachment_id": param._attachment,
866 867 868
          "revision_needed": true,
          "removeAttachment": true,
          "remove_from_attachment_list": {
869
            "_attachment": param._attachment
870 871
          }
        },
872
        function (err) {
873
          if (err) {
874
            return command.error(err);
875
          }
876
          command.success();
877 878
        }
      );
879
    };
880

881
    that.get = function (command, param, option) {
882
      priv.revisionGenericRequest(
883 884 885
        command,
        param,
        option,
886 887
        {
          "get": true
888
        },
889 890
        function (err, response) {
          if (err) {
891
            return command.error(err);
892
          }
893
          command.success({"data": response.data});
894
        }
895 896 897
      );
    };

898
    that.getAttachment = function (command, param, option) {
899
      priv.revisionGenericRequest(
900 901 902
        command,
        param,
        option,
903
        {
904 905
          "doc_id": param._id,
          "attachment_id": param._attachment,
906 907 908 909
          "getAttachment": true
        },
        function (err, response) {
          if (err) {
910
            return command.error(err);
911
          }
912
          command.success({"data": response.data});
913 914 915 916
        }
      );
    };

917 918
    that.allDocs = function (command, param, option) {
      /*jslint unparam: true */
919 920 921 922
      var rows, result = {"total_rows": 0, "rows": []}, functions = {};
      functions.finished = 0;
      functions.falseResponseGenerator = function (response, callback) {
        callback(undefined, response);
923
      };
924 925 926 927
      functions.fillResultGenerator = function (doc_id) {
        return function (err, doc_tree) {
          var document_revision, row, revs_info;
          if (err) {
928
            return command.error(err);
929
          }
930 931 932 933 934 935 936 937 938 939 940
          revs_info = priv.getWinnerRevsInfo(doc_tree);
          document_revision =
            rows.document_revisions[doc_id + "." + revs_info[0].rev];
          if (document_revision) {
            row = {
              "id": doc_id,
              "key": doc_id,
              "value": {
                "rev": revs_info[0].rev
              }
            };
941
            if (document_revision.doc && option.include_docs) {
942 943 944 945 946 947
              document_revision.doc._id = doc_id;
              document_revision.doc._rev = revs_info[0].rev;
              row.doc = document_revision.doc;
            }
            result.rows.push(row);
            result.total_rows += 1;
948
          }
949 950 951 952 953 954
          functions.success();
        };
      };
      functions.success = function () {
        functions.finished -= 1;
        if (functions.finished === 0) {
955
          command.success(result);
956
        }
957
      };
958 959
      priv.send(command, "allDocs", null, option, function (err, response) {
        var i, row, selector, selected;
960
        if (err) {
961
          return command.error(err);
962
        }
963
        response = response.data;
964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992
        selector = /\.revision_tree\.json$/;
        rows = {
          "revision_trees": {
            // id.revision_tree.json: {
            //   id: blabla
            //   doc: {...}
            // }
          },
          "document_revisions": {
            // id.rev: {
            //   id: blabla
            //   rev: 1-1
            //   doc: {...}
            // }
          }
        };
        while (response.rows.length > 0) {
          // filling rows
          row = response.rows.shift();
          selected = selector.exec(row.id);
          if (selected) {
            selected = selected.input.substring(0, selected.index);
            // this is a revision tree
            rows.revision_trees[row.id] = {
              "id": selected
            };
            if (row.doc) {
              rows.revision_trees[row.id].doc = row.doc;
            }
993
          } else {
994 995 996 997 998 999 1000 1001
            // this is a simple revision
            rows.document_revisions[row.id] = {
              "id": row.id.split(".").slice(0, -1),
              "rev": row.id.split(".").slice(-1)
            };
            if (row.doc) {
              rows.document_revisions[row.id].doc = row.doc;
            }
1002 1003
          }
        }
1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
        functions.finished += 1;
        for (i in rows.revision_trees) {
          if (rows.revision_trees.hasOwnProperty(i)) {
            functions.finished += 1;
            if (rows.revision_trees[i].doc) {
              functions.falseResponseGenerator(
                rows.revision_trees[i].doc,
                functions.fillResultGenerator(rows.revision_trees[i].id)
              );
            } else {
              priv.getRevisionTree(
1015
                command,
1016
                {"_id": rows.revision_trees[i].id},
1017
                option,
1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028
                functions.fillResultGenerator(rows.revision_trees[i].id)
              );
            }
          }
        }
        functions.success();
      });
    };
  }); // end RevisionStorage

}));