291 lines
6.9 KiB
Go
Raw Normal View History

2019-04-22 02:59:20 +00:00
package archive
import (
"sync"
xtime "time"
"go-common/library/log"
"go-common/library/time"
)
// const Video Status
const (
// video xcode and dispatch state.
VideoUploadInfo = int8(0)
VideoXcodeSDFail = int8(1)
VideoXcodeSDFinish = int8(2)
VideoXcodeHDFail = int8(3)
VideoXcodeHDFinish = int8(4)
VideoDispatchRunning = int8(5)
VideoDispatchFinish = int8(6)
// video status.
VideoStatusOpen = int16(0)
VideoStatusAccess = int16(10000)
VideoStatusWait = int16(-1)
VideoStatusRecicle = int16(-2)
VideoStatusLock = int16(-4)
VideoStatusXcodeFail = int16(-16)
VideoStatusSubmit = int16(-30)
VideoStatusUploadSubmit = int16(-50)
VideoStatusDelete = int16(-100)
// xcode fail
XcodeFailZero = 0
)
// Video is archive_video model.
type Video struct {
ID int64 `json:"-"`
Aid int64 `json:"aid"`
Title string `json:"title"`
Desc string `json:"desc"`
Filename string `json:"filename"`
SrcType string `json:"src_type"`
Cid int64 `json:"cid"`
Sid int64 `json:"-"`
Duration int64 `json:"duration"`
Filesize int64 `json:"-"`
Resolutions string `json:"-"`
Index int `json:"index"`
Playurl string `json:"-"`
Status int16 `json:"status"`
FailCode int8 `json:"fail_code"`
XcodeState int8 `json:"xcode_state"`
Attribute int32 `json:"-"`
RejectReason string `json:"reject_reason"`
CTime time.Time `json:"ctime"`
MTime time.Time `json:"-"`
Dimension *Dimension `json:"dimension"`
}
// Dimension Archive video dimension
type Dimension struct {
Width int64 `json:"width"`
Height int64 `json:"height"`
Rotate int64 `json:"rotate"`
}
// SimpleVideo for Archive History
type SimpleVideo struct {
Cid int64 `json:"cid"`
Index int `json:"part_id"`
Title string `json:"part_name"`
Status int16 `json:"status"`
MTime time.Time `json:"dm_modified"`
}
// VideoFn for Archive Video table filename check
type VideoFn struct {
Cid int64 `json:"cid"`
Filename string `json:"filename"`
CTime time.Time `json:"ctime"`
MTime time.Time `json:"mtime"`
}
type param struct {
undoneCount int // 待完成数总和
failCount int // 错误数总和
timeout *xtime.Timer
}
/*VideosEditor 处理超多分p的稿件编辑
* 0.对该稿件的编辑操作加锁
* 1.将所有视频分组分事务更新
* 2.全部更新成功后发送成功信号量
* 3.更新错误的分组多次尝试超时或超过次数后失败
* 4.收到更新成功信号量进行回调-1.同步信息给视频云 2.解锁该稿件编辑
* 5.收到更新失败信号量进行回调- (1.记录错误日志信息 2.推送错误消息 3.解锁该稿件编辑)
*/
type VideosEditor struct {
sync.Mutex
failTH int
closeFlag bool
wg sync.WaitGroup
params map[int64]*param
cbSuccess map[int64]func()
cbFail map[int64]func()
sigSuccess chan int64
sigFail chan int64
chanRetry chan func() (int64, int, int, error)
closechan, cbclose chan struct{}
}
// NewEditor new VideosEditor
func NewEditor(failTH int) *VideosEditor {
editor := &VideosEditor{
failTH: failTH,
wg: sync.WaitGroup{},
params: make(map[int64]*param),
cbSuccess: make(map[int64]func()),
cbFail: make(map[int64]func()),
sigSuccess: make(chan int64, 10),
sigFail: make(chan int64, 10),
chanRetry: make(chan func() (int64, int, int, error), 100),
closechan: make(chan struct{}, 1),
cbclose: make(chan struct{}),
}
editor.wg.Add(1)
go editor.consumerRetry(&editor.wg)
editor.wg.Add(1)
go editor.consumercb(&editor.wg)
return editor
}
// Close 等待所有消息消费完才退出
func (m *VideosEditor) Close() {
m.closeFlag = true
m.closechan <- struct{}{}
m.wg.Wait()
}
// Add add to editor
func (m *VideosEditor) Add(aid int64, cbSuccess, cbFail func(), timeout xtime.Duration, retrys ...func() (int64, int, int, error)) {
if m.closeFlag {
log.Warn("VideosEditor closed")
return
}
log.Info("VideosEditor Add(%d) len(%d)", aid, len(retrys))
timer := xtime.AfterFunc(timeout, func() { m.notifyTimeout(aid) })
m.params[aid] = &param{
undoneCount: len(retrys),
failCount: 0,
timeout: timer,
}
m.cbSuccess[aid] = cbSuccess
m.cbFail[aid] = cbFail
for _, ry := range retrys {
m.addRetry(ry, 0)
}
}
// NotifySuccess notify success
func (m *VideosEditor) notifySuccess(aid int64) {
m.Lock()
defer m.Unlock()
param, ok := m.params[aid]
if ok {
param.undoneCount--
if param.undoneCount <= 0 {
log.Info("notifySuccess(%d) undoneCount(%d)", aid, param.undoneCount)
m.sigSuccess <- aid
}
}
}
// NotifyFail 返回值表示达到触发阈值
func (m *VideosEditor) notifyFail(aid int64) (retry bool) {
m.Lock()
defer m.Unlock()
param, ok := m.params[aid]
if ok {
retry = true
param.failCount++
if param.failCount >= m.failTH {
log.Warn("notifyFail(%d) failCount(%d)", aid, param.failCount)
retry = false
if _, ok := m.cbFail[aid]; ok {
m.sigFail <- aid
}
}
}
return
}
func (m *VideosEditor) notifyTimeout(aid int64) {
log.Warn("notifyTimeout(%d)", aid)
m.Lock()
defer m.Unlock()
_, ok := m.params[aid]
if ok {
if _, ok := m.cbFail[aid]; ok {
m.sigFail <- aid
}
}
}
func (m *VideosEditor) consumercb(g *sync.WaitGroup) {
defer g.Done()
for {
select {
case aid, ok := <-m.sigSuccess:
if !ok {
log.Info("consumercb close")
return
}
if f, ok := m.cbSuccess[aid]; ok {
f()
m.release(aid)
}
case aid, ok := <-m.sigFail:
if !ok {
log.Info("consumercb close")
return
}
if f, ok := m.cbFail[aid]; ok {
f()
m.release(aid)
}
case <-m.closechan:
if len(m.cbFail) == 0 && len(m.cbSuccess) == 0 {
m.cbclose <- struct{}{}
log.Info("consumercb closechan")
return
}
xtime.Sleep(50 * xtime.Millisecond)
m.closechan <- struct{}{}
}
}
}
func (m *VideosEditor) consumerRetry(g *sync.WaitGroup) {
defer g.Done()
for {
log.Info("consumerRetry")
select {
case <-m.cbclose:
log.Info("consumerRetry closed")
return
case f, ok := <-m.chanRetry:
if !ok {
log.Info("m.chanRetry closed")
break
}
id, head, tail, err := f()
log.Info("consumerRetry(%d) head(%d) tail(%d) err(%v) ", id, head, tail, err)
if err != nil {
if m.notifyFail(id) {
go func() {
xtime.Sleep(3 * xtime.Second)
m.addRetry(f, 3)
}()
}
} else {
m.notifySuccess(id)
}
}
}
}
func (m *VideosEditor) addRetry(f func() (int64, int, int, error), asynctime int) {
if asynctime == 0 {
m.chanRetry <- f
return
}
go func() {
xtime.Sleep(3 * xtime.Second)
m.chanRetry <- f
}()
}
func (m *VideosEditor) release(aid int64) {
if p, ok := m.params[aid]; ok {
p.timeout.Stop()
}
delete(m.cbFail, aid)
delete(m.cbSuccess, aid)
}