提交 84a04fd0 编写于 作者: P peterq

add: 下载开始暂停

上级 b76d96e0
...@@ -3,9 +3,12 @@ package downloader ...@@ -3,9 +3,12 @@ package downloader
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/pkg/errors"
"log" "log"
"net/http" "net/http"
"os"
"sync" "sync"
"sync/atomic"
"time" "time"
) )
...@@ -38,7 +41,7 @@ func (m *Manager) Init() error { ...@@ -38,7 +41,7 @@ func (m *Manager) Init() error {
// 从磁盘文件恢复, 下载中的任务 // 从磁盘文件恢复, 下载中的任务
func (m *Manager) Resume( func (m *Manager) Resume(
raw map[TaskId][]byte, raw map[TaskId]string,
requestDecorator func(*http.Request) *http.Request) error { requestDecorator func(*http.Request) *http.Request) error {
for id, bin := range raw { for id, bin := range raw {
task := &Task{ task := &Task{
...@@ -54,6 +57,8 @@ func (m *Manager) Resume( ...@@ -54,6 +57,8 @@ func (m *Manager) Resume(
err := task.resume(bin) err := task.resume(bin)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} else {
m.taskMap[id] = task
} }
} }
return nil return nil
...@@ -81,7 +86,7 @@ func (m *Manager) NewTask(fileId, savePath string, ...@@ -81,7 +86,7 @@ func (m *Manager) NewTask(fileId, savePath string,
state: WaitStart, state: WaitStart,
} }
m.taskMap[id] = task m.taskMap[id] = task
go task.start() //go task.start()
return return
} }
...@@ -96,19 +101,57 @@ func (*Manager) StartAll() error { ...@@ -96,19 +101,57 @@ func (*Manager) StartAll() error {
} }
// 暂停任务 // 暂停任务
func (*Manager) PauseTask(id TaskId) error { func (m *Manager) PauseTask(id TaskId) error {
t, ok := m.taskMap[id]
return nil if !ok {
return errors.New("task 不存在")
}
return t.pause()
} }
// 开始任务 // 开始任务
func (*Manager) StartTask(id TaskId) error { func (m *Manager) StartTask(id TaskId) error {
return nil t, ok := m.taskMap[id]
if !ok {
return errors.New("task 不存在")
}
return t.start()
} }
// 取消任务 // 取消任务
func (*Manager) CancelTask(id TaskId) error { func (m *Manager) CancelTask(id TaskId) error {
return nil t, ok := m.taskMap[id]
if !ok {
return errors.New("task 不存在")
}
t.deleteFileWhenStop = true
delete(m.taskMap, id)
err := t.pause()
if err != nil {
os.Remove(t.savePath)
}
return err
}
// get state
func (m *Manager) State(id TaskId) map[string]interface{} {
t, ok := m.taskMap[id]
if !ok {
return nil
}
return map[string]interface{}{
"state": t.state,
"progress": atomic.LoadInt64(&t.downloadCount),
}
}
// get progress
func (m *Manager) Progress(id TaskId) int64 {
t, ok := m.taskMap[id]
if !ok {
return 0
}
return atomic.LoadInt64(&t.downloadCount)
} }
// 获取一个缓存 // 获取一个缓存
......
...@@ -3,6 +3,7 @@ package downloader ...@@ -3,6 +3,7 @@ package downloader
import ( import (
"bytes" "bytes"
"context" "context"
"encoding/base64"
"fmt" "fmt"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/peterq/pan-light/pc/downloader/internal" "github.com/peterq/pan-light/pc/downloader/internal"
...@@ -63,6 +64,7 @@ type Task struct { ...@@ -63,6 +64,7 @@ type Task struct {
fileHandle *os.File fileHandle *os.File
cancelSpeedCoroutine context.CancelFunc cancelSpeedCoroutine context.CancelFunc
speedCoroutineContext context.Context speedCoroutineContext context.Context
deleteFileWhenStop bool // 删除文件标识
} }
func (task *Task) Id() TaskId { func (task *Task) Id() TaskId {
...@@ -137,17 +139,15 @@ Loop: ...@@ -137,17 +139,15 @@ Loop:
break Loop break Loop
case <-t: case <-t:
cnt := atomic.SwapInt64(&task.speedCount, 0) cnt := atomic.SwapInt64(&task.speedCount, 0)
task.notifyEvent("task.speed", cnt) p := atomic.LoadInt64(&task.downloadCount)
atomic.SwapInt64(&task.speed, cnt) task.notifyEvent("task.speed", map[string]interface{}{
"speed": cnt,
"progress": p,
})
} }
} }
} }
// 获取当前速度
func (task *Task) getSpeed() int64 {
return atomic.LoadInt64(&task.speed)
}
// 开始一个任务 // 开始一个任务
func (task *Task) start() (err error) { func (task *Task) start() (err error) {
if task.state != WaitStart { if task.state != WaitStart {
...@@ -231,7 +231,7 @@ func (task *Task) capture() { ...@@ -231,7 +231,7 @@ func (task *Task) capture() {
} }
task.lastCaptureTime = time.Now() task.lastCaptureTime = time.Now()
c := &internal.TaskCapture{ c := &internal.TaskCapture{
Fid: string(task.id), Fid: string(task.fileId),
SavePath: task.savePath, SavePath: task.savePath,
Completed: []*internal.FinishSeg{}, Completed: []*internal.FinishSeg{},
Length: task.fileLength, Length: task.fileLength,
...@@ -247,7 +247,7 @@ func (task *Task) capture() { ...@@ -247,7 +247,7 @@ func (task *Task) capture() {
log.Println("快照编码错误", err) log.Println("快照编码错误", err)
return return
} }
task.notifyEvent("capture", string(bin)) task.notifyEvent("task.capture", base64.StdEncoding.EncodeToString(bin))
} }
// 下载出错, 放回片段到未下载 // 下载出错, 放回片段到未下载
...@@ -306,6 +306,10 @@ func (task *Task) onAllWorkerExit() { ...@@ -306,6 +306,10 @@ func (task *Task) onAllWorkerExit() {
} }
task.updateState(st) task.updateState(st)
log.Println(task.undistributed) log.Println(task.undistributed)
if task.deleteFileWhenStop {
os.Remove(task.savePath)
log.Println("delete", task.savePath)
}
} }
// 通知事件给外部 // 通知事件给外部
...@@ -323,6 +327,7 @@ func (task *Task) updateState(state TaskState) { ...@@ -323,6 +327,7 @@ func (task *Task) updateState(state TaskState) {
data := map[string]interface{}{ data := map[string]interface{}{
"state": state, "state": state,
} }
data["progress"] = atomic.LoadInt64(&task.downloadCount)
if state == ERRORED { if state == ERRORED {
data["error"] = task.lastErr.Error() data["error"] = task.lastErr.Error()
} }
...@@ -330,7 +335,7 @@ func (task *Task) updateState(state TaskState) { ...@@ -330,7 +335,7 @@ func (task *Task) updateState(state TaskState) {
} }
// 恢复任务 // 恢复任务
func (task *Task) resume(bin []byte) (err error) { func (task *Task) resume(str string) (err error) {
if task.state != WaitResume { if task.state != WaitResume {
return errors.New("任务当前状态不能resume") return errors.New("任务当前状态不能resume")
} }
...@@ -340,6 +345,7 @@ func (task *Task) resume(bin []byte) (err error) { ...@@ -340,6 +345,7 @@ func (task *Task) resume(bin []byte) (err error) {
task.updateState(ERRORED) task.updateState(ERRORED)
} }
}() }()
bin, err := base64.StdEncoding.DecodeString(str)
var data internal.TaskCapture var data internal.TaskCapture
err = proto.Unmarshal(bin, &data) err = proto.Unmarshal(bin, &data)
if err != nil { if err != nil {
...@@ -355,6 +361,7 @@ func (task *Task) resume(bin []byte) (err error) { ...@@ -355,6 +361,7 @@ func (task *Task) resume(bin []byte) (err error) {
finish: seg.Len, finish: seg.Len,
}) })
} }
task.init()
task.updateState(WaitStart) task.updateState(WaitStart)
return nil return nil
} }
......
...@@ -19,12 +19,24 @@ var downloadSyncRoutes = map[string]syncHandler{ ...@@ -19,12 +19,24 @@ var downloadSyncRoutes = map[string]syncHandler{
return fmt.Sprint(taskId) return fmt.Sprint(taskId)
}, },
"download.resume": func(p map[string]interface{}) interface{} { "download.resume": func(p map[string]interface{}) interface{} {
err := pan_download.Resume(p["downloadId"].(string), []byte(p["bin"].(string))) err := pan_download.Resume(p["downloadId"].(string), p["bin"].(string))
if err != nil { if err != nil {
return err return err
} }
return true return true
}, },
"download.state": func(p map[string]interface{}) interface{} {
return pan_download.State(p["downloadId"].(string))
},
"download.start": func(p map[string]interface{}) interface{} {
return pan_download.Start(p["downloadId"].(string))
},
"download.pause": func(p map[string]interface{}) interface{} {
return pan_download.Pause(p["downloadId"].(string))
},
"download.delete": func(p map[string]interface{}) interface{} {
return pan_download.Delete(p["downloadId"].(string))
},
} }
var downloadAsyncRoutes = map[string]asyncHandler{} var downloadAsyncRoutes = map[string]asyncHandler{}
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M12 38h8V10h-8v28zm16-28v28h8V10h-8z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M16 10v28l22-14z"/></svg>
\ No newline at end of file
import QtQuick 2.0
import QtQuick.Controls 2.1
import '../widget'
Button {
id: iconBtn
width: 25
height: width
anchors.verticalCenter: parent.verticalCenter
property alias iconType: icon.type
property alias title: toolTip.text
property color color: 'black'
property real lighter: 1.5
ToolTip {
id: toolTip
show: iconBtn.hovered
text: ''
}
IconFont {
id: icon
type: ''
width: parent.width
color: iconBtn.hovered ? iconBtn.color : Qt.lighter(iconBtn.color, lighter)
}
display: AbstractButton.IconOnly
background: Item {
}
}
...@@ -36,7 +36,6 @@ Window { ...@@ -36,7 +36,6 @@ Window {
} }
Login{} Login{}
Component.onCompleted: { Component.onCompleted: {
// 初始化js工具 // 初始化js工具
G.init(mainWindow) G.init(mainWindow)
App.appState.mainWindow = mainWindow App.appState.mainWindow = mainWindow
......
...@@ -149,5 +149,8 @@ ...@@ -149,5 +149,8 @@
<file>comps/confirm-window.qml</file> <file>comps/confirm-window.qml</file>
<file>comps/select-save-path.qml</file> <file>comps/select-save-path.qml</file>
<file>transfer/DownloadList.qml</file> <file>transfer/DownloadList.qml</file>
<file>comps/IconButton.qml</file>
<file>assets/images/icons/pause.svg</file>
<file>assets/images/icons/start.svg</file>
</qresource> </qresource>
</RCC> </RCC>
...@@ -6,7 +6,7 @@ import "../js/util.js" as Util ...@@ -6,7 +6,7 @@ import "../js/util.js" as Util
Item { Item {
id: root id: root
height: 50 height: topRow.height + bottomRow.height
width: parent.width width: parent.width
property bool isFinish property bool isFinish
property string downloadId: meta.downloadId property string downloadId: meta.downloadId
...@@ -14,19 +14,47 @@ Item { ...@@ -14,19 +14,47 @@ Item {
property int idx property int idx
property string resumeData: '' property string resumeData: ''
property bool isNewAdd: true property bool isNewAdd: true
property string downloadState: ''
property int progress: 0
property string errString: ''
signal taskEvent(string event, var data) signal taskEvent(string event, var data)
property string speed: '' property string speed: ''
Component.onCompleted: { DataSaver {
$key: 'download-item-' + root.downloadId
property alias resumeData: root.resumeData
property alias isNewAdd: root.isNewAdd
Component.onCompleted: {
root.dataSaverOk()
}
}
function dataSaverOk() {
if (isFinish) return
if (!isNewAdd) { if (!isNewAdd) {
console.log('恢复任务', downloadId) console.log('恢复任务', downloadId)
Util.callGoSync('download.resume', { var res = Util.callGoSync('download.resume', {
"downloadId": downloadId, "downloadId": downloadId,
"bin": resumeData "bin": resumeData
})
} else {
isNewAdd = false
Util.callGoSync('download.start', {
"downloadId": downloadId
}) })
} }
updateState()
}
function updateState() {
downloadState = ''
var data = Util.callGoSync('download.state', {
"downloadId": downloadId
})
downloadState = data.state
progress = data.progress
} }
Timer { Timer {
...@@ -38,55 +66,152 @@ Item { ...@@ -38,55 +66,152 @@ Item {
} }
onTaskEvent: { onTaskEvent: {
// 更新下载速度
if (event === 'task.speed') { if (event === 'task.speed') {
speed = Util.humanSize(data) + '/s' speed = Util.humanSize(data.speed) + '/s'
speedClearTimer.restart() speedClearTimer.restart()
progress = data.progress
return
} }
}
DataSaver { // 下载快照
$key: 'download-item-' + root.downloadId if (event === 'task.capture') {
property alias resumeData: root.resumeData resumeData = data
property alias isNewAdd: root.isNewAdd return
}
// 下载状态
if (event === 'task.state') {
downloadState = data.state
progress = data.progress
if (downloadState === 'errored')
errString = data.error
if (downloadState === 'completed')
App.appState.transferComp.itemCompleted(idx)
return
}
} }
function getMenus() { function getMenus() {
root.meta.path = '' root.meta.path = ''
return [] return []
} }
FileIcon {
id: fileIcon
width: parent.height
height: width
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 10
type: root.meta.saveName.split('.').pop()
}
Text {
id: fileNameText
text: root.meta.saveName
anchors.verticalCenter: parent.verticalCenter
anchors.left: fileIcon.right
anchors.leftMargin: 5
width: parent.width * 0.5
elide: Text.ElideRight
}
Text { Item {
anchors.verticalCenter: parent.verticalCenter id: topRow
anchors.left: fileNameText.right width: parent.width - 20
text: speed height: 50
x: 10
FileIcon {
id: fileIcon
width: parent.height
height: width
anchors.verticalCenter: parent.verticalCenter
type: root.meta.saveName.split('.').pop()
}
Text {
id: fileNameText
text: root.meta.saveName
anchors.verticalCenter: parent.verticalCenter
anchors.left: fileIcon.right
anchors.leftMargin: 5
width: parent.width * 0.4
elide: Text.ElideRight
}
MouseArea {
hoverEnabled: true
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if (mouse.button === Qt.RightButton)
Util.showMenu(getMenus())
}
}
Row {
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
spacing: 5
Text {
text: downloadState
}
IconButton {
iconType: 'start'
title: '开始'
color: enabled ? '#409EFF' : 'gray'
lighter: 1.1
visible: !isFinish
enabled: downloadState === 'wait.start'
onClicked: {
Util.callGoSync('download.start', {
"downloadId": downloadId
})
updateState()
}
}
IconButton {
iconType: 'pause'
title: '暂停'
color: enabled ? '#409EFF' : 'gray'
lighter: 1.1
visible: !isFinish
enabled: downloadState === 'downloading'
onClicked: {
Util.callGoSync('download.pause', {
"downloadId": downloadId
})
updateState()
}
}
IconButton {
iconType: 'delete'
title: '删除'
onClicked: {
Util.callGoSync('download.delete', {
"downloadId": downloadId
})
App.appState.transferComp.deleteItem(idx, isFinish)
}
}
}
} }
MouseArea { Item {
hoverEnabled: true id: bottomRow
anchors.fill: parent width: parent.width - 20
acceptedButtons: Qt.LeftButton | Qt.RightButton x: 10
onClicked: { visible: !isFinish
if (mouse.button === Qt.RightButton) height: visible ? 30 : 0
Util.showMenu(getMenus()) y: parent.height / 2
Text {
id: progressText
width: 300
x: 50
anchors.verticalCenter: parent.verticalCenter
text: (progress == 0 ? '' : Util.humanSize(
progress) + '/') + Util.humanSize(
meta.size)
}
Text {
id: speedText
anchors.verticalCenter: parent.verticalCenter
anchors.left: progressText.right
text: speed
color: 'orange'
width: 300
}
Text {
anchors.left: speedText.right
visible: downloadState === 'errored'
text: errString
color: 'red'
} }
} }
Rectangle { Rectangle {
width: parent.width width: parent.width
height: 1 height: 1
......
...@@ -73,4 +73,12 @@ Rectangle { ...@@ -73,4 +73,12 @@ Rectangle {
function add(data) { function add(data) {
updateList(Util.listModelAdd(listModel, data)) updateList(Util.listModelAdd(listModel, data))
} }
function get(idx) {
return listModel.get(idx)
}
function remove(idx) {
updateList(Util.listModelRemove(listModel, idx))
}
} }
import QtQuick 2.0 import QtQuick 2.0
import QtQuick.Controls 2.0 import QtQuick.Controls 2.0
import "../js/app.js" as App
Item { Item {
width: parent.width - 20 width: parent.width - 20
...@@ -8,7 +9,7 @@ Item { ...@@ -8,7 +9,7 @@ Item {
property alias currentTab: typeTab.currentTab property alias currentTab: typeTab.currentTab
Text { Text {
id: taskLeft id: taskLeft
text: "剩余任务: 51" text: "剩余任务: " + App.appState.downloadingList.length
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
......
...@@ -22,6 +22,7 @@ Item { ...@@ -22,6 +22,7 @@ Item {
height: parent.height - headerBar.height height: parent.height - headerBar.height
} }
DownloadList { DownloadList {
id: downloadedList
visible: headerBar.currentTab == '已完成' visible: headerBar.currentTab == '已完成'
isFinish: true isFinish: true
anchors.top: headerBar.bottom anchors.top: headerBar.bottom
...@@ -65,4 +66,15 @@ Item { ...@@ -65,4 +66,15 @@ Item {
downloadingList.add(obj) downloadingList.add(obj)
}) })
} }
function deleteItem(idx, isFinish) {
var c = isFinish ? downloadedList : downloadingList
c.remove(idx)
}
function itemCompleted(idx) {
var data = JSON.parse(JSON.stringify(downloadingList.get(idx)))
downloadingList.remove(idx)
downloadedList.add(data)
}
} }
...@@ -194,7 +194,8 @@ func Link(fid string) (link string, err error) { ...@@ -194,7 +194,8 @@ func Link(fid string) (link string, err error) {
return return
} }
if data["errno"].(float64) != 0 { if data["errno"].(float64) != 0 {
err = errors.New("获取文件夹信息错误, 错误码" + fmt.Sprint(data["errno"])) err = errors.New("获取文件信息错误, 错误码" + fmt.Sprint(data["errno"]))
return
} }
link = data["dlink"].([]interface{})[0].(map[string]interface{})["dlink"].(string) link = data["dlink"].([]interface{})[0].(map[string]interface{})["dlink"].(string)
link = getRedirectedLink(link) link = getRedirectedLink(link)
......
...@@ -18,7 +18,7 @@ var manager *downloader.Manager ...@@ -18,7 +18,7 @@ var manager *downloader.Manager
func init() { func init() {
dep.OnInit(func() { dep.OnInit(func() {
parallel := 128 parallel := 1
manager = &downloader.Manager{ manager = &downloader.Manager{
CoroutineNumber: parallel, CoroutineNumber: parallel,
SegmentSize: 1024 * 1024 * 2, SegmentSize: 1024 * 1024 * 2,
...@@ -37,17 +37,11 @@ func init() { ...@@ -37,17 +37,11 @@ func init() {
go handleDownloadEvent(evt) go handleDownloadEvent(evt)
} }
}() }()
go test() //go test()
}) })
} }
func handleDownloadEvent(event *downloader.DownloadEvent) { func handleDownloadEvent(event *downloader.DownloadEvent) {
if event.Event == "task.speed" {
speed := float64(event.Data.(int64))
log.Println(speed / 1024 / 1024)
} else {
//log.Println(event)
}
dep.NotifyQml("task.event", map[string]interface{}{ dep.NotifyQml("task.event", map[string]interface{}{
"type": event.Event, "type": event.Event,
"taskId": event.TaskId, "taskId": event.TaskId,
...@@ -78,12 +72,32 @@ func requestDecorator(request *http.Request) *http.Request { ...@@ -78,12 +72,32 @@ func requestDecorator(request *http.Request) *http.Request {
return request return request
} }
func Resume(id string, bin []byte) error { func Resume(id string, bin string) error {
return manager.Resume(map[downloader.TaskId][]byte{ return manager.Resume(map[downloader.TaskId]string{
downloader.TaskId(id): bin, downloader.TaskId(id): bin,
}, requestDecorator) }, requestDecorator)
} }
func State(id string) interface{} {
return manager.State(downloader.TaskId(id))
}
func Start(id string) error {
return manager.StartTask(downloader.TaskId(id))
}
func Pause(id string) error {
return manager.PauseTask(downloader.TaskId(id))
}
func Delete(id string) error {
return manager.CancelTask(downloader.TaskId(id))
}
func Progress(id string) int64 {
return manager.Progress(downloader.TaskId(id))
}
func fileCompare() { func fileCompare() {
f1, err := os.OpenFile("/home/peterq/dev/projects/go/github.com/peterq/pan-light/pc/yx.mp4", os.O_RDONLY, 0644) f1, err := os.OpenFile("/home/peterq/dev/projects/go/github.com/peterq/pan-light/pc/yx.mp4", os.O_RDONLY, 0644)
if err != nil { if err != nil {
......
/demo
pan-light-server.yaml
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册