@@ -111,6 +111,7 @@ type Fs struct {
111111 name string // name of this remote
112112 root string // the path we are working on
113113 provider Provider // the DOI provider
114+ doiProvider doiProvider // the interface used to interact with the DOI provider
114115 features * fs.Features // optional features
115116 opt Options // options for this backend
116117 ci * fs.ConfigInfo // global config
@@ -132,6 +133,16 @@ type Object struct {
132133 md5 string // MD5 hash of the object content
133134}
134135
136+ // doiProvider is the interface used to list objects in a DOI
137+ type doiProvider interface {
138+ // CanHaveSubDirs is true when the remote can have subdirectories
139+ CanHaveSubDirs () bool
140+ // IsFile returns true if remote is a file
141+ IsFile (ctx context.Context , remote string ) (isFile bool , err error )
142+ // ListEntries returns the full list of entries found at the remote, regardless of root
143+ ListEntries (ctx context.Context ) (entries []* Object , err error )
144+ }
145+
135146// Parse the input string as a DOI
136147// Examples:
137148// 10.1000/182 -> 10.1000/182
@@ -240,24 +251,17 @@ func (f *Fs) httpConnection(ctx context.Context, opt *Options) (isFile bool, err
240251 f .provider = provider
241252 f .opt .Provider = string (provider )
242253
243- // Determine if the root is a file
244254 switch f .provider {
245255 case Dataverse :
246- entries , err := f .listDataverseDoiFiles (ctx )
247- if err != nil {
248- return false , err
249- }
250- for _ , entry := range entries {
251- if entry .remote == f .root {
252- isFile = true
253- break
254- }
255- }
256+ f .doiProvider = newDataverseProvider (f )
256257 case Invenio , Zenodo :
257- isFile = f .root != ""
258+ f .doiProvider = newInvenioProvider (f )
259+ default :
260+ return false , fmt .Errorf ("provider type '%s' not supported" , f .provider )
258261 }
259262
260- return isFile , nil
263+ // Determine if the root is a file
264+ return f .doiProvider .IsFile (ctx , f .root )
261265}
262266
263267// retryErrorCodes is a slice of error codes that we will retry
@@ -270,8 +274,8 @@ var retryErrorCodes = []int{
270274 509 , // Bandwidth Limit Exceeded
271275}
272276
273- // shouldRetry returns a boolean as to whether this resp and err
274- // deserve to be retried. It returns the err as a convenience
277+ // shouldRetry returns a boolean as to whether this res and err
278+ // deserve to be retried. It returns the err as a convenience.
275279func shouldRetry (ctx context.Context , res * http.Response , err error ) (bool , error ) {
276280 if fserrors .ContextError (ctx , & err ) {
277281 return false , err
@@ -373,16 +377,7 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) error {
373377
374378// NewObject creates a new remote http file object
375379func (f * Fs ) NewObject (ctx context.Context , remote string ) (fs.Object , error ) {
376- var entries []* Object
377- var err error
378- switch f .provider {
379- case Dataverse :
380- entries , err = f .listDataverseDoiFiles (ctx )
381- case Invenio , Zenodo :
382- entries , err = f .listInvevioDoiFiles (ctx )
383- default :
384- err = fmt .Errorf ("provider type '%s' not supported" , f .provider )
385- }
380+ entries , err := f .doiProvider .ListEntries (ctx )
386381 if err != nil {
387382 return nil , err
388383 }
@@ -406,14 +401,59 @@ func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) {
406401// This should return ErrDirNotFound if the directory isn't
407402// found.
408403func (f * Fs ) List (ctx context.Context , dir string ) (entries fs.DirEntries , err error ) {
409- switch f .provider {
410- case Dataverse :
411- return f .listDataverse (ctx , dir )
412- case Invenio , Zenodo :
413- return f .listInvenio (ctx , dir )
414- default :
415- return nil , fmt .Errorf ("provider type '%s' not supported" , f .provider )
404+ if f .doiProvider .CanHaveSubDirs () {
405+ fileEntries , err := f .doiProvider .ListEntries (ctx )
406+ if err != nil {
407+ return nil , fmt .Errorf ("error listing %q: %w" , dir , err )
408+ }
409+
410+ fullDir := path .Join (f .root , dir )
411+ if fullDir != "" {
412+ fullDir += "/"
413+ }
414+ dirPaths := map [string ]bool {}
415+ for _ , entry := range fileEntries {
416+ // First, filter out files not in `fullDir`
417+ if ! strings .HasPrefix (entry .remote , fullDir ) {
418+ continue
419+ }
420+ // Then, find entries in subfolers
421+ remotePath := entry .remote
422+ if fullDir != "" {
423+ remotePath = strings .TrimLeft (strings .TrimPrefix (remotePath , fullDir ), "/" )
424+ }
425+ parts := strings .SplitN (remotePath , "/" , 2 )
426+ if len (parts ) == 1 {
427+ newEntry := * entry
428+ newEntry .remote = path .Join (dir , remotePath )
429+ entries = append (entries , & newEntry )
430+ } else {
431+ dirPaths [path .Join (dir , parts [0 ])] = true
432+ }
433+ }
434+ for dirPath := range dirPaths {
435+ entry := fs .NewDir (dirPath , time.Time {})
436+ entries = append (entries , entry )
437+ }
438+ return entries , nil
416439 }
440+
441+ if ! f .doiProvider .CanHaveSubDirs () {
442+ if dir != "" {
443+ return nil , fs .ErrorDirNotFound
444+ }
445+
446+ fileEntries , err := f .doiProvider .ListEntries (ctx )
447+ if err != nil {
448+ return nil , fmt .Errorf ("error listing %q: %w" , dir , err )
449+ }
450+ for _ , entry := range fileEntries {
451+ entries = append (entries , entry )
452+ }
453+ return entries , nil
454+ }
455+
456+ return nil , fmt .Errorf ("provider type '%s' not supported" , f .provider )
417457}
418458
419459// Put in to the remote path with the modTime given of the given size
0 commit comments