|
|
|
@ -3,200 +3,112 @@ 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; |
|
|
|
using System.Reactive.Linq; |
|
|
|
using System.Text.RegularExpressions; |
|
|
|
using System.Runtime.InteropServices; |
|
|
|
using System.Text; |
|
|
|
|
|
|
|
namespace Avalonia.FreeDesktop.Dbus |
|
|
|
{ |
|
|
|
public partial class LinuxMountedVolumeInfoProvider |
|
|
|
internal class LinuxMountedVolumeInfoListener : IDisposable |
|
|
|
{ |
|
|
|
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) |
|
|
|
{ |
|
|
|
_disposables = new CompositeDisposable(); |
|
|
|
this._targetObs = target; |
|
|
|
|
|
|
|
try |
|
|
|
{ |
|
|
|
this._sysDbus = Connection.System; |
|
|
|
this._udisk2Manager = _sysDbus.CreateProxy<IObjectManager>("org.freedesktop.UDisks2", "/org/freedesktop/UDisks2"); |
|
|
|
|
|
|
|
// Test if DBus works.
|
|
|
|
var _ = _udisk2Manager.GetManagedObjectsAsync().Result; |
|
|
|
|
|
|
|
StartDbus(); |
|
|
|
} |
|
|
|
catch (Exception ex) |
|
|
|
{ |
|
|
|
Logging.Logger.Warning("Linux Volume Listener", this, |
|
|
|
"Exception while initializing DBus connection: {0}; {1}" |
|
|
|
, ex.Message, ex.StackTrace); |
|
|
|
StartFallback(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void StartFallback() |
|
|
|
{ |
|
|
|
var pollTimer = Observable.Interval(TimeSpan.FromSeconds(1)) |
|
|
|
.Subscribe(PollFallback); |
|
|
|
|
|
|
|
_disposables.Add(pollTimer); |
|
|
|
|
|
|
|
PollFallback(0); |
|
|
|
} |
|
|
|
private const string DevByLabelDir = "/dev/disk/by-label/"; |
|
|
|
private const string ProcPartitionsDir = "/proc/partitions"; |
|
|
|
private const string ProcMountsDir = "/proc/mounts"; |
|
|
|
|
|
|
|
private void PollFallback(long _) |
|
|
|
{ |
|
|
|
|
|
|
|
} |
|
|
|
[DllImport("libc", SetLastError = true)] |
|
|
|
private static extern long readlink([MarshalAs(UnmanagedType.LPArray)] |
|
|
|
byte[] filename, [MarshalAs(UnmanagedType.LPArray)] byte[] buffer, long buflen); |
|
|
|
|
|
|
|
private async void PollDbus() |
|
|
|
{ |
|
|
|
var newDriveList = new List<MountedVolumeInfo>(); |
|
|
|
|
|
|
|
var fProcMounts = File.ReadAllLines("/proc/mounts"); |
|
|
|
private string ReadLink(string path) |
|
|
|
{ |
|
|
|
var symlink = Encoding.UTF8.GetBytes(path); |
|
|
|
var results = new byte[4095]; |
|
|
|
readlink(symlink, results, results.Length); |
|
|
|
var rawstr = Encoding.UTF8.GetString(results); |
|
|
|
return rawstr.Substring(0, rawstr.IndexOf('\0')); ; |
|
|
|
} |
|
|
|
|
|
|
|
var managedObj = await _udisk2Manager.GetManagedObjectsAsync(); |
|
|
|
private CompositeDisposable _disposables; |
|
|
|
private ObservableCollection<MountedVolumeInfo> _targetObs; |
|
|
|
private bool _beenDisposed = false; |
|
|
|
|
|
|
|
var res_drives = managedObj.Where(x => x.Key.ToString().Contains("/org/freedesktop/UDisks2/drives/")) |
|
|
|
.Select(x => x.Key); |
|
|
|
public LinuxMountedVolumeInfoListener(ref ObservableCollection<MountedVolumeInfo> target) |
|
|
|
{ |
|
|
|
_disposables = new CompositeDisposable(); |
|
|
|
this._targetObs = target; |
|
|
|
|
|
|
|
var res_blockdev = managedObj.Where(x => x.Key.ToString().Contains("/org/freedesktop/UDisks2/block_devices/")) |
|
|
|
.Select(x => x); |
|
|
|
var pollTimer = Observable.Interval(TimeSpan.FromSeconds(2)) |
|
|
|
.Subscribe(Poll); |
|
|
|
|
|
|
|
var res_fs = managedObj.Where(x => x.Key.ToString().Contains("system")) |
|
|
|
.Select(x => x.Key) |
|
|
|
.ToList(); |
|
|
|
_disposables.Add(pollTimer); |
|
|
|
|
|
|
|
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); |
|
|
|
} |
|
|
|
} |
|
|
|
Poll(0); |
|
|
|
} |
|
|
|
|
|
|
|
UpdateCollection(_targetObs, newDriveList); |
|
|
|
} |
|
|
|
private string GetSymlinkTarget(string x) => Path.GetFullPath(Path.Combine(DevByLabelDir, ReadLink(x))); |
|
|
|
|
|
|
|
private async void StartDbus() |
|
|
|
private void Poll(long _) |
|
|
|
{ |
|
|
|
var fProcPartitions = File.ReadAllLines(ProcPartitionsDir) |
|
|
|
.Skip(1) |
|
|
|
.Where(p => !string.IsNullOrEmpty(p)) |
|
|
|
.Select(p => Regex.Replace(p, @"\s{2,}", " ").Trim().Split(' ')) |
|
|
|
.Select(p => (p[2].Trim(), p[3].Trim())) |
|
|
|
.Select(p => (Convert.ToUInt64(p.Item1) * 1024, "/dev/" + p.Item2)); |
|
|
|
|
|
|
|
var fProcMounts = File.ReadAllLines(ProcMountsDir) |
|
|
|
.Select(x => x.Split(' ')) |
|
|
|
.Select(x => (x[0], x[1])); |
|
|
|
|
|
|
|
var labelDirEnum = Directory.Exists(DevByLabelDir) ? |
|
|
|
new DirectoryInfo(DevByLabelDir).GetFiles() : Enumerable.Empty<FileInfo>(); |
|
|
|
|
|
|
|
var labelDevPathPairs = labelDirEnum |
|
|
|
.Select(x => (GetSymlinkTarget(x.FullName), x.Name)); |
|
|
|
|
|
|
|
var q1 = from mount in fProcMounts |
|
|
|
join device in fProcPartitions on mount.Item1 equals device.Item2 |
|
|
|
join label in labelDevPathPairs on device.Item2 equals label.Item1 into labelMatches |
|
|
|
from x in labelMatches.DefaultIfEmpty() |
|
|
|
select new MountedVolumeInfo() |
|
|
|
{ |
|
|
|
VolumePath = mount.Item2, |
|
|
|
VolumeSizeBytes = device.Item1, |
|
|
|
VolumeLabel = x.Name |
|
|
|
}; |
|
|
|
|
|
|
|
var mountVolInfos = q1.ToArray(); |
|
|
|
|
|
|
|
if (_targetObs.SequenceEqual(mountVolInfos)) return; |
|
|
|
else |
|
|
|
{ |
|
|
|
var sub1 = await _udisk2Manager.WatchInterfacesAddedAsync(delegate { PollDbus(); }); |
|
|
|
var sub2 = await _udisk2Manager.WatchInterfacesRemovedAsync(delegate { PollDbus(); }); |
|
|
|
_targetObs.Clear(); |
|
|
|
|
|
|
|
_disposables.Add(sub1); |
|
|
|
_disposables.Add(sub2); |
|
|
|
|
|
|
|
PollDbus(); |
|
|
|
foreach (var i in mountVolInfos) |
|
|
|
_targetObs.Add(i); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
protected virtual void Dispose(bool disposing) |
|
|
|
protected virtual void Dispose(bool disposing) |
|
|
|
{ |
|
|
|
if (!_beenDisposed) |
|
|
|
{ |
|
|
|
if (!disposedValue) |
|
|
|
if (disposing) |
|
|
|
{ |
|
|
|
if (disposing) |
|
|
|
{ |
|
|
|
_disposables.Dispose(); |
|
|
|
_targetObs.Clear(); |
|
|
|
} |
|
|
|
|
|
|
|
disposedValue = true; |
|
|
|
_disposables.Dispose(); |
|
|
|
_targetObs.Clear(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public void Dispose() |
|
|
|
{ |
|
|
|
Dispose(true); |
|
|
|
_beenDisposed = 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++; |
|
|
|
} |
|
|
|
} |
|
|
|
public void Dispose() |
|
|
|
{ |
|
|
|
Dispose(true); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|