From: Panagiotis Kanavos Date: Sun, 2 Oct 2011 20:52:29 +0000 (+0300) Subject: Added treehash calculation X-Git-Url: https://code.grnet.gr/git/pithos-ms-client/commitdiff_plain/1caef52ee05a66de6404903b1d1191afee6a4704 Added treehash calculation --- diff --git a/trunk/Pithos.Client.WPF/NativeMethods.cs b/trunk/Pithos.Client.WPF/NativeMethods.cs new file mode 100644 index 0000000..4b50856 --- /dev/null +++ b/trunk/Pithos.Client.WPF/NativeMethods.cs @@ -0,0 +1,278 @@ +using System; +using System.Runtime.InteropServices; + +namespace Pithos.Client.WPF +{ + #region Enums & Structs + + #region enum HChangeNotifyEventID + /// + /// Describes the event that has occurred. + /// Typically, only one event is specified at a time. + /// If more than one event is specified, the values contained + /// in the dwItem1 and dwItem2 + /// parameters must be the same, respectively, for all specified events. + /// This parameter can be one or more of the following values. + /// + /// + /// Windows NT/2000/XP: dwItem2 contains the index + /// in the system image list that has changed. + /// dwItem1 is not used and should be . + /// Windows 95/98: dwItem1 contains the index + /// in the system image list that has changed. + /// dwItem2 is not used and should be . + /// + [Flags] + enum HChangeNotifyEventID + { + /// + /// All events have occurred. + /// + SHCNE_ALLEVENTS = 0x7FFFFFFF, + + /// + /// A file type association has changed. + /// must be specified in the uFlags parameter. + /// dwItem1 and dwItem2 are not used and must be . + /// + SHCNE_ASSOCCHANGED = 0x08000000, + + /// + /// The attributes of an item or folder have changed. + /// or + /// must be specified in uFlags. + /// dwItem1 contains the item or folder that has changed. + /// dwItem2 is not used and should be . + /// + SHCNE_ATTRIBUTES = 0x00000800, + + /// + /// A nonfolder item has been created. + /// or + /// must be specified in uFlags. + /// dwItem1 contains the item that was created. + /// dwItem2 is not used and should be . + /// + SHCNE_CREATE = 0x00000002, + + /// + /// A nonfolder item has been deleted. + /// or + /// must be specified in uFlags. + /// dwItem1 contains the item that was deleted. + /// dwItem2 is not used and should be . + /// + SHCNE_DELETE = 0x00000004, + + /// + /// A drive has been added. + /// or + /// must be specified in uFlags. + /// dwItem1 contains the root of the drive that was added. + /// dwItem2 is not used and should be . + /// + SHCNE_DRIVEADD = 0x00000100, + + /// + /// A drive has been added and the Shell should create a new window for the drive. + /// or + /// must be specified in uFlags. + /// dwItem1 contains the root of the drive that was added. + /// dwItem2 is not used and should be . + /// + SHCNE_DRIVEADDGUI = 0x00010000, + + /// + /// A drive has been removed. or + /// must be specified in uFlags. + /// dwItem1 contains the root of the drive that was removed. + /// dwItem2 is not used and should be . + /// + SHCNE_DRIVEREMOVED = 0x00000080, + + /// + /// Not currently used. + /// + SHCNE_EXTENDED_EVENT = 0x04000000, + + /// + /// The amount of free space on a drive has changed. + /// or + /// must be specified in uFlags. + /// dwItem1 contains the root of the drive on which the free space changed. + /// dwItem2 is not used and should be . + /// + SHCNE_FREESPACE = 0x00040000, + + /// + /// Storage media has been inserted into a drive. + /// or + /// must be specified in uFlags. + /// dwItem1 contains the root of the drive that contains the new media. + /// dwItem2 is not used and should be . + /// + SHCNE_MEDIAINSERTED = 0x00000020, + + /// + /// Storage media has been removed from a drive. + /// or + /// must be specified in uFlags. + /// dwItem1 contains the root of the drive from which the media was removed. + /// dwItem2 is not used and should be . + /// + SHCNE_MEDIAREMOVED = 0x00000040, + + /// + /// A folder has been created. + /// or must be specified in uFlags. + /// dwItem1 contains the folder that was created. + /// dwItem2 is not used and should be . + /// + SHCNE_MKDIR = 0x00000008, + + /// + /// A folder on the local computer is being shared via the network. + /// or + /// must be specified in uFlags. + /// dwItem1 contains the folder that is being shared. + /// dwItem2 is not used and should be . + /// + SHCNE_NETSHARE = 0x00000200, + + /// + /// A folder on the local computer is no longer being shared via the network. + /// or + /// must be specified in uFlags. + /// dwItem1 contains the folder that is no longer being shared. + /// dwItem2 is not used and should be . + /// + SHCNE_NETUNSHARE = 0x00000400, + + /// + /// The name of a folder has changed. + /// or + /// must be specified in uFlags. + /// dwItem1 contains the previous pointer to an item identifier list (PIDL) or name of the folder. + /// dwItem2 contains the new PIDL or name of the folder. + /// + SHCNE_RENAMEFOLDER = 0x00020000, + + /// + /// The name of a nonfolder item has changed. + /// or + /// must be specified in uFlags. + /// dwItem1 contains the previous PIDL or name of the item. + /// dwItem2 contains the new PIDL or name of the item. + /// + SHCNE_RENAMEITEM = 0x00000001, + + /// + /// A folder has been removed. + /// or + /// must be specified in uFlags. + /// dwItem1 contains the folder that was removed. + /// dwItem2 is not used and should be . + /// + SHCNE_RMDIR = 0x00000010, + + /// + /// The computer has disconnected from a server. + /// or + /// must be specified in uFlags. + /// dwItem1 contains the server from which the computer was disconnected. + /// dwItem2 is not used and should be . + /// + SHCNE_SERVERDISCONNECT = 0x00004000, + + /// + /// The contents of an existing folder have changed, + /// but the folder still exists and has not been renamed. + /// or + /// must be specified in uFlags. + /// dwItem1 contains the folder that has changed. + /// dwItem2 is not used and should be . + /// If a folder has been created, deleted, or renamed, use SHCNE_MKDIR, SHCNE_RMDIR, or + /// SHCNE_RENAMEFOLDER, respectively, instead. + /// + SHCNE_UPDATEDIR = 0x00001000, + + SHCNE_UPDATEITEM = 0x00002000, + + /// + /// An image in the system image list has changed. + /// must be specified in uFlags. + /// + SHCNE_UPDATEIMAGE = 0x00008000, + + } + #endregion // enum HChangeNotifyEventID + + #region public enum HChangeNotifyFlags + /// + /// Flags that indicate the meaning of the dwItem1 and dwItem2 parameters. + /// The uFlags parameter must be one of the following values. + /// + [Flags] + public enum HChangeNotifyFlags + { + /// + /// The dwItem1 and dwItem2 parameters are DWORD values. + /// + SHCNF_DWORD = 0x0003, + /// + /// dwItem1 and dwItem2 are the addresses of ITEMIDLIST structures that + /// represent the item(s) affected by the change. + /// Each ITEMIDLIST must be relative to the desktop folder. + /// + SHCNF_IDLIST = 0x0000, + /// + /// dwItem1 and dwItem2 are the addresses of null-terminated strings of + /// maximum length MAX_PATH that contain the full path names + /// of the items affected by the change. + /// + SHCNF_PATHA = 0x0001, + /// + /// dwItem1 and dwItem2 are the addresses of null-terminated strings of + /// maximum length MAX_PATH that contain the full path names + /// of the items affected by the change. + /// + SHCNF_PATHW = 0x0005, + /// + /// dwItem1 and dwItem2 are the addresses of null-terminated strings that + /// represent the friendly names of the printer(s) affected by the change. + /// + SHCNF_PRINTERA = 0x0002, + /// + /// dwItem1 and dwItem2 are the addresses of null-terminated strings that + /// represent the friendly names of the printer(s) affected by the change. + /// + SHCNF_PRINTERW = 0x0006, + /// + /// The function should not return until the notification + /// has been delivered to all affected components. + /// As this flag modifies other data-type flags, it cannot by used by itself. + /// + SHCNF_FLUSH = 0x1000, + /// + /// The function should begin delivering notifications to all affected components + /// but should return as soon as the notification process has begun. + /// As this flag modifies other data-type flags, it cannot by used by itself. + /// + SHCNF_FLUSHNOWAIT = 0x2000 + } + #endregion // enum HChangeNotifyFlags + + + #endregion + + + internal static class NativeMethods + { + [DllImport("shell32.dll")] + public static extern void SHChangeNotify(HChangeNotifyEventID wEventId, + HChangeNotifyFlags uFlags, + IntPtr dwItem1, + IntPtr dwItem2); + + } +} diff --git a/trunk/Pithos.Core/Agents/CloudAction.cs b/trunk/Pithos.Core/Agents/CloudAction.cs new file mode 100644 index 0000000..05173c8 --- /dev/null +++ b/trunk/Pithos.Core/Agents/CloudAction.cs @@ -0,0 +1,53 @@ +using System; +using System.IO; +using System.Threading; +using Pithos.Interfaces; +using Pithos.Network; + +namespace Pithos.Core.Agents +{ + public enum CloudActionType + { + MustSynch, + UploadUnconditional, + DownloadUnconditional, + DeleteLocal, + DeleteCloud, + RenameCloud + } + + public class CloudAction + { + public CloudActionType Action { get; set; } + public FileInfo LocalFile { get; set; } + public ObjectInfo CloudFile { get; set; } + + public Lazy LocalHash { get; private set; } + + public string OldFileName { get; set; } + public string OldPath { get; set; } + public string NewFileName { get; set; } + public string NewPath { get; set; } + + public CloudAction(CloudActionType action, string oldPath, string oldFileName, string newFileName, string newPath) + { + Action = action; + OldFileName = oldFileName; + OldPath = oldPath; + NewFileName = newFileName; + NewPath = newPath; + LocalHash = new Lazy(() => Signature.CalculateMD5(NewFileName), LazyThreadSafetyMode.ExecutionAndPublication); + } + + public CloudAction(CloudActionType action, FileInfo localFile, ObjectInfo cloudFile) + { + Action = action; + LocalFile = localFile; + CloudFile = cloudFile; + //Skip Hash calculation for folders + if (LocalFile != null) + LocalHash = new Lazy(() => Signature.CalculateMD5(LocalFile.FullName), LazyThreadSafetyMode.ExecutionAndPublication); + } + + } +} \ No newline at end of file diff --git a/trunk/Pithos.Core/Agents/FileWatcherAgent.cs b/trunk/Pithos.Core/Agents/FileAgent.cs similarity index 65% rename from trunk/Pithos.Core/Agents/FileWatcherAgent.cs rename to trunk/Pithos.Core/Agents/FileAgent.cs index 924f0e6..4a8a5a7 100644 --- a/trunk/Pithos.Core/Agents/FileWatcherAgent.cs +++ b/trunk/Pithos.Core/Agents/FileAgent.cs @@ -6,11 +6,12 @@ using System.IO; using System.Linq; using System.Text; using Pithos.Interfaces; +using Pithos.Network; namespace Pithos.Core.Agents { [Export] - public class FileWatcherAgent + public class FileAgent { Agent _agent; private FileSystemWatcher _watcher; @@ -22,9 +23,12 @@ namespace Pithos.Core.Agents [Import] public WorkflowAgent WorkflowAgent { get; set; } - public void Start(string path) + public string RootPath { get; private set; } + + public void Start(string rootPath) { - _watcher = new FileSystemWatcher(path); + RootPath = rootPath; + _watcher = new FileSystemWatcher(rootPath); _watcher.Changed += OnFileEvent; _watcher.Created += OnFileEvent; _watcher.Deleted += OnFileEvent; @@ -72,6 +76,8 @@ namespace Pithos.Core.Agents } } + public string FragmentsPath { get; set; } + public void Post(WorkflowState workflowState) { _agent.Post(workflowState); @@ -92,18 +98,68 @@ namespace Pithos.Core.Agents _agent.Stop(); } + // Enumerate all files in the Pithos directory except those in the Fragment folder + // and files with a .ignore extension + public IEnumerable EnumerateFiles(string searchPattern="*") + { + var monitoredFiles = from filePath in Directory.EnumerateFileSystemEntries(RootPath, searchPattern, SearchOption.AllDirectories) + where !Ignore(filePath) + select filePath; + return monitoredFiles; + } + + public IEnumerable EnumerateFileInfos(string searchPattern="*") + { + var rootDir = new DirectoryInfo(RootPath); + var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories) + where !Ignore(file.FullName) + select file; + return monitoredFiles; + } + + public IEnumerable EnumerateFilesAsRelativeUrls(string searchPattern="*") + { + var rootDir = new DirectoryInfo(RootPath); + var monitoredFiles = from file in rootDir.EnumerateFiles(searchPattern, SearchOption.AllDirectories) + where !Ignore(file.FullName) + select file.AsRelativeUrlTo(RootPath); + return monitoredFiles; + } + + + + + private bool Ignore(string filePath) + { + if (filePath.StartsWith(FragmentsPath)) + return true; + return false; + } + + //Post a Change message for all events except rename void OnFileEvent(object sender, FileSystemEventArgs e) { - _agent.Post(new WorkflowState { Path = e.FullPath, FileName = e.Name, TriggeringChange = e.ChangeType }); + //Ignore events that affect the Fragments folder + var filePath = e.FullPath; + if (Ignore(filePath)) + return; + _agent.Post(new WorkflowState { Path = filePath, FileName = e.Name, TriggeringChange = e.ChangeType }); } + + //Post a Change message for renames containing the old and new names void OnRenameEvent(object sender, RenamedEventArgs e) { + var oldFullPath = e.OldFullPath; + var fullPath = e.FullPath; + if (Ignore(oldFullPath) || Ignore(fullPath)) + return; + _agent.Post(new WorkflowState { - OldPath = e.OldFullPath, + OldPath = oldFullPath, OldFileName = e.OldName, - Path = e.FullPath, + Path = fullPath, FileName = e.Name, TriggeringChange = e.ChangeType }); @@ -112,10 +168,12 @@ namespace Pithos.Core.Agents private void Process(WorkflowState state) { - var networkState = StatusKeeper.GetNetworkState(state.Path); + Debug.Assert(!Ignore(state.Path)); + + var networkState = NetworkGate.GetNetworkState(state.Path); //Skip if the file is already being downloaded or uploaded and //the change is create or modify - if (networkState != NetworkState.None && + if (networkState != NetworkOperation.None && ( state.TriggeringChange == WatcherChangeTypes.Created || state.TriggeringChange == WatcherChangeTypes.Changed @@ -137,6 +195,9 @@ namespace Pithos.Core.Agents private WorkflowState UpdateFileStatus(WorkflowState state) { + Debug.Assert(!state.Path.Contains("fragments")); + Debug.Assert(!state.Path.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase)); + string path = state.Path; FileStatus status = _statusDict[state.TriggeringChange]; var oldStatus = Workflow.StatusKeeper.GetFileStatus(path); @@ -177,9 +238,9 @@ namespace Pithos.Core.Agents } if (state.Status == FileStatus.Deleted) - Workflow.RaiseChangeNotification(Path.GetDirectoryName(state.Path)); + NativeMethods.RaiseChangeNotification(Path.GetDirectoryName(state.Path)); else - Workflow.RaiseChangeNotification(state.Path); + NativeMethods.RaiseChangeNotification(state.Path); return state; } @@ -192,7 +253,11 @@ namespace Pithos.Core.Agents if (state.Status == FileStatus.Deleted) return state; - string path = state.Path; + var path = state.Path; + //Skip calculation for folders + if (Directory.Exists(path)) + return state; + string hash = Signature.CalculateMD5(path); StatusKeeper.UpdateFileChecksum(path, hash); diff --git a/trunk/Pithos.Core/Agents/FileInfoExtensions.cs b/trunk/Pithos.Core/Agents/FileInfoExtensions.cs new file mode 100644 index 0000000..26c2482 --- /dev/null +++ b/trunk/Pithos.Core/Agents/FileInfoExtensions.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using System.Text.RegularExpressions; + +namespace Pithos.Core.Agents +{ + static class FileInfoExtensions + { + public static string AsRelativeTo(this FileInfo fileInfo,string path ) + { + if (!path.EndsWith("\\")) + path=path.ToLower() + "\\"; + int pathLength = path.Length; + + var filePath = fileInfo.FullName; + + if (!filePath.StartsWith(path,StringComparison.InvariantCultureIgnoreCase)) + throw new ArgumentException(String.Format("The path {0} doesn't contain the file {1}",path,filePath)); + + var relativePath = filePath.Substring(pathLength, filePath.Length - pathLength); + + return relativePath; + } + + public static string AsRelativeUrlTo(this FileInfo fileInfo,string path ) + { + + var relativePath = fileInfo.AsRelativeTo(path); + var replacedSlashes = relativePath.Replace("\\","/"); + var escaped = Uri.EscapeUriString(replacedSlashes); + return escaped; + } + + public static string RelativeUriToFilePath(this Uri uri) + { + return RelativeUrlToFilePath(uri.ToString()); + } + + public static string RelativeUrlToFilePath(this string url) + { + var unescaped=Uri.UnescapeDataString(url); + var path = unescaped.Replace("/", "\\"); + return path; + } + + + } +} diff --git a/trunk/Pithos.Core/NetworkGate.cs b/trunk/Pithos.Core/NetworkGate.cs new file mode 100644 index 0000000..906ce5d --- /dev/null +++ b/trunk/Pithos.Core/NetworkGate.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.IO; + +namespace Pithos.Core +{ + public enum NetworkOperation + { + None, + Uploading, + Downloading + } + + //The NetworkGate prevents starting download/uploads for files that are already in the process of downloading, + //uploading. + public class NetworkGate:IDisposable + { + //The state of each file is stored in a thread-safe dictionary + static readonly ConcurrentDictionary NetworkState = new ConcurrentDictionary(); + + public static NetworkOperation GetNetworkState(string path) + { + if (String.IsNullOrWhiteSpace(path)) + throw new ArgumentNullException("path"); + if (!Path.IsPathRooted(path)) + throw new ArgumentException("path must be a rooted path", "path"); + Contract.EndContractBlock(); + + NetworkOperation operation; + if (NetworkState.TryGetValue(path.ToLower(), out operation)) + return operation; + return NetworkOperation.None; + } + + //Store a network operation for the specified path + public static void SetNetworkState(string path, NetworkOperation operation) + { + if (String.IsNullOrWhiteSpace(path)) + throw new ArgumentNullException("path"); + if (!Path.IsPathRooted(path)) + throw new ArgumentException("path must be a rooted path", "path"); + Contract.EndContractBlock(); + + var lower = path.ToLower(); + NetworkState[lower] = operation; + //By default, None values don't need to be stored. They are stored anyway + //because TryRemove may fail. + if (operation == NetworkOperation.None) + { + NetworkOperation oldOperation; + NetworkState.TryRemove(lower, out oldOperation); + } + } + + //Clients should acquire a NetworkGate before starting any network operation. + //If Acquire fails, another network operation is already in progress + public static NetworkGate Acquire(string path, NetworkOperation operation) + { + if (String.IsNullOrWhiteSpace(path)) + throw new ArgumentNullException("path"); + if (!Path.IsPathRooted(path)) + throw new ArgumentException("path must be a rooted path", "path"); + Contract.EndContractBlock(); + + var state = GetNetworkState(path); + //If no operation is in progress, return a NetworkGate + return (state == NetworkOperation.None) + ? new NetworkGate(path, operation) + //otherwise return a gate with Fail flagged + : new NetworkGate(path, NetworkOperation.None); + } + + + + public string FilePath { get; private set; } + public NetworkOperation Operation { get; private set; } + + private NetworkGate(string path,NetworkOperation operation) + { + if (String.IsNullOrWhiteSpace(path)) + throw new ArgumentNullException("path"); + Contract.EndContractBlock(); + + Operation = operation; + FilePath = path.ToLower(); + + //Skip dummy operations (those with Operation == None) + if (Operation != NetworkOperation.None) + //and store the file's operation + SetNetworkState(FilePath, operation); + } + + //A NetworkGate has Failed if its operation is None + public bool Failed { get { return Operation == NetworkOperation.None; } } + + //Release a gate by setting the NetworkOperation to None + public void Release() + { + //Skip Failed flags + if (!Failed) + //And reset the operation state for the file + SetNetworkState(FilePath,NetworkOperation.None); + } + + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected void Dispose(bool disposing) + { + if (disposing) + { + Release(); + } + } + + } +} \ No newline at end of file diff --git a/trunk/Pithos.Network.Test/CloudFilesClientTest.cs b/trunk/Pithos.Network.Test/CloudFilesClientTest.cs new file mode 100644 index 0000000..bc8b98b --- /dev/null +++ b/trunk/Pithos.Network.Test/CloudFilesClientTest.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using NUnit.Framework; + +namespace Pithos.Network.Test +{ + [TestFixture] + class CloudFilesClientTest + { + [Test] + public void TestPutHashMap() + { + CloudFilesClient client=new CloudFilesClient(); + client.AuthenticationUrl = @"https://pithos.dev.grnet.gr"; + client.Authenticate("890329@vho.grnet.gr","24989dce4e0fcb072f8cb60c8922be19"); + var fileName = @"Long Dark Tea Time of the Soul, The.txt"; + var treeHash=Signature.CalculateTreeHash(Path.Combine(@"e:\pithos\" ,fileName), 4*1024*1024 , "sha256"); + var result=client.PutHashMap("PITHOS", fileName, treeHash).Result; + + Assert.IsNotEmpty(result); + } + } +} diff --git a/trunk/Pithos.Core.Test/SignatureTest.cs b/trunk/Pithos.Network.Test/SignatureTest.cs similarity index 99% rename from trunk/Pithos.Core.Test/SignatureTest.cs rename to trunk/Pithos.Network.Test/SignatureTest.cs index 02249d2..e45c87c 100644 --- a/trunk/Pithos.Core.Test/SignatureTest.cs +++ b/trunk/Pithos.Network.Test/SignatureTest.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using Newtonsoft.Json; +using Pithos.Network; namespace Pithos.Core.Test { diff --git a/trunk/Pithos.Interfaces/ICloudClient.cs b/trunk/Pithos.Network/ICloudClient.cs similarity index 87% rename from trunk/Pithos.Interfaces/ICloudClient.cs rename to trunk/Pithos.Network/ICloudClient.cs index 84965fb..907aba2 100644 --- a/trunk/Pithos.Interfaces/ICloudClient.cs +++ b/trunk/Pithos.Network/ICloudClient.cs @@ -1,11 +1,10 @@ using System; using System.Collections.Generic; using System.Diagnostics.Contracts; -using System.Linq; -using System.Text; using System.Threading.Tasks; +using Pithos.Interfaces; -namespace Pithos.Interfaces +namespace Pithos.Network { [ContractClass(typeof(ICloudClientContract))] public interface ICloudClient @@ -39,6 +38,8 @@ namespace Pithos.Interfaces void CreateFolder(string container, string folder); + Task GetHashMap(string container, string objectName); + Task PutHashMap(string container, string objectName, TreeHash hash); } @@ -195,6 +196,27 @@ namespace Pithos.Interfaces Contract.Requires(!String.IsNullOrWhiteSpace(container)); Contract.Requires(!String.IsNullOrWhiteSpace(folder)); } + + public Task GetHashMap(string container, string objectName) + { + Contract.Requires(!String.IsNullOrWhiteSpace(Token)); + Contract.Requires(StorageUrl != null); + Contract.Requires(!String.IsNullOrWhiteSpace(container)); + Contract.Requires(!String.IsNullOrWhiteSpace(objectName)); + + return default(Task); + } + + public Task PutHashMap(string container, string objectName, TreeHash hash) + { + Contract.Requires(!String.IsNullOrWhiteSpace(Token)); + Contract.Requires(StorageUrl != null); + Contract.Requires(!String.IsNullOrWhiteSpace(container)); + Contract.Requires(!String.IsNullOrWhiteSpace(objectName)); + Contract.Requires(hash != null); + + return default(Task); + } } public class ContainerInfo @@ -202,6 +224,8 @@ namespace Pithos.Interfaces public string Name { get; set; } public long Count { get; set; } public long Bytes { get; set; } + public string BlockHash { get; set; } + public int BlockSize { get; set; } public static ContainerInfo Empty=new ContainerInfo(); } diff --git a/trunk/Pithos.Network/MarkdownSharp.dll b/trunk/Pithos.Network/MarkdownSharp.dll new file mode 100644 index 0000000..b779a46 Binary files /dev/null and b/trunk/Pithos.Network/MarkdownSharp.dll differ diff --git a/trunk/Pithos.Network/RazorEngine.dll b/trunk/Pithos.Network/RazorEngine.dll new file mode 100644 index 0000000..ba251be Binary files /dev/null and b/trunk/Pithos.Network/RazorEngine.dll differ diff --git a/trunk/Pithos.Network/Resources/Focco.cshtml b/trunk/Pithos.Network/Resources/Focco.cshtml new file mode 100644 index 0000000..441cf32 --- /dev/null +++ b/trunk/Pithos.Network/Resources/Focco.cshtml @@ -0,0 +1,54 @@ + + + + + @Title + + + + + +
+
+ @if (@Sources.Length > 1) { +
+ Jump To … +
+
+ @for (var i = 0; i < Sources.Length; i++) { + + @Sources[i].Substring(2) + + } +
+
+
+ } + + + + + + + + + @for (var i = 0; i < Sections.Length; i++) { + + + + + } + +
+

@Title

+
+
+ +
+ @Sections[i].DocsHtml +
+
@Sections[i].CodeHtml
+
+
+ + diff --git a/trunk/Pithos.Network/Resources/Focco.css b/trunk/Pithos.Network/Resources/Focco.css new file mode 100644 index 0000000..f45b30f --- /dev/null +++ b/trunk/Pithos.Network/Resources/Focco.css @@ -0,0 +1,131 @@ +/*--------------------- Layout and Typography ----------------------------*/ +body { + font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; + font-size: 15px; + line-height: 22px; + color: #252519; + margin: 0; padding: 0; +} +a { + color: #261a3b; +} + a:visited { + color: #261a3b; + } +p { + margin: 0 0 15px 0; +} +h4, h5, h6 { + color: #333; + margin: 6px 0 6px 0; + font-size: 13px; +} + h2, h3 { + margin-bottom: 0; + color: #000; + } + h1 { + margin-top: 40px; + margin-bottom: 15px; + color: #000; + } +#container { + position: relative; +} +#background { + position: fixed; + top: 0; left: 525px; right: 0; bottom: 0; + background: #f5f5ff; + border-left: 1px solid #e5e5ee; + z-index: -1; +} +#jump_to, #jump_page { + background: white; + -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; + -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; + font: 10px Arial; + text-transform: uppercase; + cursor: pointer; + text-align: right; +} +#jump_to, #jump_wrapper { + position: fixed; + right: 0; top: 0; + padding: 5px 10px; +} + #jump_wrapper { + padding: 0; + display: none; + } + #jump_to:hover #jump_wrapper { + display: block; + } + #jump_page { + padding: 5px 0 3px; + margin: 0 0 25px 25px; + } + #jump_page .source { + display: block; + padding: 5px 10px; + text-decoration: none; + border-top: 1px solid #eee; + } + #jump_page .source:hover { + background: #f5f5ff; + } + #jump_page .source:first-child { + } +table td { + border: 0; + outline: 0; +} + td.docs, th.docs { + max-width: 450px; + min-width: 450px; + min-height: 5px; + padding: 10px 25px 1px 50px; + overflow-x: hidden; + vertical-align: top; + text-align: left; + } + .docs pre { + margin: 15px 0 15px; + padding-left: 15px; + } + .docs p tt, .docs p code { + background: #f8f8ff; + border: 1px solid #dedede; + font-size: 12px; + padding: 0 0.2em; + } + .pilwrap { + position: relative; + } + .pilcrow { + font: 12px Arial; + text-decoration: none; + color: #454545; + position: absolute; + top: 3px; left: -20px; + padding: 1px 2px; + opacity: 0; + -webkit-transition: opacity 0.2s linear; + } + td.docs:hover .pilcrow { + opacity: 1; + } + td.code, th.code { + padding: 14px 15px 16px 25px; + width: 100%; + vertical-align: top; + background: #f5f5ff; + border-left: 1px solid #e5e5ee; + } + pre, tt, code { + font-size: 12px; line-height: 18px; + font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; + margin: 0; padding: 0; + } + +/*---------------------- Prettify Syntax Highlighting -----------------------------*/ +.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun{color:#660}.pln{color:#000}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec{color:#606}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}@media print{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun{color:#440}.pln{color:#000}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}} diff --git a/trunk/Pithos.Network/Resources/prettify.js b/trunk/Pithos.Network/Resources/prettify.js new file mode 100644 index 0000000..27ed455 --- /dev/null +++ b/trunk/Pithos.Network/Resources/prettify.js @@ -0,0 +1,36 @@ +window.PR_SHOULD_USE_CONTINUATION=true;window.PR_TAB_WIDTH=8;window.PR_normalizedHtml=window.PR=window.prettyPrintOne=window.prettyPrint=void 0;window._pr_isIE6=function(){var y=navigator&&navigator.userAgent&&navigator.userAgent.match(/\bMSIE ([678])\./);y=y?+y[1]:false;window._pr_isIE6=function(){return y};return y}; +(function(){function y(b){return b.replace(L,"&").replace(M,"<").replace(N,">")}function H(b,f,i){switch(b.nodeType){case 1:var o=b.tagName.toLowerCase();f.push("<",o);var l=b.attributes,n=l.length;if(n){if(i){for(var r=[],j=n;--j>=0;)r[j]=l[j];r.sort(function(q,m){return q.name"); +for(l=b.firstChild;l;l=l.nextSibling)H(l,f,i);if(b.firstChild||!/^(?:br|link|img)$/.test(o))f.push("");break;case 3:case 4:f.push(y(b.nodeValue));break}}function O(b){function f(c){if(c.charAt(0)!=="\\")return c.charCodeAt(0);switch(c.charAt(1)){case "b":return 8;case "t":return 9;case "n":return 10;case "v":return 11;case "f":return 12;case "r":return 13;case "u":case "x":return parseInt(c.substring(2),16)||c.charCodeAt(1);case "0":case "1":case "2":case "3":case "4":case "5":case "6":case "7":return parseInt(c.substring(1), +8);default:return c.charCodeAt(1)}}function i(c){if(c<32)return(c<16?"\\x0":"\\x")+c.toString(16);c=String.fromCharCode(c);if(c==="\\"||c==="-"||c==="["||c==="]")c="\\"+c;return c}function o(c){var d=c.substring(1,c.length-1).match(RegExp("\\\\u[0-9A-Fa-f]{4}|\\\\x[0-9A-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\s\\S]|-|[^-\\\\]","g"));c=[];for(var a=[],k=d[0]==="^",e=k?1:0,h=d.length;e122)){s<65||g>90||a.push([Math.max(65,g)|32,Math.min(s,90)|32]);s<97||g>122||a.push([Math.max(97,g)&-33,Math.min(s,122)&-33])}}a.sort(function(v,w){return v[0]-w[0]||w[1]-v[1]});d=[];g=[NaN,NaN];for(e=0;eh[0]){h[1]+1>h[0]&&a.push("-"); +a.push(i(h[1]))}}a.push("]");return a.join("")}function l(c){for(var d=c.source.match(RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g")),a=d.length,k=[],e=0,h=0;e=2&&c==="[")d[e]=o(g);else if(c!=="\\")d[e]=g.replace(/[a-zA-Z]/g,function(s){s=s.charCodeAt(0);return"["+String.fromCharCode(s&-33,s|32)+"]"})}return d.join("")}for(var n=0,r=false,j=false,q=0,m=b.length;q=0;l-=16)o.push(" ".substring(0,l));l=n+1;break;case "\n":f=0;break;default:++f}if(!o)return i;o.push(i.substring(l));return o.join("")}}function I(b, +f,i,o){if(f){b={source:f,c:b};i(b);o.push.apply(o,b.d)}}function B(b,f){var i={},o;(function(){for(var r=b.concat(f),j=[],q={},m=0,t=r.length;m=0;)i[c.charAt(d)]=p;p=p[1];c=""+p;if(!q.hasOwnProperty(c)){j.push(p);q[c]=null}}j.push(/[\0-\uffff]/);o=O(j)})();var l=f.length;function n(r){for(var j=r.c,q=[j,z],m=0,t=r.source.match(o)||[],p={},c=0,d=t.length;c=5&&"lang-"===k.substring(0,5))&&!(e&&typeof e[1]==="string")){h=false;k=P}h||(p[a]=k)}g=m;m+=a.length;if(h){h=e[1];var s=a.indexOf(h),v=s+h.length;if(e[2]){v=a.length-e[2].length;s=v-h.length}k=k.substring(5);I(j+g,a.substring(0,s),n,q);I(j+g+s,h,Q(k,h),q);I(j+g+v,a.substring(v),n,q)}else q.push(j+g,k)}r.d=q}return n}function x(b){var f=[],i=[];if(b.tripleQuotedStrings)f.push([A,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/, +null,"'\""]);else b.multiLineStrings?f.push([A,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"]):f.push([A,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"]);b.verbatimStrings&&i.push([A,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null]);if(b.hashComments)if(b.cStyleComments){f.push([C,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"]);i.push([A,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/, +null])}else f.push([C,/^#[^\r\n]*/,null,"#"]);if(b.cStyleComments){i.push([C,/^\/\/[^\r\n]*/,null]);i.push([C,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}b.regexLiterals&&i.push(["lang-regex",RegExp("^"+Z+"(/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/)")]);b=b.keywords.replace(/^\s+|\s+$/g,"");b.length&&i.push([R,RegExp("^(?:"+b.replace(/\s+/g,"|")+")\\b"),null]);f.push([z,/^\s+/,null," \r\n\t\u00a0"]);i.push([J,/^@[a-z_$][a-z_$@0-9]*/i,null],[S,/^@?[A-Z]+[a-z][A-Za-z_$@0-9]*/, +null],[z,/^[a-z_$][a-z_$@0-9]*/i,null],[J,/^(?:0x[a-f0-9]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+\-]?\d+)?)[a-z]*/i,null,"0123456789"],[E,/^.[^\s\w\.$@\'\"\`\/\#]*/,null]);return B(f,i)}function $(b){function f(D){if(D>r){if(j&&j!==q){n.push("");j=null}if(!j&&q){j=q;n.push('')}var T=y(p(i.substring(r,D))).replace(e?d:c,"$1 ");e=k.test(T);n.push(T.replace(a,s));r=D}}var i=b.source,o=b.g,l=b.d,n=[],r=0,j=null,q=null,m=0,t=0,p=Y(window.PR_TAB_WIDTH),c=/([\r\n ]) /g, +d=/(^| ) /gm,a=/\r\n?|\n/g,k=/[ \r\n]$/,e=true,h=window._pr_isIE6();h=h?b.b.tagName==="PRE"?h===6?" \r\n":h===7?" 
\r":" \r":" 
":"
";var g=b.b.className.match(/\blinenums\b(?::(\d+))?/),s;if(g){for(var v=[],w=0;w<10;++w)v[w]=h+'
  • ';var F=g[1]&&g[1].length?g[1]-1:0;n.push('
    1. ");s=function(){var D=v[++F%10];return j?""+D+'':D}}else s=h; +for(;;)if(m");j=null}n.push(o[m+1]);m+=2}else if(t");g&&n.push("
    ");b.a=n.join("")}function u(b,f){for(var i=f.length;--i>=0;){var o=f[i];if(G.hasOwnProperty(o))"console"in window&&console.warn("cannot override language handler %s",o);else G[o]=b}}function Q(b,f){b&&G.hasOwnProperty(b)||(b=/^\s*1&&m.charAt(0)==="<"){if(!ba.test(m))if(ca.test(m)){f.push(m.substring(9,m.length-3));n+=m.length-12}else if(da.test(m)){f.push("\n");++n}else if(m.indexOf(V)>=0&&m.replace(/\s(\w+)\s*=\s*(?:\"([^\"]*)\"|'([^\']*)'|(\S+))/g,' $1="$2$3$4"').match(/[cC][lL][aA][sS][sS]=\"[^\"]*\bnocode\b/)){var t=m.match(W)[2],p=1,c;c=j+1;a:for(;c=0;){var e=p.indexOf(";",k);if(e>=0){var h=p.substring(k+3,e),g=10;if(h&&h.charAt(0)==="x"){h=h.substring(1);g=16}var s=parseInt(h,g);isNaN(s)||(p=p.substring(0,k)+String.fromCharCode(s)+p.substring(e+1))}}a=p.replace(ea,"<").replace(fa,">").replace(ga,"'").replace(ha,'"').replace(ia," ").replace(ja, +"&")}f.push(a);n+=a.length}}o={source:f.join(""),h:r};var v=o.source;b.source=v;b.c=0;b.g=o.h;Q(i,v)(b);$(b)}catch(w){if("console"in window)console.log(w&&w.stack?w.stack:w)}}var A="str",R="kwd",C="com",S="typ",J="lit",E="pun",z="pln",P="src",V="nocode",Z=function(){for(var b=["!","!=","!==","#","%","%=","&","&&","&&=","&=","(","*","*=","+=",",","-=","->","/","/=",":","::",";","<","<<","<<=","<=","=","==","===",">",">=",">>",">>=",">>>",">>>=","?","@","[","^","^=","^^","^^=","{","|","|=","||","||=", +"~","break","case","continue","delete","do","else","finally","instanceof","return","throw","try","typeof"],f="(?:^^|[+-]",i=0;i:&a-z])/g,"\\$1");f+=")\\s*";return f}(),L=/&/g,M=//g,X=/\"/g,ea=/</g,fa=/>/g,ga=/'/g,ha=/"/g,ja=/&/g,ia=/ /g,ka=/[\r\n]/g,K=null,aa=RegExp("[^<]+|