]> git.0d.be Git - empathy.git/blob - ubuntu-online-accounts/mc-plugin/mcp-account-manager-uoa.c
UOA: Changing enable state of MC account should not modify per-service state
[empathy.git] / ubuntu-online-accounts / mc-plugin / mcp-account-manager-uoa.c
1 /*
2  * Copyright © 2012 Collabora Ltd.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18
19 #include "config.h"
20 #include "mcp-account-manager-uoa.h"
21
22 #include <telepathy-glib/telepathy-glib.h>
23
24 #include <libaccounts-glib/ag-account.h>
25 #include <libaccounts-glib/ag-account-service.h>
26 #include <libaccounts-glib/ag-manager.h>
27 #include <libaccounts-glib/ag-service.h>
28
29 #include <string.h>
30 #include <ctype.h>
31
32 #define PLUGIN_NAME "uoa"
33 #define PLUGIN_PRIORITY (MCP_ACCOUNT_STORAGE_PLUGIN_PRIO_KEYRING + 10)
34 #define PLUGIN_DESCRIPTION "Provide Telepathy Accounts from UOA via libaccounts-glib"
35 #define PLUGIN_PROVIDER EMPATHY_UOA_PROVIDER
36
37 #define DEBUG g_debug
38
39 #define SERVICE_TYPE "IM"
40 #define KEY_PREFIX "telepathy/"
41 #define KEY_ACCOUNT_NAME "mc-account-name"
42 #define KEY_READONLY_PARAMS "mc-readonly-params"
43
44 static void account_storage_iface_init (McpAccountStorageIface *iface);
45
46 G_DEFINE_TYPE_WITH_CODE (McpAccountManagerUoa, mcp_account_manager_uoa,
47     G_TYPE_OBJECT,
48     G_IMPLEMENT_INTERFACE (MCP_TYPE_ACCOUNT_STORAGE,
49         account_storage_iface_init));
50
51 struct _McpAccountManagerUoaPrivate
52 {
53   McpAccountManager *am;
54
55   AgManager *manager;
56
57   /* alloc'ed string -> ref'ed AgAccountService
58    * The key is the account_name, an MC unique identifier.
59    * Note: There could be multiple services in this table having the same
60    * AgAccount, even if unlikely. */
61   GHashTable *accounts;
62
63   /* Queue of owned DelayedSignalData */
64   GQueue *pending_signals;
65
66   gboolean loaded;
67   gboolean ready;
68 };
69
70 typedef enum {
71   DELAYED_CREATE,
72   DELAYED_DELETE,
73 } DelayedSignal;
74
75 typedef struct {
76   DelayedSignal signal;
77   AgAccountId account_id;
78 } DelayedSignalData;
79
80
81 static gchar *
82 _service_dup_tp_value (AgAccountService *service,
83     const gchar *key)
84 {
85   gchar *real_key = g_strdup_printf (KEY_PREFIX "%s", key);
86   GValue value = { 0, };
87   gchar *ret;
88
89   g_value_init (&value, G_TYPE_STRING);
90   ag_account_service_get_value (service, real_key, &value);
91   ret = g_value_dup_string (&value);
92   g_value_unset (&value);
93
94   return ret;
95 }
96
97 static void
98 _service_set_tp_value (AgAccountService *service,
99     const gchar *key,
100     const gchar *value)
101 {
102   gchar *real_key = g_strdup_printf (KEY_PREFIX "%s", key);
103
104   if (value != NULL)
105     {
106       GValue gvalue = { 0, };
107
108       g_value_init (&gvalue, G_TYPE_STRING);
109       g_value_set_string (&gvalue, value);
110       ag_account_service_set_value (service, real_key, &gvalue);
111       g_value_unset (&gvalue);
112       g_free (real_key);
113     }
114   else
115     {
116       ag_account_service_set_value (service, real_key, NULL);
117     }
118 }
119
120 /* Returns NULL if the account never has been imported into MC before */
121 static gchar *
122 _service_dup_tp_account_name (AgAccountService *service)
123 {
124   return _service_dup_tp_value (service, KEY_ACCOUNT_NAME);
125 }
126
127 static void
128 _service_set_tp_account_name (AgAccountService *service,
129     const gchar *account_name)
130 {
131   _service_set_tp_value (service, KEY_ACCOUNT_NAME, account_name);
132 }
133
134 static void
135 _service_enabled_cb (AgAccountService *service,
136     gboolean enabled,
137     McpAccountManagerUoa *self)
138 {
139   gchar *account_name = _service_dup_tp_account_name (service);
140
141   if (!self->priv->ready || account_name == NULL)
142     return;
143
144   DEBUG ("UOA account %s toggled: %s", account_name,
145       enabled ? "enabled" : "disabled");
146
147   g_signal_emit_by_name (self, "toggled", account_name, enabled);
148
149   g_free (account_name);
150 }
151
152 static void
153 _service_changed_cb (AgAccountService *service,
154     McpAccountManagerUoa *self)
155 {
156   gchar *account_name = _service_dup_tp_account_name (service);
157
158   if (!self->priv->ready || account_name == NULL)
159     return;
160
161   DEBUG ("UOA account %s changed", account_name);
162
163   /* FIXME: Could use ag_account_service_get_changed_fields()
164    * and emit "altered-one" */
165   g_signal_emit_by_name (self, "altered", account_name);
166
167   g_free (account_name);
168 }
169
170 static void
171 _account_stored_cb (AgAccount *account,
172     const GError *error,
173     gpointer user_data)
174 {
175   if (error != NULL)
176     {
177       DEBUG ("Error storing UOA account '%s': %s",
178           ag_account_get_display_name (account),
179           error->message);
180     }
181 }
182
183 static gboolean
184 _add_service (McpAccountManagerUoa *self,
185     AgAccountService *service,
186     const gchar *account_name)
187 {
188   DEBUG ("UOA account %s added", account_name);
189
190   if (g_hash_table_contains (self->priv->accounts, account_name))
191     {
192       DEBUG ("Already exists, ignoring");
193       return FALSE;
194     }
195
196   g_hash_table_insert (self->priv->accounts,
197       g_strdup (account_name),
198       g_object_ref (service));
199
200   g_signal_connect (service, "enabled",
201       G_CALLBACK (_service_enabled_cb), self);
202   g_signal_connect (service, "changed",
203       G_CALLBACK (_service_changed_cb), self);
204
205   return TRUE;
206 }
207
208 static void
209 _account_created_cb (AgManager *manager,
210     AgAccountId id,
211     McpAccountManagerUoa *self)
212 {
213   AgAccount *account;
214   GList *l;
215
216   if (!self->priv->ready)
217     {
218       DelayedSignalData *data = g_slice_new0 (DelayedSignalData);
219
220       data->signal = DELAYED_CREATE;
221       data->account_id = id;
222
223       g_queue_push_tail (self->priv->pending_signals, data);
224       return;
225     }
226
227   account = ag_manager_get_account (self->priv->manager, id);
228
229   l = ag_account_list_services_by_type (account, SERVICE_TYPE);
230   while (l != NULL)
231     {
232       AgAccountService *service = ag_account_service_new (account, l->data);
233       gchar *account_name = _service_dup_tp_account_name (service);
234
235       /* If this is the first time we see this service, we have to generate an
236        * account_name for it. */
237       if (account_name == NULL)
238         {
239           gchar *cm_name = NULL;
240           gchar *protocol_name = NULL;
241           gchar *account_param = NULL;
242
243           cm_name = _service_dup_tp_value (service, "manager");
244           protocol_name = _service_dup_tp_value (service, "protocol");
245           account_param = _service_dup_tp_value (service, "param-account");
246
247           if (!tp_str_empty (cm_name) &&
248               !tp_str_empty (protocol_name) &&
249               !tp_str_empty (account_param))
250             {
251               GHashTable *params;
252
253               params = tp_asv_new (
254                   "account", G_TYPE_STRING, account_param,
255                   NULL);
256
257               account_name = mcp_account_manager_get_unique_name (self->priv->am,
258                   cm_name, protocol_name, params);
259               _service_set_tp_account_name (service, account_name);
260
261               ag_account_store (account, _account_stored_cb, self);
262
263               g_hash_table_unref (params);
264             }
265
266           g_free (cm_name);
267           g_free (protocol_name);
268           g_free (account_param);
269         }
270
271       if (account_name != NULL)
272         {
273           if (_add_service (self, service, account_name))
274             g_signal_emit_by_name (self, "created", account_name);
275         }
276
277       g_free (account_name);
278       g_object_unref (service);
279       ag_service_unref (l->data);
280       l = g_list_delete_link (l, l);
281     }
282
283   g_object_unref (account);
284 }
285
286 static void
287 _account_deleted_cb (AgManager *manager,
288     AgAccountId id,
289     McpAccountManagerUoa *self)
290 {
291   GHashTableIter iter;
292   gpointer value;
293
294   if (!self->priv->ready)
295     {
296       DelayedSignalData *data = g_slice_new0 (DelayedSignalData);
297
298       data->signal = DELAYED_DELETE;
299       data->account_id = id;
300
301       g_queue_push_tail (self->priv->pending_signals, data);
302       return;
303     }
304
305   g_hash_table_iter_init (&iter, self->priv->accounts);
306   while (g_hash_table_iter_next (&iter, NULL, &value))
307     {
308       AgAccountService *service = value;
309       AgAccount *account = ag_account_service_get_account (service);
310       gchar *account_name;
311
312       if (account->id != id)
313         continue;
314
315       account_name = _service_dup_tp_account_name (service);
316       if (account_name == NULL)
317         continue;
318
319       DEBUG ("UOA account %s deleted", account_name);
320
321       g_hash_table_iter_remove (&iter);
322       g_signal_emit_by_name (self, "deleted", account_name);
323
324       g_free (account_name);
325     }
326 }
327
328 static void
329 mcp_account_manager_uoa_dispose (GObject *object)
330 {
331   McpAccountManagerUoa *self = (McpAccountManagerUoa *) object;
332
333   tp_clear_object (&self->priv->am);
334   tp_clear_object (&self->priv->manager);
335   tp_clear_pointer (&self->priv->accounts, g_hash_table_unref);
336
337   G_OBJECT_CLASS (mcp_account_manager_uoa_parent_class)->dispose (object);
338 }
339
340 static void
341 mcp_account_manager_uoa_init (McpAccountManagerUoa *self)
342 {
343   DEBUG ("UOA MC plugin initialised");
344
345   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
346       MCP_TYPE_ACCOUNT_MANAGER_UOA, McpAccountManagerUoaPrivate);
347
348   self->priv->accounts = g_hash_table_new_full (g_str_hash, g_str_equal,
349       g_free, g_object_unref);
350   self->priv->pending_signals = g_queue_new ();
351
352   self->priv->manager = ag_manager_new_for_service_type (SERVICE_TYPE);
353
354   g_signal_connect (self->priv->manager, "account-created",
355       G_CALLBACK (_account_created_cb), self);
356   g_signal_connect (self->priv->manager, "account-deleted",
357       G_CALLBACK (_account_deleted_cb), self);
358 }
359
360 static void
361 mcp_account_manager_uoa_class_init (McpAccountManagerUoaClass *klass)
362 {
363   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
364
365   gobject_class->dispose = mcp_account_manager_uoa_dispose;
366
367   g_type_class_add_private (gobject_class,
368       sizeof (McpAccountManagerUoaPrivate));
369 }
370
371 static void
372 _ensure_loaded (McpAccountManagerUoa *self)
373 {
374   GList *services;
375
376   if (self->priv->loaded)
377     return;
378
379   self->priv->loaded = TRUE;
380
381   g_assert (!self->priv->ready);
382
383   services = ag_manager_get_account_services (self->priv->manager);
384   while (services != NULL)
385     {
386       AgAccountService *service = services->data;
387       AgAccount *account = ag_account_service_get_account (service);
388       gchar *account_name = _service_dup_tp_account_name (service);
389
390       if (account_name != NULL)
391         {
392           /* This service was already known, we can add it now */
393           _add_service (self, service, account_name);
394           g_free (account_name);
395         }
396       else
397         {
398           DelayedSignalData *data = g_slice_new0 (DelayedSignalData);
399
400           /* This service was created while MC was not running, delay its
401            * creation until MC is ready */
402           data->signal = DELAYED_CREATE;
403           data->account_id = account->id;
404
405           g_queue_push_tail (self->priv->pending_signals, data);
406         }
407
408       g_object_unref (services->data);
409       services = g_list_delete_link (services, services);
410     }
411 }
412
413 static GList *
414 account_manager_uoa_list (const McpAccountStorage *storage,
415     const McpAccountManager *am)
416 {
417   McpAccountManagerUoa *self = (McpAccountManagerUoa *) storage;
418   GList *accounts = NULL;
419   GHashTableIter iter;
420   gpointer key;
421
422   DEBUG (G_STRFUNC);
423
424   _ensure_loaded (self);
425
426   g_hash_table_iter_init (&iter, self->priv->accounts);
427   while (g_hash_table_iter_next (&iter, &key, NULL))
428     accounts = g_list_prepend (accounts, g_strdup (key));
429
430   return accounts;
431 }
432
433 static const gchar *
434 provider_to_tp_service_name (const gchar *provider_name)
435 {
436   /* Well known services are defined in Telepathy spec:
437    * http://telepathy.freedesktop.org/spec/Account.html#Property:Service */
438   if (!tp_strdiff (provider_name, "google"))
439     return "google-talk";
440
441   return provider_name;
442 }
443
444 static gboolean
445 account_manager_uoa_get (const McpAccountStorage *storage,
446     const McpAccountManager *am,
447     const gchar *account_name,
448     const gchar *key)
449 {
450   McpAccountManagerUoa *self = (McpAccountManagerUoa *) storage;
451   AgAccountService *service;
452   AgAccount *account;
453   AgService *s;
454   gboolean handled = FALSE;
455
456   service = g_hash_table_lookup (self->priv->accounts, account_name);
457   if (service == NULL)
458     return FALSE;
459
460   DEBUG ("%s: %s, %s", G_STRFUNC, account_name, key);
461
462   account = ag_account_service_get_account (service);
463   s = ag_account_service_get_service (service);
464
465   /* NULL key means we want all settings */
466   if (key == NULL)
467     {
468       AgAccountSettingIter iter;
469       const gchar *k;
470       const GValue *v;
471
472       ag_account_service_settings_iter_init (service, &iter, KEY_PREFIX);
473       while (ag_account_service_settings_iter_next (&iter, &k, &v))
474         {
475           if (!G_VALUE_HOLDS_STRING (v))
476             continue;
477
478           mcp_account_manager_set_value (am, account_name,
479               k, g_value_get_string (v));
480         }
481     }
482
483   /* Some special keys that are not stored in setting */
484   if (key == NULL || !tp_strdiff (key, "Enabled"))
485     {
486       mcp_account_manager_set_value (am, account_name, "Enabled",
487           ag_account_service_get_enabled (service) ? "true" : "false");
488       handled = TRUE;
489     }
490
491   if (key == NULL || !tp_strdiff (key, "DisplayName"))
492     {
493       mcp_account_manager_set_value (am, account_name, "DisplayName",
494           ag_account_get_display_name (account));
495       handled = TRUE;
496     }
497
498   if (key == NULL || !tp_strdiff (key, "Service"))
499     {
500       mcp_account_manager_set_value (am, account_name, "Service",
501           provider_to_tp_service_name (ag_account_get_provider_name (account)));
502       handled = TRUE;
503     }
504
505   if (key == NULL || !tp_strdiff (key, "Icon"))
506     {
507       mcp_account_manager_set_value (am, account_name, "Icon",
508           ag_service_get_icon_name (s));
509       handled = TRUE;
510     }
511
512   /* If it was none of the above, then just lookup in service' settings */
513   if (!handled)
514     {
515       gchar *value = _service_dup_tp_value (service, key);
516
517       mcp_account_manager_set_value (am, account_name, key, value);
518       g_free (value);
519     }
520
521   return TRUE;
522 }
523
524 static gboolean
525 account_manager_uoa_set (const McpAccountStorage *storage,
526     const McpAccountManager *am,
527     const gchar *account_name,
528     const gchar *key,
529     const gchar *val)
530 {
531   McpAccountManagerUoa *self = (McpAccountManagerUoa *) storage;
532   AgAccountService *service;
533   AgAccount *account;
534
535   service = g_hash_table_lookup (self->priv->accounts, account_name);
536   if (service == NULL)
537     return FALSE;
538
539   account = ag_account_service_get_account (service);
540
541   DEBUG ("%s: %s, %s, %s", G_STRFUNC, account_name, key, val);
542
543   if (!tp_strdiff (key, "Enabled"))
544     {
545       /* Enabled is a global setting on the account, not per-services,
546        * unfortunately */
547       ag_account_select_service (account, NULL);
548       ag_account_set_enabled (account, !tp_strdiff (val, "true"));
549     }
550   else if (!tp_strdiff (key, "DisplayName"))
551     {
552       ag_account_set_display_name (account, val);
553     }
554   else
555     {
556       _service_set_tp_value (service, key, val);
557     }
558
559   return TRUE;
560 }
561
562 static gchar *
563 account_manager_uoa_create (const McpAccountStorage *storage,
564     const McpAccountManager *am,
565     const gchar *cm_name,
566     const gchar *protocol_name,
567     GHashTable *params,
568     GError **error)
569 {
570   McpAccountManagerUoa *self = (McpAccountManagerUoa *) storage;
571   gchar *account_name;
572   AgAccount *account;
573   AgAccountService *service;
574   GList *l;
575
576   if (!self->priv->ready)
577     {
578       g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
579           "Cannot create account before being ready");
580       return NULL;
581     }
582
583   DEBUG (G_STRFUNC);
584
585   /* Create a new AgAccountService and keep it internally. This won't save it
586    * into persistent storage until account_manager_uoa_commit() is called.
587    * We assume there is only one IM service */
588   account = ag_manager_create_account (self->priv->manager, protocol_name);
589   l = ag_account_list_services_by_type (account, SERVICE_TYPE);
590   if (l == NULL)
591     {
592       g_set_error (error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
593           "Cannot create a %s service for %s provider",
594           SERVICE_TYPE, protocol_name);
595       g_object_unref (account);
596       return NULL;
597     }
598   service = ag_account_service_new (account, l->data);
599   ag_service_list_free (l);
600   g_object_unref (account);
601
602   account_name = mcp_account_manager_get_unique_name (self->priv->am,
603       cm_name, protocol_name, params);
604   _service_set_tp_account_name (service, account_name);
605   g_assert (_add_service (self, service, account_name));
606
607   /* MC will set all params on the account and commit */
608
609   return account_name;
610 }
611
612 static gboolean
613 account_manager_uoa_delete (const McpAccountStorage *storage,
614     const McpAccountManager *am,
615     const gchar *account_name,
616     const gchar *key)
617 {
618   McpAccountManagerUoa *self = (McpAccountManagerUoa *) storage;
619   AgAccountService *service;
620   AgAccount *account;
621
622   service = g_hash_table_lookup (self->priv->accounts, account_name);
623   if (service == NULL)
624     return FALSE;
625
626   account = ag_account_service_get_account (service);
627
628   DEBUG ("%s: %s, %s", G_STRFUNC, account_name, key);
629
630   if (key == NULL)
631     {
632       ag_account_delete (account);
633       g_hash_table_remove (self->priv->accounts, account_name);
634     }
635   else
636     {
637       _service_set_tp_value (service, key, NULL);
638     }
639
640   return TRUE;
641 }
642
643 static gboolean
644 account_manager_uoa_commit (const McpAccountStorage *storage,
645     const McpAccountManager *am)
646 {
647   McpAccountManagerUoa *self = (McpAccountManagerUoa *) storage;
648   GHashTableIter iter;
649   gpointer value;
650
651   DEBUG (G_STRFUNC);
652
653   g_hash_table_iter_init (&iter, self->priv->accounts);
654   while (g_hash_table_iter_next (&iter, NULL, &value))
655     {
656       AgAccountService *service = value;
657       AgAccount *account = ag_account_service_get_account (service);
658
659       ag_account_store (account, _account_stored_cb, self);
660     }
661
662   return TRUE;
663 }
664
665 static void
666 account_manager_uoa_ready (const McpAccountStorage *storage,
667     const McpAccountManager *am)
668 {
669   McpAccountManagerUoa *self = (McpAccountManagerUoa *) storage;
670   DelayedSignalData *data;
671
672   if (self->priv->ready)
673     return;
674
675   DEBUG (G_STRFUNC);
676
677   self->priv->ready = TRUE;
678   self->priv->am = g_object_ref (G_OBJECT (am));
679
680   while ((data = g_queue_pop_head (self->priv->pending_signals)) != NULL)
681     {
682       switch (data->signal)
683         {
684           case DELAYED_CREATE:
685             _account_created_cb (self->priv->manager, data->account_id, self);
686             break;
687           case DELAYED_DELETE:
688             _account_deleted_cb (self->priv->manager, data->account_id, self);
689             break;
690           default:
691             g_assert_not_reached ();
692         }
693
694       g_slice_free (DelayedSignalData, data);
695     }
696
697   g_queue_free (self->priv->pending_signals);
698   self->priv->pending_signals = NULL;
699 }
700
701 static void
702 account_manager_uoa_get_identifier (const McpAccountStorage *storage,
703     const gchar *account_name,
704     GValue *identifier)
705 {
706   McpAccountManagerUoa *self = (McpAccountManagerUoa *) storage;
707   AgAccountService *service;
708   AgAccount *account;
709
710   service = g_hash_table_lookup (self->priv->accounts, account_name);
711   if (service == NULL)
712     return;
713
714   account = ag_account_service_get_account (service);
715
716   g_value_init (identifier, G_TYPE_UINT);
717   g_value_set_uint (identifier, account->id);
718 }
719
720 static guint
721 account_manager_uoa_get_restrictions (const McpAccountStorage *storage,
722     const gchar *account_name)
723 {
724   McpAccountManagerUoa *self = (McpAccountManagerUoa *) storage;
725   AgAccountService *service;
726   guint restrictions = TP_STORAGE_RESTRICTION_FLAG_CANNOT_SET_SERVICE;
727   GValue value = G_VALUE_INIT;
728
729   /* If we don't know this account, we cannot do anything */
730   service = g_hash_table_lookup (self->priv->accounts, account_name);
731   if (service == NULL)
732     return G_MAXUINT;
733
734   g_value_init (&value, G_TYPE_BOOLEAN);
735   ag_account_service_get_value (service,
736       KEY_PREFIX KEY_READONLY_PARAMS, &value);
737
738   if (g_value_get_boolean (&value))
739     restrictions |= TP_STORAGE_RESTRICTION_FLAG_CANNOT_SET_PARAMETERS;
740
741   g_value_unset (&value);
742
743   /* FIXME: We can't set Icon either, but there is no flag for that */
744   return restrictions;
745 }
746
747 static void
748 account_storage_iface_init (McpAccountStorageIface *iface)
749 {
750   mcp_account_storage_iface_set_name (iface, PLUGIN_NAME);
751   mcp_account_storage_iface_set_desc (iface, PLUGIN_DESCRIPTION);
752   mcp_account_storage_iface_set_priority (iface, PLUGIN_PRIORITY);
753   mcp_account_storage_iface_set_provider (iface, PLUGIN_PROVIDER);
754
755 #define IMPLEMENT(x) mcp_account_storage_iface_implement_##x(iface, \
756     account_manager_uoa_##x)
757   IMPLEMENT (get);
758   IMPLEMENT (list);
759   IMPLEMENT (set);
760   IMPLEMENT (create);
761   IMPLEMENT (delete);
762   IMPLEMENT (commit);
763   IMPLEMENT (ready);
764   IMPLEMENT (get_identifier);
765   IMPLEMENT (get_restrictions);
766 #undef IMPLEMENT
767 }
768
769 McpAccountManagerUoa *
770 mcp_account_manager_uoa_new (void)
771 {
772   return g_object_new (MCP_TYPE_ACCOUNT_MANAGER_UOA, NULL);
773 }