Files
Commilitia-Drop/internal/db/transfers.sql.go
T
admin 2caf1ab921 传输等待前台:后台 / 墓碑 iOS 可作可唤醒传输目标 + 发送端等其回前台再传
- 根因:presence 的 online 仅表示有活 SSE,故后台 / 墓碑态 iOS 被判离线、不进传输目标,使前面的后台响应(推送唤醒 + 离线队列)实际无从触发。诉求模型:离线但可唤醒的设备可选作目标;发送端推送唤醒对端、等其回前台上线后再走实时传输(非服务端暂存转发)。
- 后端:transfer.Init 返回 receiverOnline(=SendTo delivered);handleTransferInit 回 receiver_online。新增 ListPendingTransfersByReceiver 查询 + Service.RedeliverPendingTo——接收端重连即补发其仍 PENDING 的 transfer:incoming(窗口内),使被唤醒的 iOS 重新 arm P2P answerer(否则发送端 offer 因 p2pHandleSignal 不识未知 peer 而被丢);sse.go handleEvents 在 hub.Connect 后调用它。
- 发送端 web/transfer.ts:startOutgoingTransfer 按 receiver_online 分流——在线即 beginP2P(原 P2P + fallback 逻辑抽出);离线则 waitForPeerThenBegin:标 phase=waiting_peer、订阅 presence、对端上线即延 1.2s(待其 arm)再 beginP2P;60s 窗口内不上线则 cancel + FAILED phase=peer_offline。cleanupOutgoing 清理 waitTimer / waitUnsub。TransferPhase 加 waiting_peer / peer_offline + 三语 i18n。
- 目标纳入 + 标记:离线但可唤醒(type 含 ios)的设备可选作传输目标、标「可唤醒」。web DeviceStrip 加 isWakeable(纳入 visible、放开 disabled、标签);iOS EngineController.sendableDevices 纳入 + isWakeable + sendLabel,三处发送选择器(传输 / 分享 / 收到的文件转发)改用 sendLabel。i18n 加 home.device.wakeable。
- 机制说明(留档):content-available 静默唤醒不会让挂起的引擎重连 SSE;真正上线=用户点推送回前台 → 引擎恢复 → SSE 重连 → 后端补发 incoming → arm → 发送端 offer。若 offer 与 arm 抢拍掉了,30s 后 relay 兜底(此时对端已在线)。可唤醒判定用类型启发式(离线 + ios),未加后端 push-sub 查询。
- 测试:service_test 适配 Init 的三返回值。
2026-06-27 18:16:23 +08:00

210 lines
5.6 KiB
Go

// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: transfers.sql
package db
import (
"context"
)
const createTransferSession = `-- name: CreateTransferSession :exec
INSERT INTO transfer_sessions (
id, user_id, sender_name, receiver_name, state,
file_name, file_size, file_sha256, created_at
)
VALUES (?, ?, ?, ?, 'PENDING', ?, ?, ?, ?)
`
type CreateTransferSessionParams struct {
ID string `json:"id"`
UserID string `json:"user_id"`
SenderName string `json:"sender_name"`
ReceiverName string `json:"receiver_name"`
FileName string `json:"file_name"`
FileSize int64 `json:"file_size"`
FileSha256 *string `json:"file_sha256"`
CreatedAt int64 `json:"created_at"`
}
func (q *Queries) CreateTransferSession(ctx context.Context, arg CreateTransferSessionParams) error {
_, err := q.db.ExecContext(ctx, createTransferSession,
arg.ID,
arg.UserID,
arg.SenderName,
arg.ReceiverName,
arg.FileName,
arg.FileSize,
arg.FileSha256,
arg.CreatedAt,
)
return err
}
const expirePendingSessions = `-- name: ExpirePendingSessions :many
UPDATE transfer_sessions
SET state = 'CANCELLED',
fail_reason = 'pending_timeout',
finished_at = ?1
WHERE state = 'PENDING' AND created_at < ?2
RETURNING id, user_id, sender_name, receiver_name
`
type ExpirePendingSessionsParams struct {
FinishedAt *int64 `json:"finished_at"`
Cutoff int64 `json:"cutoff"`
}
type ExpirePendingSessionsRow struct {
ID string `json:"id"`
UserID string `json:"user_id"`
SenderName string `json:"sender_name"`
ReceiverName string `json:"receiver_name"`
}
func (q *Queries) ExpirePendingSessions(ctx context.Context, arg ExpirePendingSessionsParams) ([]ExpirePendingSessionsRow, error) {
rows, err := q.db.QueryContext(ctx, expirePendingSessions, arg.FinishedAt, arg.Cutoff)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ExpirePendingSessionsRow
for rows.Next() {
var i ExpirePendingSessionsRow
if err := rows.Scan(
&i.ID,
&i.UserID,
&i.SenderName,
&i.ReceiverName,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getTransferSession = `-- name: GetTransferSession :one
SELECT id, user_id, sender_name, receiver_name, state, mode,
file_name, file_size, file_sha256, bytes_transferred,
created_at, finished_at, fail_reason
FROM transfer_sessions
WHERE id = ?
`
func (q *Queries) GetTransferSession(ctx context.Context, id string) (TransferSession, error) {
row := q.db.QueryRowContext(ctx, getTransferSession, id)
var i TransferSession
err := row.Scan(
&i.ID,
&i.UserID,
&i.SenderName,
&i.ReceiverName,
&i.State,
&i.Mode,
&i.FileName,
&i.FileSize,
&i.FileSha256,
&i.BytesTransferred,
&i.CreatedAt,
&i.FinishedAt,
&i.FailReason,
)
return i, err
}
const listPendingTransfersByReceiver = `-- name: ListPendingTransfersByReceiver :many
SELECT id, sender_name, file_name, file_size, file_sha256
FROM transfer_sessions
WHERE user_id = ? AND receiver_name = ? AND state = 'PENDING' AND created_at > ?
ORDER BY created_at ASC
`
type ListPendingTransfersByReceiverParams struct {
UserID string `json:"user_id"`
ReceiverName string `json:"receiver_name"`
CreatedAt int64 `json:"created_at"`
}
type ListPendingTransfersByReceiverRow struct {
ID string `json:"id"`
SenderName string `json:"sender_name"`
FileName string `json:"file_name"`
FileSize int64 `json:"file_size"`
FileSha256 *string `json:"file_sha256"`
}
// Still-PENDING transfers addressed to a receiver, bounded to recently created ones (the
// sender's wait window). Re-delivered as transfer:incoming when that receiver (re)connects -
// e.g. an offline iOS device just woken by the incoming-transfer push -so it arms its P2P
// answerer for the waiting sender's offer.
func (q *Queries) ListPendingTransfersByReceiver(ctx context.Context, arg ListPendingTransfersByReceiverParams) ([]ListPendingTransfersByReceiverRow, error) {
rows, err := q.db.QueryContext(ctx, listPendingTransfersByReceiver, arg.UserID, arg.ReceiverName, arg.CreatedAt)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListPendingTransfersByReceiverRow
for rows.Next() {
var i ListPendingTransfersByReceiverRow
if err := rows.Scan(
&i.ID,
&i.SenderName,
&i.FileName,
&i.FileSize,
&i.FileSha256,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateTransferState = `-- name: UpdateTransferState :execrows
UPDATE transfer_sessions
SET state = ?1,
mode = COALESCE(?2, mode),
fail_reason = COALESCE(?3, fail_reason),
finished_at = COALESCE(?4, finished_at),
bytes_transferred = COALESCE(?5, bytes_transferred)
WHERE id = ?6
`
type UpdateTransferStateParams struct {
State string `json:"state"`
Mode *string `json:"mode"`
FailReason *string `json:"fail_reason"`
FinishedAt *int64 `json:"finished_at"`
BytesTransferred *int64 `json:"bytes_transferred"`
ID string `json:"id"`
}
func (q *Queries) UpdateTransferState(ctx context.Context, arg UpdateTransferStateParams) (int64, error) {
result, err := q.db.ExecContext(ctx, updateTransferState,
arg.State,
arg.Mode,
arg.FailReason,
arg.FinishedAt,
arg.BytesTransferred,
arg.ID,
)
if err != nil {
return 0, err
}
return result.RowsAffected()
}