1 /**
2 * @file usernotifications.cpp
3 * @brief additional megaclient code for user notifications
4 *
5 * (c) 2013-2018 by Mega Limited, Auckland, New Zealand
6 *
7 * This file is part of the MEGA SDK - Client Access Engine.
8 *
9 * Applications using the MEGA API must present a valid application key
10 * and comply with the the rules set forth in the Terms of Service.
11 *
12 * The MEGA SDK is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15 *
16 * @copyright Simplified (2-clause) BSD License.
17 *
18 * You should have received a copy of the license along with this
19 * program.
20 */
21
22 #include "mega.h"
23 #include "mega/megaclient.h"
24 #include <utility>
25
26 namespace mega {
27
UserAlertRaw()28 UserAlertRaw::UserAlertRaw()
29 : t(0)
30 {
31 }
32
field(nameid nid) const33 JSON UserAlertRaw::field(nameid nid) const
34 {
35 map<nameid, string>::const_iterator i = fields.find(nid);
36 JSON j;
37 j.pos = i == fields.end() ? NULL : i->second.c_str();
38 return j;
39 }
40
has(nameid nid) const41 bool UserAlertRaw::has(nameid nid) const
42 {
43 JSON j = field(nid);
44 return j.pos != NULL;
45 }
46
getint(nameid nid,int default_value) const47 int UserAlertRaw::getint(nameid nid, int default_value) const
48 {
49 JSON j = field(nid);
50 return j.pos && j.isnumeric() ? int(j.getint()) : default_value;
51 }
52
getint64(nameid nid,int64_t default_value) const53 int64_t UserAlertRaw::getint64(nameid nid, int64_t default_value) const
54 {
55 JSON j = field(nid);
56 return j.pos && j.isnumeric() ? j.getint() : default_value;
57 }
58
gethandle(nameid nid,int handlesize,handle default_value) const59 handle UserAlertRaw::gethandle(nameid nid, int handlesize, handle default_value) const
60 {
61 JSON j = field(nid);
62 byte buf[9] = { 0 };
63 return (j.pos && handlesize == Base64::atob(j.pos, buf, sizeof(buf))) ? MemAccess::get<handle>((const char*)buf) : default_value;
64 }
65
getnameid(nameid nid,nameid default_value) const66 nameid UserAlertRaw::getnameid(nameid nid, nameid default_value) const
67 {
68 JSON j = field(nid);
69 nameid id = 0;
70 while (*j.pos)
71 {
72 id = (id << 8) + *((const unsigned char*&)j.pos)++;
73 }
74
75 return id ? id : default_value;
76 }
77
getstring(nameid nid,const char * default_value) const78 string UserAlertRaw::getstring(nameid nid, const char* default_value) const
79 {
80 JSON j = field(nid);
81 return j.pos ? j.pos : default_value;
82 }
83
gethandletypearray(nameid nid,vector<handletype> & v) const84 bool UserAlertRaw::gethandletypearray(nameid nid, vector<handletype>& v) const
85 {
86 JSON j = field(nid);
87 if (j.pos && j.enterarray())
88 {
89 for (;;)
90 {
91 if (j.enterobject())
92 {
93 handletype ht;
94 ht.h = UNDEF;
95 ht.t = -1;
96 bool fields = true;
97 while (fields)
98 {
99 switch (j.getnameid())
100 {
101 case 'h':
102 ht.h = j.gethandle(MegaClient::NODEHANDLE);
103 break;
104 case 't':
105 ht.t = int(j.getint());
106 break;
107 case EOO:
108 fields = false;
109 break;
110 default:
111 j.storeobject(NULL);
112 }
113 }
114 v.push_back(ht);
115 j.leaveobject();
116 }
117 else
118 {
119 break;
120 }
121 }
122 j.leavearray();
123 return true;
124 }
125 return false;
126 }
127
getstringarray(nameid nid,vector<string> & v) const128 bool UserAlertRaw::getstringarray(nameid nid, vector<string>& v) const
129 {
130 JSON j = field(nid);
131 if (j.pos && j.enterarray())
132 {
133 for (;;)
134 {
135 string s;
136 if (j.storeobject(&s))
137 {
138 v.push_back(s);
139 }
140 else
141 {
142 break;
143 }
144 }
145 j.leavearray();
146 }
147 return false;
148 }
149
UserAlertFlags()150 UserAlertFlags::UserAlertFlags()
151 : cloud_enabled(true)
152 , contacts_enabled(true)
153 , cloud_newfiles(true)
154 , cloud_newshare(true)
155 , cloud_delshare(true)
156 , contacts_fcrin(true)
157 , contacts_fcrdel(true)
158 , contacts_fcracpt(true)
159 {
160 }
161
UserAlertPendingContact()162 UserAlertPendingContact::UserAlertPendingContact()
163 : u(0)
164 {
165 }
166
167
168
Base(UserAlertRaw & un,unsigned int cid)169 UserAlert::Base::Base(UserAlertRaw& un, unsigned int cid)
170 {
171 id = cid;
172 type = un.t;
173 m_time_t timeDelta = un.getint64(MAKENAMEID2('t', 'd'), 0);
174 timestamp = m_time() - timeDelta;
175 userHandle = un.gethandle('u', MegaClient::USERHANDLE, UNDEF);
176 userEmail = un.getstring('m', "");
177
178 seen = false; // to be updated on EOO
179 relevant = true;
180 tag = -1;
181 }
182
Base(nameid t,handle uh,const string & email,m_time_t ts,unsigned int cid)183 UserAlert::Base::Base(nameid t, handle uh, const string& email, m_time_t ts, unsigned int cid)
184 {
185 id = cid;
186 type = t;
187 userHandle = uh;
188 userEmail = email;
189 timestamp = ts;
190 seen = false;
191 relevant = true;
192 tag = -1;
193 }
194
~Base()195 UserAlert::Base::~Base()
196 {
197 }
198
updateEmail(MegaClient * mc)199 void UserAlert::Base::updateEmail(MegaClient* mc)
200 {
201 if (User* u = mc->finduser(userHandle))
202 {
203 userEmail = u->email;
204 }
205 }
206
checkprovisional(handle,MegaClient *)207 bool UserAlert::Base::checkprovisional(handle , MegaClient*)
208 {
209 return true;
210 }
211
text(string & header,string & title,MegaClient * mc)212 void UserAlert::Base::text(string& header, string& title, MegaClient* mc)
213 {
214 // should be overridden
215 updateEmail(mc);
216 ostringstream s;
217 s << "notification: type " << type << " time " << timestamp << " user " << userHandle << " seen " << seen;
218 title = s.str();
219 header = userEmail;
220 }
221
IncomingPendingContact(UserAlertRaw & un,unsigned int id)222 UserAlert::IncomingPendingContact::IncomingPendingContact(UserAlertRaw& un, unsigned int id)
223 : Base(un, id)
224 {
225 requestWasDeleted = un.getint64(MAKENAMEID3('d', 't', 's'), 0) != 0;
226 requestWasReminded = un.getint64(MAKENAMEID3('r', 't', 's'), 0) != 0;
227 }
228
IncomingPendingContact(m_time_t dts,m_time_t rts,handle uh,const string & email,m_time_t timestamp,unsigned int id)229 UserAlert::IncomingPendingContact::IncomingPendingContact(m_time_t dts, m_time_t rts, handle uh, const string& email, m_time_t timestamp, unsigned int id)
230 : Base(UserAlert::type_ipc, uh, email, timestamp, id)
231 {
232 requestWasDeleted = dts != 0;
233 requestWasReminded = rts != 0;
234
235 if (requestWasDeleted)
236 {
237 this->timestamp = dts;
238 }
239
240 if (requestWasReminded)
241 {
242 this->timestamp = rts;
243 }
244 }
245
text(string & header,string & title,MegaClient * mc)246 void UserAlert::IncomingPendingContact::text(string& header, string& title, MegaClient* mc)
247 {
248 updateEmail(mc);
249 if (requestWasDeleted)
250 {
251 title = "Cancelled their contact request"; // 7151
252 }
253 else if (requestWasReminded)
254 {
255 title = "Reminder: You have a contact request"; // 7150
256 }
257 else
258 {
259 title = "Sent you a contact request"; // 5851
260 }
261 header = userEmail;
262 }
263
ContactChange(UserAlertRaw & un,unsigned int id)264 UserAlert::ContactChange::ContactChange(UserAlertRaw& un, unsigned int id)
265 : Base(un, id)
266 {
267 action = un.getint('c', -1);
268 relevant = action >= 0 && action < 4;
269 assert(action >= 0 && action < 4);
270 otherUserHandle = un.gethandle(MAKENAMEID2('o', 'u'), MegaClient::USERHANDLE, UNDEF);
271 }
272
ContactChange(int c,handle uh,const string & email,m_time_t timestamp,unsigned int id)273 UserAlert::ContactChange::ContactChange(int c, handle uh, const string& email, m_time_t timestamp, unsigned int id)
274 : Base(UserAlert::type_c, uh, email, timestamp, id)
275 {
276 action = c;
277 assert(action >= 0 && action < 4);
278 }
279
checkprovisional(handle ou,MegaClient * mc)280 bool UserAlert::ContactChange::checkprovisional(handle ou, MegaClient* mc)
281 {
282 return action == 1 || ou != mc->me;
283 }
284
text(string & header,string & title,MegaClient * mc)285 void UserAlert::ContactChange::text(string& header, string& title, MegaClient* mc)
286 {
287 updateEmail(mc);
288
289 if (action == 0)
290 {
291 title = "Deleted you as a contact"; // 7146
292 }
293 else if (action == 1)
294 {
295 title = "Contact relationship established"; // 7145
296 }
297 else if (action == 2)
298 {
299 title = "Account has been deleted/deactivated"; // 7144
300 }
301 else if (action == 3)
302 {
303 title = "Blocked you as a contact"; //7143
304 }
305 header = userEmail;
306 }
307
UpdatedPendingContactIncoming(UserAlertRaw & un,unsigned int id)308 UserAlert::UpdatedPendingContactIncoming::UpdatedPendingContactIncoming(UserAlertRaw& un, unsigned int id)
309 : Base(un, id)
310 {
311 action = un.getint('s', -1);
312 relevant = action >= 1 && action < 4;
313 }
314
UpdatedPendingContactIncoming(int s,handle uh,const string & email,m_time_t timestamp,unsigned int id)315 UserAlert::UpdatedPendingContactIncoming::UpdatedPendingContactIncoming(int s, handle uh, const string& email, m_time_t timestamp, unsigned int id)
316 : Base(type_upci, uh, email, timestamp, id)
317 , action(s)
318 {
319 }
320
text(string & header,string & title,MegaClient * mc)321 void UserAlert::UpdatedPendingContactIncoming::text(string& header, string& title, MegaClient* mc)
322 {
323 updateEmail(mc);
324 if (action == 1)
325 {
326 title = "You ignored a contact request"; // 7149
327 }
328 else if (action == 2)
329 {
330 title = "You accepted a contact request"; // 7148
331 }
332 else if (action == 3)
333 {
334 title = "You denied a contact request"; // 7147
335 }
336 header = userEmail;
337 }
338
UpdatedPendingContactOutgoing(UserAlertRaw & un,unsigned int id)339 UserAlert::UpdatedPendingContactOutgoing::UpdatedPendingContactOutgoing(UserAlertRaw& un, unsigned int id)
340 : Base(un, id)
341 {
342 action = un.getint('s', -1);
343 relevant = action == 2 || action == 3;
344 }
345
UpdatedPendingContactOutgoing(int s,handle uh,const string & email,m_time_t timestamp,unsigned int id)346 UserAlert::UpdatedPendingContactOutgoing::UpdatedPendingContactOutgoing(int s, handle uh, const string& email, m_time_t timestamp, unsigned int id)
347 : Base(type_upco, uh, email, timestamp, id)
348 , action(s)
349 {
350 }
351
text(string & header,string & title,MegaClient * mc)352 void UserAlert::UpdatedPendingContactOutgoing::text(string& header, string& title, MegaClient* mc)
353 {
354 updateEmail(mc);
355 if (action == 2)
356 {
357 title = "Accepted your contact request"; // 5852
358 }
359 else if (action == 3)
360 {
361 title = "Denied your contact request"; // 5853
362 }
363 header = userEmail;
364 }
365
NewShare(UserAlertRaw & un,unsigned int id)366 UserAlert::NewShare::NewShare(UserAlertRaw& un, unsigned int id)
367 : Base(un, id)
368 {
369 folderhandle = un.gethandle('n', MegaClient::NODEHANDLE, UNDEF);
370 }
371
NewShare(handle h,handle uh,const string & email,m_time_t timestamp,unsigned int id)372 UserAlert::NewShare::NewShare(handle h, handle uh, const string& email, m_time_t timestamp, unsigned int id)
373 : Base(type_share, uh, email, timestamp, id)
374 {
375 folderhandle = h;
376 }
377
text(string & header,string & title,MegaClient * mc)378 void UserAlert::NewShare::text(string& header, string& title, MegaClient* mc)
379 {
380 updateEmail(mc);
381 if (!userEmail.empty())
382 {
383 title = "New shared folder from " + userEmail; // 824
384 }
385 else
386 {
387 title = "New shared folder"; // 825
388 }
389 header = userEmail;
390 }
391
DeletedShare(UserAlertRaw & un,unsigned int id)392 UserAlert::DeletedShare::DeletedShare(UserAlertRaw& un, unsigned int id)
393 : Base(un, id)
394 {
395 ownerHandle = un.gethandle('o', MegaClient::USERHANDLE, UNDEF);
396 folderHandle = un.gethandle('n', MegaClient::NODEHANDLE, UNDEF);
397 }
398
DeletedShare(handle uh,const string & email,handle ownerhandle,handle folderhandle,m_time_t ts,unsigned int id)399 UserAlert::DeletedShare::DeletedShare(handle uh, const string& email, handle ownerhandle, handle folderhandle, m_time_t ts, unsigned int id)
400 : Base(type_dshare, uh, email, ts, id)
401 {
402 ownerHandle = ownerhandle;
403 folderHandle = folderhandle;
404 }
405
updateEmail(MegaClient * mc)406 void UserAlert::DeletedShare::updateEmail(MegaClient* mc)
407 {
408 Base::updateEmail(mc);
409
410 if (Node* n = mc->nodebyhandle(folderHandle))
411 {
412 folderPath = n->displaypath();
413 folderName = n->displayname();
414 }
415 }
416
text(string & header,string & title,MegaClient * mc)417 void UserAlert::DeletedShare::text(string& header, string& title, MegaClient* mc)
418 {
419 updateEmail(mc);
420 ostringstream s;
421
422 if (userHandle == ownerHandle)
423 {
424 if (!userEmail.empty())
425 {
426 s << "Access to folders shared by " << userEmail << " was removed"; // 7879
427 }
428 else
429 {
430 s << "Access to folders was removed"; // 7880
431 }
432 }
433 else
434 {
435 if (!userEmail.empty())
436 {
437 s << "User " << userEmail << " has left the shared folder " << folderName; //19153
438 }
439 else
440 {
441 s << "A user has left the shared folder " << folderName; //19154
442 }
443 }
444 title = s.str();
445 header = userEmail;
446 }
447
NewSharedNodes(UserAlertRaw & un,unsigned int id)448 UserAlert::NewSharedNodes::NewSharedNodes(UserAlertRaw& un, unsigned int id)
449 : Base(un, id), fileCount(0), folderCount(0)
450 {
451 std::vector<UserAlertRaw::handletype> f;
452 un.gethandletypearray('f', f);
453 parentHandle = un.gethandle('n', MegaClient::NODEHANDLE, UNDEF);
454
455 // Count the number of new files and folders
456 for (size_t n = f.size(); n--; )
457 {
458 ++(f[n].t > 0 ? folderCount : fileCount);
459 }
460 }
461
NewSharedNodes(int nfolders,int nfiles,handle uh,handle ph,m_time_t timestamp,unsigned int id)462 UserAlert::NewSharedNodes::NewSharedNodes(int nfolders, int nfiles, handle uh, handle ph, m_time_t timestamp, unsigned int id)
463 : Base(UserAlert::type_put, uh, string(), timestamp, id)
464 , parentHandle(ph)
465 {
466 assert(!ISUNDEF(uh));
467 folderCount = nfolders;
468 fileCount = nfiles;
469 }
470
text(string & header,string & title,MegaClient * mc)471 void UserAlert::NewSharedNodes::text(string& header, string& title, MegaClient* mc)
472 {
473 updateEmail(mc);
474 ostringstream notificationText;
475
476 // Get wording for the number of files and folders added
477 if ((folderCount > 1) && (fileCount > 1)) {
478 notificationText << folderCount << " folders and " << fileCount << " files";
479 }
480 else if ((folderCount > 1) && (fileCount == 1)) {
481 notificationText << folderCount << " folders and 1 file";
482 }
483 else if ((folderCount == 1) && (fileCount > 1)) {
484 notificationText << "1 folder and " << fileCount << " files";
485 }
486 else if ((folderCount == 1) && (fileCount == 1)) {
487 notificationText << "1 folder and 1 file";
488 }
489 else if (folderCount > 1) {
490 notificationText << folderCount << " folders";
491 }
492 else if (fileCount > 1) {
493 notificationText << fileCount << " files";
494 }
495 else if (folderCount == 1) {
496 notificationText << "1 folder";
497 }
498 else if (fileCount == 1) {
499 notificationText << "1 file";
500 }
501
502 // Set wording of the title
503 if (!userEmail.empty())
504 {
505 title = userEmail + " added " + notificationText.str();
506 }
507 else if ((fileCount + folderCount) > 1)
508 {
509 title = notificationText.str() + " have been added";
510 }
511 else {
512 title = notificationText.str() + " has been added";
513 }
514 header = userEmail;
515 }
516
RemovedSharedNode(UserAlertRaw & un,unsigned int id)517 UserAlert::RemovedSharedNode::RemovedSharedNode(UserAlertRaw& un, unsigned int id)
518 : Base(un, id)
519 {
520 vector<string> handles;
521 un.getstringarray('n', handles);
522 itemsNumber = handles.empty() ? 1 : handles.size();
523 }
524
RemovedSharedNode(int nitems,handle uh,m_time_t timestamp,unsigned int id)525 UserAlert::RemovedSharedNode::RemovedSharedNode(int nitems, handle uh, m_time_t timestamp, unsigned int id)
526 : Base(UserAlert::type_d, uh, string(), timestamp, id)
527 {
528 itemsNumber = nitems;
529 }
530
text(string & header,string & title,MegaClient * mc)531 void UserAlert::RemovedSharedNode::text(string& header, string& title, MegaClient* mc)
532 {
533 updateEmail(mc);
534 ostringstream s;
535 if (itemsNumber > 1)
536 {
537 s << "Removed " << itemsNumber << " items from a share"; // 8913
538 }
539 else
540 {
541 s << "Removed item from shared folder"; // 8910
542 }
543 title = s.str();
544 header = userEmail;
545 }
546
getProPlanName()547 string UserAlert::Payment::getProPlanName()
548 {
549 switch (planNumber) {
550 case 1:
551 return "PRO I"; // 5819
552 case 2:
553 return "PRO II"; // 6125
554 case 3:
555 return "PRO III"; // 6126
556 case 4:
557 return "PRO LITE"; // 8413
558 default:
559 return "FREE"; // 435
560 }
561 }
562
Payment(UserAlertRaw & un,unsigned int id)563 UserAlert::Payment::Payment(UserAlertRaw& un, unsigned int id)
564 : Base(un, id)
565 {
566 success = 's' == un.getnameid('r', 0);
567 planNumber = un.getint('p', 0);
568 }
569
Payment(bool s,int plan,m_time_t timestamp,unsigned int id)570 UserAlert::Payment::Payment(bool s, int plan, m_time_t timestamp, unsigned int id)
571 : Base(type_psts, UNDEF, "", timestamp, id)
572 {
573 success = s;
574 planNumber = plan;
575 }
576
text(string & header,string & title,MegaClient * mc)577 void UserAlert::Payment::text(string& header, string& title, MegaClient* mc)
578 {
579 updateEmail(mc);
580 ostringstream s;
581 if (success)
582 {
583 s << "Your payment for the " << getProPlanName() << " plan was received. "; // 7142
584 }
585 else
586 {
587 s << "Your payment for the " << getProPlanName() << " plan was unsuccessful."; // 7141
588 }
589 title = s.str();
590 header = "Payment info"; // 1230
591 }
592
PaymentReminder(UserAlertRaw & un,unsigned int id)593 UserAlert::PaymentReminder::PaymentReminder(UserAlertRaw& un, unsigned int id)
594 : Base(un, id)
595 {
596 expiryTime = un.getint64(MAKENAMEID2('t', 's'), timestamp);
597 relevant = true; // relevant until we see a subsequent payment
598 }
599
PaymentReminder(m_time_t expiryts,unsigned int id)600 UserAlert::PaymentReminder::PaymentReminder(m_time_t expiryts, unsigned int id)
601 : Base(type_pses, UNDEF, "", m_time(), id)
602 {
603 expiryTime = expiryts;
604 relevant = true; // relevant until we see a subsequent payment
605 }
606
text(string & header,string & title,MegaClient * mc)607 void UserAlert::PaymentReminder::text(string& header, string& title, MegaClient* mc)
608 {
609 updateEmail(mc);
610 m_time_t now = m_time();
611 int days = int((expiryTime - now) / 86400);
612
613 ostringstream s;
614 if (expiryTime < now)
615 {
616 s << "Your PRO membership plan expired " << -days << (days == -1 ? " day" : " days") << " ago";
617 }
618 else
619 {
620 s << "Your PRO membership plan will expire in " << days << (days == 1 ? " day." : " days."); // 8596, 8597
621 }
622 title = s.str();
623 header = "PRO membership plan expiring soon"; // 8598
624 }
625
Takedown(UserAlertRaw & un,unsigned int id)626 UserAlert::Takedown::Takedown(UserAlertRaw& un, unsigned int id)
627 : Base(un, id)
628 {
629 int n = un.getint(MAKENAMEID4('d', 'o', 'w', 'n'), -1);
630 isTakedown = n == 1;
631 isReinstate = n == 0;
632 nodeHandle = un.gethandle('h', MegaClient::NODEHANDLE, UNDEF);
633 relevant = isTakedown || isReinstate;
634 }
635
Takedown(bool down,bool reinstate,int,handle nh,m_time_t timestamp,unsigned int id)636 UserAlert::Takedown::Takedown(bool down, bool reinstate, int /*t*/, handle nh, m_time_t timestamp, unsigned int id)
637 : Base(type_ph, UNDEF, "", timestamp, id)
638 {
639 isTakedown = down;
640 isReinstate = reinstate;
641 nodeHandle = nh;
642 relevant = isTakedown || isReinstate;
643 }
644
text(string & header,string & title,MegaClient * mc)645 void UserAlert::Takedown::text(string& header, string& title, MegaClient* mc)
646 {
647 updateEmail(mc);
648 const char* typestring = "node";
649 string name;
650
651 Node* node = mc->nodebyhandle(nodeHandle);
652 if (node)
653 {
654 if (node->type == FOLDERNODE)
655 {
656 typestring = "folder";
657 }
658 else if (node->type == FILENODE)
659 {
660 typestring = "file";
661 }
662
663 name = node->displaypath();
664 }
665
666 if (name.empty())
667 {
668 char buffer[12];
669 Base64::btoa((byte*)&(nodeHandle), MegaClient::NODEHANDLE, buffer);
670 name = "handle ";
671 name += buffer;
672 }
673
674 ostringstream s;
675 if (isTakedown)
676 {
677 header = "Takedown notice"; //8521
678 s << "Your publicly shared " << typestring << " (" << name << ") has been taken down."; //8522
679 }
680 else if (isReinstate)
681 {
682 header = "Takedown reinstated"; //8524
683 s << "Your taken down " << typestring << " (" << name << ") has been reinstated."; // 8523
684 }
685 title = s.str();
686 }
687
UserAlerts(MegaClient & cmc)688 UserAlerts::UserAlerts(MegaClient& cmc)
689 : mc(cmc)
690 , nextid(0)
691 , begincatchup(false)
692 , catchupdone(false)
693 , catchup_last_timestamp(0)
694 , lsn(UNDEF)
695 , fsn(UNDEF)
696 , lastTimeDelta(0)
697 , provisionalmode(false)
698 , notingSharedNodes(false)
699 , ignoreNodesUnderShare(UNDEF)
700 {
701 }
702
nextId()703 unsigned int UserAlerts::nextId()
704 {
705 return ++nextid;
706 }
707
isUnwantedAlert(nameid type,int action)708 bool UserAlerts::isUnwantedAlert(nameid type, int action)
709 {
710 using namespace UserAlert;
711
712 if (type == type_put || type == type_share || type == type_dshare)
713 {
714 if (!flags.cloud_enabled) {
715 return true;
716 }
717 }
718 else if (type == type_c || type == type_ipc || type == type_upci || type == type_upco)
719 {
720 if (!flags.contacts_enabled) {
721 return true;
722 }
723 }
724
725 if (type == type_put)
726 {
727 return !flags.cloud_newfiles;
728 }
729 else if (type == type_share)
730 {
731 return !flags.cloud_newshare;
732 }
733 else if (type == type_dshare)
734 {
735 return !flags.cloud_delshare;
736 }
737 else if (type == type_ipc)
738 {
739 return !flags.contacts_fcrin;
740 }
741 else if (type == type_c)
742 {
743 return (action == -1 || action == 0) && !flags.contacts_fcrdel;
744 }
745 else if (type == type_upco)
746 {
747 return (action == -1 || action == 2) && !flags.contacts_fcracpt;
748 }
749
750 return false;
751 }
752
add(UserAlertRaw & un)753 void UserAlerts::add(UserAlertRaw& un)
754 {
755 using namespace UserAlert;
756 namespace u = UserAlert;
757 Base* unb = NULL;
758
759 switch (un.t) {
760 case type_ipc:
761 unb = new IncomingPendingContact(un, nextId());
762 break;
763 case type_c:
764 unb = new ContactChange(un, nextId());
765 break;
766 case type_upci:
767 unb = new UpdatedPendingContactIncoming(un, nextId());
768 break;
769 case type_upco:
770 unb = new UpdatedPendingContactOutgoing(un, nextId());
771 break;
772 case type_share:
773 unb = new u::NewShare(un, nextId());
774 break;
775 case type_dshare:
776 unb = new DeletedShare(un, nextId());
777 break;
778 case type_put:
779 unb = new NewSharedNodes(un, nextId());
780 break;
781 case type_d:
782 unb = new RemovedSharedNode(un, nextId());
783 break;
784 case type_psts:
785 unb = new Payment(un, nextId());
786 break;
787 case type_pses:
788 unb = new PaymentReminder(un, nextId());
789 break;
790 case type_ph:
791 unb = new Takedown(un, nextId());
792 break;
793 default:
794 unb = NULL; // If it's a notification type we do not recognise yet
795 }
796
797 if (unb)
798 {
799 add(unb);
800 }
801 }
802
add(UserAlert::Base * unb)803 void UserAlerts::add(UserAlert::Base* unb)
804 {
805 // unb is either directly from notification json, or constructed from actionpacket.
806 // We take ownership.
807
808 if (provisionalmode)
809 {
810 provisionals.push_back(unb);
811 return;
812 }
813
814 if (!catchupdone && unb->timestamp > catchup_last_timestamp)
815 {
816 catchup_last_timestamp = unb->timestamp;
817 }
818 else if (catchupdone && unb->timestamp < catchup_last_timestamp)
819 {
820 // this is probably a duplicate from the initial set, generated from normal sc packets
821 LOG_warn << "discarding duplicate user alert of type " << unb->type;
822 delete unb;
823 return;
824 }
825
826 if (!alerts.empty() && unb->type == UserAlert::type_put && alerts.back()->type == UserAlert::type_put)
827 {
828 // If it's file/folders added, and the prior one is for the same user and within 5 mins then we can combine instead
829 UserAlert::NewSharedNodes* np = dynamic_cast<UserAlert::NewSharedNodes*>(unb);
830 UserAlert::NewSharedNodes* op = dynamic_cast<UserAlert::NewSharedNodes*>(alerts.back());
831 if (np && op)
832 {
833 if (np->userHandle == op->userHandle && np->timestamp - op->timestamp < 300 &&
834 np->parentHandle == op->parentHandle && !ISUNDEF(np->parentHandle))
835 {
836 op->fileCount += np->fileCount;
837 op->folderCount += np->folderCount;
838 LOG_debug << "Merged user alert, type " << np->type << " ts " << np->timestamp;
839
840 if (catchupdone && (useralertnotify.empty() || useralertnotify.back() != alerts.back()))
841 {
842 alerts.back()->seen = false;
843 alerts.back()->tag = 0;
844 useralertnotify.push_back(alerts.back());
845 LOG_debug << "Updated user alert added to notify queue";
846 }
847 delete unb;
848 return;
849 }
850 }
851 }
852
853 if (!alerts.empty() && unb->type == UserAlert::type_d && alerts.back()->type == UserAlert::type_d)
854 {
855 // If it's file/folders removed, and the prior one is for the same user and within 5 mins then we can combine instead
856 UserAlert::RemovedSharedNode* nd = dynamic_cast<UserAlert::RemovedSharedNode*>(unb);
857 UserAlert::RemovedSharedNode* od = dynamic_cast<UserAlert::RemovedSharedNode*>(alerts.back());
858 if (nd && od)
859 {
860 if (nd->userHandle == od->userHandle && nd->timestamp - od->timestamp < 300)
861 {
862 od->itemsNumber += nd->itemsNumber;
863 LOG_debug << "Merged user alert, type " << nd->type << " ts " << nd->timestamp;
864
865 if (catchupdone && (useralertnotify.empty() || useralertnotify.back() != alerts.back()))
866 {
867 alerts.back()->seen = false;
868 alerts.back()->tag = 0;
869 useralertnotify.push_back(alerts.back());
870 LOG_debug << "Updated user alert added to notify queue";
871 }
872 delete unb;
873 return;
874 }
875 }
876 }
877
878 if (!alerts.empty() && unb->type == UserAlert::type_psts && static_cast<UserAlert::Payment*>(unb)->success)
879 {
880 // if a successful payment is made then hide/remove any reminders received
881 for (Alerts::iterator i = alerts.begin(); i != alerts.end(); ++i)
882 {
883 if ((*i)->type == UserAlert::type_pses && (*i)->relevant)
884 {
885 (*i)->relevant = false;
886 if (catchupdone)
887 {
888 useralertnotify.push_back(*i);
889 }
890 }
891 }
892 }
893
894 unb->updateEmail(&mc);
895 alerts.push_back(unb);
896 LOG_debug << "Added user alert, type " << alerts.back()->type << " ts " << alerts.back()->timestamp;
897
898 if (catchupdone)
899 {
900 unb->tag = 0;
901 useralertnotify.push_back(unb);
902 LOG_debug << "New user alert added to notify queue";
903 }
904 }
905
startprovisional()906 void UserAlerts::startprovisional()
907 {
908 provisionalmode = true;
909 }
910
evalprovisional(handle originatinguser)911 void UserAlerts::evalprovisional(handle originatinguser)
912 {
913 provisionalmode = false;
914 for (unsigned i = 0; i < provisionals.size(); ++i)
915 {
916 if (provisionals[i]->checkprovisional(originatinguser, &mc))
917 {
918 add(provisionals[i]);
919 }
920 else
921 {
922 delete provisionals[i];
923 }
924 }
925 provisionals.clear();
926 }
927
928
beginNotingSharedNodes()929 void UserAlerts::beginNotingSharedNodes()
930 {
931 notingSharedNodes = true;
932 notedSharedNodes.clear();
933 }
934
noteSharedNode(handle user,int type,m_time_t ts,Node * n)935 void UserAlerts::noteSharedNode(handle user, int type, m_time_t ts, Node* n)
936 {
937 if (catchupdone && notingSharedNodes && (type == FILENODE || type == FOLDERNODE))
938 {
939 assert(!ISUNDEF(user));
940
941 if (!ISUNDEF(ignoreNodesUnderShare))
942 {
943 // don't make alerts on files/folders already in the new share
944 for (Node* p = n; p != NULL; p = p->parent)
945 {
946 if (p->nodehandle == ignoreNodesUnderShare)
947 return;
948 }
949 }
950
951 ff& f = notedSharedNodes[std::make_pair(user, n ? n->parenthandle : UNDEF)];
952 ++(type == FOLDERNODE ? f.folders : f.files);
953 if (!f.timestamp || (ts && ts < f.timestamp))
954 {
955 f.timestamp = ts;
956 }
957 }
958 }
959
960 // make a notification out of the shared nodes noted
convertNotedSharedNodes(bool added,handle originatingUser)961 void UserAlerts::convertNotedSharedNodes(bool added, handle originatingUser)
962 {
963 if (catchupdone && notingSharedNodes && originatingUser != mc.me)
964 {
965 using namespace UserAlert;
966 for (map<pair<handle, handle>, ff>::iterator i = notedSharedNodes.begin(); i != notedSharedNodes.end(); ++i)
967 {
968 add(added ? (Base*) new NewSharedNodes(i->second.folders, i->second.files, i->first.first, i->first.second, i->second.timestamp, nextId())
969 : (Base*) new RemovedSharedNode(i->second.folders + i->second.files, i->first.first, m_time(), nextId()));
970 }
971 }
972 notedSharedNodes.clear();
973 notingSharedNodes = false;
974 ignoreNodesUnderShare = UNDEF;
975 }
976
ignoreNextSharedNodesUnder(handle h)977 void UserAlerts::ignoreNextSharedNodesUnder(handle h)
978 {
979 ignoreNodesUnderShare = h;
980 }
981
982
983 // process server-client notifications
procsc_useralert(JSON & jsonsc)984 bool UserAlerts::procsc_useralert(JSON& jsonsc)
985 {
986 for (;;)
987 {
988 switch (jsonsc.getnameid())
989 {
990 case 'u':
991 if (jsonsc.enterarray())
992 {
993 for (;;)
994 {
995 UserAlertPendingContact ul;
996 if (jsonsc.enterobject())
997 {
998 bool inobject = true;
999 while (inobject)
1000 {
1001 switch (jsonsc.getnameid())
1002 {
1003 case 'u':
1004 ul.u = jsonsc.gethandle(MegaClient::USERHANDLE);
1005 break;
1006 case 'm':
1007 jsonsc.storeobject(&ul.m);
1008 break;
1009 case MAKENAMEID2('m', '2'):
1010 if (jsonsc.enterarray())
1011 {
1012 for (;;)
1013 {
1014 string s;
1015 if (jsonsc.storeobject(&s))
1016 {
1017 ul.m2.push_back(s);
1018 }
1019 else
1020 {
1021 break;
1022 }
1023 }
1024 jsonsc.leavearray();
1025 }
1026 break;
1027 case 'n':
1028 jsonsc.storeobject(&ul.n);
1029 break;
1030 case EOO:
1031 inobject = false;
1032 }
1033 }
1034 jsonsc.leaveobject();
1035 if (ul.u)
1036 {
1037 pendingContactUsers[ul.u] = ul;
1038 }
1039 }
1040 else
1041 {
1042 break;
1043 }
1044 }
1045 jsonsc.leavearray();
1046 }
1047 break;
1048
1049 case MAKENAMEID3('l', 's', 'n'):
1050 lsn = jsonsc.gethandle(8);
1051 break;
1052
1053 case MAKENAMEID3('f', 's', 'n'):
1054 fsn = jsonsc.gethandle(8);
1055 break;
1056
1057 case MAKENAMEID3('l', 't', 'd'): // last notifcation seen time delta (or 0)
1058 lastTimeDelta = jsonsc.getint();
1059 break;
1060
1061 case EOO:
1062 for (Alerts::iterator i = alerts.begin(); i != alerts.end(); ++i)
1063 {
1064 UserAlert::Base* b = *i;
1065 b->seen = b->timestamp + lastTimeDelta < m_time();
1066
1067 if (b->userEmail.empty() && b->userHandle != UNDEF)
1068 {
1069 map<handle, UserAlertPendingContact>::iterator i = pendingContactUsers.find(b->userHandle);
1070 if (i != pendingContactUsers.end())
1071 {
1072 b->userEmail = i->second.m;
1073 if (b->userEmail.empty() && !i->second.m2.empty())
1074 {
1075 b->userEmail = i->second.m2[0];
1076 }
1077 }
1078 }
1079 }
1080 begincatchup = false;
1081 catchupdone = true;
1082 return true;
1083
1084 case 'c': // notifications
1085 if (jsonsc.enterarray())
1086 {
1087 for (;;)
1088 {
1089 if (jsonsc.enterobject())
1090 {
1091 UserAlertRaw un;
1092 bool inobject = true;
1093 while (inobject)
1094 {
1095 // 't' designates type - but it appears late in the packet
1096 nameid nid = jsonsc.getnameid();
1097 switch (nid)
1098 {
1099
1100 case 't':
1101 un.t = jsonsc.getnameid();
1102 break;
1103
1104 case EOO:
1105 inobject = false;
1106 break;
1107
1108 default:
1109 // gather up the fields to interpret later as we don't know what type some are
1110 // until we get the 't' field which is late in the packet
1111 jsonsc.storeobject(&un.fields[nid]);
1112 }
1113 }
1114
1115 if (!isUnwantedAlert(un.t, un.getint('c', -1)))
1116 {
1117 add(un);
1118 }
1119 jsonsc.leaveobject();
1120 }
1121 else
1122 {
1123 break;
1124 }
1125 }
1126 jsonsc.leavearray();
1127 break;
1128 }
1129
1130 // fall through
1131 default:
1132 assert(false);
1133 if (!jsonsc.storeobject())
1134 {
1135 LOG_err << "Error parsing sc user alerts";
1136 begincatchup = false;
1137 catchupdone = true; // if we fail to get useralerts, continue anyway
1138 return true;
1139 }
1140 }
1141 }
1142 }
1143
acknowledgeAll()1144 void UserAlerts::acknowledgeAll()
1145 {
1146 for (Alerts::iterator i = alerts.begin(); i != alerts.end(); ++i)
1147 {
1148 if (!(*i)->seen)
1149 {
1150 (*i)->seen = true;
1151 if ((*i)->tag != 0)
1152 {
1153 (*i)->tag = mc.reqtag;
1154 }
1155 useralertnotify.push_back(*i);
1156 }
1157 }
1158
1159 // notify the API. Eg. on when user closes the useralerts list
1160 mc.reqs.add(new CommandSetLastAcknowledged(&mc));
1161 }
1162
onAcknowledgeReceived()1163 void UserAlerts::onAcknowledgeReceived()
1164 {
1165 if (catchupdone)
1166 {
1167 for (Alerts::iterator i = alerts.begin(); i != alerts.end(); ++i)
1168 {
1169 if (!(*i)->seen)
1170 {
1171 (*i)->seen = true;
1172 (*i)->tag = 0;
1173 useralertnotify.push_back(*i);
1174 }
1175 }
1176 }
1177 }
1178
clear()1179 void UserAlerts::clear()
1180 {
1181 for (Alerts::iterator i = alerts.begin(); i != alerts.end(); ++i)
1182 {
1183 delete *i;
1184 }
1185 alerts.clear();
1186 useralertnotify.clear();
1187 begincatchup = false;
1188 catchupdone = false;
1189 catchup_last_timestamp = 0;
1190 lsn = UNDEF;
1191 fsn = UNDEF;
1192 lastTimeDelta = 0;
1193 nextid = 0;
1194 }
1195
~UserAlerts()1196 UserAlerts::~UserAlerts()
1197 {
1198 clear();
1199 }
1200
1201
1202 } // namespace
1203