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