Skip to content

Commit

Permalink
files: Add drive detection support with udisks
Browse files Browse the repository at this point in the history
Very barebones but at least support is there hooray!
  • Loading branch information
HrX03 committed Mar 11, 2023
1 parent 4166d0a commit 210b473
Show file tree
Hide file tree
Showing 7 changed files with 287 additions and 30 deletions.
64 changes: 64 additions & 0 deletions lib/backend/drive_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:udisks/udisks.dart';

class DriveProvider with ChangeNotifier {
final UDisksClient _client = UDisksClient();
late final StreamSubscription _blockDeviceAddSub;
late final StreamSubscription _blockDeviceRemoveSub;
late final StreamSubscription _driveAddSub;
late final StreamSubscription _driveRemoveSub;

final List<UDisksBlockDevice> _blockDevices = [];
final List<UDisksDrive> _drives = [];

List<UDisksBlockDevice> get blockDevices => List.of(_blockDevices);
List<UDisksDrive> get drives => List.of(_drives);

Future<void> init() async {
await _client.connect();

_blockDevices.addAll(_client.blockDevices);
_drives.addAll(_client.drives);

_blockDeviceAddSub = _client.blockDeviceAdded.listen(_onBlockDeviceAdded);
_blockDeviceRemoveSub =
_client.blockDeviceRemoved.listen(_onBlockDeviceRemoved);

_driveAddSub = _client.driveAdded.listen(_onDriveAdded);
_driveRemoveSub = _client.driveRemoved.listen(_onDriveRemoved);
}

@override
Future<void> dispose() async {
await _blockDeviceAddSub.cancel();
await _blockDeviceRemoveSub.cancel();
await _driveAddSub.cancel();
await _driveRemoveSub.cancel();

await _client.close();

super.dispose();
}

void _onBlockDeviceAdded(UDisksBlockDevice event) {
_blockDevices.add(event);
notifyListeners();
}

void _onBlockDeviceRemoved(UDisksBlockDevice event) {
_blockDevices.removeWhere((e) => event.id == e.id);
notifyListeners();
}

void _onDriveAdded(UDisksDrive event) {
_drives.add(event);
notifyListeners();
}

void _onDriveRemoved(UDisksDrive event) {
_drives.removeWhere((e) => event.id == e.id);
notifyListeners();
}
}
7 changes: 7 additions & 0 deletions lib/backend/providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:io';

import 'package:files/backend/database/helper.dart';
import 'package:files/backend/database/model.dart';
import 'package:files/backend/drive_provider.dart';
import 'package:files/backend/folder_provider.dart';
import 'package:files/backend/stat_cache_proxy.dart';
import 'package:path/path.dart' as p;
Expand All @@ -27,12 +28,18 @@ Future<void> initProviders() async {
registerServiceInstance<FolderProvider>(folderProvider);
registerService<EntityStatCacheHelper>(EntityStatCacheHelper.new);
registerService<StatCacheProxy>(StatCacheProxy.new);
registerService<DriveProvider>(
DriveProvider.new,
dispose: (s) => s.dispose(),
);
}

Isar get isar => getService<Isar>();

FolderProvider get folderProvider => getService<FolderProvider>();

DriveProvider get driveProvider => getService<DriveProvider>();

EntityStatCacheHelper get helper => getService<EntityStatCacheHelper>();

StatCacheProxy get cacheProxy => getService<StatCacheProxy>();
1 change: 1 addition & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import 'package:flutter/material.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await initProviders();
await driveProvider.init();

runApp(const Files());
}
Expand Down
136 changes: 136 additions & 0 deletions lib/widgets/drive_list.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import 'dart:async';
import 'dart:convert';

import 'package:files/backend/providers.dart';
import 'package:filesize/filesize.dart';
import 'package:flutter/material.dart';
import 'package:udisks/udisks.dart';

class DriveList extends StatelessWidget {
final ValueChanged<String>? onDriveTap;

const DriveList({this.onDriveTap, super.key});

@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: driveProvider,
builder: (context, _) {
return Column(
children: driveProvider.blockDevices
.where(
(e) =>
!e.userspaceMountOptions.contains("x-gdu.hide") &&
!e.userspaceMountOptions.contains("x-gvfs-hide"),
)
.where((e) => !e.hintIgnore && e.filesystem != null)
.map(
(e) => _DriveTile(
blockDevice: e,
onTap: onDriveTap,
),
)
.toList(),
);
},
);
}
}

class _DriveTile extends StatefulWidget {
final UDisksBlockDevice blockDevice;
final ValueChanged<String>? onTap;

const _DriveTile({
required this.blockDevice,
this.onTap,
});

@override
State<_DriveTile> createState() => _DriveTileState();
}

class _DriveTileState extends State<_DriveTile> {
late Timer _pollingTimer;

late String? mountPoint;

@override
void initState() {
super.initState();
mountPoint = getMountPoint();
_pollingTimer = Timer.periodic(const Duration(milliseconds: 100), _onPoll);
}

@override
void dispose() {
_pollingTimer.cancel();
super.dispose();
}

void _onPoll(Timer ref) {
final String? currentMountPoint = getMountPoint();

if (mountPoint != currentMountPoint) {
mountPoint = currentMountPoint;
setState(() {});
}
}

String? getMountPoint() {
return widget.blockDevice.filesystem!.mountPoints.isNotEmpty
? widget.blockDevice.filesystem!.mountPoints.first.decode()
: null;
}

@override
Widget build(BuildContext context) {
String? mountPoint = widget.blockDevice.filesystem!.mountPoints.isNotEmpty
? widget.blockDevice.filesystem!.mountPoints.first.decode()
: null;

final String? idLabel = widget.blockDevice.idLabel.isNotEmpty
? widget.blockDevice.idLabel
: null;
final String? hintName = widget.blockDevice.hintName.isNotEmpty
? widget.blockDevice.hintName
: null;

return ListTile(
dense: true,
leading: Icon(
widget.blockDevice.drive?.ejectable == true ? Icons.usb : Icons.storage,
size: 20,
),
title: Text(
idLabel ?? hintName ?? "${filesize(widget.blockDevice.size, 1)} drive",
),
subtitle: mountPoint != null ? Text(mountPoint) : null,
trailing: mountPoint != null
? IconButton(
onPressed: () async {
await widget.blockDevice.filesystem!.unmount();
setState(() {});
},
icon: const Icon(Icons.eject),
iconSize: 16,
splashRadius: 16,
)
: null,
onTap: () async {
if (mountPoint == null) {
mountPoint = await widget.blockDevice.filesystem!.mount();
setState(() {});
}

widget.onTap?.call(mountPoint!);
},
);
}
}

extension on List<int> {
String decode() {
return utf8.decode(sublist(0, length - 1));
}
}
74 changes: 44 additions & 30 deletions lib/widgets/side_pane.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:files/backend/folder_provider.dart';
import 'package:files/backend/workspace.dart';
import 'package:files/widgets/context_menu.dart';
import 'package:files/widgets/drive_list.dart';
import 'package:flutter/material.dart';

typedef NewTabCallback = void Function(String);
Expand Down Expand Up @@ -53,40 +54,53 @@ class _SidePaneState extends State<SidePane> {
width: 304,
child: Material(
color: Theme.of(context).colorScheme.surface,
child: ListView.builder(
child: ListView.separated(
padding: const EdgeInsets.only(top: 56),
itemCount: widget.destinations.length,
itemBuilder: (context, index) => ContextMenu(
entries: [
ContextMenuItem(
child: const Text("Open"),
itemCount: widget.destinations.length + 1,
separatorBuilder: (context, index) {
if (index == widget.destinations.length - 1) {
return const Divider();
}

return const SizedBox();
},
itemBuilder: (context, index) {
if (index == widget.destinations.length) {
return DriveList(onDriveTap: widget.workspace.changeCurrentDir);
}

return ContextMenu(
entries: [
ContextMenuItem(
child: const Text("Open"),
onTap: () => widget.workspace
.changeCurrentDir(widget.destinations[index].path),
),
ContextMenuItem(
child: const Text("Open in new tab"),
onTap: () => widget.onNewTab(widget.destinations[index].path),
),
ContextMenuItem(
child: const Text("Open in new window"),
onTap: () {},
enabled: false,
),
],
child: ListTile(
dense: true,
leading: Icon(widget.destinations[index].icon),
selected: widget.workspace.currentDir ==
widget.destinations[index].path,
selectedTileColor:
Theme.of(context).colorScheme.primary.withOpacity(0.1),
title: Text(
widget.destinations[index].label,
),
onTap: () => widget.workspace
.changeCurrentDir(widget.destinations[index].path),
),
ContextMenuItem(
child: const Text("Open in new tab"),
onTap: () => widget.onNewTab(widget.destinations[index].path),
),
ContextMenuItem(
child: const Text("Open in new window"),
onTap: () {},
enabled: false,
),
],
child: ListTile(
dense: true,
leading: Icon(widget.destinations[index].icon),
selected: widget.workspace.currentDir ==
widget.destinations[index].path,
selectedTileColor:
Theme.of(context).colorScheme.primary.withOpacity(0.1),
title: Text(
widget.destinations[index].label,
),
onTap: () => widget.workspace
.changeCurrentDir(widget.destinations[index].path),
),
),
);
},
),
),
);
Expand Down
33 changes: 33 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
dbus:
dependency: transitive
description:
name: dbus
sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263"
url: "https://pub.dev"
source: hosted
version: "0.7.8"
ffi:
dependency: "direct main"
description:
Expand Down Expand Up @@ -443,6 +451,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: a9346a3fbba7546a28374bdbcd7f54ea48bb47772bf3a7ab4bfaadc40bc8b8c6
url: "https://pub.dev"
source: hosted
version: "5.3.0"
platform:
dependency: transitive
description:
Expand Down Expand Up @@ -616,6 +632,15 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.2.0"
udisks:
dependency: "direct main"
description:
path: "."
ref: HEAD
resolved-ref: "300a7827d60b2f2069c36913b5b39d886a3a2dbe"
url: "https://github.com/HrX03/udisks.dart"
source: git
version: "0.4.0"
url_launcher:
dependency: "direct main"
description:
Expand Down Expand Up @@ -729,6 +754,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.0"
xml:
dependency: transitive
description:
name: xml
sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5"
url: "https://pub.dev"
source: hosted
version: "6.2.2"
xxh3:
dependency: transitive
description:
Expand Down
2 changes: 2 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ dependencies:

recase: ^4.1.0
ubuntu_service: ^0.2.0
udisks:
git: https://github.com/HrX03/udisks.dart
url_launcher: any
windows_path_provider:
git:
Expand Down

0 comments on commit 210b473

Please sign in to comment.