mutil-threading download

This commit is contained in:
niuhuan 2021-10-31 14:01:19 +08:00
parent 6bf01d7a7b
commit ac601c3d79
7 changed files with 138 additions and 36 deletions

View File

@ -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"
} }

View File

@ -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)
} }

View File

@ -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 {

View File

@ -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");
}
} }

View File

@ -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(() {});
}
},
);
},
);
}

View File

@ -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有效则直接进入登录好的界面

View File

@ -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(),