/// <summary>
        ///
        /// </summary>
        /// <param name="name">A name for this tree, for your reference</param>
        /// <param name="globalRootItemPath">The 'global' path where this tree is rooted. For example, if this was '/sitecore/content', the root item in this tree would be 'content'</param>
        /// <param name="databaseName">Name of the database the items in this tree are from. This is for your reference and help when resolving this tree as a destination, and is not directly used.</param>
        /// <param name="physicalRootPath">The physical root path to write items in this tree to. Will be created if it does not exist.</param>
        /// <param name="formatter">The formatter to use when reading or writing items to disk</param>
        /// <param name="useDataCache">Whether to cache items read in memory for later rapid retrieval. Great for small trees, or if you have plenty of RAM. Bad for media trees :)</param>
        public SerializationFileSystemTree(string name, string globalRootItemPath, string databaseName, string physicalRootPath, ISerializationFormatter formatter, bool useDataCache)
        {
            Assert.ArgumentNotNullOrEmpty(globalRootItemPath, "globalRootItemPath");
            Assert.ArgumentNotNullOrEmpty(databaseName, "databaseName");
            Assert.ArgumentNotNullOrEmpty(physicalRootPath, "physicalRootPath");
            Assert.ArgumentNotNull(formatter, "formatter");
            Assert.IsTrue(globalRootItemPath.StartsWith("/"), "The global root item path must start with '/', e.g. '/sitecore' or '/sitecore/content'");
            Assert.IsTrue(globalRootItemPath.Length > 1, "The global root item path cannot be '/' - there is no root item. You probably mean '/sitecore'.");

            _globalRootItemPath = globalRootItemPath.TrimEnd('/');
            // enforce that the physical root path is filesystem-safe
            AssertValidPhysicalPath(physicalRootPath);
            _physicalRootPath = physicalRootPath;
            _formatter        = formatter;
            _dataCache        = new FsCache <IItemData>(useDataCache);
            Name         = name;
            DatabaseName = databaseName;

            if (!Directory.Exists(_physicalRootPath))
            {
                Directory.CreateDirectory(_physicalRootPath);
            }

            _treeWatcher = new TreeWatcher(_physicalRootPath, _formatter.FileExtension, HandleDataItemChanged);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="name">A name for this tree, for your reference</param>
        /// <param name="globalRootItemPath">The 'global' path where this tree is rooted. For example, if this was '/sitecore/content', the root item in this tree would be 'content'</param>
        /// <param name="databaseName">Name of the database the items in this tree are from. This is for your reference and help when resolving this tree as a destination, and is not directly used.</param>
        /// <param name="physicalRootPath">The physical root path to write items in this tree to. Will be created if it does not exist.</param>
        /// <param name="formatter">The formatter to use when reading or writing items to disk</param>
        /// <param name="useDataCache">Whether to cache items read in memory for later rapid retrieval. Great for small trees, or if you have plenty of RAM. Bad for media trees :)</param>
        public SerializationFileSystemTree(string name, string globalRootItemPath, string databaseName, string physicalRootPath, ISerializationFormatter formatter, bool useDataCache)
        {
            Assert.ArgumentNotNullOrEmpty(globalRootItemPath, "globalRootItemPath");
            Assert.ArgumentNotNullOrEmpty(databaseName, "databaseName");
            Assert.ArgumentNotNullOrEmpty(physicalRootPath, "physicalRootPath");
            Assert.ArgumentNotNull(formatter, "formatter");
            Assert.IsTrue(globalRootItemPath.StartsWith("/"), "The global root item path must start with '/', e.g. '/sitecore' or '/sitecore/content'");
            Assert.IsTrue(globalRootItemPath.Length > 1, "The global root item path cannot be '/' - there is no root item. You probably mean '/sitecore'.");

            _globalRootItemPath = globalRootItemPath.TrimEnd('/');
            PhysicalRootPath = physicalRootPath;
            _formatter = formatter;
            _dataCache = new FsCache<IItemData>(useDataCache);
            Name = name;
            DatabaseName = databaseName;

            if (!Directory.Exists(PhysicalRootPath)) Directory.CreateDirectory(PhysicalRootPath);

            _treeWatcher = new TreeWatcher(PhysicalRootPath, _formatter.FileExtension, HandleDataItemChanged);
        }
        protected virtual void HandleDataItemChanged(string path, TreeWatcher.TreeWatcherChangeType changeType)
        {
            if (changeType == TreeWatcher.TreeWatcherChangeType.ChangeOrAdd)
            {
                Log.Info(string.Format("[Rainbow] SFS tree item {0} changed ({1}), caches updating.", path, changeType), this);

                const int retries = 5;
                for (int i = 0; i < retries; i++)
                {
                    try
                    {
                        // note that the act of reading the metadata will update the metadata cache automatically
                        // (it'll either not be in cache or have a newer write time thus invalidating FsCache)
                        var metadata = ReadItemMetadata(path);
                        if (metadata != null)
                        {
                            if (TreeItemChanged != null) TreeItemChanged(metadata);
                        }

                        _dataCache.Remove(path);
                    }
                    catch (IOException iex)
                    {
                        // this is here because FSW can tell us the file has changed
                        // BEFORE it's done with writing. So if we get access denied,
                        // we wait 500ms and retry up to 5x before rethrowing
                        if (i < retries - 1)
                        {
                            Thread.Sleep(500);
                            continue;
                        }

                        Log.Error("[Rainbow] Failed to read {0} metadata because the file remained locked too long.".FormatWith(path), iex, this);
                    }
                    catch (Exception ex)
                    {
                        Log.Error("[Rainbow] Failed to read updated file {0}. This may indicate a merge conflict or corrupt file. We'll retry reading it if it changes again.".FormatWith(path), ex, this);
                    }

                    break;
                }
            }

            if (changeType == TreeWatcher.TreeWatcherChangeType.Delete)
            {
                Log.Info(string.Format("Serialized item {0} deleted, reloading caches.", path), this);

                var existingCached = _metadataCache.GetValue(path, false);

                _dataCache.Remove(path);
                _metadataCache.Remove(path);

                if (existingCached != null)
                {
                    IItemMetadata temp;
                    _idCache.TryRemove(existingCached.Id, out temp);

                    if (TreeItemChanged != null)
                    {
                        if (TreeItemChanged != null) TreeItemChanged(existingCached);
                        return;
                    }
                }

                if (TreeItemChanged != null) TreeItemChanged(null);
            }
        }