@@ -7,6 +7,16 @@ import (
7
7
mod "github.com/ipfs/go-unixfs/mod"
8
8
9
9
context "context"
10
+
11
+ ipld "github.com/ipfs/go-ipld-format"
12
+ )
13
+
14
+ type state uint8
15
+
16
+ const (
17
+ stateFlushed state = iota
18
+ stateDirty
19
+ stateClosed
10
20
)
11
21
12
22
// One `File` can have many `FileDescriptor`s associated to it
@@ -31,14 +41,31 @@ type FileDescriptor interface {
31
41
}
32
42
33
43
type fileDescriptor struct {
34
- inode * File
35
- mod * mod.DagModifier
36
- perms int
37
- sync bool
38
- hasChanges bool
39
-
40
- // TODO: Where is this variable set?
41
- closed bool
44
+ inode * File
45
+ mod * mod.DagModifier
46
+ flags Flags
47
+
48
+ state state
49
+ }
50
+
51
+ func (fi * fileDescriptor ) checkWrite () error {
52
+ if fi .state == stateClosed {
53
+ return ErrClosed
54
+ }
55
+ if ! fi .flags .Write {
56
+ return fmt .Errorf ("file is read-only" )
57
+ }
58
+ return nil
59
+ }
60
+
61
+ func (fi * fileDescriptor ) checkRead () error {
62
+ if fi .state == stateClosed {
63
+ return ErrClosed
64
+ }
65
+ if ! fi .flags .Read {
66
+ return fmt .Errorf ("file is write-only" )
67
+ }
68
+ return nil
42
69
}
43
70
44
71
// Size returns the size of the file referred to by this descriptor
@@ -48,69 +75,52 @@ func (fi *fileDescriptor) Size() (int64, error) {
48
75
49
76
// Truncate truncates the file to size
50
77
func (fi * fileDescriptor ) Truncate (size int64 ) error {
51
- if fi .perms == OpenReadOnly {
52
- return fmt .Errorf ("cannot call truncate on readonly file descriptor" )
78
+ if err := fi .checkWrite (); err != nil {
79
+ return fmt .Errorf ("truncate failed: %s" , err )
53
80
}
54
- fi .hasChanges = true
81
+ fi .state = stateDirty
55
82
return fi .mod .Truncate (size )
56
83
}
57
84
58
85
// Write writes the given data to the file at its current offset
59
86
func (fi * fileDescriptor ) Write (b []byte ) (int , error ) {
60
- if fi .perms == OpenReadOnly {
61
- return 0 , fmt .Errorf ("cannot write on not writeable descriptor" )
87
+ if err := fi .checkWrite (); err != nil {
88
+ return 0 , fmt .Errorf ("write failed: %s" , err )
62
89
}
63
- fi .hasChanges = true
90
+ fi .state = stateDirty
64
91
return fi .mod .Write (b )
65
92
}
66
93
67
94
// Read reads into the given buffer from the current offset
68
95
func (fi * fileDescriptor ) Read (b []byte ) (int , error ) {
69
- if fi .perms == OpenWriteOnly {
70
- return 0 , fmt .Errorf ("cannot read on write-only descriptor" )
96
+ if err := fi .checkRead (); err != nil {
97
+ return 0 , fmt .Errorf ("read failed: %s" , err )
71
98
}
72
99
return fi .mod .Read (b )
73
100
}
74
101
75
102
// Read reads into the given buffer from the current offset
76
103
func (fi * fileDescriptor ) CtxReadFull (ctx context.Context , b []byte ) (int , error ) {
77
- if fi .perms == OpenWriteOnly {
78
- return 0 , fmt .Errorf ("cannot read on write-only descriptor" )
104
+ if err := fi .checkRead (); err != nil {
105
+ return 0 , fmt .Errorf ("read failed: %s" , err )
79
106
}
80
107
return fi .mod .CtxReadFull (ctx , b )
81
108
}
82
109
83
110
// Close flushes, then propogates the modified dag node up the directory structure
84
111
// and signals a republish to occur
85
112
func (fi * fileDescriptor ) Close () error {
86
- defer func () {
87
- switch fi .perms {
88
- case OpenReadOnly :
89
- fi .inode .desclock .RUnlock ()
90
- case OpenWriteOnly , OpenReadWrite :
91
- fi .inode .desclock .Unlock ()
92
- }
93
- // TODO: `closed` should be set here.
94
- }()
95
-
96
- if fi .closed {
97
- panic ("attempted to close file descriptor twice!" )
113
+ if fi .state == stateClosed {
114
+ return ErrClosed
98
115
}
99
-
100
- if fi .hasChanges {
101
- err := fi .mod .Sync ()
102
- if err != nil {
103
- return err
104
- }
105
-
106
- fi .hasChanges = false
107
-
108
- // explicitly stay locked for flushUp call,
109
- // it will manage the lock for us
110
- return fi .flushUp (fi .sync )
116
+ if fi .flags .Write {
117
+ defer fi .inode .desclock .Unlock ()
118
+ } else if fi .flags .Read {
119
+ defer fi .inode .desclock .RUnlock ()
111
120
}
112
-
113
- return nil
121
+ err := fi .flushUp (fi .flags .Sync )
122
+ fi .state = stateClosed
123
+ return err
114
124
}
115
125
116
126
// Flush generates a new version of the node of the underlying
@@ -126,47 +136,57 @@ func (fi *fileDescriptor) Flush() error {
126
136
// If `fullSync` is set the changes are propagated upwards
127
137
// (the `Up` part of `flushUp`).
128
138
func (fi * fileDescriptor ) flushUp (fullSync bool ) error {
129
- nd , err := fi .mod .GetNode ()
130
- if err != nil {
131
- return err
132
- }
139
+ var nd ipld.Node
140
+ switch fi .state {
141
+ case stateDirty :
142
+ // calls mod.Sync internally.
143
+ var err error
144
+ nd , err = fi .mod .GetNode ()
145
+ if err != nil {
146
+ return err
147
+ }
148
+ err = fi .inode .dagService .Add (context .TODO (), nd )
149
+ if err != nil {
150
+ return err
151
+ }
152
+ fi .inode .nodeLock .Lock ()
153
+ fi .inode .node = nd
154
+ fi .inode .nodeLock .Unlock ()
155
+ fallthrough
156
+ case stateFlushed :
157
+ if ! fullSync {
158
+ return nil
159
+ }
133
160
134
- err = fi .inode .dagService .Add (context .TODO (), nd )
135
- if err != nil {
136
- return err
137
- }
138
- // TODO: Very similar logic to the update process in
139
- // `Directory`, the logic should be unified, both structures
140
- // (`File` and `Directory`) are backed by a IPLD node with
141
- // a UnixFS format that is the actual target of the update
142
- // (regenerating it and adding it to the DAG service).
143
-
144
- fi .inode .nodeLock .Lock ()
145
- fi .inode .node = nd
146
- // TODO: Create a `SetNode` method.
147
- name := fi .inode .name
148
- parent := fi .inode .parent
149
- // TODO: Can the parent be modified? Do we need to do this inside the lock?
150
- fi .inode .nodeLock .Unlock ()
151
- // TODO: Maybe all this logic should happen in `File`.
152
-
153
- if fullSync {
154
- return parent .updateChildEntry (child {name , nd })
155
- }
161
+ fi .inode .nodeLock .Lock ()
162
+ nd = fi .inode .node
163
+ parent := fi .inode .parent
164
+ name := fi .inode .name
165
+ fi .inode .nodeLock .Unlock ()
156
166
157
- return nil
167
+ if err := parent .updateChildEntry (child {name , nd }); err != nil {
168
+ return err
169
+ }
170
+ fi .state = stateFlushed
171
+ return nil
172
+ default :
173
+ panic ("invalid state" )
174
+ }
158
175
}
159
176
160
177
// Seek implements io.Seeker
161
178
func (fi * fileDescriptor ) Seek (offset int64 , whence int ) (int64 , error ) {
179
+ if fi .state == stateClosed {
180
+ return 0 , fmt .Errorf ("seek failed: %s" , ErrClosed )
181
+ }
162
182
return fi .mod .Seek (offset , whence )
163
183
}
164
184
165
185
// Write At writes the given bytes at the offset 'at'
166
186
func (fi * fileDescriptor ) WriteAt (b []byte , at int64 ) (int , error ) {
167
- if fi .perms == OpenReadOnly {
168
- return 0 , fmt .Errorf ("cannot write on not writeable descriptor" )
187
+ if err := fi .checkWrite (); err != nil {
188
+ return 0 , fmt .Errorf ("write-at failed: %s" , err )
169
189
}
170
- fi .hasChanges = true
190
+ fi .state = stateDirty
171
191
return fi .mod .WriteAt (b , at )
172
192
}
0 commit comments