mutil-threading download
This commit is contained in:
parent
6bf01d7a7b
commit
ac601c3d79
|
@ -10,6 +10,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"pikapi/main/database/comic_center"
|
"pikapi/main/database/comic_center"
|
||||||
"pikapi/main/utils"
|
"pikapi/main/utils"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,12 +18,14 @@ import (
|
||||||
// downloadRunning 如果为false则停止下载
|
// downloadRunning 如果为false则停止下载
|
||||||
// downloadRestart 为true则取消从新启动下载功能
|
// downloadRestart 为true则取消从新启动下载功能
|
||||||
|
|
||||||
|
var downloadThreadCount = 1
|
||||||
|
var downloadThreadFetch = 100
|
||||||
|
|
||||||
var downloadRunning = false
|
var downloadRunning = false
|
||||||
var downloadRestart = false
|
var downloadRestart = false
|
||||||
|
|
||||||
var downloadingComic *comic_center.ComicDownload
|
var downloadingComic *comic_center.ComicDownload
|
||||||
var downloadingEp *comic_center.ComicDownloadEp
|
var downloadingEp *comic_center.ComicDownloadEp
|
||||||
var downloadingPicture *comic_center.ComicDownloadPicture
|
|
||||||
|
|
||||||
var dlFlag = true
|
var dlFlag = true
|
||||||
|
|
||||||
|
@ -255,24 +258,46 @@ func downloadLoadPicture() {
|
||||||
if downloadHasStop() {
|
if downloadHasStop() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var err error
|
// 获取到这个章节需要下载的图片
|
||||||
downloadingPicture, err = comic_center.LoadFirstNeedDownloadPicture(downloadingEp.ID)
|
downloadingPictures, err := comic_center.LoadNeedDownloadPictures(downloadingEp.ID, downloadThreadFetch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
go downloadInitPicture()
|
// 如果不需要下载
|
||||||
}
|
if len(*downloadingPictures) == 0 {
|
||||||
|
|
||||||
func downloadInitPicture() {
|
|
||||||
// 暂停检测
|
|
||||||
if downloadHasStop() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if downloadingPicture == nil {
|
|
||||||
// 所有图片都下完了, 汇总EP下载情况
|
// 所有图片都下完了, 汇总EP下载情况
|
||||||
go downloadSummaryEp()
|
go downloadSummaryEp()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// 线程池
|
||||||
|
channel := make(chan int, downloadThreadCount)
|
||||||
|
defer close(channel)
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
for i := 0; i < len(*downloadingPictures); i++ {
|
||||||
|
// 暂停检测
|
||||||
|
if downloadHasStop() {
|
||||||
|
wg.Wait()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
channel <- 0
|
||||||
|
wg.Add(1)
|
||||||
|
// 不放入携程, 防止i已经变化
|
||||||
|
picPoint := &((*downloadingPictures)[i])
|
||||||
|
go func() {
|
||||||
|
downloadPicture(picPoint)
|
||||||
|
<-channel
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
// 再次新一轮的下载, 直至 len(*downloadingPictures) == 0
|
||||||
|
go downloadLoadPicture()
|
||||||
|
}
|
||||||
|
|
||||||
|
var downloadEventChannelMutex = sync.Mutex{}
|
||||||
|
|
||||||
|
// 这里不能使用暂停检测, 多次检测会导致问题
|
||||||
|
func downloadPicture(downloadingPicture *comic_center.ComicDownloadPicture) {
|
||||||
// 下载图片, 最多重试5次
|
// 下载图片, 最多重试5次
|
||||||
println("正在下载图片 " + fmt.Sprintf("%d", downloadingPicture.RankInEp))
|
println("正在下载图片 " + fmt.Sprintf("%d", downloadingPicture.RankInEp))
|
||||||
for i := 0; i < 5; i++ {
|
for i := 0; i < 5; i++ {
|
||||||
|
@ -280,22 +305,25 @@ func downloadInitPicture() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// 对下载的漫画临时变量热更新并通知前端
|
func() {
|
||||||
downloadingPicture.DownloadFinished = true
|
downloadEventChannelMutex.Lock()
|
||||||
downloadingEp.DownloadPictureCount = downloadingEp.DownloadPictureCount + 1
|
defer downloadEventChannelMutex.Unlock()
|
||||||
downloadingComic.DownloadPictureCount = downloadingComic.DownloadPictureCount + 1
|
// 对下载的漫画临时变量热更新并通知前端
|
||||||
downloadComicEventSend(downloadingComic)
|
downloadingPicture.DownloadFinished = true
|
||||||
|
downloadingEp.DownloadPictureCount = downloadingEp.DownloadPictureCount + 1
|
||||||
|
downloadingComic.DownloadPictureCount = downloadingComic.DownloadPictureCount + 1
|
||||||
|
downloadComicEventSend(downloadingComic)
|
||||||
|
}()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
// 没能下载成功, 图片置为下载失败
|
// 没能下载成功, 图片置为下载失败
|
||||||
if !downloadingPicture.DownloadFinished {
|
if !downloadingPicture.DownloadFinished {
|
||||||
err := comic_center.PictureFailed(downloadingPicture.ID)
|
err := comic_center.PictureFailed(downloadingPicture.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
// ??? panic X channel ???
|
||||||
|
// panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 加载下一张需要下载的图片
|
|
||||||
go downloadLoadPicture()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 下载指定图片
|
// 下载指定图片
|
||||||
|
@ -371,8 +399,10 @@ func downloadSummaryEp() {
|
||||||
go downloadLoadEp()
|
go downloadLoadEp()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 边下载边导出(导出路径)
|
||||||
var downloadAndExportPath = ""
|
var downloadAndExportPath = ""
|
||||||
|
|
||||||
|
// 边下载边导出(导出图片)
|
||||||
func downloadAndExport(
|
func downloadAndExport(
|
||||||
downloadingComic *comic_center.ComicDownload,
|
downloadingComic *comic_center.ComicDownload,
|
||||||
downloadingEp *comic_center.ComicDownloadEp,
|
downloadingEp *comic_center.ComicDownloadEp,
|
||||||
|
@ -412,12 +442,13 @@ func downloadAndExport(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 写入文件
|
// 写入文件
|
||||||
filePath := path.Join(epDir, fmt.Sprintf("%03d.%s", downloadingPicture.RankInEp, jFormat(format)))
|
filePath := path.Join(epDir, fmt.Sprintf("%03d.%s", downloadingPicture.RankInEp, aliasFormat(format)))
|
||||||
ioutil.WriteFile(filePath, buff, utils.CreateFileMode)
|
ioutil.WriteFile(filePath, buff, utils.CreateFileMode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 边下载边导出(导出logo)
|
||||||
func downloadAndExportLogo(
|
func downloadAndExportLogo(
|
||||||
downloadingComic *comic_center.ComicDownload,
|
downloadingComic *comic_center.ComicDownload,
|
||||||
) {
|
) {
|
||||||
|
@ -444,7 +475,7 @@ func downloadAndExportLogo(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 写入文件
|
// 写入文件
|
||||||
filePath := path.Join(comicDir, fmt.Sprintf("%s.%s", "logo", jFormat(f)))
|
filePath := path.Join(comicDir, fmt.Sprintf("%s.%s", "logo", aliasFormat(f)))
|
||||||
ioutil.WriteFile(filePath, buff, utils.CreateFileMode)
|
ioutil.WriteFile(filePath, buff, utils.CreateFileMode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -453,7 +484,8 @@ func downloadAndExportLogo(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func jFormat(format string) string {
|
// jpeg的拓展名
|
||||||
|
func aliasFormat(format string) string {
|
||||||
if format == "jpeg" {
|
if format == "jpeg" {
|
||||||
return "jpg"
|
return "jpg"
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ func InitPlugin(_remoteDir string, _downloadDir string, _tmpDir string) {
|
||||||
tmpDir = _tmpDir
|
tmpDir = _tmpDir
|
||||||
comic_center.ResetAll()
|
comic_center.ResetAll()
|
||||||
downloadAndExportPath = loadDownloadAndExportPath()
|
downloadAndExportPath = loadDownloadAndExportPath()
|
||||||
|
downloadThreadCount = loadDownloadThreadCount()
|
||||||
go downloadBackground()
|
go downloadBackground()
|
||||||
downloadRunning = true
|
downloadRunning = true
|
||||||
}
|
}
|
||||||
|
@ -73,6 +74,25 @@ func loadDownloadAndExportPath() string {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func saveDownloadThreadCount(value int) {
|
||||||
|
strValue := strconv.Itoa(value)
|
||||||
|
properties.SaveProperty("downloadThreadCount", strValue)
|
||||||
|
downloadThreadCount = value
|
||||||
|
downloadRestart = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadDownloadThreadCount() int {
|
||||||
|
count, err := properties.LoadProperty("downloadThreadCount", "2")
|
||||||
|
if err != nil {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
i, err := strconv.Atoi(count)
|
||||||
|
if err != nil {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
func setSwitchAddress(nSwitchAddress string) error {
|
func setSwitchAddress(nSwitchAddress string) error {
|
||||||
err := properties.SaveSwitchAddress(nSwitchAddress)
|
err := properties.SaveSwitchAddress(nSwitchAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -617,6 +637,15 @@ func FlatInvoke(method string, params string) (string, error) {
|
||||||
return loadDownloadAndExportPath(), nil
|
return loadDownloadAndExportPath(), nil
|
||||||
case "saveDownloadAndExportPath":
|
case "saveDownloadAndExportPath":
|
||||||
return "", saveDownloadAndExportPath(params)
|
return "", saveDownloadAndExportPath(params)
|
||||||
|
case "saveDownloadThreadCount":
|
||||||
|
i, e := strconv.Atoi(params)
|
||||||
|
if e != nil {
|
||||||
|
return "", e
|
||||||
|
}
|
||||||
|
saveDownloadThreadCount(i)
|
||||||
|
return "", nil
|
||||||
|
case "loadDownloadThreadCount":
|
||||||
|
return strconv.Itoa(loadDownloadThreadCount()), nil
|
||||||
}
|
}
|
||||||
return "", errors.New("method not found : " + method)
|
return "", errors.New("method not found : " + method)
|
||||||
}
|
}
|
||||||
|
|
|
@ -350,22 +350,17 @@ func LoadFirstNeedDownloadEp(comicId string) (*ComicDownloadEp, error) {
|
||||||
return &ep, nil
|
return &ep, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadFirstNeedDownloadPicture(epId string) (*ComicDownloadPicture, error) {
|
// LoadNeedDownloadPictures 根据EP.ID获取需要下载的图片
|
||||||
|
func LoadNeedDownloadPictures(epId string, limit int) (*[]ComicDownloadPicture, error) {
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer mutex.Unlock()
|
||||||
var picture ComicDownloadPicture
|
var pictures []ComicDownloadPicture
|
||||||
err := db.First(
|
err := db.Find(
|
||||||
&picture,
|
&pictures,
|
||||||
"ep_id = ? AND download_failed = 0 AND download_finished = 0",
|
"ep_id = ? AND download_failed = 0 AND download_finished = 0 LIMIT ?",
|
||||||
epId,
|
epId, limit,
|
||||||
).Error
|
).Error
|
||||||
if err != nil {
|
return &pictures, err
|
||||||
if err == gorm.ErrRecordNotFound {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &picture, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func FetchPictures(comicId string, epId string, list *[]ComicDownloadPicture) error {
|
func FetchPictures(comicId string, epId string, list *[]ComicDownloadPicture) error {
|
||||||
|
|
|
@ -588,4 +588,16 @@ class Method {
|
||||||
"flag": flag,
|
"flag": flag,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 获取下载线程数量
|
||||||
|
Future<int> loadDownloadThreadCount() async {
|
||||||
|
var strValue = await _flatInvoke("loadDownloadThreadCount", {});
|
||||||
|
return int.parse(strValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 设置下载线程数
|
||||||
|
Future saveDownloadThreadCount(int value) {
|
||||||
|
return _flatInvoke("saveDownloadThreadCount", "$value");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pikapi/basic/Common.dart';
|
||||||
|
import 'package:pikapi/basic/Method.dart';
|
||||||
|
|
||||||
|
late int _downloadThreadCount;
|
||||||
|
const _values = [1, 2, 3, 4, 5];
|
||||||
|
|
||||||
|
Future initDownloadThreadCount() async {
|
||||||
|
_downloadThreadCount = await method.loadDownloadThreadCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget downloadThreadCountSetting() {
|
||||||
|
return StatefulBuilder(
|
||||||
|
builder: (BuildContext context, void Function(void Function()) setState) {
|
||||||
|
return ListTile(
|
||||||
|
title: Text("下载线程数"),
|
||||||
|
subtitle: Text("$_downloadThreadCount"),
|
||||||
|
onTap: () async {
|
||||||
|
int? value = await chooseListDialog(context, "选择下载线程数", _values);
|
||||||
|
if (value != null) {
|
||||||
|
await method.saveDownloadThreadCount(value);
|
||||||
|
_downloadThreadCount = value;
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ import 'package:pikapi/basic/config/AutoFullScreen.dart';
|
||||||
import 'package:pikapi/basic/config/ChooserRoot.dart';
|
import 'package:pikapi/basic/config/ChooserRoot.dart';
|
||||||
import 'package:pikapi/basic/config/ContentFailedReloadAction.dart';
|
import 'package:pikapi/basic/config/ContentFailedReloadAction.dart';
|
||||||
import 'package:pikapi/basic/config/DownloadAndExportPath.dart';
|
import 'package:pikapi/basic/config/DownloadAndExportPath.dart';
|
||||||
|
import 'package:pikapi/basic/config/DownloadThreadCount.dart';
|
||||||
import 'package:pikapi/basic/config/FullScreenAction.dart';
|
import 'package:pikapi/basic/config/FullScreenAction.dart';
|
||||||
import 'package:pikapi/basic/config/FullScreenUI.dart';
|
import 'package:pikapi/basic/config/FullScreenUI.dart';
|
||||||
import 'package:pikapi/basic/config/KeyboardController.dart';
|
import 'package:pikapi/basic/config/KeyboardController.dart';
|
||||||
|
@ -65,6 +66,7 @@ class _InitScreenState extends State<InitScreen> {
|
||||||
await initTimeZone();
|
await initTimeZone();
|
||||||
await initDownloadAndExportPath();
|
await initDownloadAndExportPath();
|
||||||
await initAndroidSecureFlag();
|
await initAndroidSecureFlag();
|
||||||
|
await initDownloadThreadCount();
|
||||||
// 登录, 如果token失效重新登录, 网络不好的时候可能需要1分钟
|
// 登录, 如果token失效重新登录, 网络不好的时候可能需要1分钟
|
||||||
if (await method.preLogin()) {
|
if (await method.preLogin()) {
|
||||||
// 如果token或username+password有效则直接进入登录好的界面
|
// 如果token或username+password有效则直接进入登录好的界面
|
||||||
|
|
|
@ -10,6 +10,7 @@ import 'package:pikapi/basic/config/AutoFullScreen.dart';
|
||||||
import 'package:pikapi/basic/config/ChooserRoot.dart';
|
import 'package:pikapi/basic/config/ChooserRoot.dart';
|
||||||
import 'package:pikapi/basic/config/ContentFailedReloadAction.dart';
|
import 'package:pikapi/basic/config/ContentFailedReloadAction.dart';
|
||||||
import 'package:pikapi/basic/config/DownloadAndExportPath.dart';
|
import 'package:pikapi/basic/config/DownloadAndExportPath.dart';
|
||||||
|
import 'package:pikapi/basic/config/DownloadThreadCount.dart';
|
||||||
import 'package:pikapi/basic/config/FullScreenAction.dart';
|
import 'package:pikapi/basic/config/FullScreenAction.dart';
|
||||||
import 'package:pikapi/basic/config/FullScreenUI.dart';
|
import 'package:pikapi/basic/config/FullScreenUI.dart';
|
||||||
import 'package:pikapi/basic/config/KeyboardController.dart';
|
import 'package:pikapi/basic/config/KeyboardController.dart';
|
||||||
|
@ -132,6 +133,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
downloadThreadCountSetting(),
|
||||||
downloadAndExportPathSetting(),
|
downloadAndExportPathSetting(),
|
||||||
fontSetting(),
|
fontSetting(),
|
||||||
Divider(),
|
Divider(),
|
||||||
|
|
Loading…
Reference in New Issue