Rewrote references

This commit is contained in:
2014-08-31 14:41:24 +03:00
parent 520eed12a6
commit 10aa72176e
24 changed files with 1182 additions and 475 deletions

View File

@ -4,6 +4,8 @@ using System.Linq;
using System.Text;
using System.Xml.Serialization;
using RainmeterStudio.Core.Storage;
using RainmeterStudio.Core.Utils;
using Version = RainmeterStudio.Core.Utils.Version;
namespace RainmeterStudio.Core.Model
{
@ -12,85 +14,114 @@ namespace RainmeterStudio.Core.Model
/// </summary>
public class Project
{
#region Private fields
private string _name;
private string _path;
#endregion
#region Properties
/// <summary>
/// Gets or sets the name of the project
/// </summary>
[XmlElement(ElementName = "name", Order = 1)]
public string Name
{
get
{
return _name;
return Root.Name;
}
set
{
_name = value;
if (Root != null)
Root.Data = new Reference(Name, Path);
Root.Name = value;
}
}
/// <summary>
/// Gets or sets the file path of this project
/// </summary>
[XmlIgnore]
public string Path
{
get
{
return _path;
return Root.StoragePath;
}
set
{
_path = value;
if (Root != null)
Root.Data = new Reference(Name, Path);
Root.StoragePath = value;
}
}
/// <summary>
/// Gets or sets the author of the project
/// </summary>
[XmlElement(ElementName = "author", Order = 2)]
public string Author { get; set; }
/// <summary>
/// Gets or sets the version of the project
/// </summary>
[XmlElement(ElementName = "version", Order = 3)]
public Version Version { get; set; }
/// <summary>
/// Gets or sets the reference to the file to automatically load at package installation
/// Gets or sets the reference to the file that automatically loads at package installation
/// </summary>
[XmlIgnore]
public Reference AutoLoadFile { get; set; }
/// <summary>
/// Gets or sets the qualified name of the auto load file
/// </summary>
[XmlElement(ElementName = "autoLoadFile", Order = 7)]
public string AutoLoadFileQualifiedName
{
get
{
return ((AutoLoadFile == null) ? null : AutoLoadFile.QualifiedName);
}
set
{
AutoLoadFile = Root.GetReference(value);
}
}
/// <summary>
/// Gets or sets the list of variable files
/// </summary>
[XmlIgnore]
public List<Reference> VariableFiles { get; set; }
/// <summary>
/// Gets or sets the list of variable files qualified names
/// </summary>
[XmlArray(ElementName = "variableFiles", Order = 8)]
public string[] VariableFilesQualifiedNames
{
get
{
return VariableFiles.Select(x => x.QualifiedName).ToArray();
}
set
{
VariableFiles.Clear();
VariableFiles.AddRange(value.Select(x => Root.GetReference(x)));
}
}
/// <summary>
/// Gets or sets the minimum rainmeter version
/// </summary>
[XmlElement(ElementName = "minimumRainmeter", Order = 4)]
public Version MinimumRainmeter { get; set; }
/// <summary>
/// Gets or sets the minimum Windows version
/// </summary>
[XmlElement(ElementName = "minimumWindows", Order = 5)]
public Version MinimumWindows { get; set; }
/// <summary>
/// Gets or sets the root node
/// </summary>
public Tree<Reference> Root { get; set; }
[XmlElement(ElementName = "root", Order = 6)]
public Reference Root { get; set; }
#endregion
@ -101,7 +132,7 @@ namespace RainmeterStudio.Core.Model
/// </summary>
public Project()
{
Root = new Tree<Reference>();
Root = new Reference(String.Empty);
VariableFiles = new List<Reference>();
Version = new Version();
MinimumRainmeter = new Version("3.1");
@ -109,5 +140,20 @@ namespace RainmeterStudio.Core.Model
}
#endregion
#region Operations
/// <summary>
/// Looks for reference in project
/// </summary>
/// <param name="reference">Reference</param>
/// <returns>True if reference was found</returns>
public bool Contains(Reference reference)
{
return Root.TreeContains(reference);
}
#endregion
}
}

View File

@ -1,90 +1,153 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using RainmeterStudio.Core.Utils;
namespace RainmeterStudio.Core.Model
{
/// <summary>
/// Reference to a file or folder
/// </summary>
[DebuggerDisplay("ProjectPath = {ProjectPath}, StoragePath = {StoragePath}")]
public struct Reference
[DebuggerDisplay("QualifiedName = {QualifiedName}, StoragePath = {StoragePath}")]
public class Reference : INotifyCollectionChanged
{
private string[] _projectPath;
private string _storagePath;
private Dictionary<string, Reference> _children;
#region Properties
/// <summary>
/// Gets or sets the parent of this reference
/// </summary>
[XmlIgnore]
public Reference Parent { get; set; }
/// <summary>
/// Gets the children references
/// </summary>
[XmlIgnore]
public ReadOnlyDictionary<string, Reference> ChildrenDictionary
{
get
{
return new ReadOnlyDictionary<string,Reference>(_children);
}
}
/// <summary>
/// Gets or sets children
/// </summary>
[XmlArray("children")]
public Reference[] Children
{
get
{
return _children.Values.ToArray();
}
set
{
Clear();
value.ForEach(Add);
}
}
/// <summary>
/// Gets the name of the reference
/// </summary>
[XmlAttribute("name")]
public string Name
{
get; set;
}
/// <summary>
/// Gets the full qualified name of this reference
/// </summary>
[XmlIgnore]
public string QualifiedName
{
get
{
// Try to get the last item from the project path
if (_projectPath != null && _projectPath.Length > 0)
return _projectPath[_projectPath.Length - 1];
if (Parent == null)
{
// Return name
return Name;
}
else
{
// If it has a parent, get the parent's name
return Parent.QualifiedName + '/' + Name;
}
}
}
// None found, return null
return null;
/// <summary>
/// Gets the parts of the full qualified name of this reference
/// </summary>
[XmlIgnore]
public IEnumerable<string> QualifiedParts
{
get
{
if (Parent == null)
{
return Enumerable.Repeat(Name, 1);
}
else
{
return Parent.QualifiedParts.Append(Name);
}
}
}
/// <summary>
/// Gets the path to the file on the disk. If reference is in a project, the path should be relative.
/// </summary>
[XmlAttribute("storagePath")]
public string StoragePath
{
get
{
return _storagePath;
}
get;
set;
}
#endregion
#region Constructors
/// <summary>
/// Initializes the reference
/// </summary>
public Reference()
: this(null, null)
{
}
/// <summary>
/// Gets the qualified path
/// Initializes the reference
/// </summary>
public string ProjectPath
/// <param name="projectPath">Project path to item referenced</param>
public Reference(string name)
: this(name, null)
{
get
{
if (_projectPath != null)
{
return _projectPath.Aggregate(String.Empty, (a, b) => a + "/" + b);
}
return null;
}
}
/// <summary>
/// Initializes the reference
/// </summary>
/// <param name="name">Name of reference</param>
/// <param name="path">Path to item referenced</param>
public Reference(string filePath, string projectPath = null)
/// <param name="storagePath">Path to item on disk</param>
public Reference(string name, string storagePath)
{
_storagePath = filePath;
if (projectPath != null)
{
_projectPath = projectPath.Split('/').Skip(1).ToArray();
}
else
{
_projectPath = null;
}
StoragePath = storagePath;
Name = name;
_children = new Dictionary<string, Reference>();
}
/// <summary>
/// Checks if the reference points to a project item
/// </summary>
public bool IsInProject()
{
return (_projectPath != null);
}
#endregion
/// <summary>
/// Checks if the reference has a file on disk
@ -92,7 +155,90 @@ namespace RainmeterStudio.Core.Model
/// <returns></returns>
public bool IsOnStorage()
{
return (_storagePath != null);
return (StoragePath != null);
}
/// <summary>
/// Adds a child reference
/// </summary>
/// <param name="reference"></param>
public void Add(Reference reference)
{
// Make sure object is not parented yet
if (reference.Parent != null)
throw new ArgumentException("Reference must be removed from its current parent first.");
// Add and parent
reference.Parent = this;
_children.Add(reference.Name, reference);
// Trigger event
if (CollectionChanged != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, reference));
}
/// <summary>
/// Removes a reference
/// </summary>
/// <param name="reference">Reference to remove</param>
/// <returns>True if removed successfully</returns>
public bool Remove(Reference reference)
{
// Make sure we are the parent
if (reference.Parent != this)
return false;
// Remove
reference.Parent = null;
bool res = _children.Remove(reference.Name);
// Trigger event
if (CollectionChanged != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, reference));
return res;
}
/// <summary>
/// Removes this reference from its parent
/// </summary>
/// <returns>True if unparented successfully</returns>
public bool Unparent()
{
if (Parent != null)
return Parent.Remove(this);
return false;
}
/// <summary>
/// Removes all children
/// </summary>
public void Clear()
{
// Unparent
foreach (var pair in _children)
{
pair.Value.Parent = null;
}
// Clear
_children.Clear();
// Trigger event
if (CollectionChanged != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
/// <summary>
/// Gets the number of children
/// </summary>
public int Count
{
get
{
return _children.Count;
}
}
/// <summary>
@ -105,14 +251,7 @@ namespace RainmeterStudio.Core.Model
if (obj is Reference)
{
Reference other = (Reference)obj;
// 2 references are equal if they point to the same project item
if (_projectPath != null && other._projectPath != null)
return _projectPath.SequenceEqual(other._projectPath);
// If there is no project item, compare storage paths
if (_projectPath == null && other._projectPath == null)
return String.Equals(_storagePath, other._storagePath);
return (String.Equals(QualifiedName, other.QualifiedName));
}
return false;
@ -124,28 +263,114 @@ namespace RainmeterStudio.Core.Model
/// <returns>Hash code</returns>
public override int GetHashCode()
{
int hash = (_projectPath == null) ? 0 : _projectPath.GetHashCode();
if (_projectPath != null)
{
foreach (var item in _projectPath)
hash = hash * 7 + item.GetHashCode();
}
else
{
hash = hash * 2113 + ((_storagePath == null) ? 0 : _storagePath.GetHashCode());
}
return hash;
return QualifiedName.GetHashCode();
}
/// <summary>
/// Gets the string representation of this reference
/// </summary>
/// <returns></returns>
/// <returns>String representation</returns>
public override string ToString()
{
return ProjectPath;
return QualifiedName;
}
/// <summary>
/// Triggered when children are added or removed.
/// </summary>
public event NotifyCollectionChangedEventHandler CollectionChanged;
}
/// <summary>
/// Provides useful methods for references
/// </summary>
public static class ReferenceExtensions
{
/// <summary>
/// Tries to get a reference from the same tree having specified qualified name
/// </summary>
/// <param name="this">Reference contained in the tree</param>
/// <param name="qualifiedName">Full qualified name</param>
/// <param name="output">Found reference</param>
/// <returns>True if succeeded to find the reference</returns>
public static bool TryGetReference(this Reference @this, string qualifiedName, out Reference output)
{
var thisQualifiedName = @this.QualifiedName;
// Am I the reference? return myself
if (qualifiedName.Equals(thisQualifiedName))
{
output = @this;
return true;
}
// Qualified name is a child, look child up
else if (qualifiedName.StartsWith(thisQualifiedName))
{
int startIndex = thisQualifiedName.Length + 1;
int endIndex = qualifiedName.IndexOf('/', startIndex);
string child;
Reference childRef;
if (endIndex < 0)
{
child = qualifiedName.Substring(startIndex);
}
else
{
child = qualifiedName.Substring(startIndex, endIndex - startIndex);
}
// Try to get child
if (@this.ChildrenDictionary.TryGetValue(child, out childRef))
{
return childRef.TryGetReference(qualifiedName, out output);
}
}
// Qualified name is not a child and not 'this', so ask parent to find it
else if (@this.Parent != null)
{
return @this.Parent.TryGetReference(qualifiedName, out output);
}
// Failed to find child
output = null;
return false;
}
/// <summary>
/// Gets a reference from the same tree having specified qualified name
/// </summary>
/// <param name="this">Reference contained in the tree</param>
/// <param name="qualifiedName">Full qualified name</param>
/// <returns>Found reference</returns>
/// <exception cref="ArgumentException">If qualified name not found</exception>
public static Reference GetReference(this Reference @this, string qualifiedName)
{
Reference res;
if (TryGetReference(@this, qualifiedName, out res))
{
return res;
}
else
{
throw new ArgumentException("Could not find reference.");
}
}
/// <summary>
/// Checks if a reference is in the same tree as this
/// </summary>
/// <param name="this">Reference that is in the tree we search in</param>
/// <param name="other">Reference to search</param>
/// <returns>True if the tree contains the reference.</returns>
public static bool TreeContains(this Reference @this, Reference reference)
{
Reference temp;
return TryGetReference(@this, reference.QualifiedName, out temp);
}
}
}

View File

@ -153,5 +153,10 @@ namespace RainmeterStudio.Core.Model
{
return Children.GetEnumerator();
}
public void TreeExpand(bool p)
{
throw new NotImplementedException();
}
}
}