github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/rc/webgui/rc.go (about)

     1  package webgui
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  
     9  	"github.com/rclone/rclone/fs"
    10  	"github.com/rclone/rclone/fs/rc"
    11  )
    12  
    13  func init() {
    14  	rc.Add(rc.Call{
    15  		Path:         "pluginsctl/listTestPlugins",
    16  		AuthRequired: true,
    17  		Fn:           rcListTestPlugins,
    18  		Title:        "Show currently loaded test plugins",
    19  		Help: `Allows listing of test plugins with the rclone.test set to true in package.json of the plugin.
    20  
    21  This takes no parameters and returns:
    22  
    23  - loadedTestPlugins - list of currently available test plugins.
    24  
    25  E.g.
    26  
    27      rclone rc pluginsctl/listTestPlugins
    28  `,
    29  	})
    30  }
    31  
    32  func rcListTestPlugins(_ context.Context, _ rc.Params) (out rc.Params, err error) {
    33  	err = initPluginsOrError()
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  	return rc.Params{
    38  		"loadedTestPlugins": filterPlugins(loadedPlugins, func(json *PackageJSON) bool { return json.isTesting() }),
    39  	}, nil
    40  }
    41  
    42  func init() {
    43  	rc.Add(rc.Call{
    44  		Path:         "pluginsctl/removeTestPlugin",
    45  		AuthRequired: true,
    46  		Fn:           rcRemoveTestPlugin,
    47  		Title:        "Remove  a test plugin",
    48  		Help: `This allows you to remove a plugin using it's name.
    49  
    50  This takes the following parameters:
    51  
    52  - name - name of the plugin in the format ` + "`author`/`plugin_name`" + `.
    53  
    54  Example:
    55  
    56      rclone rc pluginsctl/removeTestPlugin name=rclone/rclone-webui-react
    57  `,
    58  	})
    59  }
    60  func rcRemoveTestPlugin(_ context.Context, in rc.Params) (out rc.Params, err error) {
    61  	err = initPluginsOrError()
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  	name, err := in.GetString("name")
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  	err = loadedPlugins.removePlugin(name)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  	return nil, nil
    74  }
    75  
    76  func init() {
    77  	rc.Add(rc.Call{
    78  		Path:         "pluginsctl/addPlugin",
    79  		AuthRequired: true,
    80  		Fn:           rcAddPlugin,
    81  		Title:        "Add a plugin using url",
    82  		Help: `Used for adding a plugin to the webgui.
    83  
    84  This takes the following parameters:
    85  
    86  - url - http url of the github repo where the plugin is hosted (http://github.com/rclone/rclone-webui-react).
    87  
    88  Example:
    89  
    90     rclone rc pluginsctl/addPlugin
    91  `,
    92  	})
    93  }
    94  
    95  func rcAddPlugin(_ context.Context, in rc.Params) (out rc.Params, err error) {
    96  	err = initPluginsOrError()
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  	pluginURL, err := in.GetString("url")
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  
   105  	author, repoName, repoBranch, err := getAuthorRepoBranchGitHub(pluginURL)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  
   110  	branch, err := in.GetString("branch")
   111  	if err != nil || branch == "" {
   112  		branch = repoBranch
   113  	}
   114  
   115  	version, err := in.GetString("version")
   116  	if err != nil || version == "" {
   117  		version = "latest"
   118  	}
   119  
   120  	err = CreatePathIfNotExist(PluginsPath)
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  
   125  	// fetch and package.json
   126  	// https://raw.githubusercontent.com/rclone/rclone-webui-react/master/package.json
   127  
   128  	pluginID := fmt.Sprintf("%s/%s", author, repoName)
   129  
   130  	currentPluginPath := filepath.Join(PluginsPath, pluginID)
   131  
   132  	err = CreatePathIfNotExist(currentPluginPath)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	packageJSONUrl := fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/package.json", author, repoName, branch)
   138  	packageJSONFilePath := filepath.Join(currentPluginPath, "package.json")
   139  	err = DownloadFile(packageJSONFilePath, packageJSONUrl)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  	// register in plugins
   144  
   145  	// download release and save in plugins/<author>/repo-name/app
   146  	// https://api.github.com/repos/rclone/rclone-webui-react/releases/latest
   147  	releaseURL, tag, _, err := GetLatestReleaseURL(fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/%s", author, repoName, version))
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  	zipName := tag + ".zip"
   152  	zipPath := filepath.Join(currentPluginPath, zipName)
   153  
   154  	err = DownloadFile(zipPath, releaseURL)
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  
   159  	extractPath := filepath.Join(currentPluginPath, "app")
   160  
   161  	err = CreatePathIfNotExist(extractPath)
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  	err = os.RemoveAll(extractPath)
   166  	if err != nil {
   167  		fs.Logf(nil, "No previous downloads to remove")
   168  	}
   169  
   170  	fs.Logf(nil, "Unzipping plugin binary")
   171  
   172  	err = Unzip(zipPath, extractPath)
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  
   177  	err = loadedPlugins.addPlugin(pluginID, packageJSONFilePath)
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  
   182  	return nil, nil
   183  
   184  }
   185  
   186  func init() {
   187  	rc.Add(rc.Call{
   188  		Path:         "pluginsctl/listPlugins",
   189  		AuthRequired: true,
   190  		Fn:           rcGetPlugins,
   191  		Title:        "Get the list of currently loaded plugins",
   192  		Help: `This allows you to get the currently enabled plugins and their details.
   193  
   194  This takes no parameters and returns:
   195  
   196  - loadedPlugins - list of current production plugins.
   197  - testPlugins - list of temporarily loaded development plugins, usually running on a different server.
   198  
   199  E.g.
   200  
   201     rclone rc pluginsctl/listPlugins
   202  `,
   203  	})
   204  }
   205  
   206  func rcGetPlugins(_ context.Context, _ rc.Params) (out rc.Params, err error) {
   207  	err = initPluginsOrError()
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  	err = loadedPlugins.readFromFile()
   212  	if err != nil {
   213  		return nil, err
   214  	}
   215  	return rc.Params{
   216  		"loadedPlugins":     filterPlugins(loadedPlugins, func(packageJSON *PackageJSON) bool { return !packageJSON.isTesting() }),
   217  		"loadedTestPlugins": filterPlugins(loadedPlugins, func(packageJSON *PackageJSON) bool { return packageJSON.isTesting() }),
   218  	}, nil
   219  }
   220  
   221  func init() {
   222  	rc.Add(rc.Call{
   223  		Path:         "pluginsctl/removePlugin",
   224  		AuthRequired: true,
   225  		Fn:           rcRemovePlugin,
   226  		Title:        "Remove a loaded plugin",
   227  		Help: `This allows you to remove a plugin using it's name.
   228  
   229  This takes parameters:
   230  
   231  - name - name of the plugin in the format ` + "`author`/`plugin_name`" + `.
   232  
   233  E.g.
   234  
   235     rclone rc pluginsctl/removePlugin name=rclone/video-plugin
   236  `,
   237  	})
   238  }
   239  
   240  func rcRemovePlugin(_ context.Context, in rc.Params) (out rc.Params, err error) {
   241  	err = initPluginsOrError()
   242  	if err != nil {
   243  		return nil, err
   244  	}
   245  	name, err := in.GetString("name")
   246  	if err != nil {
   247  		return nil, err
   248  	}
   249  
   250  	err = loadedPlugins.removePlugin(name)
   251  	if err != nil {
   252  		return nil, err
   253  	}
   254  	return nil, nil
   255  }
   256  
   257  func init() {
   258  	rc.Add(rc.Call{
   259  		Path:         "pluginsctl/getPluginsForType",
   260  		AuthRequired: true,
   261  		Fn:           rcGetPluginsForType,
   262  		Title:        "Get plugins with type criteria",
   263  		Help: `This shows all possible plugins by a mime type.
   264  
   265  This takes the following parameters:
   266  
   267  - type - supported mime type by a loaded plugin e.g. (video/mp4, audio/mp3).
   268  - pluginType - filter plugins based on their type e.g. (DASHBOARD, FILE_HANDLER, TERMINAL).
   269  
   270  Returns:
   271  
   272  - loadedPlugins - list of current production plugins.
   273  - testPlugins - list of temporarily loaded development plugins, usually running on a different server.
   274  
   275  Example:
   276  
   277     rclone rc pluginsctl/getPluginsForType type=video/mp4
   278  `,
   279  	})
   280  }
   281  
   282  func rcGetPluginsForType(_ context.Context, in rc.Params) (out rc.Params, err error) {
   283  	err = initPluginsOrError()
   284  	if err != nil {
   285  		return nil, err
   286  	}
   287  	handlesType, err := in.GetString("type")
   288  	if err != nil {
   289  		handlesType = ""
   290  	}
   291  
   292  	pluginType, err := in.GetString("pluginType")
   293  	if err != nil {
   294  		pluginType = ""
   295  	}
   296  	var loadedPluginsResult map[string]PackageJSON
   297  
   298  	var loadedTestPluginsResult map[string]PackageJSON
   299  
   300  	if pluginType == "" || pluginType == "FileHandler" {
   301  
   302  		loadedPluginsResult = filterPlugins(loadedPlugins, func(packageJSON *PackageJSON) bool {
   303  			for i := range packageJSON.Rclone.HandlesType {
   304  				if packageJSON.Rclone.HandlesType[i] == handlesType && !packageJSON.Rclone.Test {
   305  					return true
   306  				}
   307  			}
   308  			return false
   309  		})
   310  
   311  		loadedTestPluginsResult = filterPlugins(loadedPlugins, func(packageJSON *PackageJSON) bool {
   312  			for i := range packageJSON.Rclone.HandlesType {
   313  				if packageJSON.Rclone.HandlesType[i] == handlesType && packageJSON.Rclone.Test {
   314  					return true
   315  				}
   316  			}
   317  			return false
   318  		})
   319  	} else {
   320  		loadedPluginsResult = filterPlugins(loadedPlugins, func(packageJSON *PackageJSON) bool {
   321  			return packageJSON.Rclone.PluginType == pluginType && !packageJSON.isTesting()
   322  		})
   323  
   324  		loadedTestPluginsResult = filterPlugins(loadedPlugins, func(packageJSON *PackageJSON) bool {
   325  			return packageJSON.Rclone.PluginType == pluginType && packageJSON.isTesting()
   326  		})
   327  	}
   328  
   329  	return rc.Params{
   330  		"loadedPlugins":     loadedPluginsResult,
   331  		"loadedTestPlugins": loadedTestPluginsResult,
   332  	}, nil
   333  
   334  }