mirror of
https://github.com/rclone/rclone.git
synced 2024-11-25 09:54:44 +01:00
librclone: factor into gomobile and internal implementation #4891
This was needed because gomobile can't use a main package wheras this is required to make a normal shared C library.
This commit is contained in:
parent
62bf63d36f
commit
ba09ee18bb
@ -1,7 +1,13 @@
|
|||||||
# librclone
|
# librclone
|
||||||
|
|
||||||
This directory contains code to build rclone as a C library and the
|
This directory contains code to build rclone as a C library and the
|
||||||
shims for accessing rclone from C.
|
shims for accessing rclone from C and other languages.
|
||||||
|
|
||||||
|
**Note** for the moment, the interfaces defined here are experimental
|
||||||
|
and may change in the future. Eventually they will stabilse and this
|
||||||
|
notice will be removed.
|
||||||
|
|
||||||
|
## C
|
||||||
|
|
||||||
The shims are a thin wrapper over the rclone RPC.
|
The shims are a thin wrapper over the rclone RPC.
|
||||||
|
|
||||||
@ -18,7 +24,7 @@ be `#include`d in `C` programs wishing to use the library.
|
|||||||
|
|
||||||
The library will depend on `libdl` and `libpthread`.
|
The library will depend on `libdl` and `libpthread`.
|
||||||
|
|
||||||
## Documentation
|
### Documentation
|
||||||
|
|
||||||
For documentation see the Go documentation for:
|
For documentation see the Go documentation for:
|
||||||
|
|
||||||
@ -26,6 +32,15 @@ For documentation see the Go documentation for:
|
|||||||
- [RcloneFinalize](https://pkg.go.dev/github.com/rclone/rclone/librclone#RcloneFinalize)
|
- [RcloneFinalize](https://pkg.go.dev/github.com/rclone/rclone/librclone#RcloneFinalize)
|
||||||
- [RcloneRPC](https://pkg.go.dev/github.com/rclone/rclone/librclone#RcloneRPC)
|
- [RcloneRPC](https://pkg.go.dev/github.com/rclone/rclone/librclone#RcloneRPC)
|
||||||
|
|
||||||
## C Example
|
### C Example
|
||||||
|
|
||||||
There is an example program `ctest.c` with Makefile in the `ctest` subdirectory
|
There is an example program `ctest.c` with Makefile in the `ctest` subdirectory
|
||||||
|
|
||||||
|
## gomobile
|
||||||
|
|
||||||
|
The gomobile subdirectory contains the equivalent of the C binding but
|
||||||
|
suitable for using with gomobile using something like this.
|
||||||
|
|
||||||
|
gomobile bind -v -target=android github.com/rclone/rclone/librclone/gomobile
|
||||||
|
|
||||||
|
|
||||||
|
40
librclone/gomobile/gomobile.go
Normal file
40
librclone/gomobile/gomobile.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// Package gomobile exports shims for gomobile use
|
||||||
|
package gomobile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/rclone/rclone/librclone/librclone"
|
||||||
|
|
||||||
|
_ "github.com/rclone/rclone/backend/all" // import all backends
|
||||||
|
_ "github.com/rclone/rclone/lib/plugin" // import plugins
|
||||||
|
)
|
||||||
|
|
||||||
|
// RcloneInitialize initializes rclone as a library
|
||||||
|
func RcloneInitialize() {
|
||||||
|
librclone.Initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RcloneFinalize finalizes the library
|
||||||
|
func RcloneFinalize() {
|
||||||
|
librclone.Finalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RcloneRPCResult is returned from RcloneRPC
|
||||||
|
//
|
||||||
|
// Output will be returned as a serialized JSON object
|
||||||
|
// Status is a HTTP status return (200=OK anything else fail)
|
||||||
|
type RcloneRPCResult struct {
|
||||||
|
Output string
|
||||||
|
Status int
|
||||||
|
}
|
||||||
|
|
||||||
|
// RcloneRPC has an interface optimised for gomobile, in particular
|
||||||
|
// the function signature is valid under gobind rules.
|
||||||
|
//
|
||||||
|
// https://pkg.go.dev/golang.org/x/mobile/cmd/gobind#hdr-Type_restrictions
|
||||||
|
func RcloneRPC(method string, input string) (result *RcloneRPCResult) { //nolint:deadcode
|
||||||
|
output, status := librclone.RPC(method, input)
|
||||||
|
return &RcloneRPCResult{
|
||||||
|
Output: output,
|
||||||
|
Status: status,
|
||||||
|
}
|
||||||
|
}
|
@ -28,20 +28,7 @@ struct RcloneRPCResult {
|
|||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"github.com/rclone/rclone/librclone/librclone"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/rclone/rclone/fs"
|
|
||||||
"github.com/rclone/rclone/fs/accounting"
|
|
||||||
"github.com/rclone/rclone/fs/config/configfile"
|
|
||||||
"github.com/rclone/rclone/fs/log"
|
|
||||||
"github.com/rclone/rclone/fs/rc"
|
|
||||||
"github.com/rclone/rclone/fs/rc/jobs"
|
|
||||||
|
|
||||||
_ "github.com/rclone/rclone/backend/all" // import all backends
|
_ "github.com/rclone/rclone/backend/all" // import all backends
|
||||||
_ "github.com/rclone/rclone/lib/plugin" // import plugins
|
_ "github.com/rclone/rclone/lib/plugin" // import plugins
|
||||||
@ -51,28 +38,14 @@ import (
|
|||||||
//
|
//
|
||||||
//export RcloneInitialize
|
//export RcloneInitialize
|
||||||
func RcloneInitialize() {
|
func RcloneInitialize() {
|
||||||
// A subset of initialisation copied from cmd.go
|
librclone.Initialize()
|
||||||
// Note that we don't want to pull in anything which depends on pflags
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// Start the logger
|
|
||||||
log.InitLogging()
|
|
||||||
|
|
||||||
// Load the config - this may need to be configurable
|
|
||||||
configfile.Install()
|
|
||||||
|
|
||||||
// Start accounting
|
|
||||||
accounting.Start(ctx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RcloneFinalize finalizes the library
|
// RcloneFinalize finalizes the library
|
||||||
//
|
//
|
||||||
//export RcloneFinalize
|
//export RcloneFinalize
|
||||||
func RcloneFinalize() {
|
func RcloneFinalize() {
|
||||||
// TODO: how to clean up? what happens when rcserver terminates?
|
librclone.Finalize()
|
||||||
// what about unfinished async jobs?
|
|
||||||
runtime.GC()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RcloneRPCResult is returned from RcloneRPC
|
// RcloneRPCResult is returned from RcloneRPC
|
||||||
@ -98,103 +71,11 @@ type RcloneRPCResult struct { //nolint:deadcode
|
|||||||
//
|
//
|
||||||
//export RcloneRPC
|
//export RcloneRPC
|
||||||
func RcloneRPC(method *C.char, input *C.char) (result C.struct_RcloneRPCResult) { //nolint:golint
|
func RcloneRPC(method *C.char, input *C.char) (result C.struct_RcloneRPCResult) { //nolint:golint
|
||||||
output, status := callFunctionJSON(C.GoString(method), C.GoString(input))
|
output, status := librclone.RPC(C.GoString(method), C.GoString(input))
|
||||||
result.Output = C.CString(output)
|
result.Output = C.CString(output)
|
||||||
result.Status = C.int(status)
|
result.Status = C.int(status)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// RcloneMobileRPCResult is returned from RcloneMobileRPC
|
|
||||||
//
|
|
||||||
// Output will be returned as a serialized JSON object
|
|
||||||
// Status is a HTTP status return (200=OK anything else fail)
|
|
||||||
type RcloneMobileRPCResult struct {
|
|
||||||
Output string
|
|
||||||
Status int
|
|
||||||
}
|
|
||||||
|
|
||||||
// RcloneMobileRPC works the same as RcloneRPC but has an interface
|
|
||||||
// optimised for gomobile, in particular the function signature is
|
|
||||||
// valid under gobind rules.
|
|
||||||
//
|
|
||||||
// https://pkg.go.dev/golang.org/x/mobile/cmd/gobind#hdr-Type_restrictions
|
|
||||||
func RcloneMobileRPC(method string, input string) (result RcloneMobileRPCResult) { //nolint:deadcode
|
|
||||||
output, status := callFunctionJSON(method, input)
|
|
||||||
result.Output = output
|
|
||||||
result.Status = status
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeError returns a formatted error string and the status passed in
|
|
||||||
func writeError(path string, in rc.Params, err error, status int) (string, int) {
|
|
||||||
fs.Errorf(nil, "rc: %q: error: %v", path, err)
|
|
||||||
params, status := rc.Error(path, in, err, status)
|
|
||||||
var w strings.Builder
|
|
||||||
err = rc.WriteJSON(&w, params)
|
|
||||||
if err != nil {
|
|
||||||
// ultimate fallback error
|
|
||||||
fs.Errorf(nil, "writeError: failed to write JSON output from %#v: %v", in, err)
|
|
||||||
status = http.StatusInternalServerError
|
|
||||||
w.Reset()
|
|
||||||
fmt.Fprintf(&w, `{
|
|
||||||
"error": %q,
|
|
||||||
"path": %q,
|
|
||||||
"status": %d
|
|
||||||
}`, err, path, status)
|
|
||||||
|
|
||||||
}
|
|
||||||
return w.String(), status
|
|
||||||
}
|
|
||||||
|
|
||||||
// operations/uploadfile and core/command are not supported as they need request or response object
|
|
||||||
// modified from handlePost in rcserver.go
|
|
||||||
// call a rc function using JSON to input parameters and output the resulted JSON
|
|
||||||
func callFunctionJSON(method string, input string) (output string, status int) {
|
|
||||||
// create a buffer to capture the output
|
|
||||||
in := make(rc.Params)
|
|
||||||
err := json.NewDecoder(strings.NewReader(input)).Decode(&in)
|
|
||||||
if err != nil {
|
|
||||||
return writeError(method, in, errors.Wrap(err, "failed to read input JSON"), http.StatusBadRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the call
|
|
||||||
call := rc.Calls.Get(method)
|
|
||||||
if call == nil {
|
|
||||||
return writeError(method, in, errors.Errorf("couldn't find method %q", method), http.StatusNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: handle these cases
|
|
||||||
if call.NeedsRequest {
|
|
||||||
return writeError(method, in, errors.Errorf("method %q needs request, not supported", method), http.StatusNotFound)
|
|
||||||
// Add the request to RC
|
|
||||||
//in["_request"] = r
|
|
||||||
}
|
|
||||||
if call.NeedsResponse {
|
|
||||||
return writeError(method, in, errors.Errorf("method %q need response, not supported", method), http.StatusNotFound)
|
|
||||||
//in["_response"] = w
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.Debugf(nil, "rc: %q: with parameters %+v", method, in)
|
|
||||||
|
|
||||||
_, out, err := jobs.NewJob(context.Background(), call.Fn, in)
|
|
||||||
if err != nil {
|
|
||||||
return writeError(method, in, err, http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
if out == nil {
|
|
||||||
out = make(rc.Params)
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.Debugf(nil, "rc: %q: reply %+v: %v", method, out, err)
|
|
||||||
|
|
||||||
var w strings.Builder
|
|
||||||
err = rc.WriteJSON(&w, out)
|
|
||||||
if err != nil {
|
|
||||||
fs.Errorf(nil, "rc: failed to write JSON output: %v", err)
|
|
||||||
return writeError(method, in, err, http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
return w.String(), http.StatusOK
|
|
||||||
}
|
|
||||||
|
|
||||||
// do nothing here - necessary for building into a C library
|
// do nothing here - necessary for building into a C library
|
||||||
func main() {}
|
func main() {}
|
||||||
|
124
librclone/librclone/librclone.go
Normal file
124
librclone/librclone/librclone.go
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
// Package librclone exports shims for library use
|
||||||
|
//
|
||||||
|
// This is the internal implementation which is used for C and
|
||||||
|
// Gomobile libaries which need slightly different export styles.
|
||||||
|
//
|
||||||
|
// The shims are a thin wrapper over the rclone RPC.
|
||||||
|
package librclone
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/rclone/rclone/fs"
|
||||||
|
"github.com/rclone/rclone/fs/accounting"
|
||||||
|
"github.com/rclone/rclone/fs/config/configfile"
|
||||||
|
"github.com/rclone/rclone/fs/log"
|
||||||
|
"github.com/rclone/rclone/fs/rc"
|
||||||
|
"github.com/rclone/rclone/fs/rc/jobs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Initialize initializes rclone as a library
|
||||||
|
//
|
||||||
|
//export Initialize
|
||||||
|
func Initialize() {
|
||||||
|
// A subset of initialisation copied from cmd.go
|
||||||
|
// Note that we don't want to pull in anything which depends on pflags
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Start the logger
|
||||||
|
log.InitLogging()
|
||||||
|
|
||||||
|
// Load the config - this may need to be configurable
|
||||||
|
configfile.Install()
|
||||||
|
|
||||||
|
// Start accounting
|
||||||
|
accounting.Start(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalize finalizes the library
|
||||||
|
func Finalize() {
|
||||||
|
// TODO: how to clean up? what happens when rcserver terminates?
|
||||||
|
// what about unfinished async jobs?
|
||||||
|
runtime.GC()
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeError returns a formatted error string and the status passed in
|
||||||
|
func writeError(path string, in rc.Params, err error, status int) (string, int) {
|
||||||
|
fs.Errorf(nil, "rc: %q: error: %v", path, err)
|
||||||
|
params, status := rc.Error(path, in, err, status)
|
||||||
|
var w strings.Builder
|
||||||
|
err = rc.WriteJSON(&w, params)
|
||||||
|
if err != nil {
|
||||||
|
// ultimate fallback error
|
||||||
|
fs.Errorf(nil, "writeError: failed to write JSON output from %#v: %v", in, err)
|
||||||
|
status = http.StatusInternalServerError
|
||||||
|
w.Reset()
|
||||||
|
fmt.Fprintf(&w, `{
|
||||||
|
"error": %q,
|
||||||
|
"path": %q,
|
||||||
|
"status": %d
|
||||||
|
}`, err, path, status)
|
||||||
|
|
||||||
|
}
|
||||||
|
return w.String(), status
|
||||||
|
}
|
||||||
|
|
||||||
|
// RPC runs a transaction over the RC
|
||||||
|
//
|
||||||
|
// Calling an rc function using JSON to input parameters and output the resulted JSON
|
||||||
|
//
|
||||||
|
// operations/uploadfile and core/command are not supported as they need request or response object
|
||||||
|
// modified from handlePost in rcserver.go
|
||||||
|
func RPC(method string, input string) (output string, status int) {
|
||||||
|
// create a buffer to capture the output
|
||||||
|
in := make(rc.Params)
|
||||||
|
err := json.NewDecoder(strings.NewReader(input)).Decode(&in)
|
||||||
|
if err != nil {
|
||||||
|
return writeError(method, in, errors.Wrap(err, "failed to read input JSON"), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the call
|
||||||
|
call := rc.Calls.Get(method)
|
||||||
|
if call == nil {
|
||||||
|
return writeError(method, in, errors.Errorf("couldn't find method %q", method), http.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: handle these cases
|
||||||
|
if call.NeedsRequest {
|
||||||
|
return writeError(method, in, errors.Errorf("method %q needs request, not supported", method), http.StatusNotFound)
|
||||||
|
// Add the request to RC
|
||||||
|
//in["_request"] = r
|
||||||
|
}
|
||||||
|
if call.NeedsResponse {
|
||||||
|
return writeError(method, in, errors.Errorf("method %q need response, not supported", method), http.StatusNotFound)
|
||||||
|
//in["_response"] = w
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.Debugf(nil, "rc: %q: with parameters %+v", method, in)
|
||||||
|
|
||||||
|
_, out, err := jobs.NewJob(context.Background(), call.Fn, in)
|
||||||
|
if err != nil {
|
||||||
|
return writeError(method, in, err, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
if out == nil {
|
||||||
|
out = make(rc.Params)
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.Debugf(nil, "rc: %q: reply %+v: %v", method, out, err)
|
||||||
|
|
||||||
|
var w strings.Builder
|
||||||
|
err = rc.WriteJSON(&w, out)
|
||||||
|
if err != nil {
|
||||||
|
fs.Errorf(nil, "rc: failed to write JSON output: %v", err)
|
||||||
|
return writeError(method, in, err, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.String(), http.StatusOK
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user