Merge branch 'master' into feature-contextmenu

# Conflicts:
#	bower.json
This commit is contained in:
MaysWind 2017-10-15 00:51:25 +08:00
commit 6e7d498e2a
64 changed files with 3040 additions and 713 deletions

View file

@ -23,8 +23,3 @@ trim_trailing_whitespace = false
[{package,bower}.json]
indent_style = space
indent_size = 2
[gulpfile.js]
indent_style = space
indent_size = 2

31
.eslintrc.json Normal file
View file

@ -0,0 +1,31 @@
{
"env": {
"browser": true,
"node": true
},
"extends": [
"angular",
"eslint:recommended"
],
"parserOptions": {
"sourceType": "module"
},
"rules": {
"indent": [
"error",
4
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
]
}
}

1
.gitignore vendored
View file

@ -41,6 +41,7 @@ $RECYCLE.BIN/
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
.idea/
*.iml
# Mongo Explorer plugin:
.idea/mongoSettings.xml

View file

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2016 MaysWind (i@mayswind.net)
Copyright (c) 2016-2017 MaysWind (i@mayswind.net)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -1,50 +0,0 @@
# AriaNg
[![许可协议](https://img.shields.io/github/license/mayswind/AriaNg.svg?style=flat)](https://github.com/mayswind/AriaNg/blob/master/LICENSE)
[![最后构建](https://img.shields.io/circleci/project/mayswind/AriaNg.svg?style=flat)](https://circleci.com/gh/mayswind/AriaNg/tree/master)
[![最新版本](https://img.shields.io/github/release/mayswind/AriaNg.svg?style=flat)](https://github.com/mayswind/AriaNg/releases)
## 介绍
面向 [aria2](https://github.com/aria2/aria2) (跨平台的下载工具) 的更好用的前端. AriaNg 使用纯 html & javascript 开发, 所以既不需要编译也不依赖于任何运行环境, 您只需要打开您的游览器就可以使用. 通过借助响应式布局设计, 您可以在任何计算机或移动设备上使用.
## 特性
1. 纯 Html & Javascript 实现, 不依赖任何运行时
2. 响应式布局设计, 兼容计算机与移动设备
3. 友好的界面交互
* 任务排序 (按文件名, 文件大小, 进度, 剩余时间, 下载速度等.), 文件排序, 连接节点排序
* 任务搜索
* 拖拽调整下载顺序
* 更详细的任务信息 (健康度, 连接节点客户端信息等.)
* 根据文件类型过滤任务中的文件 (按视频, 音频, 图片, 文档, 应用程序, 存档文件等.)
* 全局以及单任务的上传/下载图表
* 完整支持 aria2 设置选项
4. 支持通过请求地址添加任务 (http://AriaNgUrl/#/new/url_base64)
5. 下载完成消息通知
6. 多语言支持
7. 节省带宽, 仅请求增量数据
## 截图
#### 计算机
![AriaNg](https://raw.githubusercontent.com/mayswind/AriaNg/gh-pages/screenshots/desktop.png)
#### 移动设备
![AriaNg](https://raw.githubusercontent.com/mayswind/AriaNg/gh-pages/screenshots/mobile.png)
## 安装
#### 下载即用
最新发布版本: https://github.com/mayswind/AriaNg/releases
最新每日构建: https://raw.githubusercontent.com/mayswind/AriaNg/gh-pages/downloads/latest_daily_build.zip
#### 从源代码中构建
首先请确保您已经安装 [Node.js](https://nodejs.org/), [NPM](https://www.npmjs.com/) 和 [Bower](https://bower.io/). 然后下载源代码并执行以下步骤.
$ npm install
$ bower install
$ gulp clean build
构建后的内容将放置在 dist 目录中.
## Demo
请访问 http://ariang.mayswind.net
## 协议
[MIT](https://github.com/mayswind/AriaNg/blob/master/LICENSE)

View file

@ -3,10 +3,8 @@
[![Lastest Build](https://img.shields.io/circleci/project/mayswind/AriaNg.svg?style=flat)](https://circleci.com/gh/mayswind/AriaNg/tree/master)
[![Lastest Release](https://img.shields.io/github/release/mayswind/AriaNg.svg?style=flat)](https://github.com/mayswind/AriaNg/releases)
[简体中文说明 (Simplified Chinese)](https://github.com/mayswind/AriaNg/blob/master/README-CHS.md)
## Introduction
A Better Frontend for [aria2](https://github.com/aria2/aria2) (a cross-platform download utility). AriaNg is written in pure html & javascript, so it does not need to be compiled and not need any runtime environment, and you just need to open it in your browser. By using responsive layout, you can open it on any desktop or mobile devices.
AriaNg is a modern web frontend making [aria2](https://github.com/aria2/aria2) easier to use. AriaNg is written in pure html & javascript, thus it does not need any compilers or runtime environment. You can just put AriaNg in your web server and open it in your browser. AriaNg uses responsive layout, and supports any desktop or mobile devices.
## Features
1. Pure Html & Javascript, no runtime required
@ -19,22 +17,23 @@ A Better Frontend for [aria2](https://github.com/aria2/aria2) (a cross-platform
* Filter files of tasks in file types (by videos, audios, pictures, documents, applications, archives, etc.)
* Download/upload history chart of global or task
* Full support of aria2 settings
4. Add new download task by requesting url (http://AriaNgUrl/#/new/url_base64)
4. Url command line api support
5. Download finished notification
6. Multi-languages support
7. Less bandwidth usage, only requesting incremental data
7. Multi aria2 RPC host support
8. Less bandwidth usage, only requesting incremental data
## Screenshots
#### Desktop
![AriaNg](https://raw.githubusercontent.com/mayswind/AriaNg/gh-pages/screenshots/desktop.png)
![AriaNg](https://raw.githubusercontent.com/mayswind/AriaNg-WebSite/master/screenshots/desktop.png)
#### Mobile Device
![AriaNg](https://raw.githubusercontent.com/mayswind/AriaNg/gh-pages/screenshots/mobile.png)
![AriaNg](https://raw.githubusercontent.com/mayswind/AriaNg-WebSite/master/screenshots/mobile.png)
## Installation
#### Prebuilt release
Latest Release: https://github.com/mayswind/AriaNg/releases
Latest Release: [https://github.com/mayswind/AriaNg/releases](https://github.com/mayswind/AriaNg/releases)
Latest Daily Build: https://raw.githubusercontent.com/mayswind/AriaNg/gh-pages/downloads/latest_daily_build.zip
Latest Daily Build: [https://github.com/mayswind/AriaNg-DailyBuild/archive/master.zip](https://github.com/mayswind/AriaNg-DailyBuild/archive/master.zip)
#### Building from source
Make sure you have [Node.js](https://nodejs.org/), [NPM](https://www.npmjs.com/) and [Bower](https://bower.io/) installed. Then download the source code, and follow these steps.
@ -45,8 +44,15 @@ Make sure you have [Node.js](https://nodejs.org/), [NPM](https://www.npmjs.com/)
The builds will be placed in the dist directory.
#### Usage Notes
Since AriaNg loads language resources asynchronously, you may not open index.html directly on the local file system to run AriaNg. It is recommended that you deploy AriaNg in a web container or download [AriaNg Native](https://github.com/mayswind/AriaNg-Native) that does not require a browser to run.
## Documents
1. [English](http://ariang.mayswind.net)
2. [Simplified Chinese (简体中文)](http://ariang.mayswind.net/zh_Hans)
## Demo
Please visit http://ariang.mayswind.net
Please visit [http://ariang.mayswind.net/latest](http://ariang.mayswind.net/latest)
## License
[MIT](https://github.com/mayswind/AriaNg/blob/master/LICENSE)

View file

@ -1,18 +1,19 @@
{
"private": true,
"name": "aria-ng",
"description": "Aria2 Ng Frontend",
"name": "ariang",
"description": "AriaNg, a modern web frontend making aria2 easier to use.",
"main": "index.html",
"authors": [
"MaysWind <i@mayswind.net>"
],
"license": "MIT",
"keywords": [
"Aria2",
"aria2",
"Web",
"Frontend"
"Frontend",
"UI"
],
"homepage": "https://github.com/mayswind/AriaNg",
"homepage": "http://ariang.mayswind.net/",
"ignore": [
"**/.*",
"node_modules",
@ -21,39 +22,38 @@
"tests"
],
"dependencies": {
"jquery": "^2.2.4",
"bootstrap": "^3.3.7",
"moment": "^2.17.1",
"moment-timezone": "^0.5.10",
"echarts": "^3.3.1",
"jquery": "2.2.4",
"bootstrap": "3.3.7",
"moment": "2.18.1",
"moment-timezone": "0.5.13",
"echarts": "3.7.1",
"font-awesome": "font-awsome#^4.7.0",
"AdminLTE": "admin-lte#^2.3.8",
"AdminLTE": "admin-lte#2.3.11",
"sweetalert": "^1.1.3",
"awesome-bootstrap-checkbox": "^0.3.7",
"jquery-slimscroll": "^1.3.8",
"bootstrap-contextmenu": "^0.3.4",
"angular": "1.4.10",
"angular-route": "1.4.10",
"angular-sanitize": "1.4.10",
"angular-touch": "1.4.10",
"angular-messages": "1.4.10",
"angular-cookies": "1.4.10",
"angular-animate": "1.4.10",
"angular-translate": "^2.13.0",
"angular-moment": "1.0.0-beta.6",
"angular-websocket": "^1.1.0",
"angular": "1.6.5",
"angular-route": "1.6.5",
"angular-sanitize": "1.6.5",
"angular-touch": "1.6.5",
"angular-messages": "1.6.5",
"angular-cookies": "1.6.5",
"angular-animate": "1.6.5",
"angular-translate": "^2.15.2",
"angular-moment": "1.0.1",
"angular-websocket": "^2.0.0",
"angular-utf8-base64": "^0.0.5",
"angular-local-storage": "^0.2.7",
"angular-local-storage": "^0.7.1",
"angular-notification": "775ee861c1737b284588bcb878ba1f4e43c70c97",
"angular-ui-notification": "^0.2.0",
"angular-bittorrent-peerid": "^1.1.1",
"angular-busy": "^4.1.3",
"angular-promise-buttons": "^0.1.18",
"angular-dragula": "^1.2.7",
"ngSweetAlert": "8df6c30b0996f09cb4cf5e90a41115a6c09fa852"
"angular-ui-notification": "^0.3.6",
"angular-bittorrent-peerid": "^1.2.0",
"angular-busy": "^4.1.4",
"angular-promise-buttons": "^0.1.21",
"angular-dragula": "^1.2.8",
"ngSweetAlert": "^1.1.2"
},
"resolutions": {
"angular": "1.4.10",
"angular-animate": "1.4.10"
"angular": "1.6.5"
}
}

View file

@ -7,144 +7,144 @@ var $ = gulpLoadPlugins();
var reload = browserSync.reload;
gulp.task('styles', function () {
return gulp.src([
'src/styles/**/*.css'
]).pipe($.autoprefixer({browsers: ['> 1%', 'last 2 versions', 'Firefox ESR']}))
.pipe(gulp.dest('.tmp/styles'))
.pipe(reload({stream: true}));
return gulp.src([
'src/styles/**/*.css'
]).pipe($.autoprefixer({browsers: ['> 1%', 'last 2 versions', 'Firefox ESR']}))
.pipe(gulp.dest('.tmp/styles'))
.pipe(reload({stream: true}));
});
gulp.task('scripts', function () {
return gulp.src([
'src/scripts/**/*.js'
]).pipe($.plumber())
.pipe(gulp.dest('.tmp/scripts'))
.pipe(reload({stream: true}));
return gulp.src([
'src/scripts/**/*.js'
]).pipe($.plumber())
.pipe(gulp.dest('.tmp/scripts'))
.pipe(reload({stream: true}));
});
gulp.task('views', function () {
return gulp.src([
'src/views/**/*.html'
]).pipe($.htmlmin({collapseWhitespace: true}))
.pipe($.angularTemplatecache({module: 'ariaNg', filename: 'views/templates.js', root: 'views/'}))
.pipe(gulp.dest('.tmp/scripts'));
return gulp.src([
'src/views/**/*.html'
]).pipe($.htmlmin({collapseWhitespace: true}))
.pipe($.angularTemplatecache({module: 'ariaNg', filename: 'views/templates.js', root: 'views/'}))
.pipe(gulp.dest('.tmp/scripts'));
});
gulp.task('lint', function () {
return gulp.src([
'src/scripts/**/*.js'
]).pipe(reload({stream: true, once: true}))
.pipe($.eslint({fix: true}))
.pipe($.eslint.format())
.pipe($.if(!browserSync.active, $.eslint.failAfterError()))
.pipe(gulp.dest('src/scripts'));
return gulp.src([
'src/scripts/**/*.js'
]).pipe(reload({stream: true, once: true}))
.pipe($.eslint.format())
.pipe($.if(!browserSync.active, $.eslint.failAfterError()))
.pipe(gulp.dest('src/scripts'));
});
gulp.task('html', ['styles', 'scripts', 'views'], function () {
return gulp.src([
'src/*.html'
]).pipe($.useref({searchPath: ['.tmp', 'src', '.']}))
.pipe($.injectVersion())
.pipe($.if('js/*.js', $.replace(/\/\/# sourceMappingURL=.*/g, '')))
.pipe($.if('css/*.css', $.replace(/\/\*# sourceMappingURL=.* \*\/$/g, '')))
.pipe($.if(['js/moment-with-locales-*.min.js', 'js/plugins.min.js', 'js/aria-ng.min.js'], $.uglify({preserveComments: 'license'})))
.pipe($.if(['css/plugins.min.css', 'css/aria-ng.min.css'], $.cssnano({safe: true, autoprefixer: false})))
.pipe($.if(['js/plugins.min.js', 'js/aria-ng.min.js', 'css/plugins.min.css', 'css/aria-ng.min.css'], $.rev()))
.pipe($.if('*.html', $.htmlmin({collapseWhitespace: true})))
.pipe($.revReplace())
.pipe(gulp.dest('dist'));
return gulp.src([
'src/*.html'
]).pipe($.useref({searchPath: ['.tmp', 'src', '.']}))
.pipe($.injectVersion())
.pipe($.if('js/*.js', $.replace(/\/\/# sourceMappingURL=.*/g, '')))
.pipe($.if('css/*.css', $.replace(/\/\*# sourceMappingURL=.* \*\/$/g, '')))
.pipe($.if(['js/moment-with-locales-*.min.js', 'js/plugins.min.js', 'js/aria-ng.min.js'], $.uglify({preserveComments: 'license'})))
.pipe($.if(['css/plugins.min.css', 'css/aria-ng.min.css'], $.cssnano({safe: true, autoprefixer: false})))
.pipe($.if(['js/plugins.min.js', 'js/aria-ng.min.js', 'css/plugins.min.css', 'css/aria-ng.min.css'], $.rev()))
.pipe($.if('*.html', $.htmlmin({collapseWhitespace: true})))
.pipe($.revReplace())
.pipe(gulp.dest('dist'));
});
gulp.task('langs', function () {
return gulp.src([
'src/langs/**/*'
]).pipe(gulp.dest('dist/langs'));
return gulp.src([
'src/langs/**/*'
]).pipe(gulp.dest('dist/langs'));
});
gulp.task('images', function () {
return gulp.src([
'src/imgs/**/*'
]).pipe(gulp.dest('dist/imgs'));
return gulp.src([
'src/imgs/**/*'
]).pipe(gulp.dest('dist/imgs'));
});
gulp.task('fonts', function () {
return gulp.src([
'bower_components/font-awesome/fonts/fontawesome-webfont.*'
]).pipe(gulp.dest('.tmp/fonts'))
.pipe(gulp.dest('dist/fonts'));
return gulp.src([
'bower_components/font-awesome/fonts/fontawesome-webfont.*'
]).pipe(gulp.dest('.tmp/fonts'))
.pipe(gulp.dest('dist/fonts'));
});
gulp.task('manifest', function () {
return gulp.src([
'dist/css/**',
'dist/js/**',
'dist/fonts/fontawesome-webfont.woff2',
'dist/*.html'
], {base: 'dist/'})
.pipe($.manifest({
hash: true,
preferOnline: true,
network: ['*'],
filename: 'index.manifest',
exclude: 'index.manifest'
}))
.pipe(gulp.dest('dist'));
return gulp.src([
'dist/css/**',
'dist/js/**',
'dist/fonts/fontawesome-webfont.woff2',
'dist/*.html'
], {base: 'dist/'})
.pipe($.manifest({
hash: true,
preferOnline: true,
network: ['*'],
filename: 'index.manifest',
exclude: 'index.manifest'
}))
.pipe(gulp.dest('dist'));
});
gulp.task('extras', function () {
return gulp.src([
'src/*.*',
'!src/*.html'
], {
dot: true
}).pipe(gulp.dest('dist'));
return gulp.src([
'LICENSE',
'src/*.*',
'!src/*.html'
], {
dot: true
}).pipe(gulp.dest('dist'));
});
gulp.task('clean', del.bind(null, ['.tmp', 'dist']));
gulp.task('serve', ['styles', 'scripts', 'fonts'], function () {
browserSync({
notify: false,
port: 9000,
server: {
baseDir: ['.tmp', 'src'],
routes: {
'/bower_components': 'bower_components'
}
}
});
browserSync({
notify: false,
port: 9000,
server: {
baseDir: ['.tmp', 'src'],
routes: {
'/bower_components': 'bower_components'
}
}
});
gulp.watch([
'src/*.html',
'src/langs/*.txt',
'src/views/*.html',
'src/imgs/**/*',
'.tmp/fonts/**/*'
]).on('change', reload);
gulp.watch([
'src/*.html',
'src/langs/*.txt',
'src/views/*.html',
'src/imgs/**/*',
'.tmp/fonts/**/*'
]).on('change', reload);
gulp.watch('src/styles/**/*.css', ['styles']);
gulp.watch('src/scripts/**/*.js', ['scripts']);
gulp.watch('src/fonts/**/*', ['fonts']);
gulp.watch('src/styles/**/*.css', ['styles']);
gulp.watch('src/scripts/**/*.js', ['scripts']);
gulp.watch('src/fonts/**/*', ['fonts']);
});
gulp.task('serve:dist', function () {
browserSync({
notify: false,
port: 9000,
server: {
baseDir: ['dist']
}
});
browserSync({
notify: false,
port: 9000,
server: {
baseDir: ['dist']
}
});
});
gulp.task('info', function () {
return gulp.src([
'dist/**/*'
]).pipe($.size({title: 'build', gzip: true}));
return gulp.src([
'dist/**/*'
]).pipe($.size({title: 'build', gzip: true}));
});
gulp.task('build', $.sequence('lint', 'html', 'langs', 'images', 'fonts', 'manifest', 'extras', 'info'));
gulp.task('default', ['clean'], function () {
gulp.start('build');
gulp.start('build');
});

View file

@ -4,31 +4,33 @@
"node": ">=4"
},
"devDependencies": {
"browser-sync": "^2.14.0",
"del": "^2.2.1",
"browser-sync": "^2.18.8",
"del": "^2.2.2",
"eslint-config-angular": "^0.5.0",
"eslint-plugin-angular": "^1.6.4",
"gulp": "^3.9.1",
"gulp-angular-templatecache": "^2.0.0",
"gulp-autoprefixer": "^3.1.0",
"gulp-autoprefixer": "^3.1.1",
"gulp-cssnano": "^2.1.2",
"gulp-eslint": "^2.1.0",
"gulp-htmlmin": "^2.0.0",
"gulp-if": "^2.0.1",
"gulp-eslint": "^3.0.1",
"gulp-htmlmin": "^3.0.0",
"gulp-if": "^2.0.2",
"gulp-inject-version": "^1.0.1",
"gulp-load-plugins": "^1.2.4",
"gulp-load-plugins": "^1.5.0",
"gulp-manifest": "^0.1.1",
"gulp-plumber": "^1.1.0",
"gulp-replace": "^0.5.4",
"gulp-rev": "^7.1.0",
"gulp-rev-replace": "^0.4.3",
"gulp-sequence": "^0.4.5",
"gulp-sequence": "^0.4.6",
"gulp-size": "^2.1.0",
"gulp-sourcemaps": "^1.6.0",
"gulp-uglify": "^1.5.4",
"gulp-sourcemaps": "^1.11.1",
"gulp-uglify": "^2.1.2",
"gulp-useref": "^3.1.0"
},
"name": "aria-ng",
"description": "AriaNg, A Better Frontend for aria2",
"version": "0.1.0",
"name": "ariang",
"description": "AriaNg, a modern web frontend making aria2 easier to use.",
"version": "0.3.0",
"main": "index.html",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 0"
@ -38,14 +40,15 @@
"url": "git+https://github.com/mayswind/AriaNg.git"
},
"keywords": [
"Aria2",
"aria2",
"Web",
"Frontend"
"Frontend",
"UI"
],
"author": "MaysWind <i@mayswind.net>",
"license": "MIT",
"bugs": {
"url": "https://github.com/mayswind/AriaNg/issues"
},
"homepage": "https://github.com/mayswind/AriaNg#readme"
"homepage": "http://ariang.mayswind.net/"
}

View file

@ -1,37 +1,18 @@
if [ $CI == "true" ] && [ $CIRCLE_BRANCH == "master" ]; then
echo "Packaging daily build...";
cd dist;
zip $CIRCLE_ARTIFACTS/dist.zip * -r -9;
cd ..;
echo "Publishing online demo...";
cp dist $CIRCLE_ARTIFACTS/ -r;
cp README.md $CIRCLE_ARTIFACTS/;
cp LICENSE $CIRCLE_ARTIFACTS/;
git config --global user.name "CircleCI";
git config --global user.email "CircleCI";
git checkout -b gh-pages remotes/origin/gh-pages;
rm -rf css;
rm -rf fonts;
rm -rf js;
rm -rf langs;
rm -rf imgs;
rm -f downloads/latest_daily_build.zip;
rm -f index.html;
rm -f index.manifest;
rm -f README.md;
rm -f LICENSE;
echo "Publishing daily build...";
git clone https://github.com/mayswind/AriaNg-DailyBuild.git $CIRCLE_ARTIFACTS/AriaNg-DailyBuild/
cp $CIRCLE_ARTIFACTS/dist/* ./ -r;
mv $CIRCLE_ARTIFACTS/dist.zip ./downloads/latest_daily_build.zip;
mv $CIRCLE_ARTIFACTS/README.md ./;
mv $CIRCLE_ARTIFACTS/LICENSE ./;
rm -rf $CIRCLE_ARTIFACTS/AriaNg-DailyBuild/*
cp dist/* $CIRCLE_ARTIFACTS/AriaNg-DailyBuild/ -r;
cd $CIRCLE_ARTIFACTS/AriaNg-DailyBuild/;
git add -A;
git commit -a -m "daily build #$CIRCLE_SHA1";
git push origin gh-pages;
git push origin master;
echo "Done. ";
echo "Done.";
fi

View file

@ -41,16 +41,30 @@
<body class="hold-transition skin-aria-ng sidebar-mini fixed">
<div class="wrapper" ng-controller="MainController" ng-swipe-left="swipeActions.leftSwipe()" ng-swipe-right="swipeActions.rightSwipe()" ng-swipe-disable-mouse>
<header class="main-header">
<a class="logo" href="#">
<span class="logo-mini">Aria</span>
<span class="logo-lg" title="AriaNg %%GULP_INJECT_VERSION%%">AriaNg</span>
</a>
<div class="logo">
<div class="logo-mini">AriaNg</div>
<div class="logo-lg" title="AriaNg %%GULP_INJECT_VERSION%%">
<div class="dropdown">
<span class="dropdown-toggle" data-toggle="dropdown">
<span class="logo-lg-title">AriaNg</span><i class="fa fa-caret-right fa-right-bottom fa-rotate-45 fa-half" aria-hidden="true"></i>
</span>
<ul class="dropdown-menu dropdown-menu-right" role="menu">
<li ng-repeat="setting in rpcSettings" ng-class="{'active': setting.isDefault}">
<a class="pointer-cursor" ng-click="switchRpcSetting(setting)">
<span ng-bind="(setting.rpcAlias ? setting.rpcAlias : setting.rpcHost + ':' + setting.rpcPort)">RPC</span>
<i class="fa" ng-class="{'fa-check': setting.isDefault}"></i>
</a>
</li>
</ul>
</div>
</div>
</div>
<nav class="navbar navbar-static-top" role="navigation">
<div class="navbar-toolbar">
<ul class="nav navbar-nav">
<li>
<a class="toolbar" title="{{'New' | translate}}" ng-href="#/new">
<a class="toolbar" title="{{'New' | translate}}" ng-href="#!/new">
<i class="fa fa-plus"></i>
<span translate>New</span>
</a>
@ -167,17 +181,17 @@
<ul id="siderbar-menu" class="sidebar-menu">
<li class="header" translate>Download</li>
<li data-href-match="/downloading">
<a href="#/downloading"><i class="fa fa-arrow-circle-o-down"></i> <span ng-bind="('Downloading' | translate) + (globalStat && globalStat.numActive > 0 ? ' (' + globalStat.numActive + ')' : '')">Downloading</span></a>
<a href="#!/downloading"><i class="fa fa-arrow-circle-o-down"></i> <span ng-bind="('Downloading' | translate) + (globalStat && globalStat.numActive > 0 ? ' (' + globalStat.numActive + ')' : '')">Downloading</span></a>
</li>
<li data-href-match="/waiting">
<a href="#/waiting"><i class="fa fa-clock-o"></i> <span ng-bind="('Waiting' | translate) + (globalStat && globalStat.numWaiting > 0 ? ' (' + globalStat.numWaiting + ')' : '')">Waiting</span></a>
<a href="#!/waiting"><i class="fa fa-clock-o"></i> <span ng-bind="('Waiting' | translate) + (globalStat && globalStat.numWaiting > 0 ? ' (' + globalStat.numWaiting + ')' : '')">Waiting</span></a>
</li>
<li data-href-match="/stopped">
<a href="#/stopped"><i class="fa fa-check-circle-o"></i> <span ng-bind="('Downloaded / Stopped' | translate) + (globalStat && globalStat.numStopped > 0 ? ' (' + globalStat.numStopped + ')' : '')">Downloaded / Stopped</span></a>
<a href="#!/stopped"><i class="fa fa-check-circle-o"></i> <span ng-bind="('Finished / Stopped' | translate) + (globalStat && globalStat.numStopped > 0 ? ' (' + globalStat.numStopped + ')' : '')">Finished / Stopped</span></a>
</li>
<li class="header" translate>Settings</li>
<li data-href-match="/settings/ariang">
<a href="#/settings/ariang"><i class="fa fa-cog"></i> <span translate>AriaNg Settings</span></a>
<a href="#!/settings/ariang"><i class="fa fa-cog"></i> <span translate>AriaNg Settings</span></a>
</li>
<li class="treeview">
<a href="javascript:void(0);">
@ -189,33 +203,33 @@
</a>
<ul class="treeview-menu">
<li data-href-match="/settings/aria2/basic">
<a href="#/settings/aria2/basic"> <span translate>Basic Settings</span></a>
<a href="#!/settings/aria2/basic"> <span translate>Basic Settings</span></a>
</li>
<li data-href-match="/settings/aria2/http-ftp-sftp">
<a href="#/settings/aria2/http-ftp-sftp"> <span translate>HTTP/FTP/SFTP Settings</span></a>
<a href="#!/settings/aria2/http-ftp-sftp"> <span translate>HTTP/FTP/SFTP Settings</span></a>
</li>
<li data-href-match="/settings/aria2/http">
<a href="#/settings/aria2/http"> <span translate>HTTP Settings</span></a>
<a href="#!/settings/aria2/http"> <span translate>HTTP Settings</span></a>
</li>
<li data-href-match="/settings/aria2/ftp-sftp">
<a href="#/settings/aria2/ftp-sftp"> <span translate>FTP/SFTP Settings</span></a>
<a href="#!/settings/aria2/ftp-sftp"> <span translate>FTP/SFTP Settings</span></a>
</li>
<li data-href-match="/settings/aria2/bt">
<a href="#/settings/aria2/bt"> <span translate>BitTorrent Settings</span></a>
<a href="#!/settings/aria2/bt"> <span translate>BitTorrent Settings</span></a>
</li>
<li data-href-match="/settings/aria2/metalink">
<a href="#/settings/aria2/metalink"> <span translate>Metalink Settings</span></a>
<a href="#!/settings/aria2/metalink"> <span translate>Metalink Settings</span></a>
</li>
<li data-href-match="/settings/aria2/rpc">
<a href="#/settings/aria2/rpc"> <span translate>RPC Settings</span></a>
<a href="#!/settings/aria2/rpc"> <span translate>RPC Settings</span></a>
</li>
<li data-href-match="/settings/aria2/advanced">
<a href="#/settings/aria2/advanced"> <span translate>Advanced Settings</span></a>
<a href="#!/settings/aria2/advanced"> <span translate>Advanced Settings</span></a>
</li>
</ul>
</li>
<li data-href-match="/status">
<a href="#/status"><i class="fa fa-server"></i> <span translate>Aria2 Status</span></a>
<a href="#!/status"><i class="fa fa-server"></i> <span translate>Aria2 Status</span> <span class="label pull-right" ng-if="globalStatusContext.isEnabled" ng-class="{'label-primary': taskContext.rpcStatus === 'Connecting', 'label-success': taskContext.rpcStatus === 'Connected', 'label-danger': taskContext.rpcStatus === 'Not Connected'}" ng-bind="taskContext.rpcStatus | translate"></span></a>
</li>
</ul>
</section>
@ -228,30 +242,55 @@
</div>
<footer class="main-footer">
<a class="sidebar-toggle" data-toggle="offcanvas" role="button" title="{{'Toggle Navigation' | translate}}"></a>
<nav class="navbar" role="navigation">
<div class="navbar-toolbar">
<ul class="nav navbar-nav">
<li>
<a data-toggle="offcanvas" role="button" title="{{'Toggle Navigation' | translate}}">
<i class="fa fa-bars"></i>
</a>
</li>
<li class="divider"></li>
<li class="dropup">
<a class="dropdown-toggle" data-toggle="dropdown" role="button" title="{{'Quick Setting' | translate}}">
<i class="fa fa-wrench"></i>
<span translate>Quick Setting</span>
<i class="fa fa-caret-right fa-right-bottom fa-rotate-45 fa-half" aria-hidden="true"></i>
</a>
<ul class="dropdown-menu" role="menu">
<li>
<a class="pointer-cursor" ng-click="showQuickSettingDialog('globalSpeedLimit', 'Global Speed Limit')">
<span translate>Global Speed Limit</span>
</a>
</li>
</ul>
</li>
</ul>
</div>
<span>&nbsp;</span>
<div class="pull-right ng-cloak" ng-if="globalStatusContext.isEnabled">
<a class="global-status" ng-pop-chart ng-data="globalStatusContext.data" ng-container="body"
ng-placement="top" ng-trigger="click hover" ng-popover-class="global-status-chart">
<div class="pull-right ng-cloak" ng-if="globalStatusContext.isEnabled">
<a class="global-status" title="{{('Click to pin' | translate)}}" ng-pop-chart ng-data="globalStatusContext.data"
ng-container="body" ng-placement="top" ng-trigger="click hover" ng-popover-class="global-status-chart">
<span class="realtime-speed">
<i class="icon-download fa fa-arrow-down"></i>
<span ng-bind="(globalStat.downloadSpeed | readableVolumn) + '/s'"></span>
<span ng-bind="(globalStat.downloadSpeed | readableVolume) + '/s'"></span>
</span>
<span class="realtime-speed">
<span class="realtime-speed">
<i class="icon-upload fa fa-arrow-up"></i>
<span ng-bind="(globalStat.uploadSpeed | readableVolumn) + '/s'"></span>
<span ng-bind="(globalStat.uploadSpeed | readableVolume) + '/s'"></span>
</span>
</a>
</div>
</a>
</div>
</nav>
</footer>
<ng-setting-dialog setting="quickSettingContext"></ng-setting-dialog>
</div>
<!-- build:js js/jquery-2.2.4.min.js -->
<script src="../bower_components/jquery/dist/jquery.min.js"></script>
<!-- endbuild -->
<!-- build:js js/angular-packages-1.4.10.min.js -->
<!-- build:js js/angular-packages-1.6.5.min.js -->
<script src="../bower_components/angular/angular.min.js"></script>
<script src="../bower_components/angular-route/angular-route.min.js"></script>
<script src="../bower_components/angular-sanitize/angular-sanitize.min.js"></script>
@ -263,14 +302,14 @@
<!-- build:js js/bootstrap-3.3.7.min.js -->
<script src="../bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
<!-- endbuild -->
<!-- build:js js/moment-with-locales-2.17.1.min.js -->
<!-- build:js js/moment-with-locales-2.18.1.min.js -->
<script src="../bower_components/moment/min/moment.min.js"></script>
<script src="../bower_components/moment/locale/zh-cn.js"></script>
<script src="../bower_components/moment/locale/zh-tw.js"></script>
<script src="../bower_components/moment-timezone/builds/moment-timezone-with-data-2010-2020.min.js"></script>
<script src="../bower_components/moment-timezone/builds/moment-timezone-with-data-2012-2022.min.js"></script>
<!-- endbuild -->
<!-- build:js js/echarts-3.3.1.min.js -->
<script src="../bower_components/echarts/dist/echarts.min.js"></script>
<!-- build:js js/echarts-common-3.7.1.min.js -->
<script src="../bower_components/echarts/dist/echarts.common.min.js"></script>
<!-- endbuild -->
<!-- build:js js/plugins.min.js -->
<script src="../bower_components/AdminLTE/dist/js/app.min.js"></script>
@ -279,7 +318,7 @@
<script src="../bower_components/bootstrap-contextmenu/bootstrap-contextmenu.js"></script>
<script src="../bower_components/angular-translate/angular-translate.min.js"></script>
<script src="../bower_components/angular-moment/angular-moment.min.js"></script>
<script src="../bower_components/angular-websocket/angular-websocket.min.js"></script>
<script src="../bower_components/angular-websocket/dist/angular-websocket.min.js"></script>
<script src="../bower_components/angular-utf8-base64/angular-utf8-base64.min.js"></script>
<script src="../bower_components/angular-local-storage/dist/angular-local-storage.min.js"></script>
<script src="../bower_components/angular-notification/angular-notification.js"></script>
@ -320,7 +359,9 @@
<script src="scripts/directives/chart.js"></script>
<script src="scripts/directives/placeholder.js"></script>
<script src="scripts/directives/setting.js"></script>
<script src="scripts/directives/settingDialog.js"></script>
<script src="scripts/directives/tooltip.js"></script>
<script src="scripts/directives/validUrls.js"></script>
<script src="scripts/filters/dateDuration.js"></script>
<script src="scripts/filters/fileOrderBy.js"></script>
<script src="scripts/filters/longDate.js"></script>
@ -328,13 +369,14 @@
<script src="scripts/filters/percent.js"></script>
<script src="scripts/filters/taskOrderBy.js"></script>
<script src="scripts/filters/taskStatus.js"></script>
<script src="scripts/filters/volumn.js"></script>
<script src="scripts/filters/volume.js"></script>
<script src="scripts/services/ariaNgCommonService.js"></script>
<script src="scripts/services/ariaNgFileService.js"></script>
<script src="scripts/services/ariaNgLanguageLoader.js"></script>
<script src="scripts/services/ariaNgMonitorService.js"></script>
<script src="scripts/services/ariaNgNotificationService.js"></script>
<script src="scripts/services/ariaNgSettingService.js"></script>
<script src="scripts/services/ariaNgTitleService.js"></script>
<script src="scripts/services/ariaNgLogService.js"></script>
<script src="scripts/services/aria2HttpRpcService.js"></script>
<script src="scripts/services/aria2WebSocketRpcService.js"></script>

View file

@ -11,10 +11,10 @@ Connecting=连接中
Connected=已连接
Not Connected=未连接
Global=全局
RPC=RPC
New=新建
Start=开始任务
Pause=暂停任务
Restart=重试
Delete=删除任务
Select All=全选
Select None=不选
@ -27,12 +27,14 @@ Expand=展开
Collapse=折叠
Remove Task=删除任务
Clear Stopped Tasks=清空已结束任务
Click to view task detail=点击查看任务详情
By File Name=按文件名
By File Size=按文件大小
By Progress=按进度
By Remain Time=按剩余时间
By Download Speed=按下载速度
By Upload Speed=按上传速度
Filters=过滤器
Download=下载
Upload=上传
Downloading=正在下载
@ -42,8 +44,9 @@ Paused=已暂停
Completed=已完成
Error Occurred=发生错误
Removed=已删除
Downloaded / Stopped=已完成 / 已停止
Finished / Stopped=已完成 / 已停止
Uncompleted=未完成
Click to pin=点击固定
Settings=系统设置
AriaNg Settings=AriaNg 设置
Aria2 Settings=Aria2 设置
@ -64,6 +67,9 @@ Remain Time=剩余时间
Download Speed=下载速度
Upload Speed=上传速度
Links=链接
Torrent File=种子文件
Metalink File=Metalink 文件
File Name:=文件名:
Options=选项
Overview=总览
Blocks=区块信息
@ -78,6 +84,7 @@ Info Hash=特征值
Seeders=种子数
Connections=连接数
Seed Creation Time=种子创建时间
Download Url=下载地址
Download Dir=下载路径
BT Tracker Servers=BT 服务器
(Choose Files)=(选择文件)
@ -95,6 +102,9 @@ Speed=速度
No Data=无数据
No connected peers=没有连接到其他节点
Failed to change some tasks state.=修改一些任务状态时失败.
Confirm Restart=确认重试
Are you sure you want to restart this task? AriaNg will create a same task after clicking OK.=您是否要重试这个任务? 点击 "确定" 后, AriaNg 将会创建一个相同的任务.
Failed to restart this task.=该任务重试失败.
Confirm Remove=确认删除
Are you sure you want to remove the selected task?=您是否要删除选中的任务?
Failed to remove some task(s).=删除一些任务时失败.
@ -115,21 +125,37 @@ Download Error=下载出错
Language=语言
Debug Mode=调试模式
Page Title=页面标题
Preview=预览
Tips: You can use the "noprefix" tag to ignore the prefix, "nosuffix" tag ignore the suffix, and "scale\=n" tag to set the decimal precision.=小提示: 您可以使用 "noprefix" 标签忽略前缀, "nosuffix" 标签忽略后缀, 以及 "scale\=n" 标签设置小数的精度.
Example: ${downspeed:noprefix:nosuffix:scale\=1}=示例: ${downspeed:noprefix:nosuffix:scale\=1}
Page Title Refresh Interval=页面标题刷新间隔
Enable Browser Notification=启用浏览器通知
Aria2 RPC Host=Aria2 RPC 主机
Aria2 RPC Port=Aria2 RPC 端口
Aria2 RPC Protocol=Aria2 RPC 协议
Aria2 RPC Secret Token=Aria2 RPC 密钥
Aria2 RPC Alias=Aria2 RPC 别名
Aria2 RPC Address=Aria2 RPC 地址
Aria2 RPC Protocol=Aria2 RPC 协议
Aria2 RPC Http Request Method=Aria2 RPC Http 请求方法
POST method only supports aria2 v1.15.2 and above.=POST 方法仅支持 aria2 v1.15.2 及以上.
Aria2 RPC Secret Token=Aria2 RPC 密钥
Activate=激活
Reset Settings=重置设置
Confirm Reset=确认重置
Are you sure you want to reset all settings?=您是否要重置所有设置?
Delete RPC Setting=删除 RPC 设置
Add New RPC Setting=添加新 RPC 配置
Are you sure you want to remove rpc setting "{{rpcName}}"?=您是否要删除 RPC 设置 "{{rpcName}}"?
Global Stat Refresh Interval=全局状态刷新间隔
Download Task Refresh Interval=下载任务刷新间隔
Action After Creating New Tasks=创建新任务后执行操作
Navigate to Task List Page=转到任务列表页面
Navigate to Task Detail Page=转到任务详情页面
Supported Placeholder=支持的占位符
AriaNg Title=AriaNg 标题
Downloading Count=正在下载数量
Waiting Count=正在等待数量
Stopped Count=已停止数量
You have disabled notification in your browser. You should change your browser's settings before you enable this function.=您已经在浏览器中禁用通知功能. 如需使用此功能, 请修改您浏览器的设置.
Configuration has been modified, please reload the page for the changes to take effect.=配置已经修改, 请重新加载页面使其生效.
Reload Page=重新加载页面
Show Secret=显示密钥
Hide Secret=隐藏密钥
Aria2 Version=Aria2 版本
@ -138,10 +164,12 @@ Functions=方法
Save Session=保存会话
Shutdown Aria2=关闭 Aria2
Confirm Shutdown=确认关闭
Are you sure you want to shutdown aria2?=您是否要关闭 Aria2?
Are you sure you want to shutdown aria2?=您是否要关闭 aria2?
Session has been saved successfully.=会话已经成功保存.
Aria2 has been shutdown successfully.=Aria2 已经成功关闭.
Toggle Navigation=切换导航
Quick Setting=快捷设置
Global Speed Limit=全局速度限制
Loading=正在加载...
More Than One Day=超过1天
Unknown=未知
@ -151,19 +179,30 @@ Minutes=分
Seconds=秒
Milliseconds=毫秒
Http=Http
Http (Disabled)=Http (禁用)
Https=Https
WebSocket=WebSocket
WebSocket (Disabled)=WebSocket (禁用)
WebSocket (Security)=WebSocket (安全)
Http and WebSocket would be disabled when accessing AriaNg via Https.=使用 Https 访问 AriaNg 时Http 和 WebSocket 将被禁用.
POST=POST
GET=GET
Disabled=禁用
BitTorrent=BitTorrent
Changes to the settings take effect after refreshing page.=设置将在页面刷新后生效.
Type is illegal!=类型错误!
Parameter is invalid!=请求参数无效
Parameter is invalid!=请求参数无效!
Option value cannot be empty!=参数内容不能为空!
Input number is invalid!=输入的数字无效!
Input number is below min value!=输入的数字小于最小值 {{value}} !
Input number is above max value!=输入的数字大于最大值 {{value}} !
Input value is invalid!=输入的内容无效!
Protocol is invalid!=协议无效!
RPC host cannot be empty!=RPC 主机不能为空!
RPC secret is not base64 encoded!=RPC 密钥不是 Base64 编码后的字符串!
URL is not base64 encoded!=指定 URL 不是 Base64 编码后的字符串!
Tap to configure and get started with AriaNg.=您还没有进行过设置, 点击这里进行设置.
Cannot initialize WebSocket!=无法初始化 WebSocket!
[error]
unknown=未知错误.
@ -207,6 +246,8 @@ time.minute={{value}} 分钟
time.minutes={{value}} 分钟
time.hour={{value}} 小时
time.hours={{value}} 小时
requires.aria2-version=需要 aria2 v{{version}} 或更高版本
task.new.download-links=下载链接 ({{count}} 个链接):
task.pieceinfo=已完成: {{completed}}, 共计: {{total}} 块
task.error-occurred=发生错误 ({{errorcode}})
settings.file-count=({{count}} 个文件)
@ -275,6 +316,8 @@ netrc-path.description=
no-netrc.name=禁用 netrc
no-netrc.description=
no-proxy.name=不使用代理服务器列表
out.name=文件名
out.description=下载文件的文件名. 其总是相对于 --dir 选项中设置的路径. 当使用 --force-sequential 参数时此选项无效.
no-proxy.description=设置不使用代理服务器的主机名, 域名, 包含或不包含子网掩码的网络地址, 多个使用逗号分隔.
proxy-method.name=代理服务器请求方法
proxy-method.description=设置用来请求代理服务器的方法. 方法可设置为 GET 或 TUNNEL. HTTPS 下载将忽略此选项并总是使用 TUNNEL.
@ -361,7 +404,7 @@ bt-enable-lpd.description=
bt-exclude-tracker.name=BT 排除服务器地址
bt-exclude-tracker.description=逗号分隔的 BT 排除服务器地址. 您可以使用 * 匹配所有地址, 因此将排除所有服务器地址. 当在 shell 命令行使用 * 时, 需要使用转义符或引号.
bt-external-ip.name=外部 IP 地址
bt-external-ip.description=指定用来报告给 BT 服务器的外部 IP 地址. 虽然这个方法叫外部, 但其可以接受各种类型的 IP 地址. IP 地址需要使用数字类型的地址.
bt-external-ip.description=指定用在 BitTorrent 下载和 DHT 中的外部 IP 地址. 它可能被发送到 BitTorrent 服务器. 对于 DHT, 此选项将会报告本地节点正在下载特定的种子. 这对于在私有网络中使用 DHT 非常关键. 虽然这个方法叫外部, 但其可以接受各种类型的 IP 地址.
bt-force-encryption.name=强制加密
bt-force-encryption.description=BT 消息中的内容需要使用 arc4 加密. 此选项是设置 --bt-require-crypto --bt-min-crypto-level=arc4 这两个选项的快捷方式. 此选项不会修改上述两个选项的内容. 如果设置为"是", 将拒绝以前的 BT 握手, 并仅使用模糊握手及加密消息.
bt-hash-check-seed.name=做种前检查文件哈希
@ -387,7 +430,7 @@ bt-save-metadata.description=保存种子文件为 ".torrent" 文件. 此选项
bt-seed-unverified.name=不检查已经下载的文件
bt-seed-unverified.description=不检查之前下载文件中每个分片的哈希值.
bt-stop-timeout.name=无速度时自动停止时间
bt-stop-timeout.description=当 BT 任务F下载速度持续为 0, 达到选项设置的时间后停止下载. 如果设置为 0, 此功能将禁用.
bt-stop-timeout.description=当 BT 任务F下载速度持续为 0, 达到选项设置的时间后停止下载. 如果设置为 0, 此功能将禁用.
bt-tracker.name=BT 服务器地址
bt-tracker.description=逗号分隔的 BT 服务器地址. 如果服务器地址在 --bt-exclude-tracker 选项中, 其将不会生效.
bt-tracker-connect-timeout.name=BT 服务器连接超时时间
@ -443,7 +486,7 @@ metalink-enable-unique-protocol.description=如果一个 Metalink 文件可用
enable-rpc.name=启用 JSON-RPC/XML-RPC 服务器
enable-rpc.description=
pause-metadata.name=种子文件下载完后暂停
pause-metadata.description=当种子文件下载完成后暂停后续的下载. 在 aria2 中有 3 种种子文件的下载类型: (1) 下载 .torrent 文件. (2) 通过磁链下载的种子文件. (3) 下载 Metalink 文件. 这些种子文件下载完后会根据文件内容继续进行下载. 这个选项会暂停这些后续的下载. 此选项仅当 --enable-rpc 选项启用时生效.
pause-metadata.description=当种子文件下载完成后暂停后续的下载. 在 aria2 中有 3 种种子文件的下载类型: (1) 下载 .torrent 文件. (2) 通过磁链下载的种子文件. (3) 下载 Metalink 文件. 这些种子文件下载完后会根据文件内容继续进行下载. 选项会暂停这些后续的下载. 此选项仅当 --enable-rpc 选项启用时生效.
rpc-allow-origin-all.name=接受所有远程请求
rpc-allow-origin-all.description=在 RPC 响应头增加 Access-Control-Allow-Origin 字段, 值为 * .
rpc-listen-all.name=在所有网卡上监听
@ -465,7 +508,7 @@ always-resume.description=始终断点续传. 如果设置为"是", aria2 始终
async-dns.name=异步 DNS
async-dns.description=
auto-file-renaming.name=文件自动重命名
auto-file-renaming.description=重新命名已经存在的文件. 此选项仅对 HTTP(S)/FTP 下载有效. 新的文件名后会追加句点和数字(1..9999).
auto-file-renaming.description=重新命名已经存在的文件. 此选项仅对 HTTP(S)/FTP 下载有效. 新的文件名后会在文件名后、扩展名 (如果有) 前追加句点和数字(1..9999).
auto-save-interval.name=自动保存间隔
auto-save-interval.description=每隔设置的秒数自动保存控制文件(*.aria2). 如果设置为 0, 下载期间控制文件不会自动保存. 不论设置的值为多少, aria2 会在任务结束时保存控制文件. 可以设置的值为 0 到 600.
conditional-get.name=条件下载
@ -474,6 +517,8 @@ conf-path.name=配置文件路径
conf-path.description=
console-log-level.name=控制台日志级别
console-log-level.description=
content-disposition-default-utf8.name=使用 UTF-8 处理 Content-Disposition
content-disposition-default-utf8.description=处理 "Content-Disposition" 头中的字符串时使用 UTF-8 字符集来代替 ISO-8859-1, 例如, 文件名参数, 但不是扩展版本的文件名.
daemon.name=启用后台进程
daemon.description=
deferred-input.name=延迟加载
@ -495,7 +540,7 @@ enable-mmap.description=内存中存放映射文件. 当文件空间没有预先
event-poll.name=事件轮询方法
event-poll.description=设置事件轮训的方法. 可选的值包括 epoll, kqueue, port, poll 和 select. 对于 epoll, kqueue, port 和 poll, 只有系统支持时才可用. 最新的 Linux 支持 epoll. 各种 *BSD 系统包括 Mac OS X 支持 kqueue. Open Solaris 支持 port. 默认值根据您使用的操作系统不同而不同.
file-allocation.name=文件分配方法
file-allocation.description=指定文件分配方法. "无" 不会预先分配文件空间. "prealloc"会在下载开始前预先分配空间. 这将会根据文件的大小需要一定的时间. 如果您使用的是较新的文件系统, 例如 ext4 (带扩展支持), btrfs, xfs 或 NTFS (仅 MinGW 构建), "falloc" 是最好的选择. 其几乎可以瞬间分配大(数 GiB)文件. 不要在旧的文件系统, 例如 ext3 和 FAT32 上使用 falloc, 因为与 prealloc 花费的时间相同, 并且其会阻塞 aria2 知道分配完成. 当您的系统不支持 posix_fallocate(3) 函数时, falloc 可能无法使用. "trunc" 使用 ftruncate(2) 系统调用或平台特定的实现将文件截取到特定的长度.
file-allocation.description=指定文件分配方法. "无" 不会预先分配文件空间. "prealloc"会在下载开始前预先分配空间. 这将会根据文件的大小需要一定的时间. 如果您使用的是较新的文件系统, 例如 ext4 (带扩展支持), btrfs, xfs 或 NTFS (仅 MinGW 构建), "falloc" 是最好的选择. 其几乎可以瞬间分配大(数 GiB)文件. 不要在旧的文件系统, 例如 ext3 和 FAT32 上使用 falloc, 因为与 prealloc 花费的时间相同, 并且其会阻塞 aria2 知道分配完成. 当您的系统不支持 posix_fallocate(3) 函数时, falloc 可能无法使用. "trunc" 使用 ftruncate(2) 系统调用或平台特定的实现将文件截取到特定的长度. 在多文件的 BitTorrent 下载中, 若某文件与其相邻的文件共享相同的分片时, 则相邻的文件也会被分配.
force-save.name=强制保存
force-save.description=即使任务完成或删除时使用 --save-session 选项时也保存该任务. 此选项在这种情况下还会保存控制文件. 此选项可以保存被认为已经完成但正在做种的 BT 任务.
save-not-found.name=保存未找到的文件

597
src/langs/zh_Hant.txt Normal file
View file

@ -0,0 +1,597 @@
[default]
Operation Succeeded=操作成功
Connection Succeeded=連接成功
Error=錯誤
OK=確定
Confirm=確認
Cancel=取消
True=是
False=否
Connecting=連接中
Connected=已連接
Not Connected=未連接
Global=全局
New=新建
Start=開始任務
Pause=暫停任務
Restart=重試
Delete=刪除任務
Select All=全選
Select None=不選
Select Invert=反選
Display Order=顯示順序
Help=幫助
Search=搜索
Default=默認
Expand=展開
Collapse=摺疊
Remove Task=刪除任務
Clear Stopped Tasks=清空已結束任務
Click to view task detail=點擊查看任務詳情
By File Name=按文件名
By File Size=按文件大小
By Progress=按進度
By Remain Time=按剩餘時間
By Download Speed=按下載速度
By Upload Speed=按上傳速度
Filters=過濾器
Download=下載
Upload=上傳
Downloading=正在下載
Seeding=正在做種
Waiting=正在等待
Paused=已暫停
Completed=已完成
Error Occurred=發生錯誤
Removed=已刪除
Finished / Stopped=已完成 / 已停止
Uncompleted=未完成
Click to pin=點擊固定
Settings=系統設置
AriaNg Settings=AriaNg 設置
Aria2 Settings=Aria2 設置
Basic Settings=基本設置
HTTP/FTP/SFTP Settings=HTTP/FTP/SFTP 設置
HTTP Settings=HTTP 設置
FTP/SFTP Settings=FTP/SFTP 設置
BitTorrent Settings=BitTorrent 設置
Metalink Settings=Metalink 設置
RPC Settings=RPC 設置
Advanced Settings=高級設置
Aria2 Status=Aria2 狀態
File Name=文件名
File Size=大小
Progress=進度
Share Ratio=分享率
Remain Time=剩餘時間
Download Speed=下載速度
Upload Speed=上傳速度
Links=鏈接
Torrent File=種子文件
Metalink File=Metalink 文件
File Name:=文件名:
Options=選項
Overview=總覽
Blocks=區塊信息
Files=文件列表
Peers=連接狀態
Task Name=任務名稱
Task Size=任務大小
Task Status=任務狀態
Error Description=錯誤描述
Health Percentage=健康度
Info Hash=特徵值
Seeders=種子數
Connections=連接數
Seed Creation Time=種子創建時間
Download Url=下載地址
Download Dir=下載路徑
BT Tracker Servers=BT 伺服器
(Choose Files)=(選擇文件)
Videos=視頻
Audios=音頻
Pictures=圖片
Documents=文檔
Applications=應用程序
Archives=存檔文件
Address=地址
Client=客戶端
Status=狀態
Speed=速度
(local)=(本機)
No Data=無數據
No connected peers=沒有連接到其他節點
Failed to change some tasks state.=修改一些任務狀態時失敗.
Confirm Restart=確認重試
Are you sure you want to restart this task? AriaNg will create a same task after clicking OK.=您是否要重試這個任務? 點擊 "確定" 後, AriaNg 將會創建一個相同的任務.
Failed to restart this task.=該任務重試失敗.
Confirm Remove=確認刪除
Are you sure you want to remove the selected task?=您是否要刪除選中的任務?
Failed to remove some task(s).=刪除一些任務時失敗.
Confirm Clear=確認清除
Are you sure you want to clear stopped tasks?=您是否要清除已結束的任務?
Download Links:=下載鏈接:
Start Download=立即下載
Manual Download=手動下載
Open Torrent File=打開種子文件
Open Metalink File=打開 Metalink 文件
Support multiple URLs, one URL per line.=支持多個 URL 地址, 每個地址佔一行.
Your browser does not support loading file!=您的瀏覽器不支持載入文件!
The selected file type is invalid!=選擇的文件類型無效!
Failed to load file!=載入文件失敗!
Download Completed=下載完成
BT Download Completed=BT 下載完成
Download Error=下載出錯
Language=語言
Debug Mode=調試模式
Page Title=頁面標題
Preview=預覽
Tips: You can use the "noprefix" tag to ignore the prefix, "nosuffix" tag ignore the suffix, and "scale\=n" tag to set the decimal precision.=小提示: 您可以使用 "noprefix" 標籤忽略前綴, "nosuffix" 標籤忽略後綴, 以及 "scale\=n" 標籤設置小數的精度.
Example: ${downspeed:noprefix:nosuffix:scale\=1}=示例: ${downspeed:noprefix:nosuffix:scale\=1}
Page Title Refresh Interval=頁面標題刷新間隔
Enable Browser Notification=啟用瀏覽器通知
Aria2 RPC Alias=Aria2 RPC 別名
Aria2 RPC Address=Aria2 RPC 地址
Aria2 RPC Protocol=Aria2 RPC 協議
Aria2 RPC Http Request Method=Aria2 RPC Http 請求方法
POST method only supports aria2 v1.15.2 and above.=POST 方法僅支持 aria2 v1.15.2 及以上.
Aria2 RPC Secret Token=Aria2 RPC 密鑰
Activate=激活
Reset Settings=重置設置
Confirm Reset=確認重置
Are you sure you want to reset all settings?=您是否要重置所有設置?
Delete RPC Setting=刪除 RPC 設置
Add New RPC Setting=添加新 RPC 配置
Are you sure you want to remove rpc setting "{{rpcName}}"?=您是否要刪除 RPC 設置 "{{rpcName}}"?
Global Stat Refresh Interval=全局狀態刷新間隔
Download Task Refresh Interval=下載任務刷新間隔
Action After Creating New Tasks=創建新任務後執行操作
Navigate to Task List Page=轉到任務列表頁面
Navigate to Task Detail Page=轉到任務詳情頁面
Supported Placeholder=支持的佔位符
AriaNg Title=AriaNg 標題
Downloading Count=正在下載數量
Waiting Count=正在等待數量
Stopped Count=已停止數量
You have disabled notification in your browser. You should change your browser's settings before you enable this function.=您已經在瀏覽器中禁用通知功能. 如需使用此功能, 請修改您瀏覽器的設置.
Configuration has been modified, please reload the page for the changes to take effect.=配置已經修改, 請重新載入頁面使其生效.
Reload Page=重新載入頁面
Show Secret=顯示密鑰
Hide Secret=隱藏密鑰
Aria2 Version=Aria2 版本
Enabled Features=已啟用的功能
Functions=方法
Save Session=保存會話
Shutdown Aria2=關閉 Aria2
Confirm Shutdown=確認關閉
Are you sure you want to shutdown aria2?=您是否要關閉 aria2?
Session has been saved successfully.=會話已經成功保存.
Aria2 has been shutdown successfully.=Aria2 已經成功關閉.
Toggle Navigation=切換導航
Quick Setting=快捷設置
Global Speed Limit=全局速度限制
Loading=正在載入...
More Than One Day=超過1天
Unknown=未知
Bytes=位元組
Hours=小時
Minutes=分
Seconds=秒
Milliseconds=毫秒
Http=Http
Http (Disabled)=Http (禁用)
Https=Https
WebSocket=WebSocket
WebSocket (Disabled)=WebSocket (禁用)
WebSocket (Security)=WebSocket (安全)
Http and WebSocket would be disabled when accessing AriaNg via Https.=使用 Https 訪問 AriaNg 時Http 和 WebSocket 將被禁用.
POST=POST
GET=GET
Disabled=禁用
BitTorrent=BitTorrent
Changes to the settings take effect after refreshing page.=設置將在頁面刷新後生效.
Type is illegal!=類型錯誤!
Parameter is invalid!=請求參數無效!
Option value cannot be empty!=參數內容不能為空!
Input number is invalid!=輸入的數字無效!
Input number is below min value!=輸入的數字小於最小值 {{value}} !
Input number is above max value!=輸入的數字大於最大值 {{value}} !
Input value is invalid!=輸入的內容無效!
Protocol is invalid!=協議無效!
RPC host cannot be empty!=RPC 主機不能為空!
RPC secret is not base64 encoded!=RPC 密鑰不是 Base64 編碼後的字元串!
URL is not base64 encoded!=指定 URL 不是 Base64 編碼後的字元串!
Tap to configure and get started with AriaNg.=您還沒有進行過設置, 點擊這裡進行設置.
Cannot initialize WebSocket!=無法初始化 WebSocket!
[error]
unknown=未知錯誤.
operation.timeout=操作超時.
resource.notfound=無法找到指定資源.
error.resource.notfound.max-file-not-found=無法找到指定資源. 參見 --max-file-not-found option 參數.
error.download.aborted.lowest-speed-limit=由於下載速度過慢, 下載已經終止. 參見 --lowest-speed-limit option 參數.
error.network.problem=網路問題.
resume.notsupported=伺服器不支持斷點續傳.
space.notenough=可用磁碟空間不足.
piece.length.different=分片大小與 .aria2 控制文件中的不同. 參見 --allow-piece-length-change 參數.
download.sametime=aria2 已經下載了另一個相同文件.
download.torrent.sametime=aria2 已經下載了另一個相同哈希的種子文件.
file.exists=文件已經存在. 參見 --allow-overwrite 參數.
file.rename.failed=文件重命名失敗. 參見 --auto-file-renaming 參數.
file.open.failed=文件打開失敗.
file.create.failed=文件創建或刪除已有文件失敗.
io.error=文件系統出錯.
directory.create.failed=無法創建指定目錄.
name.resolution.failed=域名解析失敗.
metalink.file.parse.failed=解析 Metalink 文件失敗.
ftp.command.failed=FTP 命令執行失敗.
http.response.header.bad=HTTP 返回頭無效或無法識別.
redirects.toomany=指定地址重定向過多.
http.authorization.failed=HTTP 認證失敗.
bencoded.file.parse.failed=解析種子文件失敗.
torrent.file.corrupted=指定 ".torrent" 種子文件已經損壞或缺少 aria2 需要的信息.
magnet.uri.bad=指定磁鏈地址無效.
option.bad=設置錯誤.
server.overload=遠程伺服器繁忙, 無法處理當前請求.
rpc.request.parse.failed=處理 RPC 請求失敗.
checksum.failed=文件校驗失敗.
[format]
longdate=YYYY年MM月DD日 HH:mm:ss
time.millisecond={{value}} 毫秒
time.milliseconds={{value}} 毫秒
time.second={{value}} 秒
time.seconds={{value}} 秒
time.minute={{value}} 分鐘
time.minutes={{value}} 分鐘
time.hour={{value}} 小時
time.hours={{value}} 小時
requires.aria2-version=需要 aria2 v{{version}} 或更高版本
task.new.download-links=下載鏈接 ({{count}} 个鏈接):
task.pieceinfo=已完成: {{completed}}, 共計: {{total}} 塊
task.error-occurred=發生錯誤 ({{errorcode}})
settings.file-count=({{count}} 個文件)
settings.total-count=(共計: {{count}}個)
[rpc.error]
unauthorized=認證失敗!
[options]
true=是
false=否
default=默認
none=無
hide=隱藏
full=完整
http=Http
https=Https
ftp=Ftp
mem=僅內存
plain=明文
arc4=ARC4
binary=二進位
ascii=ASCII
debug=調試 (Debug)
info=普通 (Info)
notice=一般 (Notice)
warn=警告 (Warn)
error=錯誤 (Error)
adaptive=自適應
feedback=反饋
geom=幾何
inorder=順序
random=隨機
dir.name=下載路徑
dir.description=
log.name=日誌文件
log.description=日誌文件的路徑. 如果設置為 "-", 日誌則寫入到 stdout. 如果設置為空字元串(""), 日誌將不會記錄到磁碟上.
max-concurrent-downloads.name=最大同時下載數
max-concurrent-downloads.description=
check-integrity.name=檢查完整性
check-integrity.description=通過對文件的每個分塊或整個文件進行哈希驗證來檢查文件的完整性. 此選項僅對BT、Metalink及設置了 --checksum 選項的 HTTP(S)/FTP 鏈接生效.
continue.name=斷點續傳
continue.description=繼續下載部分完成的文件. 啟用此選項可以繼續下載從瀏覽器或其他程序按順序下載的文件. 此選項目前只支持 HTTP(S)/FTP 下載的文件.
all-proxy.name=代理伺服器
all-proxy.description=設置所有協議的代理伺服器地址. 如果覆蓋之前設置的代理伺服器, 使用 "" 即可. 您還可以針對特定的協議覆蓋此選項, 即使用 --http-proxy, --https-proxy 和 --ftp-proxy 選項. 此設置將會影響所有下載. 代理伺服器地址的格式為 [http://][USER:PASSWORD@]HOST[:PORT].
all-proxy-user.name=代理伺服器用戶名
all-proxy-user.description=
all-proxy-passwd.name=代理伺服器密碼
all-proxy-passwd.description=
connect-timeout.name=連接超時時間
connect-timeout.description=設置建立 HTTP/FTP/代理伺服器 連接的超時時間(秒). 當連接建立後, 此選項不再生效, 請使用 --timeout 選項.
dry-run.name=模擬運行
dry-run.description=如果設置為"是", aria2 將僅檢查遠程文件是否存在而不會下載文件內容. 此選項僅對 HTTP/FTP 下載生效. 如果設置為 true, BT 下載將會直接取消.
lowest-speed-limit.name=最小速度限制
lowest-speed-limit.description=當下載速度低於此選項設置的值(B/s) 時將會關閉連接. 0 表示不設置最小速度限制. 您可以增加數值的單位 K 或 M (1K = 1024, 1M = 1024K). 此選項不會影響 BT 下載.
max-connection-per-server.name=單伺服器最大連接數
max-connection-per-server.description=
max-file-not-found.name=文件未找到重試次數
max-file-not-found.description=如果 aria2 從遠程 HTTP/FTP 伺服器收到 "文件未找到" 的狀態超過此選項設置的次數後下載將會失敗. 設置為 0 將會禁用此選項. 此選項僅影響 HTTP/FTP 伺服器. 重試時同時會記錄重試次數, 所以也需要設置 --max-tries 這個選項.
max-tries.name=最大嘗試次數
max-tries.description=設置最大嘗試次數. 0 表示不限制.
min-split-size.name=最小文件分片大小
min-split-size.description=aria2 不會分割小於 2*SIZE 位元組的文件. 例如, 文件大小為 20MB, 如果 SIZE 為 10M, aria2 會把文件分成 2 段 [0-10MB) 和 [10MB-20MB), 並且使用 2 個源進行下載 (如果 --split >= 2). 如果 SIZE 為 15M, 由於 2*15M > 20MB, 因此 aria2 不會分割文件並使用 1 個源進行下載. 您可以增加數值的單位 K 或 M (1K = 1024, 1M = 1024K). 可以設置的值為: 1M-1024M.
netrc-path.name=.netrc 文件路徑
netrc-path.description=
no-netrc.name=禁用 netrc
no-netrc.description=
no-proxy.name=不使用代理伺服器列表
out.name=文件名
out.description=下載文件的文件名. 其總是相對於 --dir 選項中設置的路徑. 當使用 --force-sequential 參數時此選項無效.
no-proxy.description=設置不使用代理伺服器的主機名, 域名, 包含或不包含子網掩碼的網路地址, 多個使用逗號分隔.
proxy-method.name=代理伺服器請求方法
proxy-method.description=設置用來請求代理伺服器的方法. 方法可設置為 GET 或 TUNNEL. HTTPS 下載將忽略此選項並總是使用 TUNNEL.
remote-time.name=獲取伺服器文件時間
remote-time.description=從 HTTP/FTP 服務獲取遠程文件的時間戳, 如果可用將設置到本地文件
reuse-uri.name=URI 復用
reuse-uri.description=當所有給定的 URI 地址都已使用, 繼續使用已經使用過的 URI 地址.
retry-wait.name=重試等待時間
retry-wait.description=設置重試間隔時間(秒). 當此選項的值大於 0 時, aria2 在 HTTP 伺服器返回 503 響應時將會重試.
server-stat-of.name=伺服器狀態保存文件
server-stat-of.description=指定用來保存伺服器狀態的文件名. 您可以使用 --server-stat-if 參數讀取保存的數據.
server-stat-timeout.name=伺服器狀態超時
server-stat-timeout.description=指定伺服器狀態的過期時間 (單位為秒).
split.name=單任務連接數
split.description=下載時使用 N 個連接. 如果提供超過 N 個 URI 地址, 則使用前 N 個地址, 剩餘的地址將作為備用. 如果提供的 URI 地址不足 N 個, 這些地址多次使用以保證同時建立 N 個連接. 同一伺服器的連接數會被 --max-connection-per-server 選項限制.
stream-piece-selector.name=分片選擇演算法
stream-piece-selector.description=指定 HTTP/FTP 下載使用的分片選擇演算法. 分片表示的是並行下載時固定長度的分隔段. 如果設置為"默認", aria2 將會按減少建立連接數選擇分片. 由於建立連接操作的成本較高, 因此這是合理的默認行為. 如果設置為"順序", aria2 將選擇索引最小的分片. 索引為 0 時表示為文件的第一個分片. 這將有助於視頻的邊下邊播. --enable-http-pipelining 選項有助於減少重連接的開銷. 請注意, aria2 依賴於 --min-split-size 選項, 所以有必要對 --min-split-size 選項設置一個合理的值. 如果設置為"隨機", aria2 將隨機選擇一個分片. 就像"順序"一樣, 依賴於 --min-split-size 選項. 如果設置為"幾何", aria2 會先選擇索引最小的分片, 然後會為之前選擇的分片保留指數增長的空間. 這將減少建立連接的次數, 同時文件開始部分將會先行下載. 這也有助於視頻的邊下邊播.
timeout.name=超時時間
timeout.description=
uri-selector.name=URI 選擇演算法
uri-selector.description=指定 URI 選擇的演算法. 可選的值包括 "按順序", "反饋" 和 "自適應". 如果設置為"按順序", URI 將按列表中出現的順序使用. 如果設置為"反饋", aria2 將根據之前的下載速度選擇 URI 列表中下載速度最快的伺服器. 同時也將有效跳過無效鏡像. 之前統計的下載速度將作為伺服器狀態文件的一部分, 參見 --server-stat-of 和 --server-stat-if 選項. 如果設置為"自適應", 將從最好的鏡像和保留的連接里選擇一項. 補充說明, 其返回的鏡像沒有被測試過, 同時如果每個鏡像都已經被測試過時, 返回的鏡像還會被重新測試. 否則, 其將不會選擇其他鏡像. 例如"反饋", 其使用伺服器狀態文件.
check-certificate.name=檢查證書
check-certificate.description=
http-accept-gzip.name=支持 GZip
http-accept-gzip.description=如果遠程伺服器的響應頭中包含 Content-Encoding: gzip 或 Content-Encoding: deflate , 將發送包含 Accept: deflate, gzip 的請求頭並解壓縮響應.
http-auth-challenge.name=認證質詢
http-auth-challenge.description=僅當伺服器需要時才發送 HTTP 認證請求頭. 如果設置為"否", 每次都會發送認證請求頭. 例外: 如果用戶名和密碼包含在 URI 中, 將忽略此選項並且每次都會發送認證請求頭.
http-no-cache.name=禁用緩存
http-no-cache.description=發送的請求頭中將包含 Cache-Control: no-cache 和 Pragma: no-cache header 以避免內容被緩存. 如果設置為"否", 上述請求頭將不會發送, 同時您也可以使用 --header 選項將 Cache-Control 請求頭添加進去.
http-user.name=HTTP 默認用戶名
http-user.description=
http-passwd.name=HTTP 默認密碼
http-passwd.description=
http-proxy.name=HTTP 代理伺服器
http-proxy.description=
http-proxy-user.name=HTTP 代理伺服器用戶名
http-proxy-user.description=
http-proxy-passwd.name=HTTP 代理伺服器密碼
http-proxy-passwd.description=
https-proxy.name=HTTPS 代理伺服器
https-proxy.description=
https-proxy-user.name=HTTPS 代理伺服器用戶名
https-proxy-user.description=
https-proxy-passwd.name=HTTPS 代理伺服器密碼
https-proxy-passwd.description=
referer.name=請求來源
referer.description=設置 HTTP 請求來源 (Referer). 此選項將影響所有 HTTP/HTTPS 下載. 如果設置為 *, 請求來源將設置為下載鏈接. 此選項可以配合 --parameterized-uri 選項使用.
enable-http-keep-alive.name=啟用持久連接
enable-http-keep-alive.description=啟用 HTTP/1.1 持久連接.
enable-http-pipelining.name=啟用 HTTP 管線化
enable-http-pipelining.description=啟用 HTTP/1.1 管線化.
header.name=自定義請求頭
header.description=增加 HTTP 請求頭內容.
save-cookies.name=Cookies 保存路徑
save-cookies.description=以 Mozilla/Firefox(1.x/2.x)/Netscape 格式將 Cookies 保存到文件中. 如果文件已經存在, 將被覆蓋. 會話過期的 Cookies 也將會保存, 其過期時間將會設置為 0.
use-head.name=啟用 HEAD 方法
use-head.description=第一次請求 HTTP 伺服器時使用 HEAD 方法.
user-agent.name=自定義 User Agent
user-agent.description=
ftp-user.name=FTP 默認用戶名
ftp-user.description=
ftp-passwd.name=FTP 默認密碼
ftp-passwd.description=如果 URI 中包含用戶名單不包含密碼, aria2 首先會從 .netrc 文件中獲取密碼. 如果在 .netrc 文件中找到密碼, 則使用該密碼. 否則, 使用此選項設置的密碼.
ftp-pasv.name=被動模式
ftp-pasv.description=在 FTP 中使用被動模式. 如果設置為"否", 則使用主動模式. 此選項不適用於 SFTP 傳輸.
ftp-proxy.name=FTP 代理伺服器
ftp-proxy.description=
ftp-proxy-user.name=FTP 代理伺服器用戶名
ftp-proxy-user.description=
ftp-proxy-passwd.name=FTP 代理伺服器密碼
ftp-proxy-passwd.description=
ftp-type.name=傳輸類型
ftp-type.description=
ftp-reuse-connection.name=連接復用
ftp-reuse-connection.description=
ssh-host-key-md.name=SSH 公鑰校驗和
ssh-host-key-md.description=設置 SSH 主機公鑰的校驗和. TYPE 為哈希類型. 支持的哈希類型為 sha-1 和 md5. DIGEST 是十六進位摘要. 例如: sha-1=b030503d4de4539dc7885e6f0f5e256704edf4c3. 此選項可以在使用 SFTP 時用來驗證伺服器的公鑰. 如果此選項不設置, 即保留默認, 不會進行任何驗證。
bt-detach-seed-only.name=分離僅做種任務
bt-detach-seed-only.description=統計當前活動下載任務(參見 -j 選項) 時排除僅做種的任務. 這意味著, 如果參數設置為 -j3, 此選項打開並且當前有 3 個正在活動的任務, 並且其中有 1 個進入做種模式, 那麼其會從正在下載的數量中排除(即數量會變為 2), 在隊列中等待的下一個任務將會開始執行. 但要知道, 在 RPC 方法中, 做種的任務仍然被認為是活動的下載任務.
bt-enable-hook-after-hash-check.name=啟用哈希檢查完成事件
bt-enable-hook-after-hash-check.description=允許 BT 下載哈希檢查(參見 -V 選項) 完成後調用命令. 默認情況下, 當哈希檢查成功後, 通過 --on-bt-download-complete 設置的命令將會被執行. 如果要禁用此行為, 請設置為"否".
bt-enable-lpd.name=啟用本地節點發現 (LPD)
bt-enable-lpd.description=
bt-exclude-tracker.name=BT 排除伺服器地址
bt-exclude-tracker.description=逗號分隔的 BT 排除伺服器地址. 您可以使用 * 匹配所有地址, 因此將排除所有伺服器地址. 當在 shell 命令行使用 * 時, 需要使用轉義符或引號.
bt-external-ip.name=外部 IP 地址
bt-external-ip.description=指定用在 BitTorrent 下載和 DHT 中的外部 IP 地址. 它可能被發送到 BitTorrent 伺服器. 對於 DHT, 此選項將會報告本地節點正在下載特定的種子. 這對於在私有網路中使用 DHT 非常關鍵. 雖然這個方法叫外部, 但其可以接受各種類型的 IP 地址.
bt-force-encryption.name=強制加密
bt-force-encryption.description=BT 消息中的內容需要使用 arc4 加密. 此選項是設置 --bt-require-crypto --bt-min-crypto-level=arc4 這兩個選項的快捷方式. 此選項不會修改上述兩個選項的內容. 如果設置為"是", 將拒絕以前的 BT 握手, 並僅使用模糊握手及加密消息.
bt-hash-check-seed.name=做種前檢查文件哈希
bt-hash-check-seed.description=如果設置為"是", 當使用 --check-integrity 選項完成哈希檢查及文件完成後才繼續做種. 如果您希望僅當文件損壞或未完成時檢查文件, 請設置為"否". 此選項僅對 BT 下載有效
bt-max-open-files.name=最多打開文件數
bt-max-open-files.description=設置 BT/Metalink 下載全局打開的最大文件數.
bt-max-peers.name=最大連接節點數
bt-max-peers.description=設置每個 BT 下載的最大連接節點數. 0 表示不限制.
bt-metadata-only.name=僅下載種子文件
bt-metadata-only.description=僅下載種子文件. 種子文件中描述的文件將不會下載. 此選項僅對磁鏈生效.
bt-min-crypto-level.name=最低加密級別
bt-min-crypto-level.description=設置加密方法的最小級別. 如果節點提供多種加密方法, aria2 將選擇滿足給定級別的最低級別.
bt-prioritize-piece.name=優先下載
bt-prioritize-piece.description=嘗試先下載每個文件開頭或結尾的分片. 此選項有助於預覽文件. 參數可以包括兩個關鍵詞: head 和 tail. 如果包含兩個關鍵詞, 需要使用逗號分隔. 每個關鍵詞可以包含一個參數, SIZE. 例如, 如果指定 head=SIZE, 每個文件的最前 SIZE 數據將會獲得更高的優先順序. tail=SIZE 表示每個文件的最後 SIZE 數據. SIZE 可以包含 K 或 M (1K = 1024, 1M = 1024K).
bt-remove-unselected-file.name=刪除未選擇的文件
bt-remove-unselected-file.description=當 BT 任務完成後刪除未選擇的文件. 要選擇需要下載的文件, 請使用 --select-file 選項. 如果沒有選擇, 則所有文件都默認為需要下載. 此選項會從磁碟上直接刪除文件, 請謹慎使用此選項.
bt-require-crypto.name=需要加密
bt-require-crypto.description=如果設置為"是", aria 將不會接受以前的 BitTorrent 握手協議(\\19BitTorrent 協議)並建立連接. 因此 aria2 總是模糊握手.
bt-request-peer-speed-limit.name=期望下載速度
bt-request-peer-speed-limit.description=如果一個 BT 下載的整體下載速度低於此選項設置的值, aria2 會臨時提高連接數以提高下載速度. 在某些情況下, 設置期望下載速度可以提高您的下載速度. 您可以增加數值的單位 K 或 M (1K = 1024, 1M = 1024K).
bt-save-metadata.name=保存種子文件
bt-save-metadata.description=保存種子文件為 ".torrent" 文件. 此選項僅對磁鏈生效. 文件名為十六進位編碼後的哈希值及 ".torrent"後綴. 保存的目錄與下載文件的目錄相同. 如果相同的文件已存在, 種子文件將不會保存.
bt-seed-unverified.name=不檢查已經下載的文件
bt-seed-unverified.description=不檢查之前下載文件中每個分片的哈希值.
bt-stop-timeout.name=無速度時自動停止時間
bt-stop-timeout.description=當 BT 任務F下載速度持續為 0, 達到此選項設置的時間後停止下載. 如果設置為 0, 此功能將禁用.
bt-tracker.name=BT 伺服器地址
bt-tracker.description=逗號分隔的 BT 伺服器地址. 如果伺服器地址在 --bt-exclude-tracker 選項中, 其將不會生效.
bt-tracker-connect-timeout.name=BT 伺服器連接超時時間
bt-tracker-connect-timeout.description=設置 BT 伺服器的連接超時時間 (秒). 當連接建立後, 此選項不再生效, 請使用 --bt-tracker-timeout 選項.
bt-tracker-interval.name=BT 伺服器連接間隔時間
bt-tracker-interval.description=設置請求 BT 伺服器的間隔時間 (秒). 此選項將完全覆蓋伺服器返回的最小間隔時間和間隔時間, aria2 僅使用此選項的值.如果設置為 0, aria2 將根據伺服器的響應情況和下載進程決定時間間隔.
bt-tracker-timeout.name=BT 伺服器超時時間
bt-tracker-timeout.description=
dht-file-path.name=DHT (IPv4) 文件
dht-file-path.description=修改 IPv4 DHT 路由表文件路徑.
dht-file-path6.name=DHT (IPv6) 文件
dht-file-path6.description=修改 IPv6 DHT 路由表文件路徑.
dht-listen-port.name=DHT 監聽埠
dht-listen-port.description=設置 DHT (IPv4, IPv6) 和 UDP 伺服器使用的 UCP 埠. 多個埠可以使用逗號 "," 分隔, 例如: 6881,6885. 您還可以使用短橫線 "-" 表示範圍: 6881-6999, 或可以一起使用: 6881-6889, 6999.
dht-message-timeout.name=DHT 消息超時時間
dht-message-timeout.description=
enable-dht.name=啟用 DHT (IPv4)
enable-dht.description=啟用 IPv4 DHT 功能. 此選項同時會啟用 UDP 伺服器支持. 如果種子設置為私有, 即使此選項設置為"是", aria2 也不會啟用 DHT.
enable-dht6.name=啟用 DHT (IPv6)
enable-dht6.description=啟用 IPv6 DHT 功能. 如果種子設置為私有, 即使此選項設置為"是", aria2 也不會啟用 DHT. 使用 --dht-listen-port 選項設置監聽的埠.
enable-peer-exchange.name=啟用節點交換
enable-peer-exchange.description=啟用節點交換擴展. 如果種子設置為私有, 即使此選項設置為"是", aria2 也不會啟用此功能.
follow-torrent.name=下載種子中的文件
follow-torrent.description=如果設置為"是"或"僅內存", 當後綴為 .torrent 或內容類型為 application/x-bittorrent 的文件下載完成時, aria2 將按種子文件讀取並下載該文件中提到的文件. 如果設置為"僅內存", 該種子文件將不會寫入到磁碟中, 而僅會存儲在內存中. 如果設置為"否", 則 .torrent 文件會下載到磁碟中, 但不會按種子文件讀取並且其中的文件不會進行下載.
listen-port.name=監聽埠
listen-port.description=設置 BT 下載的 TCP 埠. 多個埠可以使用逗號 "," 分隔, 例如: 6881,6885. 您還可以使用短橫線 "-" 表示範圍: 6881-6999, 或可以一起使用: 6881-6889, 6999.
max-overall-upload-limit.name=全局最大上傳速度
max-overall-upload-limit.description=設置全局最大上傳速度 (位元組/秒). 0 表示不限制. 您可以增加數值的單位 K 或 M (1K = 1024, 1M = 1024K).
max-upload-limit.name=最大上傳速度
max-upload-limit.description=設置每個任務的最大上傳速度 (位元組/秒). 0 表示不限制. 您可以增加數值的單位 K 或 M (1K = 1024, 1M = 1024K).
peer-id-prefix.name=節點 ID 前綴
peer-id-prefix.description=指定節點 ID 的前綴. BT 中節點 ID 長度為 20 位元組. 如果超過 20 位元組, 將僅使用前 20 位元組. 如果少於 20 位元組, 將在其後不足隨機的數據保證為 20 位元組.
seed-ratio.name=最小分享率
seed-ratio.description=指定分享率. 當分享率達到此選項設置的值時會完成做種. 強烈建議您將此選項設置為大於等於 1.0. 如果您想不限制分享比率, 可以設置為 0.0. 如果同時設置了 --seed-time 選項, 當任意一個條件滿足時將停止做種.
seed-time.name=最小做種時間
seed-time.description=此選項設置為 0 時, 將在 BT 任務下載完成後不進行做種.
follow-metalink.name=下載 Metalink 中的文件
follow-metalink.description=如果設置為"是"或"僅內存", 當後綴為 .meta4 或 .metalink 或內容類型為 application/metalink4+xml 或 application/metalink+xml 的文件下載完成時, aria2 將按 Metalink 文件讀取並下載該文件中提到的文件. 如果設置為"僅內存", 該 Metalink 文件將不會寫入到磁碟中, 而僅會存儲在內存中. 如果設置為"否", 則 .metalink 文件會下載到磁碟中, 但不會按 Metalink 文件讀取並且其中的文件不會進行下載.
metalink-base-uri.name=基礎 URI
metalink-base-uri.description=指定基礎 URI 以便解析本地磁碟中存儲的 Metalink 文件里 metalink:url 和 metalink:metaurl 中的相對 URI 地址. 如果 URI 表示的為目錄, 最後需要以 / 結尾.
metalink-language.name=語言
metalink-language.description=
metalink-location.name=首選伺服器位置
metalink-location.description=首選伺服器所在的位置. 可以使用逗號分隔的列表, 例如: jp,us.
metalink-os.name=操作系統
metalink-os.description=下載文件的操作系統.
metalink-version.name=版本號
metalink-version.description=下載文件的版本號.
metalink-preferred-protocol.name=首選使用協議
metalink-preferred-protocol.description=指定首選使用的協議. 可以設置為 http, https, ftp 或"無". 設置為"無"時禁用此選項.
metalink-enable-unique-protocol.name=僅使用唯一協議
metalink-enable-unique-protocol.description=如果一個 Metalink 文件可用多種協議, 並且此選項設置為"是", aria2 將只會使用其中一種. 使用 --metalink-preferred-protocol 參數指定首選的協議.
enable-rpc.name=啟用 JSON-RPC/XML-RPC 伺服器
enable-rpc.description=
pause-metadata.name=種子文件下載完後暫停
pause-metadata.description=當種子文件下載完成後暫停後續的下載. 在 aria2 中有 3 種種子文件的下載類型: (1) 下載 .torrent 文件. (2) 通過磁鏈下載的種子文件. (3) 下載 Metalink 文件. 這些種子文件下載完後會根據文件內容繼續進行下載. 此選項會暫停這些後續的下載. 此選項僅當 --enable-rpc 選項啟用時生效.
rpc-allow-origin-all.name=接受所有遠程請求
rpc-allow-origin-all.description=在 RPC 響應頭增加 Access-Control-Allow-Origin 欄位, 值為 * .
rpc-listen-all.name=在所有網卡上監聽
rpc-listen-all.description=在所有網路適配器上監聽 JSON-RPC/XML-RPC 的請求, 如果設置為"否", 僅監聽本地網路的請求.
rpc-listen-port.name=監聽埠
rpc-listen-port.description=
rpc-max-request-size.name=最大請求大小
rpc-max-request-size.description=設置 JSON-RPC/XML-RPC 最大的請求大小. 如果 aria2 檢測到請求超過設定的位元組數, 會直接取消連接.
rpc-save-upload-metadata.name=保存上傳的種子文件
rpc-save-upload-metadata.description=在 dir 選項設置的目錄中保存上傳的種子文件或 Metalink 文件. 文件名包括 SHA-1 哈希後的元數據和擴展名兩部分. 對於種子文件, 擴展名為 '.torrent'. 對於 Metalink 為 '.meta4'. 如果此選項設置為"否", 通過 aria2.addTorrent() 或 aria2.addMetalink() 方法添加的下載將無法通過 --save-session 選項保存.
rpc-secure.name=啟用 SSL/TLS
rpc-secure.description=RPC 將通過 SSL/TLS 加密傳輸. RPC 客戶端需要使用 https 協議連接伺服器. 對於 WebSocket 客戶端, 使用 wss 協議. 使用 --rpc-certificate 和 --rpc-private-key 選項設置伺服器的證書和私鑰.
allow-overwrite.name=允許覆蓋
allow-overwrite.description=如果相應的控制文件不存在時從頭重新下載文件. 參見 --auto-file-renaming 選項.
allow-piece-length-change.name=允許分片大小變化
allow-piece-length-change.description=如果設置為"否", 當分片長度與控制文件中的不同時, aria2 將會中止下載. 如果設置為"是", 您可以繼續, 但部分下載進度將會丟失.
always-resume.name=始終斷點續傳
always-resume.description=始終斷點續傳. 如果設置為"是", aria2 始終嘗試斷點續傳, 如果無法恢復, 則中止下載. 如果設置為"否", 對於不支持斷點續傳的 URI 或 aria2 遇到 N 個不支持斷點續傳的 URI (N 為 --max-resume-failure-tries 選項設置的值), aria2 會從頭下載文件. 參見 --max-resume-failure-tries 參數.
async-dns.name=非同步 DNS
async-dns.description=
auto-file-renaming.name=文件自動重命名
auto-file-renaming.description=重新命名已經存在的文件. 此選項僅對 HTTP(S)/FTP 下載有效. 新的文件名後會在文件名後、擴展名 (如果有) 前追加句點和數字(1..9999).
auto-save-interval.name=自動保存間隔
auto-save-interval.description=每隔設置的秒數自動保存控制文件(*.aria2). 如果設置為 0, 下載期間控制文件不會自動保存. 不論設置的值為多少, aria2 會在任務結束時保存控制文件. 可以設置的值為 0 到 600.
conditional-get.name=條件下載
conditional-get.description=僅當本地文件比遠程文件舊時才進行下載. 此功能僅適用於 HTTP(S) 下載. 如果在 Metalink 中文件大小已經被指定則功能無法生效. 同時此功能還將忽略 Content-Disposition 響應頭. 如果存在控制文件, 此選項將被忽略. 此功能通過 If-Modified-Since 請求頭獲取較新的文件. 當獲取到本地文件的修改時間時, 此功能將使用用戶提供的文件名 (參見 --out 選項), 如果沒有指定 --out 選項則使用 URI 中的文件名. 為了覆蓋已經存在的文件, 需要使用 --allow-overwrite 參數.
conf-path.name=配置文件路徑
conf-path.description=
console-log-level.name=控制台日誌級別
console-log-level.description=
content-disposition-default-utf8.name=使用 UTF-8 處理 Content-Disposition
content-disposition-default-utf8.description=處理 "Content-Disposition" 頭中的字元串時使用 UTF-8 字符集來代替 ISO-8859-1, 例如, 文件名參數, 但不是擴展版本的文件名.
daemon.name=啟用後台進程
daemon.description=
deferred-input.name=延遲載入
deferred-input.description=如果設置為"是", aria2 在啟動時不會讀取 --input-file 選項設置的文件中的所有 URI 地址, 而是會在之後需要時按需讀取. 如果輸入文件中包含大量要下載的 URI, 此選項可以減少內存的使用. 如果設置為"否", aria2 會在啟動時讀取所有的 URI. 當 -save-session 使用時將會禁用 --deferred-input 選項.
disable-ipv6.name=禁用 IPv6
disable-ipv6.description=
disk-cache.name=磁碟緩存
disk-cache.description=啟用磁碟緩存. 如果設置為 0, 將禁用磁碟緩存. 此功能將下載的數據緩存在內存中, 最多佔用此選項設置的位元組數. 緩存存儲由 aria2 實例創建並對所有下載共享. 由於數據以較大的單位寫入並按文件的偏移重新排序, 所以磁碟緩存的一個優點是減少磁碟的 I/O. 如果調用哈希檢查時並且數據緩存在內存中時, 將不需要從磁碟中讀取. 大小可以包含 K 或 M (1K = 1024, 1M = 1024K).
download-result.name=下載結果
download-result.description=此選項將修改下載結果的格式. 如果設置為"默認", 將列印 GID, 狀態, 平均下載速度和路徑/URI. 如果涉及多個文件, 僅列印第一個請求文件的路徑/URI, 其餘的將被忽略. 如果設置為"完整", 將列印 GID, 狀態, 平均下載速度, 下載進度和路徑/URI. 其中, 下載進度和路徑/URI 將會每個文件列印一行. 如果設置為"隱藏", 下載結果將會隱藏.
dscp.name=DSCP
dscp.description=為 QoS 設置 BT 上行 IP 包的 DSCP 值. 此參數僅設置 IP 包中 TOS 欄位的 DSCP 位, 而不是整個欄位. 如果您從 /usr/include/netinet/ip.h 得到的值, 需要除以 4 (否則值將不正確, 例如您的 CS1 類將會轉為 CS4). 如果您從 RFC, 網路供應商的文檔, 維基百科或其他來源採取常用的值, 可以直接使用.
rlimit-nofile.name=最多打開的文件描述符
rlimit-nofile.description=設置打開的文件描述符的軟限制 (soft limit). 此選項僅當滿足如下條件時開放: a. 系統支持它 (posix). b. 限制沒有超過硬限制 (hard limit). c. 指定的限制比當前的軟限制高. 這相當於設置 ulimit, 除了其不能降低限制. 此選項僅當系統支持 rlimit API 時有效.
enable-color.name=終端輸出使用顏色
enable-color.description=
enable-mmap.name=啟用 MMap
enable-mmap.description=內存中存放映射文件. 當文件空間沒有預先分配至, 此選項無效. 參見 --file-allocation.
event-poll.name=事件輪詢方法
event-poll.description=設置事件輪訓的方法. 可選的值包括 epoll, kqueue, port, poll 和 select. 對於 epoll, kqueue, port 和 poll, 只有系統支持時才可用. 最新的 Linux 支持 epoll. 各種 *BSD 系統包括 Mac OS X 支持 kqueue. Open Solaris 支持 port. 默認值根據您使用的操作系統不同而不同.
file-allocation.name=文件分配方法
file-allocation.description=指定文件分配方法. "無" 不會預先分配文件空間. "prealloc"會在下載開始前預先分配空間. 這將會根據文件的大小需要一定的時間. 如果您使用的是較新的文件系統, 例如 ext4 (帶擴展支持), btrfs, xfs 或 NTFS (僅 MinGW 構建), "falloc" 是最好的選擇. 其幾乎可以瞬間分配大(數 GiB)文件. 不要在舊的文件系統, 例如 ext3 和 FAT32 上使用 falloc, 因為與 prealloc 花費的時間相同, 並且其會阻塞 aria2 知道分配完成. 當您的系統不支持 posix_fallocate(3) 函數時, falloc 可能無法使用. "trunc" 使用 ftruncate(2) 系統調用或平台特定的實現將文件截取到特定的長度. 在多文件的 BitTorrent 下載中, 若某文件與其相鄰的文件共享相同的分片時, 則相鄰的文件也會被分配.
force-save.name=強制保存
force-save.description=即使任務完成或刪除時使用 --save-session 選項時也保存該任務. 此選項在這種情況下還會保存控制文件. 此選項可以保存被認為已經完成但正在做種的 BT 任務.
save-not-found.name=保存未找到的文件
save-not-found.description=當使用 --save-session 選項時, 即使當任務中的文件不存在時也保存該下載任務. 此選項同時會將這種情況保存到控制文件中.
hash-check-only.name=僅哈希檢查
hash-check-only.description=如果設置為"是", 哈希檢查完使用 --check-integrity 選項, 根據是否下載完成決定是否終止下載.
human-readable.name=控制台可讀輸出
human-readable.description=在控制台輸出可讀格式的大小和速度 (例如, 1.2Ki, 3.4Mi).
keep-unfinished-download-result.name=保留未完成的任務
keep-unfinished-download-result.description=保留所有未完成的下載結果, 即使超過了 --max-download-result 選項設置的數量. 這將有助於在會話文件中保存所有的未完成的下載 (參考 --save-session 選項). 需要注意的是, 未完成任務的數量沒有上限. 如果不希望這樣, 請關閉此選項.
max-download-result.name=最多下載結果
max-download-result.description=設置內存中存儲最多的下載結果數量. 下載結果包括已完成/錯誤/已刪除的下載. 下載結果存儲在一個先進先出的隊列中, 因此其可以存儲最多指定的下載結果的數量. 當隊列已滿且有新的下載結果創建時, 最老的下載結果將從隊列的最前部移除, 新的將放在最後. 此選項設置較大的值後如果經過幾千次的下載將導致較高的內存消耗. 設置為 0 表示不存儲下載結果. 注意, 未完成的下載將始終保存在內存中, 不考慮該選項的設置. 參考 --keep-unfinished-download-result 選項.
max-mmap-limit.name=MMap 最大限制
max-mmap-limit.description=設置啟用 MMap (參見 --enable-mmap 選項) 最大的文件大小. 文件大小由一個下載任務中所有文件大小的和決定. 例如, 如果一個下載包含 5 個文件, 那麼文件大小就是這些文件的總大小. 如果文件大小超過此選項設置的大小時, MMap 將會禁用.
max-resume-failure-tries.name=最大斷點續傳嘗試次數
max-resume-failure-tries.description=當 --always-resume 選項設置為"否"時, 如果 aria2 檢測到有 N 個 URI 不支持斷點續傳時, 將從頭開始下載文件. 如果 N 設置為 0, 當所有 URI 都不支持斷點續傳時才會從頭下載文件. 參見 --always-resume 選項.
min-tls-version.name=最低 TLS 版本
min-tls-version.description=指定啟用的最低 SSL/TLS 版本.
log-level.name=日誌級別
log-level.description=
optimize-concurrent-downloads.name=優化並發下載
optimize-concurrent-downloads.description=根據可用帶寬優化並發下載的數量. aria2 使用之前統計的下載速度通過規則 N = A + B Log10 (速度單位為 Mbps) 得到並發下載的數量. 其中係數 A 和 B 可以在參數中以冒號分隔自定義. 默認值 (A=5, B=25) 可以在 1Mbps 網路上使用通常 5 個並發下載, 在 100Mbps 網路上為 50 個. 並發下載的數量保持在 --max-concurrent-downloads 參數定義的最大之下.
piece-length.name=文件分片大小
piece-length.description=設置 HTTP/FTP 下載的分配大小. aria2 根據這個邊界分割文件. 所有的分割都是這個長度的倍數. 此選項不適用於 BitTorrent 下載. 如果 Metalink 文件中包含分片哈希的結果此選項也不適用.
show-console-readout.name=顯示控制台輸出
show-console-readout.description=
summary-interval.name=下載摘要輸出間隔
summary-interval.description=設置下載進度摘要的輸出間隔(秒). 設置為 0 禁止輸出.
max-overall-download-limit.name=全局最大下載速度
max-overall-download-limit.description=設置全局最大下載速度 (位元組/秒). 0 表示不限制. 您可以增加數值的單位 K 或 M (1K = 1024, 1M = 1024K).
max-download-limit.name=最大下載速度
max-download-limit.description=設置每個任務的最大下載速度 (位元組/秒). 0 表示不限制. 您可以增加數值的單位 K 或 M (1K = 1024, 1M = 1024K).
no-conf.name=禁用配置文件
no-conf.description=
no-file-allocation-limit.name=文件分配限制
no-file-allocation-limit.description=不對比此參數設置大小小的分配文件. 您可以增加數值的單位 K 或 M (1K = 1024, 1M = 1024K).
parameterized-uri.name=啟用參數化 URI 支持
parameterized-uri.description=啟用參數化 URI 支持. 您可以指定部分的集合: http://{sv1,sv2,sv3}/foo.iso. 同時您也可以使用步進計數器指定數字化的序列: http://host/image[000-100:2].img. 步進計數器可以省略. 如果所有 URI 地址不指向同樣的文件, 例如上述第二個示例, 需要使用 -Z 選項.
quiet.name=禁用控制台輸出
quiet.description=
realtime-chunk-checksum.name=實時數據塊驗證
realtime-chunk-checksum.description=如果提供了數據塊的校驗和, 將在下載過程中通過校驗和驗證數據塊.
remove-control-file.name=刪除控制文件
remove-control-file.description=在下載前刪除控制文件. 使用 --allow-overwrite=true 選項時, 總是從頭開始下載文件. 此選項將有助於使用不支持斷點續傳代理伺服器的用戶.
save-session.name=狀態保存文件
save-session.description=當退出時保存錯誤及未完成的任務到指定的文件中. 您可以在重啟 aria2 時使用 --input-file 選項重新載入. 如果您希望輸出的內容使用 GZip 壓縮, 您可以在文件名後增加 .gz 擴展名. 請注意, 通過 aria2.addTorrent() 和 aria2.addMetalink() RPC 方法添加的下載, 其元數據沒有保存到文件的將不會保存. 通過 aria2.remove() 和 aria2.forceRemove() 刪除的下載將不會保存.
save-session-interval.name=保存狀態間隔
save-session-interval.description=每隔此選項設置的時間(秒)後會保存錯誤或未完成的任務到 --save-session 選項指定的文件中. 如果設置為 0, 僅當 aria2 退出時才會保存.
socket-recv-buffer-size.name=Socket 接收緩衝區大小
socket-recv-buffer-size.description=設置 Socket 接收緩衝區最大的位元組數. 指定為 0 時將禁用此選項. 當使用 SO_RCVBUF 選項調用 setsockopt() 時此選項的值將設置到 Socket 的文件描述符中.
stop.name=自動關閉時間
stop.description=在此選項設置的時間(秒)後關閉應用. 如果設置為 0, 此功能將禁用.
truncate-console-readout.name=縮短控制台輸出內容
truncate-console-readout.description=縮短控制台輸出的內容在一行中.

View file

@ -1,7 +1,7 @@
(function () {
'use strict';
angular.module('ariaNg').constant('aria2AllOptions', {
// EXAMPLE:
// Aria2 Option Defination EXAMPLE:
// 'option key': {
// [since: '',] //This option is supported by this or higher aria2 version
// type: 'string|integer|float|text|boolean|option',
@ -108,6 +108,9 @@
split: ',',
showCount: true
},
'out': {
type: 'string'
},
'proxy-method': {
type: 'option',
options: ['get', 'tunnel'],
@ -543,7 +546,7 @@
},
'rpc-save-upload-metadata': {
type: 'boolean',
defaultValue: 'false',
defaultValue: 'true',
required: true
},
'rpc-secure': {
@ -597,6 +600,11 @@
readonly: true,
defaultValue: 'notice'
},
'content-disposition-default-utf8': {
since: '1.31.0',
type: 'boolean',
defaultValue: 'false'
},
'daemon': {
type: 'boolean',
readonly: true,
@ -806,117 +814,189 @@
defaultValue: 'true'
}
}).constant('aria2GlobalAvailableOptions', {
basicOptions: ['dir', 'log', 'max-concurrent-downloads', 'check-integrity', 'continue'],
httpFtpSFtpOptions: ['all-proxy', 'all-proxy-user', 'all-proxy-passwd', 'connect-timeout', 'dry-run', 'lowest-speed-limit', 'max-connection-per-server', 'max-file-not-found', 'max-tries', 'min-split-size', 'netrc-path', 'no-netrc', 'no-proxy', 'proxy-method', 'remote-time', 'reuse-uri', 'retry-wait', 'server-stat-of', 'server-stat-timeout', 'split', 'stream-piece-selector', 'timeout', 'uri-selector'],
httpOptions: ['check-certificate', 'http-accept-gzip', 'http-auth-challenge', 'http-no-cache', 'http-user', 'http-passwd', 'http-proxy', 'http-proxy-user', 'http-proxy-passwd', 'https-proxy', 'https-proxy-user', 'https-proxy-passwd', 'referer', 'enable-http-keep-alive', 'enable-http-pipelining', 'header', 'save-cookies', 'use-head', 'user-agent'],
ftpSFtpOptions: ['ftp-user', 'ftp-passwd', 'ftp-pasv', 'ftp-proxy', 'ftp-proxy-user', 'ftp-proxy-passwd', 'ftp-type', 'ftp-reuse-connection', 'ssh-host-key-md'],
btOptions: ['bt-detach-seed-only', 'bt-enable-hook-after-hash-check', 'bt-enable-lpd', 'bt-exclude-tracker', 'bt-external-ip', 'bt-force-encryption', 'bt-hash-check-seed', 'bt-max-open-files', 'bt-max-peers', 'bt-metadata-only', 'bt-min-crypto-level', 'bt-prioritize-piece', 'bt-remove-unselected-file', 'bt-require-crypto', 'bt-request-peer-speed-limit', 'bt-save-metadata', 'bt-seed-unverified', 'bt-stop-timeout', 'bt-tracker', 'bt-tracker-connect-timeout', 'bt-tracker-interval', 'bt-tracker-timeout', 'dht-file-path', 'dht-file-path6', 'dht-listen-port', 'dht-message-timeout', 'enable-dht', 'enable-dht6', 'enable-peer-exchange', 'follow-torrent', 'listen-port', 'max-overall-upload-limit', 'max-upload-limit', 'peer-id-prefix', 'seed-ratio', 'seed-time'],
metalinkOptions: ['follow-metalink', 'metalink-base-uri', 'metalink-language', 'metalink-location', 'metalink-os', 'metalink-version', 'metalink-preferred-protocol', 'metalink-enable-unique-protocol'],
rpcOptions: ['enable-rpc', 'pause-metadata', 'rpc-allow-origin-all', 'rpc-listen-all', 'rpc-listen-port', 'rpc-max-request-size', 'rpc-save-upload-metadata', 'rpc-secure'],
advancedOptions: ['allow-overwrite', 'allow-piece-length-change', 'always-resume', 'async-dns', 'auto-file-renaming', 'auto-save-interval', 'conditional-get', 'conf-path', 'console-log-level', 'daemon', 'deferred-input', 'disable-ipv6', 'disk-cache', 'download-result', 'dscp', 'rlimit-nofile', 'enable-color', 'enable-mmap', 'event-poll', 'file-allocation', 'force-save', 'save-not-found', 'hash-check-only', 'human-readable', 'keep-unfinished-download-result', 'max-download-result', 'max-mmap-limit', 'max-resume-failure-tries', 'min-tls-version', 'log-level', 'optimize-concurrent-downloads', 'piece-length', 'show-console-readout', 'summary-interval', 'max-overall-download-limit', 'max-download-limit', 'no-conf', 'no-file-allocation-limit', 'parameterized-uri', 'quiet', 'realtime-chunk-checksum', 'remove-control-file', 'save-session', 'save-session-interval', 'socket-recv-buffer-size', 'stop', 'truncate-console-readout']
// Aria2 Setting Page Defination EXAMPLE:
// 'category key': [
// 'option key 1', 'option key 2', // more options if possible
// ]
basicOptions: [
'dir', 'log', 'max-concurrent-downloads', 'check-integrity', 'continue'
],
httpFtpSFtpOptions: [
'all-proxy', 'all-proxy-user', 'all-proxy-passwd', 'connect-timeout', 'dry-run', 'lowest-speed-limit',
'max-connection-per-server', 'max-file-not-found', 'max-tries', 'min-split-size', 'netrc-path', 'no-netrc',
'no-proxy', 'proxy-method', 'remote-time', 'reuse-uri', 'retry-wait', 'server-stat-of',
'server-stat-timeout', 'split', 'stream-piece-selector', 'timeout', 'uri-selector'
],
httpOptions: [
'check-certificate', 'http-accept-gzip', 'http-auth-challenge', 'http-no-cache', 'http-user',
'http-passwd', 'http-proxy', 'http-proxy-user', 'http-proxy-passwd', 'https-proxy', 'https-proxy-user',
'https-proxy-passwd', 'referer', 'enable-http-keep-alive', 'enable-http-pipelining', 'header',
'save-cookies', 'use-head', 'user-agent'
],
ftpSFtpOptions: [
'ftp-user', 'ftp-passwd', 'ftp-pasv', 'ftp-proxy', 'ftp-proxy-user', 'ftp-proxy-passwd',
'ftp-type', 'ftp-reuse-connection', 'ssh-host-key-md'
],
btOptions: [
'bt-detach-seed-only', 'bt-enable-hook-after-hash-check', 'bt-enable-lpd', 'bt-exclude-tracker',
'bt-external-ip', 'bt-force-encryption', 'bt-hash-check-seed', 'bt-max-open-files', 'bt-max-peers',
'bt-metadata-only', 'bt-min-crypto-level', 'bt-prioritize-piece', 'bt-remove-unselected-file',
'bt-require-crypto', 'bt-request-peer-speed-limit', 'bt-save-metadata', 'bt-seed-unverified',
'bt-stop-timeout', 'bt-tracker', 'bt-tracker-connect-timeout', 'bt-tracker-interval', 'bt-tracker-timeout',
'dht-file-path', 'dht-file-path6', 'dht-listen-port', 'dht-message-timeout', 'enable-dht', 'enable-dht6',
'enable-peer-exchange', 'follow-torrent', 'listen-port', 'max-overall-upload-limit', 'max-upload-limit',
'peer-id-prefix', 'seed-ratio', 'seed-time'
],
metalinkOptions: [
'follow-metalink', 'metalink-base-uri', 'metalink-language', 'metalink-location', 'metalink-os',
'metalink-version', 'metalink-preferred-protocol', 'metalink-enable-unique-protocol'
],
rpcOptions: [
'enable-rpc', 'pause-metadata', 'rpc-allow-origin-all', 'rpc-listen-all', 'rpc-listen-port',
'rpc-max-request-size', 'rpc-save-upload-metadata', 'rpc-secure'
],
advancedOptions: [
'allow-overwrite', 'allow-piece-length-change', 'always-resume', 'async-dns', 'auto-file-renaming',
'auto-save-interval', 'conditional-get', 'conf-path', 'console-log-level', 'content-disposition-default-utf8', 'daemon',
'deferred-input', 'disable-ipv6', 'disk-cache', 'download-result', 'dscp', 'rlimit-nofile', 'enable-color', 'enable-mmap',
'event-poll', 'file-allocation', 'force-save', 'save-not-found', 'hash-check-only', 'human-readable',
'keep-unfinished-download-result', 'max-download-result', 'max-mmap-limit', 'max-resume-failure-tries',
'min-tls-version', 'log-level', 'optimize-concurrent-downloads', 'piece-length', 'show-console-readout',
'summary-interval', 'max-overall-download-limit', 'max-download-limit', 'no-conf',
'no-file-allocation-limit', 'parameterized-uri', 'quiet', 'realtime-chunk-checksum', 'remove-control-file',
'save-session', 'save-session-interval', 'socket-recv-buffer-size', 'stop', 'truncate-console-readout'
]
}).constant('aria2QuickSettingsAvailableOptions', {
globalSpeedLimitOptions: [
'max-overall-download-limit', 'max-overall-upload-limit'
]
}).constant('aria2TaskAvailableOptions', {
// Aria2 Task Option Defination EXAMPLE:
// {
// key: 'option key',
// category: 'global|http|bittorrent',
// [canShow: 'new|active|waiting|paused',] // possible to show in specific status, supporting multiple choice. if not set, always show
// [canUpdate: 'new|active|waiting|paused',] // possible to write in specific status, supporting multiple choice. if not set, always writable
// }
taskOptions: [
{
key: 'dir',
newOnly: true
category: 'global',
canUpdate: 'new'
},
{
key: 'out',
category: 'http',
canUpdate: 'new'
},
{
key: 'allow-overwrite',
newOnly: true
category: 'global',
canShow: 'new'
},
{
key: 'max-download-limit'
key: 'max-download-limit',
category: 'global'
},
{
key: 'max-upload-limit',
btOnly: true
category: 'bittorrent'
},
{
key: 'split',
httpOnly: true,
activeReadonly: true
category: 'http',
canUpdate: 'new|waiting|paused'
},
{
key: 'min-split-size',
httpOnly: true,
activeReadonly: true
category: 'http',
canUpdate: 'new|waiting|paused'
},
{
key: 'max-connection-per-server',
httpOnly: true,
activeReadonly: true
category: 'http',
canUpdate: 'new|waiting|paused'
},
{
key: 'lowest-speed-limit',
httpOnly: true,
activeReadonly: true
category: 'http',
canUpdate: 'new|waiting|paused'
},
{
key: 'stream-piece-selector',
httpOnly: true,
activeReadonly: true
category: 'http',
canUpdate: 'new|waiting|paused'
},
{
key: 'all-proxy',
httpOnly: true,
activeReadonly: true
category: 'http',
canUpdate: 'new|waiting|paused'
},
{
key: 'all-proxy-user',
httpOnly: true,
activeReadonly: true
category: 'http',
canUpdate: 'new|waiting|paused'
},
{
key: 'all-proxy-passwd',
httpOnly: true,
activeReadonly: true
category: 'http',
canUpdate: 'new|waiting|paused'
},
{
key: 'header',
category: 'http',
canUpdate: 'new'
},
{
key: 'bt-max-peers',
btOnly: true
category: 'bittorrent'
},
{
key: 'bt-request-peer-speed-limit',
btOnly: true
category: 'bittorrent'
},
{
key: 'bt-remove-unselected-file',
btOnly: true
category: 'bittorrent'
},
{
key: 'bt-stop-timeout',
btOnly: true,
activeReadonly: true
category: 'bittorrent',
canUpdate: 'new|waiting|paused'
},
{
key: 'bt-tracker',
btOnly: true,
activeReadonly: true
category: 'bittorrent',
canUpdate: 'new|waiting|paused'
},
{
key: 'seed-ratio',
btOnly: true,
activeReadonly: true
category: 'bittorrent',
canUpdate: 'new|waiting|paused'
},
{
key: 'seed-time',
btOnly: true,
activeReadonly: true
category: 'bittorrent',
canUpdate: 'new|waiting|paused'
},
{
key: 'conditional-get',
newOnly: true
category: 'global',
canShow: 'new'
},
{
key: 'file-allocation',
newOnly: true
category: 'global',
canShow: 'new'
},
{
key: 'parameterized-uri ',
newOnly: true
key: 'parameterized-uri',
category: 'global',
canShow: 'new'
},
{
key: 'force-save'
key: 'force-save',
category: 'global'
}
]
});

View file

@ -1,15 +1,41 @@
(function () {
'use strict';
angular.module('ariaNg').config(['$translateProvider', 'localStorageServiceProvider', 'NotificationProvider', 'ariaNgConstants', function ($translateProvider, localStorageServiceProvider, NotificationProvider, ariaNgConstants) {
angular.module('ariaNg').config(['$qProvider', '$translateProvider', 'localStorageServiceProvider', 'NotificationProvider', 'ariaNgConstants', 'ariaNgLanguages', function ($qProvider, $translateProvider, localStorageServiceProvider, NotificationProvider, ariaNgConstants, ariaNgLanguages) {
$qProvider.errorOnUnhandledRejections(false);
localStorageServiceProvider
.setPrefix(ariaNgConstants.appPrefix)
.setStorageType('localStorage')
.setStorageCookie(365, '/');
var supportedLangs = [];
var languageAliases = {};
for (var langName in ariaNgLanguages) {
if (!ariaNgLanguages.hasOwnProperty(langName)) {
continue;
}
var language = ariaNgLanguages[langName];
var aliases = language.aliases;
supportedLangs.push(langName);
if (!angular.isArray(aliases) || aliases.length < 1) {
continue;
}
for (var i = 0; i < aliases.length; i++) {
var langAlias = aliases[i];
languageAliases[langAlias] = langName;
}
}
$translateProvider.useLoader('ariaNgLanguageLoader')
.useLoaderCache(true)
.preferredLanguage(ariaNgConstants.defaultLanguage)
.registerAvailableLanguageKeys(supportedLangs, languageAliases)
.determinePreferredLanguage()
.fallbackLanguage(ariaNgConstants.defaultLanguage)
.useSanitizeValueStrategy('escapeParameters');

View file

@ -10,6 +10,7 @@
languageFileExtension: '.txt',
defaultLanguage: 'en',
defaultHost: 'localhost',
defaultSecureProtocol: 'https',
globalStatStorageCapacity: 120,
taskStatStorageCapacity: 300,
lazySaveTimeout: 500,
@ -20,11 +21,16 @@
title: '${downspeed}, ${upspeed} - ${title}',
titleRefreshInterval: 5000,
browserNotification: false,
rpcAlias: '',
rpcHost: '',
rpcPort: '6800',
rpcInterface: 'jsonrpc',
protocol: 'http',
httpMethod: 'POST',
secret: '',
extendRpcServers: [],
globalStatRefreshInterval: 1000,
downloadTaskRefreshInterval: 1000
downloadTaskRefreshInterval: 1000,
afterCreatingNewTask: 'task-list'
});
}());

View file

@ -15,10 +15,10 @@
'Connected': 'Connected',
'Not Connected': 'Not Connected',
'Global': 'Global',
'RPC': 'RPC',
'New': 'New',
'Start': 'Start',
'Pause': 'Pause',
'Restart': 'Restart',
'Delete': 'Delete',
'Select All': 'Select All',
'Select None': 'Select None',
@ -31,12 +31,14 @@
'Collapse': 'Collapse',
'Remove Task': 'Remove Task',
'Clear Stopped Tasks': 'Clear Stopped Tasks',
'Click to view task detail': 'Click to view task detail',
'By File Name': 'By File Name',
'By File Size': 'By File Size',
'By Progress': 'By Progress',
'By Remain Time': 'By Remain Time',
'By Download Speed': 'By Download Speed',
'By Upload Speed': 'By Upload Speed',
'Filters': 'Filters',
'Download': 'Download',
'Upload': 'Upload',
'Downloading': 'Downloading',
@ -46,8 +48,9 @@
'Completed': 'Completed',
'Error Occurred': 'Error Occurred',
'Removed': 'Removed',
'Downloaded / Stopped': 'Downloaded / Stopped',
'Finished / Stopped': 'Finished / Stopped',
'Uncompleted': 'Uncompleted',
'Click to pin': 'Click to pin',
'Settings': 'Settings',
'AriaNg Settings': 'AriaNg Settings',
'Aria2 Settings': 'Aria2 Settings',
@ -68,6 +71,9 @@
'Download Speed': 'Download Speed',
'Upload Speed': 'Upload Speed',
'Links': 'Links',
'Torrent File': 'Torrent File',
'Metalink File': 'Metalink File',
'File Name:': 'File Name:',
'Options': 'Options',
'Overview': 'Overview',
'Blocks': 'Blocks',
@ -82,6 +88,7 @@
'Seeders': 'Seeders',
'Connections': 'Connections',
'Seed Creation Time': 'Seed Creation Time',
'Download Url': 'Download Url',
'Download Dir': 'Download Dir',
'BT Tracker Servers': 'BT Tracker Servers',
'(Choose Files)': '(Choose Files)',
@ -99,6 +106,9 @@
'No Data': 'No Data',
'No connected peers': 'No connected peers',
'Failed to change some tasks state.': 'Failed to change some tasks state.',
'Confirm Restart': 'Confirm Restart',
'Are you sure you want to restart this task? AriaNg will create a same task after clicking OK.': 'Are you sure you want to restart this task? AriaNg will create a same task after clicking OK.',
'Failed to restart this task.': 'Failed to restart this task.',
'Confirm Remove': 'Confirm Remove',
'Are you sure you want to remove the selected task?': 'Are you sure you want to remove the selected task?',
'Failed to remove some task(s).': 'Failed to remove some task(s).',
@ -119,21 +129,37 @@
'Language': 'Language',
'Debug Mode': 'Debug Mode',
'Page Title': 'Page Title',
'Preview': 'Preview',
'Tips: You can use the "noprefix" tag to ignore the prefix, "nosuffix" tag ignore the suffix, and "scale=n" tag to set the decimal precision.': 'Tips: You can use the "noprefix" tag to ignore the prefix, "nosuffix" tag ignore the suffix, and "scale=n" tag to set the decimal precision.',
'Example: ${downspeed:noprefix:nosuffix:scale=1}': 'Example: ${downspeed:noprefix:nosuffix:scale=1}',
'Page Title Refresh Interval': 'Page Title Refresh Interval',
'Enable Browser Notification': 'Enable Browser Notification',
'Aria2 RPC Host': 'Aria2 RPC Host',
'Aria2 RPC Port': 'Aria2 RPC Port',
'Aria2 RPC Protocol': 'Aria2 RPC Protocol',
'Aria2 RPC Secret Token': 'Aria2 RPC Secret Token',
'Aria2 RPC Alias': 'Aria2 RPC Alias',
'Aria2 RPC Address': 'Aria2 RPC Address',
'Aria2 RPC Protocol': 'Aria2 RPC Protocol',
'Aria2 RPC Http Request Method': 'Aria2 RPC Http Request Method',
'POST method only supports aria2 v1.15.2 and above.': 'POST method only supports aria2 v1.15.2 and above.',
'Aria2 RPC Secret Token': 'Aria2 RPC Secret Token',
'Activate': 'Activate',
'Reset Settings': 'Reset Settings',
'Confirm Reset': 'Confirm Reset',
'Are you sure you want to reset all settings?': 'Are you sure you want to reset all settings?',
'Delete RPC Setting': 'Delete RPC Setting',
'Add New RPC Setting': 'Add New RPC Setting',
'Are you sure you want to remove rpc setting "{{rpcName}}"?': 'Are you sure you want to remove rpc setting "{{rpcName}}"?',
'Global Stat Refresh Interval': 'Global Stat Refresh Interval',
'Download Task Refresh Interval': 'Download Task Refresh Interval',
'Action After Creating New Tasks': 'Action After Creating New Tasks',
'Navigate to Task List Page': 'Navigate to Task List Page',
'Navigate to Task Detail Page': 'Navigate to Task Detail Page',
'Supported Placeholder': 'Supported Placeholder',
'AriaNg Title': 'AriaNg Title',
'Downloading Count': 'Downloading Count',
'Waiting Count': 'Waiting Count',
'Stopped Count': 'Stopped Count',
'You have disabled notification in your browser. You should change your browser\'s settings before you enable this function.': 'You have disabled notification in your browser. You should change your browser\'s settings before you enable this function.',
'Configuration has been modified, please reload the page for the changes to take effect.': 'Configuration has been modified, please reload the page for the changes to take effect.',
'Reload Page': 'Reload Page',
'Show Secret': 'Show Secret',
'Hide Secret': 'Hide Secret',
'Aria2 Version': 'Aria2 Version',
@ -146,6 +172,8 @@
'Session has been saved successfully.': 'Session has been saved successfully.',
'Aria2 has been shutdown successfully.': 'Aria2 has been shutdown successfully.',
'Toggle Navigation': 'Toggle Navigation',
'Quick Setting': 'Quick Setting',
'Global Speed Limit': 'Global Speed Limit',
'Loading': 'Loading...',
'More Than One Day': 'More than 1 day',
'Unknown': 'Unknown',
@ -155,10 +183,16 @@
'Seconds': 'Seconds',
'Milliseconds': 'Milliseconds',
'Http': 'Http',
'Http (Disabled)': 'Http (Disabled)',
'Https': 'Https',
'WebSocket': 'WebSocket',
'WebSocket (Disabled)': 'WebSocket (Disabled)',
'WebSocket (Security)': 'WebSocket (Security)',
'Http and WebSocket would be disabled when accessing AriaNg via Https.': 'Http and WebSocket would be disabled when accessing AriaNg via Https.',
'POST': 'POST',
'GET': 'GET',
'Disabled': 'Disabled',
'BitTorrent': 'BitTorrent',
'Changes to the settings take effect after refreshing page.': 'Changes to the settings take effect after refreshing page.',
'Type is illegal!': 'Type is illegal!',
'Parameter is invalid!': 'Parameter is invalid!',
@ -167,7 +201,12 @@
'Input number is below min value!': 'Input number is below min value {{value}}!',
'Input number is above max value!': 'Input number is above max value {{value}}!',
'Input value is invalid!': 'Input value is invalid!',
'Protocol is invalid!': 'Protocol is invalid!',
'RPC host cannot be empty!': 'RPC host cannot be empty!',
'RPC secret is not base64 encoded!': 'RPC secret is not base64 encoded!',
'URL is not base64 encoded!': 'URL is not base64 encoded!',
'Tap to configure and get started with AriaNg.': 'Tap to configure and get started with AriaNg.',
'Cannot initialize WebSocket!': 'Cannot initialize WebSocket!',
'error': {
'unknown': 'Unknown error occurred.',
'operation.timeout': 'Operation timed out.',
@ -210,6 +249,8 @@
'time.minutes': '{{value}} Minutes',
'time.hour': '{{value}} Hour',
'time.hours': '{{value}} Hours',
'requires.aria2-version': 'Requires aria2 v{{version}} or higher',
'task.new.download-links': 'Download Links ({{count}} Links):',
'task.pieceinfo': 'Completed: {{completed}}, Total: {{total}}',
'task.error-occurred': 'Error Occurred ({{errorcode}})',
'settings.file-count': '({{count}} Files)',
@ -295,6 +336,8 @@
'no-netrc.description': '',
'no-proxy.name': 'No Proxy List',
'no-proxy.description': 'Specify a comma separated list of host names, domains and network addresses with or without a subnet mask where no proxy should be used.',
'out.name': 'File Name',
'out.description': 'The file name of the downloaded file. It is always relative to the directory given in --dir option. When the --force-sequential option is used, this option is ignored.',
'proxy-method.name': 'Proxy Method',
'proxy-method.description': 'Set the method to use in proxy request. METHOD is either GET or TUNNEL. HTTPS downloads always use TUNNEL regardless of this option.',
'remote-time.name': 'Remote File Timestamp',
@ -380,7 +423,7 @@
'bt-exclude-tracker.name': 'BitTorrent Exclude Trackers',
'bt-exclude-tracker.description': 'Comma separated list of BitTorrent tracker\'s announce URI to remove. You can use special value * which matches all URIs, thus removes all announce URIs. When specifying * in shell command-line, don\'t forget to escape or quote it.',
'bt-external-ip.name': 'External IP',
'bt-external-ip.description': 'Specify the external IP address to report to a BitTorrent tracker. Although this function is named external, it can accept any kind of IP addresses. IPADDRESS must be a numeric IP address.',
'bt-external-ip.description': 'Specify the external IP address to use in BitTorrent download and DHT. It may be sent to BitTorrent tracker. For DHT, this option should be set to report that local node is downloading a particular torrent. This is critical to use DHT in a private network. Although this function is named external, it can accept any kind of IP addresses.',
'bt-force-encryption.name': 'Force Encryption',
'bt-force-encryption.description': 'Requires BitTorrent message payload encryption with arc4. This is a shorthand of --bt-require-crypto --bt-min-crypto-level=arc4. This option does not change the option value of those options. If true is given, deny legacy BitTorrent handshake and only use Obfuscation handshake and always encrypt message payload.',
'bt-hash-check-seed.name': 'Hash Check Before Seeding',
@ -484,7 +527,7 @@
'async-dns.name': 'Asynchronous DNS',
'async-dns.description': '',
'auto-file-renaming.name': 'Auto File Renaming',
'auto-file-renaming.description': 'Rename file name if the same file already exists. This option works only in HTTP(S)/FTP download. The new file name has a dot and a number(1..9999) appended.',
'auto-file-renaming.description': 'Rename file name if the same file already exists. This option works only in HTTP(S)/FTP download. The new file name has a dot and a number(1..9999) appended after the name, but before the file extension, if any.',
'auto-save-interval.name': 'Auto Save Interval',
'auto-save-interval.description': 'Save a control file(*.aria2) every SEC seconds. If 0 is given, a control file is not saved during download. aria2 saves a control file when it stops regardless of the value. The possible values are between 0 to 600.',
'conditional-get.name': 'Conditional Download',
@ -493,6 +536,8 @@
'conf-path.description': '',
'console-log-level.name': 'Console Log Level',
'console-log-level.description': '',
'content-disposition-default-utf8.name': 'Use UTF-8 to Handle Content-Disposition',
'content-disposition-default-utf8.description': 'Handle quoted string in Content-Disposition header as UTF-8 instead of ISO-8859-1, for example, the filename parameter, but not the extended version filename.',
'daemon.name': 'Enable Daemon',
'daemon.description': '',
'deferred-input.name': 'Deferred Load',
@ -514,7 +559,7 @@
'event-poll.name': 'Event Polling Method',
'event-poll.description': 'Specify the method for polling events. The possible values are epoll, kqueue, port, poll and select. For each epoll, kqueue, port and poll, it is available if system supports it. epoll is available on recent Linux. kqueue is available on various *BSD systems including Mac OS X. port is available on Open Solaris. The default value may vary depending on the system you use.',
'file-allocation.name': 'File Allocation Method',
'file-allocation.description': 'Specify file allocation method. none doesn\'t pre-allocate file space. prealloc pre-allocates file space before download begins. This may take some time depending on the size of the file. If you are using newer file systems such as ext4 (with extents support), btrfs, xfs or NTFS(MinGW build only), falloc is your best choice. It allocates large(few GiB) files almost instantly. Don\'t use falloc with legacy file systems such as ext3 and FAT32 because it takes almost same time as prealloc and it blocks aria2 entirely until allocation finishes. falloc may not be available if your system doesn\'t have posix_fallocate(3) function. trunc uses ftruncate(2) system call or platform-specific counterpart to truncate a file to a specified length.',
'file-allocation.description': 'Specify file allocation method. none doesn\'t pre-allocate file space. prealloc pre-allocates file space before download begins. This may take some time depending on the size of the file. If you are using newer file systems such as ext4 (with extents support), btrfs, xfs or NTFS(MinGW build only), falloc is your best choice. It allocates large(few GiB) files almost instantly. Don\'t use falloc with legacy file systems such as ext3 and FAT32 because it takes almost same time as prealloc and it blocks aria2 entirely until allocation finishes. falloc may not be available if your system doesn\'t have posix_fallocate(3) function. trunc uses ftruncate(2) system call or platform-specific counterpart to truncate a file to a specified length. In multi file torrent downloads, the files adjacent forward to the specified files are also allocated if they share the same piece.',
'force-save.name': 'Force Save',
'force-save.description': 'Save download with --save-session option even if the download is completed or removed. This option also saves control file in that situations. This may be useful to save BitTorrent seeding which is recognized as completed state.',
'save-not-found.name': 'Save Not Found File',
@ -573,5 +618,5 @@
};
$translateProvider.translations(ariaNgConstants.defaultLanguage, defaultLanguageResource);
}])
}]);
}());

View file

@ -3,13 +3,19 @@
angular.module('ariaNg').constant('ariaNgFileTypes', {
video: [
'.3g2',
'.3gp',
'.3gp2',
'.3gpp',
'.asf',
'.asx',
'.avi',
'.dat',
'.divx',
'.flv',
'.m1v',
'.m2ts',
'.m2v',
'.m4v',
'.mkv',
'.mov',
@ -17,59 +23,127 @@
'.mpe',
'.mpeg',
'.mpg',
'.mts',
'.ogv',
'.qt',
'.ram',
'.rm',
'.rmvb',
'.ts',
'.vob',
'.wmv'
],
audio: [
'.aac',
'.ac3',
'.adts',
'.amr',
'.ape',
'.eac3',
'.flac',
'.m1a',
'.m2a',
'.m4a',
'.mid',
'.mka',
'.mp2',
'.mp3',
'.mpa',
'.mpc',
'.ogg',
'.ra',
'.tak',
'.vqf',
'.wm',
'.wav',
'.wma'
'.wma',
'.wv'
],
picture: [
'.abr',
'.bmp',
'.emf',
'.gif',
'.j2c',
'.j2k',
'.jfif',
'.jif',
'.jp2',
'.jpc',
'.jpe',
'.jpeg',
'.jpf',
'.jpg',
'.jpk',
'.jpx',
'.pcx',
'.pct',
'.pic',
'.pict',
'.png',
'.pns',
'.psd',
'.psdx',
'.raw',
'.svg',
'.svgz',
'.tga',
'.tif',
'.tiff',
'.wmf'
'.wbm',
'.wbmp',
'.webp',
'.wmf',
'.xif'
],
document: [
'.csv',
'.doc',
'.docm',
'.docx',
'.dot',
'.dotm',
'.dotx',
'.key',
'.mpp',
'.numbers',
'.odp',
'.ods',
'.odt',
'.pages',
'.pdf',
'.pot',
'.potm',
'.potx',
'.pps',
'.ppsm',
'.ppsx',
'.ppt',
'.pptm',
'.pptx',
'.rtf',
'.txt',
'.vsd',
'.vsdx',
'.wk1',
'.wk2',
'.wk3',
'.wk4',
'.wks',
'.wpd',
'.wps',
'.xla',
'.xlam',
'.xll',
'.xlm',
'.xls',
'.xlsx'
'.xlsb',
'.xlsm',
'.xlsx',
'.xlt',
'.xltx',
'.xlw',
'.xps'
],
application: [
'.apk',
@ -81,20 +155,38 @@
'.exe',
'.ipa',
'.jar',
'.msi',
'.rpm',
'.sh'
],
archive: [
'.001',
'.7z',
'.ace',
'.arj',
'.bz2',
'.cab',
'.cbr',
'.cbz',
'.gz',
'.img',
'.iso',
'.lzh',
'.qcow2',
'.r',
'.rar',
'.sef',
'.tar',
'.tar',
'.taz',
'.tbz',
'.tbz2',
'.uue',
'.vdi',
'.vhd',
'.vmdk',
'.wim',
'.xar',
'.xz',
'.z',
'.zip'
]

View file

@ -6,9 +6,15 @@
name: 'English',
displayName: 'English'
},
'zh_CN': {
'zh_Hans': {
name: 'Simplified Chinese',
displayName: '简体中文'
displayName: '简体中文',
aliases: ['zh_CHS', 'zh_CN', 'zh_SG']
},
'zh_Hant': {
name: 'Traditional Chinese',
displayName: '繁體中文',
aliases: ['zh_CHT', 'zh_TW', 'zh_HK', 'zh_MO']
}
});
}());

View file

@ -1,28 +1,99 @@
(function () {
'use strict';
angular.module('ariaNg').controller('CommandController', ['$rootScope', '$location', '$routeParams', 'base64', 'ariaNgCommonService', 'aria2TaskService', function ($rootScope, $location, $routeParams, base64, ariaNgCommonService, aria2TaskService) {
angular.module('ariaNg').controller('CommandController', ['$rootScope', '$window', '$location', '$routeParams', 'base64', 'ariaNgDefaultOptions', 'ariaNgCommonService', 'ariaNgSettingService', 'aria2TaskService', 'ariaNgLogService', function ($rootScope, $window, $location, $routeParams, base64, ariaNgDefaultOptions, ariaNgCommonService, ariaNgSettingService, aria2TaskService, ariaNgLogService) {
var path = $location.path();
var newUrlDownload = function (url) {
return aria2TaskService.newUriTask({
var doNewTaskCommand = function (url) {
try {
url = base64.urldecode(url);
} catch (ex) {
ariaNgCommonService.showError('URL is not base64 encoded!');
return false;
}
$rootScope.loadPromise = aria2TaskService.newUriTask({
urls: [url],
options: {}
}, false, function (response) {
if (!response.success) {
return;
return false;
}
$location.path('/downloading');
});
ariaNgLogService.info('[CommandController] new download: ' + url);
return true;
};
if (path.indexOf('/new/') === 0) {
var base64Url = $routeParams.url;
var url = base64.urldecode(base64Url);
$rootScope.loadPromise = newUrlDownload(url);
} else {
ariaNgCommonService.error('Parameter is invalid!');
var doSetRpcCommand = function (rpcProtocol, rpcHost, rpcPort, rpcInterface, secret) {
rpcPort = rpcPort || ariaNgDefaultOptions.rpcPort;
rpcInterface = rpcInterface || ariaNgDefaultOptions.rpcInterface;
secret = secret || ariaNgDefaultOptions.secret;
ariaNgLogService.info('[CommandController] set rpc: ' + rpcProtocol + '://' + rpcHost + ':' + rpcPort + '/' + rpcInterface + ', secret: ' + secret);
if (!rpcProtocol || (rpcProtocol !== 'http' && rpcProtocol !== 'https' && rpcProtocol !== 'ws' && rpcProtocol !== 'wss')) {
ariaNgCommonService.showError('Protocol is invalid!');
return false;
}
if (!rpcHost) {
ariaNgCommonService.showError('RPC host cannot be empty!');
return false;
}
if (secret) {
try {
secret = base64.urldecode(secret);
} catch (ex) {
ariaNgCommonService.showError('RPC secret is not base64 encoded!');
return false;
}
}
var newSetting = {
rpcAlias: '',
rpcHost: rpcHost,
rpcPort: rpcPort,
rpcInterface: rpcInterface,
protocol: rpcProtocol,
httpMethod: ariaNgDefaultOptions.httpMethod,
secret: secret
};
if (ariaNgSettingService.isRpcSettingEqualsDefault(newSetting)) {
$location.path('/downloading');
} else {
ariaNgSettingService.setDefaultRpcSetting(newSetting, {
keepCurrent: false,
forceSet: true
});
$location.path('/downloading');
$window.location.reload();
}
return true;
};
var doCommand = function (path, params) {
if (path.indexOf('/new') === 0) {
return doNewTaskCommand(params.url);
} else if (path.indexOf('/settings/rpc/set') === 0) {
return doSetRpcCommand(params.protocol, params.host, params.port, params.interface, params.secret);
} else {
ariaNgCommonService.showError('Parameter is invalid!');
return false;
}
};
var allParameters = angular.extend({}, $routeParams, $location.search());
if (!doCommand(path, allParameters)) {
$location.path('/downloading');
}
}]);
}());

View file

@ -1,26 +1,12 @@
(function () {
'use strict';
angular.module('ariaNg').controller('MainController', ['$rootScope', '$scope', '$route', '$location', '$document', '$interval', 'aria2RpcErrors', 'ariaNgCommonService', 'ariaNgSettingService', 'ariaNgMonitorService', 'ariaNgNotificationService', 'aria2TaskService', 'aria2SettingService', function ($rootScope, $scope, $route, $location, $document, $interval, aria2RpcErrors, ariaNgCommonService, ariaNgSettingService, ariaNgMonitorService, ariaNgNotificationService, aria2TaskService, aria2SettingService) {
angular.module('ariaNg').controller('MainController', ['$rootScope', '$scope', '$route', '$window', '$location', '$document', '$interval', 'aria2RpcErrors', 'ariaNgCommonService', 'ariaNgSettingService', 'ariaNgTitleService', 'ariaNgMonitorService', 'ariaNgNotificationService', 'aria2TaskService', 'aria2SettingService', function ($rootScope, $scope, $route, $window, $location, $document, $interval, aria2RpcErrors, ariaNgCommonService, ariaNgSettingService, ariaNgTitleService, ariaNgMonitorService, ariaNgNotificationService, aria2TaskService, aria2SettingService) {
var pageTitleRefreshPromise = null;
var globalStatRefreshPromise = null;
var refreshPageTitle = function () {
var context = (function (globalStat) {
if (!globalStat) {
return null;
}
return {
downloadingCount: globalStat.numActive,
waitingCount: globalStat.numWaiting,
stoppedCount: globalStat.numStopped,
downloadSpeed: globalStat.downloadSpeed,
uploadSpeed: globalStat.uploadSpeed
}
})($scope.globalStat);
$document[0].title = ariaNgSettingService.getFinalTitle(context);
$document[0].title = ariaNgTitleService.getFinalTitleByGlobalStat($scope.globalStat);
};
var refreshGlobalStat = function (silent, callback) {
@ -50,6 +36,10 @@
data: ariaNgMonitorService.getGlobalStatsData()
};
$scope.quickSettingContext = null;
$scope.rpcSettings = ariaNgSettingService.getAllRpcSettings();
$scope.isTaskSelected = function () {
return $rootScope.taskContext.getSelectedTaskIds().length > 0;
};
@ -134,6 +124,27 @@
}, (gids.length > 1));
};
$scope.restart = function (task) {
ariaNgCommonService.confirm('Confirm Restart', 'Are you sure you want to restart this task? AriaNg will create a same task after clicking OK.', 'info', function () {
$rootScope.loadPromise = aria2TaskService.restartTask(task.gid, function (response) {
if (!response.success) {
ariaNgCommonService.showError('Failed to restart this task.');
return;
}
refreshGlobalStat(true);
if (response.success) {
if ($location.path() !== '/downloading') {
$location.path('/downloading');
} else {
$route.reload();
}
}
}, false);
});
};
$scope.removeTasks = function () {
var tasks = $rootScope.taskContext.getSelectedTasks();
@ -154,10 +165,10 @@
refreshGlobalStat(true);
if (!response.hasError) {
if ($location.path() === '/stopped') {
$route.reload();
} else {
if ($location.path() !== '/stopped') {
$location.path('/stopped');
} else {
$route.reload();
}
}
}, (tasks.length > 1));
@ -173,10 +184,10 @@
refreshGlobalStat(true);
if ($location.path() === '/stopped') {
$route.reload();
} else {
if ($location.path() !== '/stopped') {
$location.path('/stopped');
} else {
$route.reload();
}
});
});
@ -204,6 +215,22 @@
return orderType.equals(targetType);
};
$scope.showQuickSettingDialog = function (type, title) {
$scope.quickSettingContext = {
type: type,
title: title
};
};
$scope.switchRpcSetting = function (setting) {
if (setting.isDefault) {
return;
}
ariaNgSettingService.setDefaultRpcSetting(setting);
$window.location.reload();
};
if (ariaNgSettingService.getTitleRefreshInterval() > 0) {
pageTitleRefreshPromise = $interval(function () {
refreshPageTitle();

View file

@ -1,21 +1,76 @@
(function () {
'use strict';
angular.module('ariaNg').controller('NewTaskController', ['$rootScope', '$scope', '$location', '$timeout', 'aria2SettingService', 'aria2TaskService', 'ariaNgFileService', function ($rootScope, $scope, $location, $timeout, aria2SettingService, aria2TaskService, ariaNgFileService) {
angular.module('ariaNg').controller('NewTaskController', ['$rootScope', '$scope', '$location', '$timeout', 'base64', 'ariaNgCommonService', 'ariaNgLogService', 'ariaNgSettingService', 'ariaNgFileService', 'aria2SettingService', 'aria2TaskService', function ($rootScope, $scope, $location, $timeout, base64, ariaNgCommonService, ariaNgLogService, ariaNgSettingService, ariaNgFileService, aria2SettingService, aria2TaskService) {
var tabOrders = ['links', 'options'];
var parameters = $location.search();
var downloadByLinks = function (pauseOnAdded, responseCallback) {
var urls = ariaNgCommonService.parseUrlsFromOriginInput($scope.context.urls);
var options = angular.copy($scope.context.options);
var tasks = [];
for (var i = 0; i < urls.length; i++) {
if (urls[i] === '' || urls[i].trim() === '') {
continue;
}
tasks.push({
urls: [urls[i].trim()],
options: options
});
}
return aria2TaskService.newUriTasks(tasks, pauseOnAdded, responseCallback);
};
var downloadByTorrent = function (pauseOnAdded, responseCallback) {
var task = {
content: $scope.context.uploadFile.base64Content,
options: angular.copy($scope.context.options)
};
return aria2TaskService.newTorrentTask(task, pauseOnAdded, responseCallback);
};
var downloadByMetalink = function (pauseOnAdded, responseCallback) {
var task = {
content: $scope.context.uploadFile.base64Content,
options: angular.copy($scope.context.options)
};
return aria2TaskService.newMetalinkTask(task, pauseOnAdded, responseCallback);
};
$scope.context = {
currentTab: 'links',
taskType: 'urls',
urls: '',
uploadFile: null,
availableOptions: (function () {
var keys = aria2SettingService.getNewTaskOptionKeys();
return aria2SettingService.getSpecifiedOptions(keys);
return aria2SettingService.getSpecifiedOptions(keys, {
disableRequired: true
});
})(),
globalOptions: null,
options: {}
options: {},
optionFilter: {
global: true,
http: false,
bittorrent: false
}
};
if (parameters.url) {
try {
$scope.context.urls = base64.urldecode(parameters.url);
} catch (ex) {
ariaNgLogService.error('[NewTaskController] base64 decode error, url=' + parameters.url, ex);
}
}
$scope.changeTab = function (tabName) {
if (tabName === 'options') {
$scope.loadDefaultOption();
@ -60,65 +115,56 @@
$scope.openTorrent = function () {
ariaNgFileService.openFileContent('.torrent', function (result) {
var task = {
content: result.base64Content,
options: angular.copy($scope.context.options)
};
$rootScope.loadPromise = aria2TaskService.newTorrentTask(task, true, function (response) {
if (!response.success) {
return;
}
$location.path('/task/detail/' + response.data);
});
$scope.context.uploadFile = result;
$scope.context.taskType = 'torrent';
$scope.changeTab('options');
}, function (error) {
ariaNgCommonService.showError(error);
});
};
$scope.openMetalink = function () {
ariaNgFileService.openFileContent('.meta4,.metalink', function (result) {
var task = {
content: result.base64Content,
options: angular.copy($scope.context.options)
};
$rootScope.loadPromise = aria2TaskService.newMetalinkTask(task, true, function (response) {
if (!response.success) {
return;
}
$location.path('/task/detail/' + response.data);
});
$scope.context.uploadFile = result;
$scope.context.taskType = 'metalink';
$scope.changeTab('options');
}, function (error) {
ariaNgCommonService.showError(error);
});
};
$scope.startDownload = function (pauseOnAdded) {
var urls = $scope.context.urls.split('\n');
var options = angular.copy($scope.context.options);
var tasks = [];
for (var i = 0; i < urls.length; i++) {
if (urls[i] === '' || urls[i].trim() === '') {
continue;
}
tasks.push({
urls: [urls[i].trim()],
options: options
});
}
$rootScope.loadPromise = aria2TaskService.newUriTasks(tasks, pauseOnAdded, function (response) {
if (!response.hasSuccess) {
var responseCallback = function (response) {
if (!response.hasSuccess && !response.success) {
return;
}
if (pauseOnAdded) {
$location.path('/waiting');
} else {
$location.path('/downloading');
var firstTask = null;
if (response.results && response.results.length > 0) {
firstTask = response.results[0];
} else if (response) {
firstTask = response;
}
});
if (ariaNgSettingService.getAfterCreatingNewTask() === 'task-detail' && firstTask && firstTask.data) {
$location.path('/task/detail/' + firstTask.data);
} else {
if (pauseOnAdded) {
$location.path('/waiting');
} else {
$location.path('/downloading');
}
}
};
if ($scope.context.taskType === 'urls') {
$rootScope.loadPromise = downloadByLinks(pauseOnAdded, responseCallback);
} else if ($scope.context.taskType === 'torrent') {
$rootScope.loadPromise = downloadByTorrent(pauseOnAdded, responseCallback);
} else if ($scope.context.taskType === 'metalink') {
$rootScope.loadPromise = downloadByMetalink(pauseOnAdded, responseCallback);
}
};
$scope.setOption = function (key, value, optionStatus) {
@ -137,6 +183,11 @@
}
};
$scope.getValidUrlsCount = function () {
var urls = ariaNgCommonService.parseUrlsFromOriginInput($scope.context.urls);
return urls ? urls.length : 0;
};
$rootScope.loadPromise = $timeout(function () {}, 100);
}]);
}());

View file

@ -1,31 +1,80 @@
(function () {
'use strict';
angular.module('ariaNg').controller('AriaNgSettingsController', ['$rootScope', '$scope', '$routeParams', '$timeout', 'ariaNgLanguages', 'ariaNgCommonService', 'ariaNgSettingService', 'ariaNgNotificationService', function ($rootScope, $scope, $routeParams, $timeout, ariaNgLanguages, ariaNgCommonService, ariaNgSettingService, ariaNgNotificationService) {
var tabOrders = ['global', 'rpc'];
angular.module('ariaNg').controller('AriaNgSettingsController', ['$rootScope', '$scope', '$routeParams', '$window', '$interval', '$timeout', 'ariaNgLanguages', 'ariaNgCommonService', 'ariaNgSettingService', 'ariaNgMonitorService', 'ariaNgNotificationService', 'ariaNgTitleService', function ($rootScope, $scope, $routeParams, $window, $interval, $timeout, ariaNgLanguages, ariaNgCommonService, ariaNgSettingService, ariaNgMonitorService, ariaNgNotificationService, ariaNgTitleService) {
var extendType = $routeParams.extendType;
var lastRefreshPageNotification = null;
var getFinalTitle = function () {
return ariaNgTitleService.getFinalTitleByGlobalStat(ariaNgMonitorService.getCurrentGlobalStat());
};
var setNeedRefreshPage = function () {
if (lastRefreshPageNotification) {
return;
}
var noticicationScope = $rootScope.$new();
noticicationScope.refreshPage = function () {
$window.location.reload();
};
lastRefreshPageNotification = ariaNgNotificationService.notifyInPage('', 'Configuration has been modified, please reload the page for the changes to take effect.', {
delay: false,
type: 'info',
templateUrl: 'setting-changed-notification.html',
scope: noticicationScope,
onClose: function () {
lastRefreshPageNotification = null;
}
});
};
$scope.context = {
currentTab: 'global',
languages: ariaNgLanguages,
titlePreview: getFinalTitle(),
availableTime: ariaNgCommonService.getTimeOptions([1000, 2000, 3000, 5000, 10000, 30000, 60000], true),
trueFalseOptions: [{name: 'True', value: true}, {name: 'False', value: false}],
showRpcSecret: false,
isInsecureProtocolDisabled: ariaNgSettingService.isInsecureProtocolDisabled(),
settings: ariaNgSettingService.getAllOptions(),
sessionSettings: ariaNgSettingService.getAllSessionOptions()
sessionSettings: ariaNgSettingService.getAllSessionOptions(),
rpcSettings: ariaNgSettingService.getAllRpcSettings()
};
$scope.context.showDebugMode = $scope.context.sessionSettings.debugMode || extendType === 'debug';
$scope.changeTab = function (tabName) {
$scope.context.currentTab = tabName;
$scope.changeGlobalTab = function () {
$scope.context.currentTab = 'global';
};
$scope.isCurrentGlobalTab = function () {
return $scope.context.currentTab === 'global';
};
$scope.changeRpcTab = function (rpcIndex) {
$scope.context.currentTab = 'rpc' + rpcIndex;
};
$scope.isCurrentRpcTab = function (rpcIndex) {
return $scope.context.currentTab === 'rpc' + rpcIndex;
};
$scope.updateTitlePreview = function () {
$scope.context.titlePreview = getFinalTitle();
};
$rootScope.swipeActions.extentLeftSwipe = function () {
var tabIndex = tabOrders.indexOf($scope.context.currentTab);
var tabIndex = -1;
if (tabIndex < tabOrders.length - 1) {
$scope.changeTab(tabOrders[tabIndex + 1]);
if (!$scope.isCurrentGlobalTab()) {
tabIndex = parseInt($scope.context.currentTab.substring(3));
}
if (tabIndex < $scope.context.rpcSettings.length - 1) {
$scope.changeRpcTab(tabIndex + 1);
return true;
} else {
return false;
@ -33,21 +82,48 @@
};
$rootScope.swipeActions.extentRightSwipe = function () {
var tabIndex = tabOrders.indexOf($scope.context.currentTab);
var tabIndex = -1;
if (!$scope.isCurrentGlobalTab()) {
tabIndex = parseInt($scope.context.currentTab.substring(3));
}
if (tabIndex > 0) {
$scope.changeTab(tabOrders[tabIndex - 1]);
$scope.changeRpcTab(tabIndex - 1);
return true;
} else if (tabIndex === 0) {
$scope.changeGlobalTab();
return true;
} else {
return false;
}
};
$scope.settingService = ariaNgSettingService;
$scope.setLanguage = function (value) {
ariaNgSettingService.setLanguage(value);
$scope.updateTitlePreview();
};
$scope.setAfterCreatingNewTask = function (value) {
ariaNgSettingService.setAfterCreatingNewTask(value);
};
$scope.setDebugMode = function (value) {
ariaNgSettingService.setDebugMode(value);
};
$scope.setTitle = function (value) {
ariaNgSettingService.setTitle(value);
};
$scope.setTitleRefreshInterval = function (value) {
setNeedRefreshPage();
ariaNgSettingService.setTitleRefreshInterval(value);
};
$scope.isSupportNotification = function () {
return ariaNgNotificationService.isSupportBrowserNotification() &&
ariaNgSettingService.isUseWebSocket($scope.context.settings.protocol);
ariaNgSettingService.isCurrentRpcUseWebSocket($scope.context.settings.protocol);
};
$scope.setEnableBrowserNotification = function (value) {
@ -63,7 +139,67 @@
}
};
$('[data-toggle="popover"]').popover();
$scope.setGlobalStatRefreshInterval = function (value) {
setNeedRefreshPage();
ariaNgSettingService.setGlobalStatRefreshInterval(value);
};
$scope.setDownloadTaskRefreshInterval = function (value) {
setNeedRefreshPage();
ariaNgSettingService.setDownloadTaskRefreshInterval(value);
};
$scope.addNewRpcSetting = function () {
setNeedRefreshPage();
var newRpcSetting = ariaNgSettingService.addNewRpcSetting();
$scope.context.rpcSettings.push(newRpcSetting);
$scope.changeRpcTab($scope.context.rpcSettings.length - 1);
};
$scope.updateRpcSetting = function (setting, field) {
setNeedRefreshPage();
ariaNgSettingService.updateRpcSetting(setting, field);
};
$scope.removeRpcSetting = function (setting) {
var rpcName = (setting.rpcAlias ? setting.rpcAlias : setting.rpcHost + ':' + setting.rpcPort);
ariaNgCommonService.confirm('Confirm Remove', 'Are you sure you want to remove rpc setting "{{rpcName}}"?', 'warning', function () {
setNeedRefreshPage();
var index = $scope.context.rpcSettings.indexOf(setting);
ariaNgSettingService.removeRpcSetting(setting);
$scope.context.rpcSettings.splice(index, 1);
if (index >= $scope.context.rpcSettings.length) {
$scope.changeRpcTab($scope.context.rpcSettings.length - 1);
}
}, false, {
textParams: {
rpcName: rpcName
}
});
};
$scope.setDefaultRpcSetting = function (setting) {
if (setting.isDefault) {
return;
}
ariaNgSettingService.setDefaultRpcSetting(setting);
$window.location.reload();
};
$scope.resetSettings = function () {
ariaNgCommonService.confirm('Confirm Reset', 'Are you sure you want to reset all settings?', 'warning', function () {
ariaNgSettingService.resetSettings();
$window.location.reload();
});
};
angular.element('[data-toggle="popover"]').popover();
$rootScope.loadPromise = $timeout(function () {}, 100);
}]);

View file

@ -3,7 +3,7 @@
angular.module('ariaNg').controller('Aria2StatusController', ['$rootScope', '$scope', 'ariaNgCommonService', 'ariaNgSettingService', 'aria2SettingService', function ($rootScope, $scope, ariaNgCommonService, ariaNgSettingService, aria2SettingService) {
$scope.context = {
host: ariaNgSettingService.getJsonRpcUrl(),
host: ariaNgSettingService.getCurrentRpcUrl(),
status: 'Connecting',
serverStatus: null
};

View file

@ -9,11 +9,9 @@
var getAvailableOptions = function (status, isBittorrent) {
var keys = aria2SettingService.getAvailableTaskOptionKeys(status, isBittorrent);
if (!keys) {
return;
}
return aria2SettingService.getSpecifiedOptions(keys);
return aria2SettingService.getSpecifiedOptions(keys, {
disableRequired: true
});
};
var processTask = function (task) {
@ -59,12 +57,10 @@
var refreshDownloadTask = function (silent) {
if (pauseDownloadTaskRefresh) {
return;
}
}
var processError = function (message) {
if (message === aria2RpcErrors.Unauthorized.message) {
$interval.cancel(downloadTaskRefreshPromise);
}
$interval.cancel(downloadTaskRefreshPromise);
};
var includeLocalPeer = true;
@ -232,6 +228,10 @@
for (var i = 0; i < $scope.task.files.length; i++) {
var extension = ariaNgCommonService.getFileExtension($scope.task.files[i].fileName);
if (extension) {
extension = extension.toLowerCase();
}
if (extensions.indexOf(extension) >= 0) {
fileIndexes.push(i);

View file

@ -70,6 +70,7 @@
};
$rootScope.taskContext = {
rpcStatus: 'Connecting',
list: [],
selected: {},
enableSelectAll: false,
@ -169,6 +170,14 @@
});
});
aria2TaskService.onConnectSuccess(function () {
$rootScope.taskContext.rpcStatus = 'Connected';
});
aria2TaskService.onConnectError(function () {
$rootScope.taskContext.rpcStatus = 'Not Connected';
});
aria2TaskService.onTaskCompleted(function (event) {
ariaNgNotificationService.notifyTaskComplete(event.task);
});

View file

@ -67,6 +67,14 @@
templateUrl: 'views/settings-aria2.html',
controller: 'Aria2SettingsController'
})
.when('/settings/rpc/set', {
template: '',
controller: 'CommandController'
})
.when('/settings/rpc/set/:protocol/:host/:port/:interface/:secret?', {
template: '',
controller: 'CommandController'
})
.when('/status', {
templateUrl: 'views/status.html',
controller: 'Aria2StatusController'

View file

@ -38,6 +38,12 @@
setOptions(value);
}
}, true);
scope.$on('$destroy', function() {
if (chart && !chart.isDisposed()) {
chart.dispose();
}
});
}
};
}]).directive('ngPopChart', ['$window', 'chartTheme', function ($window, chartTheme) {
@ -65,7 +71,7 @@
content: '<div class="chart-pop-wrapper"><div class="chart-pop ' + options.ngPopoverClass + '">' + loadingIcon +'</div></div>',
html: true,
placement: options.ngPlacement,
template: '<div class="popover chart-popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>',
template: '<div class="popover chart-popover" role="tooltip"><div class="arrow"></div><div class="popover-content"></div></div>',
trigger: options.ngTrigger
}).on('shown.bs.popover', function () {
var wrapper = angular.element('.chart-pop');
@ -79,7 +85,7 @@
chart = echarts.init(wrapper[0], chartTheme.get(options.ngTheme));
}).on('hide.bs.popover', function () {
if (chart && chart.isDisposed()) {
if (chart && !chart.isDisposed()) {
chart.dispose();
}
}).on('hidden.bs.popover', function () {

View file

@ -170,17 +170,17 @@
return;
}
if (scope.option.type === 'integer' && !/^-?\d+$/.test(optionValue)) {
if (optionValue !== '' && scope.option.type === 'integer' && !/^-?\d+$/.test(optionValue)) {
scope.optionStatus.setError('Input number is invalid!');
return;
}
if (scope.option.type === 'float' && !/^-?(\d*\.)?\d+$/.test(optionValue)) {
if (optionValue !== '' && scope.option.type === 'float' && !/^-?(\d*\.)?\d+$/.test(optionValue)) {
scope.optionStatus.setError('Input number is invalid!');
return;
}
if ((scope.option.type === 'integer' || scope.option.type === 'float') && (angular.isDefined(scope.option.min) || angular.isDefined(scope.option.max))) {
if (optionValue !== '' && (scope.option.type === 'integer' || scope.option.type === 'float') && (angular.isDefined(scope.option.min) || angular.isDefined(scope.option.max))) {
var number = optionValue;
if (scope.option.type === 'integer') {
@ -200,7 +200,7 @@
}
}
if (angular.isDefined(scope.option.pattern) && !(new RegExp(scope.option.pattern).test(optionValue))) {
if (optionValue !== '' && angular.isDefined(scope.option.pattern) && !(new RegExp(scope.option.pattern).test(optionValue))) {
scope.optionStatus.setError('Input value is invalid!');
return;
}

View file

@ -0,0 +1,69 @@
(function () {
'use strict';
angular.module('ariaNg').directive('ngSettingDialog', ['ariaNgCommonService', 'aria2SettingService', function (ariaNgCommonService, aria2SettingService) {
return {
restrict: 'E',
templateUrl: 'views/setting-dialog.html',
replace: true,
scope: {
setting: '='
},
link: function (scope, element, attrs) {
scope.context = {
isLoading: false,
availableOptions: [],
globalOptions: []
};
scope.setGlobalOption = function (key, value, optionStatus) {
return aria2SettingService.setGlobalOption(key, value, function (response) {
if (response.success && response.data === 'OK') {
optionStatus.setSuccess();
} else {
optionStatus.setFailed(response.data.message);
}
}, true);
};
var loadOptions = function (type) {
var keys = aria2SettingService.getaria2QuickSettingsAvailableOptions(type);
if (!keys) {
ariaNgCommonService.showError('Type is illegal!');
return;
}
scope.context.availableOptions = aria2SettingService.getSpecifiedOptions(keys);
};
var loadAria2OptionsValue = function () {
scope.context.isLoading = true;
return aria2SettingService.getGlobalOption(function (response) {
scope.context.isLoading = false;
if (response.success) {
scope.context.globalOptions = response.data;
}
});
};
angular.element('#quickSettingModal').on('hidden.bs.modal', function () {
scope.setting = null;
scope.context.availableOptions = [];
scope.context.globalOptions = [];
});
scope.$watch('setting', function (setting) {
if (setting) {
loadOptions(setting.type);
loadAria2OptionsValue();
angular.element('#quickSettingModal').modal('show');
}
}, true);
}
};
}]);
}());

View file

@ -0,0 +1,28 @@
(function () {
'use strict';
angular.module('ariaNg').directive('ngValidUrls', ['ariaNgCommonService', function (ariaNgCommonService) {
var DIRECTIVE_ID = 'invalidUrls';
return {
restrict: 'A',
require: '?ngModel',
link: function (scope, element, attrs, ngModel) {
var handleChange = function (value) {
if (angular.isUndefined(value) || value === '') {
return;
}
var urls = ariaNgCommonService.parseUrlsFromOriginInput(value);
var valid = urls && urls.length > 0;
ngModel.$setValidity(DIRECTIVE_ID, valid);
};
scope.$watch(function () {
return ngModel.$viewValue;
}, handleChange);
}
};
}]);
}());

View file

@ -6,6 +6,6 @@
var timespan = moment.duration(duration, sourceUnit);
var time = moment.utc(timespan.asMilliseconds());
return time.format(format);
}
};
}]);
}());

View file

@ -13,7 +13,9 @@
return array;
}
if (orderType.type === 'name') {
if (orderType.type === 'index') {
return $filter('orderBy')(array, ['index'], orderType.reverse);
} else if (orderType.type === 'name') {
return $filter('orderBy')(array, ['fileName'], orderType.reverse);
} else if (orderType.type === 'size') {
return $filter('orderBy')(array, ['length'], orderType.reverse);
@ -22,6 +24,6 @@
} else {
return array;
}
}
};
}]);
}());

View file

@ -5,6 +5,6 @@
return function (time) {
var format = $translate.instant('format.longdate');
return moment(time).format(format);
}
};
}]);
}());

View file

@ -26,6 +26,6 @@
} else {
return array;
}
}
};
}]);
}());

View file

@ -7,6 +7,6 @@
var result = parseInt(value * ratio) / ratio;
return $filter('number')(result, precision);
}
};
}]);
}());

View file

@ -28,6 +28,6 @@
} else {
return array;
}
}
};
}]);
}());

View file

@ -26,6 +26,6 @@
} else {
return '';
}
}
};
});
}());

View file

@ -1,7 +1,7 @@
(function () {
'use strict';
angular.module('ariaNg').filter('readableVolumn', ['$filter', function ($filter) {
angular.module('ariaNg').filter('readableVolume', ['$filter', function ($filter) {
var units = [ 'B', 'KB', 'MB', 'GB' ];
var defaultFractionSize = 2;
@ -32,6 +32,6 @@
value = $filter('number')(value, fractionSize);
return value + ' ' + unit;
}
};
}]);
}());

View file

@ -1,8 +1,53 @@
(function () {
'use strict';
angular.module('ariaNg').factory('aria2HttpRpcService', ['$http', 'ariaNgSettingService', 'ariaNgLogService', function ($http, ariaNgSettingService, ariaNgLogService) {
var rpcUrl = ariaNgSettingService.getJsonRpcUrl();
angular.module('ariaNg').factory('aria2HttpRpcService', ['$http', 'base64', 'ariaNgSettingService', 'ariaNgLogService', function ($http, base64, ariaNgSettingService, ariaNgLogService) {
var rpcUrl = ariaNgSettingService.getCurrentRpcUrl();
var method = ariaNgSettingService.getCurrentRpcHttpMethod();
var getUrlWithQueryString = function (url, parameters) {
if (!url || url.length < 1) {
return url;
}
var queryString = '';
for (var key in parameters) {
if (!parameters.hasOwnProperty(key)) {
continue;
}
var value = parameters[key];
if (value === null || angular.isUndefined(value)) {
continue;
}
if (queryString.length > 0) {
queryString += '&';
}
if (angular.isObject(value) || angular.isArray(value)) {
value = angular.toJson(value);
value = base64.encode(value);
value = encodeURIComponent(value);
}
queryString += key + '=' + value;
}
if (queryString.length < 1) {
return url;
}
if (url.indexOf('?') < 0) {
queryString = '?' + queryString;
} else {
queryString = '&' + queryString;
}
return url + queryString;
};
return {
request: function (context) {
@ -12,14 +57,21 @@
var requestContext = {
url: rpcUrl,
method: 'POST',
data: context.requestBody
method: method
};
ariaNgLogService.debug('Http Request Start', requestContext);
if (requestContext.method === 'POST') {
requestContext.data = context.requestBody;
} else if (requestContext.method === 'GET') {
requestContext.url = getUrlWithQueryString(requestContext.url, context.requestBody);
}
return $http(requestContext).success(function (data) {
ariaNgLogService.debug('Http Response Success', data);
ariaNgLogService.debug('[aria2HttpRpcService.request] request start', requestContext);
return $http(requestContext).then(function onSuccess(response) {
var data = response.data;
ariaNgLogService.debug('[aria2HttpRpcService.request] response success', data);
if (!data) {
return;
@ -28,8 +80,10 @@
if (context.successCallback) {
context.successCallback(data.id, data.result);
}
}).error(function (data) {
ariaNgLogService.debug('Http Response Error', data);
}).catch(function onError(response) {
var data = response.data;
ariaNgLogService.debug('[aria2HttpRpcService.request] response error', data);
if (!data) {
data = {

View file

@ -1,12 +1,14 @@
(function () {
'use strict';
angular.module('ariaNg').factory('aria2RpcService', ['$q', 'aria2RpcConstants', 'aria2RpcErrors', 'ariaNgCommonService', 'ariaNgSettingService', 'aria2HttpRpcService', 'aria2WebSocketRpcService', function ($q, aria2RpcConstants, aria2RpcErrors, ariaNgCommonService, ariaNgSettingService, aria2HttpRpcService, aria2WebSocketRpcService) {
var rpcImplementService = ariaNgSettingService.isUseWebSocket() ? aria2WebSocketRpcService : aria2HttpRpcService;
angular.module('ariaNg').factory('aria2RpcService', ['$q', 'aria2RpcConstants', 'aria2RpcErrors', 'ariaNgCommonService', 'ariaNgSettingService', 'ariaNgLogService', 'aria2HttpRpcService', 'aria2WebSocketRpcService', function ($q, aria2RpcConstants, aria2RpcErrors, ariaNgCommonService, ariaNgSettingService, ariaNgLogService, aria2HttpRpcService, aria2WebSocketRpcService) {
var rpcImplementService = ariaNgSettingService.isCurrentRpcUseWebSocket() ? aria2WebSocketRpcService : aria2HttpRpcService;
var isConnected = false;
var secret = ariaNgSettingService.getSecret();
var secret = ariaNgSettingService.getCurrentRpcSecret();
var onFirstSuccessCallbacks = [];
var onConnectSuccessCallbacks = [];
var onConnectErrorCallbacks = [];
var onDownloadStartCallbacks = [];
var onDownloadPauseCallbacks = [];
var onDownloadStopCallbacks = [];
@ -65,13 +67,13 @@
});
};
var fireFirstSuccessEvent = function () {
if (!angular.isArray(onFirstSuccessCallbacks) || onFirstSuccessCallbacks.length < 1) {
var fireCustomEvent = function (callbacks) {
if (!angular.isArray(callbacks) || callbacks.length < 1) {
return;
}
for (var i = 0; i < onFirstSuccessCallbacks.length; i++) {
var callback = onFirstSuccessCallbacks[i];
for (var i = 0; i < callbacks.length; i++) {
var callback = callbacks[i];
callback();
}
};
@ -110,6 +112,8 @@
return false;
}
ariaNgLogService.error('[aria2RpcService.processError] ' + error.message, error);
if (aria2RpcErrors[error.message] && aria2RpcErrors[error.message].tipTextKey) {
ariaNgCommonService.showError(aria2RpcErrors[error.message].tipTextKey);
return true;
@ -136,11 +140,6 @@
var innerContext = arguments[1];
context.successCallback = function (id, result) {
if (!isConnected) {
isConnected = true;
fireFirstSuccessEvent();
}
if (innerContext.callback) {
innerContext.callback({
id: id,
@ -149,6 +148,13 @@
context: innerContext
});
}
fireCustomEvent(onConnectSuccessCallbacks);
if (!isConnected) {
isConnected = true;
fireCustomEvent(onFirstSuccessCallbacks);
}
};
context.errorCallback = function (id, error) {
@ -167,6 +173,8 @@
context: innerContext
});
}
fireCustomEvent(onConnectErrorCallbacks);
};
}
@ -351,7 +359,7 @@
},
tellStopped: function (context, returnContextOnly) {
return invoke(buildRequestContext('tellStopped', context,
angular.isDefined(context.offset) ? context.offset : 0,
angular.isDefined(context.offset) ? context.offset : -1,
angular.isDefined(context.num) ? context.num : 1000,
angular.isDefined(context.requestParams) ? context.requestParams: null
), !!returnContextOnly);
@ -419,6 +427,12 @@
onFirstSuccess: function (context) {
onFirstSuccessCallbacks.push(context.callback);
},
onConnectSuccess: function (context) {
onConnectSuccessCallbacks.push(context.callback);
},
onConnectError: function (context) {
onConnectErrorCallbacks.push(context.callback);
},
onDownloadStart: function (context) {
onDownloadStartCallbacks.push(context.callback);
},

View file

@ -1,7 +1,7 @@
(function () {
'use strict';
angular.module('ariaNg').factory('aria2SettingService', ['aria2AllOptions', 'aria2GlobalAvailableOptions', 'aria2TaskAvailableOptions', 'ariaNgCommonService', 'aria2RpcService', function (aria2AllOptions, aria2GlobalAvailableOptions, aria2TaskAvailableOptions, ariaNgCommonService, aria2RpcService) {
angular.module('ariaNg').factory('aria2SettingService', ['aria2AllOptions', 'aria2GlobalAvailableOptions', 'aria2QuickSettingsAvailableOptions', 'aria2TaskAvailableOptions', 'aria2RpcService', 'ariaNgLogService', function (aria2AllOptions, aria2GlobalAvailableOptions, aria2QuickSettingsAvailableOptions, aria2TaskAvailableOptions, aria2RpcService, ariaNgLogService) {
var processStatResult = function (stat) {
if (!stat) {
return stat;
@ -38,6 +38,13 @@
return false;
}
},
getaria2QuickSettingsAvailableOptions: function (type) {
if (type === 'globalSpeedLimit') {
return aria2QuickSettingsAvailableOptions.globalSpeedLimitOptions;
} else {
return false;
}
},
getAvailableTaskOptionKeys: function (status, isBittorrent) {
var allOptions = aria2TaskAvailableOptions.taskOptions;
var availableOptions = [];
@ -45,20 +52,21 @@
for (var i = 0; i < allOptions.length; i++) {
var option = allOptions[i];
var optionKey = {
key: option.key
key: option.key,
category: option.category
};
if (option.newOnly) {
if (option.canShow && option.canShow.indexOf(status) < 0) {
continue;
}
if (option.httpOnly && isBittorrent) {
if (option.category === 'http' && isBittorrent) {
continue;
} else if (option.btOnly && !isBittorrent) {
} else if (option.category === 'bittorrent' && !isBittorrent) {
continue;
}
if (option.activeReadonly && status === 'active') {
if (option.canUpdate && option.canUpdate.indexOf(status) < 0) {
optionKey.readonly = true;
}
@ -67,33 +75,48 @@
return availableOptions;
},
getNewTaskOptionKeys: function (isBittorrent) {
getNewTaskOptionKeys: function () {
var allOptions = aria2TaskAvailableOptions.taskOptions;
var availableOptions = [];
for (var i = 0; i < allOptions.length; i++) {
var option = allOptions[i];
var optionKey = {
key: option.key
key: option.key,
category: option.category
};
if (option.canShow && option.canShow.indexOf('new') < 0) {
continue;
}
if (option.canUpdate && option.canUpdate.indexOf('new') < 0) {
optionKey.readonly = true;
}
availableOptions.push(optionKey);
}
return availableOptions;
},
getSpecifiedOptions: function (keys) {
getSpecifiedOptions: function (keys, extendSettings) {
var options = [];
if (!keys) {
return options;
}
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var readonly = false;
var category = null;
if (angular.isObject(key)) {
var optionKey = key;
key = optionKey.key;
readonly = !!optionKey.readonly;
category = optionKey.category;
}
var option = aria2AllOptions[key];
@ -108,6 +131,10 @@
descriptionKey: 'options.' + key + '.description'
}, option);
if (category) {
option.category = category;
}
if (option.type === 'boolean') {
option.options = ['true', 'false'];
}
@ -116,6 +143,10 @@
option.readonly = true;
}
if (extendSettings && extendSettings.disableRequired) {
option.required = false;
}
if (option.options) {
var availableOptions = [];
@ -154,13 +185,14 @@
return aria2RpcService.getVersion({
silent: !!silent,
callback: callback
})
});
},
getGlobalStat: function (callback, silent) {
return aria2RpcService.getGlobalStat({
silent: !!silent,
callback: function (response) {
if (!callback) {
ariaNgLogService.warn('[aria2SettingService.getGlobalStat] callback is null');
return;
}
@ -173,13 +205,13 @@
return aria2RpcService.saveSession({
silent: !!silent,
callback: callback
})
});
},
shutdown: function (callback, silent) {
return aria2RpcService.shutdown({
silent: !!silent,
callback: callback
})
});
}
};
}]);

View file

@ -1,9 +1,10 @@
(function () {
'use strict';
angular.module('ariaNg').factory('aria2TaskService', ['$q', '$translate', 'bittorrentPeeridService', 'aria2Errors', 'aria2RpcService', 'ariaNgCommonService', function ($q, $translate, bittorrentPeeridService, aria2Errors, aria2RpcService, ariaNgCommonService) {
angular.module('ariaNg').factory('aria2TaskService', ['$q', '$translate', 'bittorrentPeeridService', 'aria2Errors', 'aria2RpcService', 'ariaNgCommonService', 'ariaNgLogService', function ($q, $translate, bittorrentPeeridService, aria2Errors, aria2RpcService, ariaNgCommonService, ariaNgLogService) {
var getFileName = function (file) {
if (!file) {
ariaNgLogService.warn('[aria2TaskService.getFileName] file is null');
return '';
}
@ -122,6 +123,7 @@
var processDownloadTask = function (task) {
if (!task) {
ariaNgLogService.warn('[aria2TaskService.processDownloadTask] task is null');
return task;
}
@ -157,6 +159,7 @@
for (var i = 0; i < task.files.length; i++) {
var file = task.files[i];
file.index = parseInt(file.index);
file.fileName = getFileName(file);
file.length = parseInt(file.length);
file.selected = (file.selected === true || file.selected === 'true');
@ -169,11 +172,30 @@
task.selectedFileCount = selectedFileCount;
}
if (task.files && task.files.length === 1 && task.files[0].uris && task.files[0].uris[0]) {
var isSingleUrlTask = true;
var firstUri = task.files[0].uris[0].uri;
for (var i = 0; i < task.files[0].uris.length; i++) {
var uri = task.files[0].uris[i].uri;
if (uri !== firstUri) {
isSingleUrlTask = false;
break;
}
}
if (isSingleUrlTask) {
task.singleUrl = firstUri;
}
}
return task;
};
var processBtPeers = function (peers, task, includeLocalPeer) {
if (!peers) {
ariaNgLogService.warn('[aria2TaskService.processBtPeers] peers is null');
return peers;
}
@ -237,7 +259,7 @@
} else {
callback(context);
}
}
};
};
var createLocalPeerFromTask = function (task) {
@ -272,6 +294,7 @@
silent: !!silent,
callback: function (response) {
if (!callback) {
ariaNgLogService.warn('[aria2TaskService.getTaskList] callback is null');
return;
}
@ -285,6 +308,7 @@
silent: !!silent,
callback: function (response) {
if (!callback) {
ariaNgLogService.warn('[aria2TaskService.getTaskStatus] callback is null');
return;
}
@ -333,6 +357,7 @@
silent: !!silent,
callback: function (response) {
if (!callback) {
ariaNgLogService.warn('[aria2TaskService.getBtTaskPeers] callback is null');
return;
}
@ -358,6 +383,7 @@
silent: !!silent,
callback: function (response) {
if (!callback) {
ariaNgLogService.warn('[aria2TaskService.getTaskStatusAndBtPeers] callback is null');
return;
}
@ -423,6 +449,100 @@
callback: callback
});
},
restartTask: function (gid, callback, silent) {
var deferred = $q.defer();
var methods = [
aria2RpcService.tellStatus({gid: gid}, true),
aria2RpcService.getOption({gid: gid}, true)
];
var task = null, options = null;
aria2RpcService.multicall({
methods: methods,
silent: !!silent,
callback: function (response) {
if (!callback) {
ariaNgLogService.warn('[aria2TaskService.restartTask] callback is null');
return;
}
if (!response.success) {
ariaNgLogService.warn('[aria2TaskService.restartTask] response is not success');
deferred.reject(response);
callback(response);
return;
}
if (response.data.length > 0) {
task = response.data[0][0];
}
if (response.data.length > 1) {
options = response.data[1][0];
}
if (!task || !options || !task.files || task.files.length != 1 || task.bittorrent) {
if (!task) {
ariaNgLogService.warn('[aria2TaskService.restartTask] task is null');
}
if (!options) {
ariaNgLogService.warn('[aria2TaskService.restartTask] options is null');
}
if (!task.files) {
ariaNgLogService.warn('[aria2TaskService.restartTask] task file is null');
}
if (task.files.length !== 1) {
ariaNgLogService.warn('[aria2TaskService.restartTask] task file length is not equal 1');
}
if (task.bittorrent) {
ariaNgLogService.warn('[aria2TaskService.restartTask] task is bittorrent');
}
deferred.reject(gid);
callback({
success: false
});
return;
}
var file = task.files[0];
var urls = [];
for (var i = 0; i < file.uris.length; i++) {
var uriObj = file.uris[i];
urls.push(uriObj.uri);
}
aria2RpcService.addUri({
task: {
urls: urls,
options: options
},
pauseOnAdded: false,
silent: !!silent,
callback: function (response) {
if (!response.success) {
ariaNgLogService.warn('[aria2TaskService.restartTask] addUri response is not success');
deferred.reject(response);
callback(response);
return;
}
deferred.resolve(response);
callback(response);
}
});
}
});
return deferred.promise;
},
removeTasks: function (tasks, callback, silent) {
var runningTaskGids = [];
var stoppedTaskGids = [];
@ -482,7 +602,7 @@
how: 'POS_SET',
silent: !!silent,
callback: callback
})
});
},
clearStoppedTasks: function (callback, silent) {
return aria2RpcService.purgeDownloadResult({
@ -492,6 +612,7 @@
},
onFirstSuccess: function (callback) {
if (!callback) {
ariaNgLogService.warn('[aria2TaskService.onFirstSuccess] callback is null');
return;
}
@ -499,8 +620,30 @@
callback: callback
});
},
onConnectSuccess: function (callback) {
if (!callback) {
ariaNgLogService.warn('[aria2TaskService.onConnectSuccess] callback is null');
return;
}
aria2RpcService.onConnectSuccess({
callback: callback
});
},
onConnectError: function (callback) {
if (!callback) {
ariaNgLogService.warn('[aria2TaskService.onConnectError] callback is null');
return;
}
aria2RpcService.onConnectError({
callback: callback
});
},
onTaskCompleted: function (callback) {
if (!callback) {
ariaNgLogService.warn('[aria2TaskService.onTaskCompleted] callback is null');
return;
}
@ -510,6 +653,7 @@
},
onBtTaskCompleted: function (callback) {
if (!callback) {
ariaNgLogService.warn('[aria2TaskService.onBtTaskCompleted] callback is null');
return;
}
@ -519,6 +663,7 @@
},
onTaskErrorOccur: function (callback) {
if (!callback) {
ariaNgLogService.warn('[aria2TaskService.onTaskErrorOccur] callback is null');
return;
}
@ -528,6 +673,7 @@
},
processDownloadTasks: function (tasks) {
if (!angular.isArray(tasks)) {
ariaNgLogService.warn('[aria2TaskService.processDownloadTasks] tasks is not array');
return;
}
@ -543,6 +689,7 @@
},
estimateHealthPercentFromPeers: function (task, peers) {
if (!task || task.numPieces < 1 || peers.length < 1) {
ariaNgLogService.warn('[aria2TaskService.estimateHealthPercentFromPeers] tasks is null or numPieces < 1 or peers < 1');
return task.completePercent;
}

View file

@ -2,7 +2,7 @@
'use strict';
angular.module('ariaNg').factory('aria2WebSocketRpcService', ['$q', '$websocket', 'ariaNgSettingService', 'ariaNgLogService', function ($q, $websocket, ariaNgSettingService, ariaNgLogService) {
var rpcUrl = ariaNgSettingService.getJsonRpcUrl();
var rpcUrl = ariaNgSettingService.getCurrentRpcUrl();
var socketClient = null;
var sendIdStates = {};
@ -29,13 +29,13 @@
});
if (content.result && context.successCallback) {
ariaNgLogService.debug('WebSocket Response Success', content);
ariaNgLogService.debug('[aria2WebSocketRpcService.request] response success', content);
context.successCallback(context.id, content.result);
}
if (content.error && context.errorCallback) {
ariaNgLogService.debug('WebSocket Response Error', content);
ariaNgLogService.debug('[aria2WebSocketRpcService.request] response error', content);
context.errorCallback(context.id, content.error);
}
@ -65,28 +65,39 @@
var getSocketClient = function () {
if (socketClient === null) {
socketClient = $websocket(rpcUrl);
try {
socketClient = $websocket(rpcUrl);
socketClient.onMessage(function (message) {
if (!message || !message.data) {
return;
socketClient.onMessage(function (message) {
if (!message || !message.data) {
return;
}
var content = angular.fromJson(message.data);
if (!content) {
return;
}
if (content.id) {
processMethodCallback(content);
} else if (content.method) {
processEventCallback(content);
}
});
} catch (ex) {
return {
success: false,
error: 'Cannot initialize WebSocket!',
exception: ex
}
var content = angular.fromJson(message.data);
if (!content) {
return;
}
if (content.id) {
processMethodCallback(content);
} else if (content.method) {
processEventCallback(content);
}
});
}
}
return socketClient;
return {
success: true,
instance: socketClient
};
};
return {
@ -99,7 +110,7 @@
var uniqueId = context.uniqueId;
var requestBody = angular.toJson(context.requestBody);
ariaNgLogService.debug('WebSocket Request Start', context);
ariaNgLogService.debug('[aria2WebSocketRpcService.request] request start', context);
var deferred = $q.defer();
@ -108,7 +119,17 @@
deferred: deferred
};
client.send(requestBody);
if (client.instance) {
client.instance.send(requestBody);
} else {
deferred.reject({
success: false,
context: context
});
ariaNgLogService.debug('[aria2WebSocketRpcService.request] client error', client);
context.errorCallback(context.id, { message: client.error });
}
return deferred.promise;
},

View file

@ -25,10 +25,10 @@
showOperationSucceeded: function (text) {
this.showDialog('Operation Succeeded', text, 'success');
},
confirm: function (title, text, type, callback, notClose) {
confirm: function (title, text, type, callback, notClose, extendSettings) {
var options = {
title: $translate.instant(title),
text: $translate.instant(text),
text: $translate.instant(text, (angular.isObject(extendSettings) ? extendSettings.textParams : null)),
type: type,
showCancelButton: true,
showLoaderOnConfirm: !!notClose,
@ -58,6 +58,26 @@
return filePath.substring(filePath.lastIndexOf('.'));
},
parseUrlsFromOriginInput: function (s) {
if (!s) {
return [];
}
var lines = s.split('\n');
var result = [];
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line.match(/^(http|https|ftp|sftp):\/\/.+$/)) {
result.push(line);
} else if (line.match(/^magnet:\?.+$/)) {
result.push(line);
}
}
return result;
},
decodePercentEncodedString: function (s) {
if (!s) {
return s;
@ -173,7 +193,7 @@
}
},
getValue: function () {
return this.type + ":" + this.order;
return this.type + ':' + this.order;
}
};
@ -226,7 +246,7 @@
name: name,
value: value,
optionValue: time
})
});
}
return options;

View file

@ -1,7 +1,7 @@
(function () {
'use strict';
angular.module('ariaNg').factory('ariaNgFileService', ['$window', 'ariaNgCommonService', function ($window, ariaNgCommonService) {
angular.module('ariaNg').factory('ariaNgFileService', ['$window', function ($window) {
var isSupportFileReader = !!$window.FileReader;
var getAllowedExtensions = function (fileFilter) {
@ -46,9 +46,12 @@
};
return {
openFileContent: function (fileFilter, callback) {
openFileContent: function (fileFilter, successCallback, errorCallback) {
if (!isSupportFileReader) {
ariaNgCommonService.showError('Your browser does not support loading file!');
if (errorCallback) {
errorCallback('Your browser does not support loading file!');
}
return;
}
@ -63,7 +66,10 @@
var fileName = file.name;
if (!checkFileExtension(fileName, allowedExtensions)) {
ariaNgCommonService.showError('The selected file type is invalid!');
if (errorCallback) {
errorCallback('The selected file type is invalid!');
}
return;
}
@ -75,18 +81,20 @@
base64Content: this.result.replace(/.*?base64,/, '')
};
if (callback) {
callback(result);
if (successCallback) {
successCallback(result);
}
};
reader.onerror = function () {
ariaNgCommonService.showError('Failed to load file!');
if (errorCallback) {
errorCallback('Failed to load file!');
}
};
reader.readAsDataURL(file);
}).trigger('click');
}
}
};
}]);
}());

View file

@ -3,18 +3,18 @@
angular.module('ariaNg').factory('ariaNgLanguageLoader', ['$http', '$q', 'localStorageService', 'ariaNgConstants', 'ariaNgLanguages', function ($http, $q, localStorageService, ariaNgConstants, ariaNgLanguages) {
var getKeyValuePair = function (line) {
var equalSignPos = line.indexOf('=');
if (equalSignPos > 0) {
return {
key: line.substring(0, equalSignPos),
value: line.substring(equalSignPos + 1, line.length)
for (var i = 0; i < line.length; i++) {
if (i > 0 && line.charAt(i - 1) !== '\\' && line.charAt(i) === '=') {
return {
key: line.substring(0, i).replace('\\=', '='),
value: line.substring(i + 1, line.length).replace('\\=', '=')
};
}
} else {
return {
value: line
};
}
return {
value: line
};
};
var getCategory = function (langObj, category) {
@ -64,6 +64,8 @@
continue;
}
line = line.replace('\r', '');
if (/^\[.+\]$/.test(line)) {
currentCatagory = getCategory(langObj, line);
continue;
@ -99,11 +101,11 @@
$http({
url: languagePath,
method: 'GET'
}).success(function (data) {
var languageObject = getLanguageObject(data);
}).then(function onSuccess(response) {
var languageObject = getLanguageObject(response.data);
localStorageService.set(languageKey, languageObject);
return deferred.resolve(languageObject);
}).error(function (data) {
}).catch(function onError(response) {
return deferred.reject(options.key);
});

View file

@ -5,17 +5,33 @@
return {
debug: function (msg, obj) {
if (ariaNgSettingService.isEnableDebugMode()) {
$log.debug('[AriaNg Debug]' + msg, obj);
if (obj) {
$log.debug('[AriaNg Debug]' + msg, obj);
} else {
$log.debug('[AriaNg Debug]' + msg);
}
}
},
info: function (msg, obj) {
$log.info('[AriaNg Info]' + msg, obj);
if (obj) {
$log.info('[AriaNg Info]' + msg, obj);
} else {
$log.info('[AriaNg Info]' + msg);
}
},
warn: function (msg, obj) {
$log.warn('[AriaNg Warn]' + msg, obj);
if (obj) {
$log.warn('[AriaNg Warn]' + msg, obj);
} else {
$log.warn('[AriaNg Warn]' + msg);
}
},
error: function (msg, obj) {
$log.error('[AriaNg Error]' + msg, obj);
if (obj) {
$log.error('[AriaNg Error]' + msg, obj);
} else {
$log.error('[AriaNg Error]' + msg);
}
}
};
}]);

View file

@ -2,6 +2,7 @@
'use strict';
angular.module('ariaNg').factory('ariaNgMonitorService', ['$filter', '$translate', 'moment', 'ariaNgConstants', function ($filter, $translate, moment, ariaNgConstants) {
var currentGlobalStat = {};
var storagesInMemory = {};
var globalStorageKey = 'global';
@ -32,8 +33,8 @@
}
var time = moment(params[0].name, 'X').format('HH:mm:ss');
var uploadSpeed = $filter('readableVolumn')(params[0].value) + '/s';
var downloadSpeed = $filter('readableVolumn')(params[1].value) + '/s';
var uploadSpeed = $filter('readableVolume')(params[0].value) + '/s';
var downloadSpeed = $filter('readableVolume')(params[1].value) + '/s';
return '<div>' + time + '</div>'
+ '<div><i class="icon-download fa fa-arrow-down"></i> ' + downloadSpeed +'</div>'
@ -52,7 +53,7 @@
type: 'value',
axisLabel: {
formatter: function (value) {
return $filter('readableVolumn')(value, 0);
return $filter('readableVolume')(value, 0);
}
}
},
@ -149,11 +150,15 @@
return this.getStatsData(key);
},
recordGlobalStat: function (stat) {
return this.recordStat(globalStorageKey, stat);
this.recordStat(globalStorageKey, stat);
currentGlobalStat = stat;
},
getGlobalStatsData: function () {
return this.getStatsData(globalStorageKey);
},
getCurrentGlobalStat: function () {
return currentGlobalStat;
}
}
};
}]);
}());

View file

@ -68,7 +68,7 @@
options.type = 'primary';
}
Notification[options.type](options);
return Notification[options.type](options);
},
notifyTaskComplete: function (task) {
this.notifyViaBrowser('Download Completed', (task && task.taskName ? task.taskName : ''));
@ -82,6 +82,6 @@
clearNotificationInPage: function () {
Notification.clearAll();
}
}
};
}]);
}());

View file

@ -1,7 +1,7 @@
(function () {
'use strict';
angular.module('ariaNg').factory('ariaNgSettingService', ['$window', '$location', '$filter', '$translate', 'base64', 'amMoment', 'localStorageService', 'ariaNgConstants', 'ariaNgDefaultOptions', 'ariaNgLanguages', function ($window, $location, $filter, $translate, base64, amMoment, localStorageService, ariaNgConstants, ariaNgDefaultOptions, ariaNgLanguages) {
angular.module('ariaNg').factory('ariaNgSettingService', ['$window', '$location', '$filter', '$translate', 'base64', 'amMoment', 'localStorageService', 'ariaNgConstants', 'ariaNgDefaultOptions', 'ariaNgLanguages', 'ariaNgCommonService', function ($window, $location, $filter, $translate, base64, amMoment, localStorageService, ariaNgConstants, ariaNgDefaultOptions, ariaNgLanguages, ariaNgCommonService) {
var onFirstVisitCallbacks = [];
var sessionSettings = {
debugMode: false
@ -18,6 +18,35 @@
}
};
var isInsecureProtocolDisabled = function () {
var protocol = $location.protocol();
return protocol === 'https';
};
var getLanguageNameFromAlias = function (alias) {
for (var langName in ariaNgLanguages) {
if (!ariaNgLanguages.hasOwnProperty(langName)) {
continue;
}
var language = ariaNgLanguages[langName];
var aliases = language.aliases;
if (!angular.isArray(aliases) || aliases.length < 1) {
continue;
}
for (var i = 0; i < aliases.length; i++) {
if (aliases[i] === alias) {
return langName;
}
}
}
return null;
};
var getDefaultLanguage = function () {
var browserLang = $window.navigator.browserLanguage ? $window.navigator.browserLanguage : $window.navigator.language;
@ -25,7 +54,15 @@
return ariaNgDefaultOptions.language;
}
browserLang = browserLang.replace(/\-/g, "_");
browserLang = browserLang.replace(/\-/g, '_');
if (!ariaNgLanguages[browserLang]) {
var languageName = getLanguageNameFromAlias(browserLang);
if (languageName) {
browserLang = languageName;
}
}
if (!ariaNgLanguages[browserLang]) {
return ariaNgDefaultOptions.language;
@ -34,8 +71,18 @@
return browserLang;
};
var getLanguageNameFromAliasOrDefaultLanguage = function (lang) {
var languageName = getLanguageNameFromAlias(lang);
if (languageName) {
return languageName;
}
return getDefaultLanguage();
};
var getDefaultRpcHost = function () {
var currentHost = $location.$$host;
var currentHost = $location.host();
if (currentHost) {
return currentHost;
@ -51,10 +98,28 @@
var getOptions = function () {
var options = localStorageService.get(ariaNgConstants.optionStorageKey);
if (options && !ariaNgLanguages[options.language]) {
options.language = getLanguageNameFromAliasOrDefaultLanguage(options.language);
}
if (!options) {
options = angular.extend({}, ariaNgDefaultOptions);
options.language = getDefaultLanguage();
if (!options.rpcHost) {
initRpcSettingWithDefaultHostAndProtocol(options);
}
if (angular.isArray(options.extendRpcServers)) {
for (var i = 0; i < options.extendRpcServers.length; i++) {
var rpcSetting = options.extendRpcServers[i];
if (!rpcSetting.rpcHost) {
initRpcSettingWithDefaultHostAndProtocol(rpcSetting);
}
}
}
setOptions(options);
fireFirstVisitEvent();
}
@ -62,10 +127,14 @@
return options;
};
var clearAll = function () {
return localStorageService.clearAll();
};
var getOption = function (key) {
var options = getOptions();
if (angular.isUndefined(options[key])) {
if (angular.isUndefined(options[key]) && angular.isDefined(ariaNgDefaultOptions[key])) {
options[key] = ariaNgDefaultOptions[key];
setOptions(options);
}
@ -80,23 +149,80 @@
setOptions(options);
};
var initRpcSettingWithDefaultHostAndProtocol = function (setting) {
setting.rpcHost = getDefaultRpcHost();
if (isInsecureProtocolDisabled()) {
setting.protocol = ariaNgConstants.defaultSecureProtocol;
}
};
var cloneRpcSetting = function (setting) {
return {
rpcAlias: setting.rpcAlias,
rpcHost: setting.rpcHost,
rpcPort: setting.rpcPort,
rpcInterface: setting.rpcInterface,
protocol: setting.protocol,
httpMethod: setting.httpMethod,
secret: setting.secret
};
};
var createNewRpcSetting = function () {
var setting = cloneRpcSetting(ariaNgDefaultOptions);
setting.rpcId = ariaNgCommonService.generateUniqueId();
initRpcSettingWithDefaultHostAndProtocol(setting);
return setting;
};
return {
getAllOptions: function () {
var options = angular.extend({}, ariaNgDefaultOptions, getOptions());
if (!options.rpcHost) {
options.rpcHost = getDefaultRpcHost();
}
if (options.secret) {
options.secret = base64.decode(options.secret);
}
if (angular.isArray(options.extendRpcServers)) {
for (var i = 0; i < options.extendRpcServers.length; i++) {
var rpcSetting = options.extendRpcServers[i];
if (rpcSetting.secret) {
rpcSetting.secret = base64.decode(rpcSetting.secret);
}
}
}
return options;
},
getAllRpcSettings: function () {
var result = [];
var options = this.getAllOptions();
var defaultRpcSetting = cloneRpcSetting(options);
defaultRpcSetting.isDefault = true;
result.push(defaultRpcSetting);
if (angular.isArray(options.extendRpcServers)) {
for (var i = 0; i < options.extendRpcServers.length; i++) {
var rpcSetting = cloneRpcSetting(options.extendRpcServers[i]);
rpcSetting.rpcId = options.extendRpcServers[i].rpcId;
rpcSetting.isDefault = false;
result.push(rpcSetting);
}
}
return result;
},
getAllSessionOptions: function () {
return angular.copy(sessionSettings);
},
isInsecureProtocolDisabled: function () {
return isInsecureProtocolDisabled();
},
applyLanguage: function (lang) {
if (!ariaNgLanguages[lang]) {
return false;
@ -124,26 +250,6 @@
getTitle: function () {
return getOption('title');
},
getFinalTitle: function (context) {
var title = this.getTitle();
context = angular.extend({
downloadingCount: 0,
waitingCount: 0,
stoppedCount: 0,
downloadSpeed: 0,
uploadSpeed: 0
}, context);
title = title.replace(/\$\{downloading\}/g, $translate.instant('Downloading') + ': ' + context.downloadingCount);
title = title.replace(/\$\{waiting\}/g, $translate.instant('Waiting') + ': ' + context.waitingCount);
title = title.replace(/\$\{stopped\}/g, $translate.instant('Downloaded / Stopped') + ': ' + context.stoppedCount);
title = title.replace(/\$\{downspeed\}/g, $translate.instant('Download') + ': ' + $filter('readableVolumn')(context.downloadSpeed) + '/s');
title = title.replace(/\$\{upspeed\}/g, $translate.instant('Upload') + ': ' + $filter('readableVolumn')(context.uploadSpeed) + '/s');
title = title.replace(/\$\{title\}/g, ariaNgConstants.title);
return title;
},
setTitle: function (value) {
setOption('title', value);
},
@ -153,52 +259,194 @@
setTitleRefreshInterval: function (value) {
setOption('titleRefreshInterval', Math.max(parseInt(value), 0));
},
getAfterCreatingNewTask: function () {
return getOption('afterCreatingNewTask');
},
setAfterCreatingNewTask: function (value) {
setOption('afterCreatingNewTask', value);
},
getBrowserNotification: function () {
return getOption('browserNotification');
},
setBrowserNotification: function (value) {
setOption('browserNotification', value);
},
getJsonRpcUrl: function () {
var protocol = getOption('protocol');
var rpcHost = getOption('rpcHost');
var rpcPort = getOption('rpcPort');
getCurrentRpcUrl: function () {
var options = getOptions();
var protocol = options.protocol;
var rpcHost = options.rpcHost;
var rpcPort = options.rpcPort;
var rpcInterface = options.rpcInterface;
if (!rpcHost) {
rpcHost = getDefaultRpcHost();
}
return protocol + '://' + rpcHost + ':' + rpcPort + '/jsonrpc';
return protocol + '://' + rpcHost + ':' + rpcPort + '/' + rpcInterface;
},
setRpcHost: function (value) {
setOption('rpcHost', value);
getCurrentRpcHttpMethod: function () {
return getOption('httpMethod');
},
setRpcPort: function (value) {
setOption('rpcPort', Math.max(parseInt(value), 0));
},
getProtocol: function () {
return getOption('protocol');
},
setProtocol: function (value) {
setOption('protocol', value);
},
isUseWebSocket: function (protocol) {
isCurrentRpcUseWebSocket: function (protocol) {
if (!protocol) {
protocol = this.getProtocol();
var options = getOptions();
protocol = options.protocol;
}
return protocol === 'ws' || protocol === 'wss';
},
getSecret: function () {
getCurrentRpcSecret: function () {
var value = getOption('secret');
return (value ? base64.decode(value) : value);
},
setSecret: function (value) {
if (value) {
value = base64.encode(value);
addNewRpcSetting: function () {
var options = getOptions();
if (!angular.isArray(options.extendRpcServers)) {
options.extendRpcServers = [];
}
setOption('secret', value);
var newRpcSetting = createNewRpcSetting();
options.extendRpcServers.push(newRpcSetting);
setOptions(options);
return newRpcSetting;
},
updateRpcSetting: function (setting, field) {
if (!setting) {
return setting;
}
var updatedSetting = cloneRpcSetting(setting);
if (angular.isUndefined(updatedSetting[field])) {
return setting;
}
var value = updatedSetting[field];
if (field === 'rpcPort') {
value = Math.max(parseInt(value), 0);
} else if (field === 'secret') {
if (value) {
value = base64.encode(value);
}
}
if (setting.isDefault) {
setOption(field, value);
return setting;
} else {
var options = getOptions();
if (!angular.isArray(options.extendRpcServers)) {
return setting;
}
for (var i = 0; i < options.extendRpcServers.length; i++) {
if (options.extendRpcServers[i].rpcId === setting.rpcId) {
options.extendRpcServers[i][field] = value;
break;
}
}
setOptions(options);
return setting;
}
},
removeRpcSetting: function (setting) {
var options = getOptions();
if (!angular.isArray(options.extendRpcServers)) {
return setting;
}
for (var i = 0; i < options.extendRpcServers.length; i++) {
if (options.extendRpcServers[i].rpcId === setting.rpcId) {
options.extendRpcServers.splice(i, 1);
break;
}
}
setOptions(options);
return setting;
},
setDefaultRpcSetting: function (setting, params) {
params = angular.extend({
keepCurrent: true,
forceSet: false
}, params);
var options = getOptions();
var currentSetting = cloneRpcSetting(options);
currentSetting.rpcId = ariaNgCommonService.generateUniqueId();
if (!angular.isArray(options.extendRpcServers)) {
options.extendRpcServers = [];
}
var newDefaultSetting = null;
for (var i = 0; i < options.extendRpcServers.length; i++) {
if (options.extendRpcServers[i].rpcId === setting.rpcId) {
newDefaultSetting = cloneRpcSetting(options.extendRpcServers[i]);
options.extendRpcServers.splice(i, 1);
break;
}
}
if (params.forceSet) {
newDefaultSetting = cloneRpcSetting(setting);
if (newDefaultSetting.secret) {
newDefaultSetting.secret = base64.encode(newDefaultSetting.secret);
}
}
if (newDefaultSetting) {
if (params.keepCurrent) {
options.extendRpcServers.splice(0, 0, currentSetting);
}
options = angular.extend(options, newDefaultSetting);
}
setOptions(options);
return setting;
},
isRpcSettingEqualsDefault: function (setting) {
if (!setting) {
return false;
}
var options = this.getAllOptions();
if (options.rpcHost !== setting.rpcHost) {
return false;
}
if (options.rpcPort !== setting.rpcPort) {
return false;
}
if (options.rpcInterface !== setting.rpcInterface) {
return false;
}
if (options.protocol !== setting.protocol) {
return false;
}
if (options.httpMethod !== setting.httpMethod) {
return false;
}
if (options.secret !== setting.secret) {
return false;
}
return true;
},
getGlobalStatRefreshInterval: function () {
return getOption('globalStatRefreshInterval');
@ -248,6 +496,9 @@
setPeerListDisplayOrder: function (value) {
setOption('peerListDisplayOrder', value);
},
resetSettings: function () {
clearAll();
},
onFirstAccess: function (callback) {
if (!callback) {
return;

View file

@ -0,0 +1,143 @@
(function () {
'use strict';
angular.module('ariaNg').factory('ariaNgTitleService', ['$filter', '$translate', 'ariaNgConstants', 'ariaNgSettingService', function ($filter, $translate, ariaNgConstants, ariaNgSettingService) {
var parseSettings = function (placeholder) {
if (!placeholder) {
return {};
}
var innerText = placeholder.substring(2, placeholder.length - 1); // remove ${ and }
var items = innerText.split(':');
var settings = {
oldValue: placeholder
};
for (var i = 1; i < items.length; i++) {
var pairs = items[i].split('=');
if (pairs.length === 1) {
settings[pairs[0]] = true;
} else if (pairs.length === 2) {
settings[pairs[0]] = pairs[1];
}
}
return settings;
};
var replacePlaceholder = function (title, context) {
var value = context.value;
if (context.type === 'volume') {
value = $filter('readableVolume')(value, context.scale);
}
if (context.prefix && !context.noprefix) {
value = context.prefix + value;
}
if (context.suffix && !context.nosuffix) {
value = value + context.suffix;
}
return title.replace(context.oldValue, value);
};
var replacePlaceholders = function (title, condition, context) {
var regex = new RegExp('\\$\\{' + condition + '(:[a-zA-Z0-9]+(=[a-zA-Z0-9]+)?)*\\}', 'g');
var results = title.match(regex);
if (results && results.length > 0) {
for (var i = 0; i < results.length; i++) {
var innerContext = parseSettings(results[i]);
angular.extend(innerContext, context);
title = replacePlaceholder(title, innerContext);
}
}
return title;
};
var replaceDownloadingCount = function (title, value) {
return replacePlaceholders(title, 'downloading', {
prefix: $translate.instant('Downloading') + ': ',
value: value
});
};
var replaceWaitingCount = function (title, value) {
return replacePlaceholders(title, 'waiting', {
prefix: $translate.instant('Waiting') + ': ',
value: value
});
};
var replaceStoppedCount = function (title, value) {
return replacePlaceholders(title, 'stopped', {
prefix: $translate.instant('Finished / Stopped'),
value: value
});
};
var replaceDownloadSpeed = function (title, value) {
return replacePlaceholders(title, 'downspeed', {
prefix: $translate.instant('Download') + ': ',
value: value,
type: 'volume',
suffix: '/s'
});
};
var replaceUploadSpeed = function (title, value) {
return replacePlaceholders(title, 'upspeed', {
prefix: $translate.instant('Upload') + ': ',
value: value,
type: 'volume',
suffix: '/s'
});
};
var replaceAgiaNgTitle = function (title) {
return replacePlaceholders(title, 'title', {
value: ariaNgConstants.title
});
};
return {
getFinalTitle: function (context) {
var title = ariaNgSettingService.getTitle();
context = angular.extend({
downloadingCount: 0,
waitingCount: 0,
stoppedCount: 0,
downloadSpeed: 0,
uploadSpeed: 0
}, context);
title = replaceDownloadingCount(title, context.downloadingCount);
title = replaceWaitingCount(title, context.waitingCount);
title = replaceStoppedCount(title, context.stoppedCount);
title = replaceDownloadSpeed(title, context.downloadSpeed);
title = replaceUploadSpeed(title, context.uploadSpeed);
title = replaceAgiaNgTitle(title);
return title;
},
getFinalTitleByGlobalStat: function (globalStat) {
var context = {
downloadingCount: (globalStat ? globalStat.numActive : 0),
waitingCount: (globalStat ? globalStat.numWaiting : 0),
stoppedCount: (globalStat ? globalStat.numStopped : 0),
downloadSpeed: (globalStat ? globalStat.downloadSpeed : 0),
uploadSpeed: (globalStat ? globalStat.uploadSpeed : 0)
};
return this.getFinalTitle(context);
}
};
}]);
}());

View file

@ -22,3 +22,7 @@
display: block;
}
}
.settings-table .new-task-filter-title {
padding-top: 6px;
}

View file

@ -4,6 +4,16 @@
margin-right: 15px;
}
.settings-table .settings-table-title {
font-size: 12px;
padding-top: 4px;
padding-bottom: 4px;
}
.settings-table .settings-table-title a {
color: #000;
}
.settings-table > div.row {
padding-top: 8px;
padding-bottom: 8px;
@ -58,6 +68,24 @@
z-index: inherit;
}
.settings-table .setting-value .input-group .form-control-rpcport {
min-width: 70px;
}
.settings-table .setting-value .input-group .form-control-rpcinterface {
min-width: 100px;
}
@media (max-width: 991px) {
.settings-table .setting-value .input-group .form-control-rpcport {
min-width: 60px;
}
.settings-table .setting-value .input-group .form-control-rpcinterface {
min-width: 60px;
}
}
.settings-table .tip {
font-size: 12px;
padding: 4px 8px 4px 8px;

View file

@ -4,7 +4,14 @@
*/
/* basic */
html {
margin: 0;
padding: 0;
}
body {
margin: 0;
padding: 0;
-ms-user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
@ -15,16 +22,76 @@ td {
vertical-align: middle !important;
}
.main-header .logo {
overflow: visible;
}
.main-header .logo .dropdown-menu {
z-index: 2000;
}
.main-header .navbar .nav > li {
display: inline-block;
}
.main-header .navbar .nav > li > a {
padding-left: 10px;
padding-right: 10px;
}
.main-header .navbar .nav > li.disabled > a {
pointer-events: none !important;
}
.main-header .navbar .navbar-nav {
margin-left: 5px;
}
.main-header .navbar .navbar-searchbar {
padding-top: 8px;
padding-right: 20px;
float: right;
}
.main-header .logo .logo-mini {
font-size: 14px !important;
}
.main-header .logo .logo-lg {
cursor: pointer;
}
.content-wrapper, .right-side {
background-color: #fff;
}
.content-wrapper > .content-body {
overflow-y: scroll;
}
.main-footer > .navbar {
margin-bottom: 0;
min-height: inherit;
}
.main-footer > .navbar > .navbar-toolbar > .nav {
float: left;
margin: 0;
}
.main-footer > .navbar > .navbar-toolbar > .nav > li {
display: inline-block;
float: left;
}
.main-footer > .navbar > .navbar-toolbar > .nav > li > a {
padding: 0 10px 0 10px;
}
.main-footer > .navbar > .navbar-toolbar > .nav > li:first-child > a {
padding-left: 0;
}
.dropdown-menu.right-align {
left: inherit;
right: 0;
@ -38,6 +105,10 @@ td {
cursor: pointer !important;
}
.text-cursor {
cursor: text !important;
}
.allow-word-break {
word-wrap: break-word;
word-break: break-all;
@ -52,8 +123,14 @@ td {
@media (max-width: 767px) {
.navbar-nav .open .dropdown-menu {
position: absolute;
border: 1px solid #eee;
background-color: #fff;
}
.main-footer > .navbar > .navbar-toolbar > .nav > li > a {
padding-left: 8px;
padding-right: 8px;
}
}
/* toolbar */

View file

@ -3,6 +3,50 @@
padding: 0 4px 0 4px;
}
.nav-tabs-custom .nav-tabs > li > a {
display: inline-block;
}
.nav-tabs-custom .nav-tabs > li > a.nav-tab-close {
padding-left: 0;
margin-left: -12px;
}
.nav-tabs-custom .nav-tabs > li.nav-tab-title-rpcname > a {
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: bottom;
}
@media (max-width: 991px) {
.nav-tabs-custom .nav-tabs > li.nav-tab-title-rpcname > a {
max-width: 150px;
}
}
@media (max-width: 767px) {
.nav-tabs-custom .nav-tabs > li.nav-tab-title-rpcname > a {
max-width: 120px;
}
}
.input-group.input-group-multiple > .input-group-addon {
border-left: 0;
border-right: 0;
}
.input-group.input-group-multiple > .input-group-addon:first-child,
.input-group.input-group-multiple > .input-group-addon-container:first-child {
border-left: 1px solid #d2d6de;
}
.input-group .input-group-addon-container {
width: 1%;
display: table-cell;
}
/* font-awesome extend */
.fa-half {
font-size: 0.5em;

View file

@ -12,19 +12,9 @@
border-bottom: 1px solid #ddd;
}
.skin-aria-ng .main-header .navbar .navbar-nav {
margin-left: 5px;
}
.skin-aria-ng .main-header .navbar .nav > li {
display: inline-block;
}
.skin-aria-ng .main-header .navbar .nav > li > a {
color: #707070;
font-size: 16px;
padding-left: 10px;
padding-right: 10px;
}
.skin-aria-ng .main-header .navbar .nav > li > a:hover,
@ -39,7 +29,6 @@
.skin-aria-ng .main-header .navbar .nav > li.disabled > a {
color: #ccc !important;
pointer-events: none !important;
}
.skin-aria-ng .main-header .navbar .nav > li.divider {
@ -51,14 +40,6 @@
top: 15px;
}
.skin-aria-ng .main-header .navbar .sidebar-toggle, .skin-aria-ng .main-footer .sidebar-toggle {
color: #707070;
}
.skin-aria-ng .main-header .navbar .sidebar-toggle:hover, .skin-aria-ng .main-footer .sidebar-toggle:hover {
color: #0080ff;
}
.skin-aria-ng .main-header .navbar .navbar-searchbar .form-control-icon {
color: #999999;
}
@ -85,10 +66,30 @@
border-bottom: 1px solid #59636b;
}
.skin-aria-ng .main-header .logo:hover {
.skin-aria-ng .main-header .logo .logo-lg:hover {
color: #cccccc;
}
.skin-aria-ng .main-header .logo .logo-lg .logo-lg-title {
margin-right: 6px;
}
.skin-aria-ng .main-header .logo .dropdown-menu {
margin-top: -1px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.skin-aria-ng .main-header .logo .dropdown-menu > li.active > a {
color: #777;
background-color: #fff;
}
.skin-aria-ng .main-header .logo .dropdown-menu > li.active:hover > a {
color: #333;
background-color: #e1e3e9;
}
.skin-aria-ng .content-header {
background: transparent;
}
@ -159,7 +160,8 @@
}
.skin-aria-ng .sidebar-menu i.fa {
font-size: 16px;
font-size: 18px;
margin-right: 2px;
}
.skin-aria-ng .sidebar a {
@ -194,16 +196,30 @@
font-size: 12px;
}
.skin-aria-ng .main-footer .sidebar-toggle {
float: left;
background-color: transparent;
background-image: none;
padding: 0 15px 0 0;
font-family: fontAwesome;
.skin-aria-ng .main-footer > .navbar {
border: 0;
}
.skin-aria-ng .main-footer .sidebar-toggle:before {
content: "\f0c9";
.skin-aria-ng .main-footer > .navbar > .navbar-toolbar > .nav > li.divider {
padding-top: 16px;
margin-left: 4px;
margin-right: 4px;
border-right: 1px solid #ccc;
position: relative;
top: 0;
}
.skin-aria-ng .main-footer > .navbar > .navbar-toolbar > .nav > li > a {
color: #707070;
line-height: 17px;
}
.skin-aria-ng .main-footer > .navbar > .navbar-toolbar > .nav > li > a:hover,
.skin-aria-ng .main-footer > .navbar > .navbar-toolbar > .nav > li > a:active,
.skin-aria-ng .main-footer > .navbar > .navbar-toolbar > .nav > li > a:focus,
.skin-aria-ng .main-footer > .navbar > .navbar-toolbar > .nav > li.open > a {
color: #0080ff;
background: none;
}
.skin-aria-ng .global-status {
@ -316,6 +332,16 @@
color: #208fe5;
}
.skin-aria-ng .nav-tabs-custom > .nav-tabs > li > a.nav-tab-close:hover {
color: #dd4b39 !important;
margin-left: -14px;
}
.skin-aria-ng .nav-tabs-custom > .nav-tabs > li > a.nav-tab-close:hover > .fa-times:before {
content:"\f057"; /* The same as .fa-times-circle:before */
font-size: 1.1em;
}
.skin-aria-ng .nav-tabs-custom > .nav-tabs > li > div.btn-group {
padding-top: 5px;
padding-bottom: 5px;

View file

@ -43,10 +43,11 @@
</label>
</div>
<div class="task-files">
<span ng-bind="task.totalLength | readableVolumn"></span>
<a ng-href="#/task/detail/{{task.gid}}">
<span ng-bind="task.totalLength | readableVolume"></span>
<a ng-href="#!/task/detail/{{task.gid}}" title="{{'Click to view task detail' | translate}}">
<span ng-if="task.files" ng-bind="('format.settings.file-count' | translate: {count: task.selectedFileCount})"></span>
</a><i class="icon-error fa fa-times" ng-if="task && task.status == 'error' && task.errorDescription" title="{{task.errorDescription | translate}}"></i><i class="icon-seeder fa fa-arrow-up" ng-if="task && task.status == 'active' && task.seeder" title="{{'Seeding' | translate}}"></i>
<a class="pointer-cursor" ng-if="task && task.status == 'error' && task.errorDescription && !task.bittorrent" ng-click="restart(task)" title="{{'Restart' | translate}}" translate>Restart</a>
</div>
</div>
<div class="col-md-2 col-sm-3 col-xs-12">
@ -63,12 +64,12 @@
<span class="task-last-time"
ng-bind="task.status === 'waiting' ? '--:--:--' : (task.status === 'paused' ? '' : (task.status === 'active' ? ((0 <= task.remainTime && task.remainTime < 86400) ? (task.remainTime | dateDuration: 'second': 'HH:mm:ss') : ('More Than One Day' | translate)) : ''))"></span>
<span class="task-download-speed visible-xs-inline pull-right"
ng-bind="task.status === 'waiting' ? ('Waiting' | translate) : (task.status === 'paused' ? ('Paused' | translate) : (task.status === 'active' ? (!task.seeder || task.downloadSpeed > 0 ? (task.downloadSpeed | readableVolumn) + '/s' : '-') : ''))"></span>
ng-bind="task.status === 'waiting' ? ('Waiting' | translate) : (task.status === 'paused' ? ('Paused' | translate) : (task.status === 'active' ? (!task.seeder || task.downloadSpeed > 0 ? (task.downloadSpeed | readableVolume) + '/s' : '-') : ''))"></span>
<span class="task-seeders pull-right" ng-bind="task.status === 'active' ? ((task.numSeeders ? (task.numSeeders + '/') : '') + task.connections) : ''"></span>
</div>
</div>
<div class="col-md-2 col-sm-2 hidden-xs">
<span class="task-download-speed" ng-bind="task.status === 'waiting' ? ('Waiting' | translate) : (task.status === 'paused' ? ('Paused' | translate) : (task.status === 'active' ? (!task.seeder || task.downloadSpeed > 0 ? (task.downloadSpeed | readableVolumn) + '/s' : '-') : ''))"></span>
<span class="task-download-speed" ng-bind="task.status === 'waiting' ? ('Waiting' | translate) : (task.status === 'paused' ? ('Paused' | translate) : (task.status === 'active' ? (!task.seeder || task.downloadSpeed > 0 ? (task.downloadSpeed | readableVolume) + '/s' : '-') : ''))"></span>
</div>
</div>
</div>

View file

@ -3,7 +3,7 @@
<div class="nav-tabs-custom">
<ul class="nav nav-tabs">
<li ng-class="{'active': context.currentTab == 'links'}">
<a class="pointer-cursor" ng-click="changeTab('links')" translate>Links</a>
<a class="pointer-cursor" ng-click="changeTab('links')" ng-bind="(context.taskType === 'torrent' ? 'Torrent File' : (context.taskType === 'metalink' ? 'Metalink File' : 'Links') | translate)">Links</a>
</li>
<li ng-class="{'active': context.currentTab == 'options'}">
<a class="pointer-cursor" ng-click="changeTab('options')" translate>Options</a>
@ -21,12 +21,12 @@
</div>
<div class="btn-group">
<button type="submit" class="btn btn-sm"
ng-class="{'btn-default': newTaskForm.$invalid, 'btn-success': !newTaskForm.$invalid}"
ng-disabled="newTaskForm.$invalid" translate>Start Download
</button>
ng-class="{'btn-default': !context.uploadFile && newTaskForm.$invalid, 'btn-success': context.uploadFile || !newTaskForm.$invalid}"
ng-disabled="!context.uploadFile && newTaskForm.$invalid" translate>Start Download
</button>&nbsp;
<button type="button" class="btn btn-sm dropdown-toggle"
ng-class="{'btn-default': newTaskForm.$invalid, 'btn-success': !newTaskForm.$invalid}"
ng-disabled="newTaskForm.$invalid" data-toggle="dropdown">
ng-class="{'btn-default': !context.uploadFile && newTaskForm.$invalid, 'btn-success': context.uploadFile || !newTaskForm.$invalid}"
ng-disabled="!context.uploadFile && newTaskForm.$invalid" data-toggle="dropdown">
<span class="caret"></span>
</button>
<ul class="dropdown-menu right-align">
@ -38,15 +38,14 @@
<div class="tab-content no-padding">
<div class="tab-pane" ng-class="{'active': context.currentTab == 'links'}">
<div class="new-task-table">
<div class="new-task-table" ng-if="context.taskType === 'urls'">
<div class="row">
<div class="col-sm-12">
<p translate>Download Links:</p>
<p ng-bind="'format.task.new.download-links' | translate: {count: getValidUrlsCount()}">Download Links:</p>
<div class="form-group has-feedback no-margin" ng-class="{ 'has-error' : newTaskForm.urls.$invalid && newTaskForm.urls.$dirty, 'has-success' : newTaskForm.urls.$valid && newTaskForm.urls.$dirty }">
<textarea name="urls" class="form-control" rows="10" autofocus="autofocus" ng-auto-focus
ng-model="context.urls" ng-required="true" ng-keydown="urlTextboxKeyDown($event)"
ng-placeholder="'Support multiple URLs, one URL per line.' | translate"
ng-pattern="/^(\n?(((http|https|ftp|ssh):\/\/.+)|(magnet:\?.+)))*$/i"></textarea>
<textarea name="urls" class="form-control" rows="10" autofocus="autofocus" ng-auto-focus ng-valid-urls
ng-model="context.urls" ng-required="true" ng-keydown="urlTextboxKeyDown($event)"
ng-placeholder="'Support multiple URLs, one URL per line.' | translate"></textarea>
<div class="form-control-icon" ng-if="newTaskForm.urls.$dirty">
<i class="fa form-control-feedback" ng-class="{'fa-check':newTaskForm.urls.$valid, 'fa-times':newTaskForm.urls.$invalid}"></i>
</div>
@ -54,11 +53,38 @@
</div>
</div>
</div>
<div class="new-task-table" ng-if="context.taskType === 'torrent' || context.taskType === 'metalink'">
<div class="row">
<div class="col-sm-12">
<p translate>File Name:</p>
<input class="form-control" ng-value="context.uploadFile ? context.uploadFile.fileName : ''" readonly="readonly"/>
</div>
</div>
</div>
</div>
<div class="tab-pane" ng-class="{'active': context.currentTab == 'options'}">
<div class="settings-table striped hoverable">
<ng-setting ng-repeat="option in context.availableOptions" option="option" lazy-save-timeout="0"
default-value="context.globalOptions[option.key]"
<div class="settings-table-title new-task-filter-title">
<div class="row">
<div class="col-sm-12">
<span translate>Filters</span><span>:&nbsp;</span>
<div class="checkbox checkbox-inline checkbox-primary">
<input id="optionFilterGlobal" type="checkbox" ng-model="context.optionFilter['global']"/>
<label for="optionFilterGlobal" translate>Global</label>
</div>
<div class="checkbox checkbox-inline checkbox-primary">
<input id="optionFilterHttp" type="checkbox" ng-model="context.optionFilter['http']"/>
<label for="optionFilterHttp" translate>Http</label>
</div>
<div class="checkbox checkbox-inline checkbox-primary">
<input id="optionFilterBittorrent" type="checkbox" ng-model="context.optionFilter['bittorrent']"/>
<label for="optionFilterBittorrent" translate>BitTorrent</label>
</div>
</div>
</div>
</div>
<ng-setting ng-repeat="option in context.availableOptions" ng-if="context.optionFilter[option.category]"
option="option" lazy-save-timeout="0" default-value="context.globalOptions[option.key]"
on-change-value="setOption(key, value, optionStatus)"></ng-setting>
</div>
</div>

View file

@ -0,0 +1,23 @@
<div id="quickSettingModal" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" ng-bind="(setting ? (setting.title) : 'Quick Settings') | translate">Quick Setting</h4>
</div>
<div class="modal-body overlay-wrapper no-padding">
<div class="settings-table striped hoverable">
<ng-setting ng-repeat="option in context.availableOptions" option="option"
ng-model="context.globalOptions[option.key]" default-value="option.defaultValue"
on-change-value="setGlobalOption(key, value, optionStatus)"></ng-setting>
</div>
<div class="overlay" ng-if="context.isLoading">
<i class="fa fa-refresh fa-spin"></i>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal" translate>Cancel</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->

View file

@ -6,7 +6,7 @@
data-toggle="popover" data-trigger="hover" data-placement="auto top" data-container="body" data-content="{{option.descriptionKey | translate}}"></i>
<span class="description" ng-if="option.showCount && option.split && optionValue"
ng-bind="'format.settings.total-count' | translate: {count: getTotalCount()}"></span>
<i class="icon-primary fa fa-info-circle" ng-if="(option.since && option.since !== '')" ng-tooltip="{{'>= ' + option.since}}" ng-tooltip-container="body" ng-tooltip-placement="right"></i>
<i class="icon-primary fa fa-info-circle" ng-if="(option.since && option.since !== '')" ng-tooltip="{{('format.requires.aria2-version' | translate: {version: option.since})}}" ng-tooltip-container="body" ng-tooltip-placement="right"></i>
</div>
<div class="setting-value col-sm-8">
<div ng-class="{'input-group': !!option.suffix}">

View file

@ -1,15 +1,25 @@
<section class="content no-padding">
<div class="nav-tabs-custom">
<ul class="nav nav-tabs">
<li ng-class="{'active': context.currentTab == 'global'}">
<a class="pointer-cursor" ng-click="changeTab('global')" translate>Global</a>
<li ng-class="{'active': isCurrentGlobalTab()}">
<a class="pointer-cursor" ng-click="changeGlobalTab()" translate>Global</a>
</li>
<li ng-class="{'active': context.currentTab == 'rpc'}">
<a class="pointer-cursor" ng-click="changeTab('rpc')" translate>RPC</a>
<li class="nav-tab-title-rpcname" ng-repeat="setting in context.rpcSettings" ng-class="{'active': isCurrentRpcTab($index)}">
<a class="pointer-cursor" ng-click="changeRpcTab($index)">
<span class="nav-tab-rpcname" ng-bind="'RPC' + (setting.rpcAlias || setting.rpcHost ? ' (' + (setting.rpcAlias ? setting.rpcAlias : setting.rpcHost + ':' + setting.rpcPort) + ')' : '')" title="{{(setting.rpcAlias ? setting.rpcAlias : setting.rpcHost + ':' + setting.rpcPort)}}">RPC</span>
</a>
<a class="pointer-cursor nav-tab-close" ng-if="!setting.isDefault" title="{{'Delete RPC Setting' | translate}}">
<i class="fa fa-times" ng-click="removeRpcSetting(setting)"></i>
</a>
</li>
<li class="slim">
<a class="pointer-cursor" ng-click="addNewRpcSetting()" title="{{'Add New RPC Setting' | translate}}">
<i class="fa fa-plus"></i>
</a>
</li>
</ul>
<div class="tab-content no-padding">
<div class="tab-pane" ng-class="{'active': context.currentTab == 'global'}">
<div class="tab-pane" ng-class="{'active': isCurrentGlobalTab()}">
<div class="settings-table striped hoverable">
<div class="row">
<div class="setting-key setting-key-without-desc col-sm-4">
@ -18,7 +28,7 @@
<div class="setting-value col-sm-8">
<select class="form-control" style="width: 100%;" ng-model="context.settings.language"
ng-options="type as language.displayName for (type, language) in context.languages"
ng-change="settingService.setLanguage(context.settings.language)">
ng-change="setLanguage(context.settings.language)">
</select>
</div>
</div>
@ -29,7 +39,7 @@
<div class="setting-value col-sm-8">
<select class="form-control" style="width: 100%;" ng-model="context.sessionSettings.debugMode"
ng-options="option.value as (option.name | translate) for option in context.trueFalseOptions"
ng-change="settingService.setDebugMode(context.sessionSettings.debugMode)">
ng-change="setDebugMode(context.sessionSettings.debugMode)">
</select>
</div>
</div>
@ -38,11 +48,20 @@
<span translate>Page Title</span>
<i class="icon-primary fa fa-question-circle" data-toggle="popover"
data-trigger="hover" data-placement="auto right" data-container="body" data-html="true"
data-content="{{('Supported Placeholder' | translate) + ':<br/>' + ('AriaNg Title' | translate) + ': ${title}<br/>' + ('Downloading Count' | translate) + ': ${downloading}<br/>' + ('Waiting Count' | translate) + ': ${waiting}<br/>' + ('Stopped Count' | translate) + ': ${stopped}<br/>' + ('Download Speed' | translate) + ': ${downspeed}<br/>' + ('Upload Speed' | translate) + ': ${upspeed}<br/>'}}"></i>
data-content="{{('Supported Placeholder' | translate) + ':<br/>' +
('AriaNg Title' | translate) + ': ${title}<br/>' +
('Downloading Count' | translate) + ': ${downloading}<br/>' +
('Waiting Count' | translate) + ': ${waiting}<br/>' +
('Stopped Count' | translate) + ': ${stopped}<br/>' +
('Download Speed' | translate) + ': ${downspeed}<br/>' +
('Upload Speed' | translate) + ': ${upspeed}<br/><br/>' +
('Tips: You can use the &quot;noprefix&quot; tag to ignore the prefix, &quot;nosuffix&quot; tag ignore the suffix, and &quot;scale=n&quot; tag to set the decimal precision.' | translate) + '<br/>' +
('Example: ${downspeed:noprefix:nosuffix:scale=1}' | translate)}}"></i>
</div>
<div class="setting-value col-sm-8">
<input class="form-control" type="text" ng-model="context.settings.title"
ng-change="settingService.setTitle(context.settings.title)"/>
ng-change="setTitle(context.settings.title); updateTitlePreview()"/>
<em>[<span translate>Preview</span>] <span ng-bind="context.titlePreview"></span></em>
</div>
</div>
<div class="row">
@ -53,7 +72,7 @@
<div class="setting-value col-sm-8">
<select class="form-control" style="width: 100%;"
ng-model="context.settings.titleRefreshInterval"
ng-change="settingService.setTitleRefreshInterval(context.settings.titleRefreshInterval)"
ng-change="setTitleRefreshInterval(context.settings.titleRefreshInterval)"
ng-options="time.optionValue as (time.name | translate: {value: time.value}) for time in context.availableTime">
</select>
</div>
@ -78,7 +97,7 @@
<div class="setting-value col-sm-8">
<select class="form-control" style="width: 100%;"
ng-model="context.settings.globalStatRefreshInterval"
ng-change="settingService.setGlobalStatRefreshInterval(context.settings.globalStatRefreshInterval)"
ng-change="setGlobalStatRefreshInterval(context.settings.globalStatRefreshInterval)"
ng-options="time.optionValue as (time.name | translate: {value: time.value}) for time in context.availableTime">
</select>
</div>
@ -91,59 +110,93 @@
<div class="setting-value col-sm-8">
<select class="form-control" style="width: 100%;"
ng-model="context.settings.downloadTaskRefreshInterval"
ng-change="settingService.setDownloadTaskRefreshInterval(context.settings.downloadTaskRefreshInterval)"
ng-change="setDownloadTaskRefreshInterval(context.settings.downloadTaskRefreshInterval)"
ng-options="time.optionValue as (time.name | translate: {value: time.value}) for time in context.availableTime">
</select>
</div>
</div>
<div class="row">
<div class="setting-key setting-key-without-desc col-sm-4">
<span translate>Action After Creating New Tasks</span>
</div>
<div class="setting-value col-sm-8">
<select class="form-control" style="width: 100%;" ng-model="context.settings.afterCreatingNewTask"
ng-change="setAfterCreatingNewTask(context.settings.afterCreatingNewTask)">
<option value="task-list" translate>Navigate to Task List Page</option>
<option value="task-detail" translate>Navigate to Task Detail Page</option>
</select>
</div>
</div>
<div class="row tip no-background no-hover">
<span class="asterisk">*</span>
<span translate>Changes to the settings take effect after refreshing page.</span>
<button class="btn btn-xs btn-default" ng-click="resetSettings()">
<span translate>Reset Settings</span>
</button>
</div>
</div>
</div>
<div class="tab-pane" ng-class="{'active': context.currentTab == 'rpc'}">
<div class="tab-pane" ng-repeat="setting in context.rpcSettings" ng-class="{'active': isCurrentRpcTab($index)}">
<div class="settings-table striped hoverable">
<div class="row">
<div class="setting-key setting-key-without-desc col-sm-4">
<span translate>Aria2 RPC Host</span>
<span translate>Aria2 RPC Alias</span>
<span class="asterisk">*</span>
</div>
<div class="setting-value col-sm-8">
<div class="input-group">
<span class="input-group-addon" ng-bind="context.settings.protocol + '://'"></span>
<input class="form-control" type="text" ng-model="context.settings.rpcHost"
ng-change="settingService.setRpcHost(context.settings.rpcHost)"/>
<span class="input-group-addon" style="border-left: 0">:</span>
<span class="input-group-addon" ng-bind="context.settings.rpcPort"></span>
</div>
<input class="form-control" type="text" ng-placeholder="(setting.rpcHost ? setting.rpcHost + ':' + setting.rpcPort : '')" ng-model="setting.rpcAlias" ng-change="updateRpcSetting(setting, 'rpcAlias')"/>
</div>
</div>
<div class="row">
<div class="setting-key setting-key-without-desc col-sm-4">
<span translate>Aria2 RPC Port</span>
<span translate>Aria2 RPC Address</span>
<span class="asterisk">*</span>
</div>
<div class="setting-value col-sm-8">
<input class="form-control" type="text" ng-model="context.settings.rpcPort"
ng-change="settingService.setRpcPort(context.settings.rpcPort)"/>
<div class="input-group input-group-multiple">
<span class="input-group-addon" ng-bind="setting.protocol + '://'"></span>
<input class="form-control" type="text" ng-model="setting.rpcHost" ng-change="updateRpcSetting(setting, 'rpcHost')"/>
<span class="input-group-addon">:</span>
<div class="input-group-addon-container">
<input class="form-control form-control-rpcport" type="text" ng-model="setting.rpcPort" ng-change="updateRpcSetting(setting, 'rpcPort')"/>
</div>
<span class="input-group-addon">/</span>
<div class="input-group-addon-container">
<input class="form-control form-control-rpcinterface" type="text" ng-model="setting.rpcInterface" ng-change="updateRpcSetting(setting, 'rpcInterface')"/>
</div>
</div>
</div>
</div>
<div class="row">
<div class="setting-key setting-key-without-desc col-sm-4">
<span translate>Aria2 RPC Protocol</span>
<span class="asterisk">*</span>
<i class="icon-primary fa fa-question-circle" ng-tooltip-container="body" ng-tooltip-placement="top"
ng-tooltip="{{'Http and WebSocket would be disabled when accessing AriaNg via Https.' | translate}}"></i>
</div>
<div class="setting-value col-sm-8">
<select class="form-control" style="width: 100%;" ng-model="context.settings.protocol"
ng-change="settingService.setProtocol(context.settings.protocol)">
<option value="http" translate>Http</option>
<select class="form-control" style="width: 100%;" ng-model="setting.protocol" ng-change="updateRpcSetting(setting, 'protocol')">
<option value="http" ng-disabled="::(context.isInsecureProtocolDisabled)" ng-bind="('Http' + (context.isInsecureProtocolDisabled ? ' (Disabled)' : '')) | translate">Http</option>
<option value="https" translate>Https</option>
<option value="ws" translate>WebSocket</option>
<option value="ws" ng-disabled="::(context.isInsecureProtocolDisabled)" ng-bind="('WebSocket' + (context.isInsecureProtocolDisabled ? ' (Disabled)' : '')) | translate">WebSocket</option>
<option value="wss" translate>WebSocket (Security)</option>
</select>
</div>
</div>
<div class="row" ng-if="setting.protocol === 'http' || setting.protocol === 'https'">
<div class="setting-key setting-key-without-desc col-sm-4">
<span translate>Aria2 RPC Http Request Method</span>
<span class="asterisk">*</span>
<i class="icon-primary fa fa-question-circle" ng-tooltip-container="body" ng-tooltip-placement="top"
ng-tooltip="{{'POST method only supports aria2 v1.15.2 and above.' | translate}}"></i>
</div>
<div class="setting-value col-sm-8">
<select class="form-control" style="width: 100%;" ng-model="setting.httpMethod" ng-change="updateRpcSetting(setting, 'httpMethod')">
<option value="POST" translate>POST</option>
<option value="GET" translate>GET</option>
</select>
</div>
</div>
<div class="row">
<div class="setting-key setting-key-without-desc col-sm-4">
<span translate>Aria2 RPC Secret Token</span>
@ -151,8 +204,7 @@
</div>
<div class="setting-value col-sm-8">
<div class="input-group">
<input class="form-control" type="{{context.showRpcSecret ? 'text' : 'password'}}" ng-model="context.settings.secret"
ng-change="settingService.setSecret(context.settings.secret)"/>
<input class="form-control" type="{{context.showRpcSecret ? 'text' : 'password'}}" ng-model="setting.secret" ng-change="updateRpcSetting(setting, 'secret')"/>
<span class="input-group-addon input-group-addon-compact no-vertical-padding">
<button class="btn btn-xs btn-default" title="{{context.showRpcSecret ? 'Hide Secret' : 'Show Secret' | translate}}"
ng-class="{'active': context.showRpcSecret}" ng-click="context.showRpcSecret = !context.showRpcSecret">
@ -165,9 +217,20 @@
<div class="row tip no-background no-hover">
<span class="asterisk">*</span>
<span translate>Changes to the settings take effect after refreshing page.</span>
<button class="btn btn-xs btn-default" ng-disabled="setting.isDefault" ng-click="setDefaultRpcSetting(setting)">
<span translate>Activate</span>
</button>
</div>
</div>
</div>
</div>
</div>
</section>
<script id="setting-changed-notification.html" type="text/ng-template">
<div class="ui-notification custom-template">
<div class="message" ng-bind-html="message"></div>
<div class="message">
<a class="btn btn-small btn-primary close-notification" ng-click="refreshPage()" translate>Reload Page</a>
</div>
</div>
</script>

View file

@ -30,9 +30,9 @@
<span translate>Enabled Features</span>
</div>
<div class="setting-value col-sm-8">
<div class="checkbox checkbox-primary checkbox-compact" ng-repeat="feature in context.serverStatus.enabledFeatures">
<input id="{{'feature_' + $index}}" type="checkbox" checked="checked" disabled="disabled"/>
<label for="{{'feature_' + $index}}">
<div class="checkbox checkbox-primary checkbox-compact default-cursor" ng-repeat="feature in context.serverStatus.enabledFeatures">
<input id="{{'feature_' + $index}}" type="checkbox" checked="checked" disabled="disabled" class="default-cursor"/>
<label for="{{'feature_' + $index}}" class="text-cursor">
<span ng-bind="feature"></span>
</label>
</div>
@ -43,10 +43,10 @@
<span translate>Functions</span>
</div>
<div class="setting-value col-sm-8">
<button class="btn btn-primary" ng-click="saveSession()" promise-btn>
<button class="btn btn-sm btn-primary" ng-click="saveSession()" promise-btn>
<span translate>Save Session</span>
</button>
<button class="btn btn-danger" ng-click="shutdown()">
<button class="btn btn-sm btn-danger" ng-click="shutdown()">
<span translate>Shutdown Aria2</span>
</button>
</div>

View file

@ -1,6 +1,6 @@
<section class="content no-padding">
<div class="nav-tabs-custom">
<ul class="nav nav-tabs">
<ul class="nav nav-tabs" ng-if="task">
<li ng-class="{'active': context.currentTab == 'overview'}">
<a class="pointer-cursor" ng-click="changeTab('overview')" translate>Overview</a>
</li>
@ -23,12 +23,13 @@
<div class="tab-content no-padding">
<div class="tab-pane" ng-class="{'active': context.currentTab == 'overview'}">
<div class="settings-table striped hoverable">
<div class="row">
<div class="row" ng-if="task">
<div class="setting-key col-sm-4">
<span translate>Task Name</span>
</div>
<div class="setting-value col-sm-8">
<span class="allow-word-break" ng-bind="task.taskName"></span>
<span class="allow-word-break" ng-bind="task.taskName" ng-tooltip-container="body" ng-tooltip-placement="bottom"
ng-tooltip="{{(task.bittorrent && task.bittorrent.comment) ? task.bittorrent.comment : task.taskName}}"></span>
</div>
</div>
<div class="row" ng-if="task">
@ -36,7 +37,7 @@
<span translate>Task Size</span>
</div>
<div class="setting-value col-sm-8">
<span ng-bind="task.totalLength | readableVolumn"></span>
<span ng-bind="task.totalLength | readableVolume"></span>
<a class="pointer-cursor" ng-if="task.files" ng-click="changeTab('filelist')">
<span ng-bind="('format.settings.file-count' | translate: {count: task.selectedFileCount})"></span>
</a>
@ -48,7 +49,7 @@
</div>
<div class="setting-value col-sm-8">
<span ng-bind="task | taskStatus | translate: {errorcode: task.errorCode}"></span>
<i class="icon-primary fa fa-question-circle" ng-if="task.errorCode && task.errorCode != '0'"
<i class="icon-primary fa fa-question-circle" ng-if="task.errorCode && task.errorCode != '0' && task.errorMessage"
ng-tooltip="{{task.errorMessage}}" ng-tooltip-container="body" ng-tooltip-placement="top"></i>
</div>
</div>
@ -73,7 +74,7 @@
<span translate>Download</span>
</div>
<div class="setting-value col-sm-8">
<span ng-bind="(task.completedLength | readableVolumn) + (task.status == 'active' ? ' @ ' + (task.downloadSpeed | readableVolumn) + '/s' : '')"></span>
<span ng-bind="(task.completedLength | readableVolume) + (task.status == 'active' ? ' @ ' + (task.downloadSpeed | readableVolume) + '/s' : '')"></span>
</div>
</div>
<div class="row" ng-if="task && task.bittorrent">
@ -81,7 +82,7 @@
<span translate>Upload</span>
</div>
<div class="setting-value col-sm-8">
<span ng-bind="(task.uploadLength | readableVolumn) + (task.status == 'active' ? ' @ ' + (task.uploadSpeed | readableVolumn) + '/s' : '')"></span>
<span ng-bind="(task.uploadLength | readableVolume) + (task.status == 'active' ? ' @ ' + (task.uploadSpeed | readableVolume) + '/s' : '')"></span>
</div>
</div>
<div class="row" ng-if="task && task.bittorrent">
@ -124,6 +125,14 @@
<span class="allow-word-break" ng-bind="task.infoHash"></span>
</div>
</div>
<div class="row" ng-if="task && task.singleUrl">
<div class="setting-key col-sm-4">
<span translate>Download Url</span>
</div>
<div class="setting-value col-sm-8">
<span class="allow-word-break" ng-bind="task.singleUrl"></span>
</div>
</div>
<div class="row" ng-if="task">
<div class="setting-key col-sm-4">
<span translate>Download Dir</span>
@ -142,7 +151,7 @@
title="{{(context.collapseTrackers ? 'Expand' : 'Collapse') | translate}}"></i>
</div>
<div class="setting-value col-sm-8">
<div class="auto-ellipsis" ng-bind="serverAddress" title="{{serverAddress}}"
<div class="auto-ellipsis" ng-bind="serverAddress.length ? serverAddress.join(',') : serverAddress" title="{{serverAddress.length ? serverAddress.join(',') : serverAddress}}"
ng-repeat="serverAddress in task.bittorrent.announceList | limitTo: (context.collapseTrackers ? 1 : task.bittorrent.announceList.length)"></div>
</div>
</div>
@ -240,7 +249,7 @@
</div>
</div>
<div class="col-sm-2">
<span class="task-size" ng-bind="file.length | readableVolumn"></span>
<span class="task-size" ng-bind="file.length | readableVolume"></span>
</div>
</div>
</div>
@ -305,9 +314,9 @@
<div class="col-md-3 col-sm-4 col-xs-8">
<div class="task-peer-download-speed">
<i class="icon-download fa fa-arrow-down"></i>
<span ng-bind="(peer.downloadSpeed | readableVolumn) + '/s'"></span>&nbsp;
<span ng-bind="(peer.downloadSpeed | readableVolume) + '/s'"></span>&nbsp;
<i class="icon-upload fa fa-arrow-up"></i>
<span ng-bind="(peer.uploadSpeed | readableVolumn) + '/s'"></span>
<span ng-bind="(peer.uploadSpeed | readableVolume) + '/s'"></span>
</div>
</div>
</div>