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