public void UserInfo_retrieve_subscription_for_a_page() {
            SubscriptionManager subscriptionManager = new SubscriptionManager(null, null);
            UserInfo userInfo1 = subscriptionManager.GetUser("a", 1, true);
            userInfo1.AddResource(10, "infinity");
            userInfo1.AddResource(11, "0");
            userInfo1.AddResource(12, "0");
            userInfo1.AddResource(13, "infinity");

            List<uint> pages = new List<uint>();
            pages.Add(10);
            pages.Add(12);
            pages.Add(14);
            pages.Add(16);
            XDoc subscriptions = userInfo1.GetSubscriptionDoc(pages);
            Assert.AreEqual(2, subscriptions["subscription.page"].ListLength);
            XDoc page10 = subscriptions["subscription.page[@id='10']"];
            Assert.IsFalse(page10.IsEmpty);
            Assert.AreEqual("infinity", page10["@depth"].AsText);
            XDoc page12 = subscriptions["subscription.page[@id='12']"];
            Assert.IsFalse(page12.IsEmpty);
            Assert.AreEqual("0", page12["@depth"].AsText);
        }
 protected override Yield Stop(Result result) {
     yield return _subscriptionLocation.DeleteAsync().Catch();
     _subscriptions.RecordsChanged -= PersistSubscriptions;
     _subscriptions.SubscriptionsChanged -= PushSubscriptionSetUpstream;
     _subscriptions = null;
     _wikiLanguageCache.Clear();
     yield return Coroutine.Invoke(base.Stop, new Result());
     result.Return();
 }
        //--- Methods ---
        protected override Yield Start(XDoc config, Result result) {
            yield return Coroutine.Invoke(base.Start, config, new Result());

            // set up plug for phpscript that will handle the notifications
            _emailer = Plug.New(config["uri.emailer"].AsUri);

            // set up plug deki, so we can validate users
            _deki = Plug.New(config["uri.deki"].AsUri);

            // get the apikey, which we will need as a subscription auth token for subscriptions not done on behalf of a user
            _apikey = config["apikey"].AsText;
            _cache = new PageChangeCache(_deki.With("apikey", _apikey), TimeSpan.FromSeconds(config["page-cache-ttl"].AsInt ?? 2));

            // resource manager for email template
            string resourcePath = Config["resources-path"].AsText;
            if(!string.IsNullOrEmpty(resourcePath)) {
                _resourceManager = new PlainTextResourceManager(Environment.ExpandEnvironmentVariables(resourcePath));
            } else {

                // creating a test resource manager
                _log.WarnFormat("'resource-path' was not defined in Config, using a test resource manager for email templating");
                TestResourceSet testSet = new TestResourceSet();
                testSet.Add("Notification.Page.email-subject", "Page Modified");
                testSet.Add("Notification.Page.email-header", "The following pages have changed:");
                _resourceManager = new PlainTextResourceManager(testSet);
            }

            // get persisted subscription storage
            List<Tuplet<string, List<XDoc>>> allWikiSubs = new List<Tuplet<string, List<XDoc>>>();
            Result<DreamMessage> storageCatalog;
            yield return storageCatalog = Storage.At("subscriptions").GetAsync();
            foreach(XDoc wikiSubs in storageCatalog.Value.ToDocument()["folder/name"]) {
                string wikihost = wikiSubs.AsText;
                Tuplet<string, List<XDoc>> wikiDoc = new Tuplet<string, List<XDoc>>(wikihost, new List<XDoc>());
                allWikiSubs.Add(wikiDoc);
                Result<DreamMessage> wikiUsers;
                yield return wikiUsers = Storage.At("subscriptions", wikihost).GetAsync();
                foreach(XDoc userDocname in wikiUsers.Value.ToDocument()["file/name"]) {
                    string userFile = userDocname.AsText;
                    if(!userFile.EndsWith(".xml")) {
                        _log.WarnFormat("Found stray file '{0}' in wiki '{1}' store, ignoring", userFile, wikihost);
                        continue;
                    }
                    Result<DreamMessage> userDoc;
                    yield return userDoc = Storage.At("subscriptions", wikihost, userFile).GetAsync();
                    try {
                        wikiDoc.Item2.Add(userDoc.Value.ToDocument());
                    } catch(InvalidDataException e) {
                        _log.Error(string.Format("Unable to retrieve subscription store for user {0}/{1}", wikihost, userFile), e);
                    }
                }
            }
            _subscriptions = new SubscriptionManager(Self.Uri.AsServerUri().At("notify"), allWikiSubs);
            _subscriptions.RecordsChanged += PersistSubscriptions;
            _subscriptions.SubscriptionsChanged += PushSubscriptionSetUpstream;

            // set up subscription for pubsub
            _baseSubscriptionSet = new XDoc("subscription-set")
                .Elem("uri.owner", Self.Uri.AsServerUri().ToString())
                .Start("subscription")
                    .Elem("channel", "event://*/deki/users/*")
                    .Add(DreamCookie.NewSetCookie("service-key", InternalAccessKey, Self.Uri).AsSetCookieDocument)
                    .Start("recipient")
                        .Attr("authtoken", _apikey)
                        .Elem("uri", Self.Uri.AsServerUri().At("updateuser").ToString())
                    .End()
              .End();
            XDoc subSet = _baseSubscriptionSet.Clone();
            foreach(XDoc sub in _subscriptions.Subscriptions) {
                subSet.Add(sub);
            }
            Result<DreamMessage> subscribe;
            yield return subscribe = PubSub.At("subscribers").PostAsync(subSet);
            string accessKey = subscribe.Value.ToDocument()["access-key"].AsText;
            XUri location = subscribe.Value.Headers.Location;
            Cookies.Update(DreamCookie.NewSetCookie("access-key", accessKey, location), null);
            _subscriptionLocation = Plug.New(location.AsLocalUri().WithoutQuery());
            _log.DebugFormat("set up initial subscription location at {0}", _subscriptionLocation.Uri);

            // set up notification accumulator queue
            TimeSpan accumulationMinutes = TimeSpan.FromSeconds(config["accumulation-time"].AsInt ?? 10 * 60);
            _log.DebugFormat("Initializing queue with {0:0.00} minute accumulation", accumulationMinutes.TotalMinutes);
            _notificationQueue = new NotificationDelayQueue(accumulationMinutes, SendEmail);
            result.Return();
        }
 public void SubscriptionManager_with_initial_subscriptions() {
     List<Tuplet<string, List<XDoc>>> subs = new List<Tuplet<string, List<XDoc>>>();
     List<XDoc> x = new List<XDoc>();
     Tuplet<string, List<XDoc>> xSubs = new Tuplet<string, List<XDoc>>("x", x);
     subs.Add(xSubs);
     x.Add(new XDoc("user")
               .Attr("userid", 1)
               .Elem("email", "foo")
               .Start("subscription.page").Attr("id", 1).Attr("depth", 0).End());
     x.Add(new XDoc("user")
               .Attr("userid", 2)
               .Elem("email", "foo")
               .Start("subscription.page").Attr("id", 1).Attr("depth", 0).End()
               .Start("subscription.page").Attr("id", 2).Attr("depth", 0).End());
     x.Add(new XDoc("user")
               .Attr("userid", 3)
               .Elem("email", "foo")
               .Start("subscription.page").Attr("id", 2).Attr("depth", 0).End());
     List<XDoc> y = new List<XDoc>();
     Tuplet<string, List<XDoc>> ySubs = new Tuplet<string, List<XDoc>>("y", y);
     subs.Add(ySubs);
     y.Add(new XDoc("user")
               .Attr("userid", 10)
               .Elem("email", "foo")
               .Start("subscription.page").Attr("id", 1).Attr("depth", 0).End());
     SubscriptionManager subscriptionManager = new SubscriptionManager(new XUri("test://"), subs);
     List<XDoc> subscriptions = new List<XDoc>(subscriptionManager.Subscriptions);
     Assert.AreEqual(3, subscriptions.Count);
     bool foundXa = false;
     bool foundXb = false;
     bool foundYa = false;
     foreach(XDoc sub in subscriptions) {
         switch(sub["channel"].AsText) {
         case "event://x/deki/pages/create":
             switch(sub["uri.resource"].AsText) {
             case "deki://x/pages/1#depth=0":
                 Assert.AreEqual(2, sub["recipient"].ListLength);
                 foundXa = true;
                 break;
             case "deki://x/pages/2#depth=0":
                 Assert.AreEqual(2, sub["recipient"].ListLength);
                 foundXb = true;
                 break;
             default:
                 Assert.Fail("bad resource for deki X");
                 break;
             }
             break;
         case "event://y/deki/pages/create":
             if(sub["uri.resource"].AsText != "deki://y/pages/1#depth=0") {
                 Assert.Fail("bad resource for deki Y");
             }
             XDoc recipient = sub["recipient"];
             Assert.AreEqual(1, recipient.ListLength);
             Assert.AreEqual("10", recipient["@userid"].AsText);
             foundYa = true;
             break;
         }
     }
     Assert.IsTrue(foundXa);
     Assert.IsTrue(foundXb);
     Assert.IsTrue(foundYa);
     Assert.IsNotNull(subscriptionManager.GetUser("x", 1, false));
     Assert.IsNotNull(subscriptionManager.GetUser("x", 2, false));
     Assert.IsNotNull(subscriptionManager.GetUser("x", 3, false));
     Assert.IsNull(subscriptionManager.GetUser("x", 10, false));
     Assert.IsNotNull(subscriptionManager.GetUser("y", 10, false));
 }
 public void SubscriptionManager_user_subscription_management() {
     var subscriptionManager = new SubscriptionManager(null, null);
     var recordEvents = new List<RecordEventArgs>();
     var recordsEvent = new ManualResetEvent(false);
     subscriptionManager.RecordsChanged += delegate(object sender, RecordEventArgs e) {
         recordEvents.Add(e);
         recordsEvent.Set();
     };
     SubscriptionEventArgs subscriptionEventArgs = null;
     var subscriptionsFired = 0;
     var subscriptionsEvent = new ManualResetEvent(false);
     subscriptionManager.SubscriptionsChanged += delegate(object sender, SubscriptionEventArgs e) {
         subscriptionEventArgs = e;
         subscriptionsFired++;
         subscriptionsEvent.Set();
     };
     _log.Debug("adding resource 1 to user 1");
     recordsEvent.Reset();
     subscriptionsEvent.Reset();
     UserInfo userInfo1 = subscriptionManager.GetUser("a", 1, true);
     userInfo1.AddResource(1, "0");
     userInfo1.Save();
     _log.Debug("waiting on events");
     Assert.IsTrue(recordsEvent.WaitOne(2000, true));
     Assert.IsTrue(subscriptionsEvent.WaitOne(2000, true));
     Assert.IsNotNull(subscriptionManager.GetUser("a", 1, false));
     Assert.AreEqual(1, recordEvents.Count);
     Assert.AreEqual(1, subscriptionsFired);
     Assert.AreEqual("a", recordEvents[0].WikiId);
     Assert.AreEqual(1, recordEvents[0].User.Id);
     Assert.AreEqual(1, subscriptionEventArgs.Subscriptions.Length);
     Assert.AreEqual("deki://a/pages/1#depth=0", subscriptionEventArgs.Subscriptions[0]["uri.resource"].AsText);
     _log.Debug("adding resource 1 to user 2");
     recordsEvent.Reset();
     subscriptionsEvent.Reset();
     UserInfo userInfo2 = subscriptionManager.GetUser("a", 2, true);
     userInfo2.AddResource(1, "0");
     userInfo2.Save();
     _log.Debug("waiting on events");
     Assert.IsTrue(recordsEvent.WaitOne(2000, true));
     Assert.IsTrue(subscriptionsEvent.WaitOne(2000, true));
     Assert.IsNotNull(subscriptionManager.GetUser("a", 2, false));
     Assert.AreEqual(2, recordEvents.Count);
     Assert.AreEqual(2, subscriptionsFired);
     Assert.AreEqual("a", recordEvents[1].WikiId);
     Assert.AreEqual(2, recordEvents[1].User.Id);
     Assert.AreEqual(1, subscriptionEventArgs.Subscriptions.Length);
     Assert.AreEqual("deki://a/pages/1#depth=0", subscriptionEventArgs.Subscriptions[0]["uri.resource"].AsText);
     Assert.AreEqual(2, subscriptionEventArgs.Subscriptions[0]["recipient"].ListLength);
     _log.Debug("adding resource 2 to user 2");
     recordsEvent.Reset();
     subscriptionsEvent.Reset();
     UserInfo userInfo2a = subscriptionManager.GetUser("a", 2, false);
     userInfo2a.AddResource(2, "0");
     userInfo2a.Save();
     _log.Debug("waiting on events");
     Assert.IsTrue(recordsEvent.WaitOne(2000, true));
     Assert.IsTrue(subscriptionsEvent.WaitOne(2000, true));
     UserInfo userInfo = subscriptionManager.GetUser("a", 2, false);
     Assert.AreEqual(2, userInfo.Resources.Length);
     Assert.AreEqual(3, recordEvents.Count);
     Assert.AreEqual(3, subscriptionsFired);
     Assert.AreEqual("a", recordEvents[2].WikiId);
     Assert.AreEqual(2, recordEvents[2].User.Id);
     Assert.AreEqual(2, subscriptionEventArgs.Subscriptions.Length);
     Assert.AreEqual("deki://a/pages/2#depth=0", subscriptionEventArgs.Subscriptions[1]["uri.resource"].AsText);
     Assert.AreEqual(1, subscriptionEventArgs.Subscriptions[1]["recipient"].ListLength);
     _log.Debug("removing resource 1 from user 1");
     recordsEvent.Reset();
     subscriptionsEvent.Reset();
     UserInfo userInfo1a = subscriptionManager.GetUser("a", 1, false);
     userInfo1a.RemoveResource(1);
     userInfo1a.Save();
     _log.Debug("waiting on events");
     Assert.IsTrue(recordsEvent.WaitOne(2000, true));
     Assert.IsTrue(subscriptionsEvent.WaitOne(2000, true));
     Assert.IsNull(subscriptionManager.GetUser("a", 1, false));
     Assert.AreEqual(4, subscriptionsFired);
     Assert.AreEqual(4, recordEvents.Count);
     Assert.AreEqual("a", recordEvents[3].WikiId);
     Assert.AreEqual(1, recordEvents[3].User.Id);
     Assert.AreEqual(2, subscriptionEventArgs.Subscriptions.Length);
     Assert.AreEqual("deki://a/pages/1#depth=0", subscriptionEventArgs.Subscriptions[0]["uri.resource"].AsText);
     Assert.AreEqual(1, subscriptionEventArgs.Subscriptions[0]["recipient"].ListLength);
 }
 public void SubscriptionManager_collapses_subscriptions_to_favor_infinite_depth() {
     SubscriptionManager subscriptionManager = new SubscriptionManager(null, null);
     UserInfo userInfo = subscriptionManager.GetUser("a", 1, true);
     userInfo.AddResource(1, "0");
     userInfo.AddResource(1, "infinity");
     List<XDoc> subscriptions = new List<XDoc>(subscriptionManager.Subscriptions);
     Assert.AreEqual(1, subscriptions.Count);
     Assert.AreEqual("deki://a/pages/1#depth=infinity", subscriptions[0]["uri.resource"].AsText);
 }