Skip to content

Commit

Permalink
Bump v0.2
Browse files Browse the repository at this point in the history
  • Loading branch information
bostrot committed Sep 18, 2021
1 parent 787cda1 commit 8e5572c
Show file tree
Hide file tree
Showing 10 changed files with 344 additions and 111 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.tar
*.tar
Ubuntu*

# Miscellaneous
Expand Down
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ A quick way to manage your WSL2 instances with a GUI.

![image](https://user-images.githubusercontent.com/7342321/133839228-42ae7d5f-e0d6-45c6-9d41-fc787ad714fb.png)

## Build

Enable Flutter Desktop `flutter config --enable-windows-desktop`

https://flutter.dev/desktop

## Why

WSL2 is great. It makes it very simple to spin up new workplaces with different systems for the project you need or just testing.
Expand All @@ -25,9 +31,10 @@ Fairly simple. Download the latest release from the releases Page and start wsl2
- [x] Copy WSL
- [x] Delete WSL
- [x] Start WSL
- [ ] Rename
- [ ] Create
- [ ] Download
- [X] Rename WSL
- [X] Create WSL
- [X] Download WSL
- [X] Select rootfs from storage

## FAQ

Expand Down
36 changes: 33 additions & 3 deletions lib/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ class WSLApi {
}

// Start a WSL distro by name
Future<String> copy(String distribution) async {
Future<String> copy(String distribution, String newName, {String location = ''}) async {
if (location == '') {
location = distribution + '.tar';
}
String exportRes = await export(distribution, distribution + '.tar');
String importRes = await import(distribution + '-copy',
'./' + distribution + '-copy', distribution + '.tar');
String importRes = await import(newName,
'./' + newName, distribution + '.tar');
return exportRes + ' ' + importRes;
}

Expand All @@ -30,6 +33,13 @@ class WSLApi {
return results.stdout;
}

// Install a WSL distro by name
Future<String> install(String distribution) async {
ProcessResult results =
await Process.run('wsl', ['--install', '-d', distribution]);
return results.stdout;
}

// Import a WSL distro by name
Future<String> import(
String distribution, String installLocation, String location) async {
Expand All @@ -53,6 +63,26 @@ class WSLApi {
return list;
}

// Returns list of downloadable WSL distros
Future<List<String>> getDownloadable() async {
ProcessResult results =
await Process.run('wsl', ['--list', '--online'], stdoutEncoding: null);
String output = utf8Convert(results.stdout);
List<String> list = [];
bool nameStarted = false;
output.split('\n').forEach((line) {
// Filter out docker data
if (line != '' && nameStarted) {
list.add(line.split(' ')[0]);
}
// List started
if (line.startsWith('NAME')) {
nameStarted = true;
}
});
return list;
}

// Convert bytes to human readable string while removing non-ascii characters
String utf8Convert(List<int> bytes) {
List<int> utf8Lines = List<int>.from(bytes);
Expand Down
124 changes: 124 additions & 0 deletions lib/distro_create_component.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import 'api.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:file_picker/file_picker.dart';

Widget createComponent(WSLApi api, statusMsg(msg)) {
final autoSuggestBox = TextEditingController();
final locationController = TextEditingController();
final nameController = TextEditingController();
final items = ['Debian', 'Ubuntu'];
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
child: TextBox(
controller: nameController,
placeholder: 'Name',
suffix: IconButton(
icon: const Icon(FluentIcons.close, size: 15.0),
onPressed: () {},
),
),
),
Expanded(
child: FutureBuilder < List < String >> (
future: api.getDownloadable(),
builder: (context, snapshot) {
if (snapshot.hasData) {
List < String > list = snapshot.data ?? [];
return AutoSuggestBox < String > (
controller: autoSuggestBox,
items: list,
onSelected: (text) {
print(text);
},
textBoxBuilder: (context, controller, focusNode, key) {
return TextBox(
key: key,
controller: controller,
focusNode: focusNode,
suffix: Row(
children: [IconButton(
icon: const Icon(FluentIcons.close, size: 15.0),
onPressed: () {
controller.clear();
focusNode.unfocus();
},
),
IconButton(
icon: const Icon(FluentIcons.open_folder_horizontal,
size: 15.0),
onPressed: () async {
FilePickerResult ? result =
await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['*'],
);

if (result != null) {
controller.text = result.files.single.path!;
} else {
// User canceled the picker
}
},
),
]
),
placeholder: 'Distro',
);
},
);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
// By default, show a loading spinner.
return const Center(child: ProgressRing());
}
)),
Expanded(
child: TextBox(
controller: locationController,
placeholder: 'Save location',
suffix: IconButton(
icon: const Icon(FluentIcons.open_folder_horizontal,
size: 15.0),
onPressed: () async {
String ? path =
await FilePicker.platform.getDirectoryPath();
if (path != null) {
locationController.text = path;
} else {
// User canceled the picker
}
},
),
),
),
Button(
onPressed: () async {
List<String> downloadable = await api.getDownloadable();
if (downloadable.contains(autoSuggestBox.text)) {
// Get distro from internet
// Install distro
statusMsg('Downloading ${autoSuggestBox.text}. This might take a while...');
await api.install(autoSuggestBox.text);
// Copy installed to name
statusMsg('Creating ${nameController.text}. This might take a while...');
await api.copy(autoSuggestBox.text, nameController.text, location: locationController.text);
statusMsg('DONE: Created ${nameController.text}.');
} else {
// Get distro from local storage
// Copy local storage to name
statusMsg('Creating ${nameController.text}. This might take a while...');
await api.import(nameController.text, locationController.text, autoSuggestBox.text,);
statusMsg('DONE: Created ${nameController.text}.');
}
},
child: const Padding(
padding: EdgeInsets.all(6.0),
child: Text('Create'),
),
),
],
);
}
95 changes: 87 additions & 8 deletions lib/distro_list_component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ FutureBuilder<List<String>> distroList(WSLApi api, statusMsg(msg)) {
List list = snapshot.data ?? [];
for (String item in list) {
newList.add(Padding(
padding: const EdgeInsets.all(8.0),
padding: const EdgeInsets.only(top:8.0),
child: Container(
color: const Color.fromRGBO(0, 0, 0, 0.1),
child: ListTile(
Expand All @@ -28,16 +28,14 @@ FutureBuilder<List<String>> distroList(WSLApi api, statusMsg(msg)) {
icon: const Icon(FluentIcons.copy),
onPressed: () async {
print('pushed copy ' + item);
statusMsg(
'Copying ' + item + '. This might take a while...');
String result = await api.copy(item);
statusMsg('DONE: Copying ' + item + '.');
copyDialog(context, item, api, statusMsg);
},
),
IconButton(
icon: const Icon(FluentIcons.rename),
onPressed: () {
print('pushed rename ' + item);
renameDialog(context, item, api, statusMsg);
},
),
IconButton(
Expand All @@ -62,7 +60,7 @@ FutureBuilder<List<String>> distroList(WSLApi api, statusMsg(msg)) {
}

// By default, show a loading spinner.
return const ProgressRing();
return const Center(child: ProgressRing());
},
);
}
Expand All @@ -72,7 +70,7 @@ deleteDialog(context, item, api, statusMsg(msg)) {
context: context,
builder: (context) {
return ContentDialog(
title: Text('Delete ' + item + ' permanently?'),
title: Text('Delete $item permanently?'),
content: const Text(
'If you delete this Distro you won\'t be able to recover it. Do you want to delete it?'),
actions: [
Expand All @@ -85,7 +83,7 @@ deleteDialog(context, item, api, statusMsg(msg)) {
onPressed: () {
Navigator.pop(context);
api.remove(item);
statusMsg('DONE: Deleted ' + item + '.');
statusMsg('DONE: Deleted $item.');
}),
Button(
child: const Text('Cancel'),
Expand All @@ -97,3 +95,84 @@ deleteDialog(context, item, api, statusMsg(msg)) {
},
);
}

final renameController = TextEditingController();
renameDialog(context, item, api, statusMsg(msg)) {
showDialog(
context: context,
builder: (context) {
return ContentDialog(
title: Text('Rename $item'),
content: Column(
children: [
const Text('Warning: Renaming will recreate the whole WSL2 instance.'),
TextBox(
controller: renameController,
placeholder: item,
),
],
),
actions: [
Button(
child: const Text('Rename'),
style: ButtonStyle(
backgroundColor: ButtonState.all(Colors.blue),
foregroundColor: ButtonState.all(Colors.white),
),
onPressed: () async {
Navigator.pop(context);
statusMsg('Renaming $item to ${renameController.text}. This might take a while...');
await api.copy(item, renameController.text);
await api.remove(item);
statusMsg('DONE: Renamed $item to ${renameController.text}.');
}),
Button(
child: const Text('Cancel'),
onPressed: () {
Navigator.pop(context);
}),
],
);
},
);
}

final copyController = TextEditingController();
copyDialog(context, item, api, statusMsg(msg)) {
showDialog(
context: context,
builder: (context) {
return ContentDialog(
title: Text('Copy $item'),
content: Column(
children: [
Text('Copy the WSL instance $item.'),
TextBox(
controller: copyController,
placeholder: item,
),
],
),
actions: [
Button(
child: const Text('Copy'),
style: ButtonStyle(
backgroundColor: ButtonState.all(Colors.blue),
foregroundColor: ButtonState.all(Colors.white),
),
onPressed: () async {
Navigator.pop(context);
statusMsg('Copying $item. This might take a while...');
await api.copy(item, copyController.text);
statusMsg('DONE: Copied $item to ${copyController.text}.');
}),
Button(
child: const Text('Cancel'),
onPressed: () {
Navigator.pop(context);
}),
],
);
},
);
}
Loading

0 comments on commit 8e5572c

Please sign in to comment.