/*
* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
using System;
using System.Collections.Generic;
using Gtk;
using Glade;
using Mono.Unix;
using DCSharp.Backend.Connections;
using DCSharp.Backend.Objects;
using DCSharp.Backend.Managers;
namespace DCSharp.GUI{
public class DownloadPage : Page
{
private DownloadManager downloadManager;
private DownloadStore downloadStore;
private TreePath virtualRoot;
private DownloadView directoryView;
private DownloadView downloadView;
private bool handleSelectionChanged;
private bool showSources;
private VisibleColumnsWindow columnsWindow;
private List<DownloadFileInfo> addQueue;
private List<DownloadFileInfo> activeDownloads;
private uint timeout;
private uint updateId;
[Widget]
private HPaned hpaned;
[Widget]
private ScrolledWindow scrolledWindow;
[Widget]
private ScrolledWindow scrolledWindowTree;
[Widget]
private HBox actionBox;
[Widget]
private CheckButton directoryTreeCheckButton;
[Widget]
private CheckButton sourcesCheckButton;
[Widget]
private Button cancelButton;
[Widget]
private Button clearButton;
#region Constructors
public DownloadPage(ConnectionManager connectionManager,
DownloadManager downloadManager) : base("DownloadPage.glade")
{
if (connectionManager == null)
{
throw new ArgumentNullException("connectionManager");
}
if (downloadManager == null)
{
throw new ArgumentNullException("downloadManager");
}
this.downloadManager = downloadManager;
activeDownloads = new List<DownloadFileInfo>();
addQueue = new List<DownloadFileInfo>();
handleSelectionChanged = true;
// User interface
ActionEntry[] entries = new ActionEntry[] {
new ActionEntry("VisibleColumns", null, Catalog.GetString("_Visible Columns..."), null,
Catalog.GetString("Edit visible columns"),
OnVisibleColumns),
};
actionGroup = new ActionGroup("DownloadPageActions");
actionGroup.Add(entries);
// Model
downloadStore = new DownloadStore();
// Directory tree
TreeModelFilter filterModel = new TreeModelFilter(downloadStore, null);
filterModel.VisibleFunc = DirectoryVisibleFunc;
TreeModelSort sortModel = new TreeModelSort(filterModel);
sortModel.SetSortColumnId((int)DownloadStore.Column.Name,
SortType.Ascending);
directoryView = new DownloadView(connectionManager, downloadManager,
sortModel);
directoryView.KeyPressEvent += OnDirectoryKeyPressEvent;
directoryView.Selection.Mode = SelectionMode.Single;
directoryView.Selection.Changed += OnDirectorySelectionChanged;
directoryView.SetSizeRequest(140, 0);
directoryView.HeadersVisible = false;
directoryView.Show();
scrolledWindowTree.Add(directoryView);
// Hide all but the first column
for (int i = 1; i < directoryView.Columns.Length; i++)
{
TreeViewColumn column = directoryView.Columns[i];
column.Visible = false;
}
// Directory tree remove action
Action removeAction = new Action("Remove", null, null, Stock.Remove);
removeAction.Activated += OnDirectoryRemoveSelected;
ActionGroup viewActions = new ActionGroup("DownloadPageDirectoryViewActions");
viewActions.Add(removeAction);
directoryView.Uim.InsertActionGroup(viewActions, 0);
// Download view
downloadView = new DownloadView(connectionManager, downloadManager);
downloadView.KeyPressEvent += OnKeyPressEvent;
downloadView.RowActivated += OnRowActivated;
downloadView.RowExpanded += OnRowExpanded;
downloadView.Selection.Changed += OnSelectionChanged;
downloadView.Show();
scrolledWindow.Add(downloadView);
UpdateDownloadViewModel(null);
// Download view remove action
removeAction = new Action("Remove", null, null, Stock.Remove);
removeAction.Activated += OnRemoveSelected;
viewActions = new ActionGroup("DownloadPageViewActions");
viewActions.Add(removeAction);
downloadView.Uim.InsertActionGroup(viewActions, 0);
// Events
directoryTreeCheckButton.Toggled += OnDirectoryTreeCheckButtonToggled;
sourcesCheckButton.Toggled += OnSourcesCheckButtonToggled;
cancelButton.Clicked += OnCancelButtonClicked;
clearButton.Clicked += OnClearButtonClicked;
// Initialize
lock (downloadManager.SyncRoot)
{
foreach (DownloadFileInfo download in downloadManager)
{
if (!download.IsFileList)
{
IdleAdd(download);
}
}
downloadManager.DownloadAdded += OnDownloadAdded;
downloadManager.DownloadRemoved += OnDownloadRemoved;
}
}
#endregion
#region Properties
public override string Title
{
get { return Catalog.GetString("Downloads"); }
}
public override Gdk.Pixbuf Icon
{
get { return RenderIcon(Stock.GoDown, IconSize.Menu, null); }
}
public override string Tooltip
{
get
{
if (IsWorking)
{
return Catalog.GetString("Loading downloads...");
}
int downloading = 0;
foreach (DownloadFileInfo download in downloadManager)
{
if (download.Active)
{
downloading++;
}
}
int remaining = downloadManager.Count - downloading;
string tooltip = String.Format(Catalog.GetPluralString(
"{0} file downloading", "{0} files downloading", downloading),
downloading);
tooltip += ". ";
tooltip += String.Format(Catalog.GetPluralString(
"{0} file waiting", "{0} files waiting", remaining),
remaining);
return String.Format(tooltip, downloadManager.Count);
}
}
public override string Status
{
get { return Tooltip; }
}
public override ActionGroup Actions
{
get { return actionGroup; }
}
private ActionGroup actionGroup;
public int PanePosition
{
get { return hpaned.Position; }
set { hpaned.Position = value; }
}
public bool DirectoryTreeVisible
{
get { return directoryTreeCheckButton.Active; }
set { directoryTreeCheckButton.Active = value; }
}
public bool SourcesVisible
{
get { return sourcesCheckButton.Active; }
set { sourcesCheckButton.Active = value; }
}
public int VisibleColumns
{
get { return Util.GetVisibleColumns(downloadView.Columns); }
set { Util.SetVisibleColumns(downloadView.Columns, value | 1); }
}
#endregion
#region Methods
public override void Dispose()
{
downloadManager.DownloadAdded -= OnDownloadAdded;
downloadManager.DownloadRemoved -= OnDownloadRemoved;
if (timeout != 0)
{
GLib.Source.Remove(timeout);
timeout = 0;
}
if (updateId != 0)
{
GLib.Source.Remove(updateId);
updateId = 0;
}
if (columnsWindow != null)
{
columnsWindow.Destroy();
columnsWindow = null;
}
base.Dispose();
}
protected override bool OnKeyPressEvent(Gdk.EventKey evnt)
{
if (evnt.Key == Gdk.Key.BackSpace)
{
TreeIter iter;
if (scrolledWindowTree.Visible && directoryView.Selection.GetSelected(out iter))
{
if (directoryView.Model.IterParent(out iter, iter))
{
TreePath path = directoryView.Model.GetPath(iter);
directoryView.Selection.SelectPath(path);
directoryView.ScrollToCell(path, null, false, 0f, 0f);
}
else
{
directoryView.Selection.UnselectAll();
}
return true;
}
}
return base.OnKeyPressEvent(evnt);
}
private void Add(DownloadFileInfo download)
{
downloadStore.Add(download);
download.ActiveChanged += OnActiveChanged;
download.SourceAdded += OnSourceAdded;
download.SourceRemoved += OnSourceRemoved;
TreeIter iter = downloadStore.GetIter(download);
TreeIter parent;
if (downloadStore.IterParent(out parent, iter))
{
bool loaded = (bool)downloadStore.GetValue(parent, (int)DownloadStore.Column.Loaded);
if (loaded)
{
// Add the sources
foreach (SourceInfo source in download.SourceInfos)
{
downloadStore.AppendValues(iter, source, source.Nick);
}
downloadStore.SetValue(iter, (int)DownloadStore.Column.Loaded, true);
}
}
if (download.Active)
{
activeDownloads.Add(download);
if (timeout == 0)
{
timeout = GLib.Timeout.Add(1000, OnRefreshTimeout);
}
}
}
private void Remove(DownloadFileInfo download)
{
activeDownloads.Remove(download);
download.ActiveChanged -= OnActiveChanged;
download.SourceAdded -= OnSourceAdded;
download.SourceRemoved -= OnSourceRemoved;
downloadStore.Remove(download);
}
private void Remove(IList<DownloadFileInfo> downloads)
{
// Get the selected path
TreeIter iter;
TreePath selectedPath = null;
if (directoryView.Selection.GetSelected(out iter))
{
selectedPath = directoryView.Model.GetPath(iter);
}
handleSelectionChanged = false;
// Remove downloads
foreach (DownloadFileInfo download in downloads)
{
Remove(download);
}
handleSelectionChanged = true;
// Select the next or parent directory
if (selectedPath != null &&
!directoryView.Selection.PathIsSelected(selectedPath))
{
SelectPathOrParent(directoryView, selectedPath);
}
UpdateSensitivity();
}
private void IdleAdd(DownloadFileInfo download)
{
lock (addQueue)
{
addQueue.Add(download);
if (addQueue.Count == 1)
{
GLib.Idle.Add(AddDownloadsInQueue);
}
}
}
private bool AddDownloadsInQueue()
{
IsWorking = true;
actionBox.Sensitive = false;
long ticks = DateTime.Now.Ticks;
while (addQueue.Count > 0)
{
DownloadFileInfo download;
lock (addQueue)
{
if (addQueue.Count == 0)
{
break;
}
download = addQueue[0];
addQueue.Remove(download);
}
Add(download);
// Are we blocking the GUI?
if ((DateTime.Now.Ticks - ticks) >= 500000)
{
return true;
}
}
IsWorking = false;
actionBox.Sensitive = true;
return false;
}
private void UpdateDownloadViewModel(TreePath virtualRoot)
{
this.virtualRoot = virtualRoot;
SortType order = SortType.Ascending;
int sortColumnId = (int)DownloadStore.Column.Name;
TreeModelFilter filterModel;
TreeModelSort sortModel = (TreeModelSort)downloadView.Model;
if (sortModel != null)
{
sortModel.GetSortColumnId(out sortColumnId, out order);
filterModel = (TreeModelFilter)sortModel.Model;
sortModel.Dispose();
filterModel.Dispose();
}
// Create the new models
filterModel = new TreeModelFilter(downloadStore, virtualRoot);
filterModel.VisibleFunc = DownloadVisibleFunc;
sortModel = new TreeModelSort(filterModel);
sortModel.SetSortFunc((int)DownloadStore.Column.Name, SortFunc);
sortModel.SetSortColumnId(sortColumnId, order);
downloadView.Model = sortModel;
UpdateClearButtonSensitivity();
}
private void UpdateSensitivity()
{
UpdateCancelButtonSensitivity();
UpdateClearButtonSensitivity();
}
private void UpdateCancelButtonSensitivity()
{
DownloadFileInfo[] downloads = downloadView.GetSelectedDownloads(true);
foreach (DownloadFileInfo download in downloads)
{
if (downloadManager.Contains(download))
{
cancelButton.Sensitive = true;
return;
}
}
cancelButton.Sensitive = false;
}
private void UpdateClearButtonSensitivity()
{
clearButton.Sensitive = false;
downloadView.Model.Foreach(delegate(TreeModel model,
TreePath path, TreeIter i)
{
DownloadFileInfo download = model.GetValue(i,
(int)DownloadStore.Column.Object) as DownloadFileInfo;
if (download != null && !downloadManager.Contains(download))
{
clearButton.Sensitive = true;
return true;
}
return false;
});
}
private void LoadSourcesInDir(TreeStore store, TreeIter dirIter)
{
TreeIter iter;
if (!store.IterChildren(out iter, dirIter))
{
return;
}
bool loaded = (bool)store.GetValue(dirIter, (int)DownloadStore.Column.Loaded);
if (loaded)
{
return;
}
do
{
DownloadFileInfo download = store.GetValue(iter,
(int)DownloadStore.Column.Object) as DownloadFileInfo;
if (download != null)
{
loaded = (bool)store.GetValue(iter, (int)DownloadStore.Column.Loaded);
if (!loaded)
{
// Add the sources
foreach (SourceInfo source in download.SourceInfos)
{
store.AppendValues(iter, source, source.Nick);
}
store.SetValue(iter, (int)DownloadStore.Column.Loaded, true);
}
}
}
while (store.IterNext(ref iter));
store.SetValue(dirIter, (int)DownloadStore.Column.Loaded, true);
}
private void HandleRemoveRecursive(TreeModel model, params TreeIter[] iters)
{
if (iters.Length == 0)
{
return;
}
List<DownloadFileInfo> aborted = new List<DownloadFileInfo>();
Queue<TreeIter> queue = new Queue<TreeIter>();
foreach (TreeIter i in iters)
{
queue.Enqueue(i);
}
// Iterate through the iters and remove all downloads and sources
TreeIter iter, childIter, parent;
do
{
iter = queue.Dequeue();
object o = model.GetValue(iter, (int)DownloadStore.Column.Object);
DownloadFileInfo download = o as DownloadFileInfo;
SourceInfo source = o as SourceInfo;
if (download != null)
{
if (!aborted.Contains(download))
{
downloadManager.Remove(download);
aborted.Add(download);
}
}
else if (source != null)
{
if (model.IterParent(out parent, iter))
{
download = (DownloadFileInfo)model.GetValue(parent,
(int)DownloadStore.Column.Object);
download.Remove(source);
}
}
else if (model.IterChildren(out childIter, iter))
{
do
{
queue.Enqueue(childIter);
}
while (model.IterNext(ref childIter));
}
}
while (queue.Count > 0);
// Remove aborted downloads from the model
if (aborted.Count > 0)
{
Remove(aborted);
}
}
private void OnVisibleColumns(object obj, EventArgs args)
{
if (columnsWindow == null)
{
columnsWindow = new VisibleColumnsWindow(downloadView.Columns,
Toplevel as Window, downloadView.Columns[0]);
columnsWindow.Window.Destroyed += delegate
{
columnsWindow = null;
};
}
columnsWindow.Show();
}
private void OnDirectoryRemoveSelected(object obj, EventArgs args)
{
TreeIter iter;
if (directoryView.Selection.GetSelected(out iter))
{
TreeModelSort sortModel = (TreeModelSort)directoryView.Model;
TreeModelFilter filterModel = (TreeModelFilter)sortModel.Model;
// Get the downloadStore iter
iter = sortModel.ConvertIterToChildIter(iter);
iter = filterModel.ConvertIterToChildIter(iter);
HandleRemoveRecursive(downloadStore, iter);
}
}
private void OnRemoveSelected(object obj, EventArgs args)
{
HandleRemoveRecursive(downloadView.Model, downloadView.GetSelectedIters());
}
private void OnDirectoryKeyPressEvent(object obj, KeyPressEventArgs args)
{
if (args.Event.Key == Gdk.Key.Delete)
{
OnDirectoryRemoveSelected(obj, EventArgs.Empty);
}
}
private void OnDirectorySelectionChanged(object obj, EventArgs args)
{
if (!handleSelectionChanged)
{
return;
}
TreePath virtualRoot = null;
TreeIter iter;
if (directoryView.Selection.GetSelected(out iter))
{
TreeModelSort sortModel = (TreeModelSort)directoryView.Model;
TreeModelFilter filterModel = (TreeModelFilter)sortModel.Model;
// Get the downloadStore iter
iter = sortModel.ConvertIterToChildIter(iter);
iter = filterModel.ConvertIterToChildIter(iter);
LoadSourcesInDir(downloadStore, iter);
virtualRoot = downloadStore.GetPath(iter);
}
UpdateDownloadViewModel(virtualRoot);
}
private void OnKeyPressEvent(object obj, KeyPressEventArgs args)
{
if (args.Event.Key == Gdk.Key.Delete)
{
OnRemoveSelected(obj, EventArgs.Empty);
}
}
private void OnRowActivated(object obj, RowActivatedArgs args)
{
TreeModelSort sortModel = (TreeModelSort)downloadView.Model;
TreeModelFilter filterModel = (TreeModelFilter)sortModel.Model;
if (!scrolledWindowTree.Visible)
{
return;
}
// Get the path for the downloadStore. The path is null if the
// virtual root has changed.
TreePath path = sortModel.ConvertPathToChildPath(args.Path);
if (path != null)
{
path = filterModel.ConvertPathToChildPath(path);
sortModel = (TreeModelSort)directoryView.Model;
filterModel = (TreeModelFilter)sortModel.Model;
// Get the path for directoryView model. The path is null if
// it's not a directory.
path = filterModel.ConvertChildPathToPath(path);
if (path != null)
{
path = sortModel.ConvertChildPathToPath(path);
directoryView.ExpandToPath(path);
directoryView.Selection.SelectPath(path);
directoryView.ScrollToCell(path, null, false, 0f, 0f);
}
}
}
private void OnRowExpanded(object obj, RowExpandedArgs args)
{
TreeModelSort sortModel = (TreeModelSort)downloadView.Model;
TreeModelFilter filterModel = (TreeModelFilter)sortModel.Model;
TreePath path = sortModel.ConvertPathToChildPath(args.Path);
path = filterModel.ConvertPathToChildPath(path);
TreeIter iter;
if (downloadStore.GetIter(out iter, path))
{
LoadSourcesInDir(downloadStore, iter);
}
}
private void OnSelectionChanged(object obj, EventArgs args)
{
if (!handleSelectionChanged)
{
return;
}
UpdateCancelButtonSensitivity();
}
private void OnDirectoryTreeCheckButtonToggled(object obj, EventArgs args)
{
bool visible = directoryTreeCheckButton.Active;
scrolledWindowTree.Visible = visible;
if (!visible)
{
directoryView.Selection.UnselectAll();
}
}
private void OnSourcesCheckButtonToggled(object obj, EventArgs args)
{
TreeModelSort sortModel = (TreeModelSort)downloadView.Model;
TreeModelFilter filterModel = (TreeModelFilter)sortModel.Model;
showSources = sourcesCheckButton.Active;
filterModel.Refilter();
}
private void OnCancelButtonClicked(object obj, EventArgs args)
{
DownloadFileInfo[] downloads = downloadView.GetSelectedDownloads(true);
foreach (DownloadFileInfo download in downloads)
{
downloadManager.Remove(download);
}
}
private void OnClearButtonClicked(object obj, EventArgs args)
{
List<DownloadFileInfo> aborted = new List<DownloadFileInfo>();
downloadView.Model.Foreach(delegate(TreeModel model,
TreePath path, TreeIter i)
{
DownloadFileInfo download = model.GetValue(i,
(int)DownloadStore.Column.Object) as DownloadFileInfo;
if (download != null && !downloadManager.Contains(download))
{
aborted.Add(download);
}
return false;
});
Remove(aborted);
}
private void SelectPathOrParent(TreeView treeView, TreePath path)
{
do
{
TreeIter iter;
if (treeView.Model.GetIter(out iter, path))
{
treeView.Selection.SelectPath(path);
return;
}
}
while (path.Up() && path.Depth > 0);
treeView.Selection.SelectPath(TreePath.NewFirst());
}
private void OnDownloadAdded(object obj, DownloadEventArgs args)
{
if (!args.Download.IsFileList)
{
IdleAdd(args.Download);
}
}
private void OnDownloadRemoved(object obj, DownloadEventArgs args)
{
DownloadFileInfo download = args.Download;
if (download.IsFileList)
{
return;
}
if (updateId != 0)
{
return;
}
updateId = GLib.Idle.Add(delegate
{
// A redraw is enough since we use CellDataFuncs to render the state.
QueueDraw();
OnPageChanged();
UpdateSensitivity();
updateId = 0;
return false;
});
}
private void OnActiveChanged(object obj, EventArgs args)
{
DownloadFileInfo download = (DownloadFileInfo)obj;
if (download.IsFileList)
{
return;
}
Application.Invoke(delegate
{
QueueDraw();
downloadStore.UpdateDownload(download);
UpdateDirectories();
if (download.Active)
{
if (!activeDownloads.Contains(download))
{
activeDownloads.Add(download);
if (timeout == 0)
{
timeout = GLib.Timeout.Add(1000, OnRefreshTimeout);
}
}
}
else
{
activeDownloads.Remove(download);
}
});
}
private void OnSourceAdded(object obj, SourceEventArgs args)
{
DownloadFileInfo download = (DownloadFileInfo)obj;
SourceInfo source = args.Source;
Application.Invoke(delegate
{
if (!downloadStore.Contains(download))
{
return;
}
TreeIter iter = downloadStore.GetIter(download);
bool loaded = (bool)downloadStore.GetValue(iter, (int)DownloadStore.Column.Loaded);
if (loaded)
{
// Add the source
downloadStore.AppendValues(iter, source, source.Nick);
downloadStore.UpdateDownload(download);
OnPageChanged();
}
});
}
private void OnSourceRemoved(object obj, SourceEventArgs args)
{
DownloadFileInfo download = (DownloadFileInfo)obj;
SourceInfo source = args.Source;
Application.Invoke(delegate
{
// The download might have been removed since this code is
// run in an idle handler. Eg. when selecting a download and
// its sources and pressing delete.
if (!downloadStore.Contains(download))
{
return;
}
TreeIter iter = downloadStore.GetIter(download);
TreeIter sourceIter;
// Ignore selection changes when removing a source.
handleSelectionChanged = false;
// Find and remove the source
if (downloadStore.IterChildren(out sourceIter, iter))
{
do
{
SourceInfo tsource = downloadStore.GetValue(
sourceIter, (int)DownloadStore.Column.Object) as SourceInfo;
if (tsource == source)
{
downloadStore.Remove(ref sourceIter);
break;
}
}
while (downloadStore.IterNext(ref sourceIter));
}
handleSelectionChanged = true;
downloadStore.UpdateDownload(download);
OnPageChanged();
});
}
private bool DirectoryVisibleFunc(TreeModel model, TreeIter iter)
{
// NOTE: This does NOT work. TreeModelFilter does not seem to like
// hidden parents (the downloads) and visible children (the sources).
//
//DownloadFileInfo download = model.GetValue(iter,
// (int)DownloadStore.Column.Object) as DownloadFileInfo;
//
//return download == null;
// obj is null only when the row is a directory
object obj = model.GetValue(iter, (int)DownloadStore.Column.Object);
return obj == null;
}
private bool DownloadVisibleFunc(TreeModel model, TreeIter iter)
{
SourceInfo source = model.GetValue(iter,
(int)DownloadStore.Column.Object) as SourceInfo;
if (source != null)
{
return showSources;
}
return true;
}
private int SortFunc(TreeModel model, TreeIter tia, TreeIter tib)
{
object obja = model.GetValue(tia, (int)DownloadStore.Column.Object);
object objb = model.GetValue(tib, (int)DownloadStore.Column.Object);
if (obja == null && objb is DownloadFileInfo)
{
return -1;
}
if (obja is DownloadFileInfo && objb == null)
{
return 1;
}
string namea = model.GetValue(tia, (int)DownloadStore.Column.Name) as string;
string nameb = model.GetValue(tib, (int)DownloadStore.Column.Name) as string;
return String.Compare(namea, nameb);
}
#region DownloadStore Updating
private bool OnRefreshTimeout()
{
if (activeDownloads.Count > 0)
{
Update();
return true;
}
timeout = 0;
return false;
}
private void Update()
{
foreach (DownloadFileInfo download in activeDownloads)
{
// Only update the downloads below the root
if (virtualRoot != null)
{
TreeIter iter = downloadStore.GetIter(download);
TreePath path = downloadStore.GetPath(iter);
if (path.IsDescendant(virtualRoot))
{
downloadStore.UpdateDownload(download);
}
}
else
{
downloadStore.UpdateDownload(download);
}
}
// Update all the directories
UpdateDirectories();
}
private void UpdateDirectories()
{
TreeIter iter;
if (downloadStore.GetIterFirst(out iter))
{
do
{
UpdateDirectory(iter);
}
while (downloadStore.IterNext(ref iter));
}
}
private void UpdateDirectory(TreeIter iter)
{
TreeIter childIter;
if (downloadStore.IterChildren(out childIter, iter))
{
do
{
UpdateDirectory(childIter);
}
while (downloadStore.IterNext(ref childIter));
downloadStore.UpdateDirectory(iter);
}
}
#endregion
#endregion
}
}
|