public static XDoc CleanseHtmlDocument(XDoc html)
        {
            if (html.HasName("html"))
            {
                html = html.Clone();

                // remove <head> and <tail> elements
                html["head"].RemoveAll();
                html["tail"].RemoveAll();

                // make sure there is only one body and validate it
                var mainBody = html["body[not(@target)]"];
                if (mainBody.IsEmpty)
                {
                    html.Elem("body");
                    mainBody = html["body[not(@target)]"];
                }
                foreach (XDoc body in html["body[@target]"])
                {
                    body.Remove();
                }
                ValidateXHtml(mainBody, true, true);
            }
            return(html);
        }
Esempio n. 2
0
        /// <summary>
        /// Add text to the document.
        /// </summary>
        /// <param name="tag">Enclosing tag for the text.</param>
        /// <param name="mime">Mime type of the enclosed text.</param>
        /// <param name="xml">The body document to add.</param>
        /// <returns>Returns the current document instance.</returns>
        public XAtomBase AddText(string tag, MimeType mime, XDoc xml)
        {
            if (mime.Match(MimeType.XHTML))
            {
                Start(tag).Attr("type", "xhtml");

                // add content and normalize the root node
                XDoc added = xml.Clone().Rename("div");
                if (added["@xmlns"].IsEmpty)
                {
                    added.Attr("xmlns", "http://www.w3.org/1999/xhtml");
                }
                Add(added);
            }
            else if (mime.Match(MimeType.HTML))
            {
                Start(tag).Attr("type", "html");

                // embed HTML as text
                Value(xml.ToInnerXHtml());
            }
            else
            {
                Start(tag).Attr("type", mime.FullType);
                Add(xml);
            }

            // close element
            End();
            return(this);
        }
        /// <summary>
        /// Clone the current message.
        /// </summary>
        /// <returns>A new message instance.</returns>
        public DreamMessage Clone()
        {
            if (!IsCloneable)
            {
                throw new InvalidOperationException("The current message cannot be cloned. It is either closed or contains a payload that cannot be duplicated.");
            }
            DreamMessage result;

            if (_noContent)
            {
                result = new DreamMessage(Status, Headers);
            }
            else if (_doc != null)
            {
                result = new DreamMessage(Status, Headers, _doc.Clone());
            }
            else if (_stream == Stream.Null || (_stream != null && _stream.IsStreamMemorized()))
            {
                _stream.Position = 0;
                var copy = new ChunkedMemoryStream((int)_stream.Length);
                _stream.CopyTo(copy, _stream.Length);
                _stream.Position = 0;
                copy.Position    = 0;
                result           = new DreamMessage(Status, Headers, ContentType, ContentLength, copy);
            }
            else
            {
                var bytes = ToBytes();
                result = new DreamMessage(Status, Headers, ContentType, bytes);

                // length may differ for HEAD requests
                if (bytes.LongLength != ContentLength)
                {
                    result.Headers.ContentLength = bytes.LongLength;
                }
            }
            if (HasCookies)
            {
                result.Cookies.AddRange(Cookies);
            }
            return(result);
        }
Esempio n. 4
0
        public void Adding_same_document_after_first_dispatch_fires_after_normal_delay()
        {
            XDoc m1 = new XDoc("deki-event")
                      .Attr("wikiid", "abc")
                      .Elem("channel", "event://abc/deki/pages/create")
                      .Elem("uri", "http://foo/baz")
                      .Elem("content.uri", "")
                      .Elem("revision.uri", "")
                      .Elem("path", "baz")
                      .Elem("previous-path", "bar");
            DateTime queueTime1 = DateTime.Now;

            _queue.Enqueue(m1);
            Assert.AreEqual(0, _dispatcher.Dispatches.Count);
            Assert.IsTrue(_dispatcher.ResetEvent.WaitOne(5000, true), "first callback didn't happen");
            _dispatcher.ResetEvent.Reset();
            Assert.AreEqual(1, _dispatcher.Dispatches.Count);
            _queue.Enqueue(m1.Clone());
            Assert.AreEqual(1, _dispatcher.Dispatches.Count);
            Assert.IsTrue(_dispatcher.ResetEvent.WaitOne(5000, true), "second callback didn't happen");
            Assert.AreEqual(2, _dispatcher.Dispatches.Count);
            Assert.AreEqual(0, _peekQueue.Count);
        }
        /// <summary>
        /// Create a new message.
        /// </summary>
        /// <param name="status">Http status.</param>
        /// <param name="headers">Header collection.</param>
        /// <param name="contentType">Content Mime-Type.</param>
        /// <param name="doc">Message body.</param>
        public DreamMessage(DreamStatus status, DreamHeaders headers, MimeType contentType, XDoc doc)
        {
            if (doc == null)
            {
                throw new ArgumentNullException("doc");
            }
            this.Status  = status;
            this.Headers = new DreamHeaders(headers);

            // check if document is empty
            if (doc.IsEmpty)
            {
                // we store empty XML documents as text content; it causes less confusion for browsers
                this.Headers.ContentType   = MimeType.TEXT;
                this.Headers.ContentLength = 0L;
                _doc   = doc;
                _bytes = new byte[0];
            }
            else
            {
                this.Headers.ContentType = contentType ?? MimeType.XML;
                _doc = doc.Clone();
            }
        }
        public static XDoc WebToggle(
            [DekiScriptParam("content to toggle")] XDoc content,
            [DekiScriptParam("title to display for toggle (default: \"Show\")", true)] string title,
            [DekiScriptParam("heading level for title (default: 3)", true)] int?heading,
            [DekiScriptParam("content toggle speed (one of \"slow\", \"normal\", \"fast\" or milliseconds number; default: instantaneous)", true)] string speed,
            [DekiScriptParam("hide content initially (default: true)", true)] bool?hidden
            )
        {
            if (!content["body[not(@target)]"].IsEmpty)
            {
                string id = StringUtil.CreateAlphaNumericKey(8);

                // clone content so we don't modify the original
                content = content.Clone();
                XDoc body = content["body[not(@target)]"];

                // add <style> element
                XDoc head = content["head"];
                if (head.IsEmpty)
                {
                    content.Elem("head");
                    head = content["head"];
                }
                head.Elem("style",
                          @"h1.web-expand,
h2.web-expand,
h3.web-expand,
h4.web-expand,
h5.web-expand,
h6.web-expand {
	cursor: pointer;
}
.web-expand span.web-expander { 
    padding-right: 20px;
    background: transparent url('/skins/common/images/nav-parent-open.gif') no-repeat center right;
} 
.web-expanded span.web-expander { 
    background: transparent url('/skins/common/images/nav-parent-docked.gif') no-repeat center right; 
}");

                // set speed
                if (string.IsNullOrEmpty(speed))
                {
                    speed = string.Empty;
                }
                else
                {
                    int millisec;
                    if (int.TryParse(speed, out millisec))
                    {
                        speed = millisec.ToString();
                    }
                    else
                    {
                        speed = "'" + speed + "'";
                    }
                }

                // create toggelable content
                bool hide = hidden ?? true;
                content
                .Start("body")
                .Start("h" + Math.Max(1, Math.Min(heading ?? 3, 6)))
                .Attr("class", "web-expand" + (hide ? string.Empty : " web-expanded"))
                .Attr("onclick", "$(this).toggleClass('web-expanded').next('#" + id + "').toggle(" + speed + ")")
                .Start("span")
                .Attr("class", "web-expander")
                .Value(title ?? "Show")
                .End()
                .End()
                .Start("div")
                .Attr("id", id)
                .Attr("style", hide ? "display: none;" : string.Empty)
                .AddNodes(body)
                .End()
                .End();
                body.Remove();
            }
            return(content);
        }
Esempio n. 7
0
        public void XmlClone1()
        {
            XDoc doc = _doc.Clone();

            Test("xml serialization", doc.ToString(), "<doc source=\"http://www.mindtouch.com\">Hello <bold style=\"blinking\">World</bold>!<br /><bold>Cool</bold><span>Ce\u00e7i est \"une\" id\u00e9e</span><struct><name>John</name><last>Doe</last></struct></doc>");
        }
Esempio n. 8
0
        public static XDoc GetInstanceSettingsAsDoc(SiteSettingsRetrievalSettings retrieve)
        {
            var    instance = DekiContext.Current.Instance;
            string cachekey = retrieve.IncludeHidden ? CACHE_SETTINGSDOC_WITHHIDDEN : CACHE_SETTINGSDOC;
            XDoc   result   = instance.Cache.Get <XDoc>(cachekey, null);

            if (result == null)
            {
                Dictionary <string, ConfigValue>      config = GetInstanceSettings();
                List <KeyValuePair <string, string> > items  = new List <KeyValuePair <string, string> >();
                lock (config) {
                    foreach (KeyValuePair <string, ConfigValue> entry in config)
                    {
                        if (entry.Value.IsHidden && !retrieve.IncludeHidden)
                        {
                            continue;
                        }

                        // check if overwritten setting was an element
                        int  index     = entry.Key.LastIndexOf('/');
                        bool isElement = ((index + 1) < entry.Key.Length) && (entry.Key[index + 1] != '@');
                        items.Add(new KeyValuePair <string, string>(entry.Key, entry.Value.Value));
                        if (isElement)
                        {
                            if (entry.Value.IsReadOnly)
                            {
                                // we need to add a 'readonly' attribute
                                items.Add(new KeyValuePair <string, string>(entry.Key + READONLY_SUFFIX, "true"));
                            }
                            if (entry.Value.IsHidden)
                            {
                                // we need to add a 'hidden' attribute
                                items.Add(new KeyValuePair <string, string>(entry.Key + HIDDEN_SUFFIX, "true"));
                            }
                        }
                    }
                }

                //Ensure that attributes are after their associated elements to ensure that the #text and the @attribute are part of the same element rather than creating a new element with just the #text
                //after the attribute. Consider moving this to Dream XDocFactory.From
                items.Sort((left, right) => StringUtil.CompareInvariant(left.Key, right.Key));
                result = XDocFactory.From(items, "config");
                var u       = DbUtils.CurrentSession.Users_GetByName(UserBL.ANON_USERNAME);
                var anonDoc = UserBL.GetUserXmlVerbose(u, null, Utils.ShowPrivateUserInfo(u), true, true);
                result.InsertValueAt(ANONYMOUS_USER, anonDoc.ToJson());

                // Add license information
                var licenseManager = DekiContext.Current.LicenseManager;
                var licenseDoc     = licenseManager.GetLicenseDocument(false).Clone();
                licenseDoc.Elem("type", licenseDoc["@type"].Contents);
                _log.Debug("Added private information.");
                licenseDoc["support-agreement"].Remove();
                licenseDoc["source-license"].Remove();
                licenseDoc["license.public"].Remove();
                licenseDoc["grants/service-license"].RemoveAll();
                var now = DateTime.UtcNow;
                foreach (var capability in licenseDoc["grants/*[@date.expire]"])
                {
                    if (capability["@date.expire"].AsDate < now)
                    {
                        capability.Remove();
                    }
                }
                XDoc originalLicenseElems       = result["license"];
                XDoc clonedOriginalLicenseElems = XDoc.Empty;
                if (!originalLicenseElems.IsEmpty)
                {
                    clonedOriginalLicenseElems = originalLicenseElems.Clone();
                    originalLicenseElems.Remove();
                    licenseDoc.AddNodes(clonedOriginalLicenseElems);
                }
                result.Start(LICENSE).AddNodes(licenseDoc).End();
                AddVersionInfo(result);
                instance.Cache.Set(cachekey, result.Clone(), DateTime.UtcNow.AddSeconds(60));
            }
            else
            {
                // TODO: remove the clone once cached settings come out of IKeyValueCache (i.e. are serialized)

                // NOTE(cesarn): We need to clone it because we are stripping and adding
                // elements depending on permissions and what is requested, but we do not
                // want to change the cached version that contains everything.
                result = result.Clone();
            }
            if (!retrieve.IncludeAnonymousUser)
            {
                result[ANONYMOUS_USER].Remove();
                _log.Debug("Removing anonymous's user information.");
            }
            if (!retrieve.IncludeLicense)
            {
                var mandatoryLicenseInfo = new XDoc(LICENSE).Start("state").Attr("readonly", "true").Value(result[LICENSE_STATE].Contents).End();
                if (DekiContext.Current.LicenseManager.LicenseExpiration != DateTime.MaxValue)
                {
                    mandatoryLicenseInfo.Start("expiration").Attr("readonly", "true").Value(result[LICENSE_EXPIRATION].Contents).End();
                }
                var productKey = LicenseBL.Instance.BuildProductKey();
                if (!String.IsNullOrEmpty(productKey))
                {
                    mandatoryLicenseInfo.Start("productkey").Attr("readonly", "true").Value(result[LICENSE_PRODUCTKEY].Contents).End();
                }
                result[LICENSE].Remove();
                _log.Debug("Removing private information elements");
                result.Start(LICENSE).AddNodes(mandatoryLicenseInfo).End();
            }
            result.InsertValueAt("api/@href", DekiContext.Current.Deki.Self.Uri.AsPublicUri().ToString());
            return(result);
        }
Esempio n. 9
0
        /// <summary>
        /// Add text to the document.
        /// </summary>
        /// <param name="tag">Enclosing tag for the text.</param>
        /// <param name="mime">Mime type of the enclosed text.</param>
        /// <param name="xml">The body document to add.</param>
        /// <returns>Returns the current document instance.</returns>
        public XAtomBase AddText(string tag, MimeType mime, XDoc xml)
        {
            if(mime.Match(MimeType.XHTML)) {
                Start(tag).Attr("type", "xhtml");

                // add content and normalize the root node
                XDoc added = xml.Clone().Rename("div");
                if(added["@xmlns"].IsEmpty) {
                    added.Attr("xmlns", "http://www.w3.org/1999/xhtml");
                }
                Add(added);
            } else if(mime.Match(MimeType.HTML)) {
                Start(tag).Attr("type", "html");

                // embed HTML as text
                Value(xml.ToInnerXHtml());
            } else {
                Start(tag).Attr("type", mime.FullType);
                Add(xml);
            }

            // close element
            End();
            return this;
        }
Esempio n. 10
0
        /// <summary>
        /// Create a new message.
        /// </summary>
        /// <param name="status">Http status.</param>
        /// <param name="headers">Header collection.</param>
        /// <param name="contentType">Content Mime-Type.</param>
        /// <param name="doc">Message body.</param>
        public DreamMessage(DreamStatus status, DreamHeaders headers, MimeType contentType, XDoc doc) {
            if(doc == null) {
                throw new ArgumentNullException("doc");
            }
            this.Status = status;
            this.Headers = new DreamHeaders(headers);

            // check if document is empty
            if(doc.IsEmpty) {

                // we store empty XML documents as text content; it causes less confusion for browsers
                this.Headers.ContentType = MimeType.TEXT;
                this.Headers.ContentLength = 0L;
                _doc = doc;
                _bytes = new byte[0];
            } else {
                this.Headers.ContentType = contentType ?? MimeType.XML;
                _doc = doc.Clone();
            }
        }
Esempio n. 11
0
 public void Adding_same_document_after_first_dispatch_fires_after_normal_delay() {
     XDoc m1 = new XDoc("deki-event")
         .Attr("wikiid", "abc")
         .Elem("channel", "event://abc/deki/pages/create")
         .Elem("uri", "http://foo/baz")
         .Elem("content.uri", "")
         .Elem("revision.uri", "")
         .Elem("path", "baz")
         .Elem("previous-path", "bar");
     DateTime queueTime1 = DateTime.Now;
     _queue.Enqueue(m1);
     Assert.AreEqual(0, _dispatcher.Dispatches.Count);
     Assert.IsTrue(_dispatcher.ResetEvent.WaitOne(5000, true), "first callback didn't happen");
     _dispatcher.ResetEvent.Reset();
     Assert.AreEqual(1, _dispatcher.Dispatches.Count);
     _queue.Enqueue(m1.Clone());
     Assert.AreEqual(1, _dispatcher.Dispatches.Count);
     Assert.IsTrue(_dispatcher.ResetEvent.WaitOne(5000, true), "second callback didn't happen");
     Assert.AreEqual(2, _dispatcher.Dispatches.Count);
     Assert.AreEqual(0, _peekQueue.Count);
 }
        //--- 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 static XDoc CleanseHtmlDocument(XDoc html) {
            if(html.HasName("html")) {
                html = html.Clone();

                // remove <head> and <tail> elements
                html["head"].RemoveAll();
                html["tail"].RemoveAll();

                // make sure there is only one body and validate it
                var mainBody = html["body[not(@target)]"];
                if(mainBody.IsEmpty) {
                    html.Elem("body");
                    mainBody = html["body[not(@target)]"];
                }
                foreach(XDoc body in html["body[@target]"]) {
                    body.Remove();
                }
                ValidateXHtml(mainBody, true, true);
            }
            return html;
        }
Esempio n. 14
0
        //--- Methods ---

        /// <summary>
        /// Store a document in the collection.
        /// </summary>
        /// <param name="doc">Document to store.</param>
        /// <param name="force"><see langword="True"/> if the write should proceed even if optimistic locking meta-data indicates the document is older than the document already stored.</param>
        /// <returns><see langword="True"/> if the action completed successfully.</returns>
        public bool Put(XDoc doc, bool force)
        {
            Map(doc);
            string docid          = doc[_idXPath].AsText;
            string revisionClause = string.Empty;
            XDoc   revisionAttr   = doc["@docstore:revision"];

            if (!revisionAttr.IsEmpty)
            {
                if (!force)
                {
                    int?rev = revisionAttr.AsInt;
                    if (rev.HasValue)
                    {
                        int pk = doc["@docstore:id"].AsInt ?? 0;
                        revisionClause = string.Format("AND id = {0} AND revision = {1}", pk, rev.Value);
                    }
                }

                // if we have docstore specific tags, we need to remove them before storage, but don't want to alter the doc
                // that was given to us
                doc = doc.Clone();
                Map(doc);
                doc["@docstore:revision"].Remove();
                doc["@docstore:id"].Remove();
            }
            if (string.IsNullOrEmpty(docid))
            {
                throw new ArgumentException(string.Format("Document does not contain a valid value at '{0}'", _idXPath));
            }
            int rowsAffected = 0;
            int id           = 0;
            int revision     = 0;

            // try update first, check for rows affected
            _catalog.NewQuery(string.Format(@"
UPDATE {0} SET id = (@id := id), doc = ?DOC, revision = (@revision := revision + 1) WHERE doc_id = ?DOCID {1};
SELECT ROW_COUNT(), @id, @revision;", _name, revisionClause))
            .With("DOCID", docid)
            .With("DOC", doc.ToString())
            .Execute(delegate(IDataReader reader) {
                while (reader.Read())
                {
                    rowsAffected = reader.GetInt32(0);
                    if (rowsAffected == 0)
                    {
                        continue;
                    }

                    // Note (arnec): have to fetch as string and convert to int, because @variables in mysql
                    // are already returned as byte arrays representing strings
                    id       = Convert.ToInt32(reader.GetString(1));
                    revision = Convert.ToInt32(reader.GetString(2));
                }
            });
            bool wroteData = (rowsAffected > 0);

            // if there was a revisionClause it's always an update, so we can skip the next block
            if (string.IsNullOrEmpty(revisionClause) && rowsAffected == 0)
            {
                // no row updated, try insert
                try {
                    id = _catalog.NewQuery(string.Format(@"
INSERT INTO {0} (doc_id,doc) VALUES (?DOCID,?VALUE);
SELECT last_insert_id();", _name))
                         .With("DOCID", docid)
                         .With("VALUE", doc.ToString()).ReadAsInt() ?? 0;
                    revision = 1;
                } catch (Exception e) {
                    // Note: need to do this by reflection magic, because Dream doesn't take DB dependencies at the dll level
                    bool isDuplicate = false;
                    if (StringUtil.EqualsInvariant(e.GetType().ToString(), "MySql.Data.MySqlClient.MySqlException"))
                    {
                        try {
                            int errorNumber = (int)e.GetType().GetProperty("Number").GetValue(e, null);

                            // trap for duplicate key collisions
                            if (errorNumber == 1062)
                            {
                                isDuplicate = true;
                            }
                        } catch { }
                        if (!isDuplicate)
                        {
                            throw;
                        }
                    }
                }
                if (id == 0)
                {
                    // insert failed, try update once more
                    _catalog.NewQuery(string.Format(@"
UPDATE {0} SET id = (@id := id), doc = ?DOC, revision = (@revision := revision + 1) WHERE doc_id = ?DOCID;
SELECT @id, @revision;", _name))
                    .With("DOCID", docid)
                    .With("DOC", doc.ToString())
                    .Execute(delegate(IDataReader reader) {
                        while (reader.Read())
                        {
                            // Note (arnec): have to fetch as string and convert to int, because @variables in mysql
                            // are already returned as byte arrays representing strings
                            id       = Convert.ToInt32(reader.GetString(0));
                            revision = Convert.ToInt32(reader.GetString(1));
                        }
                    });
                }
                wroteData = true;
            }

            if (wroteData)
            {
                _indexer.QueueUpdate(id, revision, doc);
            }
            return(wroteData);
        }
Esempio n. 15
0
        //--- Methods ---
        /// <summary>
        /// Store a document in the collection.
        /// </summary>
        /// <param name="doc">Document to store.</param>
        /// <param name="force"><see langword="True"/> if the write should proceed even if optimistic locking meta-data indicates the document is older than the document already stored.</param>
        /// <returns><see langword="True"/> if the action completed successfully.</returns>
        public bool Put(XDoc doc, bool force)
        {
            Map(doc);
            string docid = doc[_idXPath].AsText;
            string revisionClause = string.Empty;
            XDoc revisionAttr = doc["@docstore:revision"];
            if(!revisionAttr.IsEmpty) {
                if(!force) {
                    int? rev = revisionAttr.AsInt;
                    if(rev.HasValue) {
                        int pk = doc["@docstore:id"].AsInt ?? 0;
                        revisionClause = string.Format("AND id = {0} AND revision = {1}", pk, rev.Value);
                    }
                }

                // if we have docstore specific tags, we need to remove them before storage, but don't want to alter the doc
                // that was given to us
                doc = doc.Clone();
                Map(doc);
                doc["@docstore:revision"].Remove();
                doc["@docstore:id"].Remove();
            }
            if(string.IsNullOrEmpty(docid)) {
                throw new ArgumentException(string.Format("Document does not contain a valid value at '{0}'", _idXPath));
            }
            int rowsAffected = 0;
            int id = 0;
            int revision = 0;

            // try update first, check for rows affected
            _catalog.NewQuery(string.Format(@"
            UPDATE {0} SET id = (@id := id), doc = ?DOC, revision = (@revision := revision + 1) WHERE doc_id = ?DOCID {1};
            SELECT ROW_COUNT(), @id, @revision;", _name, revisionClause))
                    .With("DOCID", docid)
                    .With("DOC", doc.ToString())
                .Execute(delegate(IDataReader reader) {
                while(reader.Read()) {
                    rowsAffected = reader.GetInt32(0);
                    if(rowsAffected == 0) {
                        continue;
                    }

                    // Note (arnec): have to fetch as string and convert to int, because @variables in mysql
                    // are already returned as byte arrays representing strings
                    id = Convert.ToInt32(reader.GetString(1));
                    revision = Convert.ToInt32(reader.GetString(2));
                }
            });
            bool wroteData = (rowsAffected > 0);

            // if there was a revisionClause it's always an update, so we can skip the next block
            if(string.IsNullOrEmpty(revisionClause) && rowsAffected == 0) {

                // no row updated, try insert
                try {
                    id = _catalog.NewQuery(string.Format(@"
            INSERT INTO {0} (doc_id,doc) VALUES (?DOCID,?VALUE);
            SELECT last_insert_id();", _name))
                             .With("DOCID", docid)
                             .With("VALUE", doc.ToString()).ReadAsInt() ?? 0;
                    revision = 1;
                } catch(Exception e) {

                    // Note: need to do this by reflection magic, because Dream doesn't take DB dependencies at the dll level
                    bool isDuplicate = false;
                    if(StringUtil.EqualsInvariant(e.GetType().ToString(), "MySql.Data.MySqlClient.MySqlException")) {
                        try {
                            int errorNumber = (int)e.GetType().GetProperty("Number").GetValue(e, null);

                            // trap for duplicate key collisions
                            if(errorNumber == 1062) {
                                isDuplicate = true;
                            }
                        } catch { }
                        if(!isDuplicate) {
                            throw;
                        }
                    }
                }
                if(id == 0) {

                    // insert failed, try update once more
                    _catalog.NewQuery(string.Format(@"
            UPDATE {0} SET id = (@id := id), doc = ?DOC, revision = (@revision := revision + 1) WHERE doc_id = ?DOCID;
            SELECT @id, @revision;", _name))
                        .With("DOCID", docid)
                        .With("DOC", doc.ToString())
                        .Execute(delegate(IDataReader reader) {
                        while(reader.Read()) {

                            // Note (arnec): have to fetch as string and convert to int, because @variables in mysql
                            // are already returned as byte arrays representing strings
                            id = Convert.ToInt32(reader.GetString(0));
                            revision = Convert.ToInt32(reader.GetString(1));
                        }
                    });
                }
                wroteData = true;
            }

            if(wroteData) {
                _indexer.QueueUpdate(id, revision, doc);
            }
            return wroteData;
        }
Esempio n. 16
0
        internal static XDoc TruncateTocDepth(XDoc toc, int? depth) {

            // check if we need to limit the depth
            if(depth != null) {
                toc = toc.Clone();
                string xpath = "." + "/ol/li".RepeatPattern(Math.Max(0, depth.Value)) + "/ol";
                toc[xpath].RemoveAll();
            }
            return toc;
        }