mirror of
https://github.com/syncthing/syncthing.git
synced 2026-03-25 17:53:33 -04:00
Switch the database from LevelDB to SQLite, for greater stability and simpler code. Co-authored-by: Tommy van der Vorst <tommy@pixelspark.nl> Co-authored-by: bt90 <btom1990@googlemail.com>
127 lines
5.0 KiB
Go
127 lines
5.0 KiB
Go
// Copyright (C) 2025 The Syncthing Authors.
|
|
//
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
|
|
package sqlite
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"iter"
|
|
|
|
"github.com/syncthing/syncthing/internal/db"
|
|
"github.com/syncthing/syncthing/internal/itererr"
|
|
"github.com/syncthing/syncthing/lib/osutil"
|
|
"github.com/syncthing/syncthing/lib/protocol"
|
|
)
|
|
|
|
func (s *DB) GetDeviceFile(folder string, device protocol.DeviceID, file string) (protocol.FileInfo, bool, error) {
|
|
file = osutil.NormalizedFilename(file)
|
|
|
|
var ind indirectFI
|
|
err := s.stmt(`
|
|
SELECT fi.fiprotobuf, bl.blprotobuf FROM fileinfos fi
|
|
INNER JOIN files f on fi.sequence = f.sequence
|
|
LEFT JOIN blocklists bl ON bl.blocklist_hash = f.blocklist_hash
|
|
INNER JOIN devices d ON f.device_idx = d.idx
|
|
INNER JOIN folders o ON f.folder_idx = o.idx
|
|
WHERE o.folder_id = ? AND d.device_id = ? AND f.name = ?
|
|
`).Get(&ind, folder, device.String(), file)
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return protocol.FileInfo{}, false, nil
|
|
}
|
|
if err != nil {
|
|
return protocol.FileInfo{}, false, wrap(err)
|
|
}
|
|
fi, err := ind.FileInfo()
|
|
if err != nil {
|
|
return protocol.FileInfo{}, false, wrap(err, "indirect")
|
|
}
|
|
return fi, true, nil
|
|
}
|
|
|
|
func (s *DB) AllLocalFiles(folder string, device protocol.DeviceID) (iter.Seq[protocol.FileInfo], func() error) {
|
|
it, errFn := iterStructs[indirectFI](s.stmt(`
|
|
SELECT fi.fiprotobuf, bl.blprotobuf FROM fileinfos fi
|
|
INNER JOIN files f on fi.sequence = f.sequence
|
|
LEFT JOIN blocklists bl ON bl.blocklist_hash = f.blocklist_hash
|
|
INNER JOIN folders o ON o.idx = f.folder_idx
|
|
INNER JOIN devices d ON d.idx = f.device_idx
|
|
WHERE o.folder_id = ? AND d.device_id = ?
|
|
`).Queryx(folder, device.String()))
|
|
return itererr.Map(it, errFn, indirectFI.FileInfo)
|
|
}
|
|
|
|
func (s *DB) AllLocalFilesBySequence(folder string, device protocol.DeviceID, startSeq int64, limit int) (iter.Seq[protocol.FileInfo], func() error) {
|
|
var limitStr string
|
|
if limit > 0 {
|
|
limitStr = fmt.Sprintf(" LIMIT %d", limit)
|
|
}
|
|
it, errFn := iterStructs[indirectFI](s.stmt(`
|
|
SELECT fi.fiprotobuf, bl.blprotobuf FROM fileinfos fi
|
|
INNER JOIN files f on fi.sequence = f.sequence
|
|
LEFT JOIN blocklists bl ON bl.blocklist_hash = f.blocklist_hash
|
|
INNER JOIN folders o ON o.idx = f.folder_idx
|
|
INNER JOIN devices d ON d.idx = f.device_idx
|
|
WHERE o.folder_id = ? AND d.device_id = ? AND f.sequence >= ?
|
|
ORDER BY f.sequence`+limitStr).Queryx(
|
|
folder, device.String(), startSeq))
|
|
return itererr.Map(it, errFn, indirectFI.FileInfo)
|
|
}
|
|
|
|
func (s *DB) AllLocalFilesWithPrefix(folder string, device protocol.DeviceID, prefix string) (iter.Seq[protocol.FileInfo], func() error) {
|
|
if prefix == "" {
|
|
return s.AllLocalFiles(folder, device)
|
|
}
|
|
|
|
prefix = osutil.NormalizedFilename(prefix)
|
|
end := prefixEnd(prefix)
|
|
|
|
it, errFn := iterStructs[indirectFI](s.sql.Queryx(`
|
|
SELECT fi.fiprotobuf, bl.blprotobuf FROM fileinfos fi
|
|
INNER JOIN files f on fi.sequence = f.sequence
|
|
LEFT JOIN blocklists bl ON bl.blocklist_hash = f.blocklist_hash
|
|
INNER JOIN folders o ON o.idx = f.folder_idx
|
|
INNER JOIN devices d ON d.idx = f.device_idx
|
|
WHERE o.folder_id = ? AND d.device_id = ? AND f.name >= ? AND f.name < ?
|
|
`, folder, device.String(), prefix, end))
|
|
return itererr.Map(it, errFn, indirectFI.FileInfo)
|
|
}
|
|
|
|
func (s *DB) AllLocalFilesWithBlocksHash(folder string, h []byte) (iter.Seq[db.FileMetadata], func() error) {
|
|
return iterStructs[db.FileMetadata](s.stmt(`
|
|
SELECT f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.invalid, f.local_flags as localflags FROM files f
|
|
INNER JOIN folders o ON o.idx = f.folder_idx
|
|
WHERE o.folder_id = ? AND f.device_idx = {{.LocalDeviceIdx}} AND f.blocklist_hash = ?
|
|
`).Queryx(folder, h))
|
|
}
|
|
|
|
func (s *DB) AllLocalFilesWithBlocksHashAnyFolder(h []byte) (iter.Seq2[string, db.FileMetadata], func() error) {
|
|
type row struct {
|
|
FolderID string `db:"folder_id"`
|
|
db.FileMetadata
|
|
}
|
|
it, errFn := iterStructs[row](s.stmt(`
|
|
SELECT o.folder_id, f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.invalid, f.local_flags as localflags FROM files f
|
|
INNER JOIN folders o ON o.idx = f.folder_idx
|
|
WHERE f.device_idx = {{.LocalDeviceIdx}} AND f.blocklist_hash = ?
|
|
`).Queryx(h))
|
|
return itererr.Map2(it, errFn, func(r row) (string, db.FileMetadata, error) {
|
|
return r.FolderID, r.FileMetadata, nil
|
|
})
|
|
}
|
|
|
|
func (s *DB) AllLocalBlocksWithHash(hash []byte) (iter.Seq[db.BlockMapEntry], func() error) {
|
|
// We involve the files table in this select because deletion of blocks
|
|
// & blocklists is deferred (garbage collected) while the files list is
|
|
// not. This filters out blocks that are in fact deleted.
|
|
return iterStructs[db.BlockMapEntry](s.stmt(`
|
|
SELECT f.blocklist_hash as blocklisthash, b.idx as blockindex, b.offset, b.size FROM files f
|
|
LEFT JOIN blocks b ON f.blocklist_hash = b.blocklist_hash
|
|
WHERE f.device_idx = {{.LocalDeviceIdx}} AND b.hash = ?
|
|
`).Queryx(hash))
|
|
}
|