//--- Constructor ---
 public SubscriptionManager(XUri destination, List<Tuplet<string, List<XDoc>>> subscriptions) {
     _destination = destination;
     _recordChangeQueue = new ProcessingQueue<UserInfo>(RecordsChange_Helper, 1);
     _subscriptionChangeQueue = new ProcessingQueue<Empty>(UpdateSubscriptions_Helper, 1);
     if(subscriptions == null) {
         return;
     }
     foreach(Tuplet<string, List<XDoc>> subscription in subscriptions) {
         string wikiId = subscription.Item1;
         SiteInfo siteInfo = new SiteInfo(wikiId);
         _subscriptions.Add(wikiId, siteInfo);
         foreach(XDoc userDoc in subscription.Item2) {
             UserInfo userInfo = UserInfo.FromXDoc(wikiId, userDoc);
             if(userInfo == null) {
                 continue;
             }
             lock(siteInfo) {
                 siteInfo.Users.Add(userInfo.Id, userInfo);
             }
             userInfo.ResourcesChanged += OnSubscriptionChange;
             userInfo.DataChanged += OnRecordsChange;
         }
     }
 }
 //--- Methods ---
 public UserInfo GetUser(string wikiId, uint userId, bool create) {
     SiteInfo siteInfo;
     UserInfo userInfo;
     lock(_subscriptions) {
         if(_subscriptions.TryGetValue(wikiId, out siteInfo)) {
             lock(siteInfo) {
                 if(siteInfo.Users.TryGetValue(userId, out userInfo)) {
                     return userInfo;
                 }
             }
         }
         if(!create) {
             return null;
         }
         if(siteInfo == null) {
             siteInfo = new SiteInfo(wikiId);
             _subscriptions.Add(wikiId, siteInfo);
         }
         userInfo = new UserInfo(userId, wikiId);
         lock(siteInfo) {
             siteInfo.Users.Add(userId, userInfo);
         }
         userInfo.ResourcesChanged += OnSubscriptionChange;
         userInfo.DataChanged += OnRecordsChange;
         return userInfo;
     }
 }