// Copyright (C) 2017 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 db import ( "time" "github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/sync" ) // like protocol.LocalDeviceID but with 0xf8 in all positions var globalDeviceID = protocol.DeviceID{0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8} type metadataTracker struct { mut sync.RWMutex counts CountsSet indexes map[protocol.DeviceID]int // device ID -> index in counts } func newMetadataTracker() *metadataTracker { return &metadataTracker{ mut: sync.NewRWMutex(), indexes: make(map[protocol.DeviceID]int), } } // Unmarshal loads a metadataTracker from the corresponding protobuf // representation func (m *metadataTracker) Unmarshal(bs []byte) error { if err := m.counts.Unmarshal(bs); err != nil { return err } // Initialize the index map for i, c := range m.counts.Counts { m.indexes[protocol.DeviceIDFromBytes(c.DeviceID)] = i } return nil } // Unmarshal returns the protobuf representation of the metadataTracker func (m *metadataTracker) Marshal() ([]byte, error) { return m.counts.Marshal() } // toDB saves the marshalled metadataTracker to the given db, under the key // corresponding to the given folder func (m *metadataTracker) toDB(db *Instance, folder []byte) error { key := db.folderMetaKey(folder) bs, err := m.Marshal() if err != nil { return err } return db.Put(key, bs, nil) } // fromDB initializes the metadataTracker from the marshalled data found in // the database under the key corresponding to the given folder func (m *metadataTracker) fromDB(db *Instance, folder []byte) error { key := db.folderMetaKey(folder) bs, err := db.Get(key, nil) if err != nil { return err } return m.Unmarshal(bs) } // countsPtr returns a pointer to the corresponding Counts struct, if // necessary allocating one in the process func (m *metadataTracker) countsPtr(dev protocol.DeviceID) *Counts { // must be called with the mutex held idx, ok := m.indexes[dev] if !ok { idx = len(m.counts.Counts) m.counts.Counts = append(m.counts.Counts, Counts{DeviceID: dev[:]}) m.indexes[dev] = idx } return &m.counts.Counts[idx] } // addFile adds a file to the counts, adjusting the sequence number as // appropriate func (m *metadataTracker) addFile(dev protocol.DeviceID, f FileIntf) { if f.IsInvalid() { return } m.mut.Lock() cp := m.countsPtr(dev) switch { case f.IsDeleted(): cp.Deleted++ case f.IsDirectory() && !f.IsSymlink(): cp.Directories++ case f.IsSymlink(): cp.Symlinks++ default: cp.Files++ } cp.Bytes += f.FileSize() if seq := f.SequenceNo(); seq > cp.Sequence { cp.Sequence = seq } m.mut.Unlock() } // removeFile removes a file from the counts func (m *metadataTracker) removeFile(dev protocol.DeviceID, f FileIntf) { if f.IsInvalid() { return } m.mut.Lock() cp := m.countsPtr(dev) switch { case f.IsDeleted(): cp.Deleted-- case f.IsDirectory() && !f.IsSymlink(): cp.Directories-- case f.IsSymlink(): cp.Symlinks-- default: cp.Files-- } cp.Bytes -= f.FileSize() // If we've run into an impossible situation, correct it for now and set // the created timestamp to zero. Next time we start up the metadata // will be seen as infinitely old and recalculated from scratch. if cp.Deleted < 0 { cp.Deleted = 0 m.counts.Created = 0 } if cp.Files < 0 { cp.Files = 0 m.counts.Created = 0 } if cp.Directories < 0 { cp.Directories = 0 m.counts.Created = 0 } if cp.Symlinks < 0 { cp.Symlinks = 0 m.counts.Created = 0 } m.mut.Unlock() } // resetAll resets all metadata for the given device func (m *metadataTracker) resetAll(dev protocol.DeviceID) { m.mut.Lock() *m.countsPtr(dev) = Counts{DeviceID: dev[:]} m.mut.Unlock() } // resetCounts resets the file, dir, etc. counters, while retaining the // sequence number func (m *metadataTracker) resetCounts(dev protocol.DeviceID) { m.mut.Lock() c := m.countsPtr(dev) c.Bytes = 0 c.Deleted = 0 c.Directories = 0 c.Files = 0 c.Symlinks = 0 // c.Sequence deliberately untouched m.mut.Unlock() } // Counts returns the counts for the given device ID func (m *metadataTracker) Counts(dev protocol.DeviceID) Counts { m.mut.RLock() defer m.mut.RUnlock() idx, ok := m.indexes[dev] if !ok { return Counts{} } return m.counts.Counts[idx] } // nextSeq allocates a new sequence number for the given device func (m *metadataTracker) nextSeq(dev protocol.DeviceID) int64 { m.mut.Lock() defer m.mut.Unlock() c := m.countsPtr(dev) c.Sequence++ return c.Sequence } // devices returns the list of devices tracked, excluding the local device // (which we don't know the ID of) func (m *metadataTracker) devices() []protocol.DeviceID { devs := make([]protocol.DeviceID, 0, len(m.counts.Counts)) m.mut.RLock() for _, dev := range m.counts.Counts { if dev.Sequence > 0 { id := protocol.DeviceIDFromBytes(dev.DeviceID) if id == globalDeviceID || id == protocol.LocalDeviceID { continue } devs = append(devs, id) } } m.mut.RUnlock() return devs } func (m *metadataTracker) Created() time.Time { m.mut.RLock() defer m.mut.RUnlock() return time.Unix(0, m.counts.Created) } func (m *metadataTracker) SetCreated() { m.mut.Lock() m.counts.Created = time.Now().UnixNano() m.mut.Unlock() }