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