2023-01-31 01:40:30 -05:00
|
|
|
import 'package:funkblubber/funkentity.dart';
|
|
|
|
import 'package:funkblubber/console.dart' as console;
|
2023-02-04 02:23:51 -05:00
|
|
|
import 'package:funkblubber/string_utils.dart' as utils;
|
2023-01-31 01:40:30 -05:00
|
|
|
|
|
|
|
enum Action {
|
|
|
|
download,
|
|
|
|
upload,
|
|
|
|
}
|
|
|
|
|
2023-02-04 01:48:31 -05:00
|
|
|
enum ParsingStage {
|
|
|
|
nothing,
|
|
|
|
album,
|
|
|
|
artist,
|
|
|
|
track,
|
|
|
|
path,
|
|
|
|
upload,
|
|
|
|
domain,
|
|
|
|
}
|
|
|
|
|
2023-01-31 01:40:30 -05:00
|
|
|
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<String> 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;
|
2023-01-31 02:11:48 -05:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2023-02-04 01:24:05 -05:00
|
|
|
case ParsingStage.track:
|
|
|
|
final stageResult = _onEntityStage(
|
|
|
|
arg,
|
|
|
|
result,
|
|
|
|
currentStage,
|
|
|
|
FunkEntity.track,
|
|
|
|
);
|
|
|
|
currentStage = stageResult.stage;
|
|
|
|
result = stageResult.result;
|
|
|
|
break;
|
|
|
|
|
2023-01-31 02:11:48 -05:00
|
|
|
case ParsingStage.domain:
|
|
|
|
final stageResult = _onDomainStage(arg, result, currentStage);
|
|
|
|
currentStage = stageResult.stage;
|
|
|
|
result = stageResult.result;
|
|
|
|
break;
|
|
|
|
|
2023-02-01 16:00:23 -05:00
|
|
|
case ParsingStage.path:
|
|
|
|
final stageResult = _onPathStage(arg, result, currentStage);
|
|
|
|
currentStage = stageResult.stage;
|
|
|
|
result = stageResult.result;
|
|
|
|
break;
|
|
|
|
|
2023-01-31 01:40:30 -05:00
|
|
|
default:
|
|
|
|
console.error('not implemented yet');
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-02-01 16:30:06 -05:00
|
|
|
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.
|
|
|
|
|
2023-02-01 16:39:08 -05:00
|
|
|
[-A|--artist ID] Provide with artist ID to download their albums.
|
2023-02-01 16:30:06 -05:00
|
|
|
|
2023-02-04 01:24:05 -05:00
|
|
|
[-t|--track ID] Provide with track ID to download a single track entity.
|
|
|
|
|
2023-02-01 16:30:06 -05:00
|
|
|
[-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);
|
|
|
|
}
|
|
|
|
|
2023-01-31 02:11:48 -05:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2023-02-01 16:00:23 -05:00
|
|
|
StageResult _onPathStage(
|
|
|
|
final String arg,
|
|
|
|
final ParseResult previousResult,
|
|
|
|
final ParsingStage previousStage,
|
|
|
|
) {
|
|
|
|
ParsingStage currentStage = previousStage;
|
|
|
|
ParseResult result = previousResult;
|
|
|
|
|
|
|
|
currentStage = ParsingStage.nothing;
|
|
|
|
result = ParseResult(
|
2023-02-04 02:23:51 -05:00
|
|
|
localPath: utils.cutTrailingDash(arg),
|
2023-02-01 16:00:23 -05:00
|
|
|
action: previousResult.action,
|
|
|
|
success: true,
|
|
|
|
object: previousResult.object,
|
|
|
|
);
|
|
|
|
|
|
|
|
return StageResult(result: result, stage: currentStage);
|
|
|
|
}
|
|
|
|
|
2023-02-04 01:40:05 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-01-31 01:40:30 -05:00
|
|
|
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;
|
2023-02-04 01:24:05 -05:00
|
|
|
case '-t':
|
|
|
|
case '--track':
|
|
|
|
currentStage = ParsingStage.track;
|
|
|
|
break;
|
2023-01-31 01:40:30 -05:00
|
|
|
case '-u':
|
|
|
|
case '--upload':
|
|
|
|
currentStage = ParsingStage.upload;
|
|
|
|
break;
|
|
|
|
case '-p':
|
|
|
|
case '--path':
|
|
|
|
currentStage = ParsingStage.path;
|
|
|
|
break;
|
2023-01-31 02:11:48 -05:00
|
|
|
case '-d':
|
|
|
|
case '--domain':
|
|
|
|
currentStage = ParsingStage.domain;
|
|
|
|
break;
|
2023-02-01 16:30:06 -05:00
|
|
|
case '-h':
|
|
|
|
case '--help':
|
|
|
|
_onHelpStage();
|
|
|
|
currentStage = ParsingStage.nothing;
|
|
|
|
break;
|
2023-01-31 01:40:30 -05:00
|
|
|
default:
|
2023-02-04 01:40:05 -05:00
|
|
|
result = _parseUrl(arg, result);
|
2023-01-31 01:40:30 -05:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return StageResult(result: result, stage: currentStage);
|
|
|
|
}
|