import 'dart:io'; import 'package:uuid/uuid.dart'; import '../../../about/internal/application/DeploymentRecord.dart'; import '../../../about/internal/application/ServerProfile.dart'; import '../../../about/internal/application/ServiceConfig.dart'; import '../../../about/internal/application/StepTracker.dart'; import '../../../comms/Comms.dart' show DeployResult, LogLine; import '../../../utils/CommonUtils.dart'; import '../../parent/ParentViewModel.dart'; import 'ConnectBatchDeploy.dart'; class ViewBatchDeploy extends ParentViewModel { ConnectBatchDeploy connection; ViewBatchDeploy(super.context, this.connection); bool _cancelRequested = false; Stream get logs => getDataManager().logs; Stream get buildLogs => getDataManager().buildLogs; Stream> get steps => getDataManager().stepStream; Stream> get buildSteps => getDataManager().buildStepStream; /// Builds (and optionally deploys) each service in [batch], in order. One /// service failing doesn't stop the rest — the run reports per-service. void runBatch( ServerProfile server, List batch, { required bool deployAfterBuild, }) async { if (deployAfterBuild && !await canReach( server, () => runBatch(server, batch, deployAfterBuild: deployAfterBuild), )) { return; } _cancelRequested = false; connection.onBatchStarted(batch.map((s) => s.id).toList()); for (final service in batch) { if (_cancelRequested) { connection.onServiceState(service.id, BatchState.skipped); continue; } await _runOne(server, service, deployAfterBuild); } connection.onBatchFinished(); } Future _runOne( ServerProfile server, ServiceConfig service, bool deployAfterBuild, ) async { connection.onServiceState(service.id, BatchState.building); final built = service.isWeb ? await getDataManager().buildWeb(service) : await getDataManager().build(service); if (!built.ok) { connection.onServiceState( service.id, BatchState.failed, detail: built.message, ); return; } if (!deployAfterBuild) { connection.onServiceState(service.id, BatchState.done, detail: 'Built'); return; } connection.onServiceState(service.id, BatchState.deploying); DeployResult result; String artifact; try { if (service.isWeb) { final directory = built.dir!.path; if (!File('$directory/index.html').existsSync()) { connection.onServiceState( service.id, BatchState.failed, detail: 'No index.html in build output.', ); return; } final (bytes, count) = await getDataManager().packageWeb(directory); artifact = service.framework.label; result = await getDataManager().deployWeb( server: server, service: service, archiveBytes: bytes, fileCount: count, ); } else { final jar = built.jar!; artifact = baseName(jar.path); result = await getDataManager().deploy( server: server, service: service, jarBytes: await jar.readAsBytes(), jarLabel: artifact, ); } } catch (error) { connection.onServiceState( service.id, BatchState.failed, detail: 'Error: $error', ); return; } await _record(server, service, result, artifact); connection.onServiceState( service.id, result.ok ? BatchState.done : BatchState.failed, detail: result.message, ); } /// Stops after the service currently running finishes. void requestCancel() { _cancelRequested = true; getDataManager().requestCancel(); getDataManager().cancelBuild(); } /// Remembers the order services should be deployed in next time. void saveDeployOrder(ServerProfile server, List ids) async { try { final servers = await getDataManager().getServers(); final index = servers.indexWhere((s) => s.id == server.id); if (index < 0) return; servers[index].deployOrder = ids; await getDataManager().setServers(servers); } catch (error) { handleError(error, () => saveDeployOrder(server, ids), () {}, "Retry"); } } Future _record( ServerProfile server, ServiceConfig service, DeployResult result, String artifact, ) async { try { await getDataManager().addDeployment( DeploymentRecord( id: const Uuid().v4(), serverId: server.id, serverName: server.name, serviceId: service.id, serviceLabel: service.name, jarName: artifact, remotePath: service.isWeb ? service.remoteRoot : service.remotePath(artifact), time: DateTime.now(), status: result.ok ? DeployStatus.success : DeployStatus.failure, message: result.message, ), ); } catch (_) { // A missing history row must not change what the batch reports. } } }