Browse Source

Make the listener active only when there's a dialog present;

Dispose subscription to them when the dialogs are finished
pull/2777/head
Jumar Macato 7 years ago
parent
commit
4cb9e018ac
No known key found for this signature in database GPG Key ID: B19884DAC3A5BF3F
  1. 21
      src/Avalonia.Controls/Platform/IMountedDriveInfoProvider.cs
  2. 23
      src/Avalonia.Controls/Platform/IMountedVolumeInfoProvider.cs
  3. 16
      src/Avalonia.Controls/Platform/MountedDriveInfo.cs
  4. 14
      src/Avalonia.Dialogs/ManagedFileChooserSources.cs
  5. 43
      src/Avalonia.Dialogs/ManagedFileChooserViewModel.cs
  6. 4
      src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs
  7. 167
      src/Avalonia.FreeDesktop/LinuxMountedDriveInfoProvider.cs
  8. 172
      src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs
  9. 16
      src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs
  10. 2
      src/Avalonia.X11/X11Platform.cs

21
src/Avalonia.Controls/Platform/IMountedDriveInfoProvider.cs

@ -1,21 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Avalonia.Platform;
namespace Avalonia.Controls.Platform
{
/// <summary>
/// Defines a platform-specific drive mount information provider implementation.
/// </summary>
public interface IMountedDriveInfoProvider : IDisposable
{
/// <summary>
/// Observable list of currently-mounted drives.
/// </summary>
ObservableCollection<MountedDriveInfo> MountedDrives { get; }
}
}

23
src/Avalonia.Controls/Platform/IMountedVolumeInfoProvider.cs

@ -0,0 +1,23 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Avalonia.Platform;
namespace Avalonia.Controls.Platform
{
/// <summary>
/// Defines a platform-specific mount volumes info provider implementation.
/// </summary>
public interface IMountedVolumeInfoProvider
{
/// <summary>
/// Listens to any changes in volume mounts and
/// forwards updates to the referenced
/// <see cref="ObservableCollection{MountedDriveInfo}"/>.
/// </summary>
IDisposable Listen(ObservableCollection<MountedVolumeInfo> mountedDrives);
}
}

16
src/Avalonia.Controls/Platform/MountedDriveInfo.cs

@ -8,19 +8,19 @@ namespace Avalonia.Controls.Platform
/// <summary>
/// Describes a Drive's properties.
/// </summary>
public class MountedDriveInfo : IEquatable<MountedDriveInfo>
public class MountedVolumeInfo : IEquatable<MountedVolumeInfo>
{
public string DriveLabel { get; set; }
public string DriveName { get; set; }
public ulong DriveSizeBytes { get; set; }
public string VolumeLabel { get; set; }
public string VolumeName { get; set; }
public ulong VolumeSizeBytes { get; set; }
public string DevicePath { get; set; }
public string MountPath { get; set; }
public bool Equals(MountedDriveInfo other)
public bool Equals(MountedVolumeInfo other)
{
return this.DriveLabel.Equals(other.DriveLabel) &&
this.DriveName.Equals(other.DriveName) &&
this.DriveSizeBytes.Equals(other.DriveSizeBytes) &&
return this.VolumeLabel.Equals(other.VolumeLabel) &&
this.VolumeName.Equals(other.VolumeName) &&
this.VolumeSizeBytes.Equals(other.VolumeSizeBytes) &&
this.DevicePath.Equals(other.DevicePath) &&
this.MountPath.Equals(other.MountPath);
}

14
src/Avalonia.Dialogs/ManagedFileChooserSources.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
@ -20,6 +21,7 @@ namespace Avalonia.Dialogs
= DefaultGetAllItems;
public ManagedFileChooserNavigationItem[] GetAllItems() => GetAllItemsDelegate(this);
public static readonly ObservableCollection<MountedVolumeInfo> MountedVolumes = new ObservableCollection<MountedVolumeInfo>();
public static ManagedFileChooserNavigationItem[] DefaultGetAllItems(ManagedFileChooserSources sources)
{
@ -73,11 +75,7 @@ namespace Avalonia.Dialogs
}
else
{
var drivesInfos = AvaloniaLocator.CurrentMutable
.GetService<IMountedDriveInfoProvider>()
.MountedDrives;
return drivesInfos
return MountedVolumes
.Where(x => !x.MountPath.StartsWith("/boot"))
.Select(x =>
{
@ -92,13 +90,13 @@ namespace Avalonia.Dialogs
}
else
{
var dNameEmpty = string.IsNullOrEmpty(x.DriveLabel.Trim());
var dNameEmpty = string.IsNullOrEmpty(x.VolumeLabel.Trim());
return new ManagedFileChooserNavigationItem
{
ItemType = ManagedFileChooserItemType.Volume,
DisplayName = dNameEmpty ? $"{ByteSizeHelper.ToString(x.DriveSizeBytes)} Volume"
: x.DriveLabel,
DisplayName = dNameEmpty ? $"{ByteSizeHelper.ToString(x.VolumeSizeBytes)} Volume"
: x.VolumeLabel,
Path = x.MountPath
};
}

43
src/Avalonia.Dialogs/ManagedFileChooserViewModel.cs

@ -36,7 +36,9 @@ namespace Avalonia.Dialogs
private bool _selectingDirectory;
private bool _savingFile;
private bool _scheduledSelectionValidation;
private bool _alreadyCancelled = false;
private string _defaultExtension;
private CompositeDisposable _disposables;
public string Location
{
@ -95,30 +97,36 @@ namespace Avalonia.Dialogs
}
}
private void RefreshQuickLinks(object _ = null)
private void RefreshQuickLinks(ManagedFileChooserSources quickSources)
{
var quickSources = AvaloniaLocator.Current
.GetService<ManagedFileChooserSources>()
?? new ManagedFileChooserSources();
QuickLinks.Clear();
QuickLinks.AddRange(quickSources.GetAllItems().Select(i => new ManagedFileChooserItemViewModel(i)));
}
public ManagedFileChooserViewModel(FileSystemDialog dialog)
{
var drivesInfoSrv = AvaloniaLocator.Current
.GetService<IMountedDriveInfoProvider>()
.MountedDrives;
_disposables = new CompositeDisposable();
var quickSources = AvaloniaLocator.Current
.GetService<ManagedFileChooserSources>()
?? new ManagedFileChooserSources();
var sub1 = AvaloniaLocator.Current
.GetService<IMountedVolumeInfoProvider>()
.Listen(ManagedFileChooserSources.MountedVolumes);
var sub1 = Observable.FromEventPattern(drivesInfoSrv, nameof(drivesInfoSrv.CollectionChanged))
var sub2 = Observable.FromEventPattern(ManagedFileChooserSources.MountedVolumes,
nameof(ManagedFileChooserSources.MountedVolumes.CollectionChanged))
.ObserveOn(AvaloniaScheduler.Instance)
.Subscribe(RefreshQuickLinks);
.Subscribe(x => RefreshQuickLinks(quickSources));
CompleteRequested += delegate { sub1?.Dispose(); };
CancelRequested += delegate { sub1?.Dispose(); };
_disposables.Add(sub1);
_disposables.Add(sub2);
RefreshQuickLinks();
CompleteRequested += delegate { _disposables?.Dispose(); };
CancelRequested += delegate { _disposables?.Dispose(); };
RefreshQuickLinks(quickSources);
Title = dialog.Title ?? (
dialog is OpenFileDialog ? "Open file"
@ -318,7 +326,14 @@ namespace Avalonia.Dialogs
public void Cancel()
{
CancelRequested?.Invoke();
if (!_alreadyCancelled)
{
// INFO: Don't misplace this check or it might cause
// StackOverflowException because of recursive
// event invokes.
_alreadyCancelled = true;
CancelRequested?.Invoke();
}
}
public void Ok()

4
src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs

@ -23,12 +23,16 @@ namespace Avalonia.Dialogs
DataContext = model
};
dialog.Closed += delegate { model.Cancel(); };
string[] result = null;
model.CompleteRequested += items =>
{
result = items;
dialog.Close();
};
model.CancelRequested += dialog.Close;
await dialog.ShowDialog<object>(parent);

167
src/Avalonia.FreeDesktop/LinuxMountedDriveInfoProvider.cs

@ -1,167 +0,0 @@
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Avalonia.Controls.Platform;
using Tmds.DBus;
namespace Avalonia.FreeDesktop.Dbus
{
public class LinuxMountedDriveInfoProvider : IMountedDriveInfoProvider
{
private IDisposable[] _disposables;
private readonly Connection _sysDbus;
private readonly IObjectManager _udisk2Manager;
public LinuxMountedDriveInfoProvider()
{
this._sysDbus = Connection.System;
this._udisk2Manager = _sysDbus.CreateProxy<IObjectManager>("org.freedesktop.UDisks2", "/org/freedesktop/UDisks2");
Start();
}
async void Start()
{
_disposables = new[] {
await _udisk2Manager.WatchInterfacesAddedAsync(delegate { Poll(); }),
await _udisk2Manager.WatchInterfacesRemovedAsync( delegate { Poll(); })
};
Poll();
}
public ObservableCollection<MountedDriveInfo> MountedDrives { get; } = new ObservableCollection<MountedDriveInfo>();
private async void Poll()
{
var newDriveList = new List<MountedDriveInfo>();
var fProcMounts = File.ReadAllLines("/proc/mounts");
var managedObj = await _udisk2Manager.GetManagedObjectsAsync();
var res_drives = managedObj.Where(x => x.Key.ToString().Contains("/org/freedesktop/UDisks2/drives/"))
.Select(x => x.Key);
var res_blockdev = managedObj.Where(x => x.Key.ToString().Contains("/org/freedesktop/UDisks2/block_devices/"))
.Select(x => x);
var res_fs = managedObj.Where(x => x.Key.ToString().Contains("system"))
.Select(x => x.Key)
.ToList();
foreach (var block in res_blockdev)
{
try
{
var iblock = _sysDbus.CreateProxy<IBlock>("org.freedesktop.UDisks2", block.Key);
var iblockProps = await iblock.GetAllAsync();
var block_drive = await iblock.GetDriveAsync();
if (!res_drives.Contains(block_drive)) continue;
var drive_key = res_drives.Single(x => x == block_drive);
var drives = _sysDbus.CreateProxy<IDrive>("org.freedesktop.UDisks2", drive_key);
var drivesProps = await drives.GetAllAsync();
var devRawBytes = iblockProps.Device.Take(iblockProps.Device.Length - 1).ToArray();
var devPath = System.Text.Encoding.UTF8.GetString(devRawBytes);
var blockLabel = iblockProps.IdLabel;
var blockSize = iblockProps.Size;
var driveName = drivesProps.Id;
// HACK: There should be something in udisks2 to
// get this data but I have no idea where.
var mountPoint = fProcMounts.Select(x => x.Split(' '))
.Where(x => x[0] == devPath)
.Select(x => x[1])
.SingleOrDefault();
if (mountPoint is null) continue;
var k = new MountedDriveInfo()
{
DriveLabel = blockLabel,
DriveName = driveName,
DriveSizeBytes = blockSize,
DevicePath = devPath,
MountPath = mountPoint
};
newDriveList.Add(k);
}
finally
{
}
}
UpdateCollection(newDriveList);
}
// https://stackoverflow.com/questions/19558644/update-an-observablecollection-from-another-collection
private void UpdateCollection(IEnumerable<MountedDriveInfo> newCollection)
{
var newCollectionEnumerator = newCollection.GetEnumerator();
var collectionEnumerator = MountedDrives.GetEnumerator();
var itemsToDelete = new Collection<MountedDriveInfo>();
while (collectionEnumerator.MoveNext())
{
var item = collectionEnumerator.Current;
if (!newCollection.Contains(item))
itemsToDelete.Add(item);
}
foreach (var itemToDelete in itemsToDelete)
{
MountedDrives.Remove(itemToDelete);
}
var i = 0;
while (newCollectionEnumerator.MoveNext())
{
var item = newCollectionEnumerator.Current;
if (!MountedDrives.Contains(item))
{
MountedDrives.Insert(i, item);
}
else
{
int oldIndex = MountedDrives.IndexOf(item);
MountedDrives.Move(oldIndex, i);
}
i++;
}
}
private bool disposedValue = false;
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
foreach (var Disposable in _disposables)
Disposable.Dispose();
MountedDrives?.Clear();
}
disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
}
}
}

172
src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs

@ -0,0 +1,172 @@
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Avalonia.Controls.Platform;
using Tmds.DBus;
using System.Reactive.Disposables;
namespace Avalonia.FreeDesktop.Dbus
{
public partial class LinuxMountedVolumeInfoProvider
{
private class LinuxMountedVolumeInfoListener : IDisposable
{
private CompositeDisposable _disposables;
private readonly Connection _sysDbus;
private readonly IObjectManager _udisk2Manager;
private readonly ObservableCollection<MountedVolumeInfo> _targetObs;
private bool disposedValue = false;
public LinuxMountedVolumeInfoListener(ref ObservableCollection<MountedVolumeInfo> target)
{
this._sysDbus = Connection.System;
this._udisk2Manager = _sysDbus.CreateProxy<IObjectManager>("org.freedesktop.UDisks2", "/org/freedesktop/UDisks2");
this._targetObs = target;
Start();
}
private async void Poll()
{
var newDriveList = new List<MountedVolumeInfo>();
var fProcMounts = File.ReadAllLines("/proc/mounts");
var managedObj = await _udisk2Manager.GetManagedObjectsAsync();
var res_drives = managedObj.Where(x => x.Key.ToString().Contains("/org/freedesktop/UDisks2/drives/"))
.Select(x => x.Key);
var res_blockdev = managedObj.Where(x => x.Key.ToString().Contains("/org/freedesktop/UDisks2/block_devices/"))
.Select(x => x);
var res_fs = managedObj.Where(x => x.Key.ToString().Contains("system"))
.Select(x => x.Key)
.ToList();
foreach (var block in res_blockdev)
{
try
{
var iblock = _sysDbus.CreateProxy<IBlock>("org.freedesktop.UDisks2", block.Key);
var iblockProps = await iblock.GetAllAsync();
var block_drive = await iblock.GetDriveAsync();
if (!res_drives.Contains(block_drive)) continue;
var drive_key = res_drives.Single(x => x == block_drive);
var drives = _sysDbus.CreateProxy<IDrive>("org.freedesktop.UDisks2", drive_key);
var drivesProps = await drives.GetAllAsync();
var devRawBytes = iblockProps.Device.Take(iblockProps.Device.Length - 1).ToArray();
var devPath = System.Text.Encoding.UTF8.GetString(devRawBytes);
var blockLabel = iblockProps.IdLabel;
var blockSize = iblockProps.Size;
var driveName = drivesProps.Id;
// HACK: There should be something in udisks2 to
// get this data but I have no idea where.
var mountPoint = fProcMounts.Select(x => x.Split(' '))
.Where(x => x[0] == devPath)
.Select(x => x[1])
.SingleOrDefault();
if (mountPoint is null) continue;
var k = new MountedVolumeInfo()
{
VolumeLabel = blockLabel,
VolumeName = driveName,
VolumeSizeBytes = blockSize,
DevicePath = devPath,
MountPath = mountPoint
};
newDriveList.Add(k);
}
catch (Exception ex)
{
Logging.Logger.Warning("Linux Volume Listener", this, "Exception while enumerating DBus items: {0}; {1}"
, ex.Message, ex.StackTrace);
}
}
UpdateCollection(_targetObs, newDriveList);
}
private async void Start()
{
_disposables = new CompositeDisposable();
var sub1 = await _udisk2Manager.WatchInterfacesAddedAsync(delegate { Poll(); });
var sub2 = await _udisk2Manager.WatchInterfacesRemovedAsync(delegate { Poll(); });
_disposables.Add(sub1);
_disposables.Add(sub2);
Poll();
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
_disposables.Dispose();
_targetObs.Clear();
}
disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
}
// https://stackoverflow.com/questions/19558644/update-an-observablecollection-from-another-collection
private void UpdateCollection(ObservableCollection<MountedVolumeInfo> target, IEnumerable<MountedVolumeInfo> newCollection)
{
var newCollectionEnumerator = newCollection.GetEnumerator();
var collectionEnumerator = target.GetEnumerator();
var itemsToDelete = new Collection<MountedVolumeInfo>();
while (collectionEnumerator.MoveNext())
{
var item = collectionEnumerator.Current;
if (!newCollection.Contains(item))
itemsToDelete.Add(item);
}
foreach (var itemToDelete in itemsToDelete)
{
target.Remove(itemToDelete);
}
var i = 0;
while (newCollectionEnumerator.MoveNext())
{
var item = newCollectionEnumerator.Current;
if (!target.Contains(item))
{
target.Insert(i, item);
}
else
{
int oldIndex = target.IndexOf(item);
target.Move(oldIndex, i);
}
i++;
}
}
}
}
}

16
src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs

@ -0,0 +1,16 @@
using System;
using System.Collections.ObjectModel;
using Avalonia.Controls.Platform;
namespace Avalonia.FreeDesktop.Dbus
{
public partial class LinuxMountedVolumeInfoProvider : IMountedVolumeInfoProvider
{
public IDisposable Listen(ObservableCollection<MountedVolumeInfo> mountedDrives)
{
Contract.Requires<ArgumentNullException>(mountedDrives != null);
return new LinuxMountedVolumeInfoListener(ref mountedDrives);
}
}
}

2
src/Avalonia.X11/X11Platform.cs

@ -51,7 +51,7 @@ namespace Avalonia.X11
.Bind<IPlatformSettings>().ToConstant(new PlatformSettingsStub())
.Bind<IPlatformIconLoader>().ToConstant(new X11IconLoader(Info))
.Bind<ISystemDialogImpl>().ToConstant(new GtkSystemDialog())
.Bind<IMountedDriveInfoProvider>().ToConstant(new LinuxMountedDriveInfoProvider());
.Bind<IMountedVolumeInfoProvider>().ToConstant(new LinuxMountedVolumeInfoProvider());
X11Screens = Avalonia.X11.X11Screens.Init(this);
Screens = new X11Screens(X11Screens);

Loading…
Cancel
Save