/*
* Copyright (C) 2004-2005 Jonathan Bindel
* Copyright (C) 2006-2007 Eskil Bylund
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
using DCSharp.Backend.Objects;
using DCSharp.Extras;
using DCSharp.Hashing;
using DCSharp.Logging;
using DCSharp.Text;
using DCSharp.Xml.FileList;
using LFileDCSharp.Xml.FileList.File;
using LDirectoryDCSharp.Xml.FileList.Directory;
namespace DCSharp.Backend.Managers{
public class ShareInfo
{
public ShareInfo(string path, string virtualName)
{
this.path = path;
this.virtualName = virtualName;
}
public ShareInfo()
{
}
[XmlText]
public string Path
{
get { return path; }
set { path = value; }
}
private string path;
[XmlAttribute]
public string VirtualName
{
get { return virtualName; }
set { virtualName = value; }
}
private string virtualName;
}
public class DirectoryEventArgs : EventArgs
{
public DirectoryEventArgs(string path, string virtualName)
{
this.path = path;
this.virtualName = virtualName;
}
public string Path
{
get { return path; }
}
private string path;
public string VirtualName
{
get { return virtualName; }
}
private string virtualName;
}
public class DirectoryRenamedEventArgs : DirectoryEventArgs
{
public DirectoryRenamedEventArgs(string path, string oldName,
string newName) : base(path, newName)
{
this.oldName = oldName;
}
public string OldVirtualName
{
get { return oldName; }
}
private string oldName;
}
/// <summary>
/// A class managing shared files and directories.
/// </summary>
public class ShareManager : IEnumerable<ShareInfo>, IShareManager
{
public event EventHandler<DirectoryEventArgs> DirectoryAdded;
public event EventHandler<DirectoryEventArgs> DirectoryRemoved;
public event EventHandler<DirectoryRenamedEventArgs> DirectoryRenamed;
private static Logger log = LogManager.GetLogger("ShareManager");
private HashManager hashManager;
private HashStore hashStore;
private Dictionary<LFile, string> hashPending;
private Dictionary<string, string> sharedDirectories;
private bool dirty;
private bool listDirty;
/// <summary>
/// Constructs a new instance using the specified hash store.
/// </summary>
/// <param name="hashStore">The hash store to use.</param>
public ShareManager(HashStore hashStore, string dataDir)
{
if (hashStore == null)
{
throw new ArgumentNullException("hashStore");
}
this.hashStore = hashStore;
nmdcListPath = Path.Combine(dataDir, "files.DcLst");
xmlListPath = Path.Combine(dataDir, "files.xml");
sharedDirectories = new Dictionary<string, string>();
fileListing = new FileListing();
// Hashing
hashManager = new HashManager();
hashManager.FileHashed += OnFileHashed;
hashPending = new Dictionary<LFile, string>();
}
#region Properties
/// <summary>
/// Gets the number of bytes that remains until all files have been hashed.
/// </summary>
/// <remarks>The number of bytes that remains until all files have been hashed.</remarks>
public long BytesRemaining
{
get { return hashManager.BytesRemaining; }
}
/// <summary>
/// Gets the total size of the shared directories in bytes.
/// </summary>
/// <remarks>The total size of the shared directories in bytes.</remarks>
public long Total
{
get
{
if (dirty)
{
lock (sharedDirectories)
{
total = fileListing.GetSize();
dirty = false;
}
}
return total;
}
}
private long total;
/// <summary>
/// Gets or sets the path to the file list.
/// </summary>
/// <returns>The path to the file list.</returns>
public string FileListPath
{
get { return xmlListPath; }
set { xmlListPath = value; }
}
private string xmlListPath;
/// <summary>
/// Gets or sets the path to the bzip2 compressed file list.
/// </summary>
/// <returns>The path to the bzip2 compressed file list.</returns>
public string CompressedFileListPath
{
get { return xmlListPath + ".bz2"; }
}
/// <summary>
/// Gets or sets the path to the old style file list.
/// </summary>
/// <returns>The path to the old style file list.</returns>
public string LegacyFileListPath
{
get { return nmdcListPath; }
set { nmdcListPath = value; }
}
private string nmdcListPath;
public object SyncRoot
{
get { return sharedDirectories; }
}
protected FileListing FileListing
{
get { return fileListing; }
}
private FileListing fileListing;
#endregion
#region Methods
/// <summary>
/// Adds a directory to the share.
/// </summary>
/// <param name="path">The path to the directory.</param>
/// <param name="virtualName">The virtual name of the directory.</param>
public void AddDirectory(string path, string virtualName)
{
if (path == null)
{
throw new ArgumentNullException("path");
}
if (virtualName == null)
{
throw new ArgumentNullException("virtualName");
}
lock (sharedDirectories)
{
if (sharedDirectories.ContainsKey(path))
{
throw new ArgumentException("The directory already exists",
"path");
}
if (sharedDirectories.ContainsValue(virtualName))
{
throw new ArgumentException("A directory with that virtual name already exists",
"virtualName");
}
LDirectory directory = new LDirectory(virtualName);
LoadDirectory(directory, path);
sharedDirectories.Add(path, virtualName);
fileListing.Add(directory);
dirty = listDirty = true;
OnDirectoryAdded(path, virtualName);
}
}
/// <summary>
/// Removes a directory from the share.
/// </summary>
/// <param name="path">The path to the directory.</param>
public void RemoveDirectory(string path)
{
if (path == null)
{
throw new ArgumentNullException("path");
}
lock (sharedDirectories)
{
if (sharedDirectories.ContainsKey(path))
{
string virtualName = sharedDirectories[path];
LDirectory directory = fileListing[virtualName];
if (directory != null)
{
lock (hashStore)
{
AbortHashing(directory);
}
fileListing.Remove(directory);
dirty = listDirty = true;
}
sharedDirectories.Remove(path);
OnDirectoryRemoved(path, virtualName);
}
}
}
/// <summary>
/// Renames a directory.
/// </summary>
/// <param name="oldName">The old virtual name of the directory.</param>
/// <param name="newName">The new virtual name.</param>
/// <returns>True if the directory was successfully renamed, otherwise, false.</returns>
public bool RenameDirectory(string oldName, string newName)
{
lock (sharedDirectories)
{
if (sharedDirectories.ContainsValue(oldName) &&
!sharedDirectories.ContainsValue(newName))
{
string path = GetPath(oldName);
sharedDirectories.Remove(path);
sharedDirectories.Add(path, newName);
LDirectory directory = fileListing[oldName];
if (directory != null)
{
directory.Name = newName;
}
OnDirectoryRenamed(path, oldName, newName);
return true;
}
}
return false;
}
/// <summary>
/// Refreshes the list of shared files.
/// </summary>
public void Refresh()
{
lock (sharedDirectories)
{
// Abort the hashing of all files.
lock (hashStore)
{
foreach (KeyValuePair<LFile, string> pair in hashPending)
{
hashManager.AbortHash(pair.Value);
}
hashPending.Clear();
}
// Update the directories in the file list
List<LDirectory> directories = new List<LDirectory>();
foreach (KeyValuePair<string, string> pair in sharedDirectories)
{
LDirectory directory = new LDirectory(pair.Value);
directories.Add(directory);
LoadDirectory(directory, pair.Key);
}
fileListing.Directories = directories;
dirty = listDirty = true;
}
}
/// <summary>
/// Checks if the share contains a specific directory.
/// </summary>
/// <param name="path">The path to the directory.</param>
/// <returns>True if the share contains the directory, otherwise, false.</returns>
public bool ContainsDirectory(string path)
{
return sharedDirectories.ContainsKey(path);
}
/// <summary>
/// Checks if the share contains a directory with a specific name.
/// </summary>
/// <param name="virtualName">The name to check.</param>
/// <returns>True if the share contains a directory with the name, otherwise, false.</returns>
public bool ContainsVirtualName(string virtualName)
{
return sharedDirectories.ContainsValue(virtualName);
}
public IEnumerator<ShareInfo> GetEnumerator()
{
foreach (KeyValuePair<string, string> pair in sharedDirectories)
{
yield return new ShareInfo(pair.Key, pair.Value);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// Gets the virtual name of a directory.
/// </summary>
/// <param name="path">The path to the directory.</param>
/// <returns>The name of the directory, otherwise, null.</returns>
public string GetVirtualName(string path)
{
string virtualName = null;
sharedDirectories.TryGetValue(path, out virtualName);
return virtualName;
}
/// <summary>
/// Saves the file list to disk.
/// </summary>
/// <returns>The path to the file list.</returns>
public void SaveFileList()
{
lock (sharedDirectories)
{
if (!listDirty)
{
FileInfo fileInfo = new FileInfo(xmlListPath);
if (fileInfo.Exists)
{
// List not dirty, and the file exists.
return;
}
}
fileListing.Save(xmlListPath);
listDirty = false;
}
Util.SaveNMDCList(fileListing, nmdcListPath);
Util.Compress(xmlListPath, CompressedFileListPath,
Util.Compression.BZip2);
}
/// <summary>
/// Gets the hash tree with a specific root hash.
/// </summary>
/// <param name="root">The Base32 encoded root hash.</param>
/// <returns>The hash tree if found; otherwise, null.</returns>
public HashTree GetHashTree(string root)
{
lock (hashStore)
{
return hashStore.GetHashTree(root);
}
}
#region GetPath methods
/// <summary>
/// Gets the path of the directory with the specified virtual name.
/// </summary>
/// <param name="virtualName">The virtual name of the directory.</param>
/// <returns>The path to the directory with the specified virtual name.</returns>
public string GetPath(string virtualName)
{
lock (sharedDirectories)
{
foreach (KeyValuePair<string, string> pair in sharedDirectories)
{
if (pair.Value == virtualName)
{
return pair.Key;
}
}
return null;
}
}
/// <remarks>
/// The path should start with the virtual name, not a separator character.
/// </remarks>
/// <param name="virtualPath">The virtual path.</param>
public string GetFullPath(string virtualPath)
{
// FIXME: Should check if the file exists in the file list
int slash = virtualPath.IndexOf(Path.DirectorySeparatorChar);
if (slash > 0)
{
string virtualName = virtualPath.Substring(0, slash);
string path = GetPath(virtualName);
if (path != null)
{
// Removes the root from the second path to make Path.Combine
// work as intended.
return Path.Combine(path, virtualPath.Substring(slash + 1));
}
}
return null;
}
public string GetFullPathFromTTH(string tth)
{
string path = GetPathFromTTH(tth);
return path != null ? GetFullPath(path) : null;
}
public string GetPathFromTTH(string tth)
{
LDirectory[] directories;
lock (sharedDirectories)
{
directories = fileListing.Directories.ToArray();
}
foreach (LDirectory directory in directories)
{
string path = GetPathFromTTH(directory, tth, String.Empty);
if (path != null)
{
return path;
}
}
return null;
}
private static string GetPathFromTTH(LDirectory directory, string tth,
string parent)
{
string currentPath = Path.Combine(parent, directory.Name);
foreach(LFile file in directory.Files)
{
if(file.TTH == tth)
{
return Path.Combine(currentPath, file.Name);
}
}
foreach(LDirectory dir in directory.Directories)
{
string path = GetPathFromTTH(dir, tth, currentPath);
if (path != null)
{
return path;
}
}
return null;
}
#endregion
#region Directory loading
private void LoadDirectory(LDirectory directory, string path)
{
DirectoryInfo dirInfo = TryGetDirectoryInfo(path);
if (dirInfo != null && dirInfo.Exists)
{
try
{
LoadDirectory(directory, dirInfo,
Runtime.Settings.ShareHidden);
}
catch (Exception e)
{
log.Error("Loading directory failed", e);
}
}
}
private void LoadDirectory(LDirectory directory, DirectoryInfo dirInfo,
bool addHidden)
{
// Load the directories
foreach (DirectoryInfo childDirInfo in dirInfo.GetDirectories())
{
bool dirIsHidden = childDirInfo.Attributes ==
(childDirInfo.Attributes | FileAttributes.Hidden);
if (addHidden || !dirIsHidden)
{
LDirectory childDir = new LDirectory(childDirInfo.Name);
directory.Directories.Add(childDir);
try
{
LoadDirectory(childDir, childDirInfo, addHidden);
}
catch (Exception e)
{
log.Error("Loading directory failed", e);
}
}
}
// Load the files
foreach (FileInfo fileInfo in dirInfo.GetFiles())
{
bool fileIsHidden = fileInfo.Attributes ==
(fileInfo.Attributes | FileAttributes.Hidden);
if (addHidden || !fileIsHidden)
{
LFile file = new LFile(fileInfo.Name, fileInfo.Length, null);
directory.Files.Add(file);
lock (hashStore)
{
if (!hashStore.HasHash(fileInfo))
{
hashPending.Add(file, fileInfo.FullName);
hashManager.Hash(fileInfo);
}
else if (file.TTH == null)
{
file.TTH = hashStore.GetRootHash(fileInfo);
}
}
}
}
}
#endregion
#region Search
/// <summary>
/// Does a search in the shared directories for matching directories and
/// files.
/// </summary>
/// <param name="searchInfo">The search criteria.</param>
/// <param name="max">The max number of results to add.</param>
public void Search(SearchInfo searchInfo, int? max)
{
LDirectory[] directories;
lock (sharedDirectories)
{
directories = fileListing.Directories.ToArray();
}
int count = 0;
foreach (LDirectory directory in directories)
{
SearchDirectory(directory, searchInfo, String.Empty, ref count, max);
}
}
private static void SearchDirectory(LDirectory directory,
SearchInfo searchInfo, string parent, ref int count, int? max)
{
string currentPath = Path.Combine(parent, directory.Name);
foreach(LDirectory dir in directory.Directories)
{
SearchDirectory(dir, searchInfo, currentPath, ref count, max);
if (count >= max)
{
return;
}
}
long? maxSize = searchInfo.MaxSize;
long? minSize = searchInfo.MinSize;
if((searchInfo.Type == SearchFileType.Any ||
searchInfo.Type == SearchFileType.Directory) &&
searchInfo.TTH == null)
{
if((maxSize == null || directory.GetSize() < maxSize) &&
(minSize == null || directory.GetSize() > minSize))
{
if(Util.StringMatchesKeywords(directory.Name,
searchInfo.Keywords))
{
SearchResult result = new SearchResult(
ResultType.Directory, currentPath);
searchInfo.Add(result);
count++;
}
}
}
// TODO: Check if the type matches
if(searchInfo.Type != SearchFileType.Directory)
{
foreach(LFile file in directory.Files)
{
if (count >= max)
{
return;
}
if((searchInfo.TTH != null && file.TTH != searchInfo.TTH) ||
(maxSize != null && file.Size > maxSize) ||
(minSize != null && file.Size < minSize))
{
continue;
}
if((searchInfo.TTH != null && file.TTH == searchInfo.TTH) ||
Util.StringMatchesKeywords(file.Name, searchInfo.Keywords))
{
string path = Path.Combine(currentPath, file.Name);
SearchResult result = new SearchResult(ResultType.File,
path, file.Size, file.TTH);
searchInfo.Add(result);
count++;
}
}
}
}
#endregion
/// <summary>
/// Emits the DirectoryAdded event.
/// </summary>
/// <param name="path">The path to the directory.</param>
/// <param name="name">The name of the directory.</param>
protected virtual void OnDirectoryAdded(string path, string name)
{
if (DirectoryAdded != null)
{
DirectoryAdded(this, new DirectoryEventArgs(path, name));
}
}
/// <summary>
/// Emits the DirectoryRemoved event.
/// </summary>
/// <param name="path">The path to the directory.</param>
/// <param name="name">The name of the directory.</param>
protected virtual void OnDirectoryRemoved(string path, string name)
{
if (DirectoryRemoved != null)
{
DirectoryRemoved(this, new DirectoryEventArgs(path, name));
}
}
/// <summary>
/// Emits the DirectoryRenamed event.
/// </summary>
/// <param name="path">The path to the directory.</param>
/// <param name="oldName">The old virtual name.</param>
/// <param name="newName">The new virtual name.</param>
protected virtual void OnDirectoryRenamed(string path, string oldName,
string newName)
{
if (DirectoryRenamed != null)
{
DirectoryRenamed(this, new DirectoryRenamedEventArgs(path,
oldName, newName));
}
}
private void AbortHashing(LDirectory directory)
{
foreach (LFile file in directory.Files)
{
string filename;
if (hashPending.TryGetValue(file, out filename))
{
hashManager.AbortHash(filename);
hashPending.Remove(file);
}
}
directory.Directories.ForEach(AbortHashing);
}
private DirectoryInfo TryGetDirectoryInfo(string path)
{
try
{
return new DirectoryInfo(path);
}
catch
{
return null;
}
}
private void OnFileHashed(object obj, HashEventArgs args)
{
lock (hashStore)
{
LFile file = null;
foreach (KeyValuePair<LFile, string> pair in hashPending)
{
if (pair.Value == args.FileInfo.FullName)
{
file = pair.Key;
break;
}
}
if (file != null)
{
hashStore.AddFile(args.FileInfo, args.HashTree);
file.TTH = Base32.Encode(args.HashTree.Root);
hashPending.Remove(file);
}
}
}
#endregion
}
}
|