1
0

model.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756
  1. package model
  2. /*
  3. Locking
  4. =======
  5. The model has read and write locks. These must be acquired as appropriate by
  6. public methods. To prevent deadlock situations, private methods should never
  7. acquire locks, but document what locks they require.
  8. */
  9. import (
  10. "crypto/sha1"
  11. "errors"
  12. "fmt"
  13. "io"
  14. "log"
  15. "net"
  16. "os"
  17. "path"
  18. "sync"
  19. "time"
  20. "github.com/calmh/syncthing/buffers"
  21. "github.com/calmh/syncthing/protocol"
  22. )
  23. type Model struct {
  24. sync.RWMutex
  25. dir string
  26. global map[string]File // the latest version of each file as it exists in the cluster
  27. local map[string]File // the files we currently have locally on disk
  28. remote map[string]map[string]File
  29. protoConn map[string]Connection
  30. rawConn map[string]io.Closer
  31. fq FileQueue // queue for files to fetch
  32. dq chan File // queue for files to delete
  33. updatedLocal int64 // timestamp of last update to local
  34. updateGlobal int64 // timestamp of last update to remote
  35. lastIdxBcast time.Time
  36. lastIdxBcastRequest time.Time
  37. rwRunning bool
  38. delete bool
  39. trace map[string]bool
  40. fileLastChanged map[string]time.Time
  41. fileWasSuppressed map[string]int
  42. parallellRequests int
  43. limitRequestRate chan struct{}
  44. }
  45. type Connection interface {
  46. ID() string
  47. Index([]protocol.FileInfo)
  48. Request(name string, offset int64, size uint32, hash []byte) ([]byte, error)
  49. Statistics() protocol.Statistics
  50. }
  51. const (
  52. idxBcastHoldtime = 15 * time.Second // Wait at least this long after the last index modification
  53. idxBcastMaxDelay = 120 * time.Second // Unless we've already waited this long
  54. minFileHoldTimeS = 60 // Never allow file changes more often than this
  55. maxFileHoldTimeS = 600 // Always allow file changes at least this often
  56. )
  57. var (
  58. ErrNoSuchFile = errors.New("no such file")
  59. ErrInvalid = errors.New("file is invalid")
  60. )
  61. // NewModel creates and starts a new model. The model starts in read-only mode,
  62. // where it sends index information to connected peers and responds to requests
  63. // for file data without altering the local repository in any way.
  64. func NewModel(dir string) *Model {
  65. m := &Model{
  66. dir: dir,
  67. global: make(map[string]File),
  68. local: make(map[string]File),
  69. remote: make(map[string]map[string]File),
  70. protoConn: make(map[string]Connection),
  71. rawConn: make(map[string]io.Closer),
  72. lastIdxBcast: time.Now(),
  73. trace: make(map[string]bool),
  74. fileLastChanged: make(map[string]time.Time),
  75. fileWasSuppressed: make(map[string]int),
  76. dq: make(chan File),
  77. }
  78. m.fq.resolver = m
  79. go m.broadcastIndexLoop()
  80. return m
  81. }
  82. func (m *Model) LimitRate(kbps int) {
  83. m.limitRequestRate = make(chan struct{}, kbps)
  84. n := kbps/10 + 1
  85. go func() {
  86. for {
  87. time.Sleep(100 * time.Millisecond)
  88. for i := 0; i < n; i++ {
  89. select {
  90. case m.limitRequestRate <- struct{}{}:
  91. }
  92. }
  93. }
  94. }()
  95. }
  96. // Trace enables trace logging of the given facility. This is a debugging function; grep for m.trace.
  97. func (m *Model) Trace(t string) {
  98. m.Lock()
  99. defer m.Unlock()
  100. m.trace[t] = true
  101. }
  102. // StartRW starts read/write processing on the current model. When in
  103. // read/write mode the model will attempt to keep in sync with the cluster by
  104. // pulling needed files from peer nodes.
  105. func (m *Model) StartRW(del bool, threads int) {
  106. m.Lock()
  107. defer m.Unlock()
  108. if m.rwRunning {
  109. panic("starting started model")
  110. }
  111. m.rwRunning = true
  112. m.delete = del
  113. m.parallellRequests = threads
  114. go m.cleanTempFiles()
  115. if del {
  116. go m.deleteFiles()
  117. }
  118. }
  119. // Generation returns an opaque integer that is guaranteed to increment on
  120. // every change to the local repository or global model.
  121. func (m *Model) Generation() int64 {
  122. m.RLock()
  123. defer m.RUnlock()
  124. return m.updatedLocal + m.updateGlobal
  125. }
  126. type ConnectionInfo struct {
  127. protocol.Statistics
  128. Address string
  129. }
  130. // ConnectionStats returns a map with connection statistics for each connected node.
  131. func (m *Model) ConnectionStats() map[string]ConnectionInfo {
  132. type remoteAddrer interface {
  133. RemoteAddr() net.Addr
  134. }
  135. m.RLock()
  136. defer m.RUnlock()
  137. var res = make(map[string]ConnectionInfo)
  138. for node, conn := range m.protoConn {
  139. ci := ConnectionInfo{
  140. Statistics: conn.Statistics(),
  141. }
  142. if nc, ok := m.rawConn[node].(remoteAddrer); ok {
  143. ci.Address = nc.RemoteAddr().String()
  144. }
  145. res[node] = ci
  146. }
  147. return res
  148. }
  149. // LocalSize returns the number of files, deleted files and total bytes for all
  150. // files in the global model.
  151. func (m *Model) GlobalSize() (files, deleted, bytes int) {
  152. m.RLock()
  153. defer m.RUnlock()
  154. for _, f := range m.global {
  155. if f.Flags&protocol.FlagDeleted == 0 {
  156. files++
  157. bytes += f.Size()
  158. } else {
  159. deleted++
  160. }
  161. }
  162. return
  163. }
  164. // LocalSize returns the number of files, deleted files and total bytes for all
  165. // files in the local repository.
  166. func (m *Model) LocalSize() (files, deleted, bytes int) {
  167. m.RLock()
  168. defer m.RUnlock()
  169. for _, f := range m.local {
  170. if f.Flags&protocol.FlagDeleted == 0 {
  171. files++
  172. bytes += f.Size()
  173. } else {
  174. deleted++
  175. }
  176. }
  177. return
  178. }
  179. // InSyncSize returns the number and total byte size of the local files that
  180. // are in sync with the global model.
  181. func (m *Model) InSyncSize() (files, bytes int) {
  182. m.RLock()
  183. defer m.RUnlock()
  184. for n, f := range m.local {
  185. if gf, ok := m.global[n]; ok && f.Equals(gf) {
  186. files++
  187. bytes += f.Size()
  188. }
  189. }
  190. return
  191. }
  192. // NeedFiles returns the list of currently needed files and the total size.
  193. func (m *Model) NeedFiles() (files []File, bytes int) {
  194. m.RLock()
  195. defer m.RUnlock()
  196. for _, n := range m.fq.QueuedFiles() {
  197. f := m.global[n]
  198. files = append(files, f)
  199. bytes += f.Size()
  200. }
  201. return
  202. }
  203. // Index is called when a new node is connected and we receive their full index.
  204. // Implements the protocol.Model interface.
  205. func (m *Model) Index(nodeID string, fs []protocol.FileInfo) {
  206. m.Lock()
  207. defer m.Unlock()
  208. if m.trace["net"] {
  209. log.Printf("NET IDX(in): %s: %d files", nodeID, len(fs))
  210. }
  211. repo := make(map[string]File)
  212. for _, f := range fs {
  213. m.indexUpdate(repo, f)
  214. }
  215. m.remote[nodeID] = repo
  216. m.recomputeGlobal()
  217. m.recomputeNeed()
  218. }
  219. // IndexUpdate is called for incremental updates to connected nodes' indexes.
  220. // Implements the protocol.Model interface.
  221. func (m *Model) IndexUpdate(nodeID string, fs []protocol.FileInfo) {
  222. m.Lock()
  223. defer m.Unlock()
  224. if m.trace["net"] {
  225. log.Printf("NET IDXUP(in): %s: %d files", nodeID, len(fs))
  226. }
  227. repo, ok := m.remote[nodeID]
  228. if !ok {
  229. log.Printf("WARNING: Index update from node %s that does not have an index", nodeID)
  230. return
  231. }
  232. for _, f := range fs {
  233. m.indexUpdate(repo, f)
  234. }
  235. m.recomputeGlobal()
  236. m.recomputeNeed()
  237. }
  238. func (m *Model) indexUpdate(repo map[string]File, f protocol.FileInfo) {
  239. if m.trace["idx"] {
  240. var flagComment string
  241. if f.Flags&protocol.FlagDeleted != 0 {
  242. flagComment = " (deleted)"
  243. }
  244. log.Printf("IDX(in): %q m=%d f=%o%s v=%d (%d blocks)", f.Name, f.Modified, f.Flags, flagComment, f.Version, len(f.Blocks))
  245. }
  246. if extraFlags := f.Flags &^ (protocol.FlagInvalid | protocol.FlagDeleted | 0xfff); extraFlags != 0 {
  247. log.Printf("WARNING: IDX(in): Unknown flags 0x%x in index record %+v", extraFlags, f)
  248. return
  249. }
  250. repo[f.Name] = fileFromFileInfo(f)
  251. }
  252. // Close removes the peer from the model and closes the underlyign connection if possible.
  253. // Implements the protocol.Model interface.
  254. func (m *Model) Close(node string, err error) {
  255. m.Lock()
  256. defer m.Unlock()
  257. conn, ok := m.rawConn[node]
  258. if ok {
  259. conn.Close()
  260. }
  261. delete(m.remote, node)
  262. delete(m.protoConn, node)
  263. delete(m.rawConn, node)
  264. m.recomputeGlobal()
  265. m.recomputeNeed()
  266. }
  267. // Request returns the specified data segment by reading it from local disk.
  268. // Implements the protocol.Model interface.
  269. func (m *Model) Request(nodeID, name string, offset int64, size uint32, hash []byte) ([]byte, error) {
  270. // Verify that the requested file exists in the local and global model.
  271. m.RLock()
  272. lf, localOk := m.local[name]
  273. _, globalOk := m.global[name]
  274. m.RUnlock()
  275. if !localOk || !globalOk {
  276. log.Printf("SECURITY (nonexistent file) REQ(in): %s: %q o=%d s=%d h=%x", nodeID, name, offset, size, hash)
  277. return nil, ErrNoSuchFile
  278. }
  279. if lf.Flags&protocol.FlagInvalid != 0 {
  280. return nil, ErrInvalid
  281. }
  282. if m.trace["net"] && nodeID != "<local>" {
  283. log.Printf("NET REQ(in): %s: %q o=%d s=%d h=%x", nodeID, name, offset, size, hash)
  284. }
  285. fn := path.Join(m.dir, name)
  286. fd, err := os.Open(fn) // XXX: Inefficient, should cache fd?
  287. if err != nil {
  288. return nil, err
  289. }
  290. defer fd.Close()
  291. buf := buffers.Get(int(size))
  292. _, err = fd.ReadAt(buf, offset)
  293. if err != nil {
  294. return nil, err
  295. }
  296. if m.limitRequestRate != nil {
  297. for s := 0; s < len(buf); s += 1024 {
  298. <-m.limitRequestRate
  299. }
  300. }
  301. return buf, nil
  302. }
  303. // ReplaceLocal replaces the local repository index with the given list of files.
  304. // Change suppression is applied to files changing too often.
  305. func (m *Model) ReplaceLocal(fs []File) {
  306. m.Lock()
  307. defer m.Unlock()
  308. var updated bool
  309. var newLocal = make(map[string]File)
  310. for _, f := range fs {
  311. newLocal[f.Name] = f
  312. if ef := m.local[f.Name]; !ef.Equals(f) {
  313. updated = true
  314. }
  315. }
  316. if m.markDeletedLocals(newLocal) {
  317. updated = true
  318. }
  319. if len(newLocal) != len(m.local) {
  320. updated = true
  321. }
  322. if updated {
  323. m.local = newLocal
  324. m.recomputeGlobal()
  325. m.recomputeNeed()
  326. m.updatedLocal = time.Now().Unix()
  327. m.lastIdxBcastRequest = time.Now()
  328. }
  329. }
  330. // SeedLocal replaces the local repository index with the given list of files,
  331. // in protocol data types. Does not track deletes, should only be used to seed
  332. // the local index from a cache file at startup.
  333. func (m *Model) SeedLocal(fs []protocol.FileInfo) {
  334. m.Lock()
  335. defer m.Unlock()
  336. m.local = make(map[string]File)
  337. for _, f := range fs {
  338. m.local[f.Name] = fileFromFileInfo(f)
  339. }
  340. m.recomputeGlobal()
  341. m.recomputeNeed()
  342. }
  343. // ConnectedTo returns true if we are connected to the named node.
  344. func (m *Model) ConnectedTo(nodeID string) bool {
  345. m.RLock()
  346. defer m.RUnlock()
  347. _, ok := m.protoConn[nodeID]
  348. return ok
  349. }
  350. // ProtocolIndex returns the current local index in protocol data types.
  351. func (m *Model) ProtocolIndex() []protocol.FileInfo {
  352. m.RLock()
  353. defer m.RUnlock()
  354. return m.protocolIndex()
  355. }
  356. // RepoID returns a unique ID representing the current repository location.
  357. func (m *Model) RepoID() string {
  358. return fmt.Sprintf("%x", sha1.Sum([]byte(m.dir)))
  359. }
  360. // AddConnection adds a new peer connection to the model. An initial index will
  361. // be sent to the connected peer, thereafter index updates whenever the local
  362. // repository changes.
  363. func (m *Model) AddConnection(rawConn io.Closer, protoConn Connection) {
  364. nodeID := protoConn.ID()
  365. m.Lock()
  366. m.protoConn[nodeID] = protoConn
  367. m.rawConn[nodeID] = rawConn
  368. m.Unlock()
  369. m.RLock()
  370. idx := m.protocolIndex()
  371. m.RUnlock()
  372. go func() {
  373. protoConn.Index(idx)
  374. }()
  375. if m.rwRunning {
  376. for i := 0; i < m.parallellRequests; i++ {
  377. i := i
  378. go func() {
  379. if m.trace["pull"] {
  380. log.Println("PULL: Starting", nodeID, i)
  381. }
  382. for {
  383. m.RLock()
  384. if _, ok := m.protoConn[nodeID]; !ok {
  385. if m.trace["pull"] {
  386. log.Println("PULL: Exiting", nodeID, i)
  387. }
  388. m.RUnlock()
  389. return
  390. }
  391. m.RUnlock()
  392. qb, ok := m.fq.Get(nodeID)
  393. if ok {
  394. if m.trace["pull"] {
  395. log.Println("PULL: Request", nodeID, i, qb.name, qb.block.Offset)
  396. }
  397. data, _ := protoConn.Request(qb.name, qb.block.Offset, qb.block.Size, qb.block.Hash)
  398. m.fq.Done(qb.name, qb.block.Offset, data)
  399. } else {
  400. time.Sleep(1 * time.Second)
  401. }
  402. }
  403. }()
  404. }
  405. }
  406. }
  407. func (m *Model) shouldSuppressChange(name string) bool {
  408. sup := shouldSuppressChange(m.fileLastChanged[name], m.fileWasSuppressed[name])
  409. if sup {
  410. m.fileWasSuppressed[name]++
  411. } else {
  412. m.fileWasSuppressed[name] = 0
  413. m.fileLastChanged[name] = time.Now()
  414. }
  415. return sup
  416. }
  417. func shouldSuppressChange(lastChange time.Time, numChanges int) bool {
  418. sinceLast := time.Since(lastChange)
  419. if sinceLast > maxFileHoldTimeS*time.Second {
  420. return false
  421. }
  422. if sinceLast < time.Duration((numChanges+2)*minFileHoldTimeS)*time.Second {
  423. return true
  424. }
  425. return false
  426. }
  427. // protocolIndex returns the current local index in protocol data types.
  428. // Must be called with the read lock held.
  429. func (m *Model) protocolIndex() []protocol.FileInfo {
  430. var index []protocol.FileInfo
  431. for _, f := range m.local {
  432. mf := fileInfoFromFile(f)
  433. if m.trace["idx"] {
  434. var flagComment string
  435. if mf.Flags&protocol.FlagDeleted != 0 {
  436. flagComment = " (deleted)"
  437. }
  438. log.Printf("IDX(out): %q m=%d f=%o%s v=%d (%d blocks)", mf.Name, mf.Modified, mf.Flags, flagComment, mf.Version, len(mf.Blocks))
  439. }
  440. index = append(index, mf)
  441. }
  442. return index
  443. }
  444. func (m *Model) requestGlobal(nodeID, name string, offset int64, size uint32, hash []byte) ([]byte, error) {
  445. m.RLock()
  446. nc, ok := m.protoConn[nodeID]
  447. m.RUnlock()
  448. if !ok {
  449. return nil, fmt.Errorf("requestGlobal: no such node: %s", nodeID)
  450. }
  451. if m.trace["net"] {
  452. log.Printf("NET REQ(out): %s: %q o=%d s=%d h=%x", nodeID, name, offset, size, hash)
  453. }
  454. return nc.Request(name, offset, size, hash)
  455. }
  456. func (m *Model) broadcastIndexLoop() {
  457. for {
  458. m.RLock()
  459. bcastRequested := m.lastIdxBcastRequest.After(m.lastIdxBcast)
  460. holdtimeExceeded := time.Since(m.lastIdxBcastRequest) > idxBcastHoldtime
  461. m.RUnlock()
  462. maxDelayExceeded := time.Since(m.lastIdxBcast) > idxBcastMaxDelay
  463. if bcastRequested && (holdtimeExceeded || maxDelayExceeded) {
  464. m.Lock()
  465. var indexWg sync.WaitGroup
  466. indexWg.Add(len(m.protoConn))
  467. idx := m.protocolIndex()
  468. m.lastIdxBcast = time.Now()
  469. for _, node := range m.protoConn {
  470. node := node
  471. if m.trace["net"] {
  472. log.Printf("NET IDX(out/loop): %s: %d files", node.ID(), len(idx))
  473. }
  474. go func() {
  475. node.Index(idx)
  476. indexWg.Done()
  477. }()
  478. }
  479. m.Unlock()
  480. indexWg.Wait()
  481. }
  482. time.Sleep(idxBcastHoldtime)
  483. }
  484. }
  485. // markDeletedLocals sets the deleted flag on files that have gone missing locally.
  486. // Must be called with the write lock held.
  487. func (m *Model) markDeletedLocals(newLocal map[string]File) bool {
  488. // For every file in the existing local table, check if they are also
  489. // present in the new local table. If they are not, check that we already
  490. // had the newest version available according to the global table and if so
  491. // note the file as having been deleted.
  492. var updated bool
  493. for n, f := range m.local {
  494. if _, ok := newLocal[n]; !ok {
  495. if gf := m.global[n]; !gf.NewerThan(f) {
  496. if f.Flags&protocol.FlagDeleted == 0 {
  497. f.Flags = protocol.FlagDeleted
  498. f.Version++
  499. f.Blocks = nil
  500. updated = true
  501. }
  502. newLocal[n] = f
  503. }
  504. }
  505. }
  506. return updated
  507. }
  508. func (m *Model) updateLocalLocked(f File) {
  509. m.Lock()
  510. m.updateLocal(f)
  511. m.Unlock()
  512. }
  513. func (m *Model) updateLocal(f File) {
  514. if ef, ok := m.local[f.Name]; !ok || !ef.Equals(f) {
  515. m.local[f.Name] = f
  516. m.recomputeGlobal()
  517. m.recomputeNeed()
  518. m.updatedLocal = time.Now().Unix()
  519. m.lastIdxBcastRequest = time.Now()
  520. }
  521. }
  522. // Must be called with the write lock held.
  523. func (m *Model) recomputeGlobal() {
  524. var newGlobal = make(map[string]File)
  525. for n, f := range m.local {
  526. newGlobal[n] = f
  527. }
  528. var highestMod int64
  529. for _, fs := range m.remote {
  530. for n, nf := range fs {
  531. if lf, ok := newGlobal[n]; !ok || nf.NewerThan(lf) {
  532. newGlobal[n] = nf
  533. if nf.Modified > highestMod {
  534. highestMod = nf.Modified
  535. }
  536. }
  537. }
  538. }
  539. // Figure out if anything actually changed
  540. var updated bool
  541. if highestMod > m.updateGlobal || len(newGlobal) != len(m.global) {
  542. updated = true
  543. } else {
  544. for n, f0 := range newGlobal {
  545. if f1, ok := m.global[n]; !ok || !f0.Equals(f1) {
  546. updated = true
  547. break
  548. }
  549. }
  550. }
  551. if updated {
  552. m.updateGlobal = time.Now().Unix()
  553. m.global = newGlobal
  554. }
  555. }
  556. // Must be called with the write lock held.
  557. func (m *Model) recomputeNeed() {
  558. for n, gf := range m.global {
  559. if m.fq.Queued(n) {
  560. continue
  561. }
  562. lf, ok := m.local[n]
  563. if !ok || gf.NewerThan(lf) {
  564. if gf.Flags&protocol.FlagInvalid != 0 {
  565. // Never attempt to sync invalid files
  566. continue
  567. }
  568. if gf.Flags&protocol.FlagDeleted != 0 && !m.delete {
  569. // Don't want to delete files, so forget this need
  570. continue
  571. }
  572. if gf.Flags&protocol.FlagDeleted != 0 && !ok {
  573. // Don't have the file, so don't need to delete it
  574. continue
  575. }
  576. if m.trace["need"] {
  577. log.Printf("NEED: lf:%v gf:%v", lf, gf)
  578. }
  579. if gf.Flags&protocol.FlagDeleted != 0 {
  580. m.dq <- gf
  581. } else {
  582. local, remote := BlockDiff(lf.Blocks, gf.Blocks)
  583. fm := fileMonitor{
  584. name: n,
  585. path: path.Clean(path.Join(m.dir, n)),
  586. global: gf,
  587. model: m,
  588. localBlocks: local,
  589. }
  590. m.fq.Add(n, remote, &fm)
  591. }
  592. }
  593. }
  594. }
  595. // Must be called with the read lock held.
  596. func (m *Model) WhoHas(name string) []string {
  597. var remote []string
  598. gf := m.global[name]
  599. for node, files := range m.remote {
  600. if file, ok := files[name]; ok && file.Equals(gf) {
  601. remote = append(remote, node)
  602. }
  603. }
  604. return remote
  605. }
  606. func (m *Model) deleteFiles() {
  607. for file := range m.dq {
  608. if m.trace["file"] {
  609. log.Println("FILE: Delete", file.Name)
  610. }
  611. path := path.Clean(path.Join(m.dir, file.Name))
  612. err := os.Remove(path)
  613. if err != nil {
  614. log.Printf("WARNING: %s: %v", file.Name, err)
  615. }
  616. m.updateLocalLocked(file)
  617. }
  618. }
  619. func fileFromFileInfo(f protocol.FileInfo) File {
  620. var blocks = make([]Block, len(f.Blocks))
  621. var offset int64
  622. for i, b := range f.Blocks {
  623. blocks[i] = Block{
  624. Offset: offset,
  625. Size: b.Size,
  626. Hash: b.Hash,
  627. }
  628. offset += int64(b.Size)
  629. }
  630. return File{
  631. Name: f.Name,
  632. Flags: f.Flags,
  633. Modified: f.Modified,
  634. Version: f.Version,
  635. Blocks: blocks,
  636. }
  637. }
  638. func fileInfoFromFile(f File) protocol.FileInfo {
  639. var blocks = make([]protocol.BlockInfo, len(f.Blocks))
  640. for i, b := range f.Blocks {
  641. blocks[i] = protocol.BlockInfo{
  642. Size: b.Size,
  643. Hash: b.Hash,
  644. }
  645. }
  646. return protocol.FileInfo{
  647. Name: f.Name,
  648. Flags: f.Flags,
  649. Modified: f.Modified,
  650. Version: f.Version,
  651. Blocks: blocks,
  652. }
  653. }