public bool DeleteIndex(string indexName)
        {
            ILockFile lockFile         = null;
            var       settingsFilename = GetSettingsFileName(indexName);
            var       lockFilename     = settingsFilename + ".lock";

            // acquire a lock file on the index
            if (!_lockFileManager.TryAcquireLock(lockFilename, ref lockFile))
            {
                Logger.Information("Could not delete the index. Already in use.");
                return(false);
            }

            using (lockFile) {
                if (!_indexManager.HasIndexProvider())
                {
                    return(false);
                }

                var searchProvider = _indexManager.GetSearchIndexProvider();
                if (searchProvider.Exists(indexName))
                {
                    searchProvider.DeleteIndex(indexName);
                }

                DeleteSettings(indexName);
            }

            return(true);
        }
Beispiel #2
0
        public void GenerateShouldNotRunIfLocked()
        {
            _appDataFolder.CreateFile(_warmupFilename, "");
            ILockFile lockFile = null;

            _lockFileManager.TryAcquireLock(_lockFilename, ref lockFile);
            using (lockFile) {
                _warmupUpdater.Generate();
                Assert.That(_appDataFolder.ListFiles(WarmupFolder).Count(), Is.EqualTo(0));
            }

            _warmupUpdater.Generate();
            Assert.That(_appDataFolder.ListFiles(WarmupFolder).Count(), Is.EqualTo(0));
        }
Beispiel #3
0
        public void LockShouldBeGrantedWhenDoesNotExist()
        {
            ILockFile lockFile = null;
            var       granted  = _lockFileManager.TryAcquireLock("foo.txt.lock", ref lockFile);

            Assert.True(granted);
            Assert.NotNull(lockFile);
            Assert.True(_lockFileManager.IsLocked("foo.txt.lock"));
            Assert.Equal(_appDataFolder.ListFiles("").Count(), 1);
        }
Beispiel #4
0
        public virtual async Task RebuildAsync(XmlSitemapBuildContext ctx)
        {
            Guard.NotNull(ctx, nameof(ctx));

            // TODO: (core) Check later if this is still necessary
            //var dataTokens = _httpContextAccessor.HttpContext?.GetRouteData()?.DataTokens;
            //if (dataTokens != null)
            //{
            //    // Double seo code otherwise
            //    dataTokens["CultureCodeReplacement"] = string.Empty;
            //}

            var languageData = new Dictionary <int, LanguageData>();

            foreach (var language in ctx.Languages)
            {
                var lockFilePath = GetLockFilePath(ctx.Store.Id, language.Id);

                if (_lockFileManager.TryAcquireLock(lockFilePath, out var lockFile))
                {
                    // Process only languages that are unlocked right now
                    // It is possible that an HTTP request triggered the generation
                    // of a language specific sitemap.

                    try
                    {
                        var sitemapDir = BuildSitemapDirPath(ctx.Store.Id, language.Id);
                        var data       = new LanguageData
                        {
                            Store        = ctx.Store,
                            Language     = language,
                            LockFile     = lockFile,
                            LockFilePath = lockFilePath,
                            TempDir      = sitemapDir + "~",
                            FinalDir     = sitemapDir,
                            BaseUrl      = await BuildBaseUrlAsync(ctx.Store, language)
                        };

                        _tenantRoot.TryDeleteDirectory(data.TempDir);
                        _tenantRoot.TryCreateDirectory(data.TempDir);

                        languageData[language.Id] = data;
                    }
                    catch
                    {
                        await lockFile.ReleaseAsync();

                        throw;
                    }
                }
            }

            if (languageData.Count == 0)
            {
                Logger.Warn("XML sitemap rebuild already in process.");
                return;
            }

            var languages   = languageData.Values.Select(x => x.Language);
            var languageIds = languages.Select(x => x.Id).Concat(new[] { 0 }).ToArray();

            // All sitemaps grouped by language
            var sitemaps = new Multimap <int, XmlSitemapNode>();

            var compositeFileLock = new AsyncActionDisposable(async() =>
            {
                foreach (var data in languageData.Values)
                {
                    await data.LockFile.ReleaseAsync();
                }
            });

            await using (compositeFileLock)
            {
                // Impersonate
                var prevCustomer = _services.WorkContext.CurrentCustomer;
                // no need to vary xml sitemap by customer roles: it's relevant to crawlers only.
                // TODO: (core) Do not attempt to update CurrentCustomer entity if it is untracked (determine where applicable)
                _services.WorkContext.CurrentCustomer = (await _customerService.GetCustomerBySystemNameAsync(SystemCustomerNames.SearchEngine, false)) ?? prevCustomer;

                try
                {
                    var nodes = new List <XmlSitemapNode>();

                    var providers     = CreateProviders(ctx);
                    var total         = (await providers.SelectAsync(x => x.GetTotalCountAsync()).ToListAsync(ctx.CancellationToken)).Sum();
                    var totalSegments = (int)Math.Ceiling(total / (double)MaximumSiteMapNodeCount);
                    var hasIndex      = totalSegments > 1;
                    var indexNodes    = new Multimap <int, XmlSitemapNode>();
                    var segment       = 0;
                    var numProcessed  = 0;

                    CheckSitemapCount(totalSegments);

                    using (new DbContextScope(_db, autoDetectChanges: false, forceNoTracking: true, lazyLoading: false))
                    {
                        var entities = EnlistEntitiesAsync(providers);

                        await foreach (var batch in entities.SliceAsync(ctx.CancellationToken, MaximumSiteMapNodeCount))
                        {
                            if (ctx.CancellationToken.IsCancellationRequested)
                            {
                                break;
                            }

                            segment++;
                            numProcessed = segment * MaximumSiteMapNodeCount;
                            ctx.ProgressCallback?.Invoke(numProcessed, total, "{0} / {1}".FormatCurrent(numProcessed, total));

                            var slugs = await GetUrlRecordCollectionsForBatchAsync(batch.Select(x => x.Entry).ToList(), languageIds);

                            foreach (var data in languageData.Values)
                            {
                                var language = data.Language;
                                var baseUrl  = data.BaseUrl;

                                // Create all node entries for this segment
                                var entries = batch
                                              .Where(x => x.Entry.LanguageId.GetValueOrDefault() == 0 || x.Entry.LanguageId.Value == language.Id)
                                              .Select(x => x.Provider.CreateNode(_linkGenerator, baseUrl, x.Entry, slugs[x.Entry.EntityName], language));
                                sitemaps[language.Id].AddRange(entries.Where(x => x != null));

                                // Create index node for this segment/language combination
                                if (hasIndex)
                                {
                                    indexNodes[language.Id].Add(new XmlSitemapNode
                                    {
                                        LastMod = sitemaps[language.Id].Select(x => x.LastMod).Where(x => x.HasValue).DefaultIfEmpty().Max(),
                                        Loc     = GetSitemapIndexUrl(segment, baseUrl),
                                    });
                                }

                                if (segment % 5 == 0 || segment == totalSegments)
                                {
                                    // Commit every 5th segment (10.000 nodes) temporarily to disk to minimize RAM usage
                                    var documents = GetSiteMapDocuments((IReadOnlyCollection <XmlSitemapNode>)sitemaps[language.Id]);
                                    await SaveTempAsync(documents, data, segment - documents.Count + (hasIndex ? 1 : 0));

                                    documents.Clear();
                                    sitemaps.RemoveAll(language.Id);
                                }
                            }

                            slugs.Clear();
                        }

                        // Process custom nodes
                        if (!ctx.CancellationToken.IsCancellationRequested)
                        {
                            ctx.ProgressCallback?.Invoke(numProcessed, total, "Processing custom nodes".FormatCurrent(numProcessed, total));
                            await ProcessCustomNodesAsync(ctx, sitemaps);

                            foreach (var data in languageData.Values)
                            {
                                if (sitemaps.ContainsKey(data.Language.Id) && sitemaps[data.Language.Id].Count > 0)
                                {
                                    var documents = GetSiteMapDocuments((IReadOnlyCollection <XmlSitemapNode>)sitemaps[data.Language.Id]);
                                    await SaveTempAsync(documents, data, (segment + 1) - documents.Count + (hasIndex ? 1 : 0));
                                }
                                else if (segment == 0)
                                {
                                    // Ensure that at least one entry exists. Otherwise,
                                    // the system will try to rebuild again.
                                    var homeNode = new XmlSitemapNode {
                                        LastMod = DateTime.UtcNow, Loc = data.BaseUrl
                                    };
                                    var documents = GetSiteMapDocuments(new List <XmlSitemapNode> {
                                        homeNode
                                    });
                                    await SaveTempAsync(documents, data, 0);
                                }
                            }
                        }
                    }

                    ctx.CancellationToken.ThrowIfCancellationRequested();

                    ctx.ProgressCallback?.Invoke(totalSegments, totalSegments, "Finalizing...'");

                    foreach (var data in languageData.Values)
                    {
                        // Create index documents (if any)
                        if (hasIndex && indexNodes.Any())
                        {
                            var indexDocument = CreateSitemapIndexDocument(indexNodes[data.Language.Id]);
                            await SaveTempAsync(new List <string> {
                                indexDocument
                            }, data, 0);
                        }

                        // Save finally (actually renames temp folder)
                        await SaveFinalAsync(data);
                    }
                }
                finally
                {
                    // Undo impersonation
                    _services.WorkContext.CurrentCustomer = prevCustomer;
                    sitemaps.Clear();

                    foreach (var data in languageData.Values)
                    {
                        if (_tenantRoot.DirectoryExists(data.TempDir))
                        {
                            _tenantRoot.TryDeleteDirectory(data.TempDir);
                        }
                    }
                }
            }
        }
 /// <summary>
 /// Tries to acquire a lock with the specified parameters
 /// </summary>
 /// <param name="name">Unique name of the lock</param>
 /// <returns>The ILockFile instance on success or null if the lock couldn't be acquired.</returns>
 public static ILockFile TryAcquireLock(this ILockFileManager lockFileManager, string name)
 {
     return(lockFileManager.TryAcquireLock(name, new TimeSpan(0, 0, 0, 4)));
 }
        public void LockShouldBeGrantedWhenDoesNotExist()
        {
            ILockFile lockFile = null;
            var       granted  = _lockFileManager.TryAcquireLock("foo.txt.lock", ref lockFile);

            Assert.That(granted, Is.True);
            Assert.That(lockFile, Is.Not.Null);
            Assert.That(_lockFileManager.IsLocked("foo.txt.lock"), Is.True);
            Assert.That(_appDataFolder.ListFiles("").Count(), Is.EqualTo(1));
        }
        public void EnsureGenerate()
        {
            var baseUrl = _orchardServices.WorkContext.CurrentSite.BaseUrl;
            var part    = _orchardServices.WorkContext.CurrentSite.As <WarmupSettingsPart>();

            // do nothing while the base url setting is not defined
            if (String.IsNullOrWhiteSpace(baseUrl))
            {
                return;
            }

            // prevent multiple appdomains from rebuilding the static page concurrently (e.g., command line)
            ILockFile lockFile = null;

            if (!_lockFileManager.TryAcquireLock(_lockFilename, ref lockFile))
            {
                return;
            }

            using (lockFile) {
                // check if we need to regenerate the pages by reading the last time it has been done
                // 1- if the warmup file doesn't exists, generate the pages
                // 2- otherwise, if the scheduled generation option is on, check if the delay is over
                if (_appDataFolder.FileExists(_warmupPath))
                {
                    try {
                        var warmupContent = _appDataFolder.ReadFile(_warmupPath);
                        var expired       = XmlConvert.ToDateTimeOffset(warmupContent).AddMinutes(part.Delay);
                        if (expired > _clock.UtcNow)
                        {
                            return;
                        }
                    }
                    catch {
                        // invalid file, delete continue processing
                        _appDataFolder.DeleteFile(_warmupPath);
                    }
                }

                // delete peviously generated pages, by reading the Warmup Report file
                try {
                    var encodedPrefix = WarmupUtility.EncodeUrl("http://www.");

                    foreach (var reportEntry in _reportManager.Read())
                    {
                        try {
                            // use FileName as the SiteBaseUrl could have changed in the meantime
                            var path = _appDataFolder.Combine(BaseFolder, reportEntry.Filename);
                            _appDataFolder.DeleteFile(path);

                            // delete the www-less version too if it's available
                            if (reportEntry.Filename.StartsWith(encodedPrefix, StringComparison.OrdinalIgnoreCase))
                            {
                                var filename = WarmupUtility.EncodeUrl("http://") + reportEntry.Filename.Substring(encodedPrefix.Length);
                                path = _appDataFolder.Combine(BaseFolder, filename);
                                _appDataFolder.DeleteFile(path);
                            }
                        }
                        catch (Exception e) {
                            Logger.Error(e, "Could not delete specific warmup file: ", reportEntry.Filename);
                        }
                    }
                }
                catch (Exception e) {
                    Logger.Error(e, "Could not read warmup report file");
                }

                var reportEntries = new List <ReportEntry>();

                if (!String.IsNullOrEmpty(part.Urls))
                {
                    // loop over every relative url to generate the contents
                    using (var urlReader = new StringReader(part.Urls)) {
                        string relativeUrl;
                        while (null != (relativeUrl = urlReader.ReadLine()))
                        {
                            if (String.IsNullOrWhiteSpace(relativeUrl))
                            {
                                continue;
                            }

                            string url = null;
                            relativeUrl = relativeUrl.Trim();

                            try {
                                url = VirtualPathUtility.RemoveTrailingSlash(baseUrl) + relativeUrl;
                                var filename = WarmupUtility.EncodeUrl(url.TrimEnd('/'));
                                var path     = _appDataFolder.Combine(BaseFolder, filename);

                                var download = _webDownloader.Download(url);

                                if (download != null)
                                {
                                    if (download.StatusCode == HttpStatusCode.OK)
                                    {
                                        // success
                                        _appDataFolder.CreateFile(path, download.Content);

                                        reportEntries.Add(new ReportEntry {
                                            RelativeUrl = relativeUrl,
                                            Filename    = filename,
                                            StatusCode  = (int)download.StatusCode,
                                            CreatedUtc  = _clock.UtcNow
                                        });

                                        // if the base url contains http://www, then also render the www-less one);

                                        if (url.StartsWith("http://www.", StringComparison.OrdinalIgnoreCase))
                                        {
                                            url      = "http://" + url.Substring("http://www.".Length);
                                            filename = WarmupUtility.EncodeUrl(url.TrimEnd('/'));
                                            path     = _appDataFolder.Combine(BaseFolder, filename);
                                            _appDataFolder.CreateFile(path, download.Content);
                                        }
                                    }
                                    else
                                    {
                                        reportEntries.Add(new ReportEntry {
                                            RelativeUrl = relativeUrl,
                                            Filename    = filename,
                                            StatusCode  = (int)download.StatusCode,
                                            CreatedUtc  = _clock.UtcNow
                                        });
                                    }
                                }
                                else
                                {
                                    // download failed
                                    reportEntries.Add(new ReportEntry {
                                        RelativeUrl = relativeUrl,
                                        Filename    = filename,
                                        StatusCode  = 0,
                                        CreatedUtc  = _clock.UtcNow
                                    });
                                }
                            }
                            catch (Exception e) {
                                Logger.Error(e, "Could not extract warmup page content for: ", url);
                            }
                        }
                    }
                }

                _reportManager.Create(reportEntries);

                // finally write the time the generation has been executed
                _appDataFolder.CreateFile(_warmupPath, XmlConvert.ToString(_clock.UtcNow, XmlDateTimeSerializationMode.Utc));
            }
        }