static string MakeTopicName(LocalTopicName name) { if (name.Version == null || name.Version.Length == 0) return name.Name; else return name.Name + "(" + name.Version + ")"; }
/// <summary> /// Answer whether a topic exists and is writable /// </summary> /// <param name="topic">The topic (must directly be in this content base)</param> /// <returns>true if the topic exists AND is writable by the current user; else false</returns> public abstract bool IsExistingTopicWritable(LocalTopicName topic);
/// <summary> /// Write a new version of the topic (doesn't write a new version). Generate all needed federation update changes via the supplied generator. /// </summary> /// <param name="topic">Topic to write</param> /// <param name="content">New content</param> /// <param name="sink">Object to recieve change info about the topic</param> override protected void WriteTopic(LocalTopicName topic, string content, FederationUpdateGenerator gen) { string root = Root; string fullpath = MakePath(root, topic); bool isNew = !(File.Exists(fullpath)); // Get old topic so we can analyze it for properties to compare with the new one string oldText = null; Hashtable oldProperties = null; if (!isNew) { using (StreamReader sr = new StreamReader(new FileStream(fullpath, FileMode.Open, FileAccess.Read, FileShare.Read))) { oldText = sr.ReadToEnd(); } oldProperties = ExtractExplicitFieldsFromTopicBody(oldText); } // Change it using (StreamWriter sw = new StreamWriter(fullpath)) { sw.Write(content); } // Quick check to see if we're about to let somebody write the DefinitionTopic for this ContentBase. // If so, we reset our Info object to reread string pathToDefinitionTopic = MakePath(Root, DefinitionTopicName.LocalName); if (fullpath == pathToDefinitionTopic) this.Federation.InvalidateNamespace(Namespace); // Record changes try { AbsoluteTopicName absTopic = topic.AsAbsoluteTopicName(Namespace); gen.Push(); // Record the topic-level change if (isNew) gen.RecordCreatedTopic(absTopic); else gen.RecordUpdatedTopic(absTopic); // Now process the properties Hashtable newProperties = ExtractExplicitFieldsFromTopicBody(content); if (isNew) { foreach (string pName in newProperties.Keys) gen.RecordPropertyChange(absTopic, pName, FederationUpdate.PropertyChangeType.PropertyAdd); gen.RecordPropertyChange(absTopic, "_Body", FederationUpdate.PropertyChangeType.PropertyAdd); gen.RecordPropertyChange(absTopic, "_TopicName", FederationUpdate.PropertyChangeType.PropertyAdd); gen.RecordPropertyChange(absTopic, "_TopicFullName", FederationUpdate.PropertyChangeType.PropertyAdd); gen.RecordPropertyChange(absTopic, "_LastModifiedBy", FederationUpdate.PropertyChangeType.PropertyAdd); gen.RecordPropertyChange(absTopic, "_CreationTime", FederationUpdate.PropertyChangeType.PropertyAdd); gen.RecordPropertyChange(absTopic, "_ModificationTime", FederationUpdate.PropertyChangeType.PropertyAdd); } else { if (content != oldText) { FillFederationUpdateByComparingPropertyHashes(gen, absTopic, oldProperties, newProperties); gen.RecordPropertyChange(absTopic, "_Body", FederationUpdate.PropertyChangeType.PropertyUpdate); } gen.RecordPropertyChange(absTopic, "_ModificationTime", FederationUpdate.PropertyChangeType.PropertyUpdate); } } finally { gen.Pop(); } }
/// <summary> /// All of the FileInfos for the historical versions of a given topic /// </summary> /// <param name="topic"></param> /// <returns>FileInfos</returns> FileInfo[] FileInfosForTopic(LocalTopicName topic) { FileInfo [] answer = {}; // If the topic does not exist, we ignore any historical versions (the result of a delete) if (!TipFileExists(topic.Name)) return answer; try { answer = new DirectoryInfo(Root).GetFiles(topic.Name + "(*).awiki"); } catch (DirectoryNotFoundException e) { System.Diagnostics.Debug.WriteLine(e.ToString()); } return answer; }
/// <summary> /// Answer all of the versions for a given topic /// </summary> /// <remarks> /// TODO: Change this to return TopicChanges instead of the TopicNames /// </remarks> /// <param name="topic">A topic</param> /// <returns>Enumeration of the topic names (with non-null versions in them) </returns> override public IEnumerable AllVersionsForTopic(LocalTopicName topic) { ArrayList answer = new ArrayList(); FileInfo[] infos = FileInfosForTopic(topic); ArrayList sortable = new ArrayList(); foreach (FileInfo each in infos) sortable.Add(new FileInfoTopicData(each, Namespace)); BackingTopic back = GetBackingTopicNamed(topic); if (back != null) sortable.Add(new BackingTopicTopicData(back)); sortable.Sort(new TimeSort()); foreach (TopicData each in sortable) { AbsoluteTopicName name = topic.AsAbsoluteTopicName(Namespace); name.Version = each.Version; answer.Add(name); } return answer; }
/// <summary> /// Answer a TextReader for the given topic /// </summary> /// <param name="topic"></param> /// <exception cref="TopicNotFoundException">Thrown when the topic doesn't exist</exception> /// <returns>TextReader</returns> override public TextReader TextReaderForTopic(LocalTopicName topic) { string topicFile = TopicPath(topic); if (topicFile == null || !File.Exists(topicFile)) { BackingTopic back = GetBackingTopicNamed(topic); if (back != null) return new StringReader(back.Body); throw TopicNotFoundException.ForTopic(topic, Namespace); } return new StreamReader(new FileStream(topicFile, FileMode.Open, FileAccess.Read, FileShare.Read)); }
/// <summary> /// Answer when a topic was created /// </summary> /// <param name="topic">The topic</param> /// <returns></returns> override public DateTime GetTopicCreationTime(LocalTopicName topic) { string path = TopicPath(topic); if (File.Exists(path)) return File.GetCreationTime(path); BackingTopic back = GetBackingTopicNamed(topic); if (back != null) return back.CreationTime; throw TopicNotFoundException.ForTopic(topic, Namespace); }
/// <summary> /// Answer true if a topic exists in this ContentBase /// </summary> /// <param name="name">Name of the topic</param> /// <returns>true if it exists</returns> override public bool TopicExistsLocally(LocalTopicName name) { if (BackingTopics.ContainsKey(name.Name)) return true; return File.Exists(MakePath(Root, name)); }
/// <summary> /// Delete a topic /// </summary> /// <param name="topic"></param> public abstract void DeleteTopic(LocalTopicName topic);
/// <summary> /// Answer a TextReader for the given topic /// </summary> /// <param name="topic"></param> /// <exception cref="TopicNotFoundException">Thrown when the topic doesn't exist</exception> /// <returns>TextReader</returns> public abstract TextReader TextReaderForTopic(LocalTopicName topic);
/// <summary> /// Change the value of a property (aka field) in a a topic. If the topic doesn't exist, it will be created. /// </summary> /// <param name="topic">The topic whose property is to be changed</param> /// <param name="field">The name of the property to change</param> /// <param name="rep">The new value for the field</param> public void SetFieldValue(LocalTopicName topic, string field, string rep, bool writeNewVersion) { if (!TopicExistsLocally(topic)) { WriteTopic(topic, ""); } string original = Read(topic); // Multiline values need to end a complete line string repWithLineEnd = rep; if (!repWithLineEnd.EndsWith("\n")) repWithLineEnd = repWithLineEnd + "\n"; bool newValueIsMultiline = rep.IndexOf("\n") > 0; string simpleField = "(?<name>(" + field + ")):(?<val>[^\\[].*)"; string multiLineField = "(?<name>(" + field + ")):\\[(?<val>[^\\[]*\\])"; string update = original; if (new Regex(simpleField).IsMatch(original)) { if (newValueIsMultiline) update = Regex.Replace (original, simpleField, "${name}:[ " + repWithLineEnd + "]"); else update = Regex.Replace (original, simpleField, "${name}: " + rep); } else if (new Regex(multiLineField).IsMatch(original)) { if (newValueIsMultiline) update = Regex.Replace (original, multiLineField, "${name}:[ " + repWithLineEnd + "]"); else update = Regex.Replace (original, multiLineField, "${name}: " + rep); } else { if (!update.EndsWith("\n")) update = update + "\n"; if (rep.IndexOf("\n") == -1) update += field + ": " + repWithLineEnd; else update += field + ":[ " + repWithLineEnd + "]\n"; } if (writeNewVersion) WriteTopicAndNewVersion(topic, update); else WriteTopic(topic, update); }
/// <summary> /// Reach and answer all the properties (aka fields) for the given topic. This includes both the /// properties defined in the topic plus the extra properties that every topic has (e.g., _TopicName, _TopicFullName, _LastModifiedBy, etc.) /// </summary> /// <param name="topic"></param> /// <returns>Hashtable (keys = string property names, values = values [as strings]); or null if the topic doesn't exist</returns> public Hashtable GetFieldsForTopic(LocalTopicName topic) { if (!TopicExistsLocally(topic)) return null; string allLines = Read(topic); Hashtable answer = ExtractExplicitFieldsFromTopicBody(allLines); Federation.AddImplicitPropertiesToHash(answer, topic.AsAbsoluteTopicName(Namespace), GetTopicLastAuthor(topic), GetTopicCreationTime(topic), GetTopicLastWriteTime(topic), allLines); return answer; }
/// <summary> /// Answer the contents of a given topic /// </summary> /// <param name="topic">The topic</param> /// <returns>The contents of the topic or null if it can't be read (e.g., doesn't exist)</returns> public string Read(LocalTopicName topic) { using (TextReader st = TextReaderForTopic(topic)) { if (st == null) return null; return st.ReadToEnd(); } }
/// <summary> /// Answer the identify of the author who last modified a given topic /// </summary> /// <param name="topic"></param> /// <returns>a user name</returns> public abstract string GetTopicLastAuthor(LocalTopicName topic);
/// <summary> /// Answer when a topic was created /// </summary> /// <param name="topic">The topic</param> /// <returns></returns> public abstract DateTime GetTopicCreationTime(LocalTopicName topic);
string TopicPath(LocalTopicName localTopicName) { return MakePath(Root, localTopicName); }
/// <summary> /// Answer the full file system path for a given topic in a given folder. /// </summary> /// <param name="root">File system path to the root directory for the containing content base</param> /// <param name="name">The name of the topic</param> /// <returns>Full path to the file containing the content for the most recent version of the topic</returns> static string MakePath(string root, LocalTopicName name) { if (name.Version == null || name.Version.Length == 0) return root + "\\" + name.Name + ".wiki"; else return root + "\\" + name.Name + "(" + name.Version + ").awiki"; }
/// <summary> /// Answer all of the versions for a given topic /// </summary> /// <remarks> /// TODO: Change this to return TopicChanges instead of the TopicNames /// </remarks> /// <param name="topic">A topic</param> /// <returns>Enumeration of the topic names (with non-null versions in them) </returns> public abstract IEnumerable AllVersionsForTopic(LocalTopicName topic);
/// <summary> /// Answer whether a topic exists and is writable /// </summary> /// <param name="topic">The topic (must directly be in this content base)</param> /// <returns>true if the topic exists AND is writable by the current user; else false</returns> override public bool IsExistingTopicWritable(LocalTopicName topic) { string path = TopicPath(topic); if (!File.Exists(path)) { BackingTopic back = GetBackingTopicNamed(topic); if (back == null) return false; return back.CanOverride; } DateTime old = File.GetLastWriteTimeUtc(path); try { // Hacky implementation, but there's no better with the framework to do this that just to try and see what happens!!! FileStream stream = File.OpenWrite(path); stream.Close(); } catch (UnauthorizedAccessException unauth) { unauth.ToString(); return false; } File.SetLastWriteTimeUtc(path, old); return true; }
/// <summary> /// Returns the most recent version for the given topic /// </summary> /// <param name="topic">The topic</param> /// <returns>The most recent version string for the topic</returns> public abstract string LatestVersionForTopic(LocalTopicName topic);
/// <summary> /// Answer the identify of the author who last modified a given topic /// </summary> /// <param name="topic"></param> /// <returns>a user name</returns> override public string GetTopicLastAuthor(LocalTopicName topic) { FileInfo[] infos = FileInfosForTopic(topic); if (infos.Length == 0) { BackingTopic back = GetBackingTopicNamed(topic); if (back != null) return back.LastAuthor; return AnonymousUserName; } ArrayList all = new ArrayList(); foreach (FileInfo each in infos) all.Add(new FileInfoTopicData(each, Namespace)); all.Sort(new TimeSort()); TopicData info = (TopicData)(all[0]); string auth = info.Author; if (auth == null) return AnonymousUserName; return auth; }
/// <summary> /// A list of TopicChanges to a topic since a given date [sorted by date] /// </summary> /// <param name="topic">A given date</param> /// <param name="stamp">A non-null timestamp; changes before this time won't be included in the answer </param> /// <param name="rule">A composite cache rule to fill with rules that represented accumulated dependencies (or null)</param> /// <returns>Enumeration of TopicChanges</returns> public abstract IEnumerable AllChangesForTopicSince(LocalTopicName topic, DateTime stamp, CompositeCacheRule rule);
/// <summary> /// Delete a topic /// </summary> /// <param name="topic"></param> override public void DeleteTopic(LocalTopicName topic) { string path = TopicPath(topic); if (!File.Exists(path)) return; // Delete the sucker! File.Delete(path); // Fire the event FederationUpdate update = new FederationUpdate(); update.RecordDeletedTopic(topic.AsAbsoluteTopicName(Namespace)); OnFederationUpdated(new FederationUpdateEventArgs(update)); }
/// <summary> /// A list of TopicChanges to a topic since a given date [sorted by date] /// </summary> /// <param name="topic">A given date</param> /// <param name="stamp">A non-null timestamp; changes before this time won't be included in the answer </param> /// <returns>Enumeration of TopicChanges</returns> public IEnumerable AllChangesForTopicSince(LocalTopicName topic, DateTime stamp) { return AllChangesForTopicSince(topic, stamp, null); }
/// <summary> /// Returns the most recent version for the given topic /// </summary> /// <param name="topic">The topic</param> /// <returns>The most recent version string for the topic</returns> override public string LatestVersionForTopic(LocalTopicName topic) { ArrayList sortable = new ArrayList(); FileInfo [] infos = FileInfosForTopic(topic); foreach (FileInfo each in infos) sortable.Add(new FileInfoTopicData(each, Namespace)); BackingTopic back = GetBackingTopicNamed(topic); if (back != null) sortable.Add(new BackingTopicTopicData(back)); if (sortable.Count == 0) return null; sortable.Sort(new TimeSort()); return ((TopicData)(sortable[0])).Version; }
/// <summary> /// A list of TopicChanges to a topic (sorted by date) /// </summary> /// <param name="topic">The topic</param> /// <returns>Enumeration of TopicChanges </returns> public IEnumerable AllChangesForTopic(LocalTopicName topic) { return AllChangesForTopicSince(topic, DateTime.MinValue, null); }
/// <summary> /// A list of TopicChanges to a topic since a given date [sorted by date] /// </summary> /// <param name="topic">A given date</param> /// <param name="stamp">A non-null timestamp; changes before this time won't be included in the answer </param> /// <returns>Enumeration of TopicChanges</returns> override public IEnumerable AllChangesForTopicSince(LocalTopicName topic, DateTime stamp, CompositeCacheRule rule) { ArrayList answer = new ArrayList(); FileInfo[] infos = FileInfosForTopic(topic); ArrayList sortable = new ArrayList(); foreach (FileInfo each in infos) sortable.Add(new FileInfoTopicData(each, Namespace)); BackingTopic back = GetBackingTopicNamed(topic); if (back != null) sortable.Add(new BackingTopicTopicData(back)); sortable.Sort(new TimeSort()); TopicsCacheRule tcr = null; if (rule != null) { tcr = new TopicsCacheRule(Federation); tcr.AddTopic(topic.AsAbsoluteTopicName(Namespace)); rule.Add(tcr); } foreach (TopicData each in sortable) { if (each.LastModificationTime < stamp) continue; AbsoluteTopicName name = topic.AsAbsoluteTopicName(Namespace); name.Version = each.Version; TopicChange change = TopicChangeFromName(name); answer.Add(change); if (tcr != null) tcr.AddTopic(name.AsAbsoluteTopicName(Namespace)); } return answer; }
public IEnumerable AllChangesForTopic(LocalTopicName topic, CompositeCacheRule rule) { return AllChangesForTopicSince(topic, DateTime.MinValue, rule); }
/// <summary> /// Rename the given topic. If requested, find references and fix them up. Answer a report of what was fixed up. Throw a DuplicationTopicException /// if the new name is the name of a topic that already exists. /// </summary> /// <param name="oldName">Old topic name</param> /// <param name="newName">The new name</param> /// <param name="fixup">true to fixup referenced topic *in this namespace*; false to do no fixups</param> /// <returns>ArrayList of strings that can be reported back to the user of what happened during the fixup process</returns> override public ArrayList RenameTopic(LocalTopicName oldName, string newName, bool fixup) { FederationUpdateGenerator gen = CreateFederationUpdateGenerator(); // TRIGGER ArrayList answer = new ArrayList(); string root = Root; string pathToTopicFile = root + "\\" + oldName.Name + ".wiki"; string pathToArchiveFolder = root + "\\archive\\" + oldName.Name; string newNameForTopicFile = root + "\\" + newName + ".wiki"; string newNameForArchiveFolder = root + "\\archive\\" + newName; AbsoluteTopicName newFullName = new AbsoluteTopicName(newName, Namespace); // Make sure it's not goign to overwrite an existing topic if (TopicExistsLocally(newName)) { throw DuplicateTopicException.ForTopic(newFullName); } // If the topic does not exist (e.g., it's a backing topic), don't bother... if (!TipFileExists(oldName.Name)) { answer.Add("This topic can not be renamed (it is probably a backing topic)."); return answer; } try { gen.Push(); // Rename the archive files, too foreach (FileInfo each in FileInfosForTopic(oldName)) { AbsoluteTopicName newNameForThisVersion = new AbsoluteTopicName(newName, Namespace); newNameForThisVersion.Version = ExtractVersionFromHistoricalFilename(each.Name); AbsoluteTopicName oldNameForThisVersion = new AbsoluteTopicName(oldName.Name, Namespace); oldNameForThisVersion.Version = newNameForThisVersion.Version; string newFilename = MakePath(root, newNameForThisVersion.LocalName); File.Move(each.FullName, newFilename); // record changes (a delete for the old one and an add for the new one) gen.RecordCreatedTopic(newNameForThisVersion); gen.RecordDeletedTopic(oldNameForThisVersion); } // Rename the topic file File.Move(pathToTopicFile, newNameForTopicFile); // Record changes (a delete for the old one and an add for the new one) gen.RecordCreatedTopic(newFullName); gen.RecordDeletedTopic(oldName.AsAbsoluteTopicName(Namespace)); // Now get ready to do fixups if (!fixup) return answer; // OK, we need to do the hard work AbsoluteTopicName oldabs = oldName.AsAbsoluteTopicName(Namespace); AbsoluteTopicName newabs = new AbsoluteTopicName(newName, oldabs.Namespace); // Now the master loop foreach (AbsoluteTopicName topic in AllTopics(false)) if (RenameTopicReferences(topic.LocalName, oldabs, newabs, gen)) answer.Add("Found and replaced references in " + topic); } finally { gen.Pop(); } return answer; }
/// <summary> /// Find the version of a topic immediately previous to another version /// </summary> /// <param name="topic">The name (with version) of the topic for which you want the change previous to</param> /// <returns>TopicChange or null if none</returns> public AbsoluteTopicName VersionPreviousTo(LocalTopicName topic) { bool next = false; bool first = true; AbsoluteTopicName answer = topic.AsAbsoluteTopicName(Namespace); foreach (TopicChange ver in AllChangesForTopic(topic)) { answer.Version = ver.Version; if (next) return answer; if (topic.Version == null && !first) // The version prior to the most recent is the second in line return answer; if (ver.Version == topic.Version) next = true; first = false; } return null; }