Ind.ie is now Small Technology Foundation.
set.go 5.15 KB
Newer Older
Jakob Borg's avatar
Jakob Borg committed
1 2 3
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
// All rights reserved. Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
Jakob Borg's avatar
Jakob Borg committed
4

5 6 7 8 9 10
// Package files provides a set type to track local/remote files with newness
// checks. We must do a certain amount of normalization in here. We will get
// fed paths with either native or wire-format separators and encodings
// depending on who calls us. We transform paths to wire-format (NFC and
// slashes) on the way to the database, and transform to native format
// (varying separator and encoding) on the way back out.
11 12 13 14 15
package files

import (
	"sync"

16 17
	"source.ind.ie/project/pulse/lamport"
	"source.ind.ie/project/pulse/protocol"
Jakob Borg's avatar
Jakob Borg committed
18
	"github.com/syndtr/goleveldb/leveldb"
19 20 21
)

type fileRecord struct {
22
	File   protocol.FileInfo
23 24
	Usage  int
	Global bool
25 26 27 28 29
}

type bitset uint64

type Set struct {
30
	localVersion map[protocol.DeviceID]uint64
31 32 33
	mutex        sync.Mutex
	repo         string
	db           *leveldb.DB
34 35
}

Jakob Borg's avatar
Jakob Borg committed
36 37
func NewSet(repo string, db *leveldb.DB) *Set {
	var s = Set{
38
		localVersion: make(map[protocol.DeviceID]uint64),
39 40
		repo:         repo,
		db:           db,
41
	}
42

43 44 45 46 47
	var deviceID protocol.DeviceID
	ldbWithAllRepoTruncated(db, []byte(repo), func(device []byte, f protocol.FileInfoTruncated) bool {
		copy(deviceID[:], device)
		if f.LocalVersion > s.localVersion[deviceID] {
			s.localVersion[deviceID] = f.LocalVersion
48
		}
Jakob Borg's avatar
Jakob Borg committed
49
		lamport.Default.Tick(f.Version)
50 51
		return true
	})
52 53 54
	if debug {
		l.Debugf("loaded localVersion for %q: %#v", repo, s.localVersion)
	}
55
	clock(s.localVersion[protocol.LocalDeviceID])
56

Jakob Borg's avatar
Jakob Borg committed
57
	return &s
58 59
}

60
func (s *Set) Replace(device protocol.DeviceID, fs []protocol.FileInfo) {
61
	if debug {
62
		l.Debugf("%s Replace(%v, [%d])", s.repo, device, len(fs))
63
	}
64
	normalizeFilenames(fs)
Jakob Borg's avatar
Jakob Borg committed
65 66
	s.mutex.Lock()
	defer s.mutex.Unlock()
67
	s.localVersion[device] = ldbReplace(s.db, []byte(s.repo), device[:], fs)
68 69
}

70
func (s *Set) ReplaceWithDelete(device protocol.DeviceID, fs []protocol.FileInfo) {
71
	if debug {
72
		l.Debugf("%s ReplaceWithDelete(%v, [%d])", s.repo, device, len(fs))
73
	}
74
	normalizeFilenames(fs)
Jakob Borg's avatar
Jakob Borg committed
75 76
	s.mutex.Lock()
	defer s.mutex.Unlock()
77 78
	if lv := ldbReplaceWithDelete(s.db, []byte(s.repo), device[:], fs); lv > s.localVersion[device] {
		s.localVersion[device] = lv
79 80 81
	}
}

82
func (s *Set) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
83
	if debug {
84
		l.Debugf("%s Update(%v, [%d])", s.repo, device, len(fs))
85
	}
86
	normalizeFilenames(fs)
Jakob Borg's avatar
Jakob Borg committed
87 88
	s.mutex.Lock()
	defer s.mutex.Unlock()
89 90
	if lv := ldbUpdate(s.db, []byte(s.repo), device[:], fs); lv > s.localVersion[device] {
		s.localVersion[device] = lv
91 92 93
	}
}

94
func (s *Set) WithNeed(device protocol.DeviceID, fn fileIterator) {
95
	if debug {
96
		l.Debugf("%s WithNeed(%v)", s.repo, device)
97
	}
98
	ldbWithNeed(s.db, []byte(s.repo), device[:], false, nativeFileIterator(fn))
99 100
}

101
func (s *Set) WithNeedTruncated(device protocol.DeviceID, fn fileIterator) {
102
	if debug {
103
		l.Debugf("%s WithNeedTruncated(%v)", s.repo, device)
104
	}
105
	ldbWithNeed(s.db, []byte(s.repo), device[:], true, nativeFileIterator(fn))
106 107
}

108
func (s *Set) WithHave(device protocol.DeviceID, fn fileIterator) {
109
	if debug {
110
		l.Debugf("%s WithHave(%v)", s.repo, device)
111
	}
112
	ldbWithHave(s.db, []byte(s.repo), device[:], false, nativeFileIterator(fn))
113 114
}

115
func (s *Set) WithHaveTruncated(device protocol.DeviceID, fn fileIterator) {
116
	if debug {
117
		l.Debugf("%s WithHaveTruncated(%v)", s.repo, device)
118
	}
119
	ldbWithHave(s.db, []byte(s.repo), device[:], true, nativeFileIterator(fn))
120 121
}

Jakob Borg's avatar
Jakob Borg committed
122 123 124 125
func (s *Set) WithGlobal(fn fileIterator) {
	if debug {
		l.Debugf("%s WithGlobal()", s.repo)
	}
126
	ldbWithGlobal(s.db, []byte(s.repo), false, nativeFileIterator(fn))
Jakob Borg's avatar
Jakob Borg committed
127 128
}

129
func (s *Set) WithGlobalTruncated(fn fileIterator) {
130
	if debug {
Jakob Borg's avatar
Jakob Borg committed
131
		l.Debugf("%s WithGlobalTruncated()", s.repo)
132
	}
133
	ldbWithGlobal(s.db, []byte(s.repo), true, nativeFileIterator(fn))
134 135
}

136 137
func (s *Set) Get(device protocol.DeviceID, file string) protocol.FileInfo {
	f := ldbGet(s.db, []byte(s.repo), device[:], []byte(normalizedFilename(file)))
138 139
	f.Name = nativeFilename(f.Name)
	return f
140 141
}

142
func (s *Set) GetGlobal(file string) protocol.FileInfo {
143 144 145
	f := ldbGetGlobal(s.db, []byte(s.repo), []byte(normalizedFilename(file)))
	f.Name = nativeFilename(f.Name)
	return f
146 147
}

148
func (s *Set) Availability(file string) []protocol.DeviceID {
149
	return ldbAvailability(s.db, []byte(s.repo), []byte(normalizedFilename(file)))
150 151
}

152
func (s *Set) LocalVersion(device protocol.DeviceID) uint64 {
153 154
	s.mutex.Lock()
	defer s.mutex.Unlock()
155
	return s.localVersion[device]
156
}
157

158
// ListRepos returns the folder IDs seen in the database.
159 160 161 162 163 164 165 166 167 168
func ListRepos(db *leveldb.DB) []string {
	return ldbListRepos(db)
}

// DropRepo clears out all information related to the given repo from the
// database.
func DropRepo(db *leveldb.DB, repo string) {
	ldbDropRepo(db, []byte(repo))
}

169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
func normalizeFilenames(fs []protocol.FileInfo) {
	for i := range fs {
		fs[i].Name = normalizedFilename(fs[i].Name)
	}
}

func nativeFileIterator(fn fileIterator) fileIterator {
	return func(fi protocol.FileIntf) bool {
		switch f := fi.(type) {
		case protocol.FileInfo:
			f.Name = nativeFilename(f.Name)
			return fn(f)
		case protocol.FileInfoTruncated:
			f.Name = nativeFilename(f.Name)
			return fn(f)
		default:
			panic("unknown interface type")
		}
	}
}