2023-01-07 00:39:05 +01:00
|
|
|
package client
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"database/sql"
|
2026-02-28 21:04:13 +01:00
|
|
|
"fmt"
|
2023-02-15 22:08:17 +01:00
|
|
|
"math"
|
2023-01-07 00:39:05 +01:00
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
2026-04-21 15:53:56 +02:00
|
|
|
"sync"
|
2023-01-07 00:39:05 +01:00
|
|
|
|
|
|
|
|
"forge.redroom.link/yves/meowlib"
|
|
|
|
|
"github.com/google/uuid"
|
|
|
|
|
_ "github.com/mattn/go-sqlite3"
|
2023-01-08 22:57:17 +01:00
|
|
|
"google.golang.org/protobuf/proto"
|
2023-01-07 00:39:05 +01:00
|
|
|
)
|
|
|
|
|
|
2026-04-21 15:53:56 +02:00
|
|
|
// One RWMutex per SQLite file path. Entries are never deleted (bounded by
|
|
|
|
|
// peer count, which is small). RLock for reads, Lock for writes.
|
|
|
|
|
var dbFileMu sync.Map
|
|
|
|
|
|
|
|
|
|
func getDbFileMutex(path string) *sync.RWMutex {
|
|
|
|
|
v, _ := dbFileMu.LoadOrStore(path, &sync.RWMutex{})
|
|
|
|
|
return v.(*sync.RWMutex)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func withDbWrite(path string, fn func(*sql.DB) error) error {
|
|
|
|
|
mu := getDbFileMutex(path)
|
|
|
|
|
mu.Lock()
|
|
|
|
|
defer mu.Unlock()
|
|
|
|
|
db, err := sql.Open("sqlite3", path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer db.Close()
|
|
|
|
|
return fn(db)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func withDbRead(path string, fn func(*sql.DB) error) error {
|
|
|
|
|
mu := getDbFileMutex(path)
|
|
|
|
|
mu.RLock()
|
|
|
|
|
defer mu.RUnlock()
|
|
|
|
|
db, err := sql.Open("sqlite3", path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer db.Close()
|
|
|
|
|
return fn(db)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func dbPath(cfg *Config, identity *Identity, dbid string) string {
|
|
|
|
|
return filepath.Join(cfg.StoragePath, identity.Uuid, dbid+cfg.DbSuffix)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-25 22:05:58 +01:00
|
|
|
func storeMessage(peer *Peer, usermessage *meowlib.UserMessage, filenames []string, password string) error {
|
2024-03-31 19:04:37 +02:00
|
|
|
cfg := GetConfig()
|
|
|
|
|
identity := cfg.GetIdentity()
|
2026-04-21 15:53:56 +02:00
|
|
|
|
|
|
|
|
isNew := len(peer.DbIds) == 0
|
|
|
|
|
var dbid string
|
|
|
|
|
if isNew {
|
2023-01-07 00:39:05 +01:00
|
|
|
dbid = uuid.NewString()
|
2024-02-25 20:15:35 +01:00
|
|
|
peer.DbIds = []string{dbid}
|
2024-06-05 23:18:25 +02:00
|
|
|
identity.Peers.StorePeer(peer)
|
2024-03-31 19:04:37 +02:00
|
|
|
identity.CreateFolder()
|
2023-01-07 00:39:05 +01:00
|
|
|
} else {
|
|
|
|
|
dbid = peer.DbIds[len(peer.DbIds)-1]
|
|
|
|
|
}
|
2026-04-21 15:53:56 +02:00
|
|
|
|
|
|
|
|
// Detach file attachments — no DB lock needed for file I/O.
|
2024-04-05 23:07:23 +02:00
|
|
|
hiddenFilenames := []string{}
|
2023-01-07 00:39:05 +01:00
|
|
|
if len(usermessage.Files) > 0 {
|
2026-04-21 15:53:56 +02:00
|
|
|
secureDir := filepath.Join(cfg.StoragePath, identity.Uuid, "securefiles")
|
|
|
|
|
if _, err := os.Stat(secureDir); os.IsNotExist(err) {
|
|
|
|
|
if err = os.MkdirAll(secureDir, 0755); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-07 00:39:05 +01:00
|
|
|
for _, f := range usermessage.Files {
|
|
|
|
|
hiddenFilename := uuid.NewString()
|
|
|
|
|
encData, err := meowlib.SymEncrypt(password, f.Data)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-04-21 15:53:56 +02:00
|
|
|
hidden := filepath.Join(secureDir, hiddenFilename)
|
|
|
|
|
os.WriteFile(hidden, encData, 0600)
|
|
|
|
|
hiddenFilenames = append(hiddenFilenames, hidden)
|
|
|
|
|
f.Data = []byte(hidden)
|
2023-01-07 00:39:05 +01:00
|
|
|
}
|
|
|
|
|
}
|
2026-04-21 15:53:56 +02:00
|
|
|
|
|
|
|
|
outbound := usermessage.From != peer.ContactPublicKey
|
2024-04-05 23:07:23 +02:00
|
|
|
dbm := UserMessageToDbMessage(outbound, usermessage, hiddenFilenames)
|
2024-02-29 21:03:15 +01:00
|
|
|
out, err := proto.Marshal(dbm)
|
2023-01-08 22:57:17 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
encData, err := meowlib.SymEncrypt(password, out)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-04-21 15:53:56 +02:00
|
|
|
|
|
|
|
|
var id int64
|
|
|
|
|
path := dbPath(cfg, identity, dbid)
|
|
|
|
|
err = withDbWrite(path, func(db *sql.DB) error {
|
|
|
|
|
// SQLite creates the file on first Open; create the table if new DB.
|
|
|
|
|
if isNew {
|
|
|
|
|
if err := createMessageTable(db); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
stmt, err := db.Prepare(`INSERT INTO message(m) VALUES (?) RETURNING ID`)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
result, err := stmt.Exec(encData)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
id, err = result.LastInsertId()
|
2023-01-07 00:39:05 +01:00
|
|
|
return err
|
2026-04-21 15:53:56 +02:00
|
|
|
})
|
2024-03-05 20:15:48 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-04-21 15:53:56 +02:00
|
|
|
|
|
|
|
|
peer.LastMessage = DbMessageToInternalUserMessage(id, dbid, dbm)
|
2024-06-05 23:18:25 +02:00
|
|
|
identity.Peers.StorePeer(peer)
|
2023-01-07 00:39:05 +01:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-25 22:05:58 +01:00
|
|
|
func loadNewMessages(peer *Peer, lastDbId int, password string) ([]*InternalUserMessage, error) {
|
2024-02-18 13:46:11 +01:00
|
|
|
var messages []*InternalUserMessage
|
2024-03-31 19:04:37 +02:00
|
|
|
cfg := GetConfig()
|
|
|
|
|
identity := cfg.GetIdentity()
|
2024-03-05 23:15:19 +01:00
|
|
|
if len(peer.DbIds) == 0 {
|
|
|
|
|
return messages, nil
|
|
|
|
|
}
|
2024-02-17 19:30:25 +01:00
|
|
|
fileidx := len(peer.DbIds) - 1
|
|
|
|
|
if lastDbId == 0 {
|
|
|
|
|
lastDbId = math.MaxInt64
|
|
|
|
|
}
|
2026-04-21 15:53:56 +02:00
|
|
|
err := withDbRead(dbPath(cfg, identity, peer.DbIds[fileidx]), func(db *sql.DB) error {
|
|
|
|
|
stm, err := db.Prepare("SELECT id, m FROM message WHERE id > ? ORDER BY id DESC")
|
2024-02-17 19:30:25 +01:00
|
|
|
if err != nil {
|
2026-04-21 15:53:56 +02:00
|
|
|
return err
|
2024-02-17 19:30:25 +01:00
|
|
|
}
|
2026-04-21 15:53:56 +02:00
|
|
|
defer stm.Close()
|
|
|
|
|
rows, err := stm.Query(lastDbId)
|
2024-02-17 19:30:25 +01:00
|
|
|
if err != nil {
|
2026-04-21 15:53:56 +02:00
|
|
|
return err
|
2024-02-17 19:30:25 +01:00
|
|
|
}
|
2026-04-21 15:53:56 +02:00
|
|
|
defer rows.Close()
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
var id int64
|
|
|
|
|
var m []byte
|
|
|
|
|
if err = rows.Scan(&id, &m); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
decdata, err := meowlib.SymDecrypt(password, m)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
var dbm meowlib.DbMessage
|
|
|
|
|
if err = proto.Unmarshal(decdata, &dbm); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
ium := DbMessageToInternalUserMessage(id, peer.DbIds[fileidx], &dbm)
|
|
|
|
|
ium.Dbid = id
|
|
|
|
|
ium.Dbfile = peer.DbIds[fileidx]
|
|
|
|
|
messages = append(messages, ium)
|
2024-02-17 19:30:25 +01:00
|
|
|
}
|
2026-04-21 15:53:56 +02:00
|
|
|
return nil
|
|
|
|
|
})
|
2024-02-17 19:30:25 +01:00
|
|
|
// TODO DB overlap
|
2026-04-21 15:53:56 +02:00
|
|
|
return messages, err
|
2024-02-17 19:30:25 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-25 22:05:58 +01:00
|
|
|
func loadMessagesHistory(peer *Peer, inAppMsgCount int, lastDbId int, wantMore int, password string) ([]InternalUserMessage, error) {
|
2024-02-20 20:30:55 +01:00
|
|
|
var messages []InternalUserMessage
|
2026-04-21 15:53:56 +02:00
|
|
|
cfg := GetConfig()
|
2024-03-05 23:15:19 +01:00
|
|
|
if len(peer.DbIds) == 0 {
|
|
|
|
|
return messages, nil
|
|
|
|
|
}
|
2023-02-15 22:08:17 +01:00
|
|
|
fileidx := len(peer.DbIds) - 1
|
|
|
|
|
countStack, err := getMessageCount(peer.DbIds[fileidx])
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
for inAppMsgCount > countStack {
|
|
|
|
|
fileidx--
|
|
|
|
|
if fileidx < 0 {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
newCount, err := getMessageCount(peer.DbIds[fileidx])
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
countStack += newCount
|
|
|
|
|
}
|
|
|
|
|
if lastDbId == 0 {
|
|
|
|
|
lastDbId = math.MaxInt64
|
|
|
|
|
}
|
2026-04-21 15:53:56 +02:00
|
|
|
err = withDbRead(filepath.Join(cfg.StoragePath, cfg.GetIdentity().Uuid, peer.DbIds[fileidx]+cfg.DbSuffix), func(db *sql.DB) error {
|
|
|
|
|
stm, err := db.Prepare("SELECT id, m FROM message WHERE id < ? ORDER BY id DESC LIMIT ?")
|
2023-02-15 22:08:17 +01:00
|
|
|
if err != nil {
|
2026-04-21 15:53:56 +02:00
|
|
|
return err
|
2023-02-15 22:08:17 +01:00
|
|
|
}
|
2026-04-21 15:53:56 +02:00
|
|
|
defer stm.Close()
|
|
|
|
|
rows, err := stm.Query(lastDbId, wantMore)
|
2023-02-15 22:08:17 +01:00
|
|
|
if err != nil {
|
2026-04-21 15:53:56 +02:00
|
|
|
return err
|
2023-02-15 22:08:17 +01:00
|
|
|
}
|
2026-04-21 15:53:56 +02:00
|
|
|
defer rows.Close()
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
var id int64
|
|
|
|
|
var m []byte
|
|
|
|
|
if err = rows.Scan(&id, &m); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
decdata, err := meowlib.SymDecrypt(password, m)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
var dbm meowlib.DbMessage
|
|
|
|
|
if err = proto.Unmarshal(decdata, &dbm); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
ium := DbMessageToInternalUserMessage(id, peer.DbIds[fileidx], &dbm)
|
|
|
|
|
ium.Dbid = id
|
|
|
|
|
ium.Dbfile = peer.DbIds[fileidx]
|
|
|
|
|
messages = append(messages, *ium)
|
2023-02-15 22:08:17 +01:00
|
|
|
}
|
2026-04-21 15:53:56 +02:00
|
|
|
return nil
|
|
|
|
|
})
|
2023-02-15 22:08:17 +01:00
|
|
|
// TODO DB overlap
|
2026-04-21 15:53:56 +02:00
|
|
|
return messages, err
|
2023-02-15 22:08:17 +01:00
|
|
|
}
|
|
|
|
|
|
2024-05-28 14:28:10 +02:00
|
|
|
func GetDbMessage(dbFile string, dbId int64, password string) (*meowlib.DbMessage, error) {
|
2026-04-21 15:53:56 +02:00
|
|
|
cfg := GetConfig()
|
|
|
|
|
path := filepath.Join(cfg.StoragePath, cfg.GetIdentity().Uuid, dbFile+cfg.DbSuffix)
|
2024-04-06 15:44:30 +02:00
|
|
|
var dbm meowlib.DbMessage
|
2026-02-28 21:04:13 +01:00
|
|
|
found := false
|
2026-04-21 15:53:56 +02:00
|
|
|
err := withDbRead(path, func(db *sql.DB) error {
|
|
|
|
|
stm, err := db.Prepare("SELECT id, m FROM message WHERE id=?")
|
2024-04-06 15:44:30 +02:00
|
|
|
if err != nil {
|
2026-04-21 15:53:56 +02:00
|
|
|
return err
|
2024-04-06 15:44:30 +02:00
|
|
|
}
|
2026-04-21 15:53:56 +02:00
|
|
|
defer stm.Close()
|
|
|
|
|
rows, err := stm.Query(dbId)
|
2024-04-06 15:44:30 +02:00
|
|
|
if err != nil {
|
2026-04-21 15:53:56 +02:00
|
|
|
return err
|
2024-04-06 15:44:30 +02:00
|
|
|
}
|
2026-04-21 15:53:56 +02:00
|
|
|
defer rows.Close()
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
found = true
|
|
|
|
|
var id int64
|
|
|
|
|
var m []byte
|
|
|
|
|
if err = rows.Scan(&id, &m); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
decdata, err := meowlib.SymDecrypt(password, m)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if err = proto.Unmarshal(decdata, &dbm); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2024-04-06 15:44:30 +02:00
|
|
|
}
|
2026-04-21 15:53:56 +02:00
|
|
|
return nil
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
2026-02-28 21:04:13 +01:00
|
|
|
}
|
|
|
|
|
if !found {
|
|
|
|
|
return nil, fmt.Errorf("message row %d not found in %s", dbId, dbFile)
|
2024-04-06 12:55:27 +02:00
|
|
|
}
|
2024-05-28 14:28:10 +02:00
|
|
|
return &dbm, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func UpdateDbMessage(dbm *meowlib.DbMessage, dbFile string, dbId int64, password string) error {
|
2026-04-21 15:53:56 +02:00
|
|
|
cfg := GetConfig()
|
|
|
|
|
path := filepath.Join(cfg.StoragePath, cfg.GetIdentity().Uuid, dbFile+cfg.DbSuffix)
|
2024-05-28 14:28:10 +02:00
|
|
|
out, err := proto.Marshal(dbm)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
encData, err := meowlib.SymEncrypt(password, out)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-04-21 15:53:56 +02:00
|
|
|
return withDbWrite(path, func(db *sql.DB) error {
|
|
|
|
|
stmt, err := db.Prepare(`UPDATE message SET m=? WHERE id=?`)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
_, err = stmt.Exec(encData, dbId)
|
2024-05-28 14:28:10 +02:00
|
|
|
return err
|
2026-04-21 15:53:56 +02:00
|
|
|
})
|
2024-05-28 14:28:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GetMessagePreview(dbFile string, dbId int64, password string) ([]byte, error) {
|
|
|
|
|
dbm, err := GetDbMessage(dbFile, dbId, password)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2024-04-06 15:44:30 +02:00
|
|
|
return FilePreview(dbm.FilePaths[0], password)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func FilePreview(filename string, password string) ([]byte, error) {
|
|
|
|
|
encData, err := os.ReadFile(filename)
|
2024-04-06 12:55:27 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-04-21 15:53:56 +02:00
|
|
|
return meowlib.SymDecrypt(password, encData)
|
2024-04-06 12:55:27 +02:00
|
|
|
}
|
|
|
|
|
|
2024-04-06 15:44:30 +02:00
|
|
|
func InternalUserMessagePreview(msg *InternalUserMessage, password string) ([]byte, error) {
|
|
|
|
|
if len(msg.FilePaths) == 0 {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
return FilePreview(msg.FilePaths[0], password)
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-15 22:08:17 +01:00
|
|
|
func getMessageCount(dbid string) (int, error) {
|
2026-04-21 15:53:56 +02:00
|
|
|
cfg := GetConfig()
|
|
|
|
|
path := filepath.Join(cfg.StoragePath, cfg.GetIdentity().Uuid, dbid+cfg.DbSuffix)
|
2023-02-15 22:08:17 +01:00
|
|
|
var count int
|
2026-04-21 15:53:56 +02:00
|
|
|
err := withDbRead(path, func(db *sql.DB) error {
|
|
|
|
|
return db.QueryRow("SELECT COUNT(*) FROM message").Scan(&count)
|
|
|
|
|
})
|
|
|
|
|
return count, err
|
2023-01-07 00:39:05 +01:00
|
|
|
}
|
|
|
|
|
|
2026-04-21 15:53:56 +02:00
|
|
|
// SetMessageServerDelivery updates the server delivery UUID and timestamp for a stored message.
|
2026-02-28 10:08:55 +01:00
|
|
|
func SetMessageServerDelivery(dbFile string, dbId int64, serverUid string, receiveTime uint64, password string) error {
|
|
|
|
|
dbm, err := GetDbMessage(dbFile, dbId, password)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
dbm.ServerDeliveryUuid = serverUid
|
|
|
|
|
dbm.ServerDeliveryTimestamp = receiveTime
|
|
|
|
|
return UpdateDbMessage(dbm, dbFile, dbId, password)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-06 11:59:47 +01:00
|
|
|
// FindMessageByUuid scans all DB files for a peer (newest first) and returns
|
|
|
|
|
// the dbFile, row ID, and DbMessage for the message whose Status.Uuid matches.
|
|
|
|
|
func FindMessageByUuid(peer *Peer, messageUuid string, password string) (string, int64, *meowlib.DbMessage, error) {
|
|
|
|
|
cfg := GetConfig()
|
|
|
|
|
identity := cfg.GetIdentity()
|
|
|
|
|
for i := len(peer.DbIds) - 1; i >= 0; i-- {
|
|
|
|
|
dbid := peer.DbIds[i]
|
2026-04-21 15:53:56 +02:00
|
|
|
path := filepath.Join(cfg.StoragePath, identity.Uuid, dbid+cfg.DbSuffix)
|
|
|
|
|
var foundFile string
|
|
|
|
|
var foundId int64
|
|
|
|
|
var foundMsg meowlib.DbMessage
|
|
|
|
|
err := withDbRead(path, func(db *sql.DB) error {
|
|
|
|
|
rows, err := db.Query("SELECT id, m FROM message ORDER BY id DESC")
|
2026-03-06 11:59:47 +01:00
|
|
|
if err != nil {
|
2026-04-21 15:53:56 +02:00
|
|
|
return err
|
2026-03-06 11:59:47 +01:00
|
|
|
}
|
2026-04-21 15:53:56 +02:00
|
|
|
defer rows.Close()
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
var id int64
|
|
|
|
|
var m []byte
|
|
|
|
|
if err := rows.Scan(&id, &m); err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
decdata, err := meowlib.SymDecrypt(password, m)
|
|
|
|
|
if err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
var dbm meowlib.DbMessage
|
|
|
|
|
if err := proto.Unmarshal(decdata, &dbm); err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if dbm.Status != nil && dbm.Status.Uuid == messageUuid {
|
|
|
|
|
foundFile = dbid
|
|
|
|
|
foundId = id
|
|
|
|
|
foundMsg = dbm
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2026-03-06 11:59:47 +01:00
|
|
|
}
|
2026-04-21 15:53:56 +02:00
|
|
|
return nil
|
|
|
|
|
})
|
|
|
|
|
if err == nil && foundFile != "" {
|
|
|
|
|
return foundFile, foundId, &foundMsg, nil
|
2026-03-06 11:59:47 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return "", 0, nil, fmt.Errorf("message with UUID %s not found", messageUuid)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// UpdateMessageAck finds a stored outbound message by UUID and stamps it with
|
|
|
|
|
// the received and/or processed timestamps from an inbound ACK message.
|
|
|
|
|
func UpdateMessageAck(peer *Peer, messageUuid string, receivedAt uint64, processedAt uint64, password string) error {
|
|
|
|
|
dbFile, dbId, dbm, err := FindMessageByUuid(peer, messageUuid, password)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if dbm.Status == nil {
|
|
|
|
|
dbm.Status = &meowlib.ConversationStatus{}
|
|
|
|
|
}
|
|
|
|
|
if receivedAt != 0 {
|
|
|
|
|
dbm.Status.Received = receivedAt
|
|
|
|
|
}
|
|
|
|
|
if processedAt != 0 {
|
|
|
|
|
dbm.Status.Processed = processedAt
|
|
|
|
|
}
|
|
|
|
|
return UpdateDbMessage(dbm, dbFile, dbId, password)
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-27 10:44:09 +02:00
|
|
|
func createMessageTable(db *sql.DB) error {
|
2026-04-21 15:53:56 +02:00
|
|
|
stmt, err := db.Prepare(`CREATE TABLE message (
|
|
|
|
|
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
"m" BLOB)`)
|
2023-01-07 00:39:05 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-04-21 15:53:56 +02:00
|
|
|
stmt.Exec()
|
2023-01-07 00:39:05 +01:00
|
|
|
return nil
|
|
|
|
|
}
|
2023-07-27 10:44:09 +02:00
|
|
|
|
|
|
|
|
func createServerTable(db *sql.DB) error {
|
2026-04-21 15:53:56 +02:00
|
|
|
stmt, err := db.Prepare(`CREATE TABLE servers (
|
2023-07-27 10:44:09 +02:00
|
|
|
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
"country" varchar(2),
|
|
|
|
|
"public" bool,
|
|
|
|
|
"uptime" int,
|
|
|
|
|
"bandwith" float,
|
|
|
|
|
"load" float,
|
|
|
|
|
"url" varchar(2000)
|
|
|
|
|
"name" varchar(255);
|
2026-04-21 15:53:56 +02:00
|
|
|
"description" varchar(5000)
|
2023-07-27 10:44:09 +02:00
|
|
|
"publickey" varchar(10000)
|
2026-04-21 15:53:56 +02:00
|
|
|
)`)
|
2023-07-27 10:44:09 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-04-21 15:53:56 +02:00
|
|
|
stmt.Exec()
|
2023-07-27 10:44:09 +02:00
|
|
|
return nil
|
|
|
|
|
}
|