import 'package:funkblubber/funkentity.dart'; import 'package:funkblubber/console.dart' as console; import 'package:funkblubber/parsing/parsing_stage.dart'; enum Action { download, upload, } class StageResult { StageResult({required this.result, required this.stage}); final ParseResult result; final ParsingStage stage; } class ParseResult { ParseResult({ required this.success, required this.action, this.object, this.localPath, }); final FunkObject? object; final String? localPath; final Action action; final bool success; } ParseResult extract(final List args) { ParseResult result = ParseResult( success: false, action: Action.download, ); if (args.isEmpty) { console.error('no arguments provided'); return result; } ParsingStage currentStage = ParsingStage.nothing; for (final String arg in args) { switch (currentStage) { case ParsingStage.nothing: final stageResult = _onNothingStage(arg, result, currentStage); currentStage = stageResult.stage; result = stageResult.result; break; case ParsingStage.album: final stageResult = _onEntityStage( arg, result, currentStage, FunkEntity.album, ); currentStage = stageResult.stage; result = stageResult.result; break; case ParsingStage.artist: final stageResult = _onEntityStage( arg, result, currentStage, FunkEntity.artist, ); currentStage = stageResult.stage; result = stageResult.result; break; case ParsingStage.track: final stageResult = _onEntityStage( arg, result, currentStage, FunkEntity.track, ); currentStage = stageResult.stage; result = stageResult.result; break; case ParsingStage.domain: final stageResult = _onDomainStage(arg, result, currentStage); currentStage = stageResult.stage; result = stageResult.result; break; case ParsingStage.path: final stageResult = _onPathStage(arg, result, currentStage); currentStage = stageResult.stage; result = stageResult.result; break; default: console.error('not implemented yet'); break; } } return result; } void _onHelpStage() { final String help = ''' usage: funkblubber [[-OPTIONS|URL]...] A simple CLI for interaction with your Funkwhale account. Options: [-a|--album ID] Provide with album ID to download it. [-A|--artist ID] Provide with artist ID to download their albums. [-t|--track ID] Provide with track ID to download a single track entity. [-p|--path PATH] Provide to explicitly define download directory. Assumed '.'. [-d|--domain DOMAIN] Provide to explicitly define the host where your Funkwhale instance is. Required with -a, -A, and -u. Is not required if you download by a full URL. Examples: funkblubber https://my.funkwhale/library/tracks/10244/ Download album by URL to current folder. funkblubber https://my.funkwhale/library/tracks/10244/ -p music Download album by URL to local ./music folder. funkblubber -A 152 -d my.funkwhale -p media/music/ Download everything by artist with ID 152 from a funkwhale instance with my.funkwhale domain and save it to local ./media/music folder. funkblubber --album 1423 -d my.funkwhale Download album with ID 1423 from a funkwhale instance with my.funkwhale domain and save it to current folder. '''; console.info(help); } StageResult _onEntityStage( final String arg, final ParseResult previousResult, final ParsingStage previousStage, final FunkEntity kind, ) { ParsingStage currentStage = previousStage; ParseResult result = previousResult; if (int.tryParse(arg) != null) { currentStage = ParsingStage.nothing; result = ParseResult( action: previousResult.action, success: true, object: FunkObject( domain: previousResult.object?.domain ?? '', id: arg, kind: kind, ), ); } return StageResult(result: result, stage: currentStage); } StageResult _onDomainStage( final String arg, final ParseResult previousResult, final ParsingStage previousStage, ) { ParsingStage currentStage = previousStage; ParseResult result = previousResult; try { final Uri uri = Uri.parse(arg); currentStage = ParsingStage.nothing; result = ParseResult( action: previousResult.action, success: true, object: FunkObject( domain: uri.toString(), id: previousResult.object?.id ?? '', kind: previousResult.object?.kind ?? FunkEntity.album, ), ); } catch (e) { console.error(e.toString()); } return StageResult(result: result, stage: currentStage); } String _cutTrailingDash(final String path) => path.endsWith('/') ? path.substring(0, path.length - 1) : path; StageResult _onPathStage( final String arg, final ParseResult previousResult, final ParsingStage previousStage, ) { ParsingStage currentStage = previousStage; ParseResult result = previousResult; currentStage = ParsingStage.nothing; result = ParseResult( localPath: _cutTrailingDash(arg), action: previousResult.action, success: true, object: previousResult.object, ); return StageResult(result: result, stage: currentStage); } ParseResult _makeParseResultFromEntityInfo( final FunkEntity kind, final String host, final String id, ) => ParseResult( action: Action.download, success: true, object: FunkObject( kind: kind, id: id, domain: host, ), ); ParseResult _parseUrl(final String url, final ParseResult previousResult) { ParseResult result = previousResult; try { final Uri uri = Uri.parse(url); final segments = uri.pathSegments; for (int i = 0; i < segments.length; ++i) { switch (segments[i]) { case 'artists': result = _makeParseResultFromEntityInfo( FunkEntity.artist, uri.host, segments[i + 1], ); ++i; break; case 'tracks': result = _makeParseResultFromEntityInfo( FunkEntity.track, uri.host, segments[i + 1], ); ++i; break; case 'albums': result = _makeParseResultFromEntityInfo( FunkEntity.album, uri.host, segments[i + 1], ); ++i; break; } } } catch (e) { console.error(e.toString()); } return result; } StageResult _onNothingStage( final String arg, final ParseResult previousResult, final ParsingStage previousStage, ) { ParsingStage currentStage = previousStage; ParseResult result = previousResult; switch (arg) { case '-A': case '--artist': currentStage = ParsingStage.artist; break; case '-a': case '--album': currentStage = ParsingStage.album; break; case '-t': case '--track': currentStage = ParsingStage.track; break; case '-u': case '--upload': currentStage = ParsingStage.upload; break; case '-p': case '--path': currentStage = ParsingStage.path; break; case '-d': case '--domain': currentStage = ParsingStage.domain; break; case '-h': case '--help': _onHelpStage(); currentStage = ParsingStage.nothing; break; default: result = _parseUrl(arg, result); break; } return StageResult(result: result, stage: currentStage); }