Merge branch 'tizen_5.0' into tizen_5.5
[platform/core/api/webapi-plugins.git] / src / messaging / messaging_api.js
1 /*
2  * Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *    Licensed under the Apache License, Version 2.0 (the "License");
5  *    you may not use this file except in compliance with the License.
6  *    You may obtain a copy of the License at
7  *
8  *        http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *    Unless required by applicable law or agreed to in writing, software
11  *    distributed under the License is distributed on an "AS IS" BASIS,
12  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *    See the License for the specific language governing permissions and
14  *    limitations under the License.
15  */
16
17 var validator_ = xwalk.utils.validator;
18 var types_ = validator_.Types;
19 var T_ = xwalk.utils.type;
20 var native = new xwalk.utils.NativeManager(extension);
21 var privUtils_ = xwalk.utils;
22
23 function throwException_(err) {
24     throw new WebAPIException(err.code, err.name, err.message);
25 }
26
27 var Property = {
28     W: 1 << 0, // WRITABLE
29     E: 1 << 1, // ENUMERABLE
30     C: 1 << 2 // CONFIGURABLE
31 };
32
33 function addTypeToFilter_(data) {
34     var filter = {};
35
36     for (var field in data) {
37         filter[field] = data[field];
38     }
39
40     if (data instanceof tizen.AttributeFilter) {
41         filter.filterType = 'AttributeFilter';
42     } else if (data instanceof tizen.AttributeRangeFilter) {
43         filter.filterType = 'AttributeRangeFilter';
44     } else if (data instanceof tizen.CompositeFilter) {
45         filter.filterType = 'CompositeFilter';
46         // recursively convert all sub-filters
47         filter.filters = [];
48         for (var i = 0; i < data.filters.length; ++i) {
49             filter.filters[i] = addTypeToFilter_(data.filters[i]);
50         }
51     } else {
52         filter.filterType = 'Unknown';
53     }
54
55     return filter;
56 }
57
58 /**
59  * Example usage:
60  * function Messaging () {
61  *     propertyFactory_(this, 'ids', [2,3,4], Property.W | Property.E | Property.C);
62  *     propertyFactory_(this, 'name', 'Name', Property.E);
63  *     propertyFactory_(this, 'age', 25, Property.W);
64  *     propertyFactory_(this, 'something', 1);
65  *     propertyFactory_(this, 'getSomething', Property.E, {get: function(){return 100;}});
66  * }
67  * Will produce:
68  * var m = new Messaging();
69  * {
70  *     id: [2,3,4],
71  *     name: 'Name',
72  *     age: 25
73  * }
74  *
75  * m.name = 'A brand new name';
76  * privUtils_.log(m.name); // Name
77  */
78 function propertyFactory_(that, name, value, flags, options) {
79     flags = flags || 0;
80     if (options === null || typeof options !== 'object') {
81         options = {};
82     }
83     if (!options.get && !options.set) {
84         options.value = value;
85     }
86     if ((flags & Property.W) != 0) {
87         options.writable = true;
88     }
89     if ((flags & Property.E) != 0) {
90         options.enumerable = true;
91     }
92     if ((flags & Property.C) != 0) {
93         options.configurable = true;
94     }
95     Object.defineProperty(that, name, options);
96 }
97
98 function InternalValues_(data) {
99     if (!(this instanceof InternalValues_)) {
100         return new InternalValues_(data);
101     }
102     for (var key in data) {
103         if (data.hasOwnProperty(key)) {
104             this[key] = data[key];
105         }
106     }
107 }
108
109 function updateInternal_(internal, data) {
110     var values = new InternalValues_(data);
111     for (var key in data) {
112         if (values.hasOwnProperty(key) && internal.hasOwnProperty(key)) {
113             internal[key] = values;
114         }
115     }
116 }
117
118 /**
119  * Specifies the Messaging service tags.
120  */
121 var MessageServiceTag = ['messaging.sms', 'messaging.mms', 'messaging.email'];
122
123 function Message(type, data) {
124     if (!(this instanceof Message)) {
125         throw new TypeError('Constructor called like a function');
126     }
127     if (MessageServiceTag.indexOf(type) === -1) {
128         throw new WebAPIException(WebAPIException.TYPE_MISMATCH_ERR);
129     }
130     if (!data || typeof data !== 'object') {
131         // 'data' is optional
132         data = {};
133     }
134
135     // set initial data from internal MessageInit_ object or to default values
136     var internal = data instanceof MessageInit_,
137         id = internal ? data.id : null,
138         conversationId = internal ? data.conversationId : null,
139         folderId = internal ? data.folderId : null,
140         timestamp = internal ? data.timestamp : null,
141         from = internal ? data.from : null,
142         hasAttachment = internal ? data.hasAttachment : false,
143         isRead = internal ? data.isRead : false,
144         inResponseTo = internal ? data.inResponseTo : null;
145     // create MessageBody object
146     var body = new MessageBody({
147         messageId: id,
148         plainBody: data.plainBody,
149         htmlBody: data.htmlBody
150     });
151     // check 'to', 'cc' and 'bcc' fields
152     var to = data.to;
153     if (!(to instanceof Array)) {
154         to = [];
155     }
156     var cc = data.cc;
157     if (!(cc instanceof Array)) {
158         cc = [];
159     }
160     var bcc = data.bcc;
161     if (!(bcc instanceof Array)) {
162         bcc = [];
163     }
164     // 'attachments' private variable, getter and setter
165     var attachments = (internal ? data.attachments : []) || [];
166
167     var _internal = {
168         id: id || null,
169         conversationId: conversationId || null,
170         folderId: folderId || null,
171         type: type,
172         timestamp: timestamp || null,
173         from: from,
174         to: to || [],
175         cc: cc || [],
176         bcc: bcc || [],
177         body: body,
178         isRead: isRead || false,
179         hasAttachment: hasAttachment || false,
180         isHighPriority: data.isHighPriority || false,
181         subject: data.subject || '',
182         inResponseTo: inResponseTo || null,
183         attachments: attachments
184     };
185     // id
186     Object.defineProperty(this, 'id', {
187         get: function() {
188             return _internal.id;
189         },
190         set: function(value) {
191             if (value instanceof InternalValues_) _internal.id = value.id;
192         },
193         enumerable: true
194     });
195
196     //conversationId
197     Object.defineProperty(this, 'conversationId', {
198         get: function() {
199             return _internal.conversationId;
200         },
201         set: function(value) {
202             if (value instanceof InternalValues_)
203                 _internal.conversationId = value.conversationId;
204         },
205         enumerable: true
206     });
207
208     // folderId
209     Object.defineProperty(this, 'folderId', {
210         get: function() {
211             return _internal.folderId;
212         },
213         set: function(value) {
214             if (value instanceof InternalValues_) _internal.folderId = value.folderId;
215         },
216         enumerable: true
217     });
218
219     // type
220     Object.defineProperty(this, 'type', {
221         get: function() {
222             return _internal.type;
223         },
224         set: function(value) {
225             return;
226         },
227         enumerable: true
228     });
229
230     // timestamp
231     Object.defineProperty(this, 'timestamp', {
232         get: function() {
233             return _internal.timestamp
234                 ? new Date(_internal.timestamp * 1000)
235                 : _internal.timestamp;
236         },
237         set: function(value) {
238             if (value instanceof InternalValues_) {
239                 _internal.timestamp = value.timestamp;
240             }
241         },
242         enumerable: true
243     });
244
245     // from
246     Object.defineProperty(this, 'from', {
247         get: function() {
248             return _internal.from;
249         },
250         set: function(value) {
251             if (value instanceof InternalValues_) _internal.from = value.from;
252         },
253         enumerable: true
254     });
255
256     // to
257     Object.defineProperty(this, 'to', {
258         get: function() {
259             return _internal.to;
260         },
261         set: function(value) {
262             if (value instanceof InternalValues_) value = value.to;
263             if (value instanceof Array) _internal.to = value;
264         },
265         enumerable: true
266     });
267
268     // cc
269     Object.defineProperty(this, 'cc', {
270         get: function() {
271             return _internal.cc;
272         },
273         set: function(value) {
274             if (value instanceof InternalValues_) value = value.cc;
275             if (value instanceof Array) _internal.cc = value;
276         },
277         enumerable: true
278     });
279
280     // bcc
281     Object.defineProperty(this, 'bcc', {
282         get: function() {
283             return _internal.bcc;
284         },
285         set: function(value) {
286             if (value instanceof InternalValues_) value = value.bcc;
287             if (value instanceof Array) _internal.bcc = value;
288         },
289         enumerable: true
290     });
291
292     // body
293     Object.defineProperty(this, 'body', {
294         get: function() {
295             return _internal.body;
296         },
297         set: function(value) {
298             if (value instanceof InternalValues_)
299                 _internal.body = new MessageBody(value.body);
300             if (value instanceof MessageBody) _internal.body = value;
301         },
302         enumerable: true
303     });
304
305     // isRead
306     Object.defineProperty(this, 'isRead', {
307         get: function() {
308             return _internal.isRead;
309         },
310         set: function(value) {
311             if (value instanceof InternalValues_) {
312                 value = value.isRead;
313             }
314             _internal.isRead = !!value;
315         },
316         enumerable: true
317     });
318
319     // hasAttachment
320     Object.defineProperty(this, 'hasAttachment', {
321         get: function() {
322             return _internal.attachments.length > 0;
323         },
324         set: function(value) {
325             if (value instanceof InternalValues_)
326                 _internal.hasAttachment = value.hasAttachment;
327         },
328         enumerable: true
329     });
330
331     // isHighPriority
332     Object.defineProperty(this, 'isHighPriority', {
333         get: function() {
334             return _internal.isHighPriority;
335         },
336         set: function(value) {
337             if (value instanceof InternalValues_) value = value.isHighPriority;
338             _internal.isHighPriority = !!value;
339         },
340         enumerable: true
341     });
342
343     // subject
344     Object.defineProperty(this, 'subject', {
345         get: function() {
346             return _internal.subject;
347         },
348         set: function(value) {
349             if (value instanceof InternalValues_) value = value.subject;
350             _internal.subject = String(value);
351         },
352         enumerable: true
353     });
354
355     // inResponseTo
356     Object.defineProperty(this, 'inResponseTo', {
357         get: function() {
358             return _internal.inResponseTo;
359         },
360         set: function(value) {
361             if (value instanceof InternalValues_)
362                 _internal.inResponseTo = value.inResponseTo;
363         },
364         enumerable: true
365     });
366
367     // messageStatus
368     Object.defineProperty(this, 'messageStatus', {
369         get: function() {
370             if (_internal.id) {
371                 var callArgs = {
372                     id: _internal.id,
373                     type: _internal.type
374                 };
375                 var result = native.callSync('MessageGetMessageStatus', callArgs);
376                 if (native.isSuccess(result)) {
377                     return native.getResultObject(result);
378                 }
379             }
380             return '';
381         },
382         set: function(value) {
383             return;
384         },
385         enumerable: true
386     });
387
388     // attachments
389     Object.defineProperty(this, 'attachments', {
390         get: function() {
391             return _internal.attachments;
392         },
393         set: function(value) {
394             if (value instanceof InternalValues_) {
395                 value = value.attachments;
396                 for (var k = 0; k < value.length; ++k) {
397                     if (!(value[k] instanceof tizen.MessageAttachment)) {
398                         if (_internal.attachments[k]) {
399                             updateInternal_(_internal.attachments[k], value[k]);
400                         } else {
401                             _internal.attachments[k] = new MessageAttachment(
402                                 new InternalValues_(value[k])
403                             );
404                         }
405                     } else {
406                         _internal.attachments[k] = value[k];
407                     }
408                 }
409                 // if new array is shorter than the old one, remove excess elements
410                 if (value.length < _internal.length) {
411                     _internal.splice(value.length, _internal.length - value.length);
412                 }
413             } else if (T_.isArray(value)) {
414                 for (var k = 0; k < value.length; ++k) {
415                     if (!(value[k] instanceof tizen.MessageAttachment)) {
416                         return;
417                     }
418                 }
419                 _internal.attachments = value;
420             }
421         },
422         enumerable: true
423     });
424 }
425
426 function MessageInit(data) {
427     if (!(this instanceof MessageInit)) {
428         return new MessageInit(data);
429     }
430     if (data === null || typeof data !== 'object') {
431         data = {};
432     }
433     propertyFactory_(this, 'subject', data.subject || '', Property.E | Property.W);
434     propertyFactory_(this, 'to', data.to || [], Property.E | Property.W);
435     propertyFactory_(this, 'cc', data.cc || [], Property.E | Property.W);
436     propertyFactory_(this, 'bcc', data.bcc || [], Property.E | Property.W);
437     propertyFactory_(this, 'plainBody', data.plainBody || '', Property.E | Property.W);
438     propertyFactory_(this, 'htmlBody', data.htmlBody || '', Property.E | Property.W);
439     propertyFactory_(
440         this,
441         'isHighPriority',
442         data.isHighPriority || false,
443         Property.E | Property.W
444     );
445 }
446
447 function MessageInit_(data) {
448     if (!(this instanceof MessageInit_)) {
449         return new MessageInit_(data);
450     }
451     if (!data || typeof data !== 'object') {
452         data = {};
453     }
454     this.id = data.id || null;
455     this.conversationId = data.conversationId || null;
456     this.folderId = data.folderId || null;
457     this.timestamp = data.timestamp || null;
458     this.from = data.from || '';
459     this.to = data.to || [];
460     this.cc = data.cc || [];
461     this.bcc = data.bcc || [];
462     this.isRead = data.isRead || false;
463     this.hasAttachment = data.hasAttachment || null;
464     this.isHighPriority = data.isHighPriority || false;
465     this.subject = data.subject || '';
466     this.inResponseTo = data.inResponseTo || null;
467     this.attachments = [];
468     this.plainBody = data.body ? data.body.plainBody : '';
469     this.htmlBody = data.body ? data.body.htmlBody : '';
470
471     var self = this;
472     if (data.attachments && data.attachments.constructor === Array) {
473         data.attachments.forEach(function(el) {
474             if (!el) return;
475
476             if (el.constructor === MessageAttachment) {
477                 self.attachments.push(el);
478             } else {
479                 self.attachments.push(new MessageAttachment(new InternalValues_(el)));
480             }
481         });
482     }
483 }
484
485 function MessageBody(data) {
486     if (!this instanceof MessageBody) {
487         return new MessageBody(data);
488     }
489     if (data === null || typeof data !== 'object') {
490         data = {};
491     }
492
493     var _internal = {
494         messageId: data.messageId || null,
495         loaded: data.loaded || false,
496         plainBody: data.plainBody || '',
497         htmlBody: data.htmlBody || '',
498         inlineAttachments: data.inlineAttachments || []
499     };
500
501     // messageId
502     Object.defineProperty(this, 'messageId', {
503         get: function() {
504             return _internal.messageId;
505         },
506         set: function(value) {
507             if (value instanceof InternalValues_) _internal.messageId = value.messageId;
508         },
509         enumerable: true
510     });
511
512     // loaded
513     Object.defineProperty(this, 'loaded', {
514         get: function() {
515             return _internal.loaded;
516         },
517         set: function(value) {
518             if (value instanceof InternalValues_) _internal.loaded = value.loaded;
519         },
520         enumerable: true
521     });
522
523     // plainBody
524     Object.defineProperty(this, 'plainBody', {
525         get: function() {
526             return _internal.plainBody;
527         },
528         set: function(value) {
529             if (value instanceof InternalValues_) {
530                 _internal.plainBody = String(value.plainBody);
531             } else {
532                 _internal.plainBody = String(value);
533             }
534         },
535         enumerable: true
536     });
537
538     // htmlBody
539     Object.defineProperty(this, 'htmlBody', {
540         get: function() {
541             return _internal.htmlBody;
542         },
543         set: function(value) {
544             if (value instanceof InternalValues_) {
545                 _internal.htmlBody = String(value.htmlBody);
546             } else {
547                 _internal.htmlBody = String(value);
548             }
549         },
550         enumerable: true
551     });
552
553     // inlineAttachments
554     Object.defineProperty(this, 'inlineAttachments', {
555         get: function() {
556             return _internal.inlineAttachments;
557         },
558         set: function(value) {
559             if (value instanceof InternalValues_) {
560                 _internal.inlineAttachments = value.inlineAttachments;
561             } else if (T_.isArray(value)) {
562                 _internal.inlineAttachments = value;
563             }
564         },
565         enumerable: true
566     });
567 }
568
569 var messageAttachmentsLoaded = {};
570
571 function MessageAttachment(first, second) {
572     validator_.isConstructorCall(this, MessageAttachment);
573     if (!this instanceof MessageAttachment) {
574         return new MessageAttachment(data);
575     }
576
577     var internalConstructor = first instanceof InternalValues_;
578     var _internal = {
579         messageId: internalConstructor ? first.messageId : null,
580         id: internalConstructor ? first.id : null,
581         mimeType: internalConstructor
582             ? first.mimeType
583             : undefined == second
584                 ? null
585                 : second,
586         filePath: internalConstructor ? first.filePath : first
587     };
588
589     // messageId
590     Object.defineProperty(this, 'messageId', {
591         get: function() {
592             return _internal.messageId;
593         },
594         set: function(value) {
595             if (value instanceof InternalValues_) _internal.messageId = value.messageId;
596         },
597         enumerable: true
598     });
599     // id
600     Object.defineProperty(this, 'id', {
601         get: function() {
602             return _internal.id;
603         },
604         set: function(value) {
605             if (value instanceof InternalValues_) _internal.id = value.id;
606         },
607         enumerable: true
608     });
609     // mimeType
610     Object.defineProperty(this, 'mimeType', {
611         get: function() {
612             return _internal.mimeType;
613         },
614         set: function(value) {
615             if (value instanceof InternalValues_) _internal.mimeType = value.mimeType;
616         },
617         enumerable: true
618     });
619     // filePath
620     Object.defineProperty(this, 'filePath', {
621         get: function() {
622             if (_internal.id && !messageAttachmentsLoaded[_internal.id]) {
623                 return null;
624             }
625
626             return _internal.filePath;
627         },
628         set: function(value) {
629             if (value instanceof InternalValues_) _internal.filePath = value.filePath;
630         },
631         enumerable: true
632     });
633 }
634
635 function Messaging() {}
636
637 /**
638  * Gets the messaging service of a given type for a given account.
639  * @param {!MessageServiceTag} messageServiceType Type of the services to be retrieved.
640  * @param {!MessageServiceArraySuccessCallback} successCallback Callback function that
641  *      is called when the services are successfully retrieved.
642  * @param {ErrorCallback} errorCallback Callback function that is called when
643  *      an error occurs.
644  */
645 Messaging.prototype.getMessageServices = function() {
646     var args = validator_.validateArgs(arguments, [
647         { name: 'messageServiceType', type: types_.ENUM, values: MessageServiceTag },
648         { name: 'successCallback', type: types_.FUNCTION },
649         { name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true }
650     ]);
651
652     var callArgs = { messageServiceType: args.messageServiceType };
653     var callback = function(result) {
654         if (native.isFailure(result)) {
655             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
656         } else {
657             var data = native.getResultObject(result);
658             var servicesArr = [];
659             data.forEach(function(e) {
660                 servicesArr.push(new MessageService(e));
661             });
662             args.successCallback(servicesArr);
663         }
664     };
665     var result = native.call('GetMessageServices', callArgs, callback);
666     if (native.isFailure(result)) {
667         throw native.getErrorObject(result);
668     }
669 };
670
671 function MessageStorage() {}
672 function MessageService(data) {
673     propertyFactory_(this, 'id', data.id, Property.E);
674     propertyFactory_(this, 'type', data.type, Property.E);
675     propertyFactory_(this, 'name', data.name, Property.E);
676     propertyFactory_(this, 'messageStorage', new MessageStorage(this), Property.E);
677 }
678
679 MessageService.prototype.sendMessage = function() {
680     var args = validator_.validateArgs(arguments, [
681         { name: 'message', type: types_.PLATFORM_OBJECT, values: tizen.Message },
682         {
683             name: 'successCallback',
684             type: types_.FUNCTION,
685             optional: true,
686             nullable: true
687         },
688         { name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true },
689         { name: 'simIndex', type: types_.LONG, optional: true, nullable: true }
690     ]);
691
692     if (args.message.type != this.type) {
693         throw new WebAPIException(WebAPIException.TYPE_MISMATCH_ERR);
694     }
695
696     var self = this;
697
698     var callArgs = {
699         message: args.message,
700         simIndex: args.simIndex || 1,
701         serviceId: self.id
702     };
703     var callback = function(result) {
704         if (native.isFailure(result)) {
705             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
706         } else {
707             var data = native.getResultObject(result);
708             var message = data.message;
709             if (message) {
710                 var body = message.body;
711                 if (body) {
712                     updateInternal_(args.message.body, body);
713                     delete message.body;
714                 }
715                 updateInternal_(args.message, message);
716             }
717             native.callIfPossible(args.successCallback, data.recipients);
718         }
719     };
720     var result = native.call('MessageServiceSendMessage', callArgs, callback);
721     if (native.isFailure(result)) {
722         throw native.getErrorObject(result);
723     }
724 };
725
726 MessageService.prototype.loadMessageBody = function() {
727     var args = validator_.validateArgs(arguments, [
728         { name: 'message', type: types_.PLATFORM_OBJECT, values: tizen.Message },
729         { name: 'successCallback', type: types_.FUNCTION },
730         { name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true }
731     ]);
732
733     if (args.message.type != this.type) {
734         throw new WebAPIException(WebAPIException.TYPE_MISMATCH_ERR);
735     }
736
737     var self = this;
738
739     var callArgs = {
740         message: args.message,
741         serviceId: self.id
742     };
743
744     var callback = function(result) {
745         if (native.isFailure(result)) {
746             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
747         } else {
748             var data = native.getResultObject(result);
749             var body = data.messageBody;
750             if (body) {
751                 updateInternal_(args.message.body, body);
752             }
753
754             args.successCallback(args.message);
755         }
756     };
757
758     var result = native.call('MessageServiceLoadMessageBody', callArgs, callback);
759
760     if (native.isFailure(result)) {
761         throw native.getErrorObject(result);
762     }
763 };
764 MessageService.prototype.loadMessageAttachment = function() {
765     var args = validator_.validateArgs(arguments, [
766         { name: 'attachment', type: types_.PLATFORM_OBJECT, values: MessageAttachment },
767         { name: 'successCallback', type: types_.FUNCTION },
768         { name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true }
769     ]);
770
771     var self = this;
772     var firstCall = false;
773     if (!messageAttachmentsLoaded[args.attachment.id]) {
774         firstCall = true;
775         messageAttachmentsLoaded[args.attachment.id] = true;
776     }
777
778     var callArgs = {
779         attachment: args.attachment,
780         serviceId: self.id
781     };
782
783     var callback = function(result) {
784         if (native.isFailure(result)) {
785             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
786         } else {
787             var data = native.getResultObject(result);
788             var messageAttachment = data.messageAttachment;
789             if (messageAttachment) {
790                 updateInternal_(args.attachment, messageAttachment);
791             }
792
793             args.successCallback(args.attachment);
794         }
795     };
796
797     var result = native.call('MessageServiceLoadMessageAttachment', callArgs, callback);
798
799     if (native.isFailure(result)) {
800         throw native.getErrorObject(result);
801     }
802 };
803
804 MessageService.prototype.sync = function() {
805     var args = validator_.validateArgs(arguments, [
806         {
807             name: 'successCallback',
808             type: types_.FUNCTION,
809             optional: true,
810             nullable: true
811         },
812         { name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true },
813         { name: 'limit', type: types_.UNSIGNED_LONG, optional: true, nullable: true }
814     ]);
815
816     var self = this;
817
818     var callArgs = {
819         id: self.id,
820         limit: args.limit || null
821     };
822
823     var callback = function(result) {
824         if (native.isFailure(result)) {
825             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
826         } else {
827             native.callIfPossible(args.successCallback);
828         }
829     };
830
831     var result = native.call('MessageServiceSync', callArgs, callback);
832
833     if (native.isFailure(result)) {
834         throw native.getErrorObject(result);
835     }
836
837     return native.getResultObject(result);
838 };
839
840 MessageService.prototype.syncFolder = function() {
841     var args = validator_.validateArgs(arguments, [
842         { name: 'folder', type: types_.PLATFORM_OBJECT, values: MessageFolder },
843         {
844             name: 'successCallback',
845             type: types_.FUNCTION,
846             optional: true,
847             nullable: true
848         },
849         { name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true },
850         { name: 'limit', type: types_.UNSIGNED_LONG, optional: true, nullable: true }
851     ]);
852
853     var self = this;
854
855     var callArgs = {
856         id: self.id,
857         folder: args.folder,
858         limit: args.limit || null
859     };
860
861     var callback = function(result) {
862         if (native.isFailure(result)) {
863             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
864         } else {
865             native.callIfPossible(args.successCallback);
866         }
867     };
868
869     var result = native.call('MessageServiceSyncFolder', callArgs, callback);
870
871     if (native.isFailure(result)) {
872         throw native.getErrorObject(result);
873     }
874
875     return native.getResultObject(result);
876 };
877
878 MessageService.prototype.stopSync = function() {
879     var args = validator_.validateArgs(arguments, [{ name: 'opId', type: types_.LONG }]);
880
881     var self = this;
882     var callArgs = {
883         id: self.id,
884         opId: args.opId
885     };
886     var result = native.callSync('MessageServiceStopSync', callArgs);
887     if (native.isFailure(result)) {
888         throw native.getErrorObject(result);
889     }
890 };
891
892 function MessageStorage(service) {
893     propertyFactory_(this, 'service', service);
894 }
895
896 MessageStorage.prototype.addDraftMessage = function() {
897     var args = validator_.validateArgs(arguments, [
898         { name: 'message', type: types_.PLATFORM_OBJECT, values: tizen.Message },
899         {
900             name: 'successCallback',
901             type: types_.FUNCTION,
902             optional: true,
903             nullable: true
904         },
905         { name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true }
906     ]);
907
908     if (args.message.type != this.service.type) {
909         throw new WebAPIException(WebAPIException.TYPE_MISMATCH_ERR);
910     }
911
912     var self = this;
913
914     var callArgs = {
915         message: args.message,
916         serviceId: self.service.id
917     };
918     var callback = function(result) {
919         if (native.isFailure(result)) {
920             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
921         } else {
922             var data = native.getResultObject(result);
923             var message = data.message;
924             if (message) {
925                 var body = message.body;
926                 if (body) {
927                     updateInternal_(args.message.body, body);
928                     delete message.body;
929                 }
930                 var attachments = message.attachments;
931                 if (attachments) {
932                     for (var i = 0; i < attachments.length; i++) {
933                         messageAttachmentsLoaded[attachments[i].id] = true;
934                     }
935                 }
936                 updateInternal_(args.message, message);
937             }
938             native.callIfPossible(args.successCallback, data.recipients);
939         }
940     };
941     var result = native.call('MessageStorageAddDraftMessage', callArgs, callback);
942     if (native.isFailure(result)) {
943         throw native.getErrorObject(result);
944     }
945 };
946
947 MessageStorage.prototype.findMessages = function() {
948     var args = validator_.validateArgs(arguments, [
949         {
950             name: 'filter',
951             type: types_.PLATFORM_OBJECT,
952             values: [
953                 tizen.AttributeFilter,
954                 tizen.AttributeRangeFilter,
955                 tizen.CompositeFilter
956             ]
957         },
958         { name: 'successCallback', type: types_.FUNCTION },
959         { name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true },
960         {
961             name: 'sort',
962             type: types_.PLATFORM_OBJECT,
963             values: tizen.SortMode,
964             optional: true,
965             nullable: true
966         },
967         { name: 'limit', type: types_.UNSIGNED_LONG, optional: true, nullable: true },
968         { name: 'offset', type: types_.UNSIGNED_LONG, optional: true, nullable: true }
969     ]);
970
971     var self = this;
972
973     var callArgs = {
974         filter: addTypeToFilter_(args.filter) || null,
975         sort: args.sort || null,
976         limit: args.limit || null,
977         offset: args.offset || null,
978         serviceId: self.service.id,
979         type: self.service.type
980     };
981     var callback = function(result) {
982         if (native.isFailure(result)) {
983             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
984         } else {
985             var data = native.getResultObject(result);
986             var messages = [];
987             data.forEach(function(el) {
988                 messages.push(new tizen.Message(el.type, new MessageInit_(el)));
989             });
990             native.callIfPossible(args.successCallback, messages);
991         }
992     };
993     var result = native.call('MessageStorageFindMessages', callArgs, callback);
994     if (native.isFailure(result)) {
995         throw native.getErrorObject(result);
996     }
997 };
998
999 MessageStorage.prototype.removeMessages = function() {
1000     var args = validator_.validateArgs(arguments, [
1001         { name: 'messages', type: types_.ARRAY, values: Message },
1002         {
1003             name: 'successCallback',
1004             type: types_.FUNCTION,
1005             optional: true,
1006             nullable: true
1007         },
1008         { name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true }
1009     ]);
1010
1011     var self = this;
1012
1013     args.messages.forEach(function(msg) {
1014         if (msg.type != self.service.type) {
1015             throw new WebAPIException(WebAPIException.TYPE_MISMATCH_ERR);
1016         }
1017     });
1018
1019     var callArgs = {
1020         messages: args.messages,
1021         serviceId: self.service.id,
1022         type: self.service.type
1023     };
1024     var callback = function(result) {
1025         if (native.isFailure(result)) {
1026             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
1027         } else {
1028             native.callIfPossible(args.successCallback);
1029         }
1030     };
1031     var result = native.call('MessageStorageRemoveMessages', callArgs, callback);
1032     if (native.isFailure(result)) {
1033         throw native.getErrorObject(result);
1034     }
1035 };
1036
1037 MessageStorage.prototype.updateMessages = function() {
1038     var args = validator_.validateArgs(arguments, [
1039         { name: 'messages', type: types_.ARRAY, values: Message },
1040         {
1041             name: 'successCallback',
1042             type: types_.FUNCTION,
1043             optional: true,
1044             nullable: true
1045         },
1046         { name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true }
1047     ]);
1048
1049     var self = this;
1050
1051     args.messages.forEach(function(msg) {
1052         if (msg.type != self.service.type) {
1053             throw new WebAPIException(WebAPIException.TYPE_MISMATCH_ERR);
1054         }
1055     });
1056
1057     var callArgs = {
1058         messages: args.messages,
1059         serviceId: self.service.id
1060     };
1061     var callback = function(result) {
1062         if (native.isFailure(result)) {
1063             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
1064         } else {
1065             var data = native.getResultObject(result);
1066             var originals = {},
1067                 i = args.messages.length,
1068                 m;
1069             while (i--) {
1070                 m = args.messages[i];
1071                 if (m.id) {
1072                     originals[m.id] = m;
1073                 }
1074             }
1075
1076             i = data.length;
1077             while (i--) {
1078                 m = data[i];
1079                 if (originals[m.oldId]) {
1080                     var body = m.body;
1081                     if (body) {
1082                         updateInternal_(originals[m.oldId].body, body);
1083                         delete m.body;
1084                     }
1085                     updateInternal_(originals[m.oldId], m);
1086                 }
1087             }
1088
1089             native.callIfPossible(args.successCallback);
1090         }
1091     };
1092     var result = native.call('MessageStorageUpdateMessages', callArgs, callback);
1093     if (native.isFailure(result)) {
1094         throw native.getErrorObject(result);
1095     }
1096 };
1097
1098 MessageStorage.prototype.findConversations = function() {
1099     var args = validator_.validateArgs(arguments, [
1100         {
1101             name: 'filter',
1102             type: types_.PLATFORM_OBJECT,
1103             values: [
1104                 tizen.AttributeFilter,
1105                 tizen.AttributeRangeFilter,
1106                 tizen.CompositeFilter
1107             ]
1108         },
1109         { name: 'successCallback', type: types_.FUNCTION },
1110         { name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true },
1111         {
1112             name: 'sort',
1113             type: types_.PLATFORM_OBJECT,
1114             values: tizen.SortMode,
1115             optional: true,
1116             nullable: true
1117         },
1118         { name: 'limit', type: types_.UNSIGNED_LONG, optional: true, nullable: true },
1119         { name: 'offset', type: types_.UNSIGNED_LONG, optional: true, nullable: true }
1120     ]);
1121
1122     var self = this;
1123
1124     var callArgs = {
1125         filter: addTypeToFilter_(args.filter),
1126         sort: args.sort || null,
1127         limit: args.limit || null,
1128         offset: args.offset || null,
1129         serviceId: self.service.id
1130     };
1131     var callback = function(result) {
1132         if (native.isFailure(result)) {
1133             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
1134         } else {
1135             var data = native.getResultObject(result);
1136             var conversations = [];
1137             data.forEach(function(el) {
1138                 conversations.push(new MessageConversation(el));
1139             });
1140             args.successCallback(conversations);
1141         }
1142     };
1143     var result = native.call('MessageStorageFindConversations', callArgs, callback);
1144     if (native.isFailure(result)) {
1145         throw native.getErrorObject(result);
1146     }
1147 };
1148
1149 MessageStorage.prototype.removeConversations = function() {
1150     var args = validator_.validateArgs(arguments, [
1151         { name: 'conversations', type: types_.ARRAY },
1152         {
1153             name: 'successCallback',
1154             type: types_.FUNCTION,
1155             optional: true,
1156             nullable: true
1157         },
1158         { name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true }
1159     ]);
1160
1161     args.conversations.forEach(function(el) {
1162         if (!el || el.constructor !== MessageConversation) {
1163             throw new WebAPIException(WebAPIException.TYPE_MISMATCH_ERR);
1164         }
1165     });
1166
1167     var self = this;
1168
1169     var callArgs = {
1170         conversations: args.conversations,
1171         serviceId: self.service.id,
1172         type: self.service.type
1173     };
1174     var callback = function(result) {
1175         if (native.isFailure(result)) {
1176             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
1177         } else {
1178             native.callIfPossible(args.successCallback);
1179         }
1180     };
1181     var result = native.call('MessageStorageRemoveConversations', callArgs, callback);
1182     if (native.isFailure(result)) {
1183         throw native.getErrorObject(result);
1184     }
1185 };
1186
1187 MessageStorage.prototype.findFolders = function() {
1188     var args = validator_.validateArgs(arguments, [
1189         {
1190             name: 'filter',
1191             type: types_.PLATFORM_OBJECT,
1192             values: [
1193                 tizen.AttributeFilter,
1194                 tizen.AttributeRangeFilter,
1195                 tizen.CompositeFilter
1196             ]
1197         },
1198         { name: 'successCallback', type: types_.FUNCTION },
1199         { name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true }
1200     ]);
1201
1202     var self = this;
1203
1204     var callArgs = {
1205         filter: addTypeToFilter_(args.filter),
1206         sort: args.sort || null,
1207         limit: args.limit || null,
1208         offset: args.offset || null,
1209         serviceId: self.service.id
1210     };
1211     var callback = function(result) {
1212         if (native.isFailure(result)) {
1213             native.callIfPossible(args.errorCallback, native.getErrorObject(result));
1214         } else {
1215             var data = native.getResultObject(result);
1216             var folders = [];
1217             data.forEach(function(el) {
1218                 folders.push(new MessageFolder(el));
1219             });
1220             args.successCallback(folders);
1221         }
1222     };
1223     var result = native.call('MessageStorageFindFolders', callArgs, callback);
1224     if (native.isFailure(result)) {
1225         throw native.getErrorObject(result);
1226     }
1227 };
1228
1229 function pushMessage(messages, el) {
1230     messages.push(new tizen.Message(el.type, new MessageInit_(el)));
1231 }
1232
1233 function pushConversation(conversations, el) {
1234     conversations.push(new MessageConversation(el));
1235 }
1236
1237 function pushFolder(folders, el) {
1238     folders.push(new MessageFolder(el));
1239 }
1240
1241 function getListenerFunction(listenerMap, pushMethod) {
1242     return function(msg) {
1243         var action = msg.action;
1244         var data = native.getResultObject(msg);
1245         var messages = [];
1246         data.forEach(function(el) {
1247             pushMethod(messages, el);
1248         });
1249
1250         for (var key in listenerMap) {
1251             if (listenerMap.hasOwnProperty(key)) {
1252                 native.callIfPossible(listenerMap[key][action], messages);
1253             }
1254         }
1255     };
1256 }
1257
1258 var MESSAGES_CHANGE_LISTENER = 'MessagesChangeListener';
1259 var MessagesChangeListeners = {};
1260 native.addListener(
1261     MESSAGES_CHANGE_LISTENER,
1262     getListenerFunction(MessagesChangeListeners, pushMessage)
1263 );
1264
1265 var CONVERSATIONS_CHANGE_LISTENER = 'ConversationsChangeListener';
1266 var ConversationsChangeListeners = {};
1267 native.addListener(
1268     CONVERSATIONS_CHANGE_LISTENER,
1269     getListenerFunction(ConversationsChangeListeners, pushConversation)
1270 );
1271
1272 var FOLDERS_CHANGE_LISTENER = 'FoldersChangeListener';
1273 var FoldersChangeListeners = {};
1274 native.addListener(
1275     FOLDERS_CHANGE_LISTENER,
1276     getListenerFunction(FoldersChangeListeners, pushFolder)
1277 );
1278
1279 MessageStorage.prototype.addMessagesChangeListener = function() {
1280     var args = validator_.validateArgs(arguments, [
1281         {
1282             name: 'messagesChangeCallback',
1283             type: types_.LISTENER,
1284             values: ['messagesadded', 'messagesupdated', 'messagesremoved']
1285         },
1286         {
1287             name: 'filter',
1288             type: types_.PLATFORM_OBJECT,
1289             values: [
1290                 tizen.AttributeFilter,
1291                 tizen.AttributeRangeFilter,
1292                 tizen.CompositeFilter
1293             ],
1294             optional: true,
1295             nullable: true
1296         }
1297     ]);
1298
1299     var self = this;
1300
1301     var callArgs = {
1302         filter: args.filter ? addTypeToFilter_(args.filter) : null,
1303         serviceId: self.service.id
1304     };
1305     var result = native.callSync('MessageStorageAddMessagesChangeListener', callArgs);
1306     if (native.isFailure(result)) {
1307         throw native.getErrorObject(result);
1308     } else {
1309         var opId = native.getResultObject(result);
1310         MessagesChangeListeners[opId] = args.messagesChangeCallback;
1311         return opId;
1312     }
1313 };
1314
1315 MessageStorage.prototype.addConversationsChangeListener = function() {
1316     var args = validator_.validateArgs(arguments, [
1317         {
1318             name: 'conversationsChangeCallback',
1319             type: types_.LISTENER,
1320             values: ['conversationsadded', 'conversationsupdated', 'conversationsremoved']
1321         },
1322         {
1323             name: 'filter',
1324             type: types_.PLATFORM_OBJECT,
1325             values: [
1326                 tizen.AttributeFilter,
1327                 tizen.AttributeRangeFilter,
1328                 tizen.CompositeFilter
1329             ],
1330             optional: true,
1331             nullable: true
1332         }
1333     ]);
1334
1335     var self = this;
1336
1337     var callArgs = {
1338         filter: args.filter ? addTypeToFilter_(args.filter) : null,
1339         serviceId: self.service.id
1340     };
1341     var result = native.callSync(
1342         'MessageStorageAddConversationsChangeListener',
1343         callArgs
1344     );
1345     if (native.isFailure(result)) {
1346         throw native.getErrorObject(result);
1347     } else {
1348         var opId = native.getResultObject(result);
1349         ConversationsChangeListeners[opId] = args.conversationsChangeCallback;
1350         return opId;
1351     }
1352 };
1353
1354 MessageStorage.prototype.addFoldersChangeListener = function() {
1355     var args = validator_.validateArgs(arguments, [
1356         {
1357             name: 'foldersChangeCallback',
1358             type: types_.LISTENER,
1359             values: ['foldersadded', 'foldersupdated', 'foldersremoved']
1360         },
1361         {
1362             name: 'filter',
1363             type: types_.PLATFORM_OBJECT,
1364             values: [
1365                 tizen.AttributeFilter,
1366                 tizen.AttributeRangeFilter,
1367                 tizen.CompositeFilter
1368             ],
1369             optional: true,
1370             nullable: true
1371         }
1372     ]);
1373
1374     var self = this;
1375
1376     var callArgs = {
1377         filter: args.filter ? addTypeToFilter_(args.filter) : null,
1378         serviceId: self.service.id
1379     };
1380     var result = native.callSync('MessageStorageAddFolderChangeListener', callArgs);
1381     if (native.isFailure(result)) {
1382         throw native.getErrorObject(result);
1383     } else {
1384         var opId = native.getResultObject(result);
1385         FoldersChangeListeners[opId] = args.foldersChangeCallback;
1386         return opId;
1387     }
1388 };
1389
1390 MessageStorage.prototype.removeChangeListener = function() {
1391     var args = validator_.validateArgs(arguments, [
1392         { name: 'watchId', type: types_.LONG }
1393     ]);
1394
1395     var self = this;
1396
1397     var callArgs = {
1398         watchId: args.watchId,
1399         serviceId: self.service.id
1400     };
1401     var result = native.callSync('MessageStorageRemoveChangeListener', callArgs);
1402     if (native.isFailure(result)) {
1403         throw native.getErrorObject(result);
1404     } else {
1405         if (MessagesChangeListeners.hasOwnProperty(args.watchId)) {
1406             delete MessagesChangeListeners[args.watchId];
1407         } else if (ConversationsChangeListeners.hasOwnProperty(args.watchId)) {
1408             delete ConversationsChangeListeners[args.watchId];
1409         } else if (FoldersChangeListeners.hasOwnProperty(args.watchId)) {
1410             delete FoldersChangeListeners[args.watchId];
1411         }
1412     }
1413 };
1414
1415 function MessageConversation(data) {
1416     propertyFactory_(this, 'id', data.id || null, Property.E);
1417     propertyFactory_(this, 'type', data.type || '', Property.E);
1418     propertyFactory_(
1419         this,
1420         'timestamp',
1421         data.timestamp ? new Date(data.timestamp * 1000) : null,
1422         Property.E
1423     );
1424     propertyFactory_(this, 'messageCount', data.messageCount || 0, Property.E);
1425     propertyFactory_(this, 'unreadMessages', data.unreadMessages || 0, Property.E);
1426     propertyFactory_(this, 'preview', data.preview || '', Property.E);
1427     propertyFactory_(this, 'subject', data.subject || '', Property.E);
1428     propertyFactory_(this, 'isRead', data.isRead || false, Property.E);
1429     propertyFactory_(this, 'from', data.from || null, Property.E);
1430     propertyFactory_(this, 'to', data.to || [], Property.E);
1431     propertyFactory_(this, 'cc', data.cc || [], Property.E);
1432     propertyFactory_(this, 'bcc', data.bcc || [], Property.E);
1433     propertyFactory_(this, 'lastMessageId', data.lastMessageId || null, Property.E);
1434 }
1435
1436 function MessageFolder(data) {
1437     var _internal = {
1438         id: data.id || null,
1439         parentId: data.parentId || null,
1440         serviceId: data.serviceId || '',
1441         contentType: data.contentType || '',
1442         name: data.name || '',
1443         path: data.path || '',
1444         type: data.type || '',
1445         synchronizable: data.synchronizable || false
1446     };
1447
1448     Object.defineProperty(this, 'id', {
1449         get: function() {
1450             return _internal.id;
1451         },
1452         enumerable: true
1453     });
1454
1455     Object.defineProperty(this, 'parentId', {
1456         get: function() {
1457             return _internal.parentId;
1458         },
1459         enumerable: true
1460     });
1461
1462     Object.defineProperty(this, 'serviceId', {
1463         get: function() {
1464             return _internal.serviceId;
1465         },
1466         enumerable: true
1467     });
1468
1469     Object.defineProperty(this, 'contentType', {
1470         get: function() {
1471             return _internal.contentType;
1472         },
1473         enumerable: true
1474     });
1475
1476     Object.defineProperty(this, 'name', {
1477         get: function() {
1478             return _internal.name;
1479         },
1480         set: function(value) {
1481             if (value) _internal.name = value;
1482         },
1483         enumerable: true
1484     });
1485
1486     Object.defineProperty(this, 'path', {
1487         get: function() {
1488             return _internal.path;
1489         },
1490         enumerable: true
1491     });
1492
1493     Object.defineProperty(this, 'type', {
1494         get: function() {
1495             return _internal.type;
1496         },
1497         enumerable: true
1498     });
1499
1500     Object.defineProperty(this, 'synchronizable', {
1501         get: function() {
1502             return _internal.synchronizable;
1503         },
1504         set: function(value) {
1505             _internal.synchronizable = Boolean(value);
1506         },
1507         enumerable: true
1508     });
1509 }
1510
1511 tizen.Message = Message;
1512
1513 tizen.MessageAttachment = MessageAttachment;
1514
1515 exports = new Messaging();