浏览代码

完成基础重构

Signed-off-by: allan716 <[email protected]>
allan716 3 年之前
父节点
当前提交
eabf4fef3c

+ 1 - 0
.gitignore

@@ -86,3 +86,4 @@ TestData/
 /TestCode/Logs
 /TestCode/Logs
 /internal/logic/task_queue/task_queue
 /internal/logic/task_queue/task_queue
 /internal/logic/task_queue/Logs
 /internal/logic/task_queue/Logs
+/task_queue

+ 4 - 23
internal/backend/controllers/v1/job_things.go

@@ -1,7 +1,6 @@
 package v1
 package v1
 
 
 import (
 import (
-	"github.com/allanpk716/ChineseSubFinder/internal/logic/cron_helper"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/backend"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/backend"
 	"github.com/gin-gonic/gin"
 	"github.com/gin-gonic/gin"
@@ -17,7 +16,7 @@ func (cb ControllerBase) JobStartHandler(c *gin.Context) {
 
 
 	if cb.cronHelper.CronHelperRunning() == false {
 	if cb.cronHelper.CronHelperRunning() == false {
 		go func() {
 		go func() {
-			cb.cronHelper.Start(settings.GetSettings().CommonSettings.RunScanAtStartUp)
+			cb.cronHelper.Start(settings.GetSettings(true).CommonSettings.RunScanAtStartUp)
 		}()
 		}()
 	}
 	}
 
 
@@ -51,25 +50,7 @@ func (cb ControllerBase) JobStatusHandler(c *gin.Context) {
 		cb.ErrorProcess(c, "JobStatusHandler", err)
 		cb.ErrorProcess(c, "JobStatusHandler", err)
 	}()
 	}()
 
 
-	cronStatus := cb.cronHelper.CronHelperRunning()
-	coreJobStatus := cb.cronHelper.FullDownloadProcessRunning()
-
-	if coreJobStatus == true {
-		// 核心任务在运行就是运行
-		c.JSON(http.StatusOK, backend.ReplyJobStatus{
-			Status: cron_helper.Running,
-		})
-	} else {
-		// 核心任务没有运行,再判断是否定时器启动了
-		if cronStatus == true {
-			c.JSON(http.StatusOK, backend.ReplyJobStatus{
-				Status: cron_helper.Running,
-			})
-		} else {
-			c.JSON(http.StatusOK, backend.ReplyJobStatus{
-				Status: cron_helper.Stopped,
-			})
-		}
-	}
-
+	c.JSON(http.StatusOK, backend.ReplyJobStatus{
+		Status: cb.cronHelper.CronRunningStatusString(),
+	})
 }
 }

+ 125 - 97
internal/logic/cron_helper/cron_helper.go

@@ -1,11 +1,14 @@
 package cron_helper
 package cron_helper
 
 
 import (
 import (
-	"github.com/allanpk716/ChineseSubFinder/internal/logic/downloader_helper"
-	"github.com/allanpk716/ChineseSubFinder/internal/logic/pre_download_process"
+	"github.com/allanpk716/ChineseSubFinder/internal/logic/pre_job"
+	"github.com/allanpk716/ChineseSubFinder/internal/logic/task_queue"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/common"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/common"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/downloader"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_formatter"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/video_scan_and_refresh_helper"
 	"github.com/robfig/cron/v3"
 	"github.com/robfig/cron/v3"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 	"sync"
 	"sync"
@@ -13,15 +16,17 @@ import (
 )
 )
 
 
 type CronHelper struct {
 type CronHelper struct {
-	fullSubDownloadProcessing     bool // 这个是核心耗时函数执行的状态
-	fullSubDownloadProcessingLock sync.Mutex
-	cronHelperRunning             bool // 这个是定时器启动的状态,它为true,不代表核心函数在执行
-	cronHelperRunningLock         sync.Mutex
-	c                             *cron.Cron
-	dh                            *downloader_helper.DownloaderHelper
-
-	sets *settings.Settings
-	log  *logrus.Logger
+	stopping                bool                   // 正在停止
+	cronHelperRunning       bool                   // 这个是定时器启动的状态,它为true,不代表核心函数在执行
+	downloadQueue           *task_queue.TaskQueue  // 需要下载的视频的队列
+	downloader              *downloader.Downloader // 下载者线程
+	cronLock                sync.Mutex             // 锁
+	c                       *cron.Cron             // 定时器实例
+	sets                    *settings.Settings     // 设置实例
+	log                     *logrus.Logger         // 日志实例
+	entryIDScanVideoProcess cron.EntryID
+	entryIDSupplierCheck    cron.EntryID
+	entryIDQueueDownloader  cron.EntryID
 }
 }
 
 
 func NewCronHelper(_log *logrus.Logger, _sets *settings.Settings) *CronHelper {
 func NewCronHelper(_log *logrus.Logger, _sets *settings.Settings) *CronHelper {
@@ -29,15 +34,44 @@ func NewCronHelper(_log *logrus.Logger, _sets *settings.Settings) *CronHelper {
 	ch := CronHelper{
 	ch := CronHelper{
 		log:  _log,
 		log:  _log,
 		sets: _sets,
 		sets: _sets,
+		// 实例化下载队列
+		downloadQueue: task_queue.NewTaskQueue("LocalSubDownloadQueue", _sets, _log),
 	}
 	}
+
 	return &ch
 	return &ch
 }
 }
 
 
-// Start 开启定时器任务,这个任务是非阻塞的,coreSubDownloadProcess 仅仅可能是这个函数执行耗时而已
+// Start 开启定时器任务,这个任务是非阻塞的,scanVideoProcess 仅仅可能是这个函数执行耗时而已
 // runImmediately == false 那么 ch.c.Start() 是不会阻塞的
 // runImmediately == false 那么 ch.c.Start() 是不会阻塞的
 func (ch *CronHelper) Start(runImmediately bool) {
 func (ch *CronHelper) Start(runImmediately bool) {
 
 
-	_, err := cron.ParseStandard(ch.sets.CommonSettings.ScanInterval)
+	ch.cronLock.Lock()
+	if ch.cronHelperRunning == true {
+		ch.cronLock.Unlock()
+		return
+	}
+	ch.cronLock.Unlock()
+
+	ch.cronLock.Lock()
+	ch.cronHelperRunning = true
+	ch.stopping = false
+	ch.cronLock.Unlock()
+	// ----------------------------------------------
+	// 初始化下载者,里面的两个 func 需要使用定时器启动 SupplierCheck QueueDownloader
+	ch.downloader = downloader.NewDownloader(
+		sub_formatter.GetSubFormatter(ch.sets.AdvancedSettings.SubNameFormatter),
+		ch.sets, ch.log, ch.downloadQueue)
+	// ----------------------------------------------
+	// 前置的任务,热修复、字幕修改文件名格式、提前下载好浏览器
+	pj := pre_job.NewPreJob(ch.sets, ch.log)
+	err := pj.HotFix().ChangeSubNameFormat().ReloadBrowser().Wait()
+	if err != nil {
+		ch.log.Panicln("pre_job", err)
+		return
+	}
+	// ----------------------------------------------
+	// 判断扫描任务的时间间隔是否符合要求,不符合则重写默认值
+	_, err = cron.ParseStandard(ch.sets.CommonSettings.ScanInterval)
 	if err != nil {
 	if err != nil {
 		ch.log.Warningln("CommonSettings.ScanInterval format error, after v0.25.x , need reset this at WebUI")
 		ch.log.Warningln("CommonSettings.ScanInterval format error, after v0.25.x , need reset this at WebUI")
 		// 如果解析错误了,就需要重新赋值默认值过来,然后保存
 		// 如果解析错误了,就需要重新赋值默认值过来,然后保存
@@ -49,25 +83,32 @@ func (ch *CronHelper) Start(runImmediately bool) {
 			return
 			return
 		}
 		}
 	}
 	}
-
+	// ----------------------------------------------
 	ch.c = cron.New(cron.WithChain(cron.SkipIfStillRunning(cron.DefaultLogger)))
 	ch.c = cron.New(cron.WithChain(cron.SkipIfStillRunning(cron.DefaultLogger)))
 	// 定时器
 	// 定时器
-	entryID, err := ch.c.AddFunc(ch.sets.CommonSettings.ScanInterval, ch.coreSubDownloadProcess)
+	ch.entryIDScanVideoProcess, err = ch.c.AddFunc(ch.sets.CommonSettings.ScanInterval, ch.scanVideoProcess)
+	if err != nil {
+		ch.log.Panicln("CronHelper scanVideoProcess, Cron entryID:", ch.entryIDScanVideoProcess, "Error:", err)
+	}
+	ch.entryIDSupplierCheck, err = ch.c.AddFunc("@every 1h", ch.downloader.SupplierCheck)
+	if err != nil {
+		ch.log.Panicln("CronHelper SupplierCheck, Cron entryID:", ch.entryIDSupplierCheck, "Error:", err)
+	}
+	ch.entryIDQueueDownloader, err = ch.c.AddFunc("@every 15s", ch.downloader.QueueDownloader)
 	if err != nil {
 	if err != nil {
-		ch.log.Panicln("CronHelper Cron entryID:", entryID, "Error:", err)
+		ch.log.Panicln("CronHelper QueueDownloader, Cron entryID:", ch.entryIDQueueDownloader, "Error:", err)
 	}
 	}
 
 
-	ch.cronHelperRunningLock.Lock()
-	ch.cronHelperRunning = true
-	ch.cronHelperRunningLock.Unlock()
 	// 是否在定时器开启前先执行一次任务
 	// 是否在定时器开启前先执行一次任务
 	if runImmediately == true {
 	if runImmediately == true {
 
 
-		ch.log.Infoln("First Time coreSubDownloadProcess Start")
+		ch.log.Infoln("First Time scanVideoProcess Start")
+
+		ch.scanVideoProcess()
 
 
-		ch.coreSubDownloadProcess()
+		ch.downloader.SupplierCheck()
 
 
-		ch.log.Infoln("First Time coreSubDownloadProcess End")
+		ch.log.Infoln("First Time scanVideoProcess End")
 
 
 	} else {
 	} else {
 		ch.log.Infoln("RunAtStartup: false, so will not Run At Startup")
 		ch.log.Infoln("RunAtStartup: false, so will not Run At Startup")
@@ -80,7 +121,7 @@ func (ch *CronHelper) Start(runImmediately bool) {
 	if len(ch.c.Entries()) > 0 {
 	if len(ch.c.Entries()) > 0 {
 
 
 		// 不会马上启动扫描,那么就需要设置当前的时间,且为 waiting
 		// 不会马上启动扫描,那么就需要设置当前的时间,且为 waiting
-		tttt := ch.c.Entries()[0].Next.Format("2006-01-02 15:04:05")
+		tttt := ch.c.Entry(ch.entryIDScanVideoProcess).Next.Format("2006-01-02 15:04:05")
 		common.SetSubScanJobStatusWaiting(tttt)
 		common.SetSubScanJobStatusWaiting(tttt)
 
 
 		ch.log.Infoln("Next Sub Scan Will Process At:", tttt)
 		ch.log.Infoln("Next Sub Scan Will Process At:", tttt)
@@ -92,39 +133,37 @@ func (ch *CronHelper) Start(runImmediately bool) {
 // Stop 会阻塞等待任务完成
 // Stop 会阻塞等待任务完成
 func (ch *CronHelper) Stop() {
 func (ch *CronHelper) Stop() {
 
 
-	fullSubDownloadProcessing := false
-	ch.fullSubDownloadProcessingLock.Lock()
-	fullSubDownloadProcessing = ch.fullSubDownloadProcessing
-	ch.fullSubDownloadProcessingLock.Unlock()
+	cronHelperRunning := false
+	ch.cronLock.Lock()
+	cronHelperRunning = ch.cronHelperRunning
+	ch.cronLock.Unlock()
 
 
-	if fullSubDownloadProcessing == true {
-		if ch.dh != nil {
-			ch.dh.Cancel()
-		}
-		// Stop stops the cron scheduler if it is running; otherwise it does nothing.
-		// A context is returned so the caller can wait for running jobs to complete.
-		nowContext := ch.c.Stop()
-		select {
-		case <-time.After(5 * time.Minute):
-			ch.log.Warningln("Wait over 5 min, CronHelper is timeout")
-		case <-nowContext.Done():
-			ch.log.Infoln("CronHelper.Stop() Done.")
-		}
-	} else {
-		// Stop stops the cron scheduler if it is running; otherwise it does nothing.
-		// A context is returned so the caller can wait for running jobs to complete.
-		nowContext := ch.c.Stop()
-		select {
-		case <-time.After(5 * time.Second):
-			ch.log.Warningln("Wait over 5 s, CronHelper is timeout")
-		case <-nowContext.Done():
-			ch.log.Infoln("CronHelper.Stop() Done.")
-		}
+	if cronHelperRunning == false {
+		return
 	}
 	}
 
 
-	ch.cronHelperRunningLock.Lock()
+	ch.cronLock.Lock()
+	if ch.stopping == true {
+		ch.cronLock.Unlock()
+		return
+	}
+	ch.stopping = true
+	ch.cronLock.Unlock()
+
+	ch.downloader.Cancel()
+	// Stop stops the cron scheduler if it is running; otherwise it does nothing.
+	// A context is returned so the caller can wait for running jobs to complete.
+	nowContext := ch.c.Stop()
+	select {
+	case <-time.After(5 * time.Minute):
+		ch.log.Warningln("Wait over 5 min, CronHelper is timeout")
+	case <-nowContext.Done():
+		ch.log.Infoln("CronHelper.Stop() Done.")
+	}
+
+	ch.cronLock.Lock()
 	ch.cronHelperRunning = false
 	ch.cronHelperRunning = false
-	ch.cronHelperRunningLock.Unlock()
+	ch.cronLock.Unlock()
 
 
 	common.SetSubScanJobStatusNil()
 	common.SetSubScanJobStatusNil()
 }
 }
@@ -132,56 +171,47 @@ func (ch *CronHelper) Stop() {
 func (ch *CronHelper) CronHelperRunning() bool {
 func (ch *CronHelper) CronHelperRunning() bool {
 
 
 	defer func() {
 	defer func() {
-		ch.cronHelperRunningLock.Unlock()
+		ch.cronLock.Unlock()
 	}()
 	}()
-	ch.cronHelperRunningLock.Lock()
+	ch.cronLock.Lock()
 	return ch.cronHelperRunning
 	return ch.cronHelperRunning
 }
 }
 
 
-func (ch *CronHelper) CronRunningStatusString() string {
-	if ch.CronHelperRunning() == true {
-		return Running
-	} else {
-		return Stopped
-	}
-}
-
-func (ch *CronHelper) FullDownloadProcessRunning() bool {
+func (ch *CronHelper) CronHelperStopping() bool {
 
 
 	defer func() {
 	defer func() {
-		ch.fullSubDownloadProcessingLock.Unlock()
+		ch.cronLock.Unlock()
 	}()
 	}()
-	ch.fullSubDownloadProcessingLock.Lock()
-	return ch.fullSubDownloadProcessing
+	ch.cronLock.Lock()
+	return ch.stopping
 }
 }
 
 
-func (ch *CronHelper) FullDownloadProcessRunningStatusString() string {
-	if ch.FullDownloadProcessRunning() == true {
+func (ch *CronHelper) CronRunningStatusString() string {
+
+	if ch.CronHelperRunning() == true {
+		if ch.CronHelperStopping() == true {
+			return Stopping
+		}
 		return Running
 		return Running
 	} else {
 	} else {
 		return Stopped
 		return Stopped
 	}
 	}
 }
 }
 
 
-// coreSubDownloadProcess 执行一次下载任务的多个步骤
-func (ch *CronHelper) coreSubDownloadProcess() {
+// scanVideoProcess 定时执行的视频扫描任务,提交给任务队列,然后由额外的下载者线程去取队列中的任务下载
+func (ch *CronHelper) scanVideoProcess() {
 
 
 	defer func() {
 	defer func() {
-		ch.fullSubDownloadProcessingLock.Lock()
-		ch.fullSubDownloadProcessing = false
-		ch.fullSubDownloadProcessingLock.Unlock()
+		ch.cronLock.Lock()
+		ch.cronLock.Unlock()
 
 
 		ch.log.Infoln(log_helper.OnceSubsScanEnd)
 		ch.log.Infoln(log_helper.OnceSubsScanEnd)
 
 
 		// 下载完后,应该继续是等待
 		// 下载完后,应该继续是等待
-		tttt := ch.c.Entries()[0].Next.Format("2006-01-02 15:04:05")
+		tttt := ch.c.Entry(ch.entryIDScanVideoProcess).Next.Format("2006-01-02 15:04:05")
 		common.SetSubScanJobStatusWaiting(tttt)
 		common.SetSubScanJobStatusWaiting(tttt)
 	}()
 	}()
 
 
-	ch.fullSubDownloadProcessingLock.Lock()
-	ch.fullSubDownloadProcessing = true
-	ch.fullSubDownloadProcessingLock.Unlock()
-
 	// ------------------------------------------------------------------------
 	// ------------------------------------------------------------------------
 	// 如果是 Debug 模式,那么就需要写入特殊文件
 	// 如果是 Debug 模式,那么就需要写入特殊文件
 	if ch.sets.AdvancedSettings.DebugMode == true {
 	if ch.sets.AdvancedSettings.DebugMode == true {
@@ -203,33 +233,31 @@ func (ch *CronHelper) coreSubDownloadProcess() {
 
 
 	// 扫描字幕任务开始,先是扫描阶段,那么是拿不到有多少视频需要扫描的数量的
 	// 扫描字幕任务开始,先是扫描阶段,那么是拿不到有多少视频需要扫描的数量的
 	common.SetSubScanJobStatusPreparing(time.Now().Format("2006-01-02 15:04:05"))
 	common.SetSubScanJobStatusPreparing(time.Now().Format("2006-01-02 15:04:05"))
+	// ----------------------------------------------------------------------------------------
+	// ----------------------------------------------------------------------------------------
+	// 扫描有那些视频需要下载字幕,放入队列中,然后会有下载者去这个队列取出来进行下载
+	videoScanAndRefreshHelper := video_scan_and_refresh_helper.NewVideoScanAndRefreshHelper(
+		ch.sets,
+		ch.log,
+		ch.downloadQueue)
 
 
-	// 下载前的初始化
-	preDownloadProcess := pre_download_process.NewPreDownloadProcess(ch.log, ch.sets)
-	err := preDownloadProcess.
-		Init().
-		Check().
-		HotFix().
-		ChangeSubNameFormat().
-		ReloadBrowser().
-		Wait()
+	ch.log.Infoln("Video Scan Started...")
+	// 先进行扫描
+	scanResult, err := videoScanAndRefreshHelper.ScanMovieAndSeriesWait2DownloadSub()
 	if err != nil {
 	if err != nil {
-		ch.log.Errorln("pre_download_process", "Error:", err)
-		ch.log.Errorln("Skip DownloaderHelper.Start()")
+		ch.log.Errorln("ScanMovieAndSeriesWait2DownloadSub", err)
 		return
 		return
 	}
 	}
-	// 开始下载
-	ch.dh = downloader_helper.NewDownloaderHelper(settings.GetSettings(true),
-		ch.log,
-		preDownloadProcess.SubSupplierHub)
-	err = ch.dh.Start()
+	// 过滤出需要下载的视频有那些,并放入队列中
+	err = videoScanAndRefreshHelper.FilterMovieAndSeriesNeedDownload(scanResult)
 	if err != nil {
 	if err != nil {
-		ch.log.Errorln("downloader_helper.Start()", "Error:", err)
+		ch.log.Errorln("FilterMovieAndSeriesNeedDownload", err)
 		return
 		return
 	}
 	}
 }
 }
 
 
 const (
 const (
-	Stopped = "stopped"
-	Running = "running"
+	Stopped  = "stopped"
+	Running  = "running"
+	Stopping = "stopping"
 )
 )

+ 0 - 106
internal/logic/downloader_helper/downloader_helper.go

@@ -1,106 +0,0 @@
-package downloader_helper
-
-import (
-	subSupplier "github.com/allanpk716/ChineseSubFinder/internal/logic/sub_supplier"
-	"github.com/allanpk716/ChineseSubFinder/internal/pkg/downloader"
-	"github.com/allanpk716/ChineseSubFinder/internal/pkg/global_value"
-	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_folder"
-	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
-	"github.com/allanpk716/ChineseSubFinder/internal/pkg/notify_center"
-	"github.com/allanpk716/ChineseSubFinder/internal/pkg/rod_helper"
-	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
-	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_formatter"
-	"github.com/sirupsen/logrus"
-	"time"
-)
-
-type DownloaderHelper struct {
-	subSupplierHub *subSupplier.SubSupplierHub
-	downloader     *downloader.Downloader
-	settings       *settings.Settings
-	logger         *logrus.Logger
-}
-
-func NewDownloaderHelper(settings *settings.Settings, logger *logrus.Logger, _subSupplierHub *subSupplier.SubSupplierHub) *DownloaderHelper {
-	return &DownloaderHelper{
-		subSupplierHub: _subSupplierHub,
-		settings:       settings,
-		logger:         logger,
-	}
-}
-
-// Start 开启任务
-func (d *DownloaderHelper) Start() error {
-	var err error
-	// 下载实例
-	d.downloader, err = downloader.NewDownloader(d.subSupplierHub, sub_formatter.GetSubFormatter(d.settings.AdvancedSettings.SubNameFormatter), d.settings, d.logger)
-	if err != nil {
-		d.logger.Errorln("NewDownloader", err)
-	}
-	// 最后的清理和通知统计
-	defer func() {
-		d.logger.Infoln("Download One End...")
-		notify_center.Notify.Send()
-		my_util.CloseChrome(d.logger)
-		rod_helper.Clear()
-	}()
-
-	d.logger.Infoln("Download One Started...")
-
-	// 优先级最高。读取特殊文件,启用一些特殊的功能,比如 forced_scan_and_down_sub
-	err = d.downloader.ReadSpeFile()
-	if err != nil {
-		d.logger.Errorln("ReadSpeFile", err)
-	}
-	// 从 csf-bk 文件还原时间轴修复前的字幕文件
-	if d.downloader.NeedRestoreFixTimeLineBK == true {
-		err = d.downloader.RestoreFixTimelineBK()
-		if err != nil {
-			d.logger.Errorln("RestoreFixTimelineBK", err)
-		}
-	}
-	// 先进行扫描
-	scanResult, err := d.downloader.ScanMovieAndSeriesWait2DownloadSub()
-	if err != nil {
-		d.logger.Errorln("ScanMovieAndSeriesWait2DownloadSub", err)
-		return err
-	}
-	// 过滤出需要下载的视频有那些,并放入队列中
-	err = d.downloader.FilterMovieAndSeriesNeedDownload(scanResult)
-	if err != nil {
-		d.logger.Errorln("FilterMovieAndSeriesNeedDownload", err)
-		return err
-	}
-	// 开始下载,电影
-	err = d.downloader.DownloadSub4Movie()
-	if err != nil {
-		d.logger.Errorln("DownloadSub4Movie", err)
-		return err
-	}
-	// 开始下载,连续剧
-	err = d.downloader.DownloadSub4Series()
-	if err != nil {
-		d.logger.Errorln("DownloadSub4Series", err)
-		return err
-	}
-	// 刷新 Emby 的字幕,下载完毕字幕了,就统一刷新一下
-	err = d.downloader.RefreshEmbySubList()
-	if err != nil {
-		d.logger.Errorln("RefreshEmbySubList", err)
-		return err
-	}
-	d.logger.Infoln("Will Scan SubFixCache Folder, Clear files that are more than 7 * 24 hours old")
-	// 清理多天没有使用的时间轴字幕校正缓存文件
-	err = my_folder.ClearIdleSubFixCacheFolder(d.logger, global_value.DefSubFixCacheFolder(), 7*24*time.Hour)
-	if err != nil {
-		d.logger.Errorln("ClearIdleSubFixCacheFolder", err)
-		return err
-	}
-
-	return nil
-}
-
-// Cancel 提前取消任务的执行
-func (d *DownloaderHelper) Cancel() {
-	d.downloader.Cancel()
-}

+ 2 - 23
internal/logic/movie_helper/moviehelper.go

@@ -20,34 +20,13 @@ import (
 )
 )
 
 
 // OneMovieDlSubInAllSite 一部电影在所有的网站下载相应的字幕
 // OneMovieDlSubInAllSite 一部电影在所有的网站下载相应的字幕
-func OneMovieDlSubInAllSite(Suppliers []ifaces.ISupplier, oneVideoFullPath string, i int) []supplier.SubInfo {
+func OneMovieDlSubInAllSite(Suppliers []ifaces.ISupplier, oneVideoFullPath string, i int64) []supplier.SubInfo {
 
 
 	defer func() {
 	defer func() {
 		log_helper.GetLogger().Infoln(common.QueueName, i, "DlSub End", oneVideoFullPath)
 		log_helper.GetLogger().Infoln(common.QueueName, i, "DlSub End", oneVideoFullPath)
 	}()
 	}()
 
 
 	var outSUbInfos = make([]supplier.SubInfo, 0)
 	var outSUbInfos = make([]supplier.SubInfo, 0)
-	// TODO 资源占用较高,把这里的并发给取消
-	//// 同时进行查询
-	//subInfosChannel := make(chan []supplier.SubInfo)
-	//log_helper.GetLogger().Infoln(common.QueueName, i, "DlSub Start", oneVideoFullPath)
-	//for _, oneSupplier := range Suppliers {
-	//	nowSupplier := oneSupplier
-	//	go func() {
-	//		subInfos, err := OneMovieDlSubInOneSite(oneVideoFullPath, i, nowSupplier)
-	//		if err != nil {
-	//			log_helper.GetLogger().Errorln(common.QueueName, i, nowSupplier.GetSupplierName(), "oneMovieDlSubInOneSite", err)
-	//		}
-	//		subInfosChannel <- subInfos
-	//	}()
-	//}
-	//for index := 0; index < len(Suppliers); index++ {
-	//	v, ok := <-subInfosChannel
-	//	if ok == true && v != nil {
-	//		outSUbInfos = append(outSUbInfos, v...)
-	//	}
-	//}
-
 	log_helper.GetLogger().Infoln(common.QueueName, i, "DlSub Start", oneVideoFullPath)
 	log_helper.GetLogger().Infoln(common.QueueName, i, "DlSub Start", oneVideoFullPath)
 	for _, oneSupplier := range Suppliers {
 	for _, oneSupplier := range Suppliers {
 
 
@@ -69,7 +48,7 @@ func OneMovieDlSubInAllSite(Suppliers []ifaces.ISupplier, oneVideoFullPath strin
 }
 }
 
 
 // OneMovieDlSubInOneSite 一部电影在一个站点下载字幕
 // OneMovieDlSubInOneSite 一部电影在一个站点下载字幕
-func OneMovieDlSubInOneSite(oneVideoFullPath string, i int, supplier ifaces.ISupplier) ([]supplier.SubInfo, error) {
+func OneMovieDlSubInOneSite(oneVideoFullPath string, i int64, supplier ifaces.ISupplier) ([]supplier.SubInfo, error) {
 	defer func() {
 	defer func() {
 		log_helper.GetLogger().Infoln(common.QueueName, i, supplier.GetSupplierName(), "End...")
 		log_helper.GetLogger().Infoln(common.QueueName, i, supplier.GetSupplierName(), "End...")
 	}()
 	}()

+ 2 - 90
internal/logic/pre_download_process/pre_download_proces.go

@@ -8,17 +8,12 @@ import (
 	"github.com/allanpk716/ChineseSubFinder/internal/logic/sub_supplier/subhd"
 	"github.com/allanpk716/ChineseSubFinder/internal/logic/sub_supplier/subhd"
 	"github.com/allanpk716/ChineseSubFinder/internal/logic/sub_supplier/xunlei"
 	"github.com/allanpk716/ChineseSubFinder/internal/logic/sub_supplier/xunlei"
 	"github.com/allanpk716/ChineseSubFinder/internal/logic/sub_supplier/zimuku"
 	"github.com/allanpk716/ChineseSubFinder/internal/logic/sub_supplier/zimuku"
-	"github.com/allanpk716/ChineseSubFinder/internal/pkg/hot_fix"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_folder"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_folder"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/notify_center"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/notify_center"
-	"github.com/allanpk716/ChineseSubFinder/internal/pkg/rod_helper"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/something_static"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/something_static"
-	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_formatter"
-	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_formatter/common"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/url_connectedness_helper"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/url_connectedness_helper"
-	"github.com/allanpk716/ChineseSubFinder/internal/types"
 	common2 "github.com/allanpk716/ChineseSubFinder/internal/types/common"
 	common2 "github.com/allanpk716/ChineseSubFinder/internal/types/common"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 	"time"
 	"time"
@@ -180,86 +175,6 @@ func (p *PreDownloadProcess) Check() *PreDownloadProcess {
 	return p
 	return p
 }
 }
 
 
-func (p *PreDownloadProcess) HotFix() *PreDownloadProcess {
-
-	if p.gError != nil {
-		p.log.Infoln("Skip PreDownloadProcess.Check()")
-		return p
-	}
-	p.stageName = stageNameCHotFix
-
-	defer func() {
-		p.log.Infoln("PreDownloadProcess.HotFix() End")
-	}()
-	p.log.Infoln("PreDownloadProcess.HotFix() Start...")
-	// ------------------------------------------------------------------------
-	// 开始修复
-	p.log.Infoln(common2.NotifyStringTellUserWait)
-	err := hot_fix.HotFixProcess(types.HotFixParam{
-		MovieRootDirs:  p.sets.CommonSettings.MoviePaths,
-		SeriesRootDirs: p.sets.CommonSettings.SeriesPaths,
-	})
-	if err != nil {
-		p.log.Errorln("hot_fix.HotFixProcess()", err)
-		p.gError = err
-		return p
-	}
-
-	return p
-}
-
-func (p *PreDownloadProcess) ChangeSubNameFormat() *PreDownloadProcess {
-
-	if p.gError != nil {
-		p.log.Infoln("Skip PreDownloadProcess.ChangeSubNameFormat()")
-		return p
-	}
-	p.stageName = stageNameChangeSubNameFormat
-	defer func() {
-		p.log.Infoln("PreDownloadProcess.ChangeSubNameFormat() End")
-	}()
-	p.log.Infoln("PreDownloadProcess.ChangeSubNameFormat() Start...")
-	// ------------------------------------------------------------------------
-	/*
-		字幕命名格式转换,需要数据库支持
-		如果数据库没有记录经过转换,那么默认从 Emby 的格式作为检测的起点,转换到目标的格式
-		然后需要在数据库中记录本次的转换结果
-	*/
-	p.log.Infoln(common2.NotifyStringTellUserWait)
-	renameResults, err := sub_formatter.SubFormatChangerProcess(
-		p.sets.CommonSettings.MoviePaths,
-		p.sets.CommonSettings.SeriesPaths,
-		common.FormatterName(p.sets.AdvancedSettings.SubNameFormatter))
-	// 出错的文件有哪一些
-	for s, i := range renameResults.ErrFiles {
-		p.log.Errorln("reformat ErrFile:"+s, i)
-	}
-	if err != nil {
-		p.log.Errorln("SubFormatChangerProcess() Error", err)
-		p.gError = err
-		return p
-	}
-
-	return p
-}
-
-func (p *PreDownloadProcess) ReloadBrowser() *PreDownloadProcess {
-
-	if p.gError != nil {
-		p.log.Infoln("Skip PreDownloadProcess.ReloadBrowser()")
-		return p
-	}
-	p.stageName = stageNameReloadBrowser
-	defer func() {
-		p.log.Infoln("PreDownloadProcess.ReloadBrowser() End")
-	}()
-	p.log.Infoln("PreDownloadProcess.ReloadBrowser() Start...")
-	// ------------------------------------------------------------------------
-	// ReloadBrowser 提前把浏览器下载好
-	rod_helper.ReloadBrowser()
-	return p
-}
-
 func (p *PreDownloadProcess) Wait() error {
 func (p *PreDownloadProcess) Wait() error {
 	defer func() {
 	defer func() {
 		p.log.Infoln("PreDownloadProcess.Wait() Done.")
 		p.log.Infoln("PreDownloadProcess.Wait() Done.")
@@ -274,9 +189,6 @@ func (p *PreDownloadProcess) Wait() error {
 }
 }
 
 
 const (
 const (
-	stageNameInit                = "Init"
-	stageNameCheck               = "Check"
-	stageNameCHotFix             = "HotFix"
-	stageNameChangeSubNameFormat = "ChangeSubNameFormat"
-	stageNameReloadBrowser       = "ReloadBrowser"
+	stageNameInit  = "Init"
+	stageNameCheck = "Check"
 )
 )

+ 124 - 0
internal/logic/pre_job/pro_job.go

@@ -0,0 +1,124 @@
+package pre_job
+
+import (
+	"errors"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/hot_fix"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/rod_helper"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_formatter"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_formatter/common"
+	"github.com/allanpk716/ChineseSubFinder/internal/types"
+	common2 "github.com/allanpk716/ChineseSubFinder/internal/types/common"
+	"github.com/sirupsen/logrus"
+)
+
+type PreJob struct {
+	stageName string
+	gError    error
+
+	sets *settings.Settings
+	log  *logrus.Logger
+}
+
+func NewPreJob(sets *settings.Settings, log *logrus.Logger) *PreJob {
+	return &PreJob{sets: sets, log: log}
+}
+
+func (p *PreJob) HotFix() *PreJob {
+
+	if p.gError != nil {
+		p.log.Infoln("Skip PreJob.Check()")
+		return p
+	}
+	p.stageName = stageNameCHotFix
+
+	defer func() {
+		p.log.Infoln("PreJob.HotFix() End")
+	}()
+	p.log.Infoln("PreJob.HotFix() Start...")
+	// ------------------------------------------------------------------------
+	// 开始修复
+	p.log.Infoln(common2.NotifyStringTellUserWait)
+	err := hot_fix.HotFixProcess(types.HotFixParam{
+		MovieRootDirs:  p.sets.CommonSettings.MoviePaths,
+		SeriesRootDirs: p.sets.CommonSettings.SeriesPaths,
+	})
+	if err != nil {
+		p.log.Errorln("hot_fix.HotFixProcess()", err)
+		p.gError = err
+		return p
+	}
+
+	return p
+}
+
+func (p *PreJob) ChangeSubNameFormat() *PreJob {
+
+	if p.gError != nil {
+		p.log.Infoln("Skip PreJob.ChangeSubNameFormat()")
+		return p
+	}
+	p.stageName = stageNameChangeSubNameFormat
+	defer func() {
+		p.log.Infoln("PreJob.ChangeSubNameFormat() End")
+	}()
+	p.log.Infoln("PreJob.ChangeSubNameFormat() Start...")
+	// ------------------------------------------------------------------------
+	/*
+		字幕命名格式转换,需要数据库支持
+		如果数据库没有记录经过转换,那么默认从 Emby 的格式作为检测的起点,转换到目标的格式
+		然后需要在数据库中记录本次的转换结果
+	*/
+	p.log.Infoln(common2.NotifyStringTellUserWait)
+	renameResults, err := sub_formatter.SubFormatChangerProcess(
+		p.sets.CommonSettings.MoviePaths,
+		p.sets.CommonSettings.SeriesPaths,
+		common.FormatterName(p.sets.AdvancedSettings.SubNameFormatter))
+	// 出错的文件有哪一些
+	for s, i := range renameResults.ErrFiles {
+		p.log.Errorln("reformat ErrFile:"+s, i)
+	}
+	if err != nil {
+		p.log.Errorln("SubFormatChangerProcess() Error", err)
+		p.gError = err
+		return p
+	}
+
+	return p
+}
+
+func (p *PreJob) ReloadBrowser() *PreJob {
+
+	if p.gError != nil {
+		p.log.Infoln("Skip PreJob.ReloadBrowser()")
+		return p
+	}
+	p.stageName = stageNameReloadBrowser
+	defer func() {
+		p.log.Infoln("PreJob.ReloadBrowser() End")
+	}()
+	p.log.Infoln("PreJob.ReloadBrowser() Start...")
+	// ------------------------------------------------------------------------
+	// ReloadBrowser 提前把浏览器下载好
+	rod_helper.ReloadBrowser()
+	return p
+}
+
+func (p *PreJob) Wait() error {
+	defer func() {
+		p.log.Infoln("PreJob.Wait() Done.")
+	}()
+	if p.gError != nil {
+		outErrString := "PreJob.Wait() Get Error, " + "stageName:" + p.stageName + " -- " + p.gError.Error()
+		p.log.Errorln(outErrString)
+		return errors.New(outErrString)
+	} else {
+		return nil
+	}
+}
+
+const (
+	stageNameCHotFix             = "HotFix"
+	stageNameChangeSubNameFormat = "ChangeSubNameFormat"
+	stageNameReloadBrowser       = "ReloadBrowser"
+)

+ 1 - 1
internal/logic/scan_played_video_subinfo/scan_played_video_subinfo.go

@@ -305,7 +305,7 @@ func (s *ScanPlayedVideoSubInfo) dealOneVideo(index int, videoFPath, orgSubFPath
 	// 先把 IMDB 信息查询查来,不管是从数据库还是网络(查询出来也得写入到数据库)
 	// 先把 IMDB 信息查询查来,不管是从数据库还是网络(查询出来也得写入到数据库)
 	if imdbInfo, ok = imdbInfoCache[imdbInfo4Video.ImdbId]; ok == false {
 	if imdbInfo, ok = imdbInfoCache[imdbInfo4Video.ImdbId]; ok == false {
 		// 不存在,那么就去查询和新建缓存
 		// 不存在,那么就去查询和新建缓存
-		imdbInfo, err = imdb_helper.GetVideoIMDBInfoFromLocal(imdbInfo4Video.ImdbId, *s.settings.AdvancedSettings.ProxySettings)
+		imdbInfo, err = imdb_helper.GetVideoIMDBInfoFromLocal(imdbInfo4Video.ImdbId, s.settings.AdvancedSettings.ProxySettings)
 		if err != nil {
 		if err != nil {
 			s.log.Warningln("ScanPlayedVideoSubInfo.Scan", videoTypes, ".GetVideoIMDBInfoFromLocal", videoFPath, err)
 			s.log.Warningln("ScanPlayedVideoSubInfo.Scan", videoTypes, ".GetVideoIMDBInfoFromLocal", videoFPath, err)
 			return
 			return

+ 1 - 1
internal/logic/series_helper/seriesHelper.go

@@ -153,7 +153,7 @@ func SkipChineseSeries(seriesRootPath string, _proxySettings ...*settings.ProxyS
 }
 }
 
 
 // DownloadSubtitleInAllSiteByOneSeries 一部连续剧,在所有的网站,下载相应的字幕
 // DownloadSubtitleInAllSiteByOneSeries 一部连续剧,在所有的网站,下载相应的字幕
-func DownloadSubtitleInAllSiteByOneSeries(Suppliers []ifaces.ISupplier, seriesInfo *series.SeriesInfo, i int) []supplier.SubInfo {
+func DownloadSubtitleInAllSiteByOneSeries(Suppliers []ifaces.ISupplier, seriesInfo *series.SeriesInfo, i int64) []supplier.SubInfo {
 
 
 	defer func() {
 	defer func() {
 		log_helper.GetLogger().Infoln(i, "DlSub End", seriesInfo.DirPath)
 		log_helper.GetLogger().Infoln(i, "DlSub End", seriesInfo.DirPath)

+ 6 - 3
internal/logic/sub_supplier/subSupplierHub.go

@@ -134,10 +134,13 @@ func (d *SubSupplierHub) SeriesNeedDlSubFromEmby(seriesRootPath string, seriesVi
 }
 }
 
 
 // DownloadSub4Movie 某一个电影字幕下载,下载完毕后,返回下载缓存每个字幕的位置,这里将只关心下载字幕,判断是否在时间范围内要不要下载不在这里判断,包括是否是中文视频的问题
 // DownloadSub4Movie 某一个电影字幕下载,下载完毕后,返回下载缓存每个字幕的位置,这里将只关心下载字幕,判断是否在时间范围内要不要下载不在这里判断,包括是否是中文视频的问题
-func (d *SubSupplierHub) DownloadSub4Movie(videoFullPath string, index int) ([]string, error) {
+func (d *SubSupplierHub) DownloadSub4Movie(videoFullPath string, index int64) ([]string, error) {
 
 
 	// 下载所有字幕
 	// 下载所有字幕
 	subInfos := movieHelper.OneMovieDlSubInAllSite(d.Suppliers, videoFullPath, index)
 	subInfos := movieHelper.OneMovieDlSubInAllSite(d.Suppliers, videoFullPath, index)
+	if subInfos == nil {
+		return nil, nil
+	}
 	// 整理字幕,比如解压什么的
 	// 整理字幕,比如解压什么的
 	organizeSubFiles, err := sub_helper.OrganizeDlSubFiles(filepath.Base(videoFullPath), subInfos)
 	organizeSubFiles, err := sub_helper.OrganizeDlSubFiles(filepath.Base(videoFullPath), subInfos)
 	if err != nil {
 	if err != nil {
@@ -157,7 +160,7 @@ func (d *SubSupplierHub) DownloadSub4Movie(videoFullPath string, index int) ([]s
 }
 }
 
 
 // DownloadSub4Series 某一部连续剧的字幕下载,下载完毕后,返回下载缓存每个字幕的位置(通用的下载逻辑,前面把常规(没有媒体服务器模式)和 Emby 这样的模式都转换到想到的下载接口上
 // DownloadSub4Series 某一部连续剧的字幕下载,下载完毕后,返回下载缓存每个字幕的位置(通用的下载逻辑,前面把常规(没有媒体服务器模式)和 Emby 这样的模式都转换到想到的下载接口上
-func (d *SubSupplierHub) DownloadSub4Series(seriesDirPath string, seriesInfo *series.SeriesInfo, index int) (map[string][]string, error) {
+func (d *SubSupplierHub) DownloadSub4Series(seriesDirPath string, seriesInfo *series.SeriesInfo, index int64) (map[string][]string, error) {
 
 
 	organizeSubFiles, err := d.dlSubFromSeriesInfo(seriesDirPath, index, seriesInfo)
 	organizeSubFiles, err := d.dlSubFromSeriesInfo(seriesDirPath, index, seriesInfo)
 	if err != nil {
 	if err != nil {
@@ -210,7 +213,7 @@ func (d *SubSupplierHub) CheckSubSiteStatus() backend.ReplyCheckStatus {
 	return outStatus
 	return outStatus
 }
 }
 
 
-func (d *SubSupplierHub) dlSubFromSeriesInfo(seriesDirPath string, index int, seriesInfo *series.SeriesInfo) (map[string][]string, error) {
+func (d *SubSupplierHub) dlSubFromSeriesInfo(seriesDirPath string, index int64, seriesInfo *series.SeriesInfo) (map[string][]string, error) {
 	// 下载好的字幕
 	// 下载好的字幕
 	subInfos := seriesHelper.DownloadSubtitleInAllSiteByOneSeries(d.Suppliers, seriesInfo, index)
 	subInfos := seriesHelper.DownloadSubtitleInAllSiteByOneSeries(d.Suppliers, seriesInfo, index)
 	// 整理字幕,比如解压什么的
 	// 整理字幕,比如解压什么的

+ 104 - 8
internal/logic/task_queue/task_queue.go

@@ -1,9 +1,11 @@
 package task_queue
 package task_queue
 
 
 import (
 import (
+	"errors"
 	"fmt"
 	"fmt"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/task_queue"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/task_queue"
+	taskQueue2 "github.com/allanpk716/ChineseSubFinder/internal/types/task_queue"
 	"github.com/dgraph-io/badger/v3"
 	"github.com/dgraph-io/badger/v3"
 	"github.com/emirpasic/gods/maps/treemap"
 	"github.com/emirpasic/gods/maps/treemap"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
@@ -77,6 +79,26 @@ func (t *TaskQueue) Size() int {
 	return t.taskKeyMap.Size()
 	return t.taskKeyMap.Size()
 }
 }
 
 
+func (t *TaskQueue) checkPriority(oneJob taskQueue2.OneJob) taskQueue2.OneJob {
+
+	if oneJob.TaskPriority > taskPriorityCount {
+		oneJob.TaskPriority = taskPriorityCount
+	}
+
+	if oneJob.TaskPriority < 0 {
+		oneJob.TaskPriority = 0
+	}
+
+	return oneJob
+}
+
+func (t *TaskQueue) degrade(oneJob taskQueue2.OneJob) taskQueue2.OneJob {
+
+	oneJob.TaskPriority -= 1
+
+	return t.checkPriority(oneJob)
+}
+
 // Add 放入元素,放入的时候会根据 TaskPriority 进行归类,存在的不会新增和更新
 // Add 放入元素,放入的时候会根据 TaskPriority 进行归类,存在的不会新增和更新
 func (t *TaskQueue) Add(oneJob task_queue.OneJob) (bool, error) {
 func (t *TaskQueue) Add(oneJob task_queue.OneJob) (bool, error) {
 
 
@@ -86,6 +108,8 @@ func (t *TaskQueue) Add(oneJob task_queue.OneJob) (bool, error) {
 	if t.isExist(oneJob.Id) == true {
 	if t.isExist(oneJob.Id) == true {
 		return false, nil
 		return false, nil
 	}
 	}
+	// 检查权限范围
+	oneJob = t.checkPriority(oneJob)
 	// 插入到统一的 KeyMap
 	// 插入到统一的 KeyMap
 	t.taskKeyMap.Put(oneJob.Id, oneJob.TaskPriority)
 	t.taskKeyMap.Put(oneJob.Id, oneJob.TaskPriority)
 	// 分配到具体的优先级 map 中
 	// 分配到具体的优先级 map 中
@@ -113,6 +137,8 @@ func (t *TaskQueue) Update(oneJob task_queue.OneJob) (bool, error) {
 	// 这里需要判断是否有优先级的 Update,如果有就需要把之前缓存的表给更新
 	// 这里需要判断是否有优先级的 Update,如果有就需要把之前缓存的表给更新
 	// 然后再插入到新的表中
 	// 然后再插入到新的表中
 	taskPriorityIndex, _ := t.taskKeyMap.Get(oneJob.Id)
 	taskPriorityIndex, _ := t.taskKeyMap.Get(oneJob.Id)
+	// 检查权限范围
+	oneJob = t.checkPriority(oneJob)
 	if oneJob.TaskPriority != taskPriorityIndex {
 	if oneJob.TaskPriority != taskPriorityIndex {
 		// 优先级修改
 		// 优先级修改
 		// 先删除原有的优先级
 		// 先删除原有的优先级
@@ -134,8 +160,58 @@ func (t *TaskQueue) Update(oneJob task_queue.OneJob) (bool, error) {
 	return true, nil
 	return true, nil
 }
 }
 
 
-// GetOneWaiting 获取一个元素,按优先级,0 - taskPriorityCount 的级别去拿去任务,不会移除任务
-func (t *TaskQueue) GetOneWaiting() (bool, task_queue.OneJob, error) {
+// AutoDetectUpdateJobStatus 根据任务的生命周期图,进行自动判断更新,见《任务的生命周期》流程图
+func (t *TaskQueue) AutoDetectUpdateJobStatus(oneJob task_queue.OneJob, inErr error) {
+
+	defer t.queueLock.Unlock()
+	t.queueLock.Lock()
+
+	// 检查权限范围
+	oneJob = t.checkPriority(oneJob)
+
+	if inErr == nil {
+		// 没有错误就是完成
+		oneJob.JobStatus = taskQueue2.Done
+	} else {
+		// 超过了时间限制,默认是 90 天, A.Before(B) : A < B
+		if oneJob.AddedTime.AddDate(0, 0, t.settings.AdvancedSettings.TaskQueue.ExpirationTime).Before(time.Now()) == true {
+			// 超过 90 天了
+			oneJob.JobStatus = taskQueue2.Failed
+		} else {
+			// 还在 90 天内
+			// 是否是首次,那么就看它的 Level 是否是在 5,然后 retry == 0
+			if oneJob.TaskPriority == DefaultTaskPriorityLevel && oneJob.RetryTimes == 0 {
+				// 需要重置到 L6
+				oneJob.RetryTimes = 0
+				oneJob.TaskPriority = FirstRetryTaskPriorityLevel
+			} else {
+				if oneJob.RetryTimes > t.settings.AdvancedSettings.TaskQueue.MaxRetryTimes {
+					// 超过重试次数会进行一次降级,然后重置这个次数
+					oneJob.RetryTimes = 0
+					oneJob = t.degrade(oneJob)
+				}
+			}
+
+			// 强制为 waiting
+			oneJob.JobStatus = taskQueue2.Waiting
+		}
+		// 传入的错误需要放进来
+		oneJob.ErrorInfo = inErr.Error()
+	}
+
+	bok, err := t.Update(oneJob)
+	if err != nil {
+		t.log.Errorln("AutoDetectUpdateJobStatus", oneJob.VideoFPath, err)
+		return
+	}
+	if bok == false {
+		t.log.Warningln("AutoDetectUpdateJobStatus ==", oneJob.VideoFPath, "Job.ID", oneJob.Id, "Not Found")
+		return
+	}
+}
+
+// GetOneWaitingJob 获取一个元素,按优先级,0 - taskPriorityCount 的级别去拿去任务,不会移除任务
+func (t *TaskQueue) GetOneWaitingJob() (bool, task_queue.OneJob, error) {
 
 
 	defer t.queueLock.Unlock()
 	defer t.queueLock.Unlock()
 	t.queueLock.Lock()
 	t.queueLock.Lock()
@@ -160,14 +236,14 @@ func (t *TaskQueue) GetOneWaiting() (bool, task_queue.OneJob, error) {
 		})
 		})
 
 
 		if found == true {
 		if found == true {
-			break
+			return true, tOneJob, nil
 		}
 		}
 	}
 	}
 
 
-	return true, tOneJob, nil
+	return false, tOneJob, nil
 }
 }
 
 
-func (t *TaskQueue) Get(status task_queue.JobStatus) (bool, []task_queue.OneJob, error) {
+func (t *TaskQueue) GetJobsByStatus(status task_queue.JobStatus) (bool, []task_queue.OneJob, error) {
 
 
 	defer t.queueLock.Unlock()
 	defer t.queueLock.Unlock()
 	t.queueLock.Lock()
 	t.queueLock.Lock()
@@ -194,7 +270,8 @@ func (t *TaskQueue) Get(status task_queue.JobStatus) (bool, []task_queue.OneJob,
 	return true, outOneJobs, nil
 	return true, outOneJobs, nil
 }
 }
 
 
-func (t *TaskQueue) GetTaskPriority(taskPriority int, status task_queue.JobStatus) (bool, []task_queue.OneJob, error) {
+// GetJobsByPriorityAndStatus 根据任务优先级和状态获取任务列表
+func (t *TaskQueue) GetJobsByPriorityAndStatus(taskPriority int, status task_queue.JobStatus) (bool, []task_queue.OneJob, error) {
 
 
 	defer t.queueLock.Unlock()
 	defer t.queueLock.Unlock()
 	t.queueLock.Lock()
 	t.queueLock.Lock()
@@ -315,14 +392,33 @@ func (t *TaskQueue) save(taskPriority int) error {
 	return nil
 	return nil
 }
 }
 
 
-// isExist 是否已经存在
+// isExist 是否已经存在,对内,无锁
 func (t *TaskQueue) isExist(jobID string) bool {
 func (t *TaskQueue) isExist(jobID string) bool {
 	_, bok := t.taskKeyMap.Get(jobID)
 	_, bok := t.taskKeyMap.Get(jobID)
 	return bok
 	return bok
 }
 }
 
 
+// IsExist 是否已经存在,对外,有锁
+func (t *TaskQueue) IsExist(jobID string) bool {
+
+	defer t.queueLock.Unlock()
+	t.queueLock.Lock()
+
+	_, bok := t.taskKeyMap.Get(jobID)
+	return bok
+}
+
+// isEmpty 对内,无锁
 func (t *TaskQueue) isEmpty() bool {
 func (t *TaskQueue) isEmpty() bool {
 	return t.taskKeyMap.Empty()
 	return t.taskKeyMap.Empty()
 }
 }
 
 
-const taskPriorityCount = 10
+const (
+	taskPriorityCount           = 10
+	DefaultTaskPriorityLevel    = 5
+	FirstRetryTaskPriorityLevel = 6
+)
+
+var (
+	ErrNotSubFound = errors.New("Not Sub Found")
+)

+ 27 - 26
internal/logic/task_queue/task_queue_test.go

@@ -1,11 +1,12 @@
 package task_queue
 package task_queue
 
 
 import (
 import (
+	"fmt"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/common"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/common"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/task_queue"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/task_queue"
-	"github.com/davecgh/go-spew/spew"
 	"testing"
 	"testing"
 )
 )
 
 
@@ -18,7 +19,7 @@ func TestTaskQueue_AddAndGetAndDel(t *testing.T) {
 
 
 	taskQueue := NewTaskQueue("testQueue", settings.NewSettings(), log_helper.GetLogger())
 	taskQueue := NewTaskQueue("testQueue", settings.NewSettings(), log_helper.GetLogger())
 	for i := taskPriorityCount; i >= 0; i-- {
 	for i := taskPriorityCount; i >= 0; i-- {
-		bok, err := taskQueue.Add(*task_queue.NewOneJob(common.Movie, "", i))
+		bok, err := taskQueue.Add(*task_queue.NewOneJob(common.Movie, my_util.RandStringBytesMaskImprSrcSB(10), i))
 		if err != nil {
 		if err != nil {
 			t.Fatal("TestTaskQueue.Add", err)
 			t.Fatal("TestTaskQueue.Add", err)
 		}
 		}
@@ -27,7 +28,7 @@ func TestTaskQueue_AddAndGetAndDel(t *testing.T) {
 		}
 		}
 	}
 	}
 
 
-	bok, waitingJobs, err := taskQueue.Get(task_queue.Waiting)
+	bok, waitingJobs, err := taskQueue.GetJobsByStatus(task_queue.Waiting)
 	if err != nil {
 	if err != nil {
 		t.Fatal("TestTaskQueue.Get", err)
 		t.Fatal("TestTaskQueue.Get", err)
 	}
 	}
@@ -70,7 +71,7 @@ func TestTaskQueue_AddAndClear(t *testing.T) {
 
 
 	taskQueue := NewTaskQueue("testQueue", settings.NewSettings(), log_helper.GetLogger())
 	taskQueue := NewTaskQueue("testQueue", settings.NewSettings(), log_helper.GetLogger())
 	for i := taskPriorityCount; i >= 0; i-- {
 	for i := taskPriorityCount; i >= 0; i-- {
-		bok, err := taskQueue.Add(*task_queue.NewOneJob(common.Movie, "", i))
+		bok, err := taskQueue.Add(*task_queue.NewOneJob(common.Movie, my_util.RandStringBytesMaskImprSrcSB(10), i))
 		if err != nil {
 		if err != nil {
 			t.Fatal("TestTaskQueue.Add", err)
 			t.Fatal("TestTaskQueue.Add", err)
 		}
 		}
@@ -98,7 +99,7 @@ func TestTaskQueue_Update(t *testing.T) {
 
 
 	taskQueue := NewTaskQueue("testQueue", settings.NewSettings(), log_helper.GetLogger())
 	taskQueue := NewTaskQueue("testQueue", settings.NewSettings(), log_helper.GetLogger())
 	for i := taskPriorityCount; i >= 0; i-- {
 	for i := taskPriorityCount; i >= 0; i-- {
-		bok, err := taskQueue.Add(*task_queue.NewOneJob(common.Movie, "", i))
+		bok, err := taskQueue.Add(*task_queue.NewOneJob(common.Movie, my_util.RandStringBytesMaskImprSrcSB(10), i))
 		if err != nil {
 		if err != nil {
 			t.Fatal("TestTaskQueue.Add", err)
 			t.Fatal("TestTaskQueue.Add", err)
 		}
 		}
@@ -107,7 +108,7 @@ func TestTaskQueue_Update(t *testing.T) {
 		}
 		}
 	}
 	}
 
 
-	bok, waitingJobs, err := taskQueue.Get(task_queue.Waiting)
+	bok, waitingJobs, err := taskQueue.GetJobsByStatus(task_queue.Waiting)
 	if err != nil {
 	if err != nil {
 		t.Fatal("TestTaskQueue.Get", err)
 		t.Fatal("TestTaskQueue.Get", err)
 	}
 	}
@@ -139,7 +140,7 @@ func TestTaskQueue_Update(t *testing.T) {
 		}
 		}
 	}
 	}
 
 
-	bok, commitedJobs, err := taskQueue.Get(task_queue.Committed)
+	bok, committedJobs, err := taskQueue.GetJobsByStatus(task_queue.Committed)
 	if err != nil {
 	if err != nil {
 		t.Fatal("TestTaskQueue.Get", err)
 		t.Fatal("TestTaskQueue.Get", err)
 	}
 	}
@@ -147,8 +148,8 @@ func TestTaskQueue_Update(t *testing.T) {
 		t.Fatal("TestTaskQueue.Get == false")
 		t.Fatal("TestTaskQueue.Get == false")
 	}
 	}
 
 
-	if len(commitedJobs) != taskPriorityCount+1 {
-		t.Fatal("len(commitedJobs) != taskPriorityCount")
+	if len(committedJobs) != taskPriorityCount+1 {
+		t.Fatal("len(committedJobs) != taskPriorityCount")
 	}
 	}
 }
 }
 
 
@@ -161,7 +162,7 @@ func TestTaskQueue_UpdateAdGetOneWaiting(t *testing.T) {
 
 
 	taskQueue := NewTaskQueue("testQueue", settings.NewSettings(), log_helper.GetLogger())
 	taskQueue := NewTaskQueue("testQueue", settings.NewSettings(), log_helper.GetLogger())
 	for i := taskPriorityCount; i >= 0; i-- {
 	for i := taskPriorityCount; i >= 0; i-- {
-		bok, err := taskQueue.Add(*task_queue.NewOneJob(common.Movie, spew.Sprintf("%d", i), i))
+		bok, err := taskQueue.Add(*task_queue.NewOneJob(common.Movie, fmt.Sprintf("%d", i), i))
 		if err != nil {
 		if err != nil {
 			t.Fatal("TestTaskQueue.Add", err)
 			t.Fatal("TestTaskQueue.Add", err)
 		}
 		}
@@ -170,12 +171,12 @@ func TestTaskQueue_UpdateAdGetOneWaiting(t *testing.T) {
 		}
 		}
 	}
 	}
 
 
-	bok, waitingJob, err := taskQueue.GetOneWaiting()
+	bok, waitingJob, err := taskQueue.GetOneWaitingJob()
 	if err != nil {
 	if err != nil {
-		t.Fatal("TestTaskQueue.GetOneWaiting", err)
+		t.Fatal("TestTaskQueue.GetOneWaitingJob", err)
 	}
 	}
 	if bok == false {
 	if bok == false {
-		t.Fatal("TestTaskQueue.GetOneWaiting == false")
+		t.Fatal("TestTaskQueue.GetOneWaitingJob == false")
 	}
 	}
 
 
 	if waitingJob.TaskPriority != 0 {
 	if waitingJob.TaskPriority != 0 {
@@ -191,12 +192,12 @@ func TestTaskQueue_UpdateAdGetOneWaiting(t *testing.T) {
 		t.Fatal("TestTaskQueue.Update == false")
 		t.Fatal("TestTaskQueue.Update == false")
 	}
 	}
 
 
-	bok, waitingJob, err = taskQueue.GetOneWaiting()
+	bok, waitingJob, err = taskQueue.GetOneWaitingJob()
 	if err != nil {
 	if err != nil {
-		t.Fatal("TestTaskQueue.GetOneWaiting", err)
+		t.Fatal("TestTaskQueue.GetOneWaitingJob", err)
 	}
 	}
 	if bok == false {
 	if bok == false {
-		t.Fatal("TestTaskQueue.GetOneWaiting == false")
+		t.Fatal("TestTaskQueue.GetOneWaitingJob == false")
 	}
 	}
 
 
 	if waitingJob.TaskPriority != 1 {
 	if waitingJob.TaskPriority != 1 {
@@ -213,7 +214,7 @@ func TestTaskQueue_UpdatePriority(t *testing.T) {
 
 
 	taskQueue := NewTaskQueue("testQueue", settings.NewSettings(), log_helper.GetLogger())
 	taskQueue := NewTaskQueue("testQueue", settings.NewSettings(), log_helper.GetLogger())
 	for i := taskPriorityCount; i >= 0; i-- {
 	for i := taskPriorityCount; i >= 0; i-- {
-		bok, err := taskQueue.Add(*task_queue.NewOneJob(common.Movie, spew.Sprintf("%d", i), i))
+		bok, err := taskQueue.Add(*task_queue.NewOneJob(common.Movie, fmt.Sprintf("%d", i), i))
 		if err != nil {
 		if err != nil {
 			t.Fatal("TestTaskQueue.Add", err)
 			t.Fatal("TestTaskQueue.Add", err)
 		}
 		}
@@ -222,12 +223,12 @@ func TestTaskQueue_UpdatePriority(t *testing.T) {
 		}
 		}
 	}
 	}
 
 
-	bok, waitingJob, err := taskQueue.GetOneWaiting()
+	bok, waitingJob, err := taskQueue.GetOneWaitingJob()
 	if err != nil {
 	if err != nil {
-		t.Fatal("TestTaskQueue.GetOneWaiting", err)
+		t.Fatal("TestTaskQueue.GetOneWaitingJob", err)
 	}
 	}
 	if bok == false {
 	if bok == false {
-		t.Fatal("TestTaskQueue.GetOneWaiting == false")
+		t.Fatal("TestTaskQueue.GetOneWaitingJob == false")
 	}
 	}
 
 
 	if waitingJob.TaskPriority != 0 {
 	if waitingJob.TaskPriority != 0 {
@@ -243,24 +244,24 @@ func TestTaskQueue_UpdatePriority(t *testing.T) {
 		t.Fatal("TestTaskQueue.Update == false")
 		t.Fatal("TestTaskQueue.Update == false")
 	}
 	}
 
 
-	bok, waitingJobs, err := taskQueue.GetTaskPriority(0, task_queue.Waiting)
+	bok, waitingJobs, err := taskQueue.GetJobsByPriorityAndStatus(0, task_queue.Waiting)
 	if err != nil {
 	if err != nil {
-		t.Fatal("TestTaskQueue.GetTaskPriority", err)
+		t.Fatal("TestTaskQueue.GetJobsByPriorityAndStatus", err)
 	}
 	}
 	if bok == false {
 	if bok == false {
-		t.Fatal("TestTaskQueue.GetTaskPriority == false")
+		t.Fatal("TestTaskQueue.GetJobsByPriorityAndStatus == false")
 	}
 	}
 
 
 	if len(waitingJobs) != 0 {
 	if len(waitingJobs) != 0 {
 		t.Fatal("len(waitingJobs) != 0")
 		t.Fatal("len(waitingJobs) != 0")
 	}
 	}
 
 
-	bok, waitingJobs, err = taskQueue.GetTaskPriority(1, task_queue.Waiting)
+	bok, waitingJobs, err = taskQueue.GetJobsByPriorityAndStatus(1, task_queue.Waiting)
 	if err != nil {
 	if err != nil {
-		t.Fatal("TestTaskQueue.GetTaskPriority", err)
+		t.Fatal("TestTaskQueue.GetJobsByPriorityAndStatus", err)
 	}
 	}
 	if bok == false {
 	if bok == false {
-		t.Fatal("TestTaskQueue.GetTaskPriority == false")
+		t.Fatal("TestTaskQueue.GetJobsByPriorityAndStatus == false")
 	}
 	}
 
 
 	if len(waitingJobs) != 2 {
 	if len(waitingJobs) != 2 {

+ 245 - 522
internal/pkg/downloader/downloader.go

@@ -4,71 +4,54 @@ import (
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"github.com/allanpk716/ChineseSubFinder/internal/ifaces"
 	"github.com/allanpk716/ChineseSubFinder/internal/ifaces"
-	embyHelper "github.com/allanpk716/ChineseSubFinder/internal/logic/emby_helper"
-	"github.com/allanpk716/ChineseSubFinder/internal/logic/forced_scan_and_down_sub"
 	markSystem "github.com/allanpk716/ChineseSubFinder/internal/logic/mark_system"
 	markSystem "github.com/allanpk716/ChineseSubFinder/internal/logic/mark_system"
-	"github.com/allanpk716/ChineseSubFinder/internal/logic/restore_fix_timeline_bk"
-	seriesHelper "github.com/allanpk716/ChineseSubFinder/internal/logic/series_helper"
+	"github.com/allanpk716/ChineseSubFinder/internal/logic/pre_download_process"
+	"github.com/allanpk716/ChineseSubFinder/internal/logic/series_helper"
 	subSupplier "github.com/allanpk716/ChineseSubFinder/internal/logic/sub_supplier"
 	subSupplier "github.com/allanpk716/ChineseSubFinder/internal/logic/sub_supplier"
 	"github.com/allanpk716/ChineseSubFinder/internal/logic/sub_timeline_fixer"
 	"github.com/allanpk716/ChineseSubFinder/internal/logic/sub_timeline_fixer"
 	"github.com/allanpk716/ChineseSubFinder/internal/logic/task_queue"
 	"github.com/allanpk716/ChineseSubFinder/internal/logic/task_queue"
-	pkgcommon "github.com/allanpk716/ChineseSubFinder/internal/pkg/common"
-	"github.com/allanpk716/ChineseSubFinder/internal/pkg/log_helper"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_folder"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_folder"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
 	subCommon "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_formatter/common"
 	subCommon "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_formatter/common"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_helper"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_helper"
-	subTimelineFixerPKG "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_timeline_fixer"
-	"github.com/allanpk716/ChineseSubFinder/internal/pkg/task_control"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/common"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/common"
-	"github.com/allanpk716/ChineseSubFinder/internal/types/emby"
-	"github.com/allanpk716/ChineseSubFinder/internal/types/series"
-	TTaskqueue "github.com/allanpk716/ChineseSubFinder/internal/types/task_queue"
-	"github.com/emirpasic/gods/maps/treemap"
+	taskQueue2 "github.com/allanpk716/ChineseSubFinder/internal/types/task_queue"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 	"path/filepath"
 	"path/filepath"
 	"sync"
 	"sync"
+	"time"
 )
 )
 
 
 // Downloader 实例化一次用一次,不要反复的使用,很多临时标志位需要清理。
 // Downloader 实例化一次用一次,不要反复的使用,很多临时标志位需要清理。
 type Downloader struct {
 type Downloader struct {
 	settings                 *settings.Settings
 	settings                 *settings.Settings
 	log                      *logrus.Logger
 	log                      *logrus.Logger
-	subSupplierHub           *subSupplier.SubSupplierHub                  // 字幕提供源的集合
+	ctx                      context.Context
+	cancel                   context.CancelFunc
+	subSupplierHub           *subSupplier.SubSupplierHub                  // 字幕提供源的集合,这个需要定时进行扫描,这些字幕源是否有效,以及下载验证码信息
 	mk                       *markSystem.MarkingSystem                    // MarkingSystem,字幕的评价系统
 	mk                       *markSystem.MarkingSystem                    // MarkingSystem,字幕的评价系统
-	embyHelper               *embyHelper.EmbyHelper                       // Emby 的实例
-	subFormatter             ifaces.ISubFormatter                         //	字幕格式化命名的实现
+	subFormatter             ifaces.ISubFormatter                         // 字幕格式化命名的实现
 	subNameFormatter         subCommon.FormatterName                      // 从 inSubFormatter 推断出来
 	subNameFormatter         subCommon.FormatterName                      // 从 inSubFormatter 推断出来
-	needForcedScanAndDownSub bool                                         // 将会强制扫描所有的视频,下载字幕,替换已经存在的字幕,不进行时间段和已存在则跳过的判断。且不会进过 Emby API 的逻辑,智能进行强制去以本程序的方式去扫描。
-	NeedRestoreFixTimeLineBK bool                                         // 从 csf-bk 文件还原时间轴修复前的字幕文件
 	subTimelineFixerHelperEx *sub_timeline_fixer.SubTimelineFixerHelperEx // 字幕时间轴校正
 	subTimelineFixerHelperEx *sub_timeline_fixer.SubTimelineFixerHelperEx // 字幕时间轴校正
-	taskControl              *task_control.TaskControl                    // 具体下载字幕的任务控制
-	canceled                 bool                                         // 取消执行 task control
-	canceledLock             sync.Mutex                                   // 取消执行 task control 的 Lock
+	downloaderLock           sync.Mutex                                   // 取消执行 task control 的 Lock
 	downloadQueue            *task_queue.TaskQueue                        // 需要下载的视频的队列
 	downloadQueue            *task_queue.TaskQueue                        // 需要下载的视频的队列
+	supplierChecking         bool                                         // 正在检测字幕源有效性
 }
 }
 
 
-func NewDownloader(_supplierHub *subSupplier.SubSupplierHub, inSubFormatter ifaces.ISubFormatter, _settings *settings.Settings, log *logrus.Logger) (*Downloader, error) {
+func NewDownloader(inSubFormatter ifaces.ISubFormatter, _settings *settings.Settings, log *logrus.Logger, downloadQueue *task_queue.TaskQueue) *Downloader {
 
 
 	var downloader Downloader
 	var downloader Downloader
-	var err error
 	downloader.subFormatter = inSubFormatter
 	downloader.subFormatter = inSubFormatter
 	downloader.log = log
 	downloader.log = log
 	// 参入设置信息
 	// 参入设置信息
 	downloader.settings = _settings
 	downloader.settings = _settings
 	// 检测是否某些参数超出范围
 	// 检测是否某些参数超出范围
 	downloader.settings.Check()
 	downloader.settings.Check()
-	// 初始化 Emby API 接口
-	if downloader.settings.EmbySettings.Enable == true && downloader.settings.EmbySettings.AddressUrl != "" && downloader.settings.EmbySettings.APIKey != "" {
-		downloader.embyHelper = embyHelper.NewEmbyHelper(*downloader.settings.EmbySettings)
-	}
 	// 这里就不单独弄一个 settings.SubNameFormatter 字段来传递值了,因为 inSubFormatter 就已经知道是什么 formatter 了
 	// 这里就不单独弄一个 settings.SubNameFormatter 字段来传递值了,因为 inSubFormatter 就已经知道是什么 formatter 了
 	downloader.subNameFormatter = subCommon.FormatterName(downloader.subFormatter.GetFormatterFormatterName())
 	downloader.subNameFormatter = subCommon.FormatterName(downloader.subFormatter.GetFormatterFormatterName())
 
 
-	downloader.subSupplierHub = _supplierHub
-
 	var sitesSequence = make([]string, 0)
 	var sitesSequence = make([]string, 0)
 	// TODO 这里写固定了抉择字幕的顺序
 	// TODO 这里写固定了抉择字幕的顺序
 	sitesSequence = append(sitesSequence, common.SubSiteZiMuKu)
 	sitesSequence = append(sitesSequence, common.SubSiteZiMuKu)
@@ -83,611 +66,351 @@ func NewDownloader(_supplierHub *subSupplier.SubSupplierHub, inSubFormatter ifac
 	if downloader.settings.AdvancedSettings.FixTimeLine == true {
 	if downloader.settings.AdvancedSettings.FixTimeLine == true {
 		downloader.subTimelineFixerHelperEx.Check()
 		downloader.subTimelineFixerHelperEx.Check()
 	}
 	}
-	// 初始化任务控制
-	downloader.taskControl, err = task_control.NewTaskControl(downloader.settings.CommonSettings.Threads, log_helper.GetLogger())
-	if err != nil {
-		return nil, err
-	}
-	// 需要下载的视频的队列
-	downloader.downloadQueue = task_queue.NewTaskQueue("NormalDownloadQueue", _settings, downloader.log)
+	// 任务队列
+	downloader.downloadQueue = downloadQueue
+	// 单个任务的超时设置
+	downloader.ctx, downloader.cancel = context.WithTimeout(context.Background(), time.Duration(downloader.settings.AdvancedSettings.TaskQueue.OneJobTimeOut)*time.Second)
 
 
-	return &downloader, nil
+	return &downloader
 }
 }
 
 
-// ReadSpeFile 优先级最高。读取特殊文件,启用一些特殊的功能,比如 forced_scan_and_down_sub
-func (d *Downloader) ReadSpeFile() error {
-	// 理论上是一次性的,用了这个文件就应该没了
-	// 强制的字幕扫描
-	needProcessForcedScanAndDownSub, err := forced_scan_and_down_sub.CheckSpeFile()
-	if err != nil {
-		return err
-	}
-	d.needForcedScanAndDownSub = needProcessForcedScanAndDownSub
-	// 从 csf-bk 文件还原时间轴修复前的字幕文件
-	needProcessRestoreFixTimelineBK, err := restore_fix_timeline_bk.CheckSpeFile()
-	if err != nil {
-		return err
-	}
-	d.NeedRestoreFixTimeLineBK = needProcessRestoreFixTimelineBK
+func (d *Downloader) SupplierCheck() {
 
 
-	d.log.Infoln("NeedRestoreFixTimeLineBK ==", needProcessRestoreFixTimelineBK)
-
-	return nil
-}
-
-// ScanMovieAndSeriesWait2DownloadSub 扫描出有那些电影、连续剧需要进行字幕下载的
-func (d *Downloader) ScanMovieAndSeriesWait2DownloadSub() (*ScanVideoResult, error) {
-
-	var err error
-	// -----------------------------------------------------
-	// 强制下载和常规模式(没有媒体服务器)
-	if d.needForcedScanAndDownSub == true || d.embyHelper == nil {
-
-		normalScanResult := NormalScanVideoResult{}
-		// 直接由本程序自己去扫描视频视频有哪些
-		// 全扫描
-		if d.needForcedScanAndDownSub == true {
-			d.log.Infoln("Forced Scan And DownSub")
-		}
-		// --------------------------------------------------
-		// 电影
-		// 没有填写 emby_helper api 的信息,那么就走常规的全文件扫描流程
-		normalScanResult.MovieFileFullPathList, err = my_util.SearchMatchedVideoFileFromDirs(d.log, d.settings.CommonSettings.MoviePaths)
-		if err != nil {
-			return nil, err
-		}
-		// --------------------------------------------------
-		// 连续剧
-		// 遍历连续剧总目录下的第一层目录
-		normalScanResult.SeriesDirMap, err = seriesHelper.GetSeriesListFromDirs(d.settings.CommonSettings.SeriesPaths)
-		if err != nil {
-			return nil, err
-		}
-		// ------------------------------------------------------------------------------
-		// 输出调试信息,有那些连续剧文件夹名称
-		normalScanResult.SeriesDirMap.Each(func(key interface{}, value interface{}) {
-			for i, s := range value.([]string) {
-				d.log.Debugln("embyHelper == nil GetSeriesList", i, s)
-			}
-		})
-		// ------------------------------------------------------------------------------
-		return &ScanVideoResult{Normal: &normalScanResult}, nil
-	} else {
-		// TODO 如果后续支持了 Jellyfin、Plex 那么这里需要额外正在对应的扫描逻辑
-		// 进过 emby_helper api 的信息读取
-		embyScanResult := EmbyScanVideoResult{}
-		d.log.Infoln("Movie Sub Dl From Emby API...")
-		// Emby 情况,从 Emby 获取视频信息
-		err = d.RefreshEmbySubList()
-		if err != nil {
-			d.log.Errorln("RefreshEmbySubList", err)
-			return nil, err
-		}
-		// ------------------------------------------------------------------------------
-		// 有哪些更新的视频列表,包含电影、连续剧
-		embyScanResult.MovieSubNeedDlEmbyMixInfoList, embyScanResult.SeriesSubNeedDlEmbyMixInfoMap, err = d.GetUpdateVideoListFromEmby()
-		if err != nil {
-			d.log.Errorln("GetUpdateVideoListFromEmby", err)
-			return nil, err
+	defer func() {
+		if p := recover(); p != nil {
+			d.log.Errorln("Downloader.SupplierCheck() panic")
 		}
 		}
-		// ------------------------------------------------------------------------------
-		return &ScanVideoResult{Emby: &embyScanResult}, nil
-	}
-}
 
 
-// FilterMovieAndSeriesNeedDownload 过滤出需要下载字幕的视频,比如是否跳过中文的剧集,是否超过3个月的下载时间,丢入队列中
-func (d *Downloader) FilterMovieAndSeriesNeedDownload(scanVideoResult *ScanVideoResult) error {
+		d.downloaderLock.Lock()
+		d.supplierChecking = false
+		d.downloaderLock.Unlock()
 
 
-	err := d.filterMovieAndSeriesNeedDownloadNormal(scanVideoResult.Normal)
-	if err != nil {
-		return err
-	}
+		d.log.Infoln("Download.SupplierCheck() End")
+	}()
 
 
-	err = d.filterMovieAndSeriesNeedDownloadEmby(scanVideoResult.Emby)
-	if err != nil {
-		return err
+	d.log.Infoln("Download.SupplierCheck() Start ...")
+
+	supplierChecking := false
+	d.downloaderLock.Lock()
+	supplierChecking = d.supplierChecking
+	d.downloaderLock.Unlock()
+	if supplierChecking == true {
+		d.log.Warningln("Download.SupplierCheck() only run once, skip")
+		return
 	}
 	}
 
 
-	return nil
-}
+	d.downloaderLock.Lock()
+	d.supplierChecking = true
+	d.downloaderLock.Unlock()
 
 
-func (d *Downloader) filterMovieAndSeriesNeedDownloadNormal(normal *NormalScanVideoResult) error {
-	// ----------------------------------------
-	// Normal 过滤,电影
-	for _, oneMovieFPath := range normal.MovieFileFullPathList {
-		// 放入队列
-		if d.subSupplierHub.MovieNeedDlSub(oneMovieFPath, d.needForcedScanAndDownSub) == false {
-			continue
+	// 创建一个 chan 用于任务的中断和超时
+	done := make(chan interface{}, 1)
+	// 接收内部任务的 panic
+	panicChan := make(chan interface{}, 1)
+	go func() {
+		if p := recover(); p != nil {
+			panicChan <- p
 		}
 		}
-
-		bok, err := d.downloadQueue.Add(*TTaskqueue.NewOneJob(
-			common.Movie, oneMovieFPath, 5,
-		))
+		// 下载前的初始化
+		d.log.Infoln("PreDownloadProcess.Init().Check().Wait()...")
+		preDownloadProcess := pre_download_process.NewPreDownloadProcess(d.log, d.settings)
+		err := preDownloadProcess.Init().Check().Wait()
 		if err != nil {
 		if err != nil {
-			d.log.Errorln("filterMovieAndSeriesNeedDownloadNormal.Movie.NewOneJob", err)
-			continue
-		}
-		if bok == false {
-			d.log.Warningln("filterMovieAndSeriesNeedDownloadNormal", common.Movie.String(), oneMovieFPath, "downloadQueue.Add == false")
-		}
-	}
-	// Normal 过滤,连续剧
-	// seriesDirMap: dir <--> seriesList
-	normal.SeriesDirMap.Each(func(seriesRootPathName interface{}, seriesNames interface{}) {
-
-		for _, oneSeriesRootDir := range seriesNames.([]string) {
-
-			// 因为可能回去 Web 获取 IMDB 信息,所以这里的错误不返回
-			bNeedDlSub, seriesInfo, err := d.subSupplierHub.SeriesNeedDlSub(oneSeriesRootDir, d.needForcedScanAndDownSub)
-			if err != nil {
-				d.log.Errorln("filterMovieAndSeriesNeedDownloadNormal.SeriesNeedDlSub", err)
-				continue
-			}
-			if bNeedDlSub == false {
-				continue
-			}
+			done <- errors.New(fmt.Sprintf("NewPreDownloadProcess Error: %v", err))
+		} else {
+			// 更新 SubSupplierHub 实例
+			d.downloaderLock.Lock()
+			d.subSupplierHub = preDownloadProcess.SubSupplierHub
+			d.downloaderLock.Unlock()
 
 
-			for _, episodeInfo := range seriesInfo.NeedDlEpsKeyList {
-				// 放入队列
-				oneJob := TTaskqueue.NewOneJob(
-					common.Series, episodeInfo.FileFullPath, 5,
-				)
-				oneJob.Season = episodeInfo.Season
-				oneJob.Episode = episodeInfo.Episode
-
-				bok, err := d.downloadQueue.Add(*oneJob)
-				if err != nil {
-					d.log.Errorln("filterMovieAndSeriesNeedDownloadNormal.Series.NewOneJob", err)
-					continue
-				}
-				if bok == false {
-					d.log.Warningln("filterMovieAndSeriesNeedDownloadNormal", common.Series.String(), episodeInfo.FileFullPath, "downloadQueue.Add == false")
-				}
-			}
+			done <- nil
 		}
 		}
-	})
-
-	return nil
-}
+	}()
 
 
-func (d *Downloader) filterMovieAndSeriesNeedDownloadEmby(emby *EmbyScanVideoResult) error {
-	// ----------------------------------------
-	// Emby 过滤,电影
-	for _, oneMovieMixInfo := range emby.MovieSubNeedDlEmbyMixInfoList {
-		// 放入队列
-		if d.subSupplierHub.MovieNeedDlSub(oneMovieMixInfo.PhysicalVideoFileFullPath, d.needForcedScanAndDownSub) == false {
-			continue
-		}
-		bok, err := d.downloadQueue.Add(*TTaskqueue.NewOneJob(
-			common.Movie, oneMovieMixInfo.PhysicalVideoFileFullPath, 5,
-		))
+	select {
+	case err := <-done:
 		if err != nil {
 		if err != nil {
-			d.log.Errorln("filterMovieAndSeriesNeedDownloadEmby.Movie.NewOneJob", err)
-			continue
+			d.log.Errorln(err)
 		}
 		}
-		if bok == false {
-			d.log.Warningln("filterMovieAndSeriesNeedDownloadEmby", common.Movie.String(), oneMovieMixInfo.PhysicalVideoFileFullPath, "downloadQueue.Add == false")
+		break
+	case p := <-panicChan:
+		// 遇到内部的 panic,向外抛出
+		panic(p)
+	case <-d.ctx.Done():
+		{
+			d.log.Errorln("cancel SupplierCheck")
+			return
 		}
 		}
 	}
 	}
-	// Emby 过滤,连续剧
-	for _, embyMixInfos := range emby.SeriesSubNeedDlEmbyMixInfoMap {
-
-		for _, mixInfo := range embyMixInfos {
+}
 
 
-			// 因为可能回去 Web 获取 IMDB 信息,所以这里的错误不返回
-			bNeedDlSub, seriesInfo, err := d.subSupplierHub.SeriesNeedDlSub(mixInfo.PhysicalRootPath, d.needForcedScanAndDownSub)
-			if err != nil {
-				d.log.Errorln("FilterMovieAndSeriesNeedDownload.SeriesNeedDlSub", err)
-				continue
-			}
-			if bNeedDlSub == false {
-				continue
-			}
+func (d *Downloader) QueueDownloader() {
 
 
-			for _, episodeInfo := range seriesInfo.NeedDlEpsKeyList {
-				// 放入队列
-				oneJob := TTaskqueue.NewOneJob(
-					common.Series, episodeInfo.FileFullPath, 5,
-				)
-				oneJob.Season = episodeInfo.Season
-				oneJob.Episode = episodeInfo.Episode
-
-				bok, err := d.downloadQueue.Add(*oneJob)
-				if err != nil {
-					d.log.Errorln("filterMovieAndSeriesNeedDownloadEmby.Series.NewOneJob", err)
-					continue
-				}
-				if bok == false {
-					d.log.Warningln("filterMovieAndSeriesNeedDownloadEmby", common.Series.String(), episodeInfo.FileFullPath, "downloadQueue.Add == false")
-				}
-			}
+	defer func() {
+		if p := recover(); p != nil {
+			d.log.Errorln("Downloader.QueueDownloader() panic")
 		}
 		}
-	}
-
-	return nil
-}
 
 
-// GetUpdateVideoListFromEmby 这里首先会进行近期影片的获取,然后对这些影片进行刷新,然后在获取字幕列表,最终得到需要字幕获取的 video 列表
-func (d *Downloader) GetUpdateVideoListFromEmby() ([]emby.EmbyMixInfo, map[string][]emby.EmbyMixInfo, error) {
-	if d.embyHelper == nil {
-		return nil, nil, nil
-	}
-	defer func() {
-		d.log.Infoln("GetUpdateVideoListFromEmby End")
+		d.log.Infoln("Download.QueueDownloader() End")
 	}()
 	}()
-	d.log.Infoln("GetUpdateVideoListFromEmby Start...")
-	//------------------------------------------------------
-	// 是否取消执行
-	nowCancel := false
-	d.canceledLock.Lock()
-	nowCancel = d.canceled
-	d.canceledLock.Unlock()
-	if nowCancel == true {
-		d.log.Infoln("GetUpdateVideoListFromEmby Canceled")
-		return nil, nil, nil
-	}
-	var err error
-	var movieList []emby.EmbyMixInfo
-	var seriesSubNeedDlMap map[string][]emby.EmbyMixInfo //  多个需要搜索字幕的连续剧目录,连续剧文件夹名称 -- 每一集的 EmbyMixInfo List
-	movieList, seriesSubNeedDlMap, err = d.embyHelper.GetRecentlyAddVideoListWithNoChineseSubtitle()
-	if err != nil {
-		return nil, nil, err
-	}
-	// 输出调试信息
-	d.log.Debugln("GetUpdateVideoListFromEmby - DebugInfo - movieFileFullPathList Start")
-	for _, info := range movieList {
-		d.log.Debugln(info.PhysicalVideoFileFullPath)
-	}
-	d.log.Debugln("GetUpdateVideoListFromEmby - DebugInfo - movieFileFullPathList End")
-
-	d.log.Debugln("GetUpdateVideoListFromEmby - DebugInfo - seriesSubNeedDlMap Start")
-	for s := range seriesSubNeedDlMap {
-		d.log.Debugln(s)
-	}
-	d.log.Debugln("GetUpdateVideoListFromEmby - DebugInfo - seriesSubNeedDlMap End")
 
 
-	return movieList, seriesSubNeedDlMap, nil
-}
+	var downloadCounter int64
+	downloadCounter = 0
 
 
-func (d *Downloader) RefreshEmbySubList() error {
+	d.log.Infoln("Download.QueueDownloader() Start ...")
 
 
-	if d.embyHelper == nil {
-		return nil
+	// 如果正在 check supplier 的状态,那么就跳过本次
+	supplierChecking := false
+	d.downloaderLock.Lock()
+	supplierChecking = d.supplierChecking
+	d.downloaderLock.Unlock()
+	if supplierChecking == true {
+		d.log.Infoln("SupplierCheck is running, Skip QueueDownloader this time")
+		return
 	}
 	}
+	// 从队列取数据出来
+	bok, oneJob, err := d.downloadQueue.GetOneWaitingJob()
+	if err != nil {
+		d.log.Errorln("d.downloadQueue.GetOneWaitingJob()", err)
+		return
+	}
+	if bok == false {
+		d.log.Infoln("Download Queue Is Empty, Skip This Time")
+		return
+	}
+	downloadCounter++
+	// 创建一个 chan 用于任务的中断和超时
+	done := make(chan interface{}, 1)
+	// 接收内部任务的 panic
+	panicChan := make(chan interface{}, 1)
+	go func() {
+		defer func() {
+			if p := recover(); p != nil {
+				panicChan <- p
+			}
+			// 没下载完毕一次,进行一次缓存和 Chrome 的清理
+			err = my_folder.ClearRootTmpFolder()
+			if err != nil {
+				d.log.Error("ClearRootTmpFolder", err)
+			}
+			my_util.CloseChrome(d.log)
+		}()
 
 
-	bRefresh := false
-	defer func() {
-		if bRefresh == true {
-			d.log.Infoln("Refresh Emby Sub List Success")
+		if oneJob.VideoType == common.Movie {
+			// 电影
+			// 具体的下载逻辑 func()
+			done <- d.movieDlFunc(d.ctx, oneJob, downloadCounter)
+		} else if oneJob.VideoType == common.Series {
+			// 连续剧
+			// 具体的下载逻辑 func()
+			done <- d.seriesDlFunc(d.ctx, oneJob, downloadCounter)
 		} else {
 		} else {
-			d.log.Errorln("Refresh Emby Sub List Error")
+			d.log.Errorln("oneJob.VideoType not support, oneJob.VideoType = ", oneJob.VideoType)
+			done <- nil
 		}
 		}
 	}()
 	}()
-	d.log.Infoln("Refresh Emby Sub List Start...")
-	//------------------------------------------------------
-	// 是否取消执行
-	nowCancel := false
-	d.canceledLock.Lock()
-	nowCancel = d.canceled
-	d.canceledLock.Unlock()
-	if nowCancel == true {
-		d.log.Infoln("RefreshEmbySubList Canceled")
-		return nil
-	}
 
 
-	bRefresh, err := d.embyHelper.RefreshEmbySubList()
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-// DownloadSub4Movie 需要从队列中拿去一个去下载
-func (d *Downloader) DownloadSub4Movie() error {
-	defer func() {
-		// 所有的电影字幕下载完成,抉择完成,需要清理缓存目录
-		err := my_folder.ClearRootTmpFolder()
+	select {
+	case err := <-done:
+		// 跳出 select,可以外层继续,不会阻塞在这里
 		if err != nil {
 		if err != nil {
-			d.log.Error("ClearRootTmpFolder", err)
+			d.log.Errorln(err)
 		}
 		}
-		d.log.Infoln("Download Movie Sub End...")
-	}()
-	var err error
-	d.log.Infoln("Download Movie Sub Started...")
-	//------------------------------------------------------
-	// 是否取消执行
-	nowCancel := false
-	d.canceledLock.Lock()
-	nowCancel = d.canceled
-	d.canceledLock.Unlock()
-	if nowCancel == true {
-		d.log.Infoln("DownloadSub4Movie Canceled")
-		return nil
-	}
-	// -----------------------------------------------------
-	// 并发控制,设置为 movie 的处理函数
-	d.taskControl.SetCtxProcessFunc("MoviePool", d.movieDlFunc, common.OneMovieProcessTimeOut)
-	// -----------------------------------------------------
-	// 一个视频文件同时多个站点查询,阻塞完毕后,在进行下一个
-	// 需要从队列里面取出来 downloadQueue
-	for i, oneVideoFullPath := range d.movieFileFullPathList {
-
-		err = d.taskControl.Invoke(&task_control.TaskData{
-			Index: i,
-			Count: len(d.movieFileFullPathList),
-			DataEx: DownloadInputData{
-				OneVideoFullPath: oneVideoFullPath,
-			},
-		})
-		if err != nil {
-			d.log.Errorln("DownloadSub4Movie Invoke Index:", i, "Error", err)
+		break
+	case p := <-panicChan:
+		// 遇到内部的 panic,向外抛出
+		panic(p)
+	case <-d.ctx.Done():
+		{
+			// 取消这个 context
+			d.log.Warningln("cancel Downloader.QueueDownloader()")
+			return
 		}
 		}
 	}
 	}
+}
 
 
-	d.taskControl.Hold()
-	// 可以得到执行结果的统计信息
-	successList, noExecuteList, errorList := d.taskControl.GetExecuteInfo()
-
-	d.log.Infoln("--------------------------------------")
-	d.log.Infoln("successList", len(successList))
-	for i, indexId := range successList {
-		d.log.Infoln(i, d.movieFileFullPathList[indexId])
-	}
-	d.log.Infoln("--------------------------------------")
-	d.log.Infoln("noExecuteList", len(noExecuteList))
-	for i, indexId := range noExecuteList {
-		d.log.Infoln(i, d.movieFileFullPathList[indexId])
-	}
-	d.log.Infoln("--------------------------------------")
-	d.log.Infoln("errorList", len(errorList))
-	for i, indexId := range errorList {
-		d.log.Infoln(i, d.movieFileFullPathList[indexId])
+func (d *Downloader) Cancel() {
+	if d == nil {
+		return
 	}
 	}
-	d.log.Infoln("--------------------------------------")
-
-	return nil
+	d.cancel()
+	d.log.Infoln("Downloader.Cancel()")
 }
 }
 
 
-// DownloadSub4Series 需要从队列中拿去一个去下载
-func (d *Downloader) DownloadSub4Series() error {
-	var err error
-	defer func() {
-		// 所有的连续剧字幕下载完成,抉择完成,需要清理缓存目录
-		err := my_folder.ClearRootTmpFolder()
-		if err != nil {
-			d.log.Error("ClearRootTmpFolder", err)
-		}
-		d.log.Infoln("Download Series Sub End...")
+func (d *Downloader) movieDlFunc(ctx context.Context, job taskQueue2.OneJob, downloadIndex int64) error {
 
 
-		my_util.CloseChrome(d.log)
-		d.log.Infoln("CloseChrome")
-	}()
-	d.log.Infoln("Download Series Sub Started...")
-	//------------------------------------------------------
-	// 是否取消执行
-	nowCancel := false
-	d.canceledLock.Lock()
-	nowCancel = d.canceled
-	d.canceledLock.Unlock()
-	if nowCancel == true {
-		d.log.Infoln("DownloadSub4Series Canceled")
+	var nowSubSupplierHub *subSupplier.SubSupplierHub
+	d.downloaderLock.Lock()
+	nowSubSupplierHub = d.subSupplierHub
+	d.downloaderLock.Unlock()
+	if nowSubSupplierHub == nil {
+		d.log.Infoln("Wait SupplierCheck Update *subSupplierHub, movieDlFunc Skip this time")
 		return nil
 		return nil
 	}
 	}
-	// -----------------------------------------------------
-	// 并发控制,设置为 movie 的处理函数
-	d.taskControl.SetCtxProcessFunc("SeriesPool", d.seriesDlFunc, common.OneSeriesProcessTimeOut)
-	// -----------------------------------------------------
-	// 是否是通过 emby_helper api 获取的列表
-	// x://连续剧 -- 连续剧A、连续剧B、连续剧C 的名称列表
-	// 需要从队列里面取出来 downloadQueue
-	seriesCount := 0
-	seriesIndexNameMap := make(map[int]string)
-	seriesDirMap.Each(func(seriesRootPathName interface{}, seriesNames interface{}) {
-		for _, seriesName := range seriesNames.([]string) {
-
-			err = d.taskControl.Invoke(&task_control.TaskData{
-				Index: seriesCount,
-				Count: len(seriesNames.([]string)),
-				DataEx: DownloadInputData{
-					RootDirPath:   seriesRootPathName.(string),
-					OneSeriesPath: seriesName,
-				},
-			})
-			if err != nil {
-				d.log.Errorln("DownloadSub4Series", seriesRootPathName.(string), "Invoke Index:", seriesCount, "Error", err)
-			}
-
-			seriesIndexNameMap[seriesCount] = seriesName
-			seriesCount++
-		}
-	})
 
 
-	d.taskControl.Hold()
-	// 可以得到执行结果的统计信息
-	successList, noExecuteList, errorList := d.taskControl.GetExecuteInfo()
-
-	d.log.Infoln("--------------------------------------")
-	d.log.Infoln("successList", len(successList))
-	for i, indexId := range successList {
-		d.log.Infoln(i, seriesIndexNameMap[indexId])
-	}
-	d.log.Infoln("--------------------------------------")
-	d.log.Infoln("noExecuteList", len(noExecuteList))
-	for i, indexId := range noExecuteList {
-		d.log.Infoln(i, seriesIndexNameMap[indexId])
-	}
-	d.log.Infoln("--------------------------------------")
-	d.log.Infoln("errorList", len(errorList))
-	for i, indexId := range errorList {
-		d.log.Infoln(i, seriesIndexNameMap[indexId])
+	// 字幕都下载缓存好了,需要抉择存哪一个,优先选择中文双语的,然后到中文
+	organizeSubFiles, err := nowSubSupplierHub.DownloadSub4Movie(job.VideoFPath, downloadIndex)
+	if err != nil {
+		err = errors.New(fmt.Sprintf("subSupplierHub.DownloadSub4Movie: %v, %v", job.VideoFPath, err))
+		d.downloadQueue.AutoDetectUpdateJobStatus(job, err)
+		return err
 	}
 	}
-	d.log.Infoln("--------------------------------------")
-
-	return nil
-}
-
-func (d *Downloader) RestoreFixTimelineBK() error {
-
-	defer d.log.Infoln("End Restore Fix Timeline BK")
-	d.log.Infoln("Start Restore Fix Timeline BK...")
-	//------------------------------------------------------
-	// 是否取消执行
-	nowCancel := false
-	d.canceledLock.Lock()
-	nowCancel = d.canceled
-	d.canceledLock.Unlock()
-	if nowCancel == true {
-		d.log.Infoln("RestoreFixTimelineBK Canceled")
+	// 返回的两个值都是 nil 的时候,就是没有下载到字幕
+	if organizeSubFiles == nil || len(organizeSubFiles) < 1 {
+		d.log.Infoln(task_queue.ErrNotSubFound.Error(), filepath.Base(job.VideoFPath))
+		d.downloadQueue.AutoDetectUpdateJobStatus(job, task_queue.ErrNotSubFound)
 		return nil
 		return nil
 	}
 	}
 
 
-	_, err := subTimelineFixerPKG.Restore(d.settings.CommonSettings.MoviePaths, d.settings.CommonSettings.SeriesPaths)
+	err = d.oneVideoSelectBestSub(job.VideoFPath, organizeSubFiles)
 	if err != nil {
 	if err != nil {
+		d.downloadQueue.AutoDetectUpdateJobStatus(job, err)
 		return err
 		return err
 	}
 	}
-	return nil
-}
 
 
-func (d *Downloader) Cancel() {
-	d.canceledLock.Lock()
-	d.canceled = true
-	d.canceledLock.Unlock()
-
-	d.taskControl.Release()
+	d.downloadQueue.AutoDetectUpdateJobStatus(job, nil)
+	return nil
 }
 }
 
 
-func (d *Downloader) movieDlFunc(ctx context.Context, inData interface{}) error {
+func (d *Downloader) seriesDlFunc(ctx context.Context, job taskQueue2.OneJob, downloadIndex int64) error {
 
 
-	taskData := inData.(*task_control.TaskData)
-	downloadInputData := taskData.DataEx.(DownloadInputData)
-	// 设置任务的状态
-	pkgcommon.SetSubScanJobStatusScanMovie(taskData.Index+1, taskData.Count, filepath.Base(downloadInputData.OneVideoFullPath))
-	// -----------------------------------------------------
-	// 字幕都下载缓存好了,需要抉择存哪一个,优先选择中文双语的,然后到中文
-	organizeSubFiles, err := d.subSupplierHub.DownloadSub4Movie(downloadInputData.OneVideoFullPath, taskData.Index)
-	if err != nil {
-		d.log.Errorln("subSupplierHub.DownloadSub4Movie", downloadInputData.OneVideoFullPath, err)
-		return err
-	}
-	// 返回的两个值都是 nil 的时候,就是无需下载字幕,那么同样不用输出额外的信息,因为之前会输出跳过的原因
-	if organizeSubFiles == nil {
+	var nowSubSupplierHub *subSupplier.SubSupplierHub
+	d.downloaderLock.Lock()
+	nowSubSupplierHub = d.subSupplierHub
+	d.downloaderLock.Unlock()
+	if nowSubSupplierHub == nil {
+		d.log.Infoln("Wait SupplierCheck Update *subSupplierHub, seriesDlFunc Skip this time")
 		return nil
 		return nil
 	}
 	}
-	// 去搜索了没有发现字幕
-	if len(organizeSubFiles) < 1 {
-		d.log.Infoln("no sub found", filepath.Base(downloadInputData.OneVideoFullPath))
-		return nil
-	}
-	d.oneVideoSelectBestSub(downloadInputData.OneVideoFullPath, organizeSubFiles)
-	// -----------------------------------------------------
-
-	return nil
-}
-
-func (d *Downloader) seriesDlFunc(ctx context.Context, inData interface{}) error {
 
 
 	var err error
 	var err error
-	taskData := inData.(*task_control.TaskData)
-	downloadInputData := taskData.DataEx.(DownloadInputData)
 	// 这里拿到了这一部连续剧的所有的剧集信息,以及所有下载到的字幕信息
 	// 这里拿到了这一部连续剧的所有的剧集信息,以及所有下载到的字幕信息
+	seriesInfo, err := series_helper.ReadSeriesInfoFromDir(job.SeriesRootDirPath, false, d.settings.AdvancedSettings.ProxySettings)
+	if err != nil {
+		err = errors.New(fmt.Sprintf("seriesDlFunc.ReadSeriesInfoFromDir, Error: %v", err))
+		d.downloadQueue.AutoDetectUpdateJobStatus(job, err)
+		return err
+	}
+	// 设置只有一集需要下载
+	epsMap := make(map[int]int, 0)
+	epsMap[job.Season] = job.Episode
+	series_helper.SetTheSpecifiedEps2Download(seriesInfo, epsMap)
 	// 下载好的字幕文件
 	// 下载好的字幕文件
 	var organizeSubFiles map[string][]string
 	var organizeSubFiles map[string][]string
-	// 设置任务的状态
-	pkgcommon.SetSubScanJobStatusScanSeriesMain(taskData.Index+1, taskData.Count, downloadInputData.OneSeriesPath)
 	// 下载的接口是统一的
 	// 下载的接口是统一的
-	organizeSubFiles, err = d.subSupplierHub.DownloadSub4Series(downloadInputData.OneSeriesPath,
-		downloadInputData.SeriesInfo,
-		taskData.Index)
+	organizeSubFiles, err = nowSubSupplierHub.DownloadSub4Series(job.SeriesRootDirPath,
+		seriesInfo,
+		downloadIndex)
 	if err != nil {
 	if err != nil {
-		d.log.Errorln("subSupplierHub.DownloadSub4Series", downloadInputData.OneSeriesPath, err)
+		err = errors.New(fmt.Sprintf("seriesDlFunc.DownloadSub4Series %v S%vE%v %v", filepath.Base(job.SeriesRootDirPath), job.Season, job.Episode, err))
+		d.downloadQueue.AutoDetectUpdateJobStatus(job, err)
 		return err
 		return err
 	}
 	}
 	// 是否下载到字幕了
 	// 是否下载到字幕了
 	if organizeSubFiles == nil || len(organizeSubFiles) < 1 {
 	if organizeSubFiles == nil || len(organizeSubFiles) < 1 {
-		d.log.Infoln("no sub found", filepath.Base(downloadInputData.OneSeriesPath))
+		d.log.Infoln(task_queue.ErrNotSubFound.Error(), filepath.Base(job.VideoFPath), job.Season, job.Episode)
+		d.downloadQueue.AutoDetectUpdateJobStatus(job, task_queue.ErrNotSubFound)
 		return nil
 		return nil
 	}
 	}
+
+	var errSave2Local error
+	save2LocalSubCount := 0
 	// 只针对需要下载字幕的视频进行字幕的选择保存
 	// 只针对需要下载字幕的视频进行字幕的选择保存
 	subVideoCount := 0
 	subVideoCount := 0
-	for epsKey, episodeInfo := range downloadInputData.SeriesInfo.NeedDlEpsKeyList {
+	for epsKey, episodeInfo := range seriesInfo.NeedDlEpsKeyList {
 
 
-		stage := make(chan interface{}, 1)
+		// 创建一个 chan 用于任务的中断和超时
+		done := make(chan interface{}, 1)
+		// 接收内部任务的 panic
+		panicChan := make(chan interface{}, 1)
 		go func() {
 		go func() {
+			if p := recover(); p != nil {
+				panicChan <- p
+			}
 			// 匹配对应的 Eps 去处理
 			// 匹配对应的 Eps 去处理
-			d.oneVideoSelectBestSub(episodeInfo.FileFullPath, organizeSubFiles[epsKey])
-			stage <- 1
+			done <- d.oneVideoSelectBestSub(episodeInfo.FileFullPath, organizeSubFiles[epsKey])
 		}()
 		}()
 
 
 		select {
 		select {
+		case errInterface := <-done:
+			if errInterface != nil {
+				errSave2Local = errInterface.(error)
+				d.log.Errorln(errInterface.(error))
+			} else {
+				save2LocalSubCount++
+			}
+			break
+		case p := <-panicChan:
+			// 遇到内部的 panic,向外抛出
+			panic(p)
 		case <-ctx.Done():
 		case <-ctx.Done():
 			{
 			{
-				return errors.New(fmt.Sprintf("cancel at NeedDlEpsKeyList.oneVideoSelectBestSub epsKey: %s", epsKey))
+				err = errors.New(fmt.Sprintf("cancel at NeedDlEpsKeyList.oneVideoSelectBestSub, %v S%dE%d", seriesInfo.Name, episodeInfo.Season, episodeInfo.Episode))
+				d.downloadQueue.AutoDetectUpdateJobStatus(job, err)
+				return err
 			}
 			}
-		case <-stage:
-			break
 		}
 		}
 
 
 		subVideoCount++
 		subVideoCount++
 	}
 	}
 	// 这里会拿到一份季度字幕的列表比如,Key 是 S1E0 S2E0 S3E0,value 是新的存储位置
 	// 这里会拿到一份季度字幕的列表比如,Key 是 S1E0 S2E0 S3E0,value 是新的存储位置
-	fullSeasonSubDict := d.saveFullSeasonSub(downloadInputData.SeriesInfo, organizeSubFiles)
+	fullSeasonSubDict := d.saveFullSeasonSub(seriesInfo, organizeSubFiles)
 	// TODO 季度的字幕包,应该优先于零散的字幕吧,暂定就这样了,注意是全部都替换
 	// TODO 季度的字幕包,应该优先于零散的字幕吧,暂定就这样了,注意是全部都替换
 	// 需要与有下载需求的季交叉
 	// 需要与有下载需求的季交叉
-	for _, episodeInfo := range downloadInputData.SeriesInfo.EpList {
-
-		stage := make(chan interface{}, 1)
+	for _, episodeInfo := range seriesInfo.EpList {
 
 
-		_, ok := downloadInputData.SeriesInfo.NeedDlSeasonDict[episodeInfo.Season]
+		// 创建一个 chan 用于任务的中断和超时
+		done := make(chan interface{}, 1)
+		// 接收内部任务的 panic
+		panicChan := make(chan interface{}, 1)
+		_, ok := seriesInfo.NeedDlSeasonDict[episodeInfo.Season]
 		if ok == false {
 		if ok == false {
 			continue
 			continue
 		}
 		}
 
 
 		go func() {
 		go func() {
+			if p := recover(); p != nil {
+				panicChan <- p
+			}
 			// 匹配对应的 Eps 去处理
 			// 匹配对应的 Eps 去处理
 			seasonEpsKey := my_util.GetEpisodeKeyName(episodeInfo.Season, episodeInfo.Episode)
 			seasonEpsKey := my_util.GetEpisodeKeyName(episodeInfo.Season, episodeInfo.Episode)
-			d.oneVideoSelectBestSub(episodeInfo.FileFullPath, fullSeasonSubDict[seasonEpsKey])
-			stage <- 1
+			done <- d.oneVideoSelectBestSub(episodeInfo.FileFullPath, fullSeasonSubDict[seasonEpsKey])
 		}()
 		}()
 
 
 		select {
 		select {
+		case errInterface := <-done:
+			if errInterface != nil {
+				errSave2Local = errInterface.(error)
+				d.log.Errorln(errInterface.(error))
+			} else {
+				save2LocalSubCount++
+			}
+			break
+		case p := <-panicChan:
+			// 遇到内部的 panic,向外抛出
+			panic(p)
 		case <-ctx.Done():
 		case <-ctx.Done():
 			{
 			{
-				return errors.New(fmt.Sprintf("cancel at EpList.oneVideoSelectBestSub episodeInfo.FileFullPath: %s", episodeInfo.FileFullPath))
+				err = errors.New(fmt.Sprintf("cancel at NeedDlEpsKeyList.oneVideoSelectBestSub, %v S%dE%d", seriesInfo.Name, episodeInfo.Season, episodeInfo.Episode))
+				d.downloadQueue.AutoDetectUpdateJobStatus(job, err)
+				return err
 			}
 			}
-		case <-stage:
-			break
 		}
 		}
 	}
 	}
 	// 是否清理全季的缓存字幕文件夹
 	// 是否清理全季的缓存字幕文件夹
 	if d.settings.AdvancedSettings.SaveFullSeasonTmpSubtitles == false {
 	if d.settings.AdvancedSettings.SaveFullSeasonTmpSubtitles == false {
-		err = sub_helper.DeleteOneSeasonSubCacheFolder(downloadInputData.SeriesInfo.DirPath)
+		err = sub_helper.DeleteOneSeasonSubCacheFolder(seriesInfo.DirPath)
 		if err != nil {
 		if err != nil {
-			return err
+			d.log.Errorln("seriesDlFunc.DeleteOneSeasonSubCacheFolder", err)
 		}
 		}
 	}
 	}
 
 
+	if save2LocalSubCount < 1 {
+		// 下载的字幕都没有一个能够写入到本地的,那么就有问题了
+		d.downloadQueue.AutoDetectUpdateJobStatus(job, errSave2Local)
+		return errSave2Local
+	}
+	// 哪怕有一个写入到本地成功了,也无需对本次任务报错
+	d.downloadQueue.AutoDetectUpdateJobStatus(job, nil)
 	return nil
 	return nil
 }
 }
-
-type DownloadInputData struct {
-	OneVideoFullPath string
-	OneSeriesPath    string
-	RootDirPath      string
-	SeriesInfo       *series.SeriesInfo
-}
-
-type ScanVideoResult struct {
-	Normal *NormalScanVideoResult
-	Emby   *EmbyScanVideoResult
-}
-
-type NormalScanVideoResult struct {
-	MovieFileFullPathList []string
-	SeriesDirMap          *treemap.Map
-}
-
-type EmbyScanVideoResult struct {
-	MovieSubNeedDlEmbyMixInfoList []emby.EmbyMixInfo
-	SeriesSubNeedDlEmbyMixInfoMap map[string][]emby.EmbyMixInfo
-}

+ 12 - 10
internal/pkg/downloader/downloader_things.go

@@ -1,6 +1,8 @@
 package downloader
 package downloader
 
 
 import (
 import (
+	"errors"
+	"fmt"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/change_file_encode"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/change_file_encode"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/chs_cht_changer"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/chs_cht_changer"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/decode"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/decode"
@@ -15,11 +17,11 @@ import (
 )
 )
 
 
 // oneVideoSelectBestSub 一个视频,选择最佳的一个字幕(也可以保存所有网站第一个最佳字幕)
 // oneVideoSelectBestSub 一个视频,选择最佳的一个字幕(也可以保存所有网站第一个最佳字幕)
-func (d *Downloader) oneVideoSelectBestSub(oneVideoFullPath string, organizeSubFiles []string) {
+func (d *Downloader) oneVideoSelectBestSub(oneVideoFullPath string, organizeSubFiles []string) error {
 
 
 	// 如果没有则直接跳过
 	// 如果没有则直接跳过
 	if organizeSubFiles == nil || len(organizeSubFiles) < 1 {
 	if organizeSubFiles == nil || len(organizeSubFiles) < 1 {
-		return
+		return nil
 	}
 	}
 
 
 	var err error
 	var err error
@@ -31,6 +33,7 @@ func (d *Downloader) oneVideoSelectBestSub(oneVideoFullPath string, organizeSubF
 
 
 		err = my_folder.CopyFiles2DebugFolder([]string{videoFileName}, organizeSubFiles)
 		err = my_folder.CopyFiles2DebugFolder([]string{videoFileName}, organizeSubFiles)
 		if err != nil {
 		if err != nil {
+			// 这个错误可以忍
 			d.log.Errorln("copySubFile2DesFolder", err)
 			d.log.Errorln("copySubFile2DesFolder", err)
 		}
 		}
 	}
 	}
@@ -53,7 +56,7 @@ func (d *Downloader) oneVideoSelectBestSub(oneVideoFullPath string, organizeSubF
 		finalSubFile = d.mk.SelectOneSubFile(organizeSubFiles)
 		finalSubFile = d.mk.SelectOneSubFile(organizeSubFiles)
 		if finalSubFile == nil {
 		if finalSubFile == nil {
 			d.log.Warnln("Found", len(organizeSubFiles), " subtitles but not one fit:", oneVideoFullPath)
 			d.log.Warnln("Found", len(organizeSubFiles), " subtitles but not one fit:", oneVideoFullPath)
-			return
+			return nil
 		}
 		}
 		/*
 		/*
 			这里还有一个梗,Emby、jellyfin 支持 default 和 forced 扩展字段
 			这里还有一个梗,Emby、jellyfin 支持 default 和 forced 扩展字段
@@ -68,15 +71,14 @@ func (d *Downloader) oneVideoSelectBestSub(oneVideoFullPath string, organizeSubF
 		// 找到了,写入文件
 		// 找到了,写入文件
 		err = d.writeSubFile2VideoPath(oneVideoFullPath, *finalSubFile, "", bSetDefault, false)
 		err = d.writeSubFile2VideoPath(oneVideoFullPath, *finalSubFile, "", bSetDefault, false)
 		if err != nil {
 		if err != nil {
-			d.log.Errorln("SaveMultiSub:", d.settings.AdvancedSettings.SaveMultiSub, "writeSubFile2VideoPath:", err)
-			return
+			return errors.New(fmt.Sprintf("SaveMultiSub: %v, writeSubFile2VideoPath, Error: %v ", d.settings.AdvancedSettings.SaveMultiSub, err))
 		}
 		}
 	} else {
 	} else {
 		// 每个网站 Top1 的字幕
 		// 每个网站 Top1 的字幕
 		siteNames, finalSubFiles := d.mk.SelectEachSiteTop1SubFile(organizeSubFiles)
 		siteNames, finalSubFiles := d.mk.SelectEachSiteTop1SubFile(organizeSubFiles)
 		if len(siteNames) < 0 {
 		if len(siteNames) < 0 {
 			d.log.Warnln("SelectEachSiteTop1SubFile found none sub file")
 			d.log.Warnln("SelectEachSiteTop1SubFile found none sub file")
-			return
+			return nil
 		}
 		}
 		// 多网站 Top 1 字幕保存的时候,第一个设置为 Default 即可
 		// 多网站 Top 1 字幕保存的时候,第一个设置为 Default 即可
 		/*
 		/*
@@ -93,8 +95,7 @@ func (d *Downloader) oneVideoSelectBestSub(oneVideoFullPath string, organizeSubF
 				}
 				}
 				err = d.writeSubFile2VideoPath(oneVideoFullPath, file, siteNames[i], setDefault, false)
 				err = d.writeSubFile2VideoPath(oneVideoFullPath, file, siteNames[i], setDefault, false)
 				if err != nil {
 				if err != nil {
-					d.log.Errorln("SaveMultiSub:", d.settings.AdvancedSettings.SaveMultiSub, "writeSubFile2VideoPath:", err)
-					return
+					return errors.New(fmt.Sprintf("SaveMultiSub: %v, writeSubFile2VideoPath, Error: %v ", d.settings.AdvancedSettings.SaveMultiSub, err))
 				}
 				}
 			}
 			}
 		} else {
 		} else {
@@ -108,13 +109,14 @@ func (d *Downloader) oneVideoSelectBestSub(oneVideoFullPath string, organizeSubF
 			for i := len(finalSubFiles) - 1; i > -1; i-- {
 			for i := len(finalSubFiles) - 1; i > -1; i-- {
 				err = d.writeSubFile2VideoPath(oneVideoFullPath, finalSubFiles[i], siteNames[i], false, false)
 				err = d.writeSubFile2VideoPath(oneVideoFullPath, finalSubFiles[i], siteNames[i], false, false)
 				if err != nil {
 				if err != nil {
-					d.log.Errorln("SaveMultiSub:", d.settings.AdvancedSettings.SaveMultiSub, "writeSubFile2VideoPath:", err)
-					return
+					return errors.New(fmt.Sprintf("SaveMultiSub: %v, writeSubFile2VideoPath, Error: %v ", d.settings.AdvancedSettings.SaveMultiSub, err))
 				}
 				}
 			}
 			}
 		}
 		}
 	}
 	}
 	// -------------------------------------------------
 	// -------------------------------------------------
+
+	return nil
 }
 }
 
 
 // saveFullSeasonSub 这里就需要单独存储到连续剧每一季的文件夹的特殊文件夹中。需要跟 DeleteOneSeasonSubCacheFolder 关联起来
 // saveFullSeasonSub 这里就需要单独存储到连续剧每一季的文件夹的特殊文件夹中。需要跟 DeleteOneSeasonSubCacheFolder 关联起来

+ 5 - 4
internal/pkg/settings/task_queue.go

@@ -1,11 +1,12 @@
 package settings
 package settings
 
 
 type TaskQueue struct {
 type TaskQueue struct {
-	MaxRetryTimes  int `json:"max_retry_times" default:"3"`   // 单个任务失败后,最大重试次数,超过后会降级为 Low
-	Interval       int `json:"interval" default:"30"`         // 任务的间隔,单位 s,这里会有一个限制,不允许太快,然后会做一定的随机时间范围
-	ExpirationTime int `json:"expiration_time"  default:"30"` // 添加任务后,过期的时间(单位 day),超过后,任务会降级到 Low
+	MaxRetryTimes  int `json:"max_retry_times" default:"3"`    // 单个任务失败后,最大重试次数,超过后会降一级
+	OneJobTimeOut  int `json:"one_job_time_out" default:"300"` // 单个任务的超时时间 5 * 60 s
+	Interval       int `json:"interval" default:"10"`          // 任务的间隔,单位 s,这里会有一个限制,不允许太快,然后会做一定的随机时间范围,当前值 x ~ 2*x 之内随机
+	ExpirationTime int `json:"expiration_time"  default:"90"`  // 添加任务后,过期的时间(单位 day),超过后,任务会降级到 Low
 }
 }
 
 
 func NewTaskQueue() *TaskQueue {
 func NewTaskQueue() *TaskQueue {
-	return &TaskQueue{MaxRetryTimes: 3, Interval: 30, ExpirationTime: 30}
+	return &TaskQueue{MaxRetryTimes: 3, Interval: 10, ExpirationTime: 90, OneJobTimeOut: 300}
 }
 }

+ 1 - 0
internal/pkg/sub_file_hash/sub_file_hash.go

@@ -9,6 +9,7 @@ import (
 	"os"
 	"os"
 )
 )
 
 
+// Calculate 视频文件的唯一ID
 func Calculate(filePath string) (string, error) {
 func Calculate(filePath string) (string, error) {
 
 
 	h := sha1.New()
 	h := sha1.New()

+ 337 - 0
internal/pkg/video_scan_and_refresh_helper/video_scan_and_refresh_helper.go

@@ -0,0 +1,337 @@
+package video_scan_and_refresh_helper
+
+import (
+	embyHelper "github.com/allanpk716/ChineseSubFinder/internal/logic/emby_helper"
+	"github.com/allanpk716/ChineseSubFinder/internal/logic/forced_scan_and_down_sub"
+	"github.com/allanpk716/ChineseSubFinder/internal/logic/restore_fix_timeline_bk"
+	seriesHelper "github.com/allanpk716/ChineseSubFinder/internal/logic/series_helper"
+	subSupplier "github.com/allanpk716/ChineseSubFinder/internal/logic/sub_supplier"
+	"github.com/allanpk716/ChineseSubFinder/internal/logic/sub_supplier/zimuku"
+	"github.com/allanpk716/ChineseSubFinder/internal/logic/task_queue"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
+	"github.com/allanpk716/ChineseSubFinder/internal/pkg/settings"
+	subTimelineFixerPKG "github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_timeline_fixer"
+	"github.com/allanpk716/ChineseSubFinder/internal/types/common"
+	"github.com/allanpk716/ChineseSubFinder/internal/types/emby"
+	TTaskqueue "github.com/allanpk716/ChineseSubFinder/internal/types/task_queue"
+	"github.com/emirpasic/gods/maps/treemap"
+	"github.com/sirupsen/logrus"
+)
+
+type VideoScanAndRefreshHelper struct {
+	settings                 *settings.Settings          // 设置的实例
+	log                      *logrus.Logger              // 日志实例
+	needForcedScanAndDownSub bool                        // 将会强制扫描所有的视频,下载字幕,替换已经存在的字幕,不进行时间段和已存在则跳过的判断。且不会进过 Emby API 的逻辑,智能进行强制去以本程序的方式去扫描。
+	NeedRestoreFixTimeLineBK bool                        // 从 csf-bk 文件还原时间轴修复前的字幕文件
+	embyHelper               *embyHelper.EmbyHelper      // Emby 的实例
+	downloadQueue            *task_queue.TaskQueue       // 需要下载的视频的队列
+	subSupplierHub           *subSupplier.SubSupplierHub // 字幕提供源的集合,仅仅是 check 是否需要下载字幕是足够的,如果要下载则需要额外的初始化和检查
+}
+
+func NewVideoScanAndRefreshHelper(settings *settings.Settings, log *logrus.Logger, downloadQueue *task_queue.TaskQueue) *VideoScanAndRefreshHelper {
+	return &VideoScanAndRefreshHelper{settings: settings, log: log, downloadQueue: downloadQueue,
+		subSupplierHub: subSupplier.NewSubSupplierHub(
+			settings, log,
+			zimuku.NewSupplier(settings, log),
+		)}
+}
+
+// ReadSpeFile 优先级最高。读取特殊文件,启用一些特殊的功能,比如 forced_scan_and_down_sub
+func (v *VideoScanAndRefreshHelper) ReadSpeFile() error {
+	// 理论上是一次性的,用了这个文件就应该没了
+	// 强制的字幕扫描
+	needProcessForcedScanAndDownSub, err := forced_scan_and_down_sub.CheckSpeFile()
+	if err != nil {
+		return err
+	}
+	v.needForcedScanAndDownSub = needProcessForcedScanAndDownSub
+	// 从 csf-bk 文件还原时间轴修复前的字幕文件
+	needProcessRestoreFixTimelineBK, err := restore_fix_timeline_bk.CheckSpeFile()
+	if err != nil {
+		return err
+	}
+	v.NeedRestoreFixTimeLineBK = needProcessRestoreFixTimelineBK
+
+	v.log.Infoln("NeedRestoreFixTimeLineBK ==", needProcessRestoreFixTimelineBK)
+
+	return nil
+}
+
+// ScanMovieAndSeriesWait2DownloadSub 扫描出有那些电影、连续剧需要进行字幕下载的
+func (v *VideoScanAndRefreshHelper) ScanMovieAndSeriesWait2DownloadSub() (*ScanVideoResult, error) {
+
+	var err error
+	// -----------------------------------------------------
+	// 强制下载和常规模式(没有媒体服务器)
+	if v.needForcedScanAndDownSub == true || v.embyHelper == nil {
+
+		normalScanResult := NormalScanVideoResult{}
+		// 直接由本程序自己去扫描视频视频有哪些
+		// 全扫描
+		if v.needForcedScanAndDownSub == true {
+			v.log.Infoln("Forced Scan And DownSub")
+		}
+		// --------------------------------------------------
+		// 电影
+		// 没有填写 emby_helper api 的信息,那么就走常规的全文件扫描流程
+		normalScanResult.MovieFileFullPathList, err = my_util.SearchMatchedVideoFileFromDirs(v.log, v.settings.CommonSettings.MoviePaths)
+		if err != nil {
+			return nil, err
+		}
+		// --------------------------------------------------
+		// 连续剧
+		// 遍历连续剧总目录下的第一层目录
+		normalScanResult.SeriesDirMap, err = seriesHelper.GetSeriesListFromDirs(v.settings.CommonSettings.SeriesPaths)
+		if err != nil {
+			return nil, err
+		}
+		// ------------------------------------------------------------------------------
+		// 输出调试信息,有那些连续剧文件夹名称
+		normalScanResult.SeriesDirMap.Each(func(key interface{}, value interface{}) {
+			for i, s := range value.([]string) {
+				v.log.Debugln("embyHelper == nil GetSeriesList", i, s)
+			}
+		})
+		// ------------------------------------------------------------------------------
+		return &ScanVideoResult{Normal: &normalScanResult}, nil
+	} else {
+		// TODO 如果后续支持了 Jellyfin、Plex 那么这里需要额外正在对应的扫描逻辑
+		// 进过 emby_helper api 的信息读取
+		embyScanResult := EmbyScanVideoResult{}
+		v.log.Infoln("Movie Sub Dl From Emby API...")
+		// Emby 情况,从 Emby 获取视频信息
+		err = v.RefreshEmbySubList()
+		if err != nil {
+			v.log.Errorln("RefreshEmbySubList", err)
+			return nil, err
+		}
+		// ------------------------------------------------------------------------------
+		// 有哪些更新的视频列表,包含电影、连续剧
+		embyScanResult.MovieSubNeedDlEmbyMixInfoList, embyScanResult.SeriesSubNeedDlEmbyMixInfoMap, err = v.GetUpdateVideoListFromEmby()
+		if err != nil {
+			v.log.Errorln("GetUpdateVideoListFromEmby", err)
+			return nil, err
+		}
+		// ------------------------------------------------------------------------------
+		return &ScanVideoResult{Emby: &embyScanResult}, nil
+	}
+}
+
+// FilterMovieAndSeriesNeedDownload 过滤出需要下载字幕的视频,比如是否跳过中文的剧集,是否超过3个月的下载时间,丢入队列中
+func (v *VideoScanAndRefreshHelper) FilterMovieAndSeriesNeedDownload(scanVideoResult *ScanVideoResult) error {
+
+	err := v.filterMovieAndSeriesNeedDownloadNormal(scanVideoResult.Normal)
+	if err != nil {
+		return err
+	}
+
+	err = v.filterMovieAndSeriesNeedDownloadEmby(scanVideoResult.Emby)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (v *VideoScanAndRefreshHelper) filterMovieAndSeriesNeedDownloadNormal(normal *NormalScanVideoResult) error {
+	// ----------------------------------------
+	// Normal 过滤,电影
+	for _, oneMovieFPath := range normal.MovieFileFullPathList {
+		// 放入队列
+		if v.subSupplierHub.MovieNeedDlSub(oneMovieFPath, v.needForcedScanAndDownSub) == false {
+			continue
+		}
+
+		bok, err := v.downloadQueue.Add(*TTaskqueue.NewOneJob(
+			common.Movie, oneMovieFPath, task_queue.DefaultTaskPriorityLevel,
+		))
+		if err != nil {
+			v.log.Errorln("filterMovieAndSeriesNeedDownloadNormal.Movie.NewOneJob", err)
+			continue
+		}
+		if bok == false {
+			v.log.Warningln("filterMovieAndSeriesNeedDownloadNormal", common.Movie.String(), oneMovieFPath, "downloadQueue.Add == false")
+		}
+	}
+	// Normal 过滤,连续剧
+	// seriesDirMap: dir <--> seriesList
+	normal.SeriesDirMap.Each(func(seriesRootPathName interface{}, seriesNames interface{}) {
+
+		for _, oneSeriesRootDir := range seriesNames.([]string) {
+
+			// 因为可能回去 Web 获取 IMDB 信息,所以这里的错误不返回
+			bNeedDlSub, seriesInfo, err := v.subSupplierHub.SeriesNeedDlSub(oneSeriesRootDir, v.needForcedScanAndDownSub)
+			if err != nil {
+				v.log.Errorln("filterMovieAndSeriesNeedDownloadNormal.SeriesNeedDlSub", err)
+				continue
+			}
+			if bNeedDlSub == false {
+				continue
+			}
+
+			for _, episodeInfo := range seriesInfo.NeedDlEpsKeyList {
+				// 放入队列
+				oneJob := TTaskqueue.NewOneJob(
+					common.Series, episodeInfo.FileFullPath, task_queue.DefaultTaskPriorityLevel,
+				)
+				oneJob.Season = episodeInfo.Season
+				oneJob.Episode = episodeInfo.Episode
+				oneJob.SeriesRootDirPath = seriesInfo.DirPath
+
+				bok, err := v.downloadQueue.Add(*oneJob)
+				if err != nil {
+					v.log.Errorln("filterMovieAndSeriesNeedDownloadNormal.Series.NewOneJob", err)
+					continue
+				}
+				if bok == false {
+					v.log.Warningln("filterMovieAndSeriesNeedDownloadNormal", common.Series.String(), episodeInfo.FileFullPath, "downloadQueue.Add == false")
+				}
+			}
+		}
+	})
+
+	return nil
+}
+
+func (v *VideoScanAndRefreshHelper) filterMovieAndSeriesNeedDownloadEmby(emby *EmbyScanVideoResult) error {
+	// ----------------------------------------
+	// Emby 过滤,电影
+	for _, oneMovieMixInfo := range emby.MovieSubNeedDlEmbyMixInfoList {
+		// 放入队列
+		if v.subSupplierHub.MovieNeedDlSub(oneMovieMixInfo.PhysicalVideoFileFullPath, v.needForcedScanAndDownSub) == false {
+			continue
+		}
+		bok, err := v.downloadQueue.Add(*TTaskqueue.NewOneJob(
+			common.Movie, oneMovieMixInfo.PhysicalVideoFileFullPath, task_queue.DefaultTaskPriorityLevel,
+			oneMovieMixInfo.VideoInfo.Id,
+		))
+		if err != nil {
+			v.log.Errorln("filterMovieAndSeriesNeedDownloadEmby.Movie.NewOneJob", err)
+			continue
+		}
+		if bok == false {
+			v.log.Warningln("filterMovieAndSeriesNeedDownloadEmby", common.Movie.String(), oneMovieMixInfo.PhysicalVideoFileFullPath, "downloadQueue.Add == false")
+		}
+	}
+	// Emby 过滤,连续剧
+	for _, embyMixInfos := range emby.SeriesSubNeedDlEmbyMixInfoMap {
+
+		for _, mixInfo := range embyMixInfos {
+
+			// 因为可能回去 Web 获取 IMDB 信息,所以这里的错误不返回
+			bNeedDlSub, seriesInfo, err := v.subSupplierHub.SeriesNeedDlSub(mixInfo.PhysicalRootPath, v.needForcedScanAndDownSub)
+			if err != nil {
+				v.log.Errorln("FilterMovieAndSeriesNeedDownload.SeriesNeedDlSub", err)
+				continue
+			}
+			if bNeedDlSub == false {
+				continue
+			}
+
+			for _, episodeInfo := range seriesInfo.NeedDlEpsKeyList {
+				// 放入队列
+				oneJob := TTaskqueue.NewOneJob(
+					common.Series, episodeInfo.FileFullPath, task_queue.DefaultTaskPriorityLevel,
+				)
+				oneJob.Season = episodeInfo.Season
+				oneJob.Episode = episodeInfo.Episode
+				oneJob.SeriesRootDirPath = seriesInfo.DirPath
+
+				bok, err := v.downloadQueue.Add(*oneJob)
+				if err != nil {
+					v.log.Errorln("filterMovieAndSeriesNeedDownloadEmby.Series.NewOneJob", err)
+					continue
+				}
+				if bok == false {
+					v.log.Warningln("filterMovieAndSeriesNeedDownloadEmby", common.Series.String(), episodeInfo.FileFullPath, "downloadQueue.Add == false")
+				}
+			}
+		}
+	}
+
+	return nil
+}
+
+// GetUpdateVideoListFromEmby 这里首先会进行近期影片的获取,然后对这些影片进行刷新,然后在获取字幕列表,最终得到需要字幕获取的 video 列表
+func (v *VideoScanAndRefreshHelper) GetUpdateVideoListFromEmby() ([]emby.EmbyMixInfo, map[string][]emby.EmbyMixInfo, error) {
+	if v.embyHelper == nil {
+		return nil, nil, nil
+	}
+	defer func() {
+		v.log.Infoln("GetUpdateVideoListFromEmby End")
+	}()
+	v.log.Infoln("GetUpdateVideoListFromEmby Start...")
+	//------------------------------------------------------
+	var err error
+	var movieList []emby.EmbyMixInfo
+	var seriesSubNeedDlMap map[string][]emby.EmbyMixInfo //  多个需要搜索字幕的连续剧目录,连续剧文件夹名称 -- 每一集的 EmbyMixInfo List
+	movieList, seriesSubNeedDlMap, err = v.embyHelper.GetRecentlyAddVideoListWithNoChineseSubtitle()
+	if err != nil {
+		return nil, nil, err
+	}
+	// 输出调试信息
+	v.log.Debugln("GetUpdateVideoListFromEmby - DebugInfo - movieFileFullPathList Start")
+	for _, info := range movieList {
+		v.log.Debugln(info.PhysicalVideoFileFullPath)
+	}
+	v.log.Debugln("GetUpdateVideoListFromEmby - DebugInfo - movieFileFullPathList End")
+
+	v.log.Debugln("GetUpdateVideoListFromEmby - DebugInfo - seriesSubNeedDlMap Start")
+	for s := range seriesSubNeedDlMap {
+		v.log.Debugln(s)
+	}
+	v.log.Debugln("GetUpdateVideoListFromEmby - DebugInfo - seriesSubNeedDlMap End")
+
+	return movieList, seriesSubNeedDlMap, nil
+}
+
+func (v *VideoScanAndRefreshHelper) RefreshEmbySubList() error {
+
+	if v.embyHelper == nil {
+		return nil
+	}
+
+	bRefresh := false
+	defer func() {
+		if bRefresh == true {
+			v.log.Infoln("Refresh Emby Sub List Success")
+		} else {
+			v.log.Errorln("Refresh Emby Sub List Error")
+		}
+	}()
+	v.log.Infoln("Refresh Emby Sub List Start...")
+	//------------------------------------------------------
+	bRefresh, err := v.embyHelper.RefreshEmbySubList()
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (v *VideoScanAndRefreshHelper) RestoreFixTimelineBK() error {
+
+	defer v.log.Infoln("End Restore Fix Timeline BK")
+	v.log.Infoln("Start Restore Fix Timeline BK...")
+	//------------------------------------------------------
+	_, err := subTimelineFixerPKG.Restore(v.settings.CommonSettings.MoviePaths, v.settings.CommonSettings.SeriesPaths)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+type ScanVideoResult struct {
+	Normal *NormalScanVideoResult
+	Emby   *EmbyScanVideoResult
+}
+
+type NormalScanVideoResult struct {
+	MovieFileFullPathList []string
+	SeriesDirMap          *treemap.Map
+}
+
+type EmbyScanVideoResult struct {
+	MovieSubNeedDlEmbyMixInfoList []emby.EmbyMixInfo
+	SeriesSubNeedDlEmbyMixInfoMap map[string][]emby.EmbyMixInfo
+}

+ 1 - 1
internal/types/task_queue/job_status.go

@@ -4,7 +4,7 @@ type JobStatus int
 
 
 const (
 const (
 	Waiting   JobStatus = iota // 任务正在等待处理
 	Waiting   JobStatus = iota // 任务正在等待处理
-	Committed                  // 任务已经提交
+	Committed                  // 任务已经提交,这个可能是提交给服务器,然后等待查询下载 Local 的本地任务不会使用这个标注位
 	Failed                     // 任务失败了,在允许的范围内依然会允许重试
 	Failed                     // 任务失败了,在允许的范围内依然会允许重试
 	Done                       // 任务完成
 	Done                       // 任务完成
 )
 )

+ 44 - 24
internal/types/task_queue/task_queue.go

@@ -1,7 +1,8 @@
 package task_queue
 package task_queue
 
 
 import (
 import (
-	"github.com/allanpk716/ChineseSubFinder/internal/pkg/decode"
+	"crypto/sha1"
+	"fmt"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/my_util"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_file_hash"
 	"github.com/allanpk716/ChineseSubFinder/internal/pkg/sub_file_hash"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/common"
 	"github.com/allanpk716/ChineseSubFinder/internal/types/common"
@@ -10,42 +11,61 @@ import (
 )
 )
 
 
 type OneJob struct {
 type OneJob struct {
-	Id           string           `json:"id"`                        // 任务的唯一 ID
-	VideoType    common.VideoType `json:"video_type"`                // 视频的类型
-	VideoFPath   string           `json:"video_f_path"`              // 视频的全路径
-	VideoName    string           `json:"video_name"`                // 视频的名称
-	Feature      string           `json:"feature"`                   // 视频的特征码,蓝光的时候可能是空
-	Season       int              `json:"season"`                    // 如果对应的是电影则可能是 0,没有
-	Episode      int              `json:"episode"`                   // 如果对应的是电影则可能是 0,没有
-	JobStatus    JobStatus        `json:"job_status"`                // 任务的状态
-	TaskPriority int              `json:"task_priority" default:"5"` // 任务的优先级,0 - 10 个级别,0 是最高,10 是最低
-	RetryTimes   int              `json:"retry_times"`               // 重试了多少次
-	AddedTime    time.Time        `json:"added_time"`                // 任务添加的时间
-	UpdateTime   time.Time        `json:"update_time"`               // 任务更新的时间
+	Id                       string           `json:"id"`                           // 任务的唯一 ID
+	VideoType                common.VideoType `json:"video_type"`                   // 视频的类型
+	VideoFPath               string           `json:"video_f_path"`                 // 视频的全路径
+	VideoName                string           `json:"video_name"`                   // 视频的名称
+	Feature                  string           `json:"feature"`                      // 视频的特征码,蓝光的时候可能是空
+	SeriesRootDirPath        string           `json:"series_root_dir_path"`         // 连续剧的目录
+	Season                   int              `json:"season"`                       // 如果对应的是电影则可能是 0,没有
+	Episode                  int              `json:"episode"`                      // 如果对应的是电影则可能是 0,没有
+	JobStatus                JobStatus        `json:"job_status"`                   // 任务的状态
+	TaskPriority             int              `json:"task_priority" default:"5"`    // 任务的优先级,0 - 10 个级别,0 是最高,10 是最低
+	RetryTimes               int              `json:"retry_times"`                  // 重试了多少次
+	AddedTime                time.Time        `json:"added_time"`                   // 任务添加的时间
+	UpdateTime               time.Time        `json:"update_time"`                  // 任务更新的时间
+	MediaServerInsideVideoID string           `json:"media_server_inside_video_id"` // 媒体服务器中,这个视频的 ID,如果是 Emby 就对应它内部这个视频的 ID,后续用于指定刷新视频信息
+	ErrorInfo                string           `json:"error_info"`                   // 这个任务的错误信息
 }
 }
 
 
-func NewOneJob(videoType common.VideoType, videoFPath string, taskPriority int) *OneJob {
+func NewOneJob(videoType common.VideoType, videoFPath string, taskPriority int, MediaServerInsideVideoID ...string) *OneJob {
 
 
 	ob := &OneJob{VideoType: videoType, VideoFPath: videoFPath, TaskPriority: taskPriority}
 	ob := &OneJob{VideoType: videoType, VideoFPath: videoFPath, TaskPriority: taskPriority}
-	ob.Id = my_util.Get2UUID()
+
+	sha1FilePathID := func() string {
+		// ID 由 SHA1 来计算出来作为唯一性
+		h := sha1.New()
+		h.Write([]byte(videoFPath))
+		bs := h.Sum(nil)
+		return fmt.Sprintf("%x", bs)
+	}
+
+	// 如果 videoFPath 存在,那么就计算这个文件的唯一ID,使用内部的算法
+	// 如果 videoFPath 不存在,那么就是蓝光伪造的地址,就使用这个伪造的路径进行 sha1 加密即可
+	if my_util.IsFile(videoFPath) == true {
+		sha1String, err := sub_file_hash.Calculate(videoFPath)
+		if err != nil {
+			ob.Id = sha1FilePathID()
+		} else {
+			ob.Id = sha1String
+		}
+	} else {
+		ob.Id = sha1FilePathID()
+	}
+
 	ob.VideoName = filepath.Base(videoFPath)
 	ob.VideoName = filepath.Base(videoFPath)
 	// -------------------------------------------------
 	// -------------------------------------------------
 	// 使用本程序的 hash 的算法,得到视频的唯一 ID
 	// 使用本程序的 hash 的算法,得到视频的唯一 ID
 	ob.Feature, _ = sub_file_hash.Calculate(videoFPath)
 	ob.Feature, _ = sub_file_hash.Calculate(videoFPath)
 	// -------------------------------------------------
 	// -------------------------------------------------
-	if videoType == common.Series {
-		// 连续剧的时候,如果可能应该获取是 第几季  第几集
-		torrentInfo, _, err := decode.GetVideoInfoFromFileFullPath(videoFPath)
-		if err == nil {
-			ob.Season = torrentInfo.Season
-			ob.Episode = torrentInfo.Episode
-		}
-	}
-	// -------------------------------------------------
 	ob.JobStatus = Waiting
 	ob.JobStatus = Waiting
 	nTime := time.Now()
 	nTime := time.Now()
 	ob.AddedTime = nTime
 	ob.AddedTime = nTime
 	ob.UpdateTime = nTime
 	ob.UpdateTime = nTime
 
 
+	if len(MediaServerInsideVideoID) > 0 && MediaServerInsideVideoID[0] != "" {
+		ob.MediaServerInsideVideoID = MediaServerInsideVideoID[0]
+	}
+
 	return ob
 	return ob
 }
 }