123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- // 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"
- )
- func (s *DB) GetGlobalFile(folder string, 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 folders o ON o.idx = f.folder_idx
- WHERE o.folder_id = ? AND f.name = ? AND f.local_flags & {{.FlagLocalGlobal}} != 0
- `).Get(&ind, folder, 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)
- }
- return fi, true, nil
- }
- func (s *DB) GetGlobalAvailability(folder, file string) ([]protocol.DeviceID, error) {
- 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
- INNER JOIN folders o ON o.idx = f.folder_idx
- INNER JOIN files g ON f.folder_idx = g.folder_idx AND g.version = f.version AND g.name = f.name
- WHERE o.folder_id = ? AND g.name = ? AND g.local_flags & {{.FlagLocalGlobal}} != 0 AND f.device_idx != {{.LocalDeviceIdx}}
- ORDER BY d.device_id
- `).Select(&devStrs, folder, file)
- 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
- }
- func (s *DB) AllGlobalFiles(folder string) (iter.Seq[db.FileMetadata], func() error) {
- it, errFn := 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.local_flags & {{.FlagLocalGlobal}} != 0
- ORDER BY f.name
- `).Queryx(folder))
- return itererr.Map(it, errFn, func(m db.FileMetadata) (db.FileMetadata, error) {
- m.Name = osutil.NativeFilename(m.Name)
- return m, nil
- })
- }
- func (s *DB) AllGlobalFilesPrefix(folder string, prefix string) (iter.Seq[db.FileMetadata], func() error) {
- if prefix == "" {
- return s.AllGlobalFiles(folder)
- }
- prefix = osutil.NormalizedFilename(prefix)
- end := prefixEnd(prefix)
- it, errFn := 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.name >= ? AND f.name < ? AND f.local_flags & {{.FlagLocalGlobal}} != 0
- ORDER BY f.name
- `).Queryx(folder, prefix, end))
- return itererr.Map(it, errFn, func(m db.FileMetadata) (db.FileMetadata, error) {
- m.Name = osutil.NativeFilename(m.Name)
- return m, nil
- })
- }
- func (s *DB) AllNeededGlobalFiles(folder string, device protocol.DeviceID, order config.PullOrder, limit, offset int) (iter.Seq[protocol.FileInfo], func() error) {
- var selectOpts string
- switch order {
- case config.PullOrderRandom:
- selectOpts = "ORDER BY RANDOM()"
- case config.PullOrderAlphabetic:
- selectOpts = "ORDER BY g.name ASC"
- 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 {
- return s.neededGlobalFilesLocal(folder, selectOpts)
- }
- return s.neededGlobalFilesRemote(folder, device, selectOpts)
- }
- func (s *DB) neededGlobalFilesLocal(folder, selectOpts string) (iter.Seq[protocol.FileInfo], func() error) {
- // Select all the non-ignored files with the need bit set.
- it, errFn := iterStructs[indirectFI](s.stmt(`
- SELECT fi.fiprotobuf, bl.blprotobuf, g.name, g.size, g.modified FROM fileinfos fi
- INNER JOIN files g on fi.sequence = g.sequence
- LEFT JOIN blocklists bl ON bl.blocklist_hash = g.blocklist_hash
- INNER JOIN folders o ON o.idx = g.folder_idx
- WHERE o.folder_id = ? AND g.local_flags & {{.FlagLocalIgnored}} = 0 AND g.local_flags & {{.FlagLocalNeeded}} != 0
- ` + selectOpts).Queryx(folder))
- return itererr.Map(it, errFn, indirectFI.FileInfo)
- }
- func (s *DB) neededGlobalFilesRemote(folder string, device protocol.DeviceID, selectOpts string) (iter.Seq[protocol.FileInfo], func() error) {
- // Select:
- //
- // - all the valid, non-deleted global files that don't have a corresponding
- // remote file with the same version.
- //
- // - all the valid, deleted global files that have a corresponding non-deleted
- // remote file (of any version)
- it, errFn := iterStructs[indirectFI](s.stmt(`
- SELECT fi.fiprotobuf, bl.blprotobuf, g.name, g.size, g.modified FROM fileinfos fi
- INNER JOIN files g on fi.sequence = g.sequence
- LEFT JOIN blocklists bl ON bl.blocklist_hash = g.blocklist_hash
- INNER JOIN folders o ON o.idx = g.folder_idx
- WHERE o.folder_id = ? AND g.local_flags & {{.FlagLocalGlobal}} != 0 AND NOT g.deleted AND NOT g.invalid AND NOT EXISTS (
- SELECT 1 FROM FILES f
- INNER JOIN devices d ON d.idx = f.device_idx
- WHERE f.name = g.name AND f.version = g.version AND f.folder_idx = g.folder_idx AND d.device_id = ?
- )
- UNION ALL
- SELECT fi.fiprotobuf, bl.blprotobuf, g.name, g.size, g.modified FROM fileinfos fi
- INNER JOIN files g on fi.sequence = g.sequence
- LEFT JOIN blocklists bl ON bl.blocklist_hash = g.blocklist_hash
- INNER JOIN folders o ON o.idx = g.folder_idx
- WHERE o.folder_id = ? AND g.local_flags & {{.FlagLocalGlobal}} != 0 AND g.deleted AND NOT g.invalid AND EXISTS (
- SELECT 1 FROM FILES f
- INNER JOIN devices d ON d.idx = f.device_idx
- WHERE f.name = g.name AND f.folder_idx = g.folder_idx AND d.device_id = ? AND NOT f.deleted
- )
- `+selectOpts).Queryx(
- folder, device.String(),
- folder, device.String(),
- ))
- return itererr.Map(it, errFn, indirectFI.FileInfo)
- }
|