@@ -3,6 +3,7 @@ package local
33import (
44 "bytes"
55 "encoding/json"
6+ "errors"
67 "fmt"
78 "io/fs"
89 "os"
@@ -14,7 +15,9 @@ import (
1415 "strings"
1516 "sync"
1617
18+ "github.com/KarpelesLab/reflink"
1719 "github.com/OpenListTeam/OpenList/v4/internal/conf"
20+ "github.com/OpenListTeam/OpenList/v4/internal/errs"
1821 "github.com/OpenListTeam/OpenList/v4/internal/model"
1922 "github.com/OpenListTeam/OpenList/v4/pkg/utils"
2023 "github.com/disintegration/imaging"
@@ -148,7 +151,7 @@ func (d *Local) getThumb(file model.Obj) (*bytes.Buffer, *string, error) {
148151 return nil , nil , err
149152 }
150153 if d .ThumbCacheFolder != "" {
151- err = os .WriteFile (filepath .Join (d .ThumbCacheFolder , thumbName ), buf .Bytes (), 0666 )
154+ err = os .WriteFile (filepath .Join (d .ThumbCacheFolder , thumbName ), buf .Bytes (), 0o666 )
152155 if err != nil {
153156 return nil , nil , err
154157 }
@@ -405,3 +408,79 @@ func (m *DirectoryMap) DeleteDirNode(dirname string) error {
405408
406409 return nil
407410}
411+
412+ func (d * Local ) tryCopy (srcPath , dstPath string , info os.FileInfo ) error {
413+ if info .Mode ()& os .ModeDevice != 0 {
414+ return errors .New ("cannot copy a device" )
415+ } else if info .Mode ()& os .ModeSymlink != 0 {
416+ return d .copySymlink (srcPath , dstPath )
417+ } else if info .Mode ()& os .ModeNamedPipe != 0 {
418+ return copyNamedPipe (dstPath , info .Mode (), os .FileMode (d .mkdirPerm ))
419+ } else if info .IsDir () {
420+ return d .recurAndTryCopy (srcPath , dstPath )
421+ } else {
422+ return tryReflinkCopy (srcPath , dstPath )
423+ }
424+ }
425+
426+ func (d * Local ) copySymlink (srcPath , dstPath string ) error {
427+ linkOrig , err := os .Readlink (srcPath )
428+ if err != nil {
429+ return err
430+ }
431+ dstDir := filepath .Dir (dstPath )
432+ if ! filepath .IsAbs (linkOrig ) {
433+ srcDir := filepath .Dir (srcPath )
434+ rel , err := filepath .Rel (dstDir , srcDir )
435+ if err != nil {
436+ rel , err = filepath .Abs (srcDir )
437+ }
438+ if err != nil {
439+ return err
440+ }
441+ linkOrig = filepath .Clean (filepath .Join (rel , linkOrig ))
442+ }
443+ err = os .MkdirAll (dstDir , os .FileMode (d .mkdirPerm ))
444+ if err != nil {
445+ return err
446+ }
447+ return os .Symlink (linkOrig , dstPath )
448+ }
449+
450+ func (d * Local ) recurAndTryCopy (srcPath , dstPath string ) error {
451+ err := os .MkdirAll (dstPath , os .FileMode (d .mkdirPerm ))
452+ if err != nil {
453+ return err
454+ }
455+ files , err := readDir (srcPath )
456+ if err != nil {
457+ return err
458+ }
459+ for _ , f := range files {
460+ if ! f .IsDir () {
461+ sp := filepath .Join (srcPath , f .Name ())
462+ dp := filepath .Join (dstPath , f .Name ())
463+ if err = d .tryCopy (sp , dp , f ); err != nil {
464+ return err
465+ }
466+ }
467+ }
468+ for _ , f := range files {
469+ if f .IsDir () {
470+ sp := filepath .Join (srcPath , f .Name ())
471+ dp := filepath .Join (dstPath , f .Name ())
472+ if err = d .recurAndTryCopy (sp , dp ); err != nil {
473+ return err
474+ }
475+ }
476+ }
477+ return nil
478+ }
479+
480+ func tryReflinkCopy (srcPath , dstPath string ) error {
481+ err := reflink .Always (srcPath , dstPath )
482+ if errors .Is (err , reflink .ErrReflinkUnsupported ) || errors .Is (err , reflink .ErrReflinkFailed ) || isCrossDeviceError (err ) {
483+ return errs .NotImplement
484+ }
485+ return err
486+ }
0 commit comments