Skip to content

Instantly share code, notes, and snippets.

@wispborne
Last active September 6, 2024 04:31
Show Gist options
  • Save wispborne/ee751a8626311481796353ade796a454 to your computer and use it in GitHub Desktop.
Save wispborne/ee751a8626311481796353ade796a454 to your computer and use it in GitHub Desktop.
// Example Runner
try {
await replaceSelf(directoryWithNewVersionFiles);
} catch (error) {
Fimber.w('Error self-updating something. YOLOing.', ex: error);
}
if (currentPlatform == TargetPlatform.windows) {
await Process.start(
'cmd',
['/c', "start", "", Platform.resolvedExecutable],
runInShell: true,
mode: ProcessStartMode.detached,
);
} else if (currentPlatform == TargetPlatform.linux) {
await Process.start(
'nohup',
[Platform.resolvedExecutable],
runInShell: true,
mode: ProcessStartMode.detached,
);
} else if (currentPlatform == TargetPlatform.macOS) {
// Doesn't work!
await Process.start(
'open',
['-n', currentMacOSAppPath.path],
runInShell: true,
mode: ProcessStartMode.detached,
);
}
if (exitSelfAfter) {
await Future.delayed(const Duration(milliseconds: 500));
Fimber.i(
'Exiting old version of self, new should have already started.');
exit(0);
}
/// Replaces all files in the current working directory with files that have the same relative path
/// in the given source directory.
Future<void> replaceSelf(Directory sourceDirectory) async {
final allNewFiles =
sourceDirectory.listSync(recursive: true, followLinks: true);
final currentDir = currentPlatform != TargetPlatform.macOS
? currentDirectory
: currentMacOSAppPath;
final jobs = <Future<void>>[];
for (final newFile in allNewFiles) {
if (newFile.isFile()) {
final newFileRelative =
newFile.toFile().relativeTo(sourceDirectory).toFile();
final fileToReplace =
File(p.join(currentDir.path, newFileRelative.path));
jobs.add(updateLockedFileInPlace(
newFile.toFile(), fileToReplace, oldFileSuffix));
}
}
await Future.wait(jobs);
}
/// Updates or replaces a locked file in place.
///
/// Depending on the destination file's existence and type:
/// - If the file doesn't exist, it copies the source file to the destination.
/// - If it's a `.so` file, it replaces the destination's contents with the source's contents.
/// - Otherwise, it renames the destination file by appending the given suffix and then copies the source file to the destination.
///
/// Parameters:
/// - [sourceFile]: The file to copy or use for content replacement.
/// - [destFile]: The target file to update or replace.
/// - [oldFileSuffix]: Suffix for renaming the existing file.
///
/// Returns:
/// - A [Future] that completes when the operation is done.
Future<void> updateLockedFileInPlace(
File sourceFile,
File destFile,
String oldFileSuffix,
) async {
final sourceExt = sourceFile.extension;
final doesDestExist = destFile.existsSync();
// Create parent directories if they don't exist.
if (!doesDestExist && !destFile.parent.existsSync()) {
destFile.parent.createSync(recursive: true);
}
if (!doesDestExist) {
// Nothing to replace, just copy the file.
Fimber.d("Copying new file: ${sourceFile.path} to ${destFile.path}");
await sourceFile.copy(destFile.path);
} else if (currentPlatform == TargetPlatform.windows &&
sourceExt == ".so") {
// Can't rename .so files on Windows, but we can replace their content.
Fimber.d(
"Replacing contents of .so file: ${destFile.path} with that of ${sourceFile.path}");
await destFile.writeAsBytes(await sourceFile.readAsBytes());
} else {
// Can't replace content of other locked files, but can rename them.
// Can always rename on Linux.
var oldFile = File(destFile.path + oldFileSuffix);
Fimber.d("Renaming locked file: ${destFile.path} to ${oldFile.path}, "
"and copying new file: ${sourceFile.path} to ${destFile.path}");
if (oldFile.existsSync()) {
if (await oldFile.isWritable()) {
Fimber.d("Old file already exists, deleting: ${oldFile.path}");
oldFile.deleteSync();
}
}
await destFile.rename(oldFile.path);
await sourceFile.copy(destFile.path);
}
}
static Future<void> cleanUpOldUpdateFiles() async {
final filesInCurrentDir = currentDirectory
.listSync(recursive: true)
.where((element) => element.path.endsWith(oldFileSuffix))
.toList();
for (final file in filesInCurrentDir) {
if (file is File) {
try {
await file.delete();
} catch (error) {
Fimber.w('Error deleting old file: ${file.path}', ex: error);
}
}
}
Fimber.i('Cleaned up ${filesInCurrentDir.length} old update files.');
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment