public void CreateFileWillCauseDirectoryToBeCreated() { Assert.False(Directory.Exists(Path.Combine(_tempFolder, String.Format("App_Data{0}alpha{0}omega{0}foo", Path.DirectorySeparatorChar)))); _appDataFolder.CreateFile(String.Format("alpha{0}omega{0}foo{0}bar.txt", Path.DirectorySeparatorChar), "quux"); Assert.True(Directory.Exists(Path.Combine(_tempFolder, String.Format("App_Data{0}alpha{0}omega{0}foo", Path.DirectorySeparatorChar)))); }
public void Set(string key, CacheItem cacheItem) { Retry(() => { if (cacheItem == null) { throw new ArgumentNullException("cacheItem"); } if (cacheItem.ValidFor <= 0) { return; } var hash = GetCacheItemFileHash(key); lock (String.Intern(hash)) { var filename = _appDataFolder.Combine(_content, hash); using (var fileStream = _appDataFolder.CreateFile(filename)) { using (var writer = new BinaryWriter(fileStream)) { fileStream.Write(cacheItem.Output, 0, cacheItem.Output.Length); } } using (var stream = SerializeMetadata(cacheItem)) { filename = _appDataFolder.Combine(_metadata, hash); using (var fileStream = _appDataFolder.CreateFile(filename)) { stream.CopyTo(fileStream); } } } }); }
public void Store(string name, ShellDescriptor descriptor) { VerifyCacheFile(); var text = appDataFolder.ReadFile(DescriptorCacheFileName); bool tenantCacheUpdated = false; var saveWriter = new StringWriter(); var xmlDocument = new XmlDocument(); xmlDocument.LoadXml(text); XmlNode rootNode = xmlDocument.DocumentElement; if (rootNode != null) { foreach (XmlNode tenantNode in rootNode.ChildNodes) { if (String.Equals(tenantNode.Name, name, StringComparison.OrdinalIgnoreCase)) { tenantNode.InnerText = GetCacheTextForShellDescriptor(descriptor); tenantCacheUpdated = true; break; } } if (!tenantCacheUpdated) { XmlElement newTenant = xmlDocument.CreateElement(name); newTenant.InnerText = GetCacheTextForShellDescriptor(descriptor); rootNode.AppendChild(newTenant); } } xmlDocument.Save(saveWriter); appDataFolder.CreateFile(DescriptorCacheFileName, saveWriter.ToString()); }
private bool UpdateIndex(string indexName, string settingsFilename, IndexSettings indexSettings) { var addToIndex = new List <IDocumentIndex>(); // Rebuilding the inde logger.Value.Info("Rebuilding index"); indexingStatus = IndexingStatus.Rebuilding; foreach (var contentProvider in contentProviders) { addToIndex.AddRange(contentProvider.GetDocuments(id => indexProvider.New(id))); } // save current state of the index indexSettings.LastIndexedUtc = DateTime.UtcNow; appDataFolder.CreateFile(settingsFilename, indexSettings.ToXml()); if (addToIndex.Count == 0) { // nothing more to do indexingStatus = IndexingStatus.Idle; return(false); } // save new and updated documents to the index indexProvider.Store(indexName, addToIndex); logger.Value.InfoFormat("Added documents to index: {0}", addToIndex.Count); return(true); }
public PipelineFilePart Create(string name, string extension) { var file = Common.GetSafeFileName(_orchardServices.WorkContext.CurrentUser.UserName, name, extension); var fileFolder = Common.GetAppFolder(); if (!_appDataFolder.DirectoryExists(fileFolder)) { _appDataFolder.CreateDirectory(fileFolder); } if (!_appDataFolder.DirectoryExists(fileFolder)) { _appDataFolder.CreateDirectory(fileFolder); } var path = _appDataFolder.Combine(fileFolder, file); _appDataFolder.CreateFile(path, string.Empty); var part = _orchardServices.ContentManager.New <PipelineFilePart>(Common.PipelineFileName); part.FullPath = _appDataFolder.MapPath(path); part.Direction = "Out"; _orchardServices.ContentManager.Create(part); return(part); }
public FilePart Create(string name, string extension) { var file = string.Format("{0}-{1}-{2}.{3}", _orchardServices.WorkContext.CurrentUser.UserName, _clock.UtcNow.ToString(FILE_TIMESTAMP), name, extension.TrimStart(".".ToCharArray()) ); if (!_appDataFolder.DirectoryExists(TRANSFORMALIZE_FOLDER)) { _appDataFolder.CreateDirectory(TRANSFORMALIZE_FOLDER); } var path = _appDataFolder.Combine(TRANSFORMALIZE_FOLDER, file); _appDataFolder.CreateFile(path, string.Empty); var part = _orchardServices.ContentManager.New <FilePart>("File"); part.FullPath = _appDataFolder.MapPath(path); part.Direction = "Out"; _orchardServices.ContentManager.Create(part); return(part); }
public String GetInstanceId() { if (_instanceId.IsNullOrEmpty()) { lock (this) { if (_instanceId.IsNullOrEmpty()) { IAppDataFolder folder = _serviceProvider.GetRequiredService <IAppDataFolder>(); string id = ToolHelper.NewShortId(); string fileName = $"{_appName}.instance"; if (!folder.FileExists(fileName)) { folder.CreateFile(fileName, id); } else { id = folder.ReadFile(fileName); } _instanceId = id; } } } return(_instanceId); }
private void StoreConfiguration(ConfigurationCache cache) { if (!_hostEnvironment.IsFullTrust) { return; } var pathName = GetPathName(_shellSettings.Name); try { var formatter = new BinaryFormatter(); using (var stream = _appDataFolder.CreateFile(pathName)) { formatter.Serialize(stream, cache.Hash); formatter.Serialize(stream, cache.Configuration); } } catch (SerializationException e) { //Note: This can happen when multiple processes/AppDomains try to save // the cached configuration at the same time. Only one concurrent // writer will win, and it's harmless for the other ones to fail. for (Exception scan = e; scan != null; scan = scan.InnerException) { Logger.Warning("Error storing new NHibernate cache configuration: {0}", scan.Message); } } }
public PipelineFilePart Create(string name, string extension) { var file = string.Format("{0}-{1}-{2}.{3}", _orchardServices.WorkContext.CurrentUser.UserName, _clock.UtcNow.ToString(FileTimestamp), name, extension.TrimStart(".".ToCharArray()) ); var fileFolder = Common.FileFolder; if (!_appDataFolder.DirectoryExists(fileFolder)) { _appDataFolder.CreateDirectory(fileFolder); } var path = _appDataFolder.Combine(fileFolder, file); _appDataFolder.CreateFile(path, string.Empty); var part = _orchardServices.ContentManager.New <PipelineFilePart>(Common.PipelineFileName); part.FullPath = _appDataFolder.MapPath(path); part.Direction = "Out"; _orchardServices.ContentManager.Create(part); return(part); }
private void TouchFile() { try { _appDataFolder.CreateFile(fileName, "Host Restart"); } catch (Exception e) { Logger.Warning(e, "Error updating file '{0}'", fileName); } }
public LockFile(IAppDataFolder appDataFolder, string path, string content, ReaderWriterLockSlim rwLock) { _appDataFolder = appDataFolder; _path = path; _content = content; _rwLock = rwLock; // create the physical lock file _appDataFolder.CreateFile(path, content); }
public void Enqueue(string executionId, RecipeStep step) { var recipeStepElement = new XElement("RecipeStep"); recipeStepElement.Add(new XElement("Name", step.Name)); recipeStepElement.Add(step.Step); if (_appDataFolder.DirectoryExists(Path.Combine(_recipeQueueFolder, executionId))) { int stepIndex = GetLastStepIndex(executionId) + 1; _appDataFolder.CreateFile(Path.Combine(_recipeQueueFolder, executionId + Path.DirectorySeparatorChar + stepIndex), recipeStepElement.ToString()); } else { _appDataFolder.CreateFile( Path.Combine(_recipeQueueFolder, executionId + Path.DirectorySeparatorChar + "0"), recipeStepElement.ToString()); } }
private void TouchFile() { try { _appDataFolder.CreateFile(FileName, "Host Restart"); } catch (Exception e) { Logger.Warning(e, "更新文件 '{0}' 时发生了错误。", FileName); } }
private void TouchFile() { try { _appDataFolder.CreateFile(fileName, "Host Restart"); } catch (Exception ex) { if (ex.IsFatal()) { throw; } Logger.Warning(ex, "Error updating file '{0}'", fileName); } }
void IShellSettingsManager.SaveSettings(ShellSettings settings) { if (settings == null) { Logger.Error("shell settings is null"); throw new ArgumentNullException("settings"); } Logger.Debug("Saving ShellSettings"); var filePath = Path.Combine(Path.Combine("Sites", settings.Name), _settingsFileName); _appDataFolder.CreateFile(filePath, ShellSettingsSerializer.ComposeSettings(settings)); Logger.Information("ShellSettings saved successfully; "); _events.Saved(settings); }
private string ReadFileSetting() { var content = ""; _encryptionKeys = new Dictionary <string, string>(); if (!_appDataFolder.FileExists(_filePath)) { var key = ""; content = "TheDefaultChannel" + ":" + key + Environment.NewLine; _encryptionKeys.Add("TheDefaultChannel", key); _appDataFolder.CreateFile(_filePath, content); } else { var filecontent = _appDataFolder.ReadFile(_filePath); var lines = filecontent.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); foreach (string line in lines) { var keyval = line.Split(':'); if (keyval.Length > 1) { if (!string.IsNullOrEmpty(line.Substring(keyval[0].Length + 1).Trim())) { _encryptionKeys.Add(keyval[0], line.Substring(keyval[0].Length + 1).Trim()); } else { _encryptionKeys.Add(keyval[0], ""); } } } content = filecontent; } _lastWriteTimedUtc = _appDataFolder.GetFileLastWriteTimeUtc(_filePath); return(content); }
private string WriteExportFile(string exportDocument) { var exportFile = string.Format("Export-{0}-{1}.xml", _orchardServices.WorkContext.CurrentUser.UserName, DateTime.UtcNow.Ticks); if (!_appDataFolder.DirectoryExists(ExportsDirectory)) { _appDataFolder.CreateDirectory(ExportsDirectory); } var path = _appDataFolder.Combine(ExportsDirectory, exportFile); _appDataFolder.CreateFile(path, exportDocument); return(_appDataFolder.MapPath(path)); }
/// <summary> /// 将一个外壳描述符存储到缓存中。 /// </summary> /// <param name="shellName">外壳名称。</param> /// <param name="descriptor">外壳描述符。</param> public void Store(string shellName, ShellDescriptor descriptor) { if (Disabled) { return; } lock (SynLock) { VerifyCacheFile(); var text = _appDataFolder.ReadFile(DescriptorCacheFileName); var tenantCacheUpdated = false; var saveWriter = new StringWriter(); var xmlDocument = new XmlDocument(); xmlDocument.LoadXml(text); XmlNode rootNode = xmlDocument.DocumentElement; if (rootNode != null) { foreach (var tenantNode in rootNode.ChildNodes.Cast <XmlNode>().Where(tenantNode => string.Equals(tenantNode.Name, shellName, StringComparison.OrdinalIgnoreCase))) { tenantNode.InnerText = GetCacheTextForShellDescriptor(descriptor); tenantCacheUpdated = true; break; } if (!tenantCacheUpdated) { var newTenant = xmlDocument.CreateElement(shellName); newTenant.InnerText = GetCacheTextForShellDescriptor(descriptor); rootNode.AppendChild(newTenant); } } xmlDocument.Save(saveWriter); _appDataFolder.CreateFile(DescriptorCacheFileName, saveWriter.ToString()); } }
void IShellSettingsManager.SaveSettings(ShellSettings settings) { if (settings == null) { throw new ArgumentException(T("There are no settings to save.").ToString()); } if (string.IsNullOrEmpty(settings.Name)) { throw new ArgumentException(T("Settings \"Name\" is not set.").ToString()); } var filePath = Path.Combine(Path.Combine("Sites", settings.Name), "Settings.txt"); _appDataFolder.CreateFile(filePath, ShellSettingsSerializer.ComposeSettings(settings)); _events.Saved(settings); }
public void Create(IEnumerable <ReportEntry> reportEntries) { var report = new XDocument(new XElement("WarmupReport")); foreach (var reportEntry in reportEntries) { report.Root.Add( new XElement("ReportEntry", new XAttribute("RelativeUrl", reportEntry.RelativeUrl), new XAttribute("Filename", reportEntry.Filename), new XAttribute("StatusCode", reportEntry.StatusCode), new XAttribute("CreatedUtc", XmlConvert.ToString(reportEntry.CreatedUtc, XmlDateTimeSerializationMode.Utc)) ) ); } _appDataFolder.CreateFile(_warmupReportPath, report.ToString()); }
public string WriteExportFile(XDocument recipeDocument) { var exportFile = String.Format("Export-{0}-{1}.xml", _orchardServices.WorkContext.CurrentUser.UserName, _clock.UtcNow.Ticks); if (!_appDataFolder.DirectoryExists(ExportsDirectory)) { _appDataFolder.CreateDirectory(ExportsDirectory); } var path = _appDataFolder.Combine(ExportsDirectory, exportFile); using (var writer = new XmlTextWriter(_appDataFolder.CreateFile(path), Encoding.UTF8)) { writer.Formatting = Formatting.Indented; recipeDocument.WriteTo(writer); } return(_appDataFolder.MapPath(path)); }
void IShellSettingsManager.SaveSettings(ShellSettings settings) { if (settings == null) { throw new ArgumentNullException("settings"); } if (String.IsNullOrEmpty(settings.Name)) { throw new ArgumentException("The Name property of the supplied ShellSettings object is null or empty; the settings cannot be saved.", "settings"); } Logger.Information("Saving ShellSettings for tenant '{0}'...", settings.Name); var filePath = Path.Combine(Path.Combine("Sites", settings.Name), SettingsFileName); _appDataFolder.CreateFile(filePath, ShellSettingsSerializer.ComposeSettings(settings)); Logger.Information("ShellSettings saved successfully; flagging tenant '{0}' for restart.", settings.Name); _events.Saved(settings); }
public void Save(IEnumerable <Report> reports) { lock ( _synLock ) { var xmlDocument = new XDocument(); xmlDocument.Add(new XElement("Reports")); foreach (var report in reports) { var reportNode = new XElement("Report"); var writer = new StringWriter(); using (var xmlWriter = XmlWriter.Create(writer)) { _dataContractSerializer.WriteObject(xmlWriter, report); } reportNode.Value = writer.ToString(); xmlDocument.Root.Add(reportNode); } var saveWriter = new StringWriter(); xmlDocument.Save(saveWriter); _appDataFolder.CreateFile(_reportsFileName, saveWriter.ToString()); } }
public void Set(string key, CacheItem cacheItem) { Retry(() => { if (cacheItem == null) { throw new ArgumentNullException("cacheItem"); } if (cacheItem.ValidFor <= 0) { return; } var filename = GetCacheItemFilename(key); using (var stream = Serialize(cacheItem)) { using (var fileStream = _appDataFolder.CreateFile(filename)) { stream.CopyTo(fileStream); } } }); }
public void StampFileShouldBeDeletedToForceAnUpdate() { _appDataFolder.CreateFile(_warmupFilename, ""); _warmupUpdater.Generate(); Assert.That(_appDataFolder.ListFiles(WarmupFolder).Count(), Is.EqualTo(0)); }
/// <summary> /// Indexes a batch of content items /// </summary> /// <returns> /// <c>true</c> if there are more items to process; otherwise, <c>false</c>. /// </returns> private bool BatchIndex(string indexName, string settingsFilename, IndexSettings indexSettings) { var addToIndex = new List <IDocumentIndex>(); var deleteFromIndex = new List <int>(); bool loop = false; // Rebuilding the index ? if (indexSettings.Mode == IndexingMode.Rebuild) { Logger.Information("Rebuilding index"); _indexingStatus = IndexingStatus.Rebuilding; do { loop = true; // load all content items var contentItems = _contentRepository .Table.Where(versionRecord => versionRecord.Latest && versionRecord.Id > indexSettings.LastContentId) .OrderBy(versionRecord => versionRecord.Id) .Take(ContentItemsPerLoop) .ToList() .Select(versionRecord => _contentManager.Get(versionRecord.ContentItemRecord.Id, VersionOptions.VersionRecord(versionRecord.Id))) .Distinct() .ToList(); // if no more elements to index, switch to update mode if (contentItems.Count == 0) { indexSettings.Mode = IndexingMode.Update; } foreach (var item in contentItems) { try { var settings = GetTypeIndexingSettings(item); // skip items from types which are not indexed if (settings.List.Contains(indexName)) { if (item.HasPublished()) { var published = _contentManager.Get(item.Id, VersionOptions.Published); IDocumentIndex documentIndex = ExtractDocumentIndex(published); if (documentIndex != null && documentIndex.IsDirty) { addToIndex.Add(documentIndex); } } } else if (settings.List.Contains(indexName + ":latest")) { IDocumentIndex documentIndex = ExtractDocumentIndex(item); if (documentIndex != null && documentIndex.IsDirty) { addToIndex.Add(documentIndex); } } indexSettings.LastContentId = item.VersionRecord.Id; } catch (Exception ex) { Logger.Warning(ex, "Unable to index content item #{0} during rebuild", item.Id); } } if (contentItems.Count < ContentItemsPerLoop) { loop = false; } else { _transactionManager.RequireNew(); } } while (loop); } if (indexSettings.Mode == IndexingMode.Update) { Logger.Information("Updating index"); _indexingStatus = IndexingStatus.Updating; do { var indexingTasks = _taskRepository .Table.Where(x => x.Id > indexSettings.LastIndexedId) .OrderBy(x => x.Id) .Take(ContentItemsPerLoop) .ToList() .GroupBy(x => x.ContentItemRecord.Id) .Select(group => new { TaskId = group.Max(task => task.Id), Delete = group.Last().Action == IndexingTaskRecord.Delete, Id = group.Key, ContentItem = _contentManager.Get(group.Key, VersionOptions.Latest) }) .OrderBy(x => x.TaskId) .ToArray(); foreach (var item in indexingTasks) { try { IDocumentIndex documentIndex = null; // item.ContentItem can be null if the content item has been deleted if (item.ContentItem != null) { // skip items from types which are not indexed var settings = GetTypeIndexingSettings(item.ContentItem); if (settings.List.Contains(indexName)) { if (item.ContentItem.HasPublished()) { var published = _contentManager.Get(item.Id, VersionOptions.Published); documentIndex = ExtractDocumentIndex(published); } } else if (settings.List.Contains(indexName + ":latest")) { var latest = _contentManager.Get(item.Id, VersionOptions.Latest); documentIndex = ExtractDocumentIndex(latest); } } if (documentIndex == null || item.Delete) { deleteFromIndex.Add(item.Id); } else if (documentIndex.IsDirty) { addToIndex.Add(documentIndex); } indexSettings.LastIndexedId = item.TaskId; } catch (Exception ex) { Logger.Warning(ex, "Unable to index content item #{0} during update", item.Id); } } if (indexingTasks.Length < ContentItemsPerLoop) { loop = false; } else { _transactionManager.RequireNew(); } } while (loop); } // save current state of the index indexSettings.LastIndexedUtc = _clock.UtcNow; _appDataFolder.CreateFile(settingsFilename, indexSettings.ToXml()); if (deleteFromIndex.Count == 0 && addToIndex.Count == 0) { // nothing more to do _indexingStatus = IndexingStatus.Idle; return(false); } // save new and updated documents to the index try { if (addToIndex.Count > 0) { _indexProvider.Store(indexName, addToIndex); Logger.Information("Added content items to index: {0}", addToIndex.Count); } } catch (Exception ex) { Logger.Warning(ex, "An error occured while adding a document to the index"); } // removing documents from the index try { if (deleteFromIndex.Count > 0) { _indexProvider.Delete(indexName, deleteFromIndex); Logger.Information("Added content items to index: {0}", addToIndex.Count); } } catch (Exception ex) { Logger.Warning(ex, "An error occured while removing a document from the index"); } return(true); }
public void FileExistsReturnsTrueForExistingFile() { _appDataFolder.CreateFile(String.Format("alpha{0}foo{0}bar.txt", Path.DirectorySeparatorChar)); Assert.True(_appDataFolder.GetFileInfo(String.Format("alpha{0}foo{0}bar.txt", Path.DirectorySeparatorChar)).Exists); }
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)); } }
public void CreateFileWillCauseDirectoryToBeCreated() { Assert.False(Directory.Exists(Path.Combine(_tempFolder, "alpha\\omega\\foo"))); _appDataFolder.CreateFile("alpha\\omega\\foo\\bar.txt", "quux"); Assert.True(Directory.Exists(Path.Combine(_tempFolder, "alpha\\omega\\foo"))); }