feat:yoyo-getfastmirror(dart)
yoyo-getfastmirror的dart版本,易扩展
This commit is contained in:
11
yoyo-getfastmirror/.github/dependabot.yaml
vendored
Normal file
11
yoyo-getfastmirror/.github/dependabot.yaml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
version: 2
|
||||||
|
enable-beta-ecosystems: true
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
- package-ecosystem: "pub"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
47
yoyo-getfastmirror/.github/workflows/yoyo_get_fast_mirror.yaml
vendored
Normal file
47
yoyo-getfastmirror/.github/workflows/yoyo_get_fast_mirror.yaml
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
name: yoyo_get_fast_mirror
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/yoyo_get_fast_mirror.yaml"
|
||||||
|
- "lib/**"
|
||||||
|
- "test/**"
|
||||||
|
- "pubspec.yaml"
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/yoyo_get_fast_mirror.yaml"
|
||||||
|
- "lib/**"
|
||||||
|
- "test/**"
|
||||||
|
- "pubspec.yaml"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
semantic-pull-request:
|
||||||
|
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/semantic_pull_request.yml@v1
|
||||||
|
|
||||||
|
build:
|
||||||
|
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/dart_package.yml@v1
|
||||||
|
|
||||||
|
|
||||||
|
verify-version:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: 📚 Git Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: 🎯 Setup Dart
|
||||||
|
uses: dart-lang/setup-dart@v1
|
||||||
|
with:
|
||||||
|
sdk: "stable"
|
||||||
|
|
||||||
|
- name: 📦 Install Dependencies
|
||||||
|
run: |
|
||||||
|
dart pub get
|
||||||
|
|
||||||
|
- name: 🔎 Verify version
|
||||||
|
run: dart run test --run-skipped -t version-verify
|
||||||
16
yoyo-getfastmirror/.gitignore
vendored
Normal file
16
yoyo-getfastmirror/.gitignore
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# See https://www.dartlang.org/guides/libraries/private-files
|
||||||
|
|
||||||
|
# Files and directories created by pub
|
||||||
|
.dart_tool/
|
||||||
|
.packages
|
||||||
|
build/
|
||||||
|
pubspec.lock
|
||||||
|
|
||||||
|
# Files generated during tests
|
||||||
|
.test_coverage.dart
|
||||||
|
coverage/
|
||||||
|
.test_runner.dart
|
||||||
|
release/yoyo*
|
||||||
|
|
||||||
|
# Android studio and IntelliJ
|
||||||
|
.idea
|
||||||
29
yoyo-getfastmirror/README.md
Normal file
29
yoyo-getfastmirror/README.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
## yoyo-getfastmirror
|
||||||
|
|
||||||
|
[![License: MIT][license_badge]][license_link]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage 🚀
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# getfastmirror command
|
||||||
|
$ yoyo-getfastmirror getfastmirror
|
||||||
|
|
||||||
|
# Sample command optioon
|
||||||
|
$ yoyo-getfastmirror getfastmirror --only-test // yoyo-getfastmirror getfastmirror -o
|
||||||
|
|
||||||
|
# Show CLI version
|
||||||
|
$ yoyo-getfastmirror --version
|
||||||
|
|
||||||
|
# Show usage help
|
||||||
|
$ yoyo-getfastmirror --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ ./release.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
4
yoyo-getfastmirror/analysis_options.yaml
Normal file
4
yoyo-getfastmirror/analysis_options.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
include: package:very_good_analysis/analysis_options.3.1.0.yaml
|
||||||
|
linter:
|
||||||
|
rules:
|
||||||
|
public_member_api_docs: false
|
||||||
18
yoyo-getfastmirror/bin/yoyo_get_fast_mirror.dart
Normal file
18
yoyo-getfastmirror/bin/yoyo_get_fast_mirror.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:yoyo_get_fast_mirror/src/command_runner.dart';
|
||||||
|
|
||||||
|
Future<void> main(List<String> args) async {
|
||||||
|
await _flushThenExit(await YoyoGetFastMirrorCommandRunner().run(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Flushes the stdout and stderr streams, then exits the program with the given
|
||||||
|
/// status code.
|
||||||
|
///
|
||||||
|
/// This returns a Future that will never complete, since the program will have
|
||||||
|
/// exited already. This is useful to prevent Future chains from proceeding
|
||||||
|
/// after you've decided to exit.
|
||||||
|
Future<void> _flushThenExit(int status) {
|
||||||
|
return Future.wait<void>([stdout.close(), stderr.close()])
|
||||||
|
.then<void>((_) => exit(status));
|
||||||
|
}
|
||||||
165
yoyo-getfastmirror/config.config
Normal file
165
yoyo-getfastmirror/config.config
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
###################################################################
|
||||||
|
# CONFIGURATION OPTIONS
|
||||||
|
###################################################################
|
||||||
|
# Every item has a default value besides MIRRORS (which is unset).
|
||||||
|
|
||||||
|
# Use aptitude, apt-get, or apt?
|
||||||
|
# Note that apt-get is used as a fallback for outputting the
|
||||||
|
# package URI list for e.g. aptitude, which can't do this
|
||||||
|
# Optionally add the FULLPATH to apt-get or apt-rpm or aptitude
|
||||||
|
# e.g. /usr/bin/aptitude
|
||||||
|
#
|
||||||
|
# Default: apt-get
|
||||||
|
#
|
||||||
|
_APTMGR=apt
|
||||||
|
|
||||||
|
if grep -Eqi "linuxmint" /etc/os-release;then
|
||||||
|
_APTMGR=apt-get
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -x "$(command -v oyo)" ]; then
|
||||||
|
_APTMGR=apt-get
|
||||||
|
fi
|
||||||
|
|
||||||
|
####
|
||||||
|
#
|
||||||
|
# UOS sources auth config
|
||||||
|
#
|
||||||
|
#
|
||||||
|
if grep -Eqi "UnionTech" /etc/issue || grep -Eq "UnionTech" /etc/*-release;then
|
||||||
|
AUTH_UOS_USER="uos-https://license.chinauos.com-apt"
|
||||||
|
AUTH_UOS_PASSWD="`cat /etc/apt/auth.conf.d/uos.conf | grep home-packages.chinauos.com`"
|
||||||
|
AUTH_UOS_PASSWD=`echo ${AUTH_UOS_PASSWD#*password }`
|
||||||
|
|
||||||
|
fi
|
||||||
|
##### UOS自动读取账号密码以实现使用aptss来加速下载
|
||||||
|
|
||||||
|
# Enable DOWNLOADBEFORE to suppress apt-fast confirmation dialog and download
|
||||||
|
# packages directly.
|
||||||
|
#
|
||||||
|
# Default: dialog enabled
|
||||||
|
#
|
||||||
|
DOWNLOADBEFORE=true
|
||||||
|
|
||||||
|
|
||||||
|
# Choose mirror list to speed up downloads from same archive. To select some
|
||||||
|
# mirrors take a look at your distribution's archive mirror lists.
|
||||||
|
# Debian: http://www.debian.org/mirror/list
|
||||||
|
# Ubuntu: https://launchpad.net/ubuntu/+archivemirrors
|
||||||
|
#
|
||||||
|
# It is required to add mirrors in the sources.list to this array as well, so
|
||||||
|
# apt-fast can destinguish between different distributions.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
#
|
||||||
|
# Different distributions (as in operating systems):
|
||||||
|
#
|
||||||
|
# sources.list:
|
||||||
|
# deb http://deb.debian.org/debian/ unstable main non-free contrib
|
||||||
|
# deb http://de.archive.ubuntu.com/ubuntu/ bionic main universe
|
||||||
|
#
|
||||||
|
# apt-fast.conf:
|
||||||
|
# MIRRORS=( 'http://deb.debian.org/debian','http://ftp.debian.org/debian,http://ftp2.de.debian.org/debian,http://ftp.de.debian.org/debian,ftp://ftp.uni-kl.de/debian'
|
||||||
|
# 'http://archive.ubuntu.com/ubuntu,http://de.archive.ubuntu.com/ubuntu,http://ftp.halifax.rwth-aachen.de/ubuntu,http://ftp.uni-kl.de/pub/linux/ubuntu,http://mirror.informatik.uni-mannheim.de/pub/linux/distributions/ubuntu/' )
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Single distribution:
|
||||||
|
#
|
||||||
|
# sources.list:
|
||||||
|
# deb http://fr.archive.ubuntu.com/ubuntu/ bionic main
|
||||||
|
# deb http://fr.archive.ubuntu.com/ubuntu/ artful main
|
||||||
|
#
|
||||||
|
# apt-fast.conf:
|
||||||
|
# MIRRORS=( 'http://fr.archive.ubuntu.com/ubuntu,http://bouyguestelecom.ubuntu.lafibre.info/ubuntu,http://mirror.ovh.net/ubuntu,http://ubuntu-archive.mirrors.proxad.net/ubuntu' )
|
||||||
|
#
|
||||||
|
# Default: disabled
|
||||||
|
#
|
||||||
|
MIRRORS=( 'https://code.gitlink.org.cn/yoyo-os/packages/raw/branch/master, https://packages.yzzi.icu/, http://mirror2.inuyasha.love/yoyo, https://yoyo-os.github.io/packages' )
|
||||||
|
|
||||||
|
|
||||||
|
# Maximum number of connections
|
||||||
|
# You can use this value in _DOWNLOADER command. Escape with ${}: ${_MAXNUM}
|
||||||
|
#
|
||||||
|
# Default: 5
|
||||||
|
#
|
||||||
|
_MAXNUM=16
|
||||||
|
|
||||||
|
|
||||||
|
# Maximum number of connections per server
|
||||||
|
# Default: 10
|
||||||
|
#
|
||||||
|
_MAXCONPERSRV=1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Split size i.e. size of each piece
|
||||||
|
# Possible Values: 1M-1024M
|
||||||
|
#
|
||||||
|
_MINSPLITSZ=1M
|
||||||
|
|
||||||
|
|
||||||
|
# Piece selection algorithm to use
|
||||||
|
# Available values are: default, inorder, geom
|
||||||
|
# default: selects piece so that it reduces the number of establishing connection, reasonable for most cases
|
||||||
|
# inorder: selects pieces in sequential order starting from first piece
|
||||||
|
# geom: selects piece which has minimum index like inorder, but it exponentially increasingly keeps space from previously selected pieces
|
||||||
|
#
|
||||||
|
_PIECEALGO=default
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Downloadmanager listfile
|
||||||
|
# You can use this value in _DOWNLOADER command. Escape with ${}: ${DLLIST}
|
||||||
|
#
|
||||||
|
# Default: /tmp/apt-fast.list
|
||||||
|
#
|
||||||
|
DLLIST='/tmp/apt-fast.list'
|
||||||
|
|
||||||
|
|
||||||
|
# Download command to use. Temporary download list is designed for aria2. But
|
||||||
|
# you can choose another download command or download manager. It has to
|
||||||
|
# support following input file syntax (\t is tab character):
|
||||||
|
#
|
||||||
|
# # Comment
|
||||||
|
# MIRROR1\tMIRROR2\tMIRROR3...
|
||||||
|
# out=FILENAME1
|
||||||
|
# MIRROR1\tMIRROR2\tMIRROR3...
|
||||||
|
# out=FILENAME2
|
||||||
|
# ...
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# aria2c with a proxy (set username, proxy, ip and password!)
|
||||||
|
# _DOWNLOADER='aria2c --no-conf -c -j ${_MAXNUM} -x ${_MAXCONPERSRV} -s ${_SPLITCON} --min-split-size=${_MINSPLITSZ} --stream-piece-selector=${_PIECEALGO} --http-proxy=http://username:password@proxy_ip:proxy_port -i ${DLLIST}'
|
||||||
|
#
|
||||||
|
# Default: _DOWNLOADER='aria2c --no-conf -c -j ${_MAXNUM} -x ${_MAXCONPERSRV} -s ${_SPLITCON} --min-split-size=${_MINSPLITSZ} --stream-piece-selector=${_PIECEALGO} -i ${DLLIST} --connect-timeout=600 --timeout=600 -m0'
|
||||||
|
#
|
||||||
|
_DOWNLOADER='aria2c --no-conf -c -j ${_MAXNUM} -x ${_MAXCONPERSRV} --min-split-size=${_MINSPLITSZ} --stream-piece-selector=${_PIECEALGO} -i ${DLLIST} --connect-timeout=600 --timeout=600 -m0 --http-user ${AUTH_UOS_USER} --http-passwd ${AUTH_UOS_PASSWD}'
|
||||||
|
|
||||||
|
|
||||||
|
# Download temp folder for Downloadmanager
|
||||||
|
# example /tmp/apt-fast. Standard is /var/cache/apt-fast
|
||||||
|
#
|
||||||
|
# Default: /var/cache/apt/apt-fast
|
||||||
|
#
|
||||||
|
DLDIR='/var/cache/apt/apt-fast'
|
||||||
|
|
||||||
|
|
||||||
|
# APT archives cache directory
|
||||||
|
#
|
||||||
|
# Default /var/cache/apt/archives
|
||||||
|
# (APT configuration items Dir::Cache and Dir::Cache::archives)
|
||||||
|
#
|
||||||
|
APTCACHE='/var/cache/apt/archives'
|
||||||
|
|
||||||
|
|
||||||
|
# apt-fast colors
|
||||||
|
# Colors are disabled when not using a terminal.
|
||||||
|
#
|
||||||
|
# Default colors are:
|
||||||
|
# cGreen='\e[0;32m'
|
||||||
|
# cRed='\e[0;31m'
|
||||||
|
# cBlue='\e[0;34m'
|
||||||
|
# endColor='\e[0m'
|
||||||
|
|
||||||
20
yoyo-getfastmirror/coverage_badge.svg
Normal file
20
yoyo-getfastmirror/coverage_badge.svg
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="102" height="20">
|
||||||
|
<linearGradient id="b" x2="0" y2="100%">
|
||||||
|
<stop offset="0" stop-color="#bbb" stop-opacity=".1" />
|
||||||
|
<stop offset="1" stop-opacity=".1" />
|
||||||
|
</linearGradient>
|
||||||
|
<clipPath id="a">
|
||||||
|
<rect width="102" height="20" rx="3" fill="#fff" />
|
||||||
|
</clipPath>
|
||||||
|
<g clip-path="url(#a)">
|
||||||
|
<path fill="#555" d="M0 0h59v20H0z" />
|
||||||
|
<path fill="#44cc11" d="M59 0h43v20H59z" />
|
||||||
|
<path fill="url(#b)" d="M0 0h102v20H0z" />
|
||||||
|
</g>
|
||||||
|
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110">
|
||||||
|
<text x="305" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">coverage</text>
|
||||||
|
<text x="305" y="140" transform="scale(.1)" textLength="490">coverage</text>
|
||||||
|
<text x="795" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="330">100%</text>
|
||||||
|
<text x="795" y="140" transform="scale(.1)" textLength="330">100%</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
3
yoyo-getfastmirror/dart_test.yaml
Normal file
3
yoyo-getfastmirror/dart_test.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
tags:
|
||||||
|
version-verify:
|
||||||
|
skip: "Should only be run during pull request. Verifies if version file is updated."
|
||||||
98
yoyo-getfastmirror/lib/src/command_runner.dart
Normal file
98
yoyo-getfastmirror/lib/src/command_runner.dart
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import 'package:args/args.dart';
|
||||||
|
import 'package:args/command_runner.dart';
|
||||||
|
import 'package:mason_logger/mason_logger.dart';
|
||||||
|
import 'package:yoyo_get_fast_mirror/src/commands/commands.dart';
|
||||||
|
import 'package:yoyo_get_fast_mirror/src/version.dart';
|
||||||
|
|
||||||
|
const executableName = 'yoyo-getfastmirror';
|
||||||
|
const packageName = 'yoyo_get_fast_mirror';
|
||||||
|
const description = 'A Very Good Project created by Yoyo.';
|
||||||
|
|
||||||
|
class YoyoGetFastMirrorCommandRunner extends CommandRunner<int> {
|
||||||
|
/// {@macro yoyo_get_fast_mirror_command_runner}
|
||||||
|
YoyoGetFastMirrorCommandRunner({
|
||||||
|
Logger? logger,
|
||||||
|
}) : _logger = logger ?? Logger(),
|
||||||
|
super(executableName, description) {
|
||||||
|
// Add root options and flags
|
||||||
|
argParser
|
||||||
|
..addFlag(
|
||||||
|
'version',
|
||||||
|
abbr: 'v',
|
||||||
|
negatable: false,
|
||||||
|
help: 'Print the current version.',
|
||||||
|
)
|
||||||
|
..addFlag(
|
||||||
|
'verbose',
|
||||||
|
help: 'Noisy logging, including all shell commands executed.',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add sub commands
|
||||||
|
addCommand(GetfastmirroCommand(logger: _logger));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void printUsage() => _logger.info(usage);
|
||||||
|
|
||||||
|
final Logger _logger;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int> run(Iterable<String> args) async {
|
||||||
|
try {
|
||||||
|
final topLevelResults = parse(args);
|
||||||
|
if (topLevelResults['verbose'] == true) {
|
||||||
|
_logger.level = Level.verbose;
|
||||||
|
}
|
||||||
|
return await runCommand(topLevelResults) ?? ExitCode.success.code;
|
||||||
|
} on FormatException catch (e, stackTrace) {
|
||||||
|
// On format errors, show the commands error message, root usage and
|
||||||
|
// exit with an error code
|
||||||
|
_logger
|
||||||
|
..err(e.message)
|
||||||
|
..err('$stackTrace')
|
||||||
|
..info('')
|
||||||
|
..info(usage);
|
||||||
|
return ExitCode.usage.code;
|
||||||
|
} on UsageException catch (e) {
|
||||||
|
// On usage errors, show the commands usage message and
|
||||||
|
// exit with an error code
|
||||||
|
_logger
|
||||||
|
..err(e.message)
|
||||||
|
..info('')
|
||||||
|
..info(e.usage);
|
||||||
|
return ExitCode.usage.code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int?> runCommand(ArgResults topLevelResults) async {
|
||||||
|
_logger
|
||||||
|
..detail('Argument information:')
|
||||||
|
..detail(' Top level options:');
|
||||||
|
for (final option in topLevelResults.options) {
|
||||||
|
if (topLevelResults.wasParsed(option)) {
|
||||||
|
_logger.detail(' - $option: ${topLevelResults[option]}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (topLevelResults.command != null) {
|
||||||
|
final commandResult = topLevelResults.command!;
|
||||||
|
_logger
|
||||||
|
..detail(' Command: ${commandResult.name}')
|
||||||
|
..detail(' Command options:');
|
||||||
|
for (final option in commandResult.options) {
|
||||||
|
if (commandResult.wasParsed(option)) {
|
||||||
|
_logger.detail(' - $option: ${commandResult[option]}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final int? exitCode;
|
||||||
|
if (topLevelResults['version'] == true) {
|
||||||
|
_logger.info(packageVersion);
|
||||||
|
exitCode = ExitCode.success.code;
|
||||||
|
} else {
|
||||||
|
exitCode = await super.runCommand(topLevelResults);
|
||||||
|
}
|
||||||
|
return exitCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
yoyo-getfastmirror/lib/src/commands/commands.dart
Normal file
1
yoyo-getfastmirror/lib/src/commands/commands.dart
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export 'getfastmirror_command.dart';
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import 'package:args/command_runner.dart';
|
||||||
|
import 'package:mason_logger/mason_logger.dart';
|
||||||
|
import 'package:yoyo_get_fast_mirror/src/commands/modules/getfastmirro/getfastmirror.dart';
|
||||||
|
|
||||||
|
class GetfastmirroCommand extends Command<int> {
|
||||||
|
GetfastmirroCommand({
|
||||||
|
required Logger logger,
|
||||||
|
}) : _logger = logger {
|
||||||
|
argParser.addFlag(
|
||||||
|
'only-test',
|
||||||
|
abbr: 'o',
|
||||||
|
help: "Only test mirrors' speed",
|
||||||
|
negatable: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get description =>
|
||||||
|
'Get fastest mirro and generate config file for oyo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => 'getfastmirror';
|
||||||
|
|
||||||
|
final Logger _logger;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int> run() async {
|
||||||
|
final output = lightBlue.wrap('''
|
||||||
|
Get the fastest mirror which will be write into source file
|
||||||
|
And generate a config file for oyo
|
||||||
|
''');
|
||||||
|
_logger.info(output);
|
||||||
|
final onlyTest = argResults?['only-test'] == true;
|
||||||
|
final commandResult = await getFastMirror(onlyTest: onlyTest);
|
||||||
|
if (commandResult['success'] == false) {
|
||||||
|
_logger.err(commandResult['msg'] as String);
|
||||||
|
return ExitCode.unavailable.code;
|
||||||
|
}
|
||||||
|
return ExitCode.success.code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
String aptFastConfigTemplate(String yoYoMirroList,String ubuntuMirroList) {
|
||||||
|
return '''
|
||||||
|
###################################################################
|
||||||
|
# CONFIGURATION OPTIONS
|
||||||
|
###################################################################
|
||||||
|
# Every item has a default value besides MIRRORS (which is unset).
|
||||||
|
|
||||||
|
# Use aptitude, apt-get, or apt?
|
||||||
|
# Note that apt-get is used as a fallback for outputting the
|
||||||
|
# package URI list for e.g. aptitude, which can't do this
|
||||||
|
# Optionally add the FULLPATH to apt-get or apt-rpm or aptitude
|
||||||
|
# e.g. /usr/bin/aptitude
|
||||||
|
#
|
||||||
|
# Default: apt-get
|
||||||
|
#
|
||||||
|
_APTMGR=apt
|
||||||
|
|
||||||
|
if grep -Eqi "linuxmint" /etc/os-release;then
|
||||||
|
_APTMGR=apt-get
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -x "\$(command -v oyo)" ]; then
|
||||||
|
_APTMGR=apt-get
|
||||||
|
fi
|
||||||
|
|
||||||
|
####
|
||||||
|
#
|
||||||
|
# UOS sources auth config
|
||||||
|
#
|
||||||
|
#
|
||||||
|
if grep -Eqi "UnionTech" /etc/issue || grep -Eq "UnionTech" /etc/*-release;then
|
||||||
|
AUTH_UOS_USER="uos-https://license.chinauos.com-apt"
|
||||||
|
AUTH_UOS_PASSWD="`cat /etc/apt/auth.conf.d/uos.conf | grep home-packages.chinauos.com`"
|
||||||
|
AUTH_UOS_PASSWD=`echo \${AUTH_UOS_PASSWD#*password }`
|
||||||
|
|
||||||
|
fi
|
||||||
|
##### UOS自动读取账号密码以实现使用aptss来加速下载
|
||||||
|
|
||||||
|
# Enable DOWNLOADBEFORE to suppress apt-fast confirmation dialog and download
|
||||||
|
# packages directly.
|
||||||
|
#
|
||||||
|
# Default: dialog enabled
|
||||||
|
#
|
||||||
|
DOWNLOADBEFORE=true
|
||||||
|
|
||||||
|
|
||||||
|
# Choose mirror list to speed up downloads from same archive. To select some
|
||||||
|
# mirrors take a look at your distribution's archive mirror lists.
|
||||||
|
# Debian: http://www.debian.org/mirror/list
|
||||||
|
# Ubuntu: https://launchpad.net/ubuntu/+archivemirrors
|
||||||
|
#
|
||||||
|
# It is required to add mirrors in the sources.list to this array as well, so
|
||||||
|
# apt-fast can destinguish between different distributions.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
#
|
||||||
|
# Different distributions (as in operating systems):
|
||||||
|
#
|
||||||
|
# sources.list:
|
||||||
|
# deb http://deb.debian.org/debian/ unstable main non-free contrib
|
||||||
|
# deb http://de.archive.ubuntu.com/ubuntu/ bionic main universe
|
||||||
|
#
|
||||||
|
# apt-fast.conf:
|
||||||
|
# MIRRORS=( 'http://deb.debian.org/debian','http://ftp.debian.org/debian,http://ftp2.de.debian.org/debian,http://ftp.de.debian.org/debian,ftp://ftp.uni-kl.de/debian'
|
||||||
|
# 'http://archive.ubuntu.com/ubuntu,http://de.archive.ubuntu.com/ubuntu,http://ftp.halifax.rwth-aachen.de/ubuntu,http://ftp.uni-kl.de/pub/linux/ubuntu,http://mirror.informatik.uni-mannheim.de/pub/linux/distributions/ubuntu/' )
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Single distribution:
|
||||||
|
#
|
||||||
|
# sources.list:
|
||||||
|
# deb http://fr.archive.ubuntu.com/ubuntu/ bionic main
|
||||||
|
# deb http://fr.archive.ubuntu.com/ubuntu/ artful main
|
||||||
|
#
|
||||||
|
# apt-fast.conf:
|
||||||
|
# MIRRORS=( 'http://fr.archive.ubuntu.com/ubuntu,http://bouyguestelecom.ubuntu.lafibre.info/ubuntu,http://mirror.ovh.net/ubuntu,http://ubuntu-archive.mirrors.proxad.net/ubuntu' )
|
||||||
|
#
|
||||||
|
# Default: disabled
|
||||||
|
|
||||||
|
MIRRORS=( '$ubuntuMirroList'
|
||||||
|
'$yoYoMirroList' )
|
||||||
|
|
||||||
|
|
||||||
|
# Maximum number of connections
|
||||||
|
# You can use this value in _DOWNLOADER command. Escape with \${}: \${_MAXNUM}
|
||||||
|
#
|
||||||
|
# Default: 5
|
||||||
|
#
|
||||||
|
_MAXNUM=16
|
||||||
|
|
||||||
|
|
||||||
|
# Maximum number of connections per server
|
||||||
|
# Default: 10
|
||||||
|
#
|
||||||
|
_MAXCONPERSRV=1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Split size i.e. size of each piece
|
||||||
|
# Possible Values: 1M-1024M
|
||||||
|
#
|
||||||
|
_MINSPLITSZ=1M
|
||||||
|
|
||||||
|
|
||||||
|
# Piece selection algorithm to use
|
||||||
|
# Available values are: default, inorder, geom
|
||||||
|
# default: selects piece so that it reduces the number of establishing connection, reasonable for most cases
|
||||||
|
# inorder: selects pieces in sequential order starting from first piece
|
||||||
|
# geom: selects piece which has minimum index like inorder, but it exponentially increasingly keeps space from previously selected pieces
|
||||||
|
#
|
||||||
|
_PIECEALGO=default
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Downloadmanager listfile
|
||||||
|
# You can use this value in _DOWNLOADER command. Escape with \${}: \${DLLIST}
|
||||||
|
#
|
||||||
|
# Default: /tmp/apt-fast.list
|
||||||
|
#
|
||||||
|
DLLIST='/tmp/apt-fast.list'
|
||||||
|
|
||||||
|
|
||||||
|
# Download command to use. Temporary download list is designed for aria2. But
|
||||||
|
# you can choose another download command or download manager. It has to
|
||||||
|
# support following input file syntax (\\t is tab character):
|
||||||
|
#
|
||||||
|
# # Comment
|
||||||
|
# MIRROR1\\tMIRROR2\\tMIRROR3...
|
||||||
|
# out=FILENAME1
|
||||||
|
# MIRROR1\\tMIRROR2\\tMIRROR3...
|
||||||
|
# out=FILENAME2
|
||||||
|
# ...
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# aria2c with a proxy (set username, proxy, ip and password!)
|
||||||
|
# _DOWNLOADER='aria2c --no-conf -c -j \${_MAXNUM} -x \${_MAXCONPERSRV} -s \${_SPLITCON} --min-split-size=\${_MINSPLITSZ} --stream-piece-selector=\${_PIECEALGO} --http-proxy=http://username:password@proxy_ip:proxy_port -i \${DLLIST}'
|
||||||
|
#
|
||||||
|
# Default: _DOWNLOADER='aria2c --no-conf -c -j \${_MAXNUM} -x \${_MAXCONPERSRV} -s \${_SPLITCON} --min-split-size=\${_MINSPLITSZ} --stream-piece-selector=\${_PIECEALGO} -i \${DLLIST} --connect-timeout=600 --timeout=600 -m0'
|
||||||
|
#
|
||||||
|
_DOWNLOADER='aria2c --no-conf -c -j \${_MAXNUM} -x \${_MAXCONPERSRV} --min-split-size=\${_MINSPLITSZ} --stream-piece-selector=\${_PIECEALGO} -i \${DLLIST} --connect-timeout=600 --timeout=600 -m0 --http-user \${AUTH_UOS_USER} --http-passwd \${AUTH_UOS_PASSWD}'
|
||||||
|
|
||||||
|
|
||||||
|
# Download temp folder for Downloadmanager
|
||||||
|
# example /tmp/apt-fast. Standard is /var/cache/apt-fast
|
||||||
|
#
|
||||||
|
# Default: /var/cache/apt/apt-fast
|
||||||
|
#
|
||||||
|
DLDIR='/var/cache/apt/apt-fast'
|
||||||
|
|
||||||
|
|
||||||
|
# APT archives cache directory
|
||||||
|
#
|
||||||
|
# Default /var/cache/apt/archives
|
||||||
|
# (APT configuration items Dir::Cache and Dir::Cache::archives)
|
||||||
|
#
|
||||||
|
APTCACHE='/var/cache/apt/archives'
|
||||||
|
|
||||||
|
|
||||||
|
# apt-fast colors
|
||||||
|
# Colors are disabled when not using a terminal.
|
||||||
|
#
|
||||||
|
# Default colors are:
|
||||||
|
# cGreen='\\e[0;32m'
|
||||||
|
# cRed='\\e[0;31m'
|
||||||
|
# cBlue='\\e[0;34m'
|
||||||
|
# endColor='\\e[0m'
|
||||||
|
|
||||||
|
''';
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'package:yoyo_get_fast_mirror/src/commands/modules/getfastmirro/apt_fast_config_template.dart';
|
||||||
|
|
||||||
|
final tmpDir = Directory.systemTemp.path;
|
||||||
|
final String filePath = '$tmpDir/oyo/apt-fast.conf';
|
||||||
|
final File file = File(filePath);
|
||||||
|
final List<String> mirroListInConfigFileYoyo = [];
|
||||||
|
final List<String> mirroListInConfigFileUbuntu = [];
|
||||||
|
|
||||||
|
void generateAptFastConfigFile(
|
||||||
|
List<Map<String, String>> sortedUrlListAndTimeYoyo,
|
||||||
|
List<Map<String, String>> sortedUrlListAndTimeUbuntu,
|
||||||
|
) {
|
||||||
|
//Yoyo
|
||||||
|
for (final element in sortedUrlListAndTimeYoyo) {
|
||||||
|
mirroListInConfigFileYoyo.add(element['url'] ?? '');
|
||||||
|
}
|
||||||
|
final mirroListInConfigFileStringYoyo = mirroListInConfigFileYoyo
|
||||||
|
.toString()
|
||||||
|
.replaceAll('[', '')
|
||||||
|
.replaceAll(']', '');
|
||||||
|
//Ubuntu
|
||||||
|
for (final element in sortedUrlListAndTimeUbuntu) {
|
||||||
|
mirroListInConfigFileUbuntu.add(element['url'] ?? '');
|
||||||
|
}
|
||||||
|
final mirroListInConfigFileStringUbuntu = mirroListInConfigFileUbuntu
|
||||||
|
.toString()
|
||||||
|
.replaceAll('[', '')
|
||||||
|
.replaceAll(']', '');
|
||||||
|
|
||||||
|
final configContent = aptFastConfigTemplate(
|
||||||
|
mirroListInConfigFileStringYoyo,
|
||||||
|
mirroListInConfigFileStringUbuntu,
|
||||||
|
);
|
||||||
|
if (!file.existsSync()) file.createSync(recursive: true);
|
||||||
|
file.writeAsStringSync(configContent);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
Uri mirrorsUri = Uri(
|
||||||
|
scheme: 'https',
|
||||||
|
host: 'packages.yzzi.icu',
|
||||||
|
path: '/sources_mirrors.list',
|
||||||
|
);
|
||||||
|
|
||||||
|
/// 获取镜像列表,返回一个 List<String>
|
||||||
|
Future<List<String>> getMirrorList() async {
|
||||||
|
final response = await http.get(mirrorsUri);
|
||||||
|
final list = response.body.replaceAll('\n', ' ').trimRight().split(' ');
|
||||||
|
return list;
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import 'package:mason_logger/mason_logger.dart';
|
||||||
|
import 'package:yoyo_get_fast_mirror/src/commands/modules/getfastmirro/generate_apt_fast_config_file.dart';
|
||||||
|
import 'package:yoyo_get_fast_mirror/src/commands/modules/getfastmirro/get_mirro_list.dart';
|
||||||
|
import 'package:yoyo_get_fast_mirror/src/commands/modules/getfastmirro/network_test.dart';
|
||||||
|
import 'package:yoyo_get_fast_mirror/src/commands/modules/getfastmirro/test_mirro_speed.dart';
|
||||||
|
import 'package:yoyo_get_fast_mirror/src/commands/modules/getfastmirro/ubuntu_sources_list_template.dart';
|
||||||
|
import 'package:yoyo_get_fast_mirror/src/commands/modules/getfastmirro/write_mirror_into_file.dart';
|
||||||
|
import 'package:yoyo_get_fast_mirror/src/commands/modules/getfastmirro/yoyo_sources_list_template.dart';
|
||||||
|
|
||||||
|
final Map<String, dynamic> status = {};
|
||||||
|
final Logger _logger = Logger();
|
||||||
|
const List<String> ubuntuMirrorList = [
|
||||||
|
'https://mirrors.tuna.tsinghua.edu.cn/ubuntu/',
|
||||||
|
'https://mirrors.aliyun.com/ubuntu/',
|
||||||
|
'https://repo.huaweicloud.com/ubuntu/'
|
||||||
|
];
|
||||||
|
|
||||||
|
final outputYoyo = lightGreen.wrap('Yoyo Mirrors:');
|
||||||
|
final outputUbuntu = lightGreen.wrap('Ubuntu Mirrors:');
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> getFastMirror({bool onlyTest = false}) async {
|
||||||
|
//首先测试网络链接
|
||||||
|
if (!await networkTest()) {
|
||||||
|
status['success'] = false;
|
||||||
|
status['msg'] = 'NetWork Error';
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
//获取Yoyo镜像地址
|
||||||
|
final yoYoMirrorList = await getMirrorList();
|
||||||
|
_logger.info("\n$outputYoyo\n${yoYoMirrorList.join('\n')}\n");
|
||||||
|
//测试Yoyo镜像并获取时间和排序后的Map
|
||||||
|
final sortedUrlListAndTimeYoyo = await testMirroSpeed(yoYoMirrorList);
|
||||||
|
//测试Ubuntu镜像并获取时间和排序后的Map
|
||||||
|
_logger.info("\n$outputUbuntu\n${ubuntuMirrorList.join('\n')}\n");
|
||||||
|
final sortedUrlListAndTimeUbuntu = await testMirroSpeed(ubuntuMirrorList);
|
||||||
|
//生成config
|
||||||
|
if (!onlyTest) {
|
||||||
|
try {
|
||||||
|
generateAptFastConfigFile(
|
||||||
|
sortedUrlListAndTimeYoyo, sortedUrlListAndTimeUbuntu,);
|
||||||
|
} catch (e) {
|
||||||
|
status['success'] = false;
|
||||||
|
status['msg'] = 'Generate Config File Error';
|
||||||
|
return status;
|
||||||
|
} finally {
|
||||||
|
status['success'] = true;
|
||||||
|
}
|
||||||
|
//写入镜像源文件
|
||||||
|
//Yoyo
|
||||||
|
final fastestYoyoMirror =
|
||||||
|
sortedUrlListAndTimeYoyo.first.entries.first.value;
|
||||||
|
final yoyoSourcesListFile = yoyoSourcesListTemplate(fastestYoyoMirror);
|
||||||
|
writeMirrorIntoFile(
|
||||||
|
yoyoSourcesListFile,
|
||||||
|
'/etc/apt/sources.list.d/yoyo-os.list',
|
||||||
|
);
|
||||||
|
//Ubuntu
|
||||||
|
final fastestUbuntuMirror =
|
||||||
|
sortedUrlListAndTimeUbuntu.first.entries.first.value;
|
||||||
|
final ubuntuSourcesLIstFile =
|
||||||
|
ubuntuSourcesListTemplate(fastestUbuntuMirror);
|
||||||
|
writeMirrorIntoFile(
|
||||||
|
ubuntuSourcesLIstFile,
|
||||||
|
'/etc/apt/sources.list',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
//返回执行结果
|
||||||
|
return status;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
Uri mirrosUri = Uri(
|
||||||
|
scheme: 'https',
|
||||||
|
host: 'baidu.com',
|
||||||
|
path: '/',
|
||||||
|
);
|
||||||
|
|
||||||
|
/// 测试网络连接,成功返回true
|
||||||
|
Future<bool> networkTest() async {
|
||||||
|
final response = await http.get(mirrosUri);
|
||||||
|
return response.statusCode == 200;
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
|
import 'package:cli_spinner/cli_spinner.dart';
|
||||||
|
import 'package:dart_ping/dart_ping.dart';
|
||||||
|
import 'package:mason_logger/mason_logger.dart';
|
||||||
|
|
||||||
|
Logger _logger = Logger();
|
||||||
|
final speedResult = lightGreen.wrap('Speed Result:');
|
||||||
|
|
||||||
|
Future<List<Map<String, String>>> testMirroSpeed(
|
||||||
|
List<String> mirrorList,
|
||||||
|
) async {
|
||||||
|
final spinner =
|
||||||
|
Spinner.type('Speed Test ......', SpinnerType.boxCircle)..start();
|
||||||
|
final testResult = <Map<String, String>>[];
|
||||||
|
final logMsg = StringBuffer()..write(speedResult);
|
||||||
|
for (final element in mirrorList) {
|
||||||
|
late final PingData pingRes;
|
||||||
|
late final double time;
|
||||||
|
try {
|
||||||
|
pingRes = await Ping(Uri.parse(element).host, count: 1).stream.first;
|
||||||
|
time = pingRes.response!.time!.inMicroseconds * 0.001;
|
||||||
|
testResult.add({'url': element, 'time': time.toStringAsFixed(3)});
|
||||||
|
logMsg.write('\n${time.toStringAsFixed(3)}ms \t$element');
|
||||||
|
} catch (e) {
|
||||||
|
testResult.add({'url': element, 'time': '99999999'});
|
||||||
|
logMsg.write('\nTimeout \t$element');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spinner.updateMessage('');
|
||||||
|
spinner.stop();
|
||||||
|
_logger.info(logMsg.toString());
|
||||||
|
final resultMap = testResult.asMap();
|
||||||
|
final Map<int, Map<String, String>> sortedResult = SplayTreeMap.from(
|
||||||
|
resultMap,
|
||||||
|
(key1, key2) => double.parse(resultMap[key1]!['time'] ?? '') >
|
||||||
|
double.parse(resultMap[key2]!['time'] ?? '')
|
||||||
|
? 1
|
||||||
|
: -1,
|
||||||
|
);
|
||||||
|
final sortedUrlListAndTime =
|
||||||
|
sortedResult.entries.map((e) => e.value).toList();
|
||||||
|
return sortedUrlListAndTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
String ubuntuSourcesListTemplate(String ubuntuMirrorUrl) {
|
||||||
|
return '''
|
||||||
|
# Generate by yoyo-getfastmirror
|
||||||
|
# Author: Gtibhub/Riceneeder
|
||||||
|
|
||||||
|
deb $ubuntuMirrorUrl jammy main restricted universe multiverse
|
||||||
|
deb $ubuntuMirrorUrl jammy-updates main restricted universe multiverse
|
||||||
|
deb $ubuntuMirrorUrl jammy-backports main restricted universe multiverse
|
||||||
|
deb $ubuntuMirrorUrl jammy-security main restricted universe multiverse
|
||||||
|
''';
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'package:mason_logger/mason_logger.dart';
|
||||||
|
|
||||||
|
final done = lightGreen.wrap('Done');
|
||||||
|
final Logger _logger = Logger();
|
||||||
|
|
||||||
|
void writeMirrorIntoFile(String mirror, String path) {
|
||||||
|
final file = File(path);
|
||||||
|
final writting = lightYellow.wrap('Writting $path');
|
||||||
|
_logger.info(writting);
|
||||||
|
if (!file.existsSync()) file.createSync(recursive: true);
|
||||||
|
file.writeAsStringSync(mirror);
|
||||||
|
_logger.info(done);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
String yoyoSourcesListTemplate(String yoyoMirrorUrl) {
|
||||||
|
return '''
|
||||||
|
# Generate by yoyo-getfastmirror
|
||||||
|
# Author: Gtibhub/Riceneeder
|
||||||
|
|
||||||
|
deb [arch=amd64] $yoyoMirrorUrl kokomi main
|
||||||
|
''';
|
||||||
|
}
|
||||||
1
yoyo-getfastmirror/lib/src/version.dart
Normal file
1
yoyo-getfastmirror/lib/src/version.dart
Normal file
@@ -0,0 +1 @@
|
|||||||
|
const packageVersion = '0.0.1';
|
||||||
1
yoyo-getfastmirror/lib/yoyo_get_fast_mirror.dart
Normal file
1
yoyo-getfastmirror/lib/yoyo_get_fast_mirror.dart
Normal file
@@ -0,0 +1 @@
|
|||||||
|
library yoyo_get_fast_mirror;
|
||||||
26
yoyo-getfastmirror/pubspec.yaml
Normal file
26
yoyo-getfastmirror/pubspec.yaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: yoyo_get_fast_mirror
|
||||||
|
description: A Very Good Project created by Very Good CLI.
|
||||||
|
version: 0.0.1
|
||||||
|
publish_to: none
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: ">=2.18.0 <3.0.0"
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
args: ^2.3.1
|
||||||
|
cli_spinner: ^1.0.4
|
||||||
|
dart_ping: ^8.0.1
|
||||||
|
http: ^0.13.5
|
||||||
|
mason_logger: ^0.2.0
|
||||||
|
pub_updater: ^0.2.4
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
build_runner: ^2.0.0
|
||||||
|
build_verify: ^3.0.0
|
||||||
|
build_version: ^2.0.0
|
||||||
|
mocktail: ^0.3.0
|
||||||
|
test: ^1.19.2
|
||||||
|
very_good_analysis: ^3.1.0
|
||||||
|
|
||||||
|
executables:
|
||||||
|
yoyo_get_fast_mirror:
|
||||||
3
yoyo-getfastmirror/release.sh
Executable file
3
yoyo-getfastmirror/release.sh
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
dart compile exe bin/yoyo_get_fast_mirror.dart -o release/yoyo-getfastmirror
|
||||||
0
yoyo-getfastmirror/release/log.md
Normal file
0
yoyo-getfastmirror/release/log.md
Normal file
Reference in New Issue
Block a user