How does electron handle global dependencies - electron

I'm new to electron. I'm building an app that uses ffmpeg and sox, which are installed globally. How do I guarantee that these dependencies will be installed on the users computer?

Try this, it is not specific to electron, but can help
http://12factor.net/dependencies

There could be two kind of system level dependencies:
NPM based package expected to be installed at a system level
Non NPM package, like a system level library(in your case ffmpeg)
For NPM based package, you can do a peer dependency like the following:
{
....
"peerDependencies": {
"chai": "1.x"
}
....
}
For non NPM package, usual pattern is to check this during your application start flow and inform the user if it's not available.
Answering specific to ffmpeg, following should help:
/**
* Check for ffmpeg availability
*
* If the FFMPEG_PATH environment variable is set, try to use it.
* If it is unset or incorrect, try to find ffmpeg in the PATH instead.
*
* #method FfmpegCommand#_getFfmpegPath
* #param {Function} callback callback with signature (err, path)
* #private
*/
proto._getFfmpegPath = function(callback) {
if ('ffmpegPath' in cache) {
return callback(null, cache.ffmpegPath);
}
async.waterfall([
// Try FFMPEG_PATH
function(cb) {
if (process.env.FFMPEG_PATH) {
fs.exists(process.env.FFMPEG_PATH, function(exists) {
if (exists) {
cb(null, process.env.FFMPEG_PATH);
} else {
cb(null, '');
}
});
} else {
cb(null, '');
}
},
// Search in the PATH
function(ffmpeg, cb) {
if (ffmpeg.length) {
return cb(null, ffmpeg);
}
utils.which('ffmpeg', function(err, ffmpeg) {
cb(err, ffmpeg);
});
}
], function(err, ffmpeg) {
if (err) {
callback(err);
} else {
callback(null, cache.ffmpegPath = (ffmpeg || ''));
}
});
};
The above code is taken from here. node-fluent-ffmpeg is a nice project which works on top of ffmpeg. I believe, it will be a good reference for your project.

You could package the different installers for the dependencies with your app, then create a custom installer for your app that also launches the dependency installers in sequence, or simply prompt the user to install the dependencies themselves and link them to a download page.
There are lots of resources for creating installers, try a google search for electron installer for your specific platform.

sorry I'm late to the party but just in case this is still relevant - I've created ffbinaries module specifically for this purpose.
You can check it out on npm, it'll simply download the binaries on user machine during app boot, the platform will be detected automatically (you can override it though if you're planning to include it in CI of some sort).

Related

Rollup tree-shakes imported variables, causing important code to be lost

I'm using rollup to bundle the build script for one of my projects. In the builds (their source is ts), I use node's worker_threads module to parallelize some work. I'm using an import of isMainThread (which is a boolean from the worker_threads module) to check whether or not to use the worker logic or the main thread logic. However, when building the build script from its source, rollup is removing the else statement. It seems to be checking the isMainThread variable during its tree-shaking process, and deciding that isMainThread will always be true, and so the else statement isn't needed. How can I change that logic?
Source:
if (isMainThread) {
const { dev, watch } = getOptions(process.argv);
if (watch) watcher(dev);
else singleBuild(dev);
} else {
worker();
}
Output:
if (isMainThread) {
const { dev, watch } = getOptions(process.argv);
if (watch)
watcher();
else
singleBuild();
}

Electron: accessing the browser window of the single instance when called a second time

I have been able to successfully create an Electron app that links to the web app (using window.loadUrl).
I have been able to pass some command line params to the web app using window.webContents.send .... On the web app, the javascript receives the parameter and updates the screen.
I am using (2) by opening a file (right-click on it from the directory) through file association using process.argv[1]. This too works.
What I would like is that if I right-click on a second file, it must be passed on to the same electron instance. I am having some issues for this.
I have used the recommended approach for preventing multiple instances as below:
...
let myWindow = null
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
// I do not want to quit
app.quit()
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
...
}
In the above logic, when the program is unable to get-the-lock, the boiler-plate code quits. That works fine, in the sense, that a second window does not open. But in my case, I would like to use the process.argv[1] of the second request and pass it to the web program of the existing instance.
I have not been successful in getting a handle to the browserWindow of the other instance. I would not want to work on multiple windows where each window would call another load of the web app. The current webapp has the ability to update multiple tabs in the same window based on different parameters. Basically, that is handled in the web app itself.
What could be the solution? Thanks
I got it working. It starred at my face and I did not see it. Added a few logs and it helped. Something like this would be a solution.
...
let myWindow = null;
...
function createWindow() {
....
return win;
}
function processParams(...) {
...
}
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
//.. this is called the second time
// process any commandLine params
processParams(...)
...
});
app.on('whenReady')
.then(_ => {
myWindow = createWindow();
// this is called the first time
// process any argv params
processParms(...);
});
}

Creating a folder outside the exe in Electron

How would I create a folder outside the Electron exe.
I'm planning to build the app as a portable windows exe so I'm not sure how to get the path of the exe.
EDIT #1:
I have tried to use app.getPath("exe"); on the main process, but I'm getting a reference error whenever I run the app ReferenceError: exe is not defined
It was indeed app.getPath("exe"), but it has to be implemented using the Electron event emitter pattern.
To have access to the data I triggered the path on the main process.
ipcMain.on("CALL_PRINT_EXE_FILE_PATH", (event) => {
console.log("printing the file path of the exe");
const exePath = app.getPath("exe");
console.log(`exePath: ${exePath}`);
mainWindow.send("PRINT_EXE_FILE_PATH", exePath);
});
Then inside the renderer (I use React), I emit the event and also trigger an event listener.
const { ipcRenderer } = window.require("electron");
...
componentDidMount() {
ipcRenderer.send("CALL_PRINT_EXE_FILE_PATH");
}
componentWillMount() {
ipcRenderer.on("PRINT_EXE_FILE_PATH", this.handlePrintExePath);
}
componentWillUnmount() {
ipcRenderer.removeListener("PRINT_EXE_FILE_PATH", this.handlePrintExePath);
}
...
handlePrintExePath(event, exePath) {
console.log("printing the app exe in the render");
console.log(`exeFilePath: ${exePath}`);
}

Open external file with Electron

I have a running Electron app and is working great so far. For context, I need to run/open a external file which is a Go-lang binary that will do some background tasks.
Basically it will act as a backend and exposing an API that the Electron app will consume.
So far this is what i get into:
I tried to open the file with the "node way" using child_process but i have fail opening the a sample txt file probably due to path issues.
The Electron API expose a open-file event but it lacks of documentation/example and i don't know if it could be useful.
That's it.
How i open an external file in Electron ?
There are a couple api's you may want to study up on and see which helps you.
fs
The fs module allows you to open files for reading and writing directly.
var fs = require('fs');
fs.readFile(p, 'utf8', function (err, data) {
if (err) return console.log(err);
// data is the contents of the text file we just read
});
path
The path module allows you to build and parse paths in a platform agnostic way.
var path = require('path');
var p = path.join(__dirname, '..', 'game.config');
shell
The shell api is an electron only api that you can use to shell execute a file at a given path, which will use the OS default application to open the file.
const {shell} = require('electron');
// Open a local file in the default app
shell.openItem('c:\\example.txt');
// Open a URL in the default way
shell.openExternal('https://github.com');
child_process
Assuming that your golang binary is an executable then you would use child_process.spawn to call it and communicate with it. This is a node api.
var path = require('path');
var spawn = require('child_process').spawn;
var child = spawn(path.join(__dirname, '..', 'mygoap.exe'), ['game.config', '--debug']);
// attach events, etc.
addon
If your golang binary isn't an executable then you will need to make a native addon wrapper.
Maybe you are looking for this ?
dialog.showOpenDialog refer to: https://www.electronjs.org/docs/api/dialog
If using electron#13.1.0, you can do like this:
const { dialog } = require('electron')
console.log(dialog.showOpenDialog({ properties: ['openFile', 'multiSelections'] }))
dialog.showOpenDialog(function(file_paths){
console.info(file_paths) // => this gives the absolute path of selected files.
})
when the above code is triggered, you can see an "open file dialog" like this (diffrent view style for win/mac/linux)
Electron allows the use of nodejs packages.
In other words, import node packages as if you were in node, e.g.:
var fs = require('fs');
To run the golang binary, you can make use of the child_process module. The documentation is thorough.
Edit: You have to solve the path differences. The open-file event is a client-side event, triggered by the window. Not what you want here.
I was also totally struggling with this issue, and almost seven years later the documentation is quite not clear what's the case with Linux.
So, on Linux it falls under Windows treatment in this regard, which means you have to look into process.argv global in the main processor, the first value in the array is the path that fired the app. The second argument, if one exist, is holding the path that requested the app to be opened. For example, here is the output for my test case:
Array(2)
0: "/opt/Blueprint/b-test"
1: "/home/husayngonzalez/2022-01-20.md"
length: 2
So, when you're creating a new window, you check for the length of process.argv and then if it was more than 1, i.e. = 2 it means you have a path that requested to be opened with your app.
Assuming you got your application packaged with the ability to process those files, and also you set the operating system to request your application to open those.
I know this doesn't exactly meet your specification, but it does cleanly separate your golang binary and Electron application.
The way I have done it is to expose the golang binary as a web service. Like this
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
//TODO: put your call here instead of the Fprintf
fmt.Fprintf(w, "HI there from Go Web Svc. %s", r.URL.Path[1:])
}
func main() {
http.HandleFunc("/api/someMethod", handler)
http.ListenAndServe(":8080", nil)
}
Then from Electron just make ajax calls to the web service with a javascript function. Like this (you could use jQuery, but I find this pure js works fine)
function get(url, responseType) {
return new Promise(function(resolve, reject) {
var request = new XMLHttpRequest();
request.open('GET', url);
request.responseType = responseType;
request.onload = function() {
if (request.status == 200) {
resolve(request.response);
} else {
reject(Error(request.statusText));
}
};
request.onerror = function() {
reject(Error("Network Error"));
};
request.send();
});
With that method you could do something like
get('localhost/api/somemethod', 'text')
.then(function(x){
console.log(x);
}

how can I know whether the plugin is used by any jobs in jenkins

Jenkins had 600+ plugins, in the real system, we are used to install lots of plugins.
And sometimes, we want to remove some plugins to make system more clean or replace with another mature plugin (different name).
This needs to make sure no one/no job use those plugins or I need to notify them.
Are there any ways in configuration or somewhere in Jenkins system to know whether the plugin is used by any jobs ?
UPDATE 2013
Based on the answer below, I maintain the simple "plugin:keyword" mapping, like
plugin_keys = {
"git":'scm class="hudson.plugins.git.GitSCM"',
"copyartifact":"hudson.plugins.copyartifact.CopyArtifact",
# and more
}
And search the plugin keyword from the config.xml, all the information (plugins,jobs,config) can be fetched via jenkins remote API
it works for me.
UPDATE 2014.04.26
Later jenkins version, it seems the config.xml is changed to have plugin name there directly
Like
<com.coravy.hudson.plugins.github.GithubProjectProperty plugin="github#1.4">
<hudson.plugins.throttleconcurrents.ThrottleJobProperty plugin="throttle-concurrents#1.7.2">
<hudson.plugins.disk__usage.DiskUsageProperty plugin="disk-usage#0.18"/>
<scm class="hudson.plugins.git.GitSCM" plugin="git#1.4.1-SNAPSHOT">
Therefore I just check this plugin="<plugin name>" in config.xml, it works again
UPDATE 2014.05.05
See complete script in gist jenkins-stats.py
UPDATE 2018.6.7
There is plugin usage plugin support this (no REST API yet)
Here are 2 ways to find that information.
The easiest is probably to to grep the job config files:
E.g. when you know the class name (or package name) of your plugin (e.g. org.jenkinsci.plugins.unity3d.Unity3dBuilder):
find $JENKINS_HOME/jobs/ -name config.xml -maxdepth 2 | xargs grep Unity3dBuilder
Another is to use something like the scriptler plugin, but then you need more information about where the plugin is used in the build.
import hudson.model.*
import hudson.maven.*
import hudson.tasks.*
for(item in Hudson.instance.items) {
//println("JOB : "+item.name);
for (builder in item.builders){
if (builder instanceof org.jenkinsci.plugins.unity3d.Unity3dBuilder) {
println(">>" + item.name.padRight(50, " ") + "\t UNITY3D BUILDER with " + builder.unity3dName);
}
}
}
}
Update: here's a small scriplet script that might ease you finding the relevant class names. It can certainly be improved:
import jenkins.model.*;
import hudson.ExtensionFinder;
List<ExtensionFinder> finders = Jenkins.instance.getExtensionList(ExtensionFinder.class);
for (finder in finders) {
println(">>> " + finder);
if (finder instanceof hudson.ExtensionFinder.GuiceFinder) {
println(finder.annotations.size());
for (key in finder.annotations.keySet()) {
println(key);
}
} else if (finder instanceof ruby.RubyExtensionFinder) {
println(finder.parsedPlugins.size());
for (plugin in finder.parsedPlugins) {
for (extension in plugin.extensions) {
println("ruby wrapper for " + extension.instance.clazz);
}
}
} else if (finder instanceof hudson.cli.declarative.CLIRegisterer) {
println(finder.discover(Jenkins.instance));
for (extension in finder.discover(Jenkins.instance)) {
println("CLI wrapper for " + extension.instance.class);
// not sure what to do with those
}
} else {
println("UNKNOWN FINDER TYPE");
}
}
(inlined scriplet from my original listJenkinsExtensions submission to http://scriptlerweb.appspot.com which seems down)
Don't forget to backup!
As of early 2018 there is a "Plugins Usage Plugin" that gives you a nice list of the plugins and where they are used. We've noticed that depending on the system sometimes it doesn't seems to catch all the plugins, but it gives a really lovely list of the plugins and all jobs related to a specific plugin in an expandable list.
https://plugins.jenkins.io/plugin-usage-plugin
Plugins used in pipeline scripts would not be listed normally as used by jobs, because they are used dynamically in Jenkinsfiles.
I can't comment because I don't have enough reputation, but if I could, I would point out that the broken link provided by coffeebreaks for the small scriplet script mentioned in the accepted answer can be found on the Internet Archive, at this link:
https://web.archive.org/web/20131103111754/http://scriptlerweb.appspot.com/script/show/97001
In case that link breaks, here is the content of the script:
import jenkins.model.*;
import hudson.ExtensionFinder;
List<ExtensionFinder> finders = Jenkins.instance.getExtensionList(ExtensionFinder.class);
for (finder in finders) {
println(">>> " + finder);
if (finder instanceof hudson.ExtensionFinder.GuiceFinder) {
println(finder.annotations.size());
for (key in finder.annotations.keySet()) {
println(key);
}
} else if (finder instanceof ruby.RubyExtensionFinder) {
println(finder.parsedPlugins.size());
for (plugin in finder.parsedPlugins) {
for (extension in plugin.extensions) {
println("ruby wrapper for " + extension.instance.clazz);
}
}
} else if (finder instanceof hudson.cli.declarative.CLIRegisterer) {
println(finder.discover(Jenkins.instance));
for (extension in finder.discover(Jenkins.instance)) {
println("CLI wrapper for " + extension.instance.class);
// not sure what to do with those
}
} else {
println("UNKNOWN FINDER TYPE");
}
}
I wrote this parameterized Jenkins job that searches config files. You only need to know what tags the plugin generates in the config file and to use that tag's name as parameter needle:
cd $JENKINS_HOME
cd jobs
echo searching for $needle
find . -name config.xml -type f -exec grep $needle /dev/null {} \;

Resources