joint

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 2, 2023 License: MIT Imports: 16 Imported by: 3

README

Joint

Provides single interface to get access to files in ISO-9660 images, FTP-servers, SFTP-servers, WebDAV-servers. Contains cache with reusable connections to endpoints.

Go Reference Go Report Card Hits-of-Code

Goals

You can read the contents of a folder on an FTP-server either sequentially through one connection, but then this will take a lot of time, or by opening multiple connections, in which case too many connections will be opened for multiple requests. This library uses joints, which hold the connection to the FTP-server after reading a file, or some kind of access, after closing the file, they are placed into the cache, and can be reused later. If the connection has not been used for a certain time, it is reset.

Examples

HTTP file server with ISO-image content
package main

import (
    "log"
    "net/http"

    jnt "github.com/schwarzlichtbezirk/hms/joint"
)

// Open http://localhost:8080/ in browser
// to get a list of files in ISO-image.
func main() {
    var jc = jnt.NewJointCache("testdata/external.iso")
    http.Handle("/", http.FileServer(http.FS(jc)))
    log.Fatal(http.ListenAndServe(":8080", nil))
}
HTTP file server with WebDAV content
package main

import (
    "log"
    "net/http"

    jnt "github.com/schwarzlichtbezirk/hms/joint"
)

// Open http://localhost:8080/ in browser
// to get a list of files in WebDAV-server for given user.
func main() {
    var jc = jnt.NewJointCache("https://music:x@192.168.1.1/webdav/")
    http.Handle("/", http.FileServer(http.FS(jc)))
    log.Fatal(http.ListenAndServe(":8080", nil))
}
HTTP file server with ISO, WebDAV, FTP and SFTP content
package main

import (
    "log"
    "net/http"

    jnt "github.com/schwarzlichtbezirk/hms/joint"
)

var jp = jnt.NewJointPool()

// http://localhost:8080/iso/ - content of ISO-image
// http://localhost:8080/dav/ - content of WebDAV-server
// http://localhost:8080/ftp/ - content of FTP-server
// http://localhost:8080/sftp/ - content of SFTP-server
func main() {
    http.Handle("/iso/", http.StripPrefix("/iso/", http.FileServer(
        http.FS(jp.Get("testdata/external.iso")))))
    http.Handle("/dav/", http.StripPrefix("/dav/", http.FileServer(
        http.FS(jp.Get("https://music:x@192.168.1.1/webdav/")))))
    http.Handle("/ftp/", http.StripPrefix("/ftp/", http.FileServer(
        http.FS(jp.Get("ftp://music:x@192.168.1.1:21")))))
    http.Handle("/sftp/", http.StripPrefix("/sftp/", http.FileServer(
        http.FS(jp.Get("sftp://music:x@192.168.1.1:22")))))
    log.Fatal(http.ListenAndServe(":8080", nil))
}
Files reading by joints
package main

import (
    "fmt"
    "io"
    "io/fs"
    "log"

    jnt "github.com/schwarzlichtbezirk/joint"
)

func main() {
    var err error

    // Create joint to ISO-9660 image.
    var j jnt.Joint = &jnt.IsoJoint{}
    if err = j.Make(nil, "testdata/external.iso"); err != nil {
        log.Fatal(err)
    }
    // Cleanup drops joint's link at the end. Any not cached joints
    // should be destroyed by Cleanup call.
    defer j.Cleanup()

    // Working with file object returned by Open-function.
    // Open-function returns joint casted to fs.File.
    var f fs.File
    if f, err = j.Open("fox.txt"); err != nil {
        log.Fatal(err)
    }
    var b []byte
    if b, err = io.ReadAll(f); err != nil { // read from file
        log.Fatal(err)
    }
    fmt.Println(string(b))
    f.Close()

    // Working with joint explicitly. If joint is received from cache,
    // Close-call will return joint back to cache.
    if _, err = j.Open("data/lorem1.txt"); err != nil {
        log.Fatal(err)
    }
    if _, err = io.Copy(os.Stdout, j); err != nil { // read from joint
        log.Fatal(err)
    }
    j.Close()
}
Open nested ISO-image by joints
package main

import (
    "io"
    "io/fs"
    "log"
    "os"

    jnt "github.com/schwarzlichtbezirk/joint"
)

func main() {
    var err error

    // Create joint to external ISO-image.
    // This joint will be removed by top-level joint.
    var j1 jnt.Joint = &jnt.IsoJoint{}
    if err = j1.Make(nil, "testdata/external.iso"); err != nil {
        log.Fatal(err)
    }

    // Create top-level joint to internal ISO-image placed inside of first.
    var j2 jnt.Joint = &jnt.IsoJoint{}
    if err = j2.Make(j1, "disk/internal.iso"); err != nil {
        log.Fatal(err)
    }
    defer j2.Cleanup() // only top-level joint must be called for Cleanup

    // Open file at internal ISO-image.
    var f fs.File
    if f, err = j2.Open("fox.txt"); err != nil {
        log.Fatal(err)
    }
    defer f.Close()

    io.Copy(os.Stdout, f)
}
Simple file reading
package main

import (
    "io"
    "log"
    "os"

    jnt "github.com/schwarzlichtbezirk/joint"
)

var jp = jnt.NewJointPool()

func main() {
    // Open file and get the joint by one call.
    var j, err = jp.Open("testdata/external.iso/disk/internal.iso/docs/doc1.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer j.Close()

    io.Copy(os.Stdout, j)
}

(c) schwarzlichtbezirk, 2023.

Documentation

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrFtpWhence = errors.New("invalid whence at FTP seeker")
	ErrFtpNegPos = errors.New("negative position at FTP seeker")
)
View Source
var Cfg = Config{
	DialTimeout:     5 * time.Second,
	DiskCacheExpire: 2 * time.Minute,
}

Functions

func FtpEscapeBrackets

func FtpEscapeBrackets(s string) string

FtpEscapeBrackets escapes square brackets at FTP-path. FTP-server does not recognize path with square brackets as is to get a list of files, so such path should be escaped.

Example
package main

import (
	"fmt"

	jnt "github.com/schwarzlichtbezirk/joint"
)

func main() {
	fmt.Println(jnt.FtpEscapeBrackets("Music/Denney [2018]"))
}
Output:

Music/Denney [[]2018[]]

func FtpPwd

func FtpPwd(ftpaddr string, conn *ftp.ServerConn) (pwd string)

FtpPwd return FTP current directory. It's used cache to avoid extra calls to FTP-server to get current directory for every call.

func GetDavPath

func GetDavPath(davurl string) (dpath, fpath string, ok bool)

GetDavPath searches true path to WebDAV resource by checking step-by-step elements of the path.

func IsTypeIso added in v0.2.0

func IsTypeIso(fpath string) bool

IsTypeIso checks that endpoint-file in given path has ISO-extension.

func JoinFast

func JoinFast(dir, base string) string

JoinFast performs fast join of two path chunks.

Example
package main

import (
	"fmt"

	jnt "github.com/schwarzlichtbezirk/joint"
)

func main() {
	fmt.Println(jnt.JoinFast("any/path", "fox.txt"))
	fmt.Println(jnt.JoinFast("any/path/", "fox.txt"))
	fmt.Println(jnt.JoinFast("any", "/path/fox.txt"))
	fmt.Println(jnt.JoinFast("any/path/", "/fox.txt"))
	fmt.Println(jnt.JoinFast("/any/path", "fox.txt"))
}
Output:

any/path/fox.txt
any/path/fox.txt
any/path/fox.txt
any/path/fox.txt
/any/path/fox.txt

func SftpPwd

func SftpPwd(ftpaddr string, client *sftp.Client) (pwd string)

SftpPwd return SFTP current directory. It's used cache to avoid extra calls to SFTP-server to get current directory for every call.

func SplitKey added in v0.2.0

func SplitKey(fullpath string) (key, fpath string)

SplitKey splits full path to joint key to establish link, and remained local path.

Example
package main

import (
	"fmt"

	jnt "github.com/schwarzlichtbezirk/joint"
)

func main() {
	var list = []string{
		"some/path/fox.txt",
		"testdata/external.iso",
		"testdata/external.iso/fox.txt",
		"testdata/external.iso/disk/internal.iso/fox.txt",
		"ftp://music:x@192.168.1.1:21/Music",
		"ftp://music:x@192.168.1.1:21/testdata/external.iso/disk/internal.iso/docs/doc1.txt",
	}
	for _, s := range list {
		var key, fpath = jnt.SplitKey(s)
		fmt.Printf("key: '%s', path: '%s'\n", key, fpath)
	}
}
Output:

key: '', path: 'some/path/fox.txt'
key: 'testdata/external.iso', path: ''
key: 'testdata/external.iso', path: 'fox.txt'
key: 'testdata/external.iso/disk/internal.iso', path: 'fox.txt'
key: 'ftp://music:x@192.168.1.1:21', path: 'Music'
key: 'ftp://music:x@192.168.1.1:21/testdata/external.iso/disk/internal.iso', path: 'docs/doc1.txt'

func SplitUrl

func SplitUrl(urlpath string) (string, string)

SplitUrl splits URL to address string and to path as is.

Example
package main

import (
	"fmt"

	jnt "github.com/schwarzlichtbezirk/joint"
)

func main() {
	var list = []string{
		"ftp://music:x@192.168.1.1:21/Music/DJ.m3u",
		"sftp://music:x@192.168.1.1:22/Video",
		"https://github.com/schwarzlichtbezirk/joint",
		"https://pkg.go.dev/github.com/schwarzlichtbezirk/joint",
	}
	for _, s := range list {
		var addr, fpath = jnt.SplitUrl(s)
		fmt.Printf("addr: %s, path: %s\n", addr, fpath)
	}
}
Output:

addr: ftp://music:x@192.168.1.1:21, path: Music/DJ.m3u
addr: sftp://music:x@192.168.1.1:22, path: Video
addr: https://github.com, path: schwarzlichtbezirk/joint
addr: https://pkg.go.dev, path: github.com/schwarzlichtbezirk/joint

Types

type Config

type Config struct {
	// Timeout to establish connection to FTP-server.
	DialTimeout time.Duration `json:"dial-timeout" yaml:"dial-timeout" xml:"dial-timeout"`
	// Expiration duration to keep opened iso-disk structures in cache from last access to it.
	DiskCacheExpire time.Duration `json:"disk-cache-expire" yaml:"disk-cache-expire" xml:"disk-cache-expire"`
}

type DavFileInfo

type DavFileInfo = gowebdav.File

type DavJoint

type DavJoint struct {
	io.ReadCloser
	// contains filtered or unexported fields
}

DavJoint keeps gowebdav.Client object. Key is URL to service, address + service route, i.e. https://user:pass@example.com/webdav/.

func (*DavJoint) Busy

func (j *DavJoint) Busy() bool

func (*DavJoint) Cleanup

func (j *DavJoint) Cleanup() error

func (*DavJoint) Close

func (j *DavJoint) Close() (err error)

func (*DavJoint) Info

func (j *DavJoint) Info(fpath string) (fi fs.FileInfo, err error)

func (*DavJoint) Make

func (j *DavJoint) Make(base Joint, urladdr string) (err error)

func (*DavJoint) Open

func (j *DavJoint) Open(fpath string) (file fs.File, err error)

func (*DavJoint) Read

func (j *DavJoint) Read(b []byte) (n int, err error)

func (*DavJoint) ReadAt

func (j *DavJoint) ReadAt(b []byte, off int64) (n int, err error)

func (*DavJoint) ReadDir

func (j *DavJoint) ReadDir(n int) (ret []fs.DirEntry, err error)

func (*DavJoint) Seek

func (j *DavJoint) Seek(offset int64, whence int) (abs int64, err error)

func (*DavJoint) Stat

func (j *DavJoint) Stat() (fi fs.FileInfo, err error)

type FtpFileInfo

type FtpFileInfo struct {
	*ftp.Entry
}

FtpFileInfo encapsulates ftp.Entry structure and provides fs.FileInfo implementation.

func (FtpFileInfo) IsDir

func (fi FtpFileInfo) IsDir() bool

fs.FileInfo implementation.

func (FtpFileInfo) ModTime

func (fi FtpFileInfo) ModTime() time.Time

fs.FileInfo implementation.

func (FtpFileInfo) Mode

func (fi FtpFileInfo) Mode() fs.FileMode

fs.FileInfo implementation.

func (FtpFileInfo) Name

func (fi FtpFileInfo) Name() string

fs.FileInfo implementation.

func (FtpFileInfo) Size

func (fi FtpFileInfo) Size() int64

fs.FileInfo implementation.

func (FtpFileInfo) Sys

func (fi FtpFileInfo) Sys() interface{}

fs.FileInfo implementation. Returns structure pointer itself.

type FtpJoint

type FtpJoint struct {
	io.ReadCloser
	// contains filtered or unexported fields
}

FtpJoint create connection to FTP-server, login with provided by given URL credentials, and gets a once current directory. Key is address of FTP-service, i.e. ftp://user:pass@example.com.

func (*FtpJoint) Busy

func (j *FtpJoint) Busy() bool

func (*FtpJoint) Cleanup

func (j *FtpJoint) Cleanup() error

func (*FtpJoint) Close

func (j *FtpJoint) Close() (err error)

func (*FtpJoint) Info

func (j *FtpJoint) Info(fpath string) (fi fs.FileInfo, err error)

func (*FtpJoint) Make

func (j *FtpJoint) Make(base Joint, urladdr string) (err error)

func (*FtpJoint) Open

func (j *FtpJoint) Open(fpath string) (file fs.File, err error)

func (*FtpJoint) Read

func (j *FtpJoint) Read(b []byte) (n int, err error)

func (*FtpJoint) ReadAt

func (j *FtpJoint) ReadAt(b []byte, off int64) (n int, err error)

func (*FtpJoint) ReadDir

func (j *FtpJoint) ReadDir(n int) (ret []fs.DirEntry, err error)

func (*FtpJoint) Seek

func (j *FtpJoint) Seek(offset int64, whence int) (abs int64, err error)

func (*FtpJoint) Size

func (j *FtpJoint) Size() int64

func (*FtpJoint) Stat

func (j *FtpJoint) Stat() (fi fs.FileInfo, err error)

func (*FtpJoint) Write

func (j *FtpJoint) Write(p []byte) (n int, err error)

type IsoFileInfo

type IsoFileInfo struct {
	*iso.File
}

func (IsoFileInfo) Name

func (fi IsoFileInfo) Name() string

func (IsoFileInfo) Sys

func (fi IsoFileInfo) Sys() interface{}

type IsoJoint

type IsoJoint struct {
	Base Joint

	*iso.File
	*io.SectionReader
	// contains filtered or unexported fields
}

IsoJoint opens file with ISO9660 disk and prepares disk-structure to access to nested files. Key is external path, to ISO9660-file disk image at local filesystem.

func (*IsoJoint) Busy

func (j *IsoJoint) Busy() bool

func (*IsoJoint) Cleanup

func (j *IsoJoint) Cleanup() error

func (*IsoJoint) Close

func (j *IsoJoint) Close() error

func (*IsoJoint) Info

func (j *IsoJoint) Info(fpath string) (fi fs.FileInfo, err error)

func (*IsoJoint) Make

func (j *IsoJoint) Make(base Joint, isopath string) (err error)

func (*IsoJoint) Open

func (j *IsoJoint) Open(fpath string) (file fs.File, err error)

func (*IsoJoint) OpenFile

func (j *IsoJoint) OpenFile(intpath string) (file *iso.File, err error)

func (*IsoJoint) ReadDir

func (j *IsoJoint) ReadDir(n int) (ret []fs.DirEntry, err error)

func (*IsoJoint) Stat

func (j *IsoJoint) Stat() (fs.FileInfo, error)

type Joint

type Joint interface {
	Make(Joint, string) error // establish connection to file system provider
	Cleanup() error           // close connection to file system provider
	Busy() bool               // file is opened
	fs.FS                     // open file with local file path
	io.Closer                 // close local file
	fs.ReadDirFile            // read directory pointed by local file path
	RFile
}

Joint describes interface with joint to some file system provider.

func MakeJoint added in v0.2.0

func MakeJoint(fullpath string) (j Joint, err error)

MakeJoint creates joint with all subsequent chain of joints. Please note that folders with .iso extension and non ISO-images with .iso extension will cause an error.

type JointCache

type JointCache struct {
	// contains filtered or unexported fields
}

JointCache implements cache with opened joints to some file system resource.

func NewJointCache

func NewJointCache(key string) *JointCache

func (*JointCache) Close

func (jc *JointCache) Close() (err error)

Close performs close-call to all cached disk joints.

func (*JointCache) Count

func (jc *JointCache) Count() int

Count is number of free joints in cache for one key path.

func (*JointCache) Get

func (jc *JointCache) Get() (jw JointWrap, err error)

Get retrieves cached disk joint, or makes new one.

func (*JointCache) Has

func (jc *JointCache) Has(j Joint) bool

Checks whether it is contained joint in cache.

func (*JointCache) Key added in v0.2.0

func (jc *JointCache) Key() string

func (*JointCache) Open

func (jc *JointCache) Open(fpath string) (f fs.File, err error)

Open implements fs.FS interface, and returns file that can be casted to joint wrapper.

Example
package main

import (
	"io"
	"log"
	"os"

	jnt "github.com/schwarzlichtbezirk/joint"
)

func main() {
	var jc = jnt.NewJointCache("testdata/external.iso")
	defer jc.Close()

	var f, err = jc.Open("fox.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	io.Copy(os.Stdout, f)
}
Output:

The quick brown fox jumps over the lazy dog.

func (*JointCache) Pop

func (jc *JointCache) Pop() (jw JointWrap, ok bool)

Pop retrieves cached disk joint, and returns ok if it has.

func (*JointCache) Put

func (jc *JointCache) Put(j Joint)

Put disk joint to cache.

func (*JointCache) ReadDir

func (jc *JointCache) ReadDir(fpath string) (des []fs.DirEntry, err error)

ReadDir implements fs.ReadDirFS interface.

func (*JointCache) Stat

func (jc *JointCache) Stat(fpath string) (fi fs.FileInfo, err error)

Stat implements fs.StatFS interface.

type JointPool

type JointPool struct {
	// contains filtered or unexported fields
}

JointPool is map with joint caches. Each key in map is path to file system resource, value - cached for this resource list of joints.

func NewJointPool added in v0.2.0

func NewJointPool() *JointPool

func (*JointPool) Clear added in v0.2.0

func (jp *JointPool) Clear() error

Clear is same as Close, and removes all entries in the map.

func (*JointPool) Close added in v0.2.0

func (jp *JointPool) Close() error

Close resets all caches.

func (*JointPool) GetCache added in v0.2.0

func (jp *JointPool) GetCache(key string) (jc *JointCache)

GetCache returns cache from pool for given key path, or creates new one.

func (*JointPool) GetJoint added in v0.2.0

func (jp *JointPool) GetJoint(key string) (j Joint, err error)

GetJoint returns joint for given key.

func (*JointPool) Keys added in v0.2.0

func (jp *JointPool) Keys() []string

Keys returns list of all joints key paths.

func (*JointPool) Open added in v0.2.0

func (jp *JointPool) Open(fullpath string) (f fs.File, err error)

Open opens file with given full path to this file, that can be located inside of nested ISO-images and/or on FTP, SFTP, WebDAV servers. Open implements fs.FS interface, and returns file that can be casted to joint wrapper.

Example
package main

import (
	"io"
	"log"
	"os"

	jnt "github.com/schwarzlichtbezirk/joint"
)

func main() {
	var jp = jnt.NewJointPool() // can be global declaration
	defer jp.Close()

	var j, err = jp.Open("testdata/external.iso/fox.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer j.Close()

	io.Copy(os.Stdout, j)
}
Output:

The quick brown fox jumps over the lazy dog.

func (*JointPool) ReadDir added in v0.2.0

func (jp *JointPool) ReadDir(fullpath string) (ret []fs.DirEntry, err error)

ReadDir returns directory files fs.DirEntry list pointed by given full path. ReadDir implements ReadDirFS interface.

Example
package main

import (
	"fmt"
	"log"

	jnt "github.com/schwarzlichtbezirk/joint"
)

func main() {
	var jp = jnt.NewJointPool() // can be global declaration
	defer jp.Close()

	var files, err = jp.ReadDir("testdata/external.iso/data")
	if err != nil {
		log.Fatal(err)
	}
	for _, de := range files {
		if de.IsDir() {
			fmt.Printf("dir:  %s\n", de.Name())
		} else {
			var fi, _ = de.Info()
			fmt.Printf("file: %s, %d bytes\n", de.Name(), fi.Size())
		}
	}
}
Output:

dir:  docs
dir:  empty
file: lorem1.txt, 2747 bytes
file: lorem2.txt, 2629 bytes
file: lorem3.txt, 2714 bytes
dir:  доки
file: рыба.txt, 1789 bytes

func (*JointPool) Stat added in v0.2.0

func (jp *JointPool) Stat(fullpath string) (fi fs.FileInfo, err error)

Stat returns fs.FileInfo of file pointed by given full path. Stat implements fs.StatFS interface.

Example
package main

import (
	"fmt"
	"log"

	jnt "github.com/schwarzlichtbezirk/joint"
)

func main() {
	var jp = jnt.NewJointPool() // can be global declaration
	defer jp.Close()

	var fi, err = jp.Stat("testdata/external.iso/fox.txt")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("name: %s, size: %d\n", fi.Name(), fi.Size())
}
Output:

name: fox.txt, size: 44

type JointWrap

type JointWrap struct {
	Joint
	// contains filtered or unexported fields
}

JointWrap helps to return joint into cache after Close-call.

func (JointWrap) Cleanup added in v0.2.0

func (jw JointWrap) Cleanup() error

Cleanup ejects itself from binded cache, and make inner Cleanup-call. This function is for special cases only.

func (JointWrap) Close

func (jw JointWrap) Close() error

func (JointWrap) Eject

func (jw JointWrap) Eject() bool

Eject joint from the cache.

func (JointWrap) GetCache added in v0.2.0

func (jw JointWrap) GetCache() *JointCache

GetCache returns binded cache.

type RFile

type RFile interface {
	io.Reader
	io.ReaderAt
	io.Seeker
	fs.File
}

RFile combines fs.File interface and io.Seeker interface.

type SftpFileStat

type SftpFileStat = sftp.FileStat

type SftpJoint

type SftpJoint struct {
	*sftp.File
	// contains filtered or unexported fields
}

SftpJoint create SSH-connection to SFTP-server, login with provided by given URL credentials, and gets a once current directory. Key is address of SFTP-service, i.e. sftp://user:pass@example.com.

func (*SftpJoint) Busy

func (j *SftpJoint) Busy() bool

func (*SftpJoint) Cleanup

func (j *SftpJoint) Cleanup() error

func (*SftpJoint) Close

func (j *SftpJoint) Close() (err error)

func (*SftpJoint) Info

func (j *SftpJoint) Info(fpath string) (fs.FileInfo, error)

func (*SftpJoint) Make

func (j *SftpJoint) Make(base Joint, urladdr string) (err error)

func (*SftpJoint) Open

func (j *SftpJoint) Open(fpath string) (file fs.File, err error)

Opens new connection for any some one file with given full SFTP URL.

func (*SftpJoint) ReadDir

func (j *SftpJoint) ReadDir(n int) (ret []fs.DirEntry, err error)

type SysJoint

type SysJoint struct {
	*os.File
	// contains filtered or unexported fields
}

func (*SysJoint) Busy

func (j *SysJoint) Busy() bool

func (*SysJoint) Cleanup

func (j *SysJoint) Cleanup() error

func (*SysJoint) Close

func (j *SysJoint) Close() (err error)

func (*SysJoint) Info

func (j *SysJoint) Info(fpath string) (fs.FileInfo, error)

func (*SysJoint) Make

func (j *SysJoint) Make(base Joint, dir string) (err error)

func (*SysJoint) Open

func (j *SysJoint) Open(fpath string) (file fs.File, err error)

Opens file at local file system.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL