2025-03-29 12:50:08 +00:00
|
|
|
// 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/config"
|
|
|
|
|
"github.com/syncthing/syncthing/lib/osutil"
|
|
|
|
|
"github.com/syncthing/syncthing/lib/protocol"
|
|
|
|
|
)
|
|
|
|
|
|
2025-04-06 05:30:43 -07:00
|
|
|
func (s *folderDB) GetGlobalFile(file string) (protocol.FileInfo, bool, error) {
|
2025-03-29 12:50:08 +00:00
|
|
|
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
|
2025-09-12 09:27:41 +00:00
|
|
|
INNER JOIN file_names n ON f.name_idx = n.idx
|
|
|
|
|
WHERE n.name = ? AND f.local_flags & {{.FlagLocalGlobal}} != 0
|
2025-04-06 05:30:43 -07:00
|
|
|
`).Get(&ind, file)
|
2025-03-29 12:50:08 +00:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
return fi, true, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-06 05:30:43 -07:00
|
|
|
func (s *folderDB) GetGlobalAvailability(file string) ([]protocol.DeviceID, error) {
|
2025-03-29 12:50:08 +00:00
|
|
|
file = osutil.NormalizedFilename(file)
|
|
|
|
|
|
|
|
|
|
var devStrs []string
|
|
|
|
|
err := s.stmt(`
|
|
|
|
|
SELECT d.device_id FROM files f
|
|
|
|
|
INNER JOIN devices d ON d.idx = f.device_idx
|
2025-09-12 09:27:41 +00:00
|
|
|
INNER JOIN files g ON g.version_idx = f.version_idx AND g.name_idx = f.name_idx
|
|
|
|
|
INNER JOIN file_names n ON f.name_idx = n.idx
|
|
|
|
|
WHERE n.name = ? AND g.local_flags & {{.FlagLocalGlobal}} != 0 AND f.device_idx != {{.LocalDeviceIdx}}
|
2025-03-29 12:50:08 +00:00
|
|
|
ORDER BY d.device_id
|
2025-04-06 05:30:43 -07:00
|
|
|
`).Select(&devStrs, file)
|
2025-03-29 12:50:08 +00:00
|
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, wrap(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
devs := make([]protocol.DeviceID, 0, len(devStrs))
|
|
|
|
|
for _, s := range devStrs {
|
|
|
|
|
d, err := protocol.DeviceIDFromString(s)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, wrap(err)
|
|
|
|
|
}
|
|
|
|
|
devs = append(devs, d)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return devs, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-06 05:30:43 -07:00
|
|
|
func (s *folderDB) AllGlobalFiles() (iter.Seq[db.FileMetadata], func() error) {
|
2025-03-29 12:50:08 +00:00
|
|
|
it, errFn := iterStructs[db.FileMetadata](s.stmt(`
|
2025-09-12 09:27:41 +00:00
|
|
|
SELECT f.sequence, n.name, f.type, f.modified as modnanos, f.size, f.deleted, f.local_flags as localflags FROM files f
|
|
|
|
|
INNER JOIN file_names n ON f.name_idx = n.idx
|
2025-04-06 05:30:43 -07:00
|
|
|
WHERE f.local_flags & {{.FlagLocalGlobal}} != 0
|
2025-09-12 09:27:41 +00:00
|
|
|
ORDER BY n.name
|
2025-04-06 05:30:43 -07:00
|
|
|
`).Queryx())
|
2025-03-29 12:50:08 +00:00
|
|
|
return itererr.Map(it, errFn, func(m db.FileMetadata) (db.FileMetadata, error) {
|
|
|
|
|
m.Name = osutil.NativeFilename(m.Name)
|
|
|
|
|
return m, nil
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-06 05:30:43 -07:00
|
|
|
func (s *folderDB) AllGlobalFilesPrefix(prefix string) (iter.Seq[db.FileMetadata], func() error) {
|
2025-03-29 12:50:08 +00:00
|
|
|
if prefix == "" {
|
2025-04-06 05:30:43 -07:00
|
|
|
return s.AllGlobalFiles()
|
2025-03-29 12:50:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prefix = osutil.NormalizedFilename(prefix)
|
|
|
|
|
end := prefixEnd(prefix)
|
|
|
|
|
|
|
|
|
|
it, errFn := iterStructs[db.FileMetadata](s.stmt(`
|
2025-09-12 09:27:41 +00:00
|
|
|
SELECT f.sequence, n.name, f.type, f.modified as modnanos, f.size, f.deleted, f.local_flags as localflags FROM files f
|
|
|
|
|
INNER JOIN file_names n ON f.name_idx = n.idx
|
|
|
|
|
WHERE n.name >= ? AND n.name < ? AND f.local_flags & {{.FlagLocalGlobal}} != 0
|
|
|
|
|
ORDER BY n.name
|
2025-04-06 05:30:43 -07:00
|
|
|
`).Queryx(prefix, end))
|
2025-03-29 12:50:08 +00:00
|
|
|
return itererr.Map(it, errFn, func(m db.FileMetadata) (db.FileMetadata, error) {
|
|
|
|
|
m.Name = osutil.NativeFilename(m.Name)
|
|
|
|
|
return m, nil
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-06 05:30:43 -07:00
|
|
|
func (s *folderDB) AllNeededGlobalFiles(device protocol.DeviceID, order config.PullOrder, limit, offset int) (iter.Seq[protocol.FileInfo], func() error) {
|
2025-03-29 12:50:08 +00:00
|
|
|
var selectOpts string
|
|
|
|
|
switch order {
|
|
|
|
|
case config.PullOrderRandom:
|
|
|
|
|
selectOpts = "ORDER BY RANDOM()"
|
|
|
|
|
case config.PullOrderAlphabetic:
|
2025-09-12 09:27:41 +00:00
|
|
|
selectOpts = "ORDER BY n.name ASC"
|
2025-03-29 12:50:08 +00:00
|
|
|
case config.PullOrderSmallestFirst:
|
|
|
|
|
selectOpts = "ORDER BY g.size ASC"
|
|
|
|
|
case config.PullOrderLargestFirst:
|
|
|
|
|
selectOpts = "ORDER BY g.size DESC"
|
|
|
|
|
case config.PullOrderOldestFirst:
|
|
|
|
|
selectOpts = "ORDER BY g.modified ASC"
|
|
|
|
|
case config.PullOrderNewestFirst:
|
|
|
|
|
selectOpts = "ORDER BY g.modified DESC"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if limit > 0 {
|
|
|
|
|
selectOpts += fmt.Sprintf(" LIMIT %d", limit)
|
|
|
|
|
}
|
|
|
|
|
if offset > 0 {
|
|
|
|
|
selectOpts += fmt.Sprintf(" OFFSET %d", offset)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if device == protocol.LocalDeviceID {
|
2025-04-06 05:30:43 -07:00
|
|
|
return s.neededGlobalFilesLocal(selectOpts)
|
2025-03-29 12:50:08 +00:00
|
|
|
}
|
|
|
|
|
|
2025-04-06 05:30:43 -07:00
|
|
|
return s.neededGlobalFilesRemote(device, selectOpts)
|
2025-03-29 12:50:08 +00:00
|
|
|
}
|
|
|
|
|
|
2025-04-06 05:30:43 -07:00
|
|
|
func (s *folderDB) neededGlobalFilesLocal(selectOpts string) (iter.Seq[protocol.FileInfo], func() error) {
|
2025-03-29 12:50:08 +00:00
|
|
|
// Select all the non-ignored files with the need bit set.
|
|
|
|
|
it, errFn := iterStructs[indirectFI](s.stmt(`
|
2025-09-12 09:27:41 +00:00
|
|
|
SELECT fi.fiprotobuf, bl.blprotobuf, n.name, g.size, g.modified FROM fileinfos fi
|
2025-03-29 12:50:08 +00:00
|
|
|
INNER JOIN files g on fi.sequence = g.sequence
|
|
|
|
|
LEFT JOIN blocklists bl ON bl.blocklist_hash = g.blocklist_hash
|
2025-09-12 09:27:41 +00:00
|
|
|
INNER JOIN file_names n ON g.name_idx = n.idx
|
2025-04-06 05:30:43 -07:00
|
|
|
WHERE g.local_flags & {{.FlagLocalIgnored}} = 0 AND g.local_flags & {{.FlagLocalNeeded}} != 0
|
|
|
|
|
` + selectOpts).Queryx())
|
2025-03-29 12:50:08 +00:00
|
|
|
return itererr.Map(it, errFn, indirectFI.FileInfo)
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-06 05:30:43 -07:00
|
|
|
func (s *folderDB) neededGlobalFilesRemote(device protocol.DeviceID, selectOpts string) (iter.Seq[protocol.FileInfo], func() error) {
|
2025-03-29 12:50:08 +00:00
|
|
|
// Select:
|
|
|
|
|
//
|
2025-04-09 16:24:17 +02:00
|
|
|
// - all the valid, non-deleted global files that don't have a
|
|
|
|
|
// corresponding remote file with the same version.
|
2025-03-29 12:50:08 +00:00
|
|
|
//
|
2025-04-09 16:24:17 +02:00
|
|
|
// - all the valid, deleted global files that have a corresponding
|
|
|
|
|
// non-deleted and valid remote file (of any version)
|
2025-03-29 12:50:08 +00:00
|
|
|
|
|
|
|
|
it, errFn := iterStructs[indirectFI](s.stmt(`
|
2025-09-12 09:27:41 +00:00
|
|
|
SELECT fi.fiprotobuf, bl.blprotobuf, n.name, g.size, g.modified FROM fileinfos fi
|
2025-03-29 12:50:08 +00:00
|
|
|
INNER JOIN files g on fi.sequence = g.sequence
|
|
|
|
|
LEFT JOIN blocklists bl ON bl.blocklist_hash = g.blocklist_hash
|
2025-09-12 09:27:41 +00:00
|
|
|
INNER JOIN file_names n ON g.name_idx = n.idx
|
2025-06-13 05:33:31 +00:00
|
|
|
WHERE g.local_flags & {{.FlagLocalGlobal}} != 0 AND NOT g.deleted AND g.local_flags & {{.LocalInvalidFlags}} = 0 AND NOT EXISTS (
|
2025-03-29 12:50:08 +00:00
|
|
|
SELECT 1 FROM FILES f
|
|
|
|
|
INNER JOIN devices d ON d.idx = f.device_idx
|
2025-09-12 09:27:41 +00:00
|
|
|
WHERE f.name_idx = g.name_idx AND f.version_idx = g.version_idx AND d.device_id = ?
|
2025-03-29 12:50:08 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
UNION ALL
|
|
|
|
|
|
2025-09-12 09:27:41 +00:00
|
|
|
SELECT fi.fiprotobuf, bl.blprotobuf, n.name, g.size, g.modified FROM fileinfos fi
|
2025-03-29 12:50:08 +00:00
|
|
|
INNER JOIN files g on fi.sequence = g.sequence
|
|
|
|
|
LEFT JOIN blocklists bl ON bl.blocklist_hash = g.blocklist_hash
|
2025-09-12 09:27:41 +00:00
|
|
|
INNER JOIN file_names n ON g.name_idx = n.idx
|
2025-06-13 05:33:31 +00:00
|
|
|
WHERE g.local_flags & {{.FlagLocalGlobal}} != 0 AND g.deleted AND g.local_flags & {{.LocalInvalidFlags}} = 0 AND EXISTS (
|
2025-03-29 12:50:08 +00:00
|
|
|
SELECT 1 FROM FILES f
|
|
|
|
|
INNER JOIN devices d ON d.idx = f.device_idx
|
2025-09-12 09:27:41 +00:00
|
|
|
WHERE f.name_idx = g.name_idx AND d.device_id = ? AND NOT f.deleted AND f.local_flags & {{.LocalInvalidFlags}} = 0
|
2025-03-29 12:50:08 +00:00
|
|
|
)
|
|
|
|
|
`+selectOpts).Queryx(
|
2025-04-06 05:30:43 -07:00
|
|
|
device.String(),
|
|
|
|
|
device.String(),
|
2025-03-29 12:50:08 +00:00
|
|
|
))
|
|
|
|
|
return itererr.Map(it, errFn, indirectFI.FileInfo)
|
|
|
|
|
}
|