From 83cd92b9034d4abd03f528d41723dcb395f15067 Mon Sep 17 00:00:00 2001 From: Sheepit Renderfarm Date: Sat, 8 Jun 2024 03:21:33 +0000 Subject: [PATCH] Feat: incompatible process --- src/main/java/com/sheepit/client/Client.java | 24 +++++- .../com/sheepit/client/Configuration.java | 3 + src/main/java/com/sheepit/client/Error.java | 3 + .../client/IncompatibleProcessChecker.java | 86 +++++++++++++++++++ src/main/java/com/sheepit/client/Job.java | 14 +++ .../com/sheepit/client/SettingsLoader.java | 15 +++- src/main/java/com/sheepit/client/os/OS.java | 5 ++ .../com/sheepit/client/standalone/Worker.java | 5 ++ .../standalone/swing/SwingTooltips.java | 2 + .../standalone/swing/activity/Settings.java | 13 ++- 10 files changed, 163 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/sheepit/client/IncompatibleProcessChecker.java diff --git a/src/main/java/com/sheepit/client/Client.java b/src/main/java/com/sheepit/client/Client.java index 42fe15a..daa2e85 100644 --- a/src/main/java/com/sheepit/client/Client.java +++ b/src/main/java/com/sheepit/client/Client.java @@ -187,14 +187,24 @@ import okhttp3.HttpUrl; Thread threadSender = new Thread(runnableSender); threadSender.start(); + IncompatibleProcessChecker incompatibleProcessChecker = new IncompatibleProcessChecker(this); + Timer incompatibleProcessCheckerTimer = new Timer(); + incompatibleProcessCheckerTimer.schedule(incompatibleProcessChecker, TimeUnit.MINUTES.toMillis(1), TimeUnit.MINUTES.toMillis(1)); + incompatibleProcessChecker.run(); // before the first request, check if it should be stopped + do { while (this.running) { this.renderingJob = null; synchronized (this) { if (this.suspended) { - this.gui.status("Client paused", true); - this.log.debug("Client paused"); + if (incompatibleProcessChecker.isSuspendedDueToOtherProcess()) { + this.gui.status("Client paused due to 'incompatible process' feature", true); + } + else { + this.gui.status("Client paused", true); + this.log.debug("Client paused"); + } } while (this.suspended && !this.shuttingdown) { wait(); @@ -215,8 +225,14 @@ import okhttp3.HttpUrl; } this.sleep(wait); } - this.gui.status("Requesting Job"); - this.renderingJob = this.server.requestJob(); + if (incompatibleProcessChecker.isRunningCompatibleProcess() == false) { + this.gui.status("Requesting Job"); + this.renderingJob = this.server.requestJob(); + } + else { + this.gui.status("Wait until compatible process is stopped"); + this.sleep(30 * 1000); + } } catch (SheepItExceptionNoRightToRender e) { this.gui.error("User does not have enough right to render project"); diff --git a/src/main/java/com/sheepit/client/Configuration.java b/src/main/java/com/sheepit/client/Configuration.java index 8383537..5fd0714 100644 --- a/src/main/java/com/sheepit/client/Configuration.java +++ b/src/main/java/com/sheepit/client/Configuration.java @@ -82,6 +82,7 @@ import lombok.Data; private String hostname; private String theme; private boolean disableLargeDownloads; + private String incompatibleProcess; public Configuration(File cache_dir_, String login_, String password_) { this.configFilePath = null; @@ -116,6 +117,7 @@ import lombok.Data; this.UIType = null; this.theme = null; this.disableLargeDownloads = false; + this.incompatibleProcess = null; } /** @@ -155,6 +157,7 @@ import lombok.Data; c + "UIType: " + UIType + n + c + "hostname: " + hostname + n + c + "theme: " + theme + n + + c + "incompatibleProcess: " + incompatibleProcess + n + c + "disableLargeDownloads: " + disableLargeDownloads; } diff --git a/src/main/java/com/sheepit/client/Error.java b/src/main/java/com/sheepit/client/Error.java index 6bc3752..7a733bc 100644 --- a/src/main/java/com/sheepit/client/Error.java +++ b/src/main/java/com/sheepit/client/Error.java @@ -44,6 +44,7 @@ public class Error { RENDERER_KILLED_BY_USER(20), RENDERER_KILLED_BY_USER_OVER_TIME(23), RENDERER_KILLED_BY_SERVER(22), + RENDERER_KILLED_BY_USER_INCOMPATIBLE_PROCESS(34), RENDERER_MISSING_LIBRARIES(15), FAILED_TO_EXECUTE(16), OS_NOT_SUPPORTED(17), @@ -202,6 +203,8 @@ public class Error { return "Render canceled because you've blocked the project."; case RENDERER_KILLED_BY_SERVER: return "Render canceled because the project has been stopped by the server. Usually because the project will take too much time or it's been paused."; + case RENDERER_KILLED_BY_USER_INCOMPATIBLE_PROCESS: + return "Stopped rendering: The incompatible user-specified process is running."; case SESSION_DISABLED: return "The server has disabled your session. Your client may have generated a broken frame (GPU not compatible, not enough RAM/VRAM, etc)."; case RENDERER_NOT_AVAILABLE: diff --git a/src/main/java/com/sheepit/client/IncompatibleProcessChecker.java b/src/main/java/com/sheepit/client/IncompatibleProcessChecker.java new file mode 100644 index 0000000..71f0aad --- /dev/null +++ b/src/main/java/com/sheepit/client/IncompatibleProcessChecker.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2024 Laurent CLOUET + * Author Laurent CLOUET + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package com.sheepit.client; + +import java.util.TimerTask; + +import com.sheepit.client.os.OS; +import lombok.Getter; +import oshi.software.os.OSProcess; + +public class IncompatibleProcessChecker extends TimerTask { + + private final Client client; + @Getter + private boolean suspendedDueToOtherProcess; + + public IncompatibleProcessChecker(Client client_) { + this.client = client_; + this.suspendedDueToOtherProcess = false; + } + + @Override public void run() { + String search = this.client.getConfiguration().getIncompatibleProcess(); + if (search == null || search.isEmpty()) { // to nothing + return; + } + search = search.toLowerCase(); + + if (isSearchProcessRunning(search)) { + if (this.client.getRenderingJob() != null && this.client.getRenderingJob().getProcessRender().getProcess() != null) { + this.client.getRenderingJob().incompatibleProcessBlock(); + } + this.client.suspend(); + this.client.getGui().status("Client paused due to 'incompatible process' feature", true); + this.suspendedDueToOtherProcess = true; + } + else { + if (this.client.isSuspended() && this.suspendedDueToOtherProcess) { + // restart the client since the other process has been shutdown + this.client.resume(); + } + } + } + + public boolean isRunningCompatibleProcess() { + String search = this.client.getConfiguration().getIncompatibleProcess(); + if (search == null || search.isEmpty()) { // to nothing + return false; + } + + return isSearchProcessRunning(search.toLowerCase()); + } + + private boolean isSearchProcessRunning(String search) { + for (OSProcess processInfo : OS.getOS().getProcesses()) { + String name = processInfo.getName(); + if (name == null || name.isEmpty()) { + continue; + } + + if (name.toLowerCase().contains(search)) { + this.client.getLog().debug("IncompatibleProcessChecker(" + search + ") found " + processInfo.getName()); + return true; + } + } + + return false; + } +} diff --git a/src/main/java/com/sheepit/client/Job.java b/src/main/java/com/sheepit/client/Job.java index 2281ca7..902637d 100644 --- a/src/main/java/com/sheepit/client/Job.java +++ b/src/main/java/com/sheepit/client/Job.java @@ -86,6 +86,7 @@ import java.util.regex.Pattern; private boolean askForRendererKill; private boolean userBlockJob; private boolean serverBlockJob; + private boolean incompatibleProcessKillJob; private Gui gui; private Configuration configuration; private Log log; @@ -113,6 +114,7 @@ import java.util.regex.Pattern; askForRendererKill = false; userBlockJob = false; serverBlockJob = false; + incompatibleProcessKillJob = false; log = log_; render = new RenderProcess(log_); blenderShortVersion = null; @@ -129,6 +131,15 @@ import java.util.regex.Pattern; } } + public void incompatibleProcessBlock() { + setAskForRendererKill(true); + setIncompatibleProcessKillJob(true); + RenderProcess process = getProcessRender(); + if (process != null) { + process.kill(); + } + } + public RenderProcess getProcessRender() { return render; } @@ -596,6 +607,9 @@ import java.util.regex.Pattern; if (isUserBlockJob()) { return Error.Type.RENDERER_KILLED_BY_USER; } + if (isIncompatibleProcessKillJob()) { + return Error.Type.RENDERER_KILLED_BY_USER_INCOMPATIBLE_PROCESS; + } return Error.Type.RENDERER_KILLED; } diff --git a/src/main/java/com/sheepit/client/SettingsLoader.java b/src/main/java/com/sheepit/client/SettingsLoader.java index 3159bd1..5df7392 100644 --- a/src/main/java/com/sheepit/client/SettingsLoader.java +++ b/src/main/java/com/sheepit/client/SettingsLoader.java @@ -66,6 +66,7 @@ public class SettingsLoader { THEME("theme"), LOG_DIR("log-dir"), DEBUG("debug"), + INCOMPATIBLE_PROCESS("incompatible-process"), DISABLE_LARGE_DOWNLOADS("disable-large-downloads"); String propertyName; @@ -110,6 +111,7 @@ public class SettingsLoader { public static final String ARG_HOSTNAME = "-hostname"; public static final String ARG_HEADLESS = "--headless"; public static final String ARG_DISABLE_LARGE_DOWNLOADS = "--disable-large-downloads"; + public static final String ARG_INCOMPATIBLE_PROCESS = "-incompatible-process"; private String path; @@ -135,6 +137,7 @@ public class SettingsLoader { private Option theme; private Option priority; private Option disableLargeDownloads; + private Option incompatibleProcess; public SettingsLoader(String path_) { if (path_ == null) { @@ -148,7 +151,7 @@ public class SettingsLoader { public void setSettings(String path_, String login_, String password_, String proxy_, String hostname_, ComputeType computeMethod_, GPUDevice gpu_, Integer cores_, Long maxRam_, Integer maxRenderTime_, String cacheDir_, String sharedZip_, Boolean autoSignIn_, Boolean useSysTray_, - Boolean isHeadless, String ui_, String theme_, Integer priority_, Boolean disableLargeDownloads_, Boolean debug_) { + Boolean isHeadless, String ui_, String theme_, Integer priority_, Boolean disableLargeDownloads_, Boolean debug_, String incompatibleProcess_) { if (path_ == null) { path = OS.getOS().getDefaultConfigFilePath(); } @@ -169,6 +172,7 @@ public class SettingsLoader { theme = setValue(theme_, theme, ARG_THEME); disableLargeDownloads = setValue(disableLargeDownloads_.toString(), disableLargeDownloads, ARG_DISABLE_LARGE_DOWNLOADS); debug = setValue(debug_.toString(), debug, ARG_VERBOSE); + incompatibleProcess = setValue(incompatibleProcess_, incompatibleProcess, ARG_INCOMPATIBLE_PROCESS); if (cores_ > 0) { cores = setValue(cores_.toString(), cores, ARG_CORES); @@ -290,6 +294,7 @@ public class SettingsLoader { setProperty(prop, configFileProp, PropertyNames.DISABLE_LARGE_DOWNLOADS, disableLargeDownloads); setProperty(prop, configFileProp, PropertyNames.LOG_DIR, logDir); setProperty(prop, configFileProp, PropertyNames.DEBUG, debug); + setProperty(prop, configFileProp, PropertyNames.INCOMPATIBLE_PROCESS, incompatibleProcess); prop.store(output, null); } catch (IOException io) { @@ -395,6 +400,8 @@ public class SettingsLoader { disableLargeDownloads = loadConfigOption(prop, PropertyNames.DISABLE_LARGE_DOWNLOADS, disableLargeDownloads, ARG_DISABLE_LARGE_DOWNLOADS); logDir = loadConfigOption(prop, PropertyNames.LOG_DIR, logDir, ARG_LOG_DIRECTORY); + + incompatibleProcess = loadConfigOption(prop, PropertyNames.INCOMPATIBLE_PROCESS, incompatibleProcess, ""); debug = loadConfigOption(prop, PropertyNames.DEBUG, debug, ARG_VERBOSE); @@ -470,6 +477,11 @@ public class SettingsLoader { if (config.getPriority() == 19) { // 19 is default value config.setPriority(priority.getValue()); } + + if (incompatibleProcess != null) { + config.setIncompatibleProcess(incompatibleProcess.getValue()); + } + try { if (config.getComputeMethod() == null && computeMethod == null) { config.setComputeMethod(ComputeType.CPU); @@ -574,6 +586,7 @@ public class SettingsLoader { this.disableLargeDownloads = new Option<>(String.valueOf(defaultConfigValues.isDisableLargeDownloads()), ARG_DISABLE_LARGE_DOWNLOADS); this.logDir = null; this.debug = null; + this.incompatibleProcess = null; } @Override public String toString() { diff --git a/src/main/java/com/sheepit/client/os/OS.java b/src/main/java/com/sheepit/client/os/OS.java index c9e9974..9be987c 100644 --- a/src/main/java/com/sheepit/client/os/OS.java +++ b/src/main/java/com/sheepit/client/os/OS.java @@ -25,6 +25,7 @@ import java.util.Map; import oshi.SystemInfo; import oshi.hardware.CentralProcessor; +import oshi.software.os.OSProcess; import oshi.software.os.OperatingSystem; import oshi.hardware.HardwareAbstractionLayer; import com.sheepit.client.hardware.cpu.CPU; @@ -219,4 +220,8 @@ public abstract class OS { file.mkdirs(); return file.getAbsolutePath() + File.separator + "sheepit.conf"; } + + public List getProcesses() { + return operatingSystem.getProcesses(); + } } diff --git a/src/main/java/com/sheepit/client/standalone/Worker.java b/src/main/java/com/sheepit/client/standalone/Worker.java index 5bc311c..7ecfde3 100644 --- a/src/main/java/com/sheepit/client/standalone/Worker.java +++ b/src/main/java/com/sheepit/client/standalone/Worker.java @@ -116,6 +116,9 @@ public class Worker { @Option(name = SettingsLoader.ARG_HEADLESS, usage = "Mark your client manually as headless to block Eevee projects", required = false) private boolean headless = java.awt.GraphicsEnvironment.isHeadless(); @Option(name = SettingsLoader.ARG_DISABLE_LARGE_DOWNLOADS, usage = "Disable download of larger projects to preserve internet traffic", required = false) private boolean disableLargeDownloads = false; + @Option(name = SettingsLoader.ARG_INCOMPATIBLE_PROCESS, usage = "Specify a process to stop the current render job and pause while the said process is running. For example, if we take Firefox the formatting is firefox.exe on Windows and firefox on Linux.", required = false) private String incompatibleProcess = null; + + public static void main(String[] args) { if (OS.getOS() == null) { System.err.println(Error.humanString(Error.Type.OS_NOT_SUPPORTED)); @@ -144,6 +147,8 @@ public class Worker { config.setPrintLog(print_log); config.setPriority(priority); config.setDetectGPUs(!no_gpu_detection); + config.setIncompatibleProcess(incompatibleProcess); + if (sharedDownloadsDir != null) { File dir = new File(sharedDownloadsDir); diff --git a/src/main/java/com/sheepit/client/standalone/swing/SwingTooltips.java b/src/main/java/com/sheepit/client/standalone/swing/SwingTooltips.java index 3111b71..0b7b61d 100644 --- a/src/main/java/com/sheepit/client/standalone/swing/SwingTooltips.java +++ b/src/main/java/com/sheepit/client/standalone/swing/SwingTooltips.java @@ -22,6 +22,8 @@ public enum SwingTooltips { COMPUTER_NAME("What this machine will be displayed as on your Sheepit profile page. Only you and admins can see this."), + INCOMPATIBLE_PROCESS("If your specified process starts or is running, SheepIt will stop the current render job and pause. For example, if we take Firefox the formatting is firefox.exe on Windows and firefox on Linux."), + MAX_TIME_PER_FRAME("How much time a frame should take at most. Sheepit will try to assign jobs to your machine that take less time to compute."), DISABLE_LARGE_DOWNLOADS("Limits the client to smaller projects <= 750 MB. Consider this option if you are on a metered connection"); diff --git a/src/main/java/com/sheepit/client/standalone/swing/activity/Settings.java b/src/main/java/com/sheepit/client/standalone/swing/activity/Settings.java index 41a66c9..1b9b3b2 100644 --- a/src/main/java/com/sheepit/client/standalone/swing/activity/Settings.java +++ b/src/main/java/com/sheepit/client/standalone/swing/activity/Settings.java @@ -91,6 +91,7 @@ public class Settings implements Activity { private JTextField proxy; private JTextField hostname; private JCheckBox disableLargeDownloads; + private JTextField incompatibleProcess; private ButtonGroup themeOptionsGroup; private JRadioButton lightMode; @@ -442,7 +443,7 @@ public class Settings implements Activity { parent.getContentPanel().add(compute_devices_panel, constraints); // other - CollapsibleJPanel advanced_panel = new CollapsibleJPanel(new GridLayout(6, 2), this); + CollapsibleJPanel advanced_panel = new CollapsibleJPanel(new GridLayout(7, 2), this); advanced_panel.setBorder(BorderFactory.createTitledBorder("Advanced options")); JLabel useSysTrayLabel = new JLabel("Minimize to SysTray"); @@ -484,6 +485,14 @@ public class Settings implements Activity { advanced_panel.add(hostnameLabel); advanced_panel.add(hostname); + JLabel incompatibleProcessLabel = new JLabel("Incompatible process:"); + incompatibleProcessLabel.setToolTipText(SwingTooltips.INCOMPATIBLE_PROCESS.getText()); + incompatibleProcess = new JTextField(); + incompatibleProcess.setText(parent.getConfiguration().getIncompatibleProcess()); + + advanced_panel.add(incompatibleProcessLabel); + advanced_panel.add(incompatibleProcess); + JLabel renderTimeLabel = new JLabel("Max time per frame (in minute):"); renderTimeLabel.setToolTipText(SwingTooltips.MAX_TIME_PER_FRAME.getText()); int val = 0; @@ -789,7 +798,7 @@ public class Settings implements Activity { .setSettings(config.getConfigFilePath(), login.getText(), new String(password.getPassword()), proxyText, hostnameText, method, selected_gpu, cpu_cores, max_ram, max_rendertime, getCachePath(config), getSharedPath(config), autoSignIn.isSelected(), useSysTray.isSelected(), headlessCheckbox.isSelected(), GuiSwing.type, themeOptionsGroup.getSelection().getActionCommand(), - config.getPriority(), disableLargeDownloads.isSelected(), config.isDebugLevel()); + config.getPriority(), disableLargeDownloads.isSelected(), config.isDebugLevel(), incompatibleProcess.getText().trim()); // wait for successful authentication (to store the public key) // or do we already have one?