1// Copyright (C) 2021 Storj Labs, Inc.
2// See LICENSE for copying information.
3
4package ulfs
5
6import (
7	"context"
8	"io"
9	"os"
10	"time"
11
12	"github.com/zeebo/clingy"
13	"github.com/zeebo/errs"
14
15	"storj.io/storj/cmd/uplinkng/ulloc"
16	"storj.io/uplink"
17)
18
19// ListOptions describes options to the List command.
20type ListOptions struct {
21	Recursive bool
22	Pending   bool
23	Expanded  bool
24}
25
26func (lo *ListOptions) isRecursive() bool { return lo != nil && lo.Recursive }
27func (lo *ListOptions) isPending() bool   { return lo != nil && lo.Pending }
28
29// RemoveOptions describes options to the Remove command.
30type RemoveOptions struct {
31	Pending bool
32}
33
34func (ro *RemoveOptions) isPending() bool { return ro != nil && ro.Pending }
35
36// OpenOptions describes options for Filesystem.Open.
37type OpenOptions struct {
38	Offset int64
39	Length int64
40}
41
42// Filesystem represents either the local Filesystem or the data backed by a project.
43type Filesystem interface {
44	Close() error
45	Open(ctx clingy.Context, loc ulloc.Location, opts *OpenOptions) (ReadHandle, error)
46	Create(ctx clingy.Context, loc ulloc.Location) (WriteHandle, error)
47	Move(ctx clingy.Context, source, dest ulloc.Location) error
48	Remove(ctx context.Context, loc ulloc.Location, opts *RemoveOptions) error
49	List(ctx context.Context, prefix ulloc.Location, opts *ListOptions) (ObjectIterator, error)
50	IsLocalDir(ctx context.Context, loc ulloc.Location) bool
51	Stat(ctx context.Context, loc ulloc.Location) (*ObjectInfo, error)
52}
53
54//
55// object info
56//
57
58// ObjectInfo is a simpler *uplink.Object that contains the minimal information the
59// uplink command needs that multiple types can be converted to.
60type ObjectInfo struct {
61	Loc           ulloc.Location
62	IsPrefix      bool
63	Created       time.Time
64	ContentLength int64
65	Expires       time.Time
66	Metadata      uplink.CustomMetadata
67}
68
69// uplinkObjectToObjectInfo returns an objectInfo converted from an *uplink.Object.
70func uplinkObjectToObjectInfo(bucket string, obj *uplink.Object) ObjectInfo {
71	return ObjectInfo{
72		Loc:           ulloc.NewRemote(bucket, obj.Key),
73		IsPrefix:      obj.IsPrefix,
74		Created:       obj.System.Created,
75		ContentLength: obj.System.ContentLength,
76		Expires:       obj.System.Expires,
77		Metadata:      obj.Custom,
78	}
79}
80
81// uplinkUploadInfoToObjectInfo returns an objectInfo converted from an *uplink.Object.
82func uplinkUploadInfoToObjectInfo(bucket string, upl *uplink.UploadInfo) ObjectInfo {
83	return ObjectInfo{
84		Loc:           ulloc.NewRemote(bucket, upl.Key),
85		IsPrefix:      upl.IsPrefix,
86		Created:       upl.System.Created,
87		ContentLength: upl.System.ContentLength,
88		Expires:       upl.System.Expires,
89		Metadata:      upl.Custom,
90	}
91}
92
93//
94// read handles
95//
96
97// ReadHandle is something that can be read from.
98type ReadHandle interface {
99	io.Reader
100	io.Closer
101	Info() ObjectInfo
102}
103
104// uplinkReadHandle implements readHandle for *uplink.Downloads.
105type uplinkReadHandle struct {
106	bucket string
107	dl     *uplink.Download
108}
109
110// newUplinkReadHandle constructs an *uplinkReadHandle from an *uplink.Download.
111func newUplinkReadHandle(bucket string, dl *uplink.Download) *uplinkReadHandle {
112	return &uplinkReadHandle{
113		bucket: bucket,
114		dl:     dl,
115	}
116}
117
118func (u *uplinkReadHandle) Read(p []byte) (int, error) { return u.dl.Read(p) }
119func (u *uplinkReadHandle) Close() error               { return u.dl.Close() }
120func (u *uplinkReadHandle) Info() ObjectInfo           { return uplinkObjectToObjectInfo(u.bucket, u.dl.Info()) }
121
122// osReadHandle implements readHandle for *os.Files.
123type osReadHandle struct {
124	raw  *os.File
125	info ObjectInfo
126}
127
128// newOsReadHandle constructs an *osReadHandle from an *os.File.
129func newOSReadHandle(fh *os.File) (*osReadHandle, error) {
130	fi, err := fh.Stat()
131	if err != nil {
132		return nil, errs.Wrap(err)
133	}
134	return &osReadHandle{
135		raw: fh,
136		info: ObjectInfo{
137			Loc:           ulloc.NewLocal(fh.Name()),
138			IsPrefix:      false,
139			Created:       fi.ModTime(), // TODO: os specific crtime
140			ContentLength: fi.Size(),
141		},
142	}, nil
143}
144
145func (o *osReadHandle) Read(p []byte) (int, error) { return o.raw.Read(p) }
146func (o *osReadHandle) Close() error               { return o.raw.Close() }
147func (o *osReadHandle) Info() ObjectInfo           { return o.info }
148
149// genericReadHandle implements readHandle for an io.Reader.
150type genericReadHandle struct{ r io.Reader }
151
152// newGenericReadHandle constructs a *genericReadHandle from any io.Reader.
153func newGenericReadHandle(r io.Reader) *genericReadHandle {
154	return &genericReadHandle{r: r}
155}
156
157func (g *genericReadHandle) Read(p []byte) (int, error) { return g.r.Read(p) }
158func (g *genericReadHandle) Close() error               { return nil }
159func (g *genericReadHandle) Info() ObjectInfo           { return ObjectInfo{ContentLength: -1} }
160
161//
162// write handles
163//
164
165// WriteHandle is anything that can be written to with commit/abort semantics.
166type WriteHandle interface {
167	io.Writer
168	Commit() error
169	Abort() error
170}
171
172// uplinkWriteHandle implements writeHandle for *uplink.Uploads.
173type uplinkWriteHandle uplink.Upload
174
175// newUplinkWriteHandle constructs an *uplinkWriteHandle from an *uplink.Upload.
176func newUplinkWriteHandle(dl *uplink.Upload) *uplinkWriteHandle {
177	return (*uplinkWriteHandle)(dl)
178}
179
180func (u *uplinkWriteHandle) raw() *uplink.Upload {
181	return (*uplink.Upload)(u)
182}
183
184func (u *uplinkWriteHandle) Write(p []byte) (int, error) { return u.raw().Write(p) }
185func (u *uplinkWriteHandle) Commit() error               { return u.raw().Commit() }
186func (u *uplinkWriteHandle) Abort() error                { return u.raw().Abort() }
187
188// osWriteHandle implements writeHandle for *os.Files.
189type osWriteHandle struct {
190	fh   *os.File
191	done bool
192}
193
194// newOSWriteHandle constructs an *osWriteHandle from an *os.File.
195func newOSWriteHandle(fh *os.File) *osWriteHandle {
196	return &osWriteHandle{fh: fh}
197}
198
199func (o *osWriteHandle) Write(p []byte) (int, error) { return o.fh.Write(p) }
200
201func (o *osWriteHandle) Commit() error {
202	if o.done {
203		return nil
204	}
205	o.done = true
206
207	return o.fh.Close()
208}
209
210func (o *osWriteHandle) Abort() error {
211	if o.done {
212		return nil
213	}
214	o.done = true
215
216	return errs.Combine(
217		o.fh.Close(),
218		os.Remove(o.fh.Name()),
219	)
220}
221
222// genericWriteHandle implements writeHandle for an io.Writer.
223type genericWriteHandle struct{ w io.Writer }
224
225// newGenericWriteHandle constructs a *genericWriteHandle from an io.Writer.
226func newGenericWriteHandle(w io.Writer) *genericWriteHandle {
227	return &genericWriteHandle{w: w}
228}
229
230func (g *genericWriteHandle) Write(p []byte) (int, error) { return g.w.Write(p) }
231func (g *genericWriteHandle) Commit() error               { return nil }
232func (g *genericWriteHandle) Abort() error                { return nil }
233
234//
235// object iteration
236//
237
238// ObjectIterator is an interface type for iterating over objectInfo values.
239type ObjectIterator interface {
240	Next() bool
241	Err() error
242	Item() ObjectInfo
243}
244
245// filteredObjectIterator removes any iteration entries that do not begin with the filter.
246// all entries must begin with the trim string which is removed before checking for the
247// filter.
248type filteredObjectIterator struct {
249	trim   ulloc.Location
250	filter ulloc.Location
251	iter   ObjectIterator
252}
253
254func (f *filteredObjectIterator) Next() bool {
255	for {
256		if !f.iter.Next() {
257			return false
258		}
259		loc := f.iter.Item().Loc
260		if !loc.HasPrefix(f.trim) {
261			return false
262		}
263		if loc.HasPrefix(f.filter.AsDirectoryish()) || loc == f.filter {
264			return true
265		}
266	}
267}
268
269func (f *filteredObjectIterator) Err() error { return f.iter.Err() }
270
271func (f *filteredObjectIterator) Item() ObjectInfo {
272	item := f.iter.Item()
273	item.Loc = item.Loc.RemovePrefix(f.trim)
274	return item
275}
276
277// emptyObjectIterator is an objectIterator that has no objects.
278type emptyObjectIterator struct{}
279
280func (emptyObjectIterator) Next() bool       { return false }
281func (emptyObjectIterator) Err() error       { return nil }
282func (emptyObjectIterator) Item() ObjectInfo { return ObjectInfo{} }
283