feat: only retrieve 50 download records

This commit is contained in:
ltaoo committed 2026-03-03 00:06:34 +08:00
1 parent 24d0776fee
commit de5b261666
9 files changed
+259 -87

No files matched your search

+1 -1
View File
@@ -69,7 +69,7 @@ CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 SDKROOT=$(xcrun --sdk macosx --show-sdk-p
### Linux
```bash
# CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o wx_video_download
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o wx_video_download
$env:CGO_ENABLED=0; $env:GOOS="linux"; $env:GOARCH="amd64"; go build -trimpath -ldflags="-s -w" -o wx_video_download
+39 -25
View File
@@ -52,6 +52,7 @@ func init() {
root_cmd.AddCommand(serve_cmd)
serve_cmd.AddCommand(server_status_cmd)
serve_cmd.AddCommand(mp_stop_cmd)
serve_cmd.AddCommand(mp_restart_cmd)
}
func serve_command() {
@@ -207,34 +208,47 @@ var mp_stop_cmd = &cobra.Command{
Use: "stop",
Short: "停止公众号服务",
Run: func(cmd *cobra.Command, args []string) {
pid, err := read_wx_pidfile()
if err != nil || pid == 0 {
color.Red("未发现守护进程")
return
}
if !system.IsProcessRunning(pid) {
color.Green("进程已停止")
_ = remove_wx_pidfile()
return
}
_ = system.TerminateProcess(pid)
expire := time.After(8 * time.Second)
tick := time.NewTicker(200 * time.Millisecond)
defer tick.Stop()
for {
select {
case <-tick.C:
if !system.IsProcessRunning(pid) {
_ = remove_wx_pidfile()
color.Green("服务已关闭")
return
}
case <-expire:
color.Red("关闭超时")
stop_daemon()
},
}
var mp_restart_cmd = &cobra.Command{
Use: "restart",
Short: "重启公众号服务",
Run: func(cmd *cobra.Command, args []string) {
stop_daemon()
start_daemon()
},
}
func stop_daemon() {
pid, err := read_wx_pidfile()
if err != nil || pid == 0 {
color.Red("未发现守护进程")
return
}
if !system.IsProcessRunning(pid) {
color.Green("进程已停止")
_ = remove_wx_pidfile()
return
}
_ = system.TerminateProcess(pid)
expire := time.After(8 * time.Second)
tick := time.NewTicker(200 * time.Millisecond)
defer tick.Stop()
for {
select {
case <-tick.C:
if !system.IsProcessRunning(pid) {
_ = remove_wx_pidfile()
color.Green("服务已关闭")
return
}
case <-expire:
color.Red("关闭超时")
return
}
},
}
}
func port_listening(addr string) bool {
+59 -2
View File
@@ -4,6 +4,7 @@ import (
"encoding/json"
"net/http"
"os"
"sort"
"strconv"
"time"
@@ -41,13 +42,69 @@ func NewAPIClient(cfg *APIConfig, parent_logger *zerolog.Logger) *APIClient {
official_cfg := officialaccount.NewOfficialAccountConfig(cfg.Original, cfg.RemoteServerMode)
officialaccount_client := officialaccount.NewOfficialAccountClient(official_cfg, parent_logger)
channels_client = channels.NewChannelsClient()
get_sorted_tasks := func() []*downloadpkg.Task {
tasks := downloader.GetTasks()
sort.Slice(tasks, func(i, j int) bool {
return tasks[i].CreatedAt.After(tasks[j].CreatedAt)
})
return tasks
}
channels_client.OnConnected = func(client *channels.Client) {
// Initial tasks
tasks := downloader.GetTasks()
if data, err := json.Marshal(APIClientWSMessage{Type: "tasks", Data: tasks}); err == nil {
all_tasks := get_sorted_tasks()
limit := 50
if limit > len(all_tasks) {
limit = len(all_tasks)
}
tasks := all_tasks[:limit]
if data, err := json.Marshal(APIClientWSMessage{Type: "tasks", Data: map[string]interface{}{
"list": tasks,
"total": len(all_tasks),
}}); err == nil {
client.Send <- data
}
}
channels_client.OnMessage = func(client *channels.Client, message []byte) {
var req struct {
Type string `json:"type"`
Page int `json:"page"`
Limit int `json:"limit"`
}
if err := json.Unmarshal(message, &req); err == nil && req.Type == "fetch_tasks" {
allTasks := get_sorted_tasks()
start := (req.Page - 1) * req.Limit
if start < 0 {
start = 0
}
if req.Limit <= 0 {
req.Limit = 50
}
if start >= len(allTasks) {
if data, err := json.Marshal(APIClientWSMessage{Type: "tasks", Data: map[string]interface{}{
"list": []*downloadpkg.Task{},
"total": len(allTasks),
}}); err == nil {
client.Send <- data
}
return
}
end := start + req.Limit
if end > len(allTasks) {
end = len(allTasks)
}
tasks := allTasks[start:end]
if data, err := json.Marshal(APIClientWSMessage{Type: "tasks", Data: map[string]interface{}{
"list": tasks,
"total": len(allTasks),
}}); err == nil {
client.Send <- data
}
}
}
logger := parent_logger.With().Str("Client", "api_client").Logger()
client := &APIClient{
downloader: downloader,
+42 -16
View File
@@ -11,6 +11,7 @@ import (
"net/url"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
@@ -269,10 +270,15 @@ func (c *APIClient) handleCreateFeedDownloadTask(ctx *gin.Context) {
result.Err(ctx, 500, "创建任务失败:"+err.Error())
return
}
c.channels.Broadcast(APIClientWSMessage{
Type: "tasks",
Data: c.downloader.GetTasks(),
})
task := c.downloader.GetTask(id)
if task != nil {
c.channels.Broadcast(APIClientWSMessage{
Type: "event",
Data: map[string]interface{}{
"task": task,
},
})
}
result.Ok(ctx, gin.H{"id": id})
}
@@ -328,6 +334,9 @@ func (c *APIClient) handleFetchTaskList(ctx *gin.Context) {
filter.Statuses = []base.Status{base.Status(status)}
}
list := c.downloader.GetTasksByFilter(filter)
sort.Slice(list, func(i, j int) bool {
return list[i].CreatedAt.After(list[j].CreatedAt)
})
total := len(list)
page_num, err := strconv.Atoi(page_str)
if err != nil {
@@ -429,10 +438,15 @@ func (c *APIClient) handleCreateLiveTask(ctx *gin.Context) {
result.Err(ctx, 500, "创建任务失败: "+err.Error())
return
}
c.channels.Broadcast(APIClientWSMessage{
Type: "tasks",
Data: c.downloader.GetTasks(),
})
task := c.downloader.GetTask(id)
if task != nil {
c.channels.Broadcast(APIClientWSMessage{
Type: "event",
Data: map[string]interface{}{
"task": task,
},
})
}
result.Ok(ctx, gin.H{"id": id})
}
@@ -474,10 +488,17 @@ func (c *APIClient) handleBatchCreateTask(ctx *gin.Context) {
// Int("count", len(task.Reqs)).
// Dur("cost", time.Since(start)).
// Msg("批量创建任务完成")
c.channels.Broadcast(APIClientWSMessage{
Type: "tasks",
Data: c.downloader.GetTasks(),
})
for _, id := range ids {
task := c.downloader.GetTask(id)
if task != nil {
c.channels.Broadcast(APIClientWSMessage{
Type: "event",
Data: map[string]interface{}{
"task": task,
},
})
}
}
result.Ok(ctx, gin.H{"ids": ids})
}
@@ -682,10 +703,15 @@ func (c *APIClient) handleCreateChannelsTask(ctx *gin.Context) {
result.Err(ctx, 500, "下载失败")
return
}
c.channels.Broadcast(APIClientWSMessage{
Type: "tasks",
Data: c.downloader.GetTasks(),
})
task := c.downloader.GetTask(id)
if task != nil {
c.channels.Broadcast(APIClientWSMessage{
Type: "event",
Data: map[string]interface{}{
"task": task,
},
})
}
result.Ok(ctx, gin.H{"id": id})
}
+6
View File
@@ -42,6 +42,7 @@ type ChannelsClient struct {
cache *cache.Cache
req_seq uint64
OnConnected func(client *Client)
OnMessage func(client *Client, message []byte)
}
func NewChannelsClient() *ChannelsClient {
@@ -92,8 +93,13 @@ func (c *ChannelsClient) HandleChannelsWebsocket(ctx *gin.Context) {
c.requests_mu.RUnlock()
if ok {
ch <- resp
continue
}
}
if c.OnMessage != nil {
c.OnMessage(client, message)
}
}
}
func (c *ChannelsClient) Stop() {
@@ -303,7 +303,7 @@ function total_speed(tasks) {
return sum;
}
function __wx_refresh_downloader(selector, tasks) {
function __wx_refresh_downloader(selector, tasks, total) {
const container = document.querySelector(selector);
if (!container) return;
container.innerHTML = "";
@@ -313,7 +313,8 @@ function __wx_refresh_downloader(selector, tasks) {
const countEl = document.getElementById("wx-dl-count");
if (countEl) {
countEl.innerText = list.length > 0 ? `(${list.length})` : "";
const count = total !== undefined ? total : list.length;
countEl.innerText = count > 0 ? `(${count})` : "";
}
if (list.length === 0) {
@@ -470,12 +471,12 @@ function __wx_refresh_downloader(selector, tasks) {
});
if (list.length > 0) {
const footer = document.createElement("div");
footer.className = "weui-loadmore weui-loadmore_line";
footer.style.marginTop = "20px";
footer.innerHTML =
'<span class="weui-loadmore__tips">没有更多内容了</span>';
container.appendChild(footer);
// const footer = document.createElement("div");
// footer.className = "weui-loadmore weui-loadmore_line";
// footer.style.marginTop = "20px";
// footer.innerHTML =
// '<span class="weui-loadmore__tips">没有更多内容了</span>';
// container.appendChild(footer);
}
}
+92 -31
View File
@@ -6,6 +6,12 @@ var ua = navigator.userAgent || navigator.platform || "";
var isWin = /Windows|Win/i.test(ua);
(() => {
const tasks = new Map();
let ws_conn = null;
let cur_page = 1;
let is_loading = false;
let has_more = true;
let total_count = 0;
function upsert(task) {
if (!task || !task.id) return;
tasks.set(task.id, {
@@ -29,17 +35,24 @@ var isWin = /Windows|Win/i.test(ua);
});
}
function connect(selector) {
console.log('[]download connect websocket', FakeAPIServerAddr, WXU.config.remoteServerEnabled);
console.log(
"[]download connect websocket",
FakeAPIServerAddr,
WXU.config.remoteServerEnabled,
);
return new Promise((resolve, reject) => {
// 在注入场景下始终使用 wss 与 fake 域名通信,由代理层降级/转发到后端
const protocol = "wss://";
const pathname = FakeAPIServerAddr;
const ws = new WebSocket(protocol + pathname + "/ws/channels");
ws_conn = ws;
ws.onopen = () => {
if (WXU.downloader) {
WXU.downloader.status = "connected";
}
cur_page = 1;
has_more = true;
is_loading = false;
resolve(true);
};
ws.onclose = () => {
@@ -47,6 +60,7 @@ var isWin = /Windows|Win/i.test(ua);
if (WXU.downloader) {
WXU.downloader.status = "disconnected";
}
ws_conn = null;
};
ws.onerror = (e) => {
if (WXU.downloader && WXU.downloader.status !== "connected") {
@@ -58,26 +72,44 @@ var isWin = /Windows|Win/i.test(ua);
if (err) {
return;
}
console.log('[]remote ws event', msg);
console.log("[]remote ws event", msg);
if (msg.type === "tasks") {
if (Array.isArray(msg.data)) {
msg.data.forEach(upsert);
let list = msg.data;
if (msg.data && msg.data.list) {
list = msg.data.list;
total_count = msg.data.total;
}
__wx_refresh_downloader(selector, tasks);
if (Array.isArray(list)) {
list.reverse().forEach(upsert);
if (list.length < 50) {
has_more = false;
}
} else {
has_more = false;
}
if (is_loading) {
is_loading = false;
cur_page++;
}
__wx_refresh_downloader(selector, tasks, total_count);
return;
}
if (msg.type === "clear") {
tasks.clear();
__wx_refresh_downloader(selector, tasks);
total_count = 0;
__wx_refresh_downloader(selector, tasks, total_count);
return;
}
if (msg.type === "event") {
const evt = msg && msg.data ? msg.data : null;
const task = evt ? evt.Task || evt.task : null; // 兼容大小写字段
if (task) {
if (!tasks.has(task.id)) {
total_count++;
}
upsert(task);
}
__wx_refresh_downloader(selector, tasks);
__wx_refresh_downloader(selector, tasks, total_count);
return;
}
if (msg.type === "api_call") {
@@ -86,24 +118,24 @@ var isWin = /Windows|Win/i.test(ua);
};
if (WXU.config.remoteServerEnabled) {
// 额外再连接本地ws用于API调用
// const lws = new WebSocket(
// protocol + FakeLocalAPIServerAddr + "/ws/channels",
// );
// lws.onclose = () => {
// WXU.error({ msg: "本地ws连接已关闭" });
// };
// lws.onerror = (e) => {
// WXU.error({ msg: "本地ws连接发生错误" });
// };
// lws.onmessage = (ev) => {
// const [err, msg] = WXU.parseJSON(ev.data);
// if (err) {
// return;
// }
// if (msg.type === "api_call") {
// __wx_handle_api_call(msg.data, lws);
// }
// };
const lws = new WebSocket(
protocol + FakeLocalAPIServerAddr + "/ws/channels",
);
lws.onclose = () => {
WXU.error({ msg: "本地ws连接已关闭" });
};
lws.onerror = (e) => {
WXU.error({ msg: "本地ws连接发生错误" });
};
lws.onmessage = (ev) => {
const [err, msg] = WXU.parseJSON(ev.data);
if (err) {
return;
}
if (msg.type === "api_call") {
__wx_handle_api_call(msg.data, lws);
}
};
}
});
}
@@ -125,7 +157,7 @@ var isWin = /Windows|Win/i.test(ua);
const t = tasks.get(id);
if (t) {
tasks.set(id, { ...t, status: "running" });
__wx_refresh_downloader("#downloader_container", tasks);
__wx_refresh_downloader("#downloader_container", tasks, total_count);
}
}
const $task_action_btn = e.target.closest("[data-action]");
@@ -186,18 +218,19 @@ var isWin = /Windows|Win/i.test(ua);
}
if (action === "delete") {
tasks.delete(id);
__wx_refresh_downloader("#downloader_container", tasks);
if (total_count > 0) total_count--;
__wx_refresh_downloader("#downloader_container", tasks, total_count);
} else if (action === "pause") {
const t = tasks.get(id);
if (t) {
tasks.set(id, { ...t, status: "paused" });
__wx_refresh_downloader("#downloader_container", tasks);
__wx_refresh_downloader("#downloader_container", tasks, total_count);
}
} else if (action === "resume") {
const t = tasks.get(id);
if (t) {
tasks.set(id, { ...t, status: "running" });
__wx_refresh_downloader("#downloader_container", tasks);
__wx_refresh_downloader("#downloader_container", tasks, total_count);
}
}
}
@@ -253,7 +286,7 @@ var isWin = /Windows|Win/i.test(ua);
label: "清空记录",
onClick: async () => {
moredropdown$.hide();
// methods
},
}),
@@ -313,6 +346,34 @@ var isWin = /Windows|Win/i.test(ua);
WXU.observe_node(".home-header", () => {
insert_downloader();
});
// document.addEventListener(
// "scroll",
// (e) => {
// if (e.target && e.target.id === "downloader_container") {
// const el = e.target;
// if (el.scrollTop + el.clientHeight >= el.scrollHeight - 50) {
// if (
// !is_loading &&
// has_more &&
// ws_conn &&
// ws_conn.readyState === WebSocket.OPEN
// ) {
// is_loading = true;
// console.log("[]fetching next page", cur_page + 1);
// ws_conn.send(
// JSON.stringify({
// type: "fetch_tasks",
// page: cur_page + 1,
// limit: 50,
// }),
// );
// }
// }
// }
// },
// true,
// );
})();
async function __wx_handle_api_call(msg, socket) {
@@ -171,8 +171,9 @@ async function __wx_handle_api_call(msg, socket) {
function GopeedDownloaderPanel() {
const tasks = [];
const tasks_ = ref(tasks);
const task_count_ = computed(tasks_, (t) => {
return t.length;
const total_ = ref(0);
const task_count_ = computed(total_, (t) => {
return t > 0 ? t : tasks_.value.length;
});
const runningCount_ = computed(tasks_, (t) => {
return t.filter((v) => v.status === "running").length;
@@ -235,7 +236,13 @@ function GopeedDownloaderPanel() {
return;
}
if (msg.type === "tasks") {
tasks_.value = msg.data;
if (Array.isArray(msg.data)) {
tasks_.value = msg.data;
total_.value = msg.data.length;
} else if (msg.data.list) {
tasks_.value = msg.data.list;
total_.value = msg.data.total;
}
return;
}
if (msg.type === "clear") {
+1 -1
View File
@@ -187,7 +187,7 @@ func (fp *FilenameProcessor) truncateString(s string, maxBytes int) string {
// 验证和清理文件名
func (fp *FilenameProcessor) SanitizeFilename(filename string) (string, error) {
fmt.Println("SanitizeFilename", filename)
// fmt.Println("SanitizeFilename", filename)
if filename == "" {
return "", fmt.Errorf("filename cannot be empty")
}