1 /**
  2  * Copyright (C) 2014 KO GmbH <copyright@kogmbh.com>
  3  *
  4  * @licstart
  5  * This file is part of WebODF.
  6  *
  7  * WebODF is free software: you can redistribute it and/or modify it
  8  * under the terms of the GNU Affero General Public License (GNU AGPL)
  9  * as published by the Free Software Foundation, either version 3 of
 10  * the License, or (at your option) any later version.
 11  *
 12  * WebODF is distributed in the hope that it will be useful, but
 13  * WITHOUT ANY WARRANTY; without even the implied warranty of
 14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15  * GNU Affero General Public License for more details.
 16  *
 17  * You should have received a copy of the GNU Affero General Public License
 18  * along with WebODF.  If not, see <http://www.gnu.org/licenses/>.
 19  * @licend
 20  *
 21  * @source: http://www.webodf.org/
 22  * @source: https://github.com/kogmbh/WebODF/
 23  */
 24 
 25 /*global gui, runtime, core, ops, odf*/
 26 
 27 /**
 28  * @constructor
 29  * @implements {core.Destroyable}
 30  * @param {!ops.Session} session
 31  * @param {!string} inputMemberId
 32  */
 33 gui.MetadataController = function MetadataController(session, inputMemberId) {
 34     "use strict";
 35 
 36     var odtDocument = session.getOdtDocument(),
 37         eventNotifier = new core.EventNotifier([gui.MetadataController.signalMetadataChanged]),
 38         /** @const @type {!Array.<!string>} */
 39         readonlyProperties = [
 40             "dc:creator",
 41             "dc:date",
 42             "meta:editing-cycles",
 43             "meta:editing-duration",
 44             "meta:document-statistic"
 45         ];
 46 
 47     /**
 48      * @param {!Object} changes
 49      * @return {undefined}
 50      */
 51     function onMetadataUpdated(changes) {
 52         eventNotifier.emit(gui.MetadataController.signalMetadataChanged, changes);
 53     }
 54 
 55     /**
 56      * @param {!string} property
 57      * @return {!boolean}
 58      */
 59     function isWriteableMetadata(property) {
 60         var isWriteable = (readonlyProperties.indexOf(property) === -1);
 61         if (! isWriteable) {
 62             runtime.log("Setting " + property + " is restricted.");
 63         }
 64         return isWriteable;
 65     }
 66 
 67     /**
 68      * Sets the metadata fields from the given properties map.
 69      * Avoid setting certain fields since they are automatically set:
 70      *     dc:creator
 71      *     dc:date
 72      *     meta:editing-cycles
 73      * If you do wish to externally set these fields, try getting
 74      * the master session to inject operations into the timeline
 75      * with the relevant properties.
 76      *
 77      * The following properties are never used and will be removed for semantic
 78      * consistency from the document:
 79      *     meta:editing-duration
 80      *     meta:document-statistic
 81      *
 82      * Setting any of the above mentioned fields using this method will have no effect.
 83      *
 84      * @param {?Object.<!string, !string>} setProperties A flat object that is a string->string map of field name -> value.
 85      * @param {?Array.<!string>|undefined=} removedProperties An array of metadata field names (prefixed).
 86      * @return {undefined}
 87      */
 88     this.setMetadata = function (setProperties, removedProperties) {
 89         var /** @type {!Object.<!string,!string>} */
 90             filteredSetProperties = {},
 91             /** @type {!string} */
 92             filteredRemovedProperties = "",
 93             op;
 94 
 95         if (setProperties) {
 96             Object.keys(setProperties).filter(isWriteableMetadata).forEach(function (property) {
 97                 filteredSetProperties[property] = setProperties[property];
 98             });
 99         }
100         if (removedProperties) {
101             filteredRemovedProperties = removedProperties.filter(isWriteableMetadata).join(",");
102         }
103 
104         if (filteredRemovedProperties.length > 0
105                 || Object.keys(filteredSetProperties).length > 0) {
106             op = new ops.OpUpdateMetadata();
107             op.init({
108                 memberid: inputMemberId,
109                 setProperties: filteredSetProperties,
110                 removedProperties: filteredRemovedProperties.length > 0 ? { attributes: filteredRemovedProperties } : null
111             });
112             session.enqueue([op]);
113         }
114     };
115 
116     /**
117      * Returns the value of the requested document metadata field
118      * @param {!string} property A namespace-prefixed field name, for example
119      * dc:creator
120      * @return {?string}
121      */
122     this.getMetadata = function (property) {
123         var namespaceUri, parts;
124 
125         runtime.assert(typeof property === "string", "Property must be a string");
126         parts = property.split(':');
127         runtime.assert(parts.length === 2, "Property must be a namespace-prefixed string");
128         namespaceUri = odf.Namespaces.lookupNamespaceURI(parts[0]);
129         // TODO: support other namespaces
130         runtime.assert(Boolean(namespaceUri), "Prefix must be for an ODF namespace.");
131         return odtDocument.getOdfCanvas().odfContainer().getMetadata(/**@type{!string}*/(namespaceUri), parts[1]);
132     };
133 
134     /**
135      * @param {!string} eventid
136      * @param {!Function} cb
137      * @return {undefined}
138      */
139     this.subscribe = function (eventid, cb) {
140         eventNotifier.subscribe(eventid, cb);
141     };
142 
143     /**
144      * @param {!string} eventid
145      * @param {!Function} cb
146      * @return {undefined}
147      */
148     this.unsubscribe = function (eventid, cb) {
149         eventNotifier.unsubscribe(eventid, cb);
150     };
151 
152     /**
153      * @param {!function(!Error=):undefined} callback, passing an error object in case of error
154      * @return {undefined}
155      */
156     this.destroy = function(callback) {
157         odtDocument.unsubscribe(ops.OdtDocument.signalMetadataUpdated, onMetadataUpdated);
158         callback();
159     };
160 
161     function init() {
162         odtDocument.subscribe(ops.OdtDocument.signalMetadataUpdated, onMetadataUpdated);
163     }
164 
165     init();
166 };
167 
168 /**@const*/gui.MetadataController.signalMetadataChanged = "metadata/changed";
169