mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-11 10:54:08 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31228e3edf | ||
|
|
31a32d7e14 | ||
|
|
65fd6fb489 | ||
|
|
953b878a89 | ||
|
|
ae4ca3da58 |
No files matched your search
@@ -306,26 +306,13 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
if resp.StatusCode != base.HttpCodeOK && resp.StatusCode != base.HttpCodePartialContent {
|
if resp.StatusCode != base.HttpCodeOK && resp.StatusCode != base.HttpCodePartialContent {
|
||||||
err = NewRequestError(resp.StatusCode, resp.Status)
|
err = NewRequestError(resp.StatusCode, resp.Status)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
// Http request success, reset retry times
|
||||||
}()
|
chunk.retryTimes = 0
|
||||||
if err != nil {
|
|
||||||
// If canceled, do not retry
|
|
||||||
if errors.Is(err, context.Canceled) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// retry request after 1 second
|
|
||||||
chunk.retryTimes = chunk.retryTimes + 1
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Http request success, reset retry times
|
|
||||||
chunk.retryTimes = 0
|
|
||||||
err = func() error {
|
|
||||||
defer resp.Body.Close()
|
|
||||||
for {
|
for {
|
||||||
n, err := resp.Body.Read(buf)
|
n, err := resp.Body.Read(buf)
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
@@ -342,7 +329,18 @@ func (f *Fetcher) fetchChunk(index int, ctx context.Context) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}()
|
}()
|
||||||
|
if err != nil {
|
||||||
|
// If canceled, do not retry
|
||||||
|
if errors.Is(err, context.Canceled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// retry request after 1 second
|
||||||
|
chunk.retryTimes = chunk.retryTimes + 1
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
// if chunk is completed, reset other not complete chunks retry times
|
// if chunk is completed, reset other not complete chunks retry times
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, c := range f.chunks {
|
for _, c := range f.chunks {
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import (
|
|||||||
"github.com/GopeedLab/gopeed/pkg/base"
|
"github.com/GopeedLab/gopeed/pkg/base"
|
||||||
"github.com/GopeedLab/gopeed/pkg/util"
|
"github.com/GopeedLab/gopeed/pkg/util"
|
||||||
gonanoid "github.com/matoous/go-nanoid/v2"
|
gonanoid "github.com/matoous/go-nanoid/v2"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/pkgerrors"
|
||||||
"github.com/virtuald/go-paniclog"
|
"github.com/virtuald/go-paniclog"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -91,6 +93,7 @@ func NewDownloader(cfg *DownloaderConfig) *Downloader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack
|
||||||
d.Logger = logger.NewLogger(cfg.ProductionMode, filepath.Join(cfg.StorageDir, "logs", "core.log"))
|
d.Logger = logger.NewLogger(cfg.ProductionMode, filepath.Join(cfg.StorageDir, "logs", "core.log"))
|
||||||
d.ExtensionLogger = logger.NewLogger(cfg.ProductionMode, filepath.Join(cfg.StorageDir, "logs", "extension.log"))
|
d.ExtensionLogger = logger.NewLogger(cfg.ProductionMode, filepath.Join(cfg.StorageDir, "logs", "extension.log"))
|
||||||
if cfg.ProductionMode {
|
if cfg.ProductionMode {
|
||||||
|
|||||||
@@ -254,12 +254,12 @@ func (d *Downloader) triggerOnResolve(req *base.Request) (res *base.Resource, er
|
|||||||
gopeed.Logger.logger.Warn().Err(err).Msgf("[%s] resource invalid", ext.buildIdentity())
|
gopeed.Logger.logger.Warn().Err(err).Msgf("[%s] resource invalid", ext.buildIdentity())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ctx.Res.Name = util.ReplaceInvalidFilename(ctx.Res.Name)
|
||||||
|
for _, file := range ctx.Res.Files {
|
||||||
|
file.Name = util.ReplaceInvalidFilename(file.Name)
|
||||||
|
}
|
||||||
ctx.Res.CalcSize(nil)
|
ctx.Res.CalcSize(nil)
|
||||||
}
|
}
|
||||||
ctx.Res.Name = util.ReplaceInvalidFilename(ctx.Res.Name)
|
|
||||||
for _, file := range ctx.Res.Files {
|
|
||||||
file.Name = util.ReplaceInvalidFilename(file.Name)
|
|
||||||
}
|
|
||||||
res = ctx.Res
|
res = ctx.Res
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ package rest
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"github.com/GopeedLab/gopeed/pkg/download"
|
"github.com/GopeedLab/gopeed/pkg/download"
|
||||||
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
"github.com/GopeedLab/gopeed/pkg/rest/model"
|
||||||
"github.com/GopeedLab/gopeed/pkg/util"
|
"github.com/GopeedLab/gopeed/pkg/util"
|
||||||
"github.com/gorilla/handlers"
|
"github.com/gorilla/handlers"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -131,6 +133,20 @@ func BuildServer(startCfg *model.StartConfig) (*http.Server, net.Listener, error
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// recover panic
|
||||||
|
r.Use(func(h http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer func() {
|
||||||
|
if v := recover(); v != nil {
|
||||||
|
err := errors.WithStack(fmt.Errorf("%v", v))
|
||||||
|
Downloader.Logger.Error().Stack().Err(err).Msg("http server panic")
|
||||||
|
WriteJson(w, model.NewErrorResult(err.Error(), model.CodeError))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
srv = &http.Server{Handler: handlers.CORS(
|
srv = &http.Server{Handler: handlers.CORS(
|
||||||
handlers.AllowedHeaders([]string{"Content-Type", "X-Api-Token", "X-Target-Uri"}),
|
handlers.AllowedHeaders([]string{"Content-Type", "X-Api-Token", "X-Target-Uri"}),
|
||||||
handlers.AllowedMethods([]string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}),
|
handlers.AllowedMethods([]string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}),
|
||||||
|
|||||||
@@ -387,35 +387,33 @@ class SettingView extends GetView<SettingController> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
final items = <Widget>[
|
final scheme = SizedBox(
|
||||||
SizedBox(
|
width: 100,
|
||||||
width: 150,
|
child: DropdownButtonFormField<String>(
|
||||||
child: DropdownButtonFormField<String>(
|
value: proxy.scheme,
|
||||||
value: proxy.scheme,
|
onChanged: (value) async {
|
||||||
onChanged: (value) async {
|
if (value != null && value != proxy.scheme) {
|
||||||
if (value != null && value != proxy.scheme) {
|
proxy.scheme = value;
|
||||||
proxy.scheme = value;
|
|
||||||
|
|
||||||
await debounceSave();
|
await debounceSave();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
items: const [
|
items: const [
|
||||||
DropdownMenuItem<String>(
|
DropdownMenuItem<String>(
|
||||||
value: 'http',
|
value: 'http',
|
||||||
child: Text('HTTP'),
|
child: Text('HTTP'),
|
||||||
),
|
),
|
||||||
DropdownMenuItem<String>(
|
DropdownMenuItem<String>(
|
||||||
value: 'https',
|
value: 'https',
|
||||||
child: Text('HTTPS'),
|
child: Text('HTTPS'),
|
||||||
),
|
),
|
||||||
DropdownMenuItem<String>(
|
DropdownMenuItem<String>(
|
||||||
value: 'socks5',
|
value: 'socks5',
|
||||||
child: Text('SOCKS5'),
|
child: Text('SOCKS5'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
);
|
||||||
];
|
|
||||||
|
|
||||||
final arr = proxy.host.split(':');
|
final arr = proxy.host.split(':');
|
||||||
var ip = '';
|
var ip = '';
|
||||||
@@ -438,10 +436,8 @@ class SettingView extends GetView<SettingController> {
|
|||||||
|
|
||||||
ipController.addListener(updateAddress);
|
ipController.addListener(updateAddress);
|
||||||
portController.addListener(updateAddress);
|
portController.addListener(updateAddress);
|
||||||
items.addAll([
|
final host = [
|
||||||
const Padding(padding: EdgeInsets.only(left: 20)),
|
Flexible(
|
||||||
SizedBox(
|
|
||||||
width: 150,
|
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
controller: ipController,
|
controller: ipController,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
@@ -455,8 +451,7 @@ class SettingView extends GetView<SettingController> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Padding(padding: EdgeInsets.only(left: 10)),
|
const Padding(padding: EdgeInsets.only(left: 10)),
|
||||||
SizedBox(
|
Flexible(
|
||||||
width: 150,
|
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
controller: portController,
|
controller: portController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
@@ -470,14 +465,13 @@ class SettingView extends GetView<SettingController> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]);
|
];
|
||||||
|
|
||||||
final usrController = TextEditingController(text: proxy.usr);
|
final usrController = TextEditingController(text: proxy.usr);
|
||||||
final pwdController = TextEditingController(text: proxy.pwd);
|
final pwdController = TextEditingController(text: proxy.pwd);
|
||||||
|
|
||||||
final auth = [
|
final auth = [
|
||||||
SizedBox(
|
Flexible(
|
||||||
width: 150,
|
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
controller: usrController,
|
controller: usrController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
@@ -491,8 +485,7 @@ class SettingView extends GetView<SettingController> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Padding(padding: EdgeInsets.only(left: 10)),
|
const Padding(padding: EdgeInsets.only(left: 10)),
|
||||||
SizedBox(
|
Flexible(
|
||||||
width: 150,
|
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
controller: pwdController,
|
controller: pwdController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
@@ -513,8 +506,9 @@ class SettingView extends GetView<SettingController> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: _addPadding([
|
children: _addPadding([
|
||||||
switcher,
|
switcher,
|
||||||
|
scheme,
|
||||||
Row(
|
Row(
|
||||||
children: items,
|
children: host,
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: auth,
|
children: auth,
|
||||||
@@ -534,7 +528,7 @@ class SettingView extends GetView<SettingController> {
|
|||||||
(Key key) {
|
(Key key) {
|
||||||
final items = <Widget>[
|
final items = <Widget>[
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 150,
|
width: 80,
|
||||||
child: DropdownButtonFormField<String>(
|
child: DropdownButtonFormField<String>(
|
||||||
value: startCfg.value.network,
|
value: startCfg.value.network,
|
||||||
onChanged: Util.isDesktop()
|
onChanged: Util.isDesktop()
|
||||||
@@ -585,8 +579,7 @@ class SettingView extends GetView<SettingController> {
|
|||||||
portController.addListener(updateAddress);
|
portController.addListener(updateAddress);
|
||||||
items.addAll([
|
items.addAll([
|
||||||
const Padding(padding: EdgeInsets.only(left: 20)),
|
const Padding(padding: EdgeInsets.only(left: 20)),
|
||||||
SizedBox(
|
Flexible(
|
||||||
width: 150,
|
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
controller: ipController,
|
controller: ipController,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
@@ -600,8 +593,7 @@ class SettingView extends GetView<SettingController> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Padding(padding: EdgeInsets.only(left: 10)),
|
const Padding(padding: EdgeInsets.only(left: 10)),
|
||||||
SizedBox(
|
Flexible(
|
||||||
width: 150,
|
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
controller: portController,
|
controller: portController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
|
|||||||
@@ -59,17 +59,17 @@ const zhTW = {
|
|||||||
'deleteTask': '刪除任務',
|
'deleteTask': '刪除任務',
|
||||||
'deleteTaskTip': '保留已經下載的文件',
|
'deleteTaskTip': '保留已經下載的文件',
|
||||||
'delete': '刪除',
|
'delete': '刪除',
|
||||||
'newVersionTitle': '發現新版本 @version',
|
'newVersionTitle': '現新版本 @version 已發佈',
|
||||||
'newVersionUpdate': '立刻更新',
|
'newVersionUpdate': '下載',
|
||||||
'newVersionLater': '稍後更新',
|
'newVersionLater': '取消',
|
||||||
'extensions': '外掛程序',
|
'extensions': '擴充程序',
|
||||||
'extensionInstallUrl': '安裝URL',
|
'extensionInstallUrl': '安裝URL',
|
||||||
'extensionInstallSuccess': '安裝成功',
|
'extensionInstallSuccess': '安裝成功',
|
||||||
'extensionUpdateSuccess': '更新完成',
|
'extensionUpdateSuccess': '更新完成',
|
||||||
'extensionDelete': '解除安裝',
|
'extensionDelete': '解除安裝',
|
||||||
'extensionAlreadyLatest': '已是最新版本',
|
'extensionAlreadyLatest': '已是最新版本',
|
||||||
'extensionFind': '取得外掛',
|
'extensionFind': '取得擴充程序',
|
||||||
'extensionDevelop': '開發外掛',
|
'extensionDevelop': '開發擴充程序',
|
||||||
'history': '歷史記錄',
|
'history': '歷史記錄',
|
||||||
'clearHistory': '清空記錄',
|
'clearHistory': '清空記錄',
|
||||||
'noHistoryFound': '還沒有記錄',
|
'noHistoryFound': '還沒有記錄',
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 1.5.2+1
|
version: 1.5.3+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0 <4.0.0'
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
|
|||||||
Reference in new issue
Block a user