X-Git-Url: https://git.r.bdr.sh/rbdr/mobius/blobdiff_plain/7cd900d61edbd6d322db3cecb913adf574389320..a55350daaf83498b7a237c027ad0dd2377f06fee:/hotline/file_wrapper.go diff --git a/hotline/file_wrapper.go b/hotline/file_wrapper.go index 3773e79..bc6319b 100644 --- a/hotline/file_wrapper.go +++ b/hotline/file_wrapper.go @@ -1,20 +1,20 @@ package hotline import ( + "bytes" "encoding/binary" "errors" "fmt" "io" "io/fs" "os" - "path" - "strings" + "path/filepath" ) const ( incompleteFileSuffix = ".incomplete" - infoForkNameTemplate = "%s.info_%s" // template string for info fork filenames - rsrcForkNameTemplate = "%s.rsrc_%s" // template string for resource fork filenames + infoForkNameTemplate = ".info_%s" // template string for info fork filenames + rsrcForkNameTemplate = ".rsrc_%s" // template string for resource fork filenames ) // fileWrapper encapsulates the data, info, and resource forks of a Hotline file and provides methods to manage the files. @@ -27,24 +27,21 @@ type fileWrapper struct { rsrcPath string // path to the file resource fork infoPath string // path to the file information fork incompletePath string // path to partially transferred temp file - saveMetaData bool // if true, enables saving of info and resource forks in sidecar files - infoFork *FlatFileInformationFork ffo *flattenedFileObject } func newFileWrapper(fs FileStore, path string, dataOffset int64) (*fileWrapper, error) { - pathSegs := strings.Split(path, pathSeparator) - dir := strings.Join(pathSegs[:len(pathSegs)-1], pathSeparator) - fName := pathSegs[len(pathSegs)-1] + dir := filepath.Dir(path) + fName := filepath.Base(path) f := fileWrapper{ fs: fs, name: fName, path: dir, dataPath: path, dataOffset: dataOffset, - rsrcPath: fmt.Sprintf(rsrcForkNameTemplate, dir+"/", fName), - infoPath: fmt.Sprintf(infoForkNameTemplate, dir+"/", fName), - incompletePath: dir + "/" + fName + incompleteFileSuffix, + rsrcPath: filepath.Join(dir, fmt.Sprintf(rsrcForkNameTemplate, fName)), + infoPath: filepath.Join(dir, fmt.Sprintf(infoForkNameTemplate, fName)), + incompletePath: filepath.Join(dir, fName+incompleteFileSuffix), ffo: &flattenedFileObject{}, } @@ -100,28 +97,14 @@ func (f *fileWrapper) incompleteDataName() string { } func (f *fileWrapper) rsrcForkName() string { - return fmt.Sprintf(rsrcForkNameTemplate, "", f.name) + return fmt.Sprintf(rsrcForkNameTemplate, f.name) } func (f *fileWrapper) infoForkName() string { - return fmt.Sprintf(infoForkNameTemplate, "", f.name) + return fmt.Sprintf(infoForkNameTemplate, f.name) } -func (f *fileWrapper) creatorCode() []byte { - if f.ffo.FlatFileInformationFork.CreatorSignature != nil { - return f.infoFork.CreatorSignature - } - return []byte(fileTypeFromFilename(f.name).CreatorCode) -} - -func (f *fileWrapper) typeCode() []byte { - if f.infoFork != nil { - return f.infoFork.TypeSignature - } - return []byte(fileTypeFromFilename(f.name).TypeCode) -} - -func (f *fileWrapper) rsrcForkWriter() (io.Writer, error) { +func (f *fileWrapper) rsrcForkWriter() (io.WriteCloser, error) { file, err := os.OpenFile(f.rsrcPath, os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return nil, err @@ -130,8 +113,8 @@ func (f *fileWrapper) rsrcForkWriter() (io.Writer, error) { return file, nil } -func (f *fileWrapper) infoForkWriter() (io.Writer, error) { - file, err := os.OpenFile(f.infoPath, os.O_CREATE|os.O_WRONLY, 0644) +func (f *fileWrapper) infoForkWriter() (io.WriteCloser, error) { + file, err := os.OpenFile(f.infoPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) if err != nil { return nil, err } @@ -139,7 +122,7 @@ func (f *fileWrapper) infoForkWriter() (io.Writer, error) { return file, nil } -func (f *fileWrapper) incFileWriter() (io.Writer, error) { +func (f *fileWrapper) incFileWriter() (io.WriteCloser, error) { file, err := os.OpenFile(f.incompletePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) if err != nil { return nil, err @@ -167,26 +150,31 @@ func (f *fileWrapper) dataFile() (os.FileInfo, error) { return nil, errors.New("file or directory not found") } -// move a fileWrapper and its associated metadata files to newPath +// move a fileWrapper and its associated meta files to newPath. +// Meta files include: +// * Partially uploaded file ending with .incomplete +// * Resource fork starting with .rsrc_ +// * Info fork starting with .info +// During move of the meta files, os.ErrNotExist is ignored as these files may legitimately not exist. func (f *fileWrapper) move(newPath string) error { - err := f.fs.Rename(f.dataPath, path.Join(newPath, f.name)) + err := f.fs.Rename(f.dataPath, filepath.Join(newPath, f.name)) if err != nil { - // TODO + return err } - err = f.fs.Rename(f.incompletePath, path.Join(newPath, f.incompleteDataName())) - if err != nil { - // TODO + err = f.fs.Rename(f.incompletePath, filepath.Join(newPath, f.incompleteDataName())) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return err } - err = f.fs.Rename(f.rsrcPath, path.Join(newPath, f.rsrcForkName())) - if err != nil { - // TODO + err = f.fs.Rename(f.rsrcPath, filepath.Join(newPath, f.rsrcForkName())) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return err } - err = f.fs.Rename(f.infoPath, path.Join(newPath, f.infoForkName())) - if err != nil { - // TODO + err = f.fs.Rename(f.infoPath, filepath.Join(newPath, f.infoForkName())) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return err } return nil @@ -196,22 +184,22 @@ func (f *fileWrapper) move(newPath string) error { func (f *fileWrapper) delete() error { err := f.fs.RemoveAll(f.dataPath) if err != nil { - // TODO + return err } err = f.fs.Remove(f.incompletePath) - if err != nil { - // TODO + if err != nil && !errors.Is(err, os.ErrNotExist) { + return err } err = f.fs.Remove(f.rsrcPath) - if err != nil { - // TODO + if err != nil && !errors.Is(err, os.ErrNotExist) { + return err } err = f.fs.Remove(f.infoPath) - if err != nil { - // TODO + if err != nil && !errors.Is(err, os.ErrNotExist) { + return err } return nil @@ -219,7 +207,7 @@ func (f *fileWrapper) delete() error { func (f *fileWrapper) flattenedFileObject() (*flattenedFileObject, error) { dataSize := make([]byte, 4) - mTime := make([]byte, 8) + mTime := [8]byte{} ft := defaultFileType @@ -256,26 +244,25 @@ func (f *fileWrapper) flattenedFileObject() (*flattenedFileObject, error) { f.ffo.FlatFileHeader.ForkCount[1] = 3 - if err := f.ffo.FlatFileInformationFork.UnmarshalBinary(b); err != nil { - return nil, err + _, err = io.Copy(&f.ffo.FlatFileInformationFork, bytes.NewReader(b)) + if err != nil { + return nil, fmt.Errorf("error copying FlatFileInformationFork: %w", err) } } else { f.ffo.FlatFileInformationFork = FlatFileInformationFork{ - Platform: []byte("AMAC"), // TODO: Remove hardcode to support "AWIN" Platform (maybe?) - TypeSignature: []byte(ft.TypeCode), - CreatorSignature: []byte(ft.CreatorCode), - Flags: []byte{0, 0, 0, 0}, - PlatformFlags: []byte{0, 0, 1, 0}, // TODO: What is this? - RSVD: make([]byte, 32), - CreateDate: mTime, // some filesystems don't support createTime + Platform: [4]byte{0x41, 0x4D, 0x41, 0x43}, // "AMAC" TODO: Remove hardcode to support "AWIN" Platform (maybe?) + TypeSignature: [4]byte([]byte(ft.TypeCode)), + CreatorSignature: [4]byte([]byte(ft.CreatorCode)), + PlatformFlags: [4]byte{0, 0, 1, 0}, // TODO: What is this? + CreateDate: mTime, // some filesystems don't support createTime ModifyDate: mTime, - NameScript: []byte{0, 0}, Name: []byte(f.name), - NameSize: []byte{0, 0}, - CommentSize: []byte{0, 0}, Comment: []byte{}, } - binary.BigEndian.PutUint16(f.ffo.FlatFileInformationFork.NameSize, uint16(len(f.name))) + + ns := make([]byte, 2) + binary.BigEndian.PutUint16(ns, uint16(len(f.name))) + f.ffo.FlatFileInformationFork.NameSize = [2]byte(ns[:]) } f.ffo.FlatFileInformationForkHeader = FlatFileForkHeader{