diff --git a/cmd/stindex/main.go b/cmd/stindex/main.go index 49c4f50c2..e00c4fb2c 100644 --- a/cmd/stindex/main.go +++ b/cmd/stindex/main.go @@ -44,7 +44,7 @@ func main() { if *device == "" { log.Printf("*** Global index for folder %q", *folder) fs.WithGlobalTruncated(func(fi files.FileIntf) bool { - f := fi.(protocol.FileInfoTruncated) + f := fi.(files.FileInfoTruncated) fmt.Println(f) fmt.Println("\t", fs.Availability(f.Name)) return true @@ -56,7 +56,7 @@ func main() { } log.Printf("*** Have index for folder %q device %q", *folder, n) fs.WithHaveTruncated(n, func(fi files.FileIntf) bool { - f := fi.(protocol.FileInfoTruncated) + f := fi.(files.FileInfoTruncated) fmt.Println(f) return true }) diff --git a/cmd/syncthing/gui.go b/cmd/syncthing/gui.go index e46cd8039..a4398db32 100644 --- a/cmd/syncthing/gui.go +++ b/cmd/syncthing/gui.go @@ -36,6 +36,7 @@ import ( "github.com/syncthing/syncthing/internal/config" "github.com/syncthing/syncthing/internal/discover" "github.com/syncthing/syncthing/internal/events" + "github.com/syncthing/syncthing/internal/files" "github.com/syncthing/syncthing/internal/model" "github.com/syncthing/syncthing/internal/osutil" "github.com/syncthing/syncthing/internal/protocol" @@ -782,9 +783,9 @@ func mimeTypeForFile(file string) string { } } -func toNeedSlice(files []protocol.FileInfoTruncated) []map[string]interface{} { - output := make([]map[string]interface{}, len(files)) - for i, file := range files { +func toNeedSlice(fs []files.FileInfoTruncated) []map[string]interface{} { + output := make([]map[string]interface{}, len(fs)) + for i, file := range fs { output[i] = map[string]interface{}{ "Name": file.Name, "Flags": file.Flags, @@ -792,7 +793,7 @@ func toNeedSlice(files []protocol.FileInfoTruncated) []map[string]interface{} { "Version": file.Version, "LocalVersion": file.LocalVersion, "NumBlocks": file.NumBlocks, - "Size": protocol.BlocksToSize(file.NumBlocks), + "Size": files.BlocksToSize(file.NumBlocks), } } return output diff --git a/internal/files/leveldb.go b/internal/files/leveldb.go index 51c7fb3fa..9e4f308cf 100644 --- a/internal/files/leveldb.go +++ b/internal/files/leveldb.go @@ -244,7 +244,7 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File if debugDB { l.Debugln("generic replace; exists - compare") } - var ef protocol.FileInfoTruncated + var ef FileInfoTruncated ef.UnmarshalXDR(dbi.Value()) if fs[fsi].Version > ef.Version || (fs[fsi].Version == ef.Version && fs[fsi].Flags != ef.Flags) { @@ -306,7 +306,7 @@ func ldbReplace(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) u func ldbReplaceWithDelete(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) uint64 { return ldbGenericReplace(db, folder, device, fs, func(db dbReader, batch dbWriter, folder, device, name []byte, dbi iterator.Iterator) uint64 { - var tf protocol.FileInfoTruncated + var tf FileInfoTruncated err := tf.UnmarshalXDR(dbi.Value()) if err != nil { panic(err) @@ -376,7 +376,7 @@ func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) ui continue } - var ef protocol.FileInfoTruncated + var ef FileInfoTruncated err = ef.UnmarshalXDR(bs) if err != nil { panic(err) @@ -557,7 +557,7 @@ func ldbWithHave(db *leveldb.DB, folder, device []byte, truncate bool, fn Iterat } } -func ldbWithAllFolderTruncated(db *leveldb.DB, folder []byte, fn func(device []byte, f protocol.FileInfoTruncated) bool) { +func ldbWithAllFolderTruncated(db *leveldb.DB, folder []byte, fn func(device []byte, f FileInfoTruncated) bool) { runtime.GC() start := deviceKey(folder, nil, nil) // before all folder/device files @@ -581,7 +581,7 @@ func ldbWithAllFolderTruncated(db *leveldb.DB, folder []byte, fn func(device []b for dbi.Next() { device := deviceKeyDevice(dbi.Key()) - var f protocol.FileInfoTruncated + var f FileInfoTruncated err := f.UnmarshalXDR(dbi.Value()) if err != nil { panic(err) @@ -938,7 +938,7 @@ func ldbDropFolder(db *leveldb.DB, folder []byte) { func unmarshalTrunc(bs []byte, truncate bool) (FileIntf, error) { if truncate { - var tf protocol.FileInfoTruncated + var tf FileInfoTruncated err := tf.UnmarshalXDR(bs) return tf, err } else { diff --git a/internal/files/set.go b/internal/files/set.go index 4c1c418b4..f6ddb41b9 100644 --- a/internal/files/set.go +++ b/internal/files/set.go @@ -65,7 +65,7 @@ func NewSet(folder string, db *leveldb.DB) *Set { ldbCheckGlobals(db, []byte(folder)) var deviceID protocol.DeviceID - ldbWithAllFolderTruncated(db, []byte(folder), func(device []byte, f protocol.FileInfoTruncated) bool { + ldbWithAllFolderTruncated(db, []byte(folder), func(device []byte, f FileInfoTruncated) bool { copy(deviceID[:], device) if f.LocalVersion > s.localVersion[deviceID] { s.localVersion[deviceID] = f.LocalVersion @@ -232,7 +232,7 @@ func nativeFileIterator(fn Iterator) Iterator { case protocol.FileInfo: f.Name = osutil.NativeFilename(f.Name) return fn(f) - case protocol.FileInfoTruncated: + case FileInfoTruncated: f.Name = osutil.NativeFilename(f.Name) return fn(f) default: diff --git a/internal/files/set_test.go b/internal/files/set_test.go index 44839fc54..bba871729 100644 --- a/internal/files/set_test.go +++ b/internal/files/set_test.go @@ -51,7 +51,7 @@ func genBlocks(n int) []protocol.BlockInfo { func globalList(s *files.Set) []protocol.FileInfo { var fs []protocol.FileInfo - s.WithGlobal(func(fi FileIntf) bool { + s.WithGlobal(func(fi files.FileIntf) bool { f := fi.(protocol.FileInfo) fs = append(fs, f) return true @@ -61,7 +61,7 @@ func globalList(s *files.Set) []protocol.FileInfo { func haveList(s *files.Set, n protocol.DeviceID) []protocol.FileInfo { var fs []protocol.FileInfo - s.WithHave(n, func(fi FileIntf) bool { + s.WithHave(n, func(fi files.FileIntf) bool { f := fi.(protocol.FileInfo) fs = append(fs, f) return true @@ -71,7 +71,7 @@ func haveList(s *files.Set, n protocol.DeviceID) []protocol.FileInfo { func needList(s *files.Set, n protocol.DeviceID) []protocol.FileInfo { var fs []protocol.FileInfo - s.WithNeed(n, func(fi FileIntf) bool { + s.WithNeed(n, func(fi files.FileIntf) bool { f := fi.(protocol.FileInfo) fs = append(fs, f) return true diff --git a/internal/files/truncated.go b/internal/files/truncated.go new file mode 100644 index 000000000..7f4519d53 --- /dev/null +++ b/internal/files/truncated.go @@ -0,0 +1,86 @@ +// Copyright (C) 2014 The Syncthing Authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see . + +//go:generate -command genxdr go run ../../Godeps/_workspace/src/github.com/calmh/xdr/cmd/genxdr/main.go +//go:generate genxdr -o truncated_xdr.go truncated.go + +package files + +import ( + "fmt" + + "github.com/syncthing/syncthing/internal/protocol" +) + +// Used for unmarshalling a FileInfo structure but skipping the block list. +type FileInfoTruncated struct { + Name string // max:8192 + Flags uint32 + Modified int64 + Version uint64 + LocalVersion uint64 + NumBlocks uint32 +} + +func (f FileInfoTruncated) String() string { + return fmt.Sprintf("File{Name:%q, Flags:0%o, Modified:%d, Version:%d, Size:%d, NumBlocks:%d}", + f.Name, f.Flags, f.Modified, f.Version, f.Size(), f.NumBlocks) +} + +// Returns a statistical guess on the size, not the exact figure +func (f FileInfoTruncated) Size() int64 { + if f.IsDeleted() || f.IsDirectory() { + return 128 + } + return BlocksToSize(f.NumBlocks) +} + +func (f FileInfoTruncated) IsDeleted() bool { + return f.Flags&protocol.FlagDeleted != 0 +} + +func (f FileInfoTruncated) IsInvalid() bool { + return f.Flags&protocol.FlagInvalid != 0 +} + +func (f FileInfoTruncated) IsDirectory() bool { + return f.Flags&protocol.FlagDirectory != 0 +} + +func (f FileInfoTruncated) IsSymlink() bool { + return f.Flags&protocol.FlagSymlink != 0 +} + +func (f FileInfoTruncated) HasPermissionBits() bool { + return f.Flags&protocol.FlagNoPermBits == 0 +} + +func Truncate(f protocol.FileInfo) FileInfoTruncated { + return FileInfoTruncated{ + Name: f.Name, + Flags: f.Flags, + Modified: f.Modified, + Version: f.Version, + LocalVersion: f.LocalVersion, + NumBlocks: uint32(len(f.Blocks)), + } +} + +func BlocksToSize(num uint32) int64 { + if num < 2 { + return protocol.BlockSize / 2 + } + return int64(num-1)*protocol.BlockSize + protocol.BlockSize/2 +} diff --git a/internal/files/truncated_xdr.go b/internal/files/truncated_xdr.go new file mode 100644 index 000000000..272cea5c3 --- /dev/null +++ b/internal/files/truncated_xdr.go @@ -0,0 +1,112 @@ +// ************************************************************ +// This file is automatically generated by genxdr. Do not edit. +// ************************************************************ + +package files + +import ( + "bytes" + "io" + + "github.com/calmh/xdr" +) + +/* + +FileInfoTruncated Structure: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Length of Name | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/ / +\ Name (variable length) \ +/ / ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Flags | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | ++ Modified (64 bits) + +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | ++ Version (64 bits) + +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | ++ Local Version (64 bits) + +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Num Blocks | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +struct FileInfoTruncated { + string Name<8192>; + unsigned int Flags; + hyper Modified; + unsigned hyper Version; + unsigned hyper LocalVersion; + unsigned int NumBlocks; +} + +*/ + +func (o FileInfoTruncated) EncodeXDR(w io.Writer) (int, error) { + var xw = xdr.NewWriter(w) + return o.encodeXDR(xw) +} + +func (o FileInfoTruncated) MarshalXDR() ([]byte, error) { + return o.AppendXDR(make([]byte, 0, 128)) +} + +func (o FileInfoTruncated) MustMarshalXDR() []byte { + bs, err := o.MarshalXDR() + if err != nil { + panic(err) + } + return bs +} + +func (o FileInfoTruncated) AppendXDR(bs []byte) ([]byte, error) { + var aw = xdr.AppendWriter(bs) + var xw = xdr.NewWriter(&aw) + _, err := o.encodeXDR(xw) + return []byte(aw), err +} + +func (o FileInfoTruncated) encodeXDR(xw *xdr.Writer) (int, error) { + if l := len(o.Name); l > 8192 { + return xw.Tot(), xdr.ElementSizeExceeded("Name", l, 8192) + } + xw.WriteString(o.Name) + xw.WriteUint32(o.Flags) + xw.WriteUint64(uint64(o.Modified)) + xw.WriteUint64(o.Version) + xw.WriteUint64(o.LocalVersion) + xw.WriteUint32(o.NumBlocks) + return xw.Tot(), xw.Error() +} + +func (o *FileInfoTruncated) DecodeXDR(r io.Reader) error { + xr := xdr.NewReader(r) + return o.decodeXDR(xr) +} + +func (o *FileInfoTruncated) UnmarshalXDR(bs []byte) error { + var br = bytes.NewReader(bs) + var xr = xdr.NewReader(br) + return o.decodeXDR(xr) +} + +func (o *FileInfoTruncated) decodeXDR(xr *xdr.Reader) error { + o.Name = xr.ReadStringMax(8192) + o.Flags = xr.ReadUint32() + o.Modified = int64(xr.ReadUint64()) + o.Version = xr.ReadUint64() + o.LocalVersion = xr.ReadUint64() + o.NumBlocks = xr.ReadUint32() + return xr.Error() +} diff --git a/internal/model/model.go b/internal/model/model.go index b598d57ef..cfdf0a281 100644 --- a/internal/model/model.go +++ b/internal/model/model.go @@ -422,33 +422,33 @@ func (m *Model) NeedSize(folder string) (nfiles int, bytes int64) { // NeedFiles returns the list of currently needed files in progress, queued, // and to be queued on next puller iteration. Also takes a soft cap which is // only respected when adding files from the model rather than the runner queue. -func (m *Model) NeedFolderFiles(folder string, max int) ([]protocol.FileInfoTruncated, []protocol.FileInfoTruncated, []protocol.FileInfoTruncated) { +func (m *Model) NeedFolderFiles(folder string, max int) ([]files.FileInfoTruncated, []files.FileInfoTruncated, []files.FileInfoTruncated) { defer m.leveldbPanicWorkaround() m.fmut.RLock() defer m.fmut.RUnlock() if rf, ok := m.folderFiles[folder]; ok { - var progress, queued, rest []protocol.FileInfoTruncated + var progress, queued, rest []files.FileInfoTruncated var seen map[string]bool runner, ok := m.folderRunners[folder] if ok { progressNames, queuedNames := runner.Jobs() - progress = make([]protocol.FileInfoTruncated, len(progressNames)) - queued = make([]protocol.FileInfoTruncated, len(queuedNames)) + progress = make([]files.FileInfoTruncated, len(progressNames)) + queued = make([]files.FileInfoTruncated, len(queuedNames)) seen = make(map[string]bool, len(progressNames)+len(queuedNames)) for i, name := range progressNames { if f, ok := rf.GetGlobal(name); ok { - progress[i] = protocol.Truncate(f) /// XXX: Should implement GetGlobalTruncated directly + progress[i] = files.Truncate(f) /// XXX: Should implement GetGlobalTruncated directly seen[name] = true } } for i, name := range queuedNames { if f, ok := rf.GetGlobal(name); ok { - queued[i] = protocol.Truncate(f) /// XXX: Should implement GetGlobalTruncated directly + queued[i] = files.Truncate(f) /// XXX: Should implement GetGlobalTruncated directly seen[name] = true } } @@ -457,7 +457,7 @@ func (m *Model) NeedFolderFiles(folder string, max int) ([]protocol.FileInfoTrun if max < 1 || left > 0 { rf.WithNeedTruncated(protocol.LocalDeviceID, func(f files.FileIntf) bool { left-- - ft := f.(protocol.FileInfoTruncated) + ft := f.(files.FileInfoTruncated) if !seen[ft.Name] { rest = append(rest, ft) } @@ -1170,7 +1170,7 @@ func (m *Model) ScanFolderSub(folder, sub string) error { // TODO: We should limit the Have scanning to start at sub seenPrefix := false fs.WithHaveTruncated(protocol.LocalDeviceID, func(fi files.FileIntf) bool { - f := fi.(protocol.FileInfoTruncated) + f := fi.(files.FileInfoTruncated) if !strings.HasPrefix(f.Name, sub) { // Return true so that we keep iterating, until we get to the part // of the tree we are interested in. Then return false so we stop diff --git a/internal/model/sharedpullerstate.go b/internal/model/sharedpullerstate.go index 015b05deb..943e04a18 100644 --- a/internal/model/sharedpullerstate.go +++ b/internal/model/sharedpullerstate.go @@ -21,6 +21,7 @@ import ( "path/filepath" "sync" + "github.com/syncthing/syncthing/internal/files" "github.com/syncthing/syncthing/internal/protocol" ) @@ -259,7 +260,7 @@ func (s *sharedPullerState) Progress() *pullerProgress { CopiedFromElsewhere: s.copyTotal - s.copyNeeded - s.copyOrigin, Pulled: s.pullTotal - s.pullNeeded, Pulling: s.pullNeeded, - BytesTotal: protocol.BlocksToSize(total), - BytesDone: protocol.BlocksToSize(done), + BytesTotal: files.BlocksToSize(total), + BytesDone: files.BlocksToSize(done), } } diff --git a/internal/protocol/message.go b/internal/protocol/message.go index 3129f3de0..0d8de8070 100644 --- a/internal/protocol/message.go +++ b/internal/protocol/message.go @@ -71,67 +71,6 @@ func (f FileInfo) HasPermissionBits() bool { return f.Flags&FlagNoPermBits == 0 } -func (f FileInfo) ToTruncated() FileInfoTruncated { - return FileInfoTruncated{ - Name: f.Name, - Flags: f.Flags, - Modified: f.Modified, - Version: f.Version, - LocalVersion: f.LocalVersion, - NumBlocks: uint32(len(f.Blocks)), - } -} - -// Used for unmarshalling a FileInfo structure but skipping the actual block list -type FileInfoTruncated struct { - Name string // max:8192 - Flags uint32 - Modified int64 - Version uint64 - LocalVersion uint64 - NumBlocks uint32 -} - -func (f FileInfoTruncated) String() string { - return fmt.Sprintf("File{Name:%q, Flags:0%o, Modified:%d, Version:%d, Size:%d, NumBlocks:%d}", - f.Name, f.Flags, f.Modified, f.Version, f.Size(), f.NumBlocks) -} - -func BlocksToSize(num uint32) int64 { - if num < 2 { - return BlockSize / 2 - } - return int64(num-1)*BlockSize + BlockSize/2 -} - -// Returns a statistical guess on the size, not the exact figure -func (f FileInfoTruncated) Size() int64 { - if f.IsDeleted() || f.IsDirectory() { - return 128 - } - return BlocksToSize(f.NumBlocks) -} - -func (f FileInfoTruncated) IsDeleted() bool { - return f.Flags&FlagDeleted != 0 -} - -func (f FileInfoTruncated) IsInvalid() bool { - return f.Flags&FlagInvalid != 0 -} - -func (f FileInfoTruncated) IsDirectory() bool { - return f.Flags&FlagDirectory != 0 -} - -func (f FileInfoTruncated) IsSymlink() bool { - return f.Flags&FlagSymlink != 0 -} - -func (f FileInfoTruncated) HasPermissionBits() bool { - return f.Flags&FlagNoPermBits == 0 -} - type BlockInfo struct { Offset int64 // noencode (cache only) Size uint32 diff --git a/internal/protocol/message_xdr.go b/internal/protocol/message_xdr.go index da13111c2..fa7b5802b 100644 --- a/internal/protocol/message_xdr.go +++ b/internal/protocol/message_xdr.go @@ -245,106 +245,6 @@ func (o *FileInfo) decodeXDR(xr *xdr.Reader) error { /* -FileInfoTruncated Structure: - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -| Length of Name | -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -/ / -\ Name (variable length) \ -/ / -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -| Flags | -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -| | -+ Modified (64 bits) + -| | -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -| | -+ Version (64 bits) + -| | -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -| | -+ Local Version (64 bits) + -| | -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -| Num Blocks | -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - - -struct FileInfoTruncated { - string Name<8192>; - unsigned int Flags; - hyper Modified; - unsigned hyper Version; - unsigned hyper LocalVersion; - unsigned int NumBlocks; -} - -*/ - -func (o FileInfoTruncated) EncodeXDR(w io.Writer) (int, error) { - var xw = xdr.NewWriter(w) - return o.encodeXDR(xw) -} - -func (o FileInfoTruncated) MarshalXDR() ([]byte, error) { - return o.AppendXDR(make([]byte, 0, 128)) -} - -func (o FileInfoTruncated) MustMarshalXDR() []byte { - bs, err := o.MarshalXDR() - if err != nil { - panic(err) - } - return bs -} - -func (o FileInfoTruncated) AppendXDR(bs []byte) ([]byte, error) { - var aw = xdr.AppendWriter(bs) - var xw = xdr.NewWriter(&aw) - _, err := o.encodeXDR(xw) - return []byte(aw), err -} - -func (o FileInfoTruncated) encodeXDR(xw *xdr.Writer) (int, error) { - if l := len(o.Name); l > 8192 { - return xw.Tot(), xdr.ElementSizeExceeded("Name", l, 8192) - } - xw.WriteString(o.Name) - xw.WriteUint32(o.Flags) - xw.WriteUint64(uint64(o.Modified)) - xw.WriteUint64(o.Version) - xw.WriteUint64(o.LocalVersion) - xw.WriteUint32(o.NumBlocks) - return xw.Tot(), xw.Error() -} - -func (o *FileInfoTruncated) DecodeXDR(r io.Reader) error { - xr := xdr.NewReader(r) - return o.decodeXDR(xr) -} - -func (o *FileInfoTruncated) UnmarshalXDR(bs []byte) error { - var br = bytes.NewReader(bs) - var xr = xdr.NewReader(br) - return o.decodeXDR(xr) -} - -func (o *FileInfoTruncated) decodeXDR(xr *xdr.Reader) error { - o.Name = xr.ReadStringMax(8192) - o.Flags = xr.ReadUint32() - o.Modified = int64(xr.ReadUint64()) - o.Version = xr.ReadUint64() - o.LocalVersion = xr.ReadUint64() - o.NumBlocks = xr.ReadUint32() - return xr.Error() -} - -/* - BlockInfo Structure: 0 1 2 3