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