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