diff --git a/.classpath b/.classpath
new file mode 100644
index 0000000..352e960
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/.project b/.project
new file mode 100644
index 0000000..2f82e76
--- /dev/null
+++ b/.project
@@ -0,0 +1,17 @@
+
+
+ SheepitClient
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..c97c55c
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/extern/args4j.jar b/extern/args4j.jar
new file mode 100644
index 0000000..e188b3d
Binary files /dev/null and b/extern/args4j.jar differ
diff --git a/extern/commons-compress.jar b/extern/commons-compress.jar
new file mode 100644
index 0000000..51baf91
Binary files /dev/null and b/extern/commons-compress.jar differ
diff --git a/extern/jna.jar b/extern/jna.jar
new file mode 100644
index 0000000..9038048
Binary files /dev/null and b/extern/jna.jar differ
diff --git a/extern/platform.jar b/extern/platform.jar
new file mode 100644
index 0000000..4b3d567
Binary files /dev/null and b/extern/platform.jar differ
diff --git a/protocol.txt b/protocol.txt
new file mode 100644
index 0000000..e94d042
--- /dev/null
+++ b/protocol.txt
@@ -0,0 +1,172 @@
+=== Session creation ===
+
+Url: Root server url (not including /server)
+Parameters as GET:
+ * login: User's login in plain text.
+ * password: User's password in plain text.
+ * version: Client's version, for example 3.3.1762.
+ * os: Computer's operating system, at the moment only "windows", "mac" and "linux" are supported.
+ * bits: Architecture size, at the moment only "32bit" and "64bit" are supported.
+ * cpu_family: CPU's family, on linux it can be get in /proc/cpuinfo via the attribute "cpu fanily".
+ * cpu_model: CPU's model, on linux it can be get in /proc/cpuinfo via the attribute "model".
+ * cpu_model_name: CPU's model as human readable, on linux it can be get in /proc/cpuinfo via the attribute "model name".
+ * cpu_cores: Number of core (or thread) available.
+ * ram: Memory available in bytes.
+ * extras (optional): Extra data use for the configuration.
+
+Answer in case of error:
+
+
+where X:
+ * 100 => Error no version given.
+ * 101 => Client is too old.
+ * 102 => Authentification failure.
+ * 103 => WebSession have expirated.
+ * 104 => Missing parameter.
+ * something else => Unknown error.
+
+Answer with no error:
+The status="0" to specify everything is okay, plus a list of destination for request, validation job, etc.
+It provide a destination for error, job request, job validation, download needed file, heart beat (keepmealive) and logout.
+The maximum duration between two heart beat is given by the attribute "max-period" who is in second.
+
+
+
+
+
+
+
+
+
+
+
+=== Session end ===
+
+Url: use the request type "logout" from the configuration answer.
+No additional parameter is required.
+
+=== Download renderer archive ===
+
+Url: use the request type "download-archive" from the configuration answer.
+Parameter as GET or POST:
+ * type: "binary"
+ * job: id of the job
+
+Answer:
+No error: the file
+On error: an 404 http code
+
+
+=== Download job archive ===
+
+Url: use the request type "download-archive" from the configuration answer.
+Parameter as GET or POST:
+ * type: "job"
+ * job: Id of the job
+ * revision: Revision of the job
+
+Answer:
+No error: the file
+On error: an 404 http code
+
+
+=== Job request ===
+
+Url: use the request type "request-job" from the configuration answer.
+Parameter as GET or POST:
+ * computemethod: What are my computer capability, 0 for using gpu and cpu (the computer is capable of using both but it will not use it at the same time), 1 for cpu only, 2 for gpu only.
+
+Answer in case of error:
+
+
+where X:
+ * 0 => No error
+ * 200 => No job available, the client should wait few minutes before requesting a new job (typical value is 1hour).
+ * 201 => The client does not have rendering right.
+ * 202 => Client's session is dead. Client should do a config request before requesting a new job.
+ * 203 => Client's session have been disabled (usually because the client is sending broken frame). The client warms the end user and logout.
+ * something else => unknown error
+
+Answer with no error:
+
+
+
+
+
+
+
+
+
+
+
+=== Job validation ===
+
+Url: use the request type "validate-job" from the configuration answer.
+Parameter as GET or POST:
+ * job: Job's id
+ * frame: Job's frame number
+ * rendertime: Job's render time
+ * revision: Job's revision
+ * extras: Job's extra data
+ * file: Frame to validate as form-data post
+ * memoryused (optional): Max memory used for the render
+ * cores (optional): Number of cores used from the render
+Parameter as form-urlencoded:
+ * file: the frame to send
+
+Answer in case of error:
+
+where X:
+ * 0 => No error
+ * 300 => Missing parameter in request.
+ * 301 => Client generated a broken frame (usually an too old gpu who generated black frame).
+ * 302 => Client sent a file who is not an image.
+ * 303 => Failed to upload the image to the server.
+ * 304 => Client's session is disabled or dead.
+ * something else => unknown error
+
+=== Session heart bit ===
+
+Url: use the request type "keepmealive" from the configuration answer.
+Parameter as GET or POST:
+ * job: Id of the rendering job
+ * frame: Frame number of the rendering job
+ * extras: Extras data get on job request of the rendering job
+
+Answer:
+
+
+where X:
+ * 0 => No error.
+ * 400 => Stop this job (usually because the job has be cancelled on the server side).
+
+=== Error send ===
+
+Url: use the request type "error" from the configuration answer.
+Parameter as GET or POST:
+ * job: job's id
+ * frame: job's frame number
+ * extras: job's extra data
+ * rendertime (optional): job's frame number
+ * memoryused (optional): max memory used for the render
+Parameter as form-urlencoded:
+ * file: the error log to send
+
+
+Answer:
+
+
+where X:
+ * 0 => No error
+
diff --git a/src/com/sheepit/client/Client.java b/src/com/sheepit/client/Client.java
new file mode 100644
index 0000000..2d8864e
--- /dev/null
+++ b/src/com/sheepit/client/Client.java
@@ -0,0 +1,890 @@
+/*
+ * Copyright (C) 2010-2014 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.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+
+import com.sheepit.client.Error.ServerCode;
+import com.sheepit.client.Error.Type;
+import com.sheepit.client.exception.FermeException;
+import com.sheepit.client.exception.FermeExceptionNoRightToRender;
+import com.sheepit.client.exception.FermeExceptionNoSession;
+import com.sheepit.client.exception.FermeExceptionSessionDisabled;
+import com.sheepit.client.os.OS;
+
+public class Client {
+ public static final String UPDATE_METHOD_BY_LINE_NUMBER = "linenumber";
+ public static final String UPDATE_METHOD_BY_REMAINING_TIME = "remainingtime";
+
+ private Gui gui;
+ private Server server;
+ private Configuration config;
+ private Log log;
+ private Job renderingJob;
+ private BlockingQueue jobsToValidate;
+ private boolean isValidatingJob;
+
+ private boolean disableErrorSending;
+ private boolean running;
+
+ public Client(Gui gui_, Configuration config, String url_) {
+ this.config = config;
+ this.server = new Server(url_, this.config, this);
+ this.log = Log.getInstance(this.config);
+ this.gui = gui_;
+ this.renderingJob = null;
+ this.jobsToValidate = new ArrayBlockingQueue(1024);
+ this.isValidatingJob = false;
+
+ this.disableErrorSending = false;
+ this.running = true;
+ }
+
+ public String toString() {
+ return String.format("Client (config %s, server %s)", this.config, this.server);
+ }
+
+ public Job getRenderingJob() {
+ return this.renderingJob;
+ }
+
+ public Gui getGui() {
+ return this.gui;
+ }
+
+ public Configuration getConfiguration() {
+ return this.config;
+ }
+
+ public int run() {
+ int step;
+ try {
+ step = this.log.newCheckPoint();
+ this.gui.status("Starting");
+
+ this.config.cleanWorkingDirectory();
+
+ Error.Type ret;
+ ret = this.server.getConfiguration();
+
+ if (ret != Error.Type.OK) {
+ this.gui.error(Error.humainString(ret));
+ if (ret != Error.Type.AUTHENTICATION_FAILED) {
+ Log.printCheckPoint(step);
+ }
+ return -1;
+ }
+
+ this.server.start(); // for staying alive
+
+ // create a thread who will send the frame
+ Runnable runnable_sender = new Runnable() {
+ public void run() {
+ senderLoop();
+ }
+ };
+ Thread thread_sender = new Thread(runnable_sender);
+ thread_sender.start();
+
+ this.renderingJob = null;
+
+ while (this.running == true) {
+ step = this.log.newCheckPoint();
+ try {
+ Calendar next_request = this.nextJobRequest();
+ if (next_request != null) {
+ // wait
+ Date now = new Date();
+ this.gui.status(String.format("Waiting until %tR before requesting job", next_request));
+ try {
+ Thread.sleep(next_request.getTimeInMillis() - now.getTime());
+ }
+ catch (InterruptedException e3) {
+
+ }
+ catch (IllegalArgumentException e3) {
+ this.log.error("Client::run sleepA failed " + e3);
+ }
+ }
+ this.gui.status("Requesting Job");
+ this.renderingJob = this.server.requestJob();
+ }
+ catch (FermeExceptionNoRightToRender e) {
+ this.gui.error("User does not enough right to render scene");
+ return -2;
+ }
+ catch (FermeExceptionSessionDisabled e) {
+ this.gui.error(Error.humainString(Error.Type.SESSION_DISABLED));
+ // should wait forever to actually display the message to the user
+ while (true) {
+ try {
+ Thread.sleep(100000);
+ }
+ catch (InterruptedException e1) {
+ }
+ }
+ }
+ catch (FermeExceptionNoSession e) {
+ // User have no session need to re-authenticate
+
+ ret = this.server.getConfiguration();
+ if (ret != Error.Type.OK) {
+ this.renderingJob = null;
+ }
+ else {
+ try {
+ Calendar next_request = this.nextJobRequest();
+ if (next_request != null) {
+ // wait
+ Date now = new Date();
+ this.gui.status(String.format("Waiting until %tR before requesting job", next_request));
+ try {
+ Thread.sleep(next_request.getTimeInMillis() - now.getTime());
+ }
+ catch (InterruptedException e3) {
+
+ }
+ catch (IllegalArgumentException e3) {
+ this.log.error("Client::run sleepB failed " + e3);
+ }
+ }
+ this.gui.status("Requesting Job");
+ this.renderingJob = this.server.requestJob();
+ }
+ catch (FermeException e1) {
+ this.renderingJob = null;
+ }
+ }
+ }
+ catch (FermeException e) {
+ this.gui.error("Client::renderingManagement exception requestJob (1) " + e.getMessage());
+ this.sendError(step);
+ continue;
+ }
+
+ if (this.renderingJob == null) { // no job
+ int time_sleep = 1000 * 60 * 15;
+ Date wakeup_time = new Date(new Date().getTime() + time_sleep);
+ this.gui.status(String.format("No job available. Sleeping for 15 minutes (will wake up at ~%tR)", wakeup_time));
+ this.gui.framesRemaining(0);
+ int time_slept = 0;
+ while (time_slept < time_sleep && this.running == true) {
+ try {
+ Thread.sleep(5000);
+ }
+ catch (InterruptedException e) {
+ return -3;
+ }
+ time_slept += 5000;
+ }
+ continue; // go back to ask job
+ }
+
+ this.log.debug("Got work to do id: " + this.renderingJob.getId() + " frame: " + this.renderingJob.getFrameNumber());
+
+ ret = this.work(this.renderingJob);
+ if (ret != Error.Type.OK) {
+ Job frame_to_reset = this.renderingJob; // copie it because the sendError will take ~5min to execute
+ this.renderingJob = null;
+ this.gui.error(Error.humainString(ret));
+ this.sendError(step, frame_to_reset, ret);
+ this.log.removeCheckPoint(step);
+ continue;
+ }
+
+ if (this.renderingJob.simultaneousUploadIsAllowed() == false) { // power or compute_method job, need to upload right away
+ ret = confirmJob(this.renderingJob);
+ if (ret != Error.Type.OK) {
+ gui.error("Client::renderingManagement problem with confirmJob (returned " + ret + ")");
+ sendError(step);
+ }
+ }
+ else {
+ this.jobsToValidate.add(this.renderingJob);
+ this.renderingJob = null;
+ }
+
+ while (this.shouldWaitBeforeRender() == true) {
+ try {
+ Thread.sleep(4000); // wait a little bit
+ }
+ catch (InterruptedException e3) {
+ }
+ }
+ this.log.removeCheckPoint(step);
+ }
+
+ // not running but maybe still sending frame
+ while (this.jobsToValidate.size() > 0) {
+ try {
+ Thread.sleep(2300); // wait a little bit
+ }
+ catch (InterruptedException e3) {
+ }
+ }
+ }
+ catch (Exception e1) {
+ // no exception should be raise to actual launcher (applet or standalone)
+ return -99; // the this.stop will be done after the return of this.run()
+ }
+
+ return 0;
+ }
+
+ public synchronized int stop() {
+ System.out.println("Client::stop");
+ this.running = false;
+ this.disableErrorSending = true;
+
+ if (this.renderingJob != null) {
+ if (this.renderingJob.getProcess() != null) {
+ OS.getOS().kill(this.renderingJob.getProcess());
+ }
+ }
+
+ // this.config.workingDirectory.delete();
+ this.config.removeWorkingDirectory();
+
+ if (this.server == null) {
+ return 0;
+ }
+
+ try {
+ this.server.HTTPRequest(this.server.getPage("logout"));
+ }
+ catch (IOException e) {
+ // nothing to do: if the logout failed that's ok
+ }
+ this.server.interrupt();
+ try {
+ this.server.join();
+ }
+ catch (InterruptedException e) {
+ }
+
+ this.server = null;
+
+ this.gui.stop();
+ return 0;
+ }
+
+ public void askForStop() {
+ System.out.println("Client::askForStop");
+ this.running = false;
+ }
+
+ public int senderLoop() {
+ int step = log.newCheckPoint();
+ Error.Type ret;
+ while (true) {
+ Job job_to_send;
+ try {
+ job_to_send = (Job) jobsToValidate.take();
+ this.log.debug("will validate " + job_to_send);
+ //gui.status("Sending frame");
+ ret = confirmJob(job_to_send);
+ if (ret != Error.Type.OK) {
+ this.gui.error(Error.humainString(ret));
+ sendError(step);
+ }
+ else {
+ gui.AddFrameRendered();
+ }
+ }
+ catch (InterruptedException e) {
+ }
+ }
+ }
+
+ protected void sendError(int step_) {
+ this.sendError(step_, null, null);
+ }
+
+ protected void sendError(int step_, Job job_to_reset_, Error.Type error) {
+ if (this.disableErrorSending) {
+ this.log.debug("Error sending is disable, do not send log");
+ return;
+ }
+
+ try {
+ File temp_file = File.createTempFile("farm_", "");
+ temp_file.createNewFile();
+ temp_file.deleteOnExit();
+ FileOutputStream writer = new FileOutputStream(temp_file);
+
+ ArrayList logs = this.log.getForCheckPoint(step_);
+ for (String line : logs) {
+ writer.write(line.getBytes());
+ writer.write('\n');
+ }
+
+ writer.close();
+ String args = "";
+ if (job_to_reset_ != null) {
+ args = "?frame=" + job_to_reset_.getFrameNumber() + "&job=" + job_to_reset_.getId() + "&render_time=" + job_to_reset_.getRenderDuration();
+ if (job_to_reset_.getExtras() != null && job_to_reset_.getExtras().length() > 0) {
+ args += "&extras=" + job_to_reset_.getExtras();
+ }
+ }
+ this.server.HTTPSendFile(this.server.getPage("error") + args, temp_file.getAbsolutePath());
+ temp_file.delete();
+ }
+ catch (Exception e1) {
+ e1.printStackTrace();
+ // no exception should be raise to actual launcher (applet or standalone)
+ }
+
+ if (error != null && error == Error.Type.RENDERER_CRASHED) {
+ // do nothing, we can ask for a job right away
+ }
+ else {
+ try {
+ Thread.sleep(300000); // sleeping for 5min
+ }
+ catch (InterruptedException e) {
+ }
+ }
+ }
+
+ /**
+ *
+ * @return the date of the next request, or null is there is not delay (null <=> now)
+ */
+ public Calendar nextJobRequest() {
+ if (this.config.requestTime == null) {
+ return null;
+ }
+ else {
+ Calendar next = null;
+ Calendar now = Calendar.getInstance();
+ for (Pair interval : this.config.requestTime) {
+ Calendar start = (Calendar) now.clone();
+ Calendar end = (Calendar) now.clone();
+ start.set(Calendar.SECOND, 00);
+ start.set(Calendar.MINUTE, interval.first.get(Calendar.MINUTE));
+ start.set(Calendar.HOUR_OF_DAY, interval.first.get(Calendar.HOUR_OF_DAY));
+
+ end.set(Calendar.SECOND, 59);
+ end.set(Calendar.MINUTE, interval.second.get(Calendar.MINUTE));
+ end.set(Calendar.HOUR_OF_DAY, interval.second.get(Calendar.HOUR_OF_DAY));
+
+ if (start.before(now) && now.before(end)) {
+ return null;
+ }
+ if (start.after(now)) {
+ if (next == null) {
+ next = start;
+ }
+ else {
+ if (start.before(next)) {
+ next = start;
+ }
+ }
+ }
+ }
+
+ return next;
+ }
+ }
+
+ public Error.Type work(Job ajob) {
+ if (ajob.workeable() == false) {
+ this.log.error("Client::work The received job is not workeable");
+ return Error.Type.WRONG_CONFIGURATION;
+ }
+ int ret;
+
+ ret = this.downloadExecutable(ajob);
+ if (ret != 0) {
+ this.log.error("Client::work problem with downloadExecutable (ret " + ret + ")");
+ return Error.Type.DOWNLOAD_FILE;
+ }
+
+ ret = this.downloadSceneFile(ajob);
+ if (ret != 0) {
+ this.log.error("Client::work problem with downloadSceneFile (ret " + ret + ")");
+ return Error.Type.DOWNLOAD_FILE;
+ }
+
+ ret = this.prepareWorkeableDirectory(ajob); // decompress renderer and scene archives
+ if (ret != 0) {
+ this.log.error("Client::work problem with this.prepareWorkeableDirectory (ret " + ret + ")");
+ return Error.Type.CAN_NOT_CREATE_DIRECTORY;
+ }
+
+ File scene_file = new File(ajob.getScenePath());
+ File renderer_file = new File(ajob.getRendererPath());
+
+ if (scene_file.exists() == false) {
+ this.log.error("Client::work job prepration failed (scene file '" + scene_file.getAbsolutePath() + "' does not exist)");
+ return Error.Type.MISSING_SCENE;
+ }
+
+ if (renderer_file.exists() == false) {
+ this.log.error("Client::work job prepration failed (renderer file '" + renderer_file.getAbsolutePath() + "' does not exist)");
+ return Error.Type.MISSING_RENDER;
+ }
+
+ Error.Type err = this.runRenderer(ajob);
+ if (err != Error.Type.OK) {
+ this.log.error("Client::work problem with runRenderer (ret " + err + ")");
+ return err;
+ }
+
+ return Error.Type.OK;
+ }
+
+ protected Error.Type runRenderer(Job ajob) {
+ this.gui.status("Rendering");
+ String core_script = "";
+ if (ajob.getUseGPU() && this.config.getGPUDevice() != null) {
+ core_script += "import bpy\n" + "bpy.context.user_preferences.system.compute_device_type = \"CUDA\"" + "\n" + "bpy.context.scene.cycles.device = \"GPU\"" + "\n" + "bpy.context.user_preferences.system.compute_device = \"" + this.config.getGPUDevice().getCudaName() + "\"\n" + "bpy.context.scene.render.tile_x = 256" + "\n" + "bpy.context.scene.render.tile_y = 256" + "\n";
+ }
+ else {
+ core_script += "import bpy\n" + "bpy.context.user_preferences.system.compute_device_type = \"NONE\"" + "\n" + "bpy.context.scene.cycles.device = \"CPU\"" + "\n" + "bpy.context.scene.render.tile_x = 32" + "\n" + "bpy.context.scene.render.tile_y = 32" + "\n";
+ }
+ File script_file = null;
+ String command1[] = ajob.getRenderCommand().split(" ");
+ int size_command = command1.length + 2; // + 2 for script
+
+ if (this.config.getNbCores() > 0) { // user have specified something
+ size_command += 2;
+ }
+
+ String[] command = new String[size_command];
+
+ int index = 0;
+ for (int i = 0; i < command1.length; i++) {
+ if (command1[i].equals(".c")) {
+ command[index] = ajob.getScenePath();
+ index += 1;
+ command[index] = "-P";
+ index += 1;
+
+ try {
+ script_file = File.createTempFile("script_", "", this.config.workingDirectory);
+ File file = new File(script_file.getAbsolutePath());
+ FileWriter txt;
+ txt = new FileWriter(file);
+
+ PrintWriter out = new PrintWriter(txt);
+ out.write(ajob.getScript());
+ out.write("\n");
+ out.write(core_script); // GPU part
+ out.write("\n"); // GPU part
+ out.close();
+
+ command[index] = script_file.getAbsolutePath();
+ }
+ catch (IOException e) {
+ return Error.Type.UNKNOWN;
+ }
+ script_file.deleteOnExit();
+ }
+ else if (command1[i].equals(".e")) {
+ command[index] = ajob.getRendererPath();
+ // the number of cores have to be put after the binary and before the scene arg
+ if (this.config.getNbCores() > 0) {
+ index += 1;
+ command[index] = "-t";
+ index += 1;
+ command[index] = Integer.toString(this.config.getNbCores());
+ //index += 1; // do not do it, it will be done at the end of the loop
+ }
+ }
+ else if (command1[i].equals(".o")) {
+ command[index] = this.config.workingDirectory.getAbsolutePath() + File.separator + ajob.getPrefixOutputImage();
+ }
+ else if (command1[i].equals(".f")) {
+ command[index] = ajob.getFrameNumber();
+ }
+ else {
+ command[index] = command1[i];
+ }
+ index += 1;
+ }
+
+ long rending_start = new Date().getTime();
+
+ int nb_lines = 0;
+ try {
+ String line;
+ this.log.debug(Arrays.toString(command));
+ OS os = OS.getOS();
+ ajob.setProcess(os.exec(command));
+ BufferedReader input = new BufferedReader(new InputStreamReader(ajob.getProcess().getInputStream()));
+
+ long last_update_status = 0;
+ this.log.debug("renderer output");
+ while ((line = input.readLine()) != null) {
+ nb_lines++;
+ this.updateRenderingMemoryPeak(line, ajob);
+
+ this.log.debug(line);
+ if ((new Date().getTime() - last_update_status) > 2000) { // only call the update every two seconds
+ this.updateRenderingStatus(line, nb_lines, ajob);
+ last_update_status = new Date().getTime();
+ }
+ }
+ input.close();
+ this.log.debug("end of rendering");
+ }
+ catch (Exception err) {
+ StringWriter sw = new StringWriter();
+ err.printStackTrace(new PrintWriter(sw));
+ this.log.error("Client:runRenderer exception(A) " + err + " stacktrace " + sw.toString());
+ return Error.Type.FAILED_TO_EXECUTE;
+ }
+
+ long rending_end = new Date().getTime();
+
+ if (script_file != null) {
+ script_file.delete();
+ }
+
+ ajob.setRenderDuration((int) ((rending_end - rending_start) / 1000 + 1)); // render time is in seconds but the getTime is in millisecond
+
+ ajob.setMaxOutputNbLines(nb_lines);
+ int exit_value = 0;
+ try {
+ exit_value = ajob.getProcess().exitValue();
+ }
+ catch (IllegalThreadStateException e) {
+ // the process is not finished yet
+ exit_value = 0;
+ }
+ catch (Exception e) {
+ // actually is for java.io.IOException: GetExitCodeProcess error=6, The handle is invalid
+ // it was not declared throwable
+
+ // the process is not finished yet
+ exit_value = 0;
+ }
+
+ ajob.setProcess(null);
+
+ // find the picture file
+ final String namefile_without_extension = ajob.getPrefixOutputImage() + ajob.getFrameNumber();
+
+ FilenameFilter textFilter = new FilenameFilter() {
+ public boolean accept(File dir, String name) {
+ return name.startsWith(namefile_without_extension);
+ }
+ };
+
+ File[] files = this.config.workingDirectory.listFiles(textFilter);
+
+ if (files.length == 0) {
+ this.log.error("Client::runRenderer no picture file found (after finished render (namefile_without_extension " + namefile_without_extension + ")");
+
+ String basename = "";
+ try {
+ basename = ajob.getPath().substring(0, ajob.getPath().lastIndexOf('.'));
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+ File crash_file = new File(this.config.workingDirectory + File.separator + basename + ".crash.txt");
+ if (crash_file.exists()) {
+ this.log.error("Client::runRenderer crash file found => the renderer crashed");
+ crash_file.delete();
+ return Error.Type.RENDERER_CRASHED;
+ }
+
+ if (exit_value == 127 && ajob.getRenderDuration() < 10) {
+ this.log.error("Client::runRenderer renderer return 127 and render time took " + ajob.getRenderDuration() + "s, mostly missing libraries");
+ return Error.Type.RENDERER_MISSING_LIBRARIES;
+ }
+
+ return Error.Type.NOOUTPUTFILE;
+ }
+ else {
+ ajob.setOutputImagePath(files[0].getAbsolutePath());
+ this.log.debug("Client::runRenderer pictureFilename: '" + ajob.getOutputImagePath() + "'");
+ }
+
+ File scene_dir = new File(ajob.getSceneDirectory());
+ long date_modification_scene_directory = (long) Utils.lastModificationTime(scene_dir);
+ if (date_modification_scene_directory > rending_start) {
+ scene_dir.delete();
+ }
+
+ this.gui.status(String.format("Frame rendered in %dmin%ds", ajob.getRenderDuration() / 60, ajob.getRenderDuration() % 60));
+
+ return Error.Type.OK;
+ }
+
+ protected int downloadSceneFile(Job ajob_) {
+ this.gui.status("Downloading scene");
+
+ String achive_local_path = ajob_.getSceneArchivePath();
+
+ File renderer_achive_local_path_file = new File(achive_local_path);
+
+ if (renderer_achive_local_path_file.exists()) {
+ // the archive have been already downloaded
+ }
+ else {
+ // we must download the archive
+ int ret;
+ String real_url;
+ real_url = String.format("%s?type=job&job=%s&revision=%s", this.server.getPage("download-archive"), ajob_.getId(), ajob_.getRevision());
+ ret = this.server.HTTPGetFile(real_url, achive_local_path, this.gui, "Downloading scene %s %%");
+ if (ret != 0) {
+ this.gui.error("Client::downloadSceneFile problem with Utils.DownloadFile returned " + ret);
+ return -1;
+ }
+
+ String md5_local;
+ md5_local = Utils.md5(achive_local_path);
+
+ if (md5_local.equals(ajob_.getSceneMD5()) == false) {
+ System.err.println("md5 of the downloaded file and the local file are not the same (local '" + md5_local + "' scene: '" + ajob_.getSceneMD5() + "')");
+ this.log.error("Client::downloadSceneFile mismatch on md5 local: '" + md5_local + "' server: '" + ajob_.getSceneMD5() + "'");
+ // md5 of the file downloaded and the file excepted is not the same
+ return -2;
+ }
+ }
+ return 0;
+ }
+
+ protected int downloadExecutable(Job ajob) {
+ this.gui.status("Downloading renderer");
+ String real_url = new String();
+ real_url = String.format("%s?type=binary&job=%s", this.server.getPage("download-archive"), ajob.getId());
+
+ // we have the MD5 of the renderer archive
+ String renderer_achive_local_path = ajob.getRendererArchivePath();
+ File renderer_achive_local_path_file = new File(renderer_achive_local_path);
+
+ if (renderer_achive_local_path_file.exists()) {
+ // the archive have been already downloaded
+ }
+ else {
+ // we must download the archive
+ int ret;
+ ret = this.server.HTTPGetFile(real_url, renderer_achive_local_path, this.gui, "Downloading renderer %s %%");
+ if (ret != 0) {
+ this.gui.error("Client::downloadExecutable problem with Utils.DownloadFile returned " + ret);
+ return -9;
+ }
+ }
+
+ String md5_local;
+ md5_local = Utils.md5(renderer_achive_local_path);
+
+ if (md5_local.equals(ajob.getRenderMd5()) == false) {
+ this.log.error("Client::downloadExecutable mismatch on md5 local: '" + md5_local + "' server: '" + ajob.getRenderMd5() + "'");
+ // md5 of the file downloaded and the file excepted is not the same
+ return -10;
+ }
+ return 0;
+ }
+
+ protected int prepareWorkeableDirectory(Job ajob) {
+ int ret;
+ String renderer_archive = ajob.getRendererArchivePath();
+ String renderer_path = ajob.getRendererDirectory();
+ File renderer_path_file = new File(renderer_path);
+
+ if (renderer_path_file.exists()) {
+ // Directory already exists -> do nothing
+ }
+ else {
+ // we create the directory
+ renderer_path_file.mkdir();
+
+ // unzip the archive
+ ret = Utils.unzipFileIntoDirectory(renderer_archive, renderer_path);
+ if (ret != 0) {
+ this.gui.error("Client::prepareWorkeableDirectory, error with Utils.unzipFileIntoDirectory of the renderer (returned " + ret + ")");
+ return -1;
+ }
+ }
+
+ String scene_archive = ajob.getSceneArchivePath();
+ String scene_path = ajob.getSceneDirectory();
+ File scene_path_file = new File(scene_path);
+
+ if (scene_path_file.exists()) {
+ // Directory already exists -> do nothing
+ }
+ else {
+ // we create the directory
+ scene_path_file.mkdir();
+
+ // unzip the archive
+ ret = Utils.unzipFileIntoDirectory(scene_archive, scene_path);
+ if (ret != 0) {
+ this.gui.error("Client::prepareWorkeableDirectory, error with Utils.unzipFileIntoDirectory of the scene (returned " + ret + ")");
+ return -2;
+ }
+ }
+
+ return 0;
+ }
+
+ protected Error.Type confirmJob(Job ajob) {
+ String extras_config = "";
+ if (this.config.getNbCores() > 0) {
+ extras_config = "&cores=" + this.config.getNbCores();
+ }
+
+ String url_real = String.format("%s?job=%s&frame=%s&rendertime=%d&revision=%s&memoryused=%s&extras=%s%s", this.server.getPage("validate-job"), ajob.getId(), ajob.getFrameNumber(), ajob.getRenderDuration(), ajob.getRevision(), ajob.getMemoryUsed(), ajob.getExtras(), extras_config);
+
+ this.isValidatingJob = true;
+ int nb_try = 1;
+ int max_try = 3;
+ ServerCode ret = ServerCode.UNKNOWN;
+ while (nb_try < max_try && ret != ServerCode.OK) {
+ ret = this.server.HTTPSendFile(url_real, ajob.getOutputImagePath());
+ switch (ret) {
+ case OK:
+ // no issue, exit the loop
+ nb_try = max_try;
+ break;
+
+ case JOB_VALIDATION_ERROR_SESSION_DISABLED:
+ case JOB_VALIDATION_ERROR_BROKEN_MACHINE:
+ return Type.SESSION_DISABLED;
+
+ case JOB_VALIDATION_ERROR_MISSING_PARAMETER:
+ // no point to retry the request
+ return Error.Type.UNKNOWN;
+
+ default:
+ // do nothing, try to do a request on the next loop
+ break;
+ }
+
+ nb_try++;
+ if (ret != ServerCode.OK && nb_try < max_try) {
+ try {
+ this.log.debug("Sleep for 32s before trying to re-upload the frame");
+ Thread.sleep(32000);
+ }
+ catch (InterruptedException e) {
+ return Error.Type.UNKNOWN;
+ }
+ }
+ }
+ this.isValidatingJob = false;
+
+ // we can remove the frame file
+ File frame = new File(ajob.getOutputImagePath());
+ frame.delete();
+ ajob.setOutputImagePath(null);
+
+ this.isValidatingJob = false;
+ return Error.Type.OK;
+ }
+
+ protected boolean shouldWaitBeforeRender() {
+ int concurrent_job = this.jobsToValidate.size();
+ if (this.isValidatingJob) {
+ concurrent_job++;
+ }
+ return (concurrent_job >= this.config.maxUploadingJob());
+ }
+
+ protected void updateRenderingStatus(String line, int current_number_of_lines, Job ajob) {
+ if (ajob.getUpdateRenderingStatusMethod() != null && ajob.getUpdateRenderingStatusMethod().equals(Client.UPDATE_METHOD_BY_LINE_NUMBER) && ajob.getMaxOutputNbLines() > 0) {
+ this.gui.status(String.format("Rendering %s %%", (int) (100.0 * current_number_of_lines / ajob.getMaxOutputNbLines())));
+ }
+ else if (ajob.getUpdateRenderingStatusMethod() == null || ajob.getUpdateRenderingStatusMethod().equals(Client.UPDATE_METHOD_BY_REMAINING_TIME)) {
+ String search_remaining = "remaining:";
+ int index = line.toLowerCase().indexOf(search_remaining);
+ if (index != -1) {
+ String buf1 = line.substring(index + search_remaining.length());
+ index = buf1.indexOf(" ");
+
+ if (index != -1) {
+ String remaining_time = buf1.substring(0, index).trim();
+ int last_index = remaining_time.lastIndexOf('.'); //format 00:00:00.00 (hr:min:sec)
+ if (last_index > 0) {
+ remaining_time = remaining_time.substring(0, last_index);
+ }
+
+ try {
+ DateFormat date_parse_minute = new SimpleDateFormat("m:s");
+ DateFormat date_parse_hour = new SimpleDateFormat("h:m:s");
+ SimpleDateFormat date_output_minute = new SimpleDateFormat("mm'min'ss");
+ SimpleDateFormat date_output_hour = new SimpleDateFormat("HH'h'mm'min'ss");
+ DateFormat date_parse = date_parse_minute;
+ DateFormat date_output = date_output_minute;
+ if (remaining_time.split(":").length > 2) {
+ date_parse = date_parse_hour;
+ date_output = date_output_hour;
+ }
+ Date d1 = date_parse.parse(remaining_time);
+ this.gui.status(String.format("Rendering (remaining %s)", date_output.format(d1)));
+ }
+ catch (ParseException err) {
+ this.log.error("Client::updateRenderingStatus ParseException " + err);
+ }
+ }
+ }
+ }
+ }
+
+ protected void updateRenderingMemoryPeak(String line, Job ajob) {
+ String[] elements = line.toLowerCase().split("(peak)");
+
+ for (String element : elements) {
+ if (element.isEmpty() == false && element.charAt(0) == ' ') {
+ int end = element.indexOf(')');
+ if (end > 0) {
+ long mem = Utils.parseNumber(element.substring(1, end).trim());
+ if (mem > ajob.getMemoryUsed()) {
+ ajob.setMemoryUsed(mem);
+ }
+ }
+ }
+ else {
+ if (element.isEmpty() == false && element.charAt(0) == ':') {
+ int end = element.indexOf('|');
+ if (end > 0) {
+ long mem = Utils.parseNumber(element.substring(1, end).trim());
+ if (mem > ajob.getMemoryUsed()) {
+ ajob.setMemoryUsed(mem);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/sheepit/client/Configuration.java b/src/com/sheepit/client/Configuration.java
new file mode 100644
index 0000000..90c7287
--- /dev/null
+++ b/src/com/sheepit/client/Configuration.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2010-2014 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.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.sheepit.client.hardware.gpu.GPUDevice;
+
+public class Configuration {
+ public enum ComputeType {
+ CPU_GPU, CPU_ONLY, GPU_ONLY
+ }; // acccept job for ...
+
+ public File workingDirectory;
+ public File storageDirectory; // for permanent storage (binary archive)
+ public boolean userSpecifiedACacheDir;
+ public String static_exeDirName;
+ private String login;
+ private String password;
+ private int maxUploadingJob;
+ private int nbCores;
+ private ComputeType computeMethod;
+ private GPUDevice GPUDevice;
+ private boolean printLog;
+ public List> requestTime;
+ private String extras;
+
+ public Configuration(File cache_dir_, String login_, String password_) {
+ this.login = login_;
+ this.password = password_;
+ this.static_exeDirName = "exe";
+ this.maxUploadingJob = 1;
+ this.nbCores = -1; // ie not set
+ this.computeMethod = ComputeType.CPU_GPU;
+ this.GPUDevice = null;
+ this.userSpecifiedACacheDir = false;
+ this.workingDirectory = null;
+ this.storageDirectory = null;
+ this.setCacheDir(cache_dir_);
+ this.printLog = false;
+ this.requestTime = null;
+ this.extras = "";
+ }
+
+ public String toString() {
+ return String.format("Configuration (workingDirectory '%s')", this.workingDirectory.getAbsolutePath());
+ }
+
+ public String login() {
+ return this.login;
+ }
+
+ public String password() {
+ return this.password;
+ }
+
+ public int maxUploadingJob() {
+ return this.maxUploadingJob;
+ }
+
+ public GPUDevice getGPUDevice() {
+ return this.GPUDevice;
+ }
+
+ public void setMaxUploadingJob(int max) {
+ this.maxUploadingJob = max;
+ }
+
+ public void setUseNbCores(int nbcores) {
+ this.nbCores = nbcores;
+ }
+
+ public int getNbCores() {
+ return this.nbCores;
+ }
+
+ public void setPrintLog(boolean val) {
+ this.printLog = val;
+ }
+
+ public boolean getPrintLog() {
+ return this.printLog;
+ }
+
+ public int computeMethodToInt() {
+ return this.computeMethod.ordinal();
+ }
+
+ public ComputeType getComputeMethod() {
+ return this.computeMethod;
+ }
+
+ public void setUseGPU(GPUDevice device) {
+ this.GPUDevice = device;
+ if (device == null) {
+ this.computeMethod = ComputeType.CPU_GPU;
+ }
+ }
+
+ public void setComputeMethod(ComputeType meth) {
+ this.computeMethod = meth;
+ }
+
+ public void setCacheDir(File cache_dir_) {
+ removeWorkingDirectory();
+ if (cache_dir_ == null) {
+ this.userSpecifiedACacheDir = false;
+ try {
+ this.workingDirectory = File.createTempFile("farm_", "");
+ this.workingDirectory.createNewFile(); // hoho...
+ this.workingDirectory.delete(); // hoho
+ this.workingDirectory.mkdir();
+ this.workingDirectory.deleteOnExit();
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ else {
+ this.userSpecifiedACacheDir = true;
+ this.workingDirectory = cache_dir_;
+ }
+
+ }
+
+ public void setStorageDir(File dir) {
+ if (dir != null) {
+ if (dir.exists() == false) {
+ dir.mkdir();
+ }
+ this.storageDirectory = dir;
+ }
+ }
+
+ public File getStorageDir() {
+ if (this.storageDirectory == null) {
+ return this.workingDirectory;
+ }
+ else {
+ return this.storageDirectory;
+ }
+ }
+
+ public void setExtras(String str) {
+ this.extras = str;
+ }
+
+ public String getExtras() {
+ return this.extras;
+ }
+
+ public void cleanWorkingDirectory() {
+ this.cleanDirectory(this.workingDirectory);
+ this.cleanDirectory(this.storageDirectory);
+ }
+
+ public boolean cleanDirectory(File dir) {
+ if (dir == null) {
+ return false;
+ }
+
+ File[] files = dir.listFiles();
+ if (files != null) {
+ for (int i = 0; i < files.length; i++) {
+ File file = files[i];
+ if (file.isDirectory()) {
+ Utils.delete(file);
+ }
+ else {
+ try {
+ String extension = file.getName().substring(file.getName().lastIndexOf('.')).toLowerCase();
+ String name = file.getName().substring(0, file.getName().length() - 1 * extension.length());
+ if (extension.equals(".zip")) {
+ // check if the md5 of the file is ok
+ String md5_local = Utils.md5(file.getAbsolutePath());
+
+ if (md5_local.equals(name) == false) {
+ System.err.println("cleanDirectory find an partial file => remove (" + file.getAbsolutePath() + ")");
+ file.delete();
+ }
+
+ // TODO: remove old one
+ }
+ else {
+ file.delete();
+ }
+ }
+ catch (StringIndexOutOfBoundsException e) { // because the file does not have an . in his path
+ file.delete();
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ public void removeWorkingDirectory() {
+ if (this.userSpecifiedACacheDir == true) {
+ this.cleanWorkingDirectory();
+ }
+ else {
+ Utils.delete(this.workingDirectory);
+ }
+ }
+
+ public List getLocalCacheFiles() {
+ List files_local = new LinkedList();
+ List files = new LinkedList();
+ if (this.workingDirectory != null) {
+ files.addAll(Arrays.asList(this.workingDirectory.listFiles()));
+ }
+ if (this.storageDirectory != null) {
+ files.addAll(Arrays.asList(this.storageDirectory.listFiles()));
+ }
+
+ for (File file : files) {
+ if (file.isFile()) {
+ try {
+ String extension = file.getName().substring(file.getName().lastIndexOf('.')).toLowerCase();
+ String name = file.getName().substring(0, file.getName().length() - 1 * extension.length());
+ if (extension.equals(".zip")) {
+ // check if the md5 of the file is ok
+ String md5_local = Utils.md5(file.getAbsolutePath());
+
+ if (md5_local.equals(name)) {
+ files_local.add(file);
+ }
+ }
+ }
+ catch (StringIndexOutOfBoundsException e) { // because the file does not have an . his path
+ }
+ }
+ }
+ return files_local;
+ }
+
+ public String getJarVersion() {
+ String versionPath = "/VERSION";
+
+ InputStream versionStream = Client.class.getResourceAsStream(versionPath);
+ if (versionStream == null) {
+ System.err.println("Configuration::getJarVersion Failed to get version file");
+ return "";
+ }
+
+ try {
+ InputStreamReader reader = new InputStreamReader(versionStream);
+ BufferedReader in = new BufferedReader(reader);
+ String version = in.readLine();
+
+ return version;
+ }
+ catch (IOException ex) {
+ System.err.println("Configuration::getJarVersion error while reading manifest file (" + versionPath + "): " + ex.getMessage());
+ return "";
+ }
+ }
+}
diff --git a/src/com/sheepit/client/Error.java b/src/com/sheepit/client/Error.java
new file mode 100644
index 0000000..a2cae96
--- /dev/null
+++ b/src/com/sheepit/client/Error.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2013-2014 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;
+
+public class Error {
+ public enum Type {
+ OK,
+ WRONG_CONFIGURATION,
+ AUTHENTICATION_FAILED,
+ TOO_OLD_CLIENT,
+ SESSION_DISABLED,
+ MISSING_RENDER,
+ MISSING_SCENE,
+ NOOUTPUTFILE,
+ DOWNLOAD_FILE,
+ CAN_NOT_CREATE_DIRECTORY,
+ NETWORK_ISSUE, RENDERER_CRASHED,
+ RENDERER_MISSING_LIBRARIES,
+ FAILED_TO_EXECUTE,
+ UNKNOWN
+ };
+
+ public enum ServerCode {
+ OK(0),
+ UNKNOWN(999),
+
+ CONFIGURATION_ERROR_NO_CLIENT_VERSION_GIVEN(100),
+ CONFIGURATION_ERROR_CLIENT_TOO_OLD(101),
+ CONFIGURATION_ERROR_AUTH_FAILED(102),
+ CONFIGURATION_ERROR_WEB_SESSION_EXPIRATED(103),
+ CONFIGURATION_ERROR_MISSING_PARAMETER(104),
+
+ JOB_REQUEST_NOJOB(200),
+ JOB_REQUEST_ERROR_NO_RENDERING_RIGHT(201),
+ JOB_REQUEST_ERROR_DEAD_SESSION(202),
+ JOB_REQUEST_ERROR_SESSION_DISABLED(203),
+ JOB_REQUEST_ERROR_INTERNAL_ERROR(204),
+
+ JOB_VALIDATION_ERROR_MISSING_PARAMETER(300),
+ JOB_VALIDATION_ERROR_BROKEN_MACHINE(301), // in GPU the generated frame is black
+ JOB_VALIDATION_ERROR_FRAME_IS_NOT_IMAGE(302),
+ JOB_VALIDATION_ERROR_UPLOAD_FAILED(303),
+ JOB_VALIDATION_ERROR_SESSION_DISABLED(304), // missing heartbeat or broken machine
+
+ KEEPMEALIVE_STOP_RENDERING(400),
+
+ // internal error handling
+ ERROR_NO_ROOT(2),
+ ERROR_REQUEST_FAILED(5);
+
+ private final int id;
+
+ private ServerCode(int id) {
+ this.id = id;
+ }
+
+ public int getValue() {
+ return id;
+ }
+
+ public static ServerCode fromInt(int val) {
+ ServerCode[] As = ServerCode.values();
+ for (int i = 0; i < As.length; i++) {
+ if (As[i].getValue() == val) {
+ return As[i];
+ }
+ }
+ return ServerCode.UNKNOWN;
+ }
+ }
+
+ public static Type ServerCodeToType(ServerCode sc) {
+ switch (sc) {
+ case OK:
+ return Type.OK;
+ case UNKNOWN:
+ return Type.UNKNOWN;
+ case CONFIGURATION_ERROR_CLIENT_TOO_OLD:
+ return Type.TOO_OLD_CLIENT;
+ case CONFIGURATION_ERROR_AUTH_FAILED:
+ return Type.AUTHENTICATION_FAILED;
+
+ case CONFIGURATION_ERROR_NO_CLIENT_VERSION_GIVEN:
+ case CONFIGURATION_ERROR_WEB_SESSION_EXPIRATED:
+ return Type.WRONG_CONFIGURATION;
+
+ case JOB_REQUEST_ERROR_SESSION_DISABLED:
+ case JOB_VALIDATION_ERROR_SESSION_DISABLED:
+ return Type.SESSION_DISABLED;
+
+ default:
+ return Type.UNKNOWN;
+ }
+ }
+
+ public static String humainString(Type in) {
+ switch (in) {
+ case TOO_OLD_CLIENT:
+ return "This client is too old, you need to update it";
+ case AUTHENTICATION_FAILED:
+ return "Failed to authenticate, please check your login and password";
+ case NOOUTPUTFILE:
+ return "Renderer have generated no output file, it's mostly a wrong project configuration or your are missing required libraries. Will try an another project in few minutes.";
+ case RENDERER_CRASHED:
+ return "Renderer have crashed. It's mostly due to a bad project or not enough memory. There is nothing you can do about it. Will try an another project in few minutes.";
+ case RENDERER_MISSING_LIBRARIES:
+ return "Failed to launch runderer. Please check if you have necessary libraries installed and if you have enough free place in working directory.";
+ case SESSION_DISABLED:
+ return "The server have disabled your session. It's mostly because your client generate broken frame (gpu not compatible for example).";
+ default:
+ return in.toString();
+ }
+ }
+}
diff --git a/src/com/sheepit/client/Gui.java b/src/com/sheepit/client/Gui.java
new file mode 100644
index 0000000..b6916de
--- /dev/null
+++ b/src/com/sheepit/client/Gui.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010-2013 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;
+
+public interface Gui {
+ public abstract void start();
+
+ public abstract void stop();
+
+ public abstract void status(String msg_);
+
+ public void error(String err_);
+
+ public void AddFrameRendered();
+
+ public void framesRemaining(int nb_);
+}
diff --git a/src/com/sheepit/client/Job.java b/src/com/sheepit/client/Job.java
new file mode 100644
index 0000000..b954d20
--- /dev/null
+++ b/src/com/sheepit/client/Job.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2010-2014 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.io.File;
+
+public class Job {
+ private String numFrame;
+ private String sceneMD5;
+ private String rendererMD5;
+ private String id;
+ private String revision;
+ private String pictureFilename;
+ private String path; // path inside of the archive
+ private int renderDuration; // in second
+ private long memoryUsed; // in kB
+ private String rendererCommand;
+ private String script;
+ private int maxOutputNbLines;
+ private boolean useGPU;
+ private String extras;
+ private String updateRenderingStatusMethod;
+
+ private Process process;
+
+ private Configuration config;
+
+ public Job(Configuration config_, String id_, String frame_, String revision_, String path_, boolean use_gpu, String command_, String script_, String sceneMd5_, String rendererMd5_, String extras_) {
+ config = config_;
+ id = id_;
+ numFrame = frame_;
+ revision = revision_;
+ path = path_;
+ useGPU = use_gpu;
+ rendererCommand = command_;
+ sceneMD5 = sceneMd5_;
+ rendererMD5 = rendererMd5_;
+ extras = extras_;
+
+ pictureFilename = null;
+ renderDuration = 0;
+ memoryUsed = 0;
+ script = script_;
+ maxOutputNbLines = 0;
+ updateRenderingStatusMethod = null;
+ process = null;
+
+ }
+
+ public String toString() {
+ return String.format("Job (numFrame '%s' sceneMD5 '%s' rendererMD5 '%s' ID '%s' revision '%s' pictureFilename '%s' jobPath '%s' renderDuration '%s', memoryUsed %skB gpu %s extras '%s' updateRenderingStatusMethod '%s')", this.numFrame, this.sceneMD5, this.rendererMD5, this.id, this.revision, this.pictureFilename, this.path, this.renderDuration, this.memoryUsed, this.useGPU, this.extras, this.updateRenderingStatusMethod);
+ }
+
+ public boolean workeable() {
+ return true;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getFrameNumber() {
+ return numFrame;
+ }
+
+ public String getExtras() {
+ return extras;
+ }
+
+ public String getScript() {
+ return script;
+ }
+
+ public String getSceneMD5() {
+ return sceneMD5;
+ }
+
+ public String getRenderMd5() {
+ return rendererMD5;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public String getUpdateRenderingStatusMethod() {
+ return updateRenderingStatusMethod;
+ }
+
+ public int getMaxOutputNbLines() {
+ return maxOutputNbLines;
+ }
+
+ public void setProcess(Process val) {
+ process = val;
+ }
+
+ public Process getProcess() {
+ return process;
+ }
+
+ public long getMemoryUsed() {
+ return memoryUsed;
+ }
+
+ public void setMemoryUsed(long val) {
+ memoryUsed = val;
+ }
+
+ public int getRenderDuration() {
+ return renderDuration;
+ }
+
+ public void setRenderDuration(int val) {
+ renderDuration = val;
+ }
+
+ public void setMaxOutputNbLines(int val) {
+ maxOutputNbLines = val;
+ }
+
+ public String getRenderCommand() {
+ return rendererCommand;
+ }
+
+ public boolean getUseGPU() {
+ return useGPU;
+ }
+
+ public String getRevision() {
+ return revision;
+ }
+
+ public void setOutputImagePath(String path) {
+ pictureFilename = path;
+ }
+
+ public String getOutputImagePath() {
+ return pictureFilename;
+ }
+
+ public String getPrefixOutputImage() {
+ return id + "_";
+ }
+
+ public String getRendererDirectory() {
+ return config.workingDirectory.getAbsolutePath() + File.separator + rendererMD5;
+ }
+
+ public String getRendererPath() {
+ return getRendererDirectory() + File.separator + "rend.exe";
+ }
+
+ public String getRendererArchivePath() {
+ return config.getStorageDir().getAbsolutePath() + File.separator + rendererMD5 + ".zip";
+ }
+
+ public String getSceneDirectory() {
+ return config.workingDirectory.getAbsolutePath() + File.separator + sceneMD5;
+ }
+
+ public String getScenePath() {
+ return getSceneDirectory() + File.separator + this.path;
+ }
+
+ public String getSceneArchivePath() {
+ return config.workingDirectory.getAbsolutePath() + File.separator + sceneMD5 + ".zip";
+ }
+
+ public boolean simultaneousUploadIsAllowed() {
+ // id 0 is power project
+ // id 1 is compute method project
+ // they are made to check is the computer can do render
+ return id.equals("0") == false && id.equals("1") == false;
+ }
+}
diff --git a/src/com/sheepit/client/Log.java b/src/com/sheepit/client/Log.java
new file mode 100644
index 0000000..c10ada5
--- /dev/null
+++ b/src/com/sheepit/client/Log.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2011-2014 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.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+public class Log {
+ private static Log instance = null;
+
+ private Map> checkpoints = new HashMap>();;
+ private int lastCheckPoint;
+ private DateFormat dateFormat;
+
+ private boolean printStdOut;
+
+ private Log(boolean print_) {
+ this.printStdOut = print_;
+ this.lastCheckPoint = 0;
+ this.checkpoints.put(this.lastCheckPoint, new ArrayList());
+ this.dateFormat = new SimpleDateFormat("dd-MM kk:mm:ss");
+ }
+
+ public void debug(String msg_) {
+ this.append("debug", msg_);
+ }
+
+ public void info(String msg_) {
+ this.append("info", msg_);
+ }
+
+ public void error(String msg_) {
+ this.append("error", msg_);
+ }
+
+ private void append(String level_, String msg_) {
+ if (msg_.equals("") == false) {
+ String line = this.dateFormat.format(new java.util.Date()) + " (" + level_ + ") " + msg_;
+ if (this.checkpoints.containsKey(this.lastCheckPoint)) {
+ this.checkpoints.get(this.lastCheckPoint).add(line);
+ }
+ if (this.printStdOut == true) {
+ System.out.println(line);
+ }
+ }
+ }
+
+ public int newCheckPoint() {
+ int time = (int) (new Date().getTime());
+ this.checkpoints.put(time, new ArrayList());
+ this.lastCheckPoint = time;
+ return this.lastCheckPoint;
+ }
+
+ public ArrayList getForCheckPoint(int point_) {
+ return this.checkpoints.get(point_);
+ }
+
+ public void removeCheckPoint(int point_) {
+ try {
+ this.checkpoints.remove(point_);
+ }
+ catch (UnsupportedOperationException e) {
+ }
+ }
+
+ public final synchronized static Log getInstance(Configuration config) {
+ if (instance == null) {
+ boolean print = false;
+ if (config != null) {
+ print = config.getPrintLog();
+ }
+ instance = new Log(print);
+ }
+ return instance;
+ }
+
+ public final synchronized static void printCheckPoint(int point_) {
+ Log log = Log.getInstance(null);
+ ArrayList logs = log.getForCheckPoint(point_);
+ Iterator it = logs.iterator();
+ while (it.hasNext()) {
+ System.out.println(it.next());
+ }
+ }
+}
diff --git a/src/com/sheepit/client/Pair.java b/src/com/sheepit/client/Pair.java
new file mode 100644
index 0000000..d127cad
--- /dev/null
+++ b/src/com/sheepit/client/Pair.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.sheepit.client;
+
+import java.util.Objects;
+
+/**
+* Container to ease passing around a tuple of two objects. This object provides a sensible
+* implementation of equals(), returning true if equals() is true on each of the contained
+* objects.
+*/
+public class Pair {
+ public final F first;
+ public final S second;
+
+ /**
+ * Constructor for a Pair.
+ *
+ * @param first the first object in the Pair
+ * @param second the second object in the pair
+ */
+ public Pair(F first, S second) {
+ this.first = first;
+ this.second = second;
+ }
+
+ /**
+ * Checks the two objects for equality by delegating to their respective
+ * {@link Object#equals(Object)} methods.
+ *
+ * @param o the {@link Pair} to which this one is to be checked for equality
+ * @return true if the underlying objects of the Pair are both considered
+ * equal
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Pair)) {
+ return false;
+ }
+ Pair, ?> p = (Pair, ?>) o;
+ return Objects.equals(p.first, first) && Objects.equals(p.second, second);
+ }
+
+ /**
+ * Compute a hash code using the hash codes of the underlying objects
+ *
+ * @return a hashcode of the Pair
+ */
+ @Override
+ public int hashCode() {
+ return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode());
+ }
+
+ /**
+ * Convenience method for creating an appropriately typed pair.
+ * @param a the first object in the Pair
+ * @param b the second object in the pair
+ * @return a Pair that is templatized with the types of a and b
+ */
+ public static Pair create(A a, B b) {
+ return new Pair(a, b);
+ }
+}
diff --git a/src/com/sheepit/client/Server.java b/src/com/sheepit/client/Server.java
new file mode 100644
index 0000000..0f7c8bc
--- /dev/null
+++ b/src/com/sheepit/client/Server.java
@@ -0,0 +1,794 @@
+/*
+ * Copyright (C) 2010-2014 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.io.BufferedReader;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.ConnectException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.NoRouteToHostException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.net.UnknownHostException;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import com.sheepit.client.Configuration.ComputeType;
+import com.sheepit.client.Error.ServerCode;
+import com.sheepit.client.exception.FermeException;
+import com.sheepit.client.exception.FermeExceptionNoRightToRender;
+import com.sheepit.client.exception.FermeExceptionNoSession;
+import com.sheepit.client.exception.FermeExceptionSessionDisabled;
+import com.sheepit.client.os.OS;
+
+public class Server extends Thread implements HostnameVerifier, X509TrustManager {
+ private String base_url;
+ private Configuration user_config;
+ private Client client;
+ private ArrayList cookies;
+ private HashMap pages;
+ private Log log;
+ private long lastRequestTime;
+ private int keepmealive_duration; // time is ms
+
+ public Server(String url_, Configuration user_config_, Client client_) {
+ super();
+ this.base_url = url_;
+ this.user_config = user_config_;
+ this.client = client_;
+ this.pages = new HashMap();
+ this.cookies = new ArrayList();
+ this.log = Log.getInstance(this.user_config);
+ this.lastRequestTime = 0;
+ this.keepmealive_duration = 15 * 60 * 1000; // default 15min
+ }
+
+ public void run() {
+ this.stayAlive();
+ }
+
+ public void stayAlive() {
+ while (true) {
+ long current_time = new Date().getTime();
+ if ((current_time - this.lastRequestTime) > this.keepmealive_duration) {
+ try {
+ String args = "";
+ if (this.client != null && this.client.getRenderingJob() != null) {
+ args = "?frame=" + this.client.getRenderingJob().getFrameNumber() + "&job=" + this.client.getRenderingJob().getId();
+ if (this.client.getRenderingJob().getExtras() != null && this.client.getRenderingJob().getExtras().length() > 0) {
+ args += "&extras=" + this.client.getRenderingJob().getExtras();
+ }
+ }
+
+ HttpURLConnection connection = this.HTTPRequest(this.base_url + "/server/keepmealive.php" + args);
+
+ if (connection.getResponseCode() == HttpURLConnection.HTTP_OK && connection.getContentType().startsWith("text/xml")) {
+ DataInputStream in = new DataInputStream(connection.getInputStream());
+ try {
+ Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in);
+ ServerCode ret = Utils.statusIsOK(document, "keepmealive");
+ if (ret == ServerCode.KEEPMEALIVE_STOP_RENDERING) {
+ this.log.debug("Server::keeepmealive server ask to kill local render process");
+ // kill the current process, it will generate an error but it's okay
+ if (this.client != null && this.client.getRenderingJob() != null && this.client.getRenderingJob().getProcess() != null) {
+ OS.getOS().kill(this.client.getRenderingJob().getProcess());
+ }
+ }
+ }
+ catch (SAXException e) {
+ }
+ catch (ParserConfigurationException e) {
+ }
+ }
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ try {
+ Thread.sleep(60 * 1000); // 1min
+ }
+ catch (InterruptedException e) {
+ return;
+ }
+ catch (Exception e) {
+ return;
+ }
+ }
+ }
+
+ public String toString() {
+ return String.format("Server (base_url '%s', user_config %s, pages %s", this.base_url, this.user_config, this.pages);
+ }
+
+ public Error.Type getConfiguration() {
+ OS os = OS.getOS();
+ HttpURLConnection connection = null;
+ try {
+ String url_contents = String.format("%s%s?login=%s&password=%s&cpu_family=%s&cpu_model=%s&cpu_model_name=%s&cpu_cores=%s&os=%s&ram=%s&bits=%s&version=%s&extras=%s",
+ this.base_url,
+ "/server/config.php",
+ URLEncoder.encode(this.user_config.login(), "UTF-8"),
+ URLEncoder.encode(this.user_config.password(), "UTF-8"),
+ os.getCPU().family(),
+ os.getCPU().model(),
+ URLEncoder.encode(os.getCPU().name(), "UTF-8"),
+ ((this.user_config.getNbCores() == -1) ? os.getCPU().cores() : this.user_config.getNbCores()),
+ os.name(),
+ os.getMemory(),
+ os.getCPU().arch(),
+ this.user_config.getJarVersion(),
+ this.user_config.getExtras());
+ this.log.debug("Server::getConfiguration url " + url_contents);
+
+ connection = this.HTTPRequest(url_contents);
+ int r = connection.getResponseCode();
+ String contentType = connection.getContentType();
+
+ if (r == HttpURLConnection.HTTP_OK && contentType.startsWith("text/xml")) {
+ DataInputStream in = new DataInputStream(connection.getInputStream());
+ Document document = null;
+
+ try {
+ document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in);
+ }
+ catch (SAXException e) {
+ this.log.error("getConfiguration error: failed to parse XML SAXException " + e);
+ return Error.Type.WRONG_CONFIGURATION;
+ }
+ catch (IOException e) {
+ this.log.error("getConfiguration error: failed to parse XML IOException " + e);
+ return Error.Type.WRONG_CONFIGURATION;
+ }
+ catch (ParserConfigurationException e) {
+ this.log.error("getConfiguration error: failed to parse XML ParserConfigurationException " + e);
+ return Error.Type.WRONG_CONFIGURATION;
+ }
+
+ ServerCode ret = Utils.statusIsOK(document, "config");
+ if (ret != ServerCode.OK) {
+ return Error.ServerCodeToType(ret);
+ }
+
+ Element config_node = null;
+ NodeList ns = null;
+ ns = document.getElementsByTagName("config");
+ if (ns.getLength() == 0) {
+ this.log.error("getConfiguration error: failed to parse XML, no node 'config_serveur'");
+ return Error.Type.WRONG_CONFIGURATION;
+ }
+ config_node = (Element) ns.item(0);
+
+ ns = config_node.getElementsByTagName("request");
+ if (ns.getLength() == 0) {
+ this.log.error("getConfiguration error: failed to parse XML, node 'config' have no child node 'request'");
+ return Error.Type.WRONG_CONFIGURATION;
+ }
+ for (int i = 0; i < ns.getLength(); i++) {
+ Element element = (Element) ns.item(i);
+ if (element.hasAttribute("type") && element.hasAttribute("path")) {
+ this.pages.put(element.getAttribute("type"), element.getAttribute("path"));
+ if (element.getAttribute("type").equals("keepmealive") && element.hasAttribute("max-period")) {
+ this.keepmealive_duration = (Integer.parseInt(element.getAttribute("max-period")) - 120) * 1000; // put 2min of safety net
+ }
+ }
+ }
+ }
+ else {
+ this.log.error("Server::getConfiguration: Invalid response " + contentType + " " + r);
+ return Error.Type.WRONG_CONFIGURATION;
+ }
+ }
+ catch (ConnectException e) {
+ this.log.error("Server::getConfiguration error ConnectException " + e);
+ return Error.Type.NETWORK_ISSUE;
+ }
+ catch (UnknownHostException e) {
+ this.log.error("Server::getConfiguration error UnknownHostException " + e);
+ return Error.Type.NETWORK_ISSUE;
+ }
+ catch (NoRouteToHostException e) {
+ this.log.error("Server::getConfiguration error NoRouteToHost " + e);
+ return Error.Type.NETWORK_ISSUE;
+ }
+ catch (Exception e) {
+ this.log.error("Server::getConfiguration: exception 02R " + e);
+ e.printStackTrace();
+ return Error.Type.UNKNOWN;
+ }
+ finally {
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
+ return Error.Type.OK;
+ }
+
+ public Job requestJob() throws FermeException, FermeExceptionNoRightToRender, FermeExceptionNoSession, FermeExceptionSessionDisabled {
+ this.log.debug("Server::requestJob");
+ String url_contents = "";
+
+ HttpURLConnection connection = null;
+ try {
+ String url = String.format("%s?computemethod=%s", this.getPage("request-job"), this.user_config.computeMethodToInt());
+ if (this.user_config.getComputeMethod() != ComputeType.CPU_ONLY && this.user_config.getGPUDevice() != null) {
+ String gpu_model = "";
+ try {
+ gpu_model = URLEncoder.encode(this.user_config.getGPUDevice().getModel(), "UTF-8");
+ }
+ catch (UnsupportedEncodingException e) {
+ }
+ url += "&gpu_model=" + gpu_model + "&gpu_ram=" + this.user_config.getGPUDevice().getMemory();
+ }
+
+ connection = this.HTTPRequest(url, this.generateXMLForMD5cache());
+
+ int r = connection.getResponseCode();
+ String contentType = connection.getContentType();
+
+ if (r == HttpURLConnection.HTTP_OK && contentType.startsWith("text/xml")) {
+ DataInputStream in = new DataInputStream(connection.getInputStream());
+ Document document = null;
+ try {
+ document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in);
+ }
+ catch (SAXException e) {
+ throw new FermeException("error requestJob: parseXML failed, SAXException " + e);
+ }
+ catch (IOException e) {
+ throw new FermeException("error requestJob: parseXML failed IOException " + e);
+ }
+ catch (ParserConfigurationException e) {
+ throw new FermeException("error requestJob: parseXML failed ParserConfigurationException " + e);
+ }
+
+ ServerCode ret = Utils.statusIsOK(document, "jobrequest");
+ if (ret != ServerCode.OK) {
+ if (ret == ServerCode.JOB_REQUEST_NOJOB) {
+ handleFileMD5DeleteDocument(document, "jobrequest");
+ return null;
+ }
+ else if (ret == ServerCode.JOB_REQUEST_ERROR_NO_RENDERING_RIGHT) {
+ throw new FermeExceptionNoRightToRender();
+ }
+ else if (ret == ServerCode.JOB_REQUEST_ERROR_DEAD_SESSION) {
+ throw new FermeExceptionNoSession();
+ }
+ else if (ret == ServerCode.JOB_REQUEST_ERROR_SESSION_DISABLED) {
+ throw new FermeExceptionSessionDisabled();
+ }
+ this.log.error("Server::requestJob: Utils.statusIsOK(document, 'jobrequest') -> ret " + ret);
+ throw new FermeException("error requestJob: status is not ok (it's " + ret + ")");
+ }
+
+ handleFileMD5DeleteDocument(document, "jobrequest");
+
+ Element a_node = null;
+ NodeList ns = null;
+
+ ns = document.getElementsByTagName("frames");
+ if (ns.getLength() == 0) {
+ throw new FermeException("error requestJob: parseXML failed, no 'frame' node");
+ }
+ a_node = (Element) ns.item(0);
+
+ int remaining_frames = -1;
+ if (a_node.hasAttribute("remaining")) {
+ remaining_frames = Integer.parseInt(a_node.getAttribute("remaining"));
+ }
+
+ ns = document.getElementsByTagName("job");
+ if (ns.getLength() == 0) {
+ throw new FermeException("error requestJob: parseXML failed, no 'job' node");
+ }
+ Element job_node = (Element) ns.item(0);
+
+ ns = job_node.getElementsByTagName("renderer");
+ if (ns.getLength() == 0) {
+ throw new FermeException("error requestJob: parseXML failed, node 'job' have no sub-node 'renderer'");
+ }
+ Element renderer_node = (Element) ns.item(0);
+
+ String script = "import bpy\nbpy.context.user_preferences.filepaths.temporary_directory = \"" + this.user_config.workingDirectory.getAbsolutePath().replace("\\", "\\\\") + "\"\n";
+ try {
+ ns = job_node.getElementsByTagName("script");
+ if (ns.getLength() != 0) {
+ Element a_node3 = (Element) ns.item(0);
+ script += new String(a_node3.getTextContent());
+ }
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ String[] job_node_require_attribute = { "id", "archive_md5", "path", "revision", "use_gpu", "frame", "extras" };
+ String[] renderer_node_require_attribute = { "md5", "commandline" };
+
+ for (String e : job_node_require_attribute) {
+ if (job_node.hasAttribute(e) == false) {
+ throw new FermeException("error requestJob: parseXML failed, job_node have to attribute '" + e + "'");
+ }
+ }
+
+ for (String e : renderer_node_require_attribute) {
+ if (renderer_node.hasAttribute(e) == false) {
+ throw new FermeException("error requestJob: parseXML failed, renderer_node have to attribute '" + e + "'");
+ }
+ }
+
+ boolean use_gpu = (job_node.getAttribute("use_gpu").compareTo("1") == 0);
+
+ String frame_extras = "";
+ if (job_node.hasAttribute("extras")) {
+ frame_extras = job_node.getAttribute("extras");
+ }
+
+ Job a_job = new Job(
+ this.user_config,
+ job_node.getAttribute("id"),
+ job_node.getAttribute("frame"),
+ job_node.getAttribute("revision"),
+ job_node.getAttribute("path").replace("/", File.separator),
+ use_gpu,
+ renderer_node.getAttribute("commandline"),
+ script,
+ job_node.getAttribute("archive_md5"),
+ renderer_node.getAttribute("md5"),
+ frame_extras
+ );
+
+ this.client.getGui().framesRemaining(remaining_frames);
+
+ return a_job;
+ }
+ else {
+ System.out.println("Server::requestJob url " + url_contents + " r " + r + " contentType " + contentType);
+ InputStream in = connection.getInputStream();
+ String line;
+ BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
+ while ((line = reader.readLine()) != null) {
+ System.out.print(line);
+ }
+ System.out.println("");
+ }
+ }
+ catch (FermeExceptionNoRightToRender e) {
+ throw e;
+ }
+ catch (FermeExceptionNoSession e) {
+ throw e;
+ }
+ catch (FermeExceptionSessionDisabled e) {
+ throw e;
+ }
+ catch (FermeException e) {
+ throw new FermeException(e.getMessage());
+ }
+ catch (Exception e) {
+ throw new FermeException("error requestJob: unknow exception " + e);
+ }
+ finally {
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
+ throw new FermeException("error requestJob, end of function");
+ }
+
+ public HttpURLConnection HTTPRequest(String url_) throws IOException {
+ return this.HTTPRequest(url_, null);
+ }
+
+ public HttpURLConnection HTTPRequest(String url_, String data_) throws IOException {
+ this.log.debug("Server::HTTPRequest url(" + url_ + ")");
+ HttpURLConnection connection = null;
+ URL url = new URL(url_);
+
+ connection = (HttpURLConnection) url.openConnection();
+ connection.setDoInput(true);
+ connection.setDoOutput(true);
+ connection.setRequestMethod("GET");
+ for (String cookie : this.cookies) {
+ connection.setRequestProperty("Cookie", cookie);
+ }
+
+ if (url_.startsWith("https://")) {
+ try {
+ SSLContext sc;
+ sc = SSLContext.getInstance("SSL");
+ sc.init(null, new TrustManager[] { this }, null);
+ SSLSocketFactory factory = sc.getSocketFactory();
+ ((HttpsURLConnection) connection).setSSLSocketFactory(factory);
+ ((HttpsURLConnection) connection).setHostnameVerifier(this);
+ }
+ catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ return null;
+ }
+ catch (KeyManagementException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ if (data_ != null) {
+ connection.setRequestProperty("Content-type", "application/x-www-form-urlencoded");
+ connection.setRequestMethod("POST");
+ OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream());
+ out.write(data_);
+ out.flush();
+ out.close();
+ }
+
+ String headerName = null;
+ for (int i = 1; (headerName = connection.getHeaderFieldKey(i)) != null; i++) {
+ if (headerName.equals("Set-Cookie")) {
+ String cookie = connection.getHeaderField(i);
+
+ boolean cookieIsPresent = false;
+ for (String value : this.cookies) {
+ if (value.equalsIgnoreCase(cookie))
+ cookieIsPresent = true;
+ }
+ if (!cookieIsPresent)
+ this.cookies.add(cookie);
+ }
+ }
+
+ this.lastRequestTime = new Date().getTime();
+
+ return connection;
+ }
+
+ public int HTTPGetFile(String url_, String destination_, Gui gui_, String status_) {
+ // the destination_ parent directory must exist
+ try {
+ HttpURLConnection httpCon = this.HTTPRequest(url_);
+
+ InputStream inStrm = httpCon.getInputStream();
+ if (httpCon.getResponseCode() != HttpURLConnection.HTTP_OK) {
+ return -1;
+ }
+ int size = httpCon.getContentLength();
+ long start = new Date().getTime();
+
+ FileOutputStream fos = new FileOutputStream(destination_);
+ byte[] ch = new byte[512 * 1024];
+ int nb;
+ long writed = 0;
+ long last_gui_update = 0; // size in byte
+ while ((nb = inStrm.read(ch)) != -1) {
+ fos.write(ch, 0, nb);
+ writed += nb;
+ if ((writed - last_gui_update) > 1000000) { // only update the gui every 1MB
+ gui_.status(String.format(status_, (int) (100.0 * writed / size)));
+ last_gui_update = writed;
+ }
+ }
+ fos.close();
+ inStrm.close();
+ long end = new Date().getTime();
+ System.out.println(String.format("File downloaded at %.1f kB/s", ((float) (size / 1000)) / ((float) (end - start) / 1000)));
+ this.lastRequestTime = new Date().getTime();
+ return 0;
+ }
+ catch (Exception e) {
+ System.err.println("Server::HTTPGetFile exception");
+ e.printStackTrace();
+ }
+ return -2;
+ }
+
+ public ServerCode HTTPSendFile(String surl, String file1) {
+ this.log.debug("Server::HTTPSendFile(" + surl + "," + file1 + ")");
+
+ HttpURLConnection conn = null;
+ DataOutputStream dos = null;
+ BufferedReader inStream = null;
+
+ String exsistingFileName = file1;
+ File fFile2Snd = new File(exsistingFileName);
+
+ String lineEnd = "\r\n";
+ String twoHyphens = "--";
+ String boundary = "***232404jkg4220957934FW**";
+
+ int bytesRead, bytesAvailable, bufferSize;
+ byte[] buffer;
+ int maxBufferSize = 1 * 1024 * 1024;
+
+ String urlString = surl;
+
+ try {
+ FileInputStream fileInputStream = new FileInputStream(new File(exsistingFileName));
+ URL url = new URL(urlString);
+
+ conn = (HttpURLConnection) url.openConnection();
+ conn.setDoInput(true);
+ conn.setDoOutput(true);
+ conn.setUseCaches(false);
+ for (String cookie : this.cookies) {
+ conn.setRequestProperty("Cookie", cookie);
+ }
+
+ conn.setRequestMethod("POST");
+ conn.setRequestProperty("Connection", "Keep-Alive");
+ conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
+
+ if (urlString.startsWith("https://")) {
+ try {
+ SSLContext sc;
+ sc = SSLContext.getInstance("SSL");
+ sc.init(null, new TrustManager[] { this }, null);
+ SSLSocketFactory factory = sc.getSocketFactory();
+ ((HttpsURLConnection) conn).setSSLSocketFactory(factory);
+ ((HttpsURLConnection) conn).setHostnameVerifier(this);
+ }
+ catch (NoSuchAlgorithmException e) {
+ this.log.error("Server::HTTPSendFile, exception NoSuchAlgorithmException " + e);
+ try {
+ fileInputStream.close();
+ }
+ catch (Exception e1) {
+
+ }
+ return ServerCode.UNKNOWN;
+ }
+ catch (KeyManagementException e) {
+ this.log.error("Server::HTTPSendFile, exception KeyManagementException " + e);
+ try {
+ fileInputStream.close();
+ }
+ catch (Exception e1) {
+
+ }
+ return ServerCode.UNKNOWN;
+ }
+ }
+
+ dos = new DataOutputStream(conn.getOutputStream());
+ dos.writeBytes(twoHyphens + boundary + lineEnd);
+ dos.writeBytes("Content-Disposition: form-data; name=\"file\";" + " filename=\"" + fFile2Snd.getName() + "\"" + lineEnd);
+ dos.writeBytes(lineEnd);
+
+ bytesAvailable = fileInputStream.available();
+ bufferSize = Math.min(bytesAvailable, maxBufferSize);
+ buffer = new byte[bufferSize];
+
+ bytesRead = fileInputStream.read(buffer, 0, bufferSize);
+
+ while (bytesRead > 0) {
+ dos.write(buffer, 0, bufferSize);
+ bytesAvailable = fileInputStream.available();
+ bufferSize = Math.min(bytesAvailable, maxBufferSize);
+ bytesRead = fileInputStream.read(buffer, 0, bufferSize);
+ }
+
+ dos.writeBytes(lineEnd);
+ dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
+ fileInputStream.close();
+ dos.flush();
+ dos.close();
+ }
+ catch (MalformedURLException ex) {
+ this.log.error("Server::HTTPSendFile, exception MalformedURLException " + ex);
+ return ServerCode.UNKNOWN;
+ }
+ catch (IOException ioe) {
+ this.log.error("Server::HTTPSendFile, exception IOException " + ioe);
+ return ServerCode.UNKNOWN;
+ }
+ catch (Exception e6) {
+ this.log.error("Server::HTTPSendFile, exception Exception " + e6);
+ return ServerCode.UNKNOWN;
+ }
+
+ int r;
+ try {
+ r = conn.getResponseCode();
+ }
+ catch (IOException e1) {
+ e1.printStackTrace();
+ return ServerCode.UNKNOWN;
+ }
+ String contentType = conn.getContentType();
+
+ if (r == HttpURLConnection.HTTP_OK && contentType.startsWith("text/xml")) {
+ DataInputStream in;
+ try {
+ in = new DataInputStream(conn.getInputStream());
+ }
+ catch (IOException e1) {
+ e1.printStackTrace();
+ return ServerCode.UNKNOWN;
+ }
+ Document document = null;
+ try {
+ document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in);
+ }
+ catch (SAXException e) {
+ e.printStackTrace();
+ return ServerCode.UNKNOWN;
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ return ServerCode.UNKNOWN;
+ }
+ catch (ParserConfigurationException e) {
+ e.printStackTrace();
+ return ServerCode.UNKNOWN;
+ }
+
+ this.lastRequestTime = new Date().getTime();
+
+ ServerCode ret1 = Utils.statusIsOK(document, "jobvalidate");
+ if (ret1 != ServerCode.OK) {
+ this.log.error("Server::HTTPSendFile wrong status (is " + ret1 + ")");
+ return ret1;
+ }
+ return ServerCode.OK;
+ }
+ else {
+ try {
+ inStream = new BufferedReader(new InputStreamReader(conn.getInputStream()));
+
+ String str;
+ while ((str = inStream.readLine()) != null) {
+ System.out.println(str);
+ System.out.println("");
+ }
+ inStream.close();
+ }
+ catch (IOException ioex) {
+ }
+ }
+ return ServerCode.UNKNOWN;
+ }
+
+ private String generateXMLForMD5cache() {
+ String xml_str = null;
+ try {
+ DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
+ Document document_cache = docBuilder.newDocument();
+
+ Element rootElement = document_cache.createElement("cache");
+ document_cache.appendChild(rootElement);
+
+ List local_files = this.user_config.getLocalCacheFiles();
+ for (File local_file : local_files) {
+ Element node_file = document_cache.createElement("file");
+ rootElement.appendChild(node_file);
+ try {
+ String extension = local_file.getName().substring(local_file.getName().lastIndexOf('.')).toLowerCase();
+ String name = local_file.getName().substring(0, local_file.getName().length() - 1 * extension.length());
+ if (extension.equals(".zip")) {
+ node_file.setAttribute("md5", name);
+ }
+ }
+ catch (StringIndexOutOfBoundsException e) { // because the file does not have an . his path
+ }
+ }
+
+ TransformerFactory tf = TransformerFactory.newInstance();
+ Transformer transformer = tf.newTransformer();
+ transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
+ StringWriter writer = new StringWriter();
+ transformer.transform(new DOMSource(document_cache), new StreamResult(writer));
+ xml_str = writer.getBuffer().toString();
+ }
+ catch (TransformerConfigurationException e) {
+ this.log.debug("Server::generateXMLForMD5cache " + e);
+ }
+ catch (TransformerException e) {
+ this.log.debug("Server::generateXMLForMD5cache " + e);
+ }
+ catch (ParserConfigurationException e) {
+ this.log.debug("Server::generateXMLForMD5cache " + e);
+ }
+
+ return xml_str;
+ }
+
+ private void handleFileMD5DeleteDocument(Document document, String root_nodename) {
+ NodeList ns = document.getElementsByTagName(root_nodename);
+ if (ns.getLength() > 0) {
+ Element root_node = (Element) ns.item(0);
+ ns = root_node.getElementsByTagName("file");
+ if (ns.getLength() > 0) {
+ for (int i = 0; i < ns.getLength(); ++i) {
+ Element file_node = (Element) ns.item(i);
+ if (file_node.hasAttribute("md5") && file_node.hasAttribute("action") && file_node.getAttribute("action").equals("delete")) {
+ String path = this.user_config.workingDirectory.getAbsolutePath() + File.separatorChar + file_node.getAttribute("md5");
+ this.log.debug("Server::handleFileMD5DeleteDocument delete old file " + path);
+ File file_to_delete = new File(path + ".zip");
+ file_to_delete.delete();
+ Utils.delete(new File(path));
+ }
+ }
+ }
+ }
+ }
+
+ public String getPage(String key) {
+ if (this.pages.containsKey(key)) {
+ return this.base_url + this.pages.get(key);
+ }
+ return "";
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+
+ @Override
+ public boolean verify(String arg0, SSLSession arg1) {
+ return true; // trust every ssl certificate
+ }
+}
diff --git a/src/com/sheepit/client/ShutdownHook.java b/src/com/sheepit/client/ShutdownHook.java
new file mode 100644
index 0000000..7b8ac75
--- /dev/null
+++ b/src/com/sheepit/client/ShutdownHook.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 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;
+
+public class ShutdownHook {
+ private Client _client;
+
+ public ShutdownHook(Client client_) {
+ this._client = client_;
+ }
+
+ public void attachShutDownHook() {
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ @Override
+ public void run() {
+ _client.stop();
+ }
+ });
+ }
+}
diff --git a/src/com/sheepit/client/Utils.java b/src/com/sheepit/client/Utils.java
new file mode 100644
index 0000000..2473eeb
--- /dev/null
+++ b/src/com/sheepit/client/Utils.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2010-2014 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.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import com.sheepit.client.Error.ServerCode;
+
+public class Utils {
+ public static int unzipFileIntoDirectory(String zipFileName_, String jiniHomeParentDirName_) {
+ File rootdir = new File(jiniHomeParentDirName_);
+ try {
+ ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFileName_));
+ byte[] buffer = new byte[4096];
+ ZipEntry ze;
+ while ((ze = zis.getNextEntry()) != null) {
+ FileOutputStream fos = null;
+ try {
+ File f = new File(rootdir.getAbsolutePath() + File.separator + ze.getName());
+ if (ze.isDirectory()) {
+ f.mkdirs();
+ continue;
+ }
+ else {
+ f.getParentFile().mkdirs();
+ f.createNewFile();
+ try {
+ f.setExecutable(true);
+ }
+ catch (NoSuchMethodError e2) {
+ // do nothing it's related to the filesystem
+ }
+ }
+
+ fos = new FileOutputStream(f);
+ int numBytes;
+ while ((numBytes = zis.read(buffer, 0, buffer.length)) != -1) {
+ fos.write(buffer, 0, numBytes);
+ }
+ fos.close();
+ }
+ catch (Exception e1) {
+ e1.printStackTrace();
+ }
+ if (fos != null) {
+ try {
+ fos.close();
+ }
+ catch (IOException e) {
+ }
+ }
+ zis.closeEntry();
+ }
+ }
+ catch (IllegalArgumentException e) {
+ Log logger = Log.getInstance(null); // might not print the log since the config is null
+ logger.error("Utils::unzipFileIntoDirectory(" + zipFileName_ + "," + jiniHomeParentDirName_ + ") exception " + e);
+ return -2;
+ }
+ catch (Exception e) {
+ Log logger = Log.getInstance(null); // might not print the log since the config is null
+ logger.error("Utils::unzipFileIntoDirectory(" + zipFileName_ + "," + jiniHomeParentDirName_ + ") exception " + e);
+ return -1;
+ }
+ return 0;
+ }
+
+ public static String md5(String path_of_file_) {
+ MessageDigest digest;
+ try {
+ digest = MessageDigest.getInstance("MD5");
+ }
+ catch (NoSuchAlgorithmException e1) {
+ e1.printStackTrace();
+ return "";
+ }
+ File f = new File(path_of_file_);
+ InputStream is;
+ try {
+ is = new FileInputStream(f);
+ }
+ catch (FileNotFoundException e1) {
+ e1.printStackTrace();
+ return "";
+ }
+ byte[] buffer = new byte[8192];
+ int read = 0;
+ try {
+ while ((read = is.read(buffer)) > 0) {
+ digest.update(buffer, 0, read);
+ }
+ byte[] md5sum = digest.digest();
+ BigInteger bigInt = new BigInteger(1, md5sum);
+
+ String output = bigInt.toString(16);
+
+ // fill with "0" because bigInt.toString does not add 0 at the beginning of the result
+ int zero_to_add = 32 - output.length();
+ for (int i = 0; i < zero_to_add; i++)
+ output = "0" + output;
+
+ return output;
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+ finally {
+ try {
+ is.close();
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ //throw new RuntimeException("Unable to close input stream for MD5 calculation", e);
+ }
+ }
+ return "";
+ }
+
+ public static double lastModificationTime(File directory_) {
+ double max = 0.0;
+ if (directory_.isDirectory()) {
+ File[] list = directory_.listFiles();
+ if (list != null) {
+ for (int i = 0; i < list.length; i++) {
+ double max1 = lastModificationTime(list[i]);
+ if (max1 > max) {
+ max = max1;
+ }
+ }
+ }
+ }
+ else if (directory_.isFile()) {
+ return directory_.lastModified();
+ }
+
+ return max;
+ }
+
+ public static ServerCode statusIsOK(Document document_, String rootname_) {
+ NodeList ns = document_.getElementsByTagName(rootname_);
+ if (ns.getLength() == 0) {
+ return Error.ServerCode.ERROR_NO_ROOT;
+ }
+ Element a_node = (Element) ns.item(0);
+ if (a_node.hasAttribute("status")) {
+ return Error.ServerCode.fromInt(Integer.parseInt(a_node.getAttribute("status")));
+ }
+ return Error.ServerCode.UNKNOWN;
+ }
+
+ /**
+ * Will recursively delete a directory
+ */
+ public static void delete(File file) {
+ if (file == null) {
+ return;
+ }
+ if (file.isDirectory()) {
+ String files[] = file.list();
+ if (files != null) {
+ if (files.length != 0) {
+ for (String temp : files) {
+ File fileDelete = new File(file, temp);
+ delete(fileDelete);
+ }
+ }
+ }
+ }
+ file.delete();
+ }
+
+ public static long parseNumber(String in) {
+ in = in.trim();
+ in = in.replaceAll(",", ".");
+ try {
+ return Long.parseLong(in);
+ }
+ catch (NumberFormatException e) {
+ }
+ final Matcher m = Pattern.compile("([\\d.,]+)\\s*(\\w)").matcher(in);
+ m.find();
+ int scale = 1;
+ switch (m.group(2).charAt(0)) {
+ case 'G':
+ scale *= 1000;
+ case 'g':
+ scale *= 1000;
+ case 'M':
+ scale *= 1000;
+ case 'm':
+ scale *= 1000;
+ case 'K':
+ break;
+ default:
+ return 0;
+ }
+ return Math.round(Double.parseDouble(m.group(1)) * scale);
+ }
+
+ public static T[] concatAll(T[] first, T[]... rest) {
+ int totalLength = first.length;
+ for (T[] array : rest) {
+ totalLength += array.length;
+ }
+ T[] result = Arrays.copyOf(first, totalLength);
+ int offset = first.length;
+ for (T[] array : rest) {
+ System.arraycopy(array, 0, result, offset, array.length);
+ offset += array.length;
+ }
+ return result;
+ }
+}
diff --git a/src/com/sheepit/client/exception/FermeException.java b/src/com/sheepit/client/exception/FermeException.java
new file mode 100644
index 0000000..6eda682
--- /dev/null
+++ b/src/com/sheepit/client/exception/FermeException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2010-2013 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.exception;
+
+public class FermeException extends Exception {
+ public FermeException() {
+ super();
+ }
+
+ public FermeException(String message_) {
+ super(message_);
+ }
+}
diff --git a/src/com/sheepit/client/exception/FermeExceptionNoRightToRender.java b/src/com/sheepit/client/exception/FermeExceptionNoRightToRender.java
new file mode 100644
index 0000000..3d5ec6e
--- /dev/null
+++ b/src/com/sheepit/client/exception/FermeExceptionNoRightToRender.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2013 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.exception;
+
+public class FermeExceptionNoRightToRender extends FermeException {
+ public FermeExceptionNoRightToRender() {
+ super();
+ }
+
+ public FermeExceptionNoRightToRender(String message_) {
+ super(message_);
+ }
+}
diff --git a/src/com/sheepit/client/exception/FermeExceptionNoSession.java b/src/com/sheepit/client/exception/FermeExceptionNoSession.java
new file mode 100644
index 0000000..4b6e17e
--- /dev/null
+++ b/src/com/sheepit/client/exception/FermeExceptionNoSession.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2013 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.exception;
+
+public class FermeExceptionNoSession extends FermeException {
+ public FermeExceptionNoSession() {
+ super();
+ }
+
+ public FermeExceptionNoSession(String message_) {
+ super(message_);
+ }
+}
diff --git a/src/com/sheepit/client/exception/FermeExceptionSessionDisabled.java b/src/com/sheepit/client/exception/FermeExceptionSessionDisabled.java
new file mode 100644
index 0000000..3997ec1
--- /dev/null
+++ b/src/com/sheepit/client/exception/FermeExceptionSessionDisabled.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2013 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.exception;
+
+public class FermeExceptionSessionDisabled extends FermeException {
+ public FermeExceptionSessionDisabled() {
+ super();
+ }
+
+ public FermeExceptionSessionDisabled(String message_) {
+ super(message_);
+ }
+}
diff --git a/src/com/sheepit/client/hardware/cpu/CPU.java b/src/com/sheepit/client/hardware/cpu/CPU.java
new file mode 100644
index 0000000..e114b8d
--- /dev/null
+++ b/src/com/sheepit/client/hardware/cpu/CPU.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2010-2014 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.hardware.cpu;
+
+public class CPU {
+ private String name;
+ private String model;
+ private String family;
+ private String arch; // 32 or 64 bits
+
+ public CPU() {
+ this.name = null;
+ this.model = null;
+ this.family = null;
+ this.generateArch();
+ }
+
+ public String name() {
+ return this.name;
+ }
+
+ public String model() {
+ return this.model;
+ }
+
+ public String family() {
+ return this.family;
+ }
+
+ public String arch() {
+ return this.arch;
+ }
+
+ public int cores() {
+ return Runtime.getRuntime().availableProcessors();
+ }
+
+ public void setName(String name_) {
+ this.name = name_;
+ }
+
+ public void setModel(String model_) {
+ this.model = model_;
+ }
+
+ public void setFamily(String family_) {
+ this.family = family_;
+ }
+
+ public void setArch(String arch_) {
+ this.arch = arch_;
+ }
+
+ public void generateArch() {
+ String arch = System.getProperty("os.arch").toLowerCase();
+ if (arch.equals("i386") || arch.equals("i686") || arch.equals("x86")) {
+ this.arch = "32bit";
+ }
+ else if (arch.equals("amd64") || arch.equals("x86_64")) {
+ this.arch = "64bit";
+ }
+ else {
+ this.arch = "xxbit";
+ }
+ }
+}
diff --git a/src/com/sheepit/client/hardware/gpu/CUDA.java b/src/com/sheepit/client/hardware/gpu/CUDA.java
new file mode 100644
index 0000000..3f824fa
--- /dev/null
+++ b/src/com/sheepit/client/hardware/gpu/CUDA.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2013-2014 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.hardware.gpu;
+
+import com.sun.jna.Library;
+import com.sun.jna.NativeLong;
+
+public interface CUDA extends Library {
+ public int cuInit(int flags);
+
+ /*
+ * @return: CUDA_SUCCESS, CUDA_ERROR_DEINITIALIZED, CUDA_ERROR_NOT_INITIALIZED, CUDA_ERROR_INVALID_CONTEXT, CUDA_ERROR_INVALID_VALUE
+ */
+ public int cuDeviceGetCount(int count[]);
+
+ public int cuDeviceGetName(byte[] name, int len, int dev);
+
+ public int cuDeviceTotalMem(NativeLong bytes[], int dev);
+}
diff --git a/src/com/sheepit/client/hardware/gpu/CUresult.java b/src/com/sheepit/client/hardware/gpu/CUresult.java
new file mode 100644
index 0000000..c2724b2
--- /dev/null
+++ b/src/com/sheepit/client/hardware/gpu/CUresult.java
@@ -0,0 +1,508 @@
+/*
+ * JCuda - Java bindings for NVIDIA CUDA driver and runtime API
+ *
+ * Copyright (c) 2009-2012 Marco Hutter - http://www.jcuda.org
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+//package jcuda.driver;
+package com.sheepit.client.hardware.gpu;
+
+/**
+ * Error codes.
+ *
+ * Most comments are taken from the CUDA reference manual.
+ */
+public class CUresult {
+ /**
+ * The API call returned with no errors. In the case of query calls, this
+ * can also mean that the operation being queried is complete (see
+ * ::cuEventQuery() and ::cuStreamQuery()).
+ */
+ public static final int CUDA_SUCCESS = 0;
+
+ /**
+ * This indicates that one or more of the parameters passed to the API call
+ * is not within an acceptable range of values.
+ */
+ public static final int CUDA_ERROR_INVALID_VALUE = 1;
+
+ /**
+ * The API call failed because it was unable to allocate enough memory to
+ * perform the requested operation.
+ */
+ public static final int CUDA_ERROR_OUT_OF_MEMORY = 2;
+
+ /**
+ * This indicates that the CUDA driver has not been initialized with
+ * ::cuInit() or that initialization has failed.
+ */
+ public static final int CUDA_ERROR_NOT_INITIALIZED = 3;
+
+ /**
+ * This indicates that the CUDA driver is in the process of shutting down.
+ */
+ public static final int CUDA_ERROR_DEINITIALIZED = 4;
+
+ /**
+ * This indicates profiling APIs are called while application is running
+ * in visual profiler mode.
+ */
+ public static final int CUDA_ERROR_PROFILER_DISABLED = 5;
+
+ /**
+ * This indicates profiling has not been initialized for this context.
+ * Call cuProfilerInitialize() to resolve this.
+ * @deprecated This error return is deprecated as of CUDA 5.0.
+ * It is no longer an error to attempt to enable/disable the
+ * profiling via ::cuProfilerStart or ::cuProfilerStop without
+ * initialization.
+ */
+ public static final int CUDA_ERROR_PROFILER_NOT_INITIALIZED = 6;
+
+ /**
+ * This indicates profiler has already been started and probably
+ * cuProfilerStart() is incorrectly called.
+ * @deprecated This error return is deprecated as of CUDA 5.0.
+ * It is no longer an error to call cuProfilerStart() when
+ * profiling is already enabled.
+ */
+ public static final int CUDA_ERROR_PROFILER_ALREADY_STARTED = 7;
+
+ /**
+ * This indicates profiler has already been stopped and probably
+ * cuProfilerStop() is incorrectly called.
+ * @deprecated This error return is deprecated as of CUDA 5.0.
+ * It is no longer an error to call cuProfilerStop() when
+ * profiling is already disabled.
+ */
+ public static final int CUDA_ERROR_PROFILER_ALREADY_STOPPED = 8;
+
+ /**
+ * This indicates that no CUDA-capable devices were detected by the installed
+ * CUDA driver.
+ */
+ public static final int CUDA_ERROR_NO_DEVICE = 100;
+
+ /**
+ * This indicates that the device ordinal supplied by the user does not
+ * correspond to a valid CUDA device.
+ */
+ public static final int CUDA_ERROR_INVALID_DEVICE = 101;
+
+ /**
+ * This indicates that the device kernel image is invalid. This can also
+ * indicate an invalid CUDA module.
+ */
+ public static final int CUDA_ERROR_INVALID_IMAGE = 200;
+
+ /**
+ * This most frequently indicates that there is no context bound to the
+ * current thread. This can also be returned if the context passed to an
+ * API call is not a valid handle (such as a context that has had
+ * ::cuCtxDestroy() invoked on it). This can also be returned if a user
+ * mixes different API versions (i.e. 3010 context with 3020 API calls).
+ * See ::cuCtxGetApiVersion() for more details.
+ */
+ public static final int CUDA_ERROR_INVALID_CONTEXT = 201;
+
+ /**
+ * This indicated that the context being supplied as a parameter to the
+ * API call was already the active context.
+ * \deprecated
+ * This error return is deprecated as of CUDA 3.2. It is no longer an
+ * error to attempt to push the active context via ::cuCtxPushCurrent().
+ */
+ public static final int CUDA_ERROR_CONTEXT_ALREADY_CURRENT = 202;
+
+ /**
+ * This indicates that a map or register operation has failed.
+ */
+ public static final int CUDA_ERROR_MAP_FAILED = 205;
+
+ /**
+ * This indicates that an unmap or unregister operation has failed.
+ */
+ public static final int CUDA_ERROR_UNMAP_FAILED = 206;
+
+ /**
+ * This indicates that the specified array is currently mapped and thus
+ * cannot be destroyed.
+ */
+ public static final int CUDA_ERROR_ARRAY_IS_MAPPED = 207;
+
+ /**
+ * This indicates that the resource is already mapped.
+ */
+ public static final int CUDA_ERROR_ALREADY_MAPPED = 208;
+
+ /**
+ * This indicates that there is no kernel image available that is suitable
+ * for the device. This can occur when a user specifies code generation
+ * options for a particular CUDA source file that do not include the
+ * corresponding device configuration.
+ */
+ public static final int CUDA_ERROR_NO_BINARY_FOR_GPU = 209;
+
+ /**
+ * This indicates that a resource has already been acquired.
+ */
+ public static final int CUDA_ERROR_ALREADY_ACQUIRED = 210;
+
+ /**
+ * This indicates that a resource is not mapped.
+ */
+ public static final int CUDA_ERROR_NOT_MAPPED = 211;
+
+ /**
+ * This indicates that a mapped resource is not available for access as an
+ * array.
+ */
+ public static final int CUDA_ERROR_NOT_MAPPED_AS_ARRAY = 212;
+
+ /**
+ * This indicates that a mapped resource is not available for access as a
+ * pointer.
+ */
+ public static final int CUDA_ERROR_NOT_MAPPED_AS_POINTER = 213;
+
+ /**
+ * This indicates that an uncorrectable ECC error was detected during
+ * execution.
+ */
+ public static final int CUDA_ERROR_ECC_UNCORRECTABLE = 214;
+
+ /**
+ * This indicates that the ::CUlimit passed to the API call is not
+ * supported by the active device.
+ */
+ public static final int CUDA_ERROR_UNSUPPORTED_LIMIT = 215;
+
+ /**
+ * This indicates that the ::CUcontext passed to the API call can
+ * only be bound to a single CPU thread at a time but is already
+ * bound to a CPU thread.
+ */
+ public static final int CUDA_ERROR_CONTEXT_ALREADY_IN_USE = 216;
+
+ /**
+ * This indicates that peer access is not supported across the given
+ * devices.
+ */
+ public static final int CUDA_ERROR_PEER_ACCESS_UNSUPPORTED = 217;
+
+ /**
+ * This indicates that the device kernel source is invalid.
+ */
+ public static final int CUDA_ERROR_INVALID_SOURCE = 300;
+
+ /**
+ * This indicates that the file specified was not found.
+ */
+ public static final int CUDA_ERROR_FILE_NOT_FOUND = 301;
+
+ /**
+ * This indicates that a link to a shared object failed to resolve.
+ */
+ public static final int CUDA_ERROR_SHARED_OBJECT_SYMBOL_NOT_FOUND = 302;
+
+ /**
+ * This indicates that initialization of a shared object failed.
+ */
+ public static final int CUDA_ERROR_SHARED_OBJECT_INIT_FAILED = 303;
+
+ /**
+ * This indicates that an OS call failed.
+ */
+ public static final int CUDA_ERROR_OPERATING_SYSTEM = 304;
+
+ /**
+ * This indicates that a resource handle passed to the API call was not
+ * valid. Resource handles are opaque types like ::CUstream and ::CUevent.
+ */
+ public static final int CUDA_ERROR_INVALID_HANDLE = 400;
+
+ /**
+ * This indicates that a named symbol was not found. Examples of symbols
+ * are global/constant variable names, texture names, and surface names.
+ */
+ public static final int CUDA_ERROR_NOT_FOUND = 500;
+
+ /**
+ * This indicates that asynchronous operations issued previously have not
+ * completed yet. This result is not actually an error, but must be indicated
+ * differently than ::CUDA_SUCCESS (which indicates completion). Calls that
+ * may return this value include ::cuEventQuery() and ::cuStreamQuery().
+ */
+ public static final int CUDA_ERROR_NOT_READY = 600;
+
+ /**
+ * An exception occurred on the device while executing a kernel. Common
+ * causes include dereferencing an invalid device pointer and accessing
+ * out of bounds shared memory. The context cannot be used, so it must
+ * be destroyed (and a new one should be created). All existing device
+ * memory allocations from this context are invalid and must be
+ * reconstructed if the program is to continue using CUDA.
+ */
+ public static final int CUDA_ERROR_LAUNCH_FAILED = 700;
+
+ /**
+ * This indicates that a launch did not occur because it did not have
+ * appropriate resources. This error usually indicates that the user has
+ * attempted to pass too many arguments to the device kernel, or the
+ * kernel launch specifies too many threads for the kernel's register
+ * count. Passing arguments of the wrong size (i.e. a 64-bit pointer
+ * when a 32-bit int is expected) is equivalent to passing too many
+ * arguments and can also result in this error.
+ */
+ public static final int CUDA_ERROR_LAUNCH_OUT_OF_RESOURCES = 701;
+
+ /**
+ * This indicates that the device kernel took too long to execute. This can
+ * only occur if timeouts are enabled - see the device attribute
+ * ::CU_DEVICE_ATTRIBUTE_KERNEL_EXEC_TIMEOUT for more information. The
+ * context cannot be used (and must be destroyed similar to
+ * ::CUDA_ERROR_LAUNCH_FAILED). All existing device memory allocations from
+ * this context are invalid and must be reconstructed if the program is to
+ * continue using CUDA.
+ */
+ public static final int CUDA_ERROR_LAUNCH_TIMEOUT = 702;
+
+ /**
+ * This error indicates a kernel launch that uses an incompatible texturing
+ * mode.
+ */
+ public static final int CUDA_ERROR_LAUNCH_INCOMPATIBLE_TEXTURING = 703;
+
+ /**
+ * This error indicates that a call to ::cuCtxEnablePeerAccess() is
+ * trying to re-enable peer access to a context which has already
+ * had peer access to it enabled.
+ */
+ public static final int CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED = 704;
+
+ /**
+ * This error indicates that a call to ::cuMemPeerRegister is trying to
+ * register memory from a context which has not had peer access
+ * enabled yet via ::cuCtxEnablePeerAccess(), or that
+ * ::cuCtxDisablePeerAccess() is trying to disable peer access
+ * which has not been enabled yet.
+ */
+ public static final int CUDA_ERROR_PEER_ACCESS_NOT_ENABLED = 705;
+
+ /**
+ * This error indicates that a call to ::cuMemPeerRegister is trying to
+ * register already-registered memory.
+ * @deprecated This value has been added in CUDA 4.0 RC,
+ * and removed in CUDA 4.0 RC2
+ */
+ public static final int CUDA_ERROR_PEER_MEMORY_ALREADY_REGISTERED = 706;
+
+ /**
+ * This error indicates that a call to ::cuMemPeerUnregister is trying to
+ * unregister memory that has not been registered.
+ * @deprecated This value has been added in CUDA 4.0 RC,
+ * and removed in CUDA 4.0 RC2
+ */
+ public static final int CUDA_ERROR_PEER_MEMORY_NOT_REGISTERED = 707;
+
+ /**
+ * This error indicates that ::cuCtxCreate was called with the flag
+ * ::CU_CTX_PRIMARY on a device which already has initialized its
+ * primary context.
+ */
+ public static final int CUDA_ERROR_PRIMARY_CONTEXT_ACTIVE = 708;
+
+ /**
+ * This error indicates that the context current to the calling thread
+ * has been destroyed using ::cuCtxDestroy, or is a primary context which
+ * has not yet been initialized.
+ */
+ public static final int CUDA_ERROR_CONTEXT_IS_DESTROYED = 709;
+
+ /**
+ * A device-side assert triggered during kernel execution. The context
+ * cannot be used anymore, and must be destroyed. All existing device
+ * memory allocations from this context are invalid and must be
+ * reconstructed if the program is to continue using CUDA.
+ */
+ public static final int CUDA_ERROR_ASSERT = 710;
+
+ /**
+ * This error indicates that the hardware resources required to enable
+ * peer access have been exhausted for one or more of the devices
+ * passed to ::cuCtxEnablePeerAccess().
+ */
+ public static final int CUDA_ERROR_TOO_MANY_PEERS = 711;
+
+ /**
+ * This error indicates that the memory range passed to ::cuMemHostRegister()
+ * has already been registered.
+ */
+ public static final int CUDA_ERROR_HOST_MEMORY_ALREADY_REGISTERED = 712;
+
+ /**
+ * This error indicates that the pointer passed to ::cuMemHostUnregister()
+ * does not correspond to any currently registered memory region.
+ */
+ public static final int CUDA_ERROR_HOST_MEMORY_NOT_REGISTERED = 713;
+
+ /**
+ * This error indicates that the attempted operation is not permitted.
+ */
+ public static final int CUDA_ERROR_NOT_PERMITTED = 800;
+
+ /**
+ * This error indicates that the attempted operation is not supported
+ * on the current system or device.
+ */
+ public static final int CUDA_ERROR_NOT_SUPPORTED = 801;
+
+ /**
+ * This indicates that an unknown internal error has occurred.
+ */
+ public static final int CUDA_ERROR_UNKNOWN = 999;
+
+ /**
+ * Returns the String identifying the given CUresult
+ *
+ * @param result The CUresult value
+ * @return The String identifying the given CUresult
+ */
+ public static String stringFor(int result) {
+ switch (result) {
+ case CUDA_SUCCESS:
+ return "CUDA_SUCCESS";
+ case CUDA_ERROR_INVALID_VALUE:
+ return "CUDA_ERROR_INVALID_VALUE";
+ case CUDA_ERROR_OUT_OF_MEMORY:
+ return "CUDA_ERROR_OUT_OF_MEMORY";
+ case CUDA_ERROR_NOT_INITIALIZED:
+ return "CUDA_ERROR_NOT_INITIALIZED";
+ case CUDA_ERROR_DEINITIALIZED:
+ return "CUDA_ERROR_DEINITIALIZED";
+ case CUDA_ERROR_PROFILER_DISABLED:
+ return "CUDA_ERROR_PROFILER_DISABLED";
+ case CUDA_ERROR_PROFILER_NOT_INITIALIZED:
+ return "CUDA_ERROR_PROFILER_NOT_INITIALIZED";
+ case CUDA_ERROR_PROFILER_ALREADY_STARTED:
+ return "CUDA_ERROR_PROFILER_ALREADY_STARTED";
+ case CUDA_ERROR_PROFILER_ALREADY_STOPPED:
+ return "CUDA_ERROR_PROFILER_ALREADY_STOPPED";
+ case CUDA_ERROR_NO_DEVICE:
+ return "CUDA_ERROR_NO_DEVICE";
+ case CUDA_ERROR_INVALID_DEVICE:
+ return "CUDA_ERROR_INVALID_DEVICE";
+ case CUDA_ERROR_INVALID_IMAGE:
+ return "CUDA_ERROR_INVALID_IMAGE";
+ case CUDA_ERROR_INVALID_CONTEXT:
+ return "CUDA_ERROR_INVALID_CONTEXT";
+ case CUDA_ERROR_CONTEXT_ALREADY_CURRENT:
+ return "CUDA_ERROR_CONTEXT_ALREADY_CURRENT";
+ case CUDA_ERROR_MAP_FAILED:
+ return "CUDA_ERROR_MAP_FAILED";
+ case CUDA_ERROR_UNMAP_FAILED:
+ return "CUDA_ERROR_UNMAP_FAILED";
+ case CUDA_ERROR_ARRAY_IS_MAPPED:
+ return "CUDA_ERROR_ARRAY_IS_MAPPED";
+ case CUDA_ERROR_ALREADY_MAPPED:
+ return "CUDA_ERROR_ALREADY_MAPPED";
+ case CUDA_ERROR_NO_BINARY_FOR_GPU:
+ return "CUDA_ERROR_NO_BINARY_FOR_GPU";
+ case CUDA_ERROR_ALREADY_ACQUIRED:
+ return "CUDA_ERROR_ALREADY_ACQUIRED";
+ case CUDA_ERROR_NOT_MAPPED:
+ return "CUDA_ERROR_NOT_MAPPED";
+ case CUDA_ERROR_NOT_MAPPED_AS_ARRAY:
+ return "CUDA_ERROR_NOT_MAPPED_AS_ARRAY";
+ case CUDA_ERROR_NOT_MAPPED_AS_POINTER:
+ return "CUDA_ERROR_NOT_MAPPED_AS_POINTER";
+ case CUDA_ERROR_ECC_UNCORRECTABLE:
+ return "CUDA_ERROR_ECC_UNCORRECTABLE";
+ case CUDA_ERROR_UNSUPPORTED_LIMIT:
+ return "CUDA_ERROR_UNSUPPORTED_LIMIT";
+ case CUDA_ERROR_CONTEXT_ALREADY_IN_USE:
+ return "CUDA_ERROR_CONTEXT_ALREADY_IN_USE";
+ case CUDA_ERROR_PEER_ACCESS_UNSUPPORTED:
+ return "CUDA_ERROR_PEER_ACCESS_UNSUPPORTED";
+ case CUDA_ERROR_INVALID_SOURCE:
+ return "CUDA_ERROR_INVALID_SOURCE";
+ case CUDA_ERROR_FILE_NOT_FOUND:
+ return "CUDA_ERROR_FILE_NOT_FOUND";
+ case CUDA_ERROR_SHARED_OBJECT_SYMBOL_NOT_FOUND:
+ return "CUDA_ERROR_SHARED_OBJECT_SYMBOL_NOT_FOUND";
+ case CUDA_ERROR_SHARED_OBJECT_INIT_FAILED:
+ return "CUDA_ERROR_SHARED_OBJECT_INIT_FAILED";
+ case CUDA_ERROR_OPERATING_SYSTEM:
+ return "CUDA_ERROR_OPERATING_SYSTEM";
+ case CUDA_ERROR_INVALID_HANDLE:
+ return "CUDA_ERROR_INVALID_HANDLE";
+ case CUDA_ERROR_NOT_FOUND:
+ return "CUDA_ERROR_NOT_FOUND";
+ case CUDA_ERROR_NOT_READY:
+ return "CUDA_ERROR_NOT_READY";
+ case CUDA_ERROR_LAUNCH_FAILED:
+ return "CUDA_ERROR_LAUNCH_FAILED";
+ case CUDA_ERROR_LAUNCH_OUT_OF_RESOURCES:
+ return "CUDA_ERROR_LAUNCH_OUT_OF_RESOURCES";
+ case CUDA_ERROR_LAUNCH_TIMEOUT:
+ return "CUDA_ERROR_LAUNCH_TIMEOUT";
+ case CUDA_ERROR_LAUNCH_INCOMPATIBLE_TEXTURING:
+ return "CUDA_ERROR_LAUNCH_INCOMPATIBLE_TEXTURING";
+ case CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED:
+ return "CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED";
+ case CUDA_ERROR_PEER_ACCESS_NOT_ENABLED:
+ return "CUDA_ERROR_PEER_ACCESS_NOT_ENABLED";
+ case CUDA_ERROR_PEER_MEMORY_ALREADY_REGISTERED:
+ return "CUDA_ERROR_PEER_MEMORY_ALREADY_REGISTERED";
+ case CUDA_ERROR_PEER_MEMORY_NOT_REGISTERED:
+ return "CUDA_ERROR_PEER_MEMORY_NOT_REGISTERED";
+ case CUDA_ERROR_PRIMARY_CONTEXT_ACTIVE:
+ return "CUDA_ERROR_PRIMARY_CONTEXT_ACTIVE";
+ case CUDA_ERROR_CONTEXT_IS_DESTROYED:
+ return "CUDA_ERROR_CONTEXT_IS_DESTROYED";
+ case CUDA_ERROR_ASSERT:
+ return "CUDA_ERROR_ASSERT";
+ case CUDA_ERROR_TOO_MANY_PEERS:
+ return "CUDA_ERROR_TOO_MANY_PEERS";
+ case CUDA_ERROR_HOST_MEMORY_ALREADY_REGISTERED:
+ return "CUDA_ERROR_HOST_MEMORY_ALREADY_REGISTERED";
+ case CUDA_ERROR_HOST_MEMORY_NOT_REGISTERED:
+ return "CUDA_ERROR_HOST_MEMORY_NOT_REGISTERED";
+ case CUDA_ERROR_NOT_PERMITTED:
+ return "CUDA_ERROR_NOT_PERMITTED";
+ case CUDA_ERROR_NOT_SUPPORTED:
+ return "CUDA_ERROR_NOT_SUPPORTED";
+ case CUDA_ERROR_UNKNOWN:
+ return "CUDA_ERROR_UNKNOWN";
+ }
+ return "INVALID CUresult: " + result;
+ }
+
+ /**
+ * Private constructor to prevent instantiation.
+ */
+ private CUresult() {
+ }
+
+}
diff --git a/src/com/sheepit/client/hardware/gpu/GPU.java b/src/com/sheepit/client/hardware/gpu/GPU.java
new file mode 100644
index 0000000..fdbf8a4
--- /dev/null
+++ b/src/com/sheepit/client/hardware/gpu/GPU.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2013-2014 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.hardware.gpu;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import com.sheepit.client.os.OS;
+import com.sun.jna.Native;
+import com.sun.jna.NativeLong;
+
+public class GPU {
+ public static List devices = null;
+
+ public static boolean generate() {
+ OS os = OS.getOS();
+ String path = os.getCUDALib();
+ if (path == null) {
+ System.out.println("GPU.listDevices failed to get CUDA lib");
+ return false;
+ }
+ CUDA cudalib = null;
+ try {
+ cudalib = (CUDA) Native.loadLibrary(path, CUDA.class);
+ }
+ catch (java.lang.UnsatisfiedLinkError e) {
+ System.out.println("GPU.listDevices failed to load CUDA lib");
+ return false;
+ }
+ catch (java.lang.ExceptionInInitializerError e) {
+ System.out.println("GPU.listDevices ExceptionInInitializerError " + e);
+ return false;
+ }
+ catch (Exception e) {
+ System.out.println("GPU.listDevices generic exception " + e);
+ return false;
+ }
+
+ int result = CUresult.CUDA_ERROR_UNKNOWN;
+
+ result = cudalib.cuInit(0);
+ if (result != CUresult.CUDA_SUCCESS) {
+ return false;
+ }
+
+ if (result == CUresult.CUDA_ERROR_NO_DEVICE) {
+ System.out.println("NO DEVICE");
+ return false;
+ }
+
+ int[] count = new int[1];
+ result = cudalib.cuDeviceGetCount(count);
+
+ if (result != CUresult.CUDA_SUCCESS) {
+ System.out.println("GPU.listDevices cuDeviceGetCount failed (ret: " + CUresult.stringFor(result) + ")");
+ return false;
+ }
+
+ devices = new LinkedList();
+
+ for (int num = 0; num < count[0]; num++) {
+ byte name[] = new byte[256];
+
+ result = cudalib.cuDeviceGetName(name, 256, num);
+ if (result != CUresult.CUDA_SUCCESS) {
+ System.out.println("GPU.listDevices cuDeviceGetName failed (ret: " + CUresult.stringFor(result) + ")");
+ continue;
+ }
+
+ NativeLong[] ram = new NativeLong[1];
+ result = cudalib.cuDeviceTotalMem(ram, num);
+
+ if (result != CUresult.CUDA_SUCCESS) {
+ System.out.println("GPU.listDevices cuDeviceTotalMem failed (ret: " + CUresult.stringFor(result) + ")");
+ return false;
+ }
+
+ devices.add(new GPUDevice(new String(name).trim(), ram[0].longValue(), "CUDA_" + Integer.toString(num)));
+ }
+ return true;
+ }
+
+ public static List listDevices() {
+ if (devices == null) {
+ generate();
+ }
+ if (devices == null) {
+ return null;
+ }
+
+ List devs = new LinkedList();
+ for (GPUDevice dev : devices) {
+ devs.add(dev.getModel());
+ }
+ return devs;
+ }
+
+ public static GPUDevice getGPUDevice(String device_model) {
+ if (device_model == null) {
+ return null;
+ }
+
+ if (devices == null) {
+ generate();
+ }
+
+ if (devices == null) {
+ return null;
+ }
+
+ for (GPUDevice dev : devices) {
+ if (device_model.equals(dev.getCudaName()) || device_model.equals(dev.getModel())) {
+ return dev;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/com/sheepit/client/hardware/gpu/GPUDevice.java b/src/com/sheepit/client/hardware/gpu/GPUDevice.java
new file mode 100644
index 0000000..5c78388
--- /dev/null
+++ b/src/com/sheepit/client/hardware/gpu/GPUDevice.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2013-2014 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.hardware.gpu;
+
+public class GPUDevice {
+ private String model;
+ private long memory; // in B
+
+ private String cudaName;
+
+ public GPUDevice(String model, long ram, String cuda) {
+ this.model = model;
+ this.memory = ram;
+ this.cudaName = cuda;
+ }
+
+ public String getModel() {
+ return model;
+ }
+
+ public void setModel(String model) {
+ this.model = model;
+ }
+
+ public long getMemory() {
+ return memory;
+ }
+
+ public void setMemory(long memory) {
+ this.memory = memory;
+ }
+
+ public String getCudaName() {
+ return cudaName;
+ }
+
+ public void setCudaName(String cudaName) {
+ this.cudaName = cudaName;
+ }
+
+ @Override
+ public String toString() {
+ return "GPUDevice [model=" + model + ", memory=" + memory + ", cudaName=" + cudaName + "]";
+ }
+
+}
diff --git a/src/com/sheepit/client/network/ProxyAuthenticator.java b/src/com/sheepit/client/network/ProxyAuthenticator.java
new file mode 100644
index 0000000..196cba4
--- /dev/null
+++ b/src/com/sheepit/client/network/ProxyAuthenticator.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 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.network;
+
+import java.net.Authenticator;
+import java.net.PasswordAuthentication;
+
+public class ProxyAuthenticator extends Authenticator {
+
+ private String user;
+ private String password;
+
+ public ProxyAuthenticator(String user, String password) {
+ this.user = user;
+ this.password = password;
+ }
+
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication(user, password.toCharArray());
+ }
+}
diff --git a/src/com/sheepit/client/os/Linux.java b/src/com/sheepit/client/os/Linux.java
new file mode 100644
index 0000000..cd07cc9
--- /dev/null
+++ b/src/com/sheepit/client/os/Linux.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2010-2014 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.os;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Scanner;
+
+import com.sheepit.client.Log;
+import com.sheepit.client.Utils;
+import com.sheepit.client.hardware.cpu.CPU;
+
+public class Linux extends OS {
+ private final String NICE_BINARY_PATH = "nice";
+ private Boolean hasNiceBinary;
+
+ public Linux() {
+ super();
+ this.hasNiceBinary = null;
+ }
+
+ public String name() {
+ return "linux";
+ }
+
+ @Override
+ public CPU getCPU() {
+ CPU ret = new CPU();
+ try {
+ String filePath = "/proc/cpuinfo";
+ Scanner scanner = new Scanner(new File(filePath));
+
+ while (scanner.hasNextLine()) {
+ String line = scanner.nextLine();
+ if (line.startsWith("model name")) {
+ String buf[] = line.split(":");
+ if (buf.length > 0) {
+ ret.setName(buf[1].trim());
+ }
+ }
+
+ if (line.startsWith("cpu family")) {
+ String buf[] = line.split(":");
+ if (buf.length > 0) {
+ ret.setFamily(buf[1].trim());
+ }
+ }
+
+ if (line.startsWith("model") && line.startsWith("model name") == false) {
+ String buf[] = line.split(":");
+ if (buf.length > 0) {
+ ret.setModel(buf[1].trim());
+ }
+ }
+ }
+ scanner.close();
+ }
+ catch (java.lang.NoClassDefFoundError e) {
+ System.err.println("OS.Linux::getCPU error " + e + " mostly because Scanner class was introducted by Java 5 and you are running are lower version");
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+ return ret;
+ }
+
+ @Override
+ public int getMemory() {
+ try {
+ String filePath = "/proc/meminfo";
+ Scanner scanner = new Scanner(new File(filePath));
+
+ while (scanner.hasNextLine()) {
+ String line = scanner.nextLine();
+
+ if (line.startsWith("MemTotal")) {
+ String buf[] = line.split(":");
+ if (buf.length > 0) {
+ Integer buf2 = new Integer(buf[1].trim().split(" ")[0]);
+ return (((buf2 / 262144) + 1) * 262144); // 256*1024 = 262144
+ }
+ }
+ }
+ scanner.close();
+ }
+ catch (java.lang.NoClassDefFoundError e) {
+ System.err.println("Machine::type error " + e + " mostly because Scanner class was introducted by Java 5 and you are running are lower version");
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return 0;
+ }
+
+ @Override
+ public String getCUDALib() {
+ return "cuda";
+ }
+
+ @Override
+ public Process exec(String[] command) throws IOException {
+ // the renderer have a lib directory so add to the LD_LIBRARY_PATH
+ // (even if we are not sure that it is the renderer who is launch
+
+ Map new_env = new HashMap();
+ new_env.putAll(java.lang.System.getenv()); // clone the env
+ Boolean has_ld_library_path = new_env.containsKey("LD_LIBRARY_PATH");
+
+ String lib_dir = (new File(command[0])).getParent() + File.separator + "lib";
+ String new_ld_library_path = "/lib:/lib64:/usr/lib:/usr/lib64:/lib/i386-linux-gnu:/lib/x86_64-linux-gnu:/usr/share/local" + ":" + lib_dir;
+ if (has_ld_library_path == false) {
+ new_env.put("LD_LIBRARY_PATH", new_ld_library_path);
+ }
+ else {
+ new_env.put("LD_LIBRARY_PATH", new_env.get("LD_LIBRARY_PATH") + ":" + new_ld_library_path);
+ }
+
+ String[] actual_command = command;
+ if (this.hasNiceBinary == null) {
+ this.checkNiceAvailability();
+ }
+ if (this.hasNiceBinary.booleanValue()) {
+ String[] low = { NICE_BINARY_PATH, "-n", "19" }; // launch the process in lowest priority
+ actual_command = Utils.concatAll(low, command);
+ }
+ else {
+ Log.getInstance(null).error("No low priority binary, will not launch renderer in normal prioity");
+ }
+
+ ProcessBuilder builder = new ProcessBuilder(actual_command);
+ builder.redirectErrorStream(true);
+ Map env = builder.environment();
+ env.putAll(new_env);
+ return builder.start();
+ }
+
+ protected void checkNiceAvailability() {
+ ProcessBuilder builder = new ProcessBuilder();
+ builder.command(NICE_BINARY_PATH);
+ builder.redirectErrorStream(true);
+ Process process = null;
+ try {
+ process = builder.start();
+ this.hasNiceBinary = true;
+ }
+ catch (IOException e) {
+ this.hasNiceBinary = false;
+ Log.getInstance(null).error("Failed to find low priority binary, will not launch renderer in normal prioity (" + e + ")");
+ }
+ finally {
+ if (process != null) {
+ process.destroy();
+ }
+ }
+ }
+}
diff --git a/src/com/sheepit/client/os/Mac.java b/src/com/sheepit/client/os/Mac.java
new file mode 100644
index 0000000..7c24b4f
--- /dev/null
+++ b/src/com/sheepit/client/os/Mac.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2010-2014 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.os;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+import com.sheepit.client.Log;
+import com.sheepit.client.Utils;
+import com.sheepit.client.hardware.cpu.CPU;
+
+public class Mac extends OS {
+ private final String NICE_BINARY_PATH = "nice";
+ private Boolean hasNiceBinary;
+
+ public Mac() {
+ super();
+ this.hasNiceBinary = null;
+ }
+
+ public String name() {
+ return "mac";
+ }
+
+ @Override
+ public CPU getCPU() {
+ CPU ret = new CPU();
+
+ String command = "sysctl machdep.cpu.family machdep.cpu.brand_string";
+
+ Process p = null;
+ BufferedReader input = null;
+ try {
+ String line;
+ p = Runtime.getRuntime().exec(command);
+ input = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+ while ((line = input.readLine()) != null) {
+ String option_cpu_family = "machdep.cpu.family:";
+ String option_model_name = "machdep.cpu.brand_string:";
+ if (line.startsWith(option_model_name)) {
+ ret.setName(line.substring(option_model_name.length()).trim());
+ }
+ if (line.startsWith(option_cpu_family)) {
+ ret.setFamily(line.substring(option_cpu_family.length()).trim());
+ }
+ }
+ input.close();
+ input = null;
+ }
+ catch (Exception err) {
+ System.out.println("exception " + err);
+ err.printStackTrace();
+ ret.setName("Unknown Mac name");
+ ret.setFamily("Unknown Mac family");
+ }
+ finally {
+ if (input != null) {
+ try {
+ input.close();
+ }
+ catch (IOException e) {
+ }
+ }
+
+ if (p != null) {
+ p.destroy();
+ }
+ }
+
+ ret.setModel("Unknown");
+
+ return ret;
+ }
+
+ @Override
+ public int getMemory() {
+ String command = "sysctl hw.memsize";
+
+ Process p = null;
+ BufferedReader input = null;
+ try {
+ String line;
+ p = Runtime.getRuntime().exec(command);
+ input = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+ while ((line = input.readLine()) != null) {
+ String option = "hw.memsize:";
+ if (line.startsWith(option)) {
+ String memory = line.substring(option.length()).trim(); // memory in bytes
+
+ return (int) (Long.parseLong(memory) / 1024);
+ }
+ }
+ input.close();
+ input = null;
+ }
+ catch (Exception err) {
+ System.out.println("exception " + err);
+ err.printStackTrace();
+ }
+ finally {
+ if (input != null) {
+ try {
+ input.close();
+ }
+ catch (IOException e) {
+ }
+ }
+
+ if (p != null) {
+ p.destroy();
+ }
+ }
+
+ return -1;
+ }
+
+ @Override
+ public Process exec(String[] command) throws IOException {
+ String[] actual_command = command;
+ if (this.hasNiceBinary == null) {
+ this.checkNiceAvailability();
+ }
+ if (this.hasNiceBinary.booleanValue()) {
+ String[] low = { NICE_BINARY_PATH, "-n", "19" }; // launch the process in lowest priority
+ actual_command = Utils.concatAll(low, command);
+ }
+ else {
+ Log.getInstance(null).error("No low priority binary, will not launch renderer in normal prioity");
+ }
+ ProcessBuilder builder = new ProcessBuilder(actual_command);
+ builder.redirectErrorStream(true);
+ return builder.start();
+ }
+
+ @Override
+ public String getCUDALib() {
+ return "/usr/local/cuda/lib/libcuda.dylib";
+ }
+
+ protected void checkNiceAvailability() {
+ ProcessBuilder builder = new ProcessBuilder();
+ builder.command(NICE_BINARY_PATH);
+ builder.redirectErrorStream(true);
+ Process process = null;
+ try {
+ process = builder.start();
+ this.hasNiceBinary = true;
+ }
+ catch (IOException e) {
+ this.hasNiceBinary = false;
+ Log.getInstance(null).error("Failed to find low priority binary, will not launch renderer in normal prioity (" + e + ")");
+ }
+ finally {
+ if (process != null) {
+ process.destroy();
+ }
+ }
+ }
+}
diff --git a/src/com/sheepit/client/os/OS.java b/src/com/sheepit/client/os/OS.java
new file mode 100644
index 0000000..c343f5d
--- /dev/null
+++ b/src/com/sheepit/client/os/OS.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2010-2014 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.os;
+
+import java.io.IOException;
+
+import com.sheepit.client.hardware.cpu.CPU;
+
+public abstract class OS {
+ public String name() {
+ return "others";
+ }
+
+ public abstract CPU getCPU();
+
+ public abstract int getMemory();
+
+ public String getCUDALib() {
+ return null;
+ }
+
+ public Process exec(String[] command) throws IOException {
+ ProcessBuilder builder = new ProcessBuilder(command);
+ builder.redirectErrorStream(true);
+ return builder.start();
+ }
+
+ public boolean kill(Process proc) {
+ if (proc != null) {
+ proc.destroy();
+ return true;
+ }
+ return false;
+ }
+
+ public static OS getOS() {
+ String os = System.getProperty("os.name").toLowerCase();
+ if (os.indexOf("win") >= 0) {
+ return new Windows();
+ }
+ else if (os.indexOf("mac") >= 0) {
+ return new Mac();
+ }
+ else if (os.indexOf("nix") >= 0 || os.indexOf("nux") >= 0) {
+ return new Linux();
+ }
+ else {
+ return null;
+ }
+ }
+}
diff --git a/src/com/sheepit/client/os/Windows.java b/src/com/sheepit/client/os/Windows.java
new file mode 100644
index 0000000..fd450f8
--- /dev/null
+++ b/src/com/sheepit/client/os/Windows.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2010-2014 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.os;
+
+import java.io.IOException;
+
+import com.sheepit.client.hardware.cpu.CPU;
+import com.sheepit.client.os.windows.Kernel32Lib;
+import com.sheepit.client.os.windows.WinProcess;
+import com.sun.jna.Native;
+import com.sun.jna.platform.win32.Advapi32Util;
+import com.sun.jna.platform.win32.Kernel32;
+import com.sun.jna.platform.win32.WinBase.MEMORYSTATUSEX;
+import com.sun.jna.platform.win32.WinReg;
+
+public class Windows extends OS {
+
+ public String name() {
+ return "windows";
+ }
+
+ @Override
+ public CPU getCPU() {
+ CPU ret = new CPU();
+ try {
+ String[] identifier = java.lang.System.getenv("PROCESSOR_IDENTIFIER").split(" ");
+ for (int i = 0; i < (identifier.length - 1); i++) {
+ if (identifier[i].equals("Family")) {
+ ret.setFamily(new String(identifier[i + 1]));
+ }
+ if (identifier[i].equals("Model")) {
+ ret.setModel(new String(identifier[i + 1]));
+ }
+ }
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ try {
+ final String cpuRegistryRoot = "HARDWARE\\DESCRIPTION\\System\\CentralProcessor";
+ String[] processorIds = Advapi32Util.registryGetKeys(WinReg.HKEY_LOCAL_MACHINE, cpuRegistryRoot);
+ if (processorIds.length > 0) {
+ String processorId = processorIds[0];
+ String cpuRegistryPath = cpuRegistryRoot + "\\" + processorId;
+ ret.setName(Advapi32Util.registryGetStringValue(WinReg.HKEY_LOCAL_MACHINE, cpuRegistryPath, "ProcessorNameString").trim());
+ }
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ // override the arch
+ String env_arch = java.lang.System.getenv("PROCESSOR_ARCHITEW6432");
+ if (env_arch == null || env_arch.compareTo("") == 0) {
+ env_arch = java.lang.System.getenv("PROCESSOR_ARCHITECTURE");
+ }
+ if (env_arch.compareTo("AMD64") == 0) {
+ ret.setArch("64bit");
+ }
+ else {
+ ret.setArch("32bit");
+ }
+
+ return ret;
+ }
+
+ @Override
+ public int getMemory() {
+ try {
+ MEMORYSTATUSEX _memory = new MEMORYSTATUSEX();
+ if (Kernel32.INSTANCE.GlobalMemoryStatusEx(_memory)) {
+ return (int) (_memory.ullTotalPhys.longValue() / 1024); // size in KB
+ }
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+ return 0;
+ }
+
+ @Override
+ public String getCUDALib() {
+ return "nvcuda";
+ }
+
+ @Override
+ public Process exec(String[] command) throws IOException {
+ // disable a popup because the renderer might crash (seg fault)
+ Kernel32Lib kernel32lib = null;
+ try {
+ kernel32lib = (Kernel32Lib) Native.loadLibrary(Kernel32Lib.path, Kernel32Lib.class);
+ kernel32lib.SetErrorMode(Kernel32Lib.SEM_NOGPFAULTERRORBOX);
+ }
+ catch (java.lang.UnsatisfiedLinkError e) {
+ System.out.println("OS.Windows::exec failed to load kernel32lib " + e);
+ }
+ catch (java.lang.ExceptionInInitializerError e) {
+ System.out.println("OS.Windows::exec failed to load kernel32lib " + e);
+ }
+ catch (Exception e) {
+ System.out.println("OS.Windows::exec failed to load kernel32lib " + e);
+ }
+
+ ProcessBuilder builder = new ProcessBuilder(command);
+ builder.redirectErrorStream(true);
+ Process p = builder.start();
+ WinProcess wproc = new WinProcess(p);
+ wproc.setPriority(WinProcess.PRIORITY_BELOW_NORMAL);
+ return p;
+ }
+
+ @Override
+ public boolean kill(Process process) {
+ if (process != null) {
+ WinProcess wproc = new WinProcess(process);
+ wproc.kill();
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/com/sheepit/client/os/windows/Kernel32Lib.java b/src/com/sheepit/client/os/windows/Kernel32Lib.java
new file mode 100644
index 0000000..56a4d2b
--- /dev/null
+++ b/src/com/sheepit/client/os/windows/Kernel32Lib.java
@@ -0,0 +1,220 @@
+/* This file was originally taken from JNA project (https://github.com/twall/jna)
+ * filename: contrib/platform/src/com/sun/jna/platform/win32/Tlhelp32.java
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+package com.sheepit.client.os.windows;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.sun.jna.Library;
+import com.sun.jna.Pointer;
+import com.sun.jna.Structure;
+import com.sun.jna.platform.win32.BaseTSD;
+import com.sun.jna.platform.win32.WinDef;
+import com.sun.jna.platform.win32.WinDef.DWORD;
+import com.sun.jna.platform.win32.WinNT;
+import com.sun.jna.platform.win32.WinNT.HANDLE;
+
+public interface Kernel32Lib extends Library {
+ public static final String path = "kernel32";
+
+ /**
+ * Includes all heaps of the process specified in th32ProcessID in the snapshot. To enumerate the heaps, see
+ * Heap32ListFirst.
+ */
+ WinDef.DWORD TH32CS_SNAPHEAPLIST = new WinDef.DWORD(0x00000001);
+
+ /**
+ * Includes all processes in the system in the snapshot. To enumerate the processes, see Process32First.
+ */
+ WinDef.DWORD TH32CS_SNAPPROCESS = new WinDef.DWORD(0x00000002);
+
+ /**
+ * Includes all threads in the system in the snapshot. To enumerate the threads, see Thread32First.
+ */
+ WinDef.DWORD TH32CS_SNAPTHREAD = new WinDef.DWORD(0x00000004);
+
+ /**
+ * Includes all modules of the process specified in th32ProcessID in the snapshot. To enumerate the modules, see
+ * Module32First. If the function fails with ERROR_BAD_LENGTH, retry the function until it succeeds.
+ */
+ WinDef.DWORD TH32CS_SNAPMODULE = new WinDef.DWORD(0x00000008);
+
+ /**
+ * Includes all 32-bit modules of the process specified in th32ProcessID in the snapshot when called from a 64-bit
+ * process. This flag can be combined with TH32CS_SNAPMODULE or TH32CS_SNAPALL. If the function fails with
+ * ERROR_BAD_LENGTH, retry the function until it succeeds.
+ */
+ WinDef.DWORD TH32CS_SNAPMODULE32 = new WinDef.DWORD(0x00000010);
+
+ /**
+ * Includes all processes and threads in the system, plus the heaps and modules of the process specified in th32ProcessID.
+ */
+ WinDef.DWORD TH32CS_SNAPALL = new WinDef.DWORD((TH32CS_SNAPHEAPLIST.intValue() | TH32CS_SNAPPROCESS.intValue() | TH32CS_SNAPTHREAD.intValue() | TH32CS_SNAPMODULE.intValue()));
+
+ /**
+ * Indicates that the snapshot handle is to be inheritable.
+ */
+ WinDef.DWORD TH32CS_INHERIT = new WinDef.DWORD(0x80000000);
+
+ /**
+ * The system does not display the Windows Error Reporting dialog.
+ * See: http://msdn.microsoft.com/en-us/library/ms680621%28VS.85%29.aspx
+ */
+ WinDef.DWORD SEM_NOGPFAULTERRORBOX = new WinDef.DWORD(0x0002);
+
+ /**
+ * Describes an entry from a list of the processes residing in the system address space when a snapshot was taken.
+ */
+ public static class PROCESSENTRY32 extends Structure {
+
+ public static class ByReference extends PROCESSENTRY32 implements Structure.ByReference {
+ public ByReference() {
+ }
+
+ public ByReference(Pointer memory) {
+ super(memory);
+ }
+ }
+
+ public PROCESSENTRY32() {
+ dwSize = new WinDef.DWORD(size());
+ }
+
+ public PROCESSENTRY32(Pointer memory) {
+ useMemory(memory);
+ read();
+ }
+
+ /**
+ * The size of the structure, in bytes. Before calling the Process32First function, set this member to
+ * sizeof(PROCESSENTRY32). If you do not initialize dwSize, Process32First fails.
+ */
+ public WinDef.DWORD dwSize;
+
+ /**
+ * This member is no longer used and is always set to zero.
+ */
+ public WinDef.DWORD cntUsage;
+
+ /**
+ * The process identifier.
+ */
+ public WinDef.DWORD th32ProcessID;
+
+ /**
+ * This member is no longer used and is always set to zero.
+ */
+ public BaseTSD.ULONG_PTR th32DefaultHeapID;
+
+ /**
+ * This member is no longer used and is always set to zero.
+ */
+ public WinDef.DWORD th32ModuleID;
+
+ /**
+ * The number of execution threads started by the process.
+ */
+ public WinDef.DWORD cntThreads;
+
+ /**
+ * The identifier of the process that created this process (its parent process).
+ */
+ public WinDef.DWORD th32ParentProcessID;
+
+ /**
+ * The base priority of any threads created by this process.
+ */
+ public WinDef.LONG pcPriClassBase;
+
+ /**
+ * This member is no longer used, and is always set to zero.
+ */
+ public WinDef.DWORD dwFlags;
+
+ /**
+ * The name of the executable file for the process. To retrieve the full path to the executable file, call the
+ * Module32First function and check the szExePath member of the MODULEENTRY32 structure that is returned.
+ * However, if the calling process is a 32-bit process, you must call the QueryFullProcessImageName function to
+ * retrieve the full path of the executable file for a 64-bit process.
+ */
+ public char[] szExeFile = new char[WinDef.MAX_PATH];
+
+ @Override
+ protected List getFieldOrder() {
+ return Arrays.asList(new String[] { "dwSize", "cntUsage", "th32ProcessID", "th32DefaultHeapID", "th32ModuleID", "cntThreads", "th32ParentProcessID", "pcPriClassBase", "dwFlags", "szExeFile" });
+ }
+ }
+
+ /**
+ * Takes a snapshot of the specified processes, as well as the heaps, modules, and threads used by these processes.
+ *
+ * @param dwFlags
+ * The portions of the system to be included in the snapshot.
+ *
+ * @param th32ProcessID
+ * The process identifier of the process to be included in the snapshot. This parameter can be zero to indicate
+ * the current process. This parameter is used when the TH32CS_SNAPHEAPLIST, TH32CS_SNAPMODULE,
+ * TH32CS_SNAPMODULE32, or TH32CS_SNAPALL value is specified. Otherwise, it is ignored and all processes are
+ * included in the snapshot.
+ *
+ * If the specified process is the Idle process or one of the CSRSS processes, this function fails and the last
+ * error code is ERROR_ACCESS_DENIED because their access restrictions prevent user-level code from opening them.
+ *
+ * If the specified process is a 64-bit process and the caller is a 32-bit process, this function fails and the
+ * last error code is ERROR_PARTIAL_COPY (299).
+ *
+ * @return
+ * If the function succeeds, it returns an open handle to the specified snapshot.
+ *
+ * If the function fails, it returns INVALID_HANDLE_VALUE. To get extended error information, call GetLastError.
+ * Possible error codes include ERROR_BAD_LENGTH.
+ */
+ public WinNT.HANDLE CreateToolhelp32Snapshot(WinDef.DWORD dwFlags, WinDef.DWORD th32ProcessID);
+
+ /**
+ * Retrieves information about the first process encountered in a system snapshot.
+ *
+ * @param hSnapshot A handle to the snapshot returned from a previous call to the CreateToolhelp32Snapshot function.
+ * @param lppe A pointer to a PROCESSENTRY32 structure. It contains process information such as the name of the
+ * executable file, the process identifier, and the process identifier of the parent process.
+ * @return
+ * Returns TRUE if the first entry of the process list has been copied to the buffer or FALSE otherwise. The
+ * ERROR_NO_MORE_FILES error value is returned by the GetLastError function if no processes exist or the snapshot
+ * does not contain process information.
+ */
+ public boolean Process32First(WinNT.HANDLE hSnapshot, Kernel32Lib.PROCESSENTRY32.ByReference lppe);
+
+ /**
+ * Retrieves information about the next process recorded in a system snapshot.
+ *
+ * @param hSnapshot A handle to the snapshot returned from a previous call to the CreateToolhelp32Snapshot function.
+ * @param lppe A pointer to a PROCESSENTRY32 structure.
+ * @return
+ * Returns TRUE if the next entry of the process list has been copied to the buffer or FALSE otherwise. The
+ * ERROR_NO_MORE_FILES error value is returned by the GetLastError function if no processes exist or the snapshot
+ * does not contain process information.
+ */
+ public boolean Process32Next(WinNT.HANDLE hSnapshot, Kernel32Lib.PROCESSENTRY32.ByReference lppe);
+
+ public boolean SetPriorityClass(HANDLE hProcess, int dwPriorityClass);
+
+ /**
+ * Controls whether the system will handle the specified types of serious errors or whether the process will handle them.
+ * See: http://msdn.microsoft.com/en-us/library/ms680621%28VS.85%29.aspx
+ * @param uMode The process error mode. This parameter can be one or more of the following values.
+ */
+ public int SetErrorMode(DWORD uMode);
+
+}
diff --git a/src/com/sheepit/client/os/windows/WinProcess.java b/src/com/sheepit/client/os/windows/WinProcess.java
new file mode 100644
index 0000000..0b3c6fa
--- /dev/null
+++ b/src/com/sheepit/client/os/windows/WinProcess.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2013 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.os.windows;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.sun.jna.Native;
+import com.sun.jna.Pointer;
+import com.sun.jna.platform.win32.Kernel32;
+import com.sun.jna.platform.win32.Kernel32Util;
+import com.sun.jna.platform.win32.WinDef.DWORD;
+import com.sun.jna.platform.win32.WinNT;
+
+public class WinProcess {
+ public static final int PRIORITY_IDLE = 0x40;
+ public static final int PRIORITY_BELOW_NORMAL = 0x4000;
+ public static final int PRIORITY_NORMAL = 0x20;
+ public static final int PRIORITY_ABOVE_NORMAL = 0x8000;
+ public static final int PRIORITY_HIGH = 0x80;
+ public static final int PRIORITY_REALTIME = 0x100;
+
+ private WinNT.HANDLE handle;
+ private int pid;
+ Kernel32Lib kernel32lib;
+
+ public WinProcess() {
+ this.handle = null;
+ this.pid = -1;
+ this.kernel32lib = null;
+ try {
+ this.kernel32lib = (Kernel32Lib) Native.loadLibrary(Kernel32Lib.path, Kernel32Lib.class);
+ }
+ catch (java.lang.UnsatisfiedLinkError e) {
+ System.out.println("WinProcess::construct " + e);
+ }
+ catch (java.lang.ExceptionInInitializerError e) {
+ System.out.println("WinProcess::construct " + e);
+ }
+ catch (Exception e) {
+ System.out.println("WinProcess::construct " + e);
+ }
+ }
+
+ public WinProcess(Process process) {
+ this();
+ try {
+ Field f = process.getClass().getDeclaredField("handle");
+ f.setAccessible(true);
+ long val = f.getLong(process);
+ this.handle = new WinNT.HANDLE();
+ this.handle.setPointer(Pointer.createConstant(val));
+ this.pid = Kernel32.INSTANCE.GetProcessId(this.handle);
+ }
+ catch (NoSuchFieldException e) {
+ }
+ catch (IllegalArgumentException e) {
+ }
+ catch (IllegalAccessException e) {
+ }
+ }
+
+ public WinProcess(int pid_) throws IOException {
+ this.handle = Kernel32.INSTANCE.OpenProcess(0x0400 | // PROCESS_QUERY_INFORMATION
+ 0x0800 | // PROCESS_SUSPEND_RESUME
+ 0x0001 | // PROCESS_TERMINATE
+ 0x0200 | // PROCESS_SET_INFORMATION
+ 0x00100000, // SYNCHRONIZE
+ false, pid);
+ if (this.handle == null) {
+ throw new IOException("OpenProcess failed: " + Kernel32Util.formatMessageFromLastErrorCode(Kernel32.INSTANCE.GetLastError()) + " (pid: " + pid_ + ")");
+ }
+ this.pid = pid_;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (this.handle != null) {
+ Kernel32.INSTANCE.CloseHandle(this.handle);
+ this.handle = null;
+ }
+ this.pid = -1;
+ }
+
+ public boolean kill() {
+ try {
+ List children = this.getChildren();
+ this.terminate();
+ for (WinProcess child : children) {
+ child.kill();
+ }
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return false;
+ }
+
+ public boolean setPriority(int priority) {
+ return this.kernel32lib.SetPriorityClass(this.handle, priority);
+ }
+
+ private void terminate() {
+ Kernel32.INSTANCE.TerminateProcess(this.handle, 0);
+ }
+
+ private List getChildren() throws IOException {
+ ArrayList result = new ArrayList();
+
+ WinNT.HANDLE hSnap = this.kernel32lib.CreateToolhelp32Snapshot(Kernel32Lib.TH32CS_SNAPPROCESS, new DWORD(0));
+ Kernel32Lib.PROCESSENTRY32.ByReference ent = new Kernel32Lib.PROCESSENTRY32.ByReference();
+ if (!this.kernel32lib.Process32First(hSnap, ent)) {
+ return result;
+ }
+ do {
+ if (ent.th32ParentProcessID.intValue() == this.pid) {
+ try {
+ result.add(new WinProcess(ent.th32ProcessID.intValue()));
+ }
+ catch (IOException e) {
+ System.err.println("WinProcess::getChildren, IOException " + e);
+ }
+ }
+ }
+ while (this.kernel32lib.Process32Next(hSnap, ent));
+
+ Kernel32.INSTANCE.CloseHandle(hSnap);
+
+ return result;
+ }
+
+ public String toString() {
+ return "WinProcess(pid: " + this.pid + ", handle " + this.handle + ")";
+ }
+}
diff --git a/src/com/sheepit/client/standalone/GuiText.java b/src/com/sheepit/client/standalone/GuiText.java
new file mode 100644
index 0000000..73fe442
--- /dev/null
+++ b/src/com/sheepit/client/standalone/GuiText.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2010-2014 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.standalone;
+
+import com.sheepit.client.Gui;
+import com.sheepit.client.Log;
+
+public class GuiText implements Gui {
+ private int framesRendered;
+ private Log log;
+
+ public GuiText() {
+ this.framesRendered = 0;
+ this.log = Log.getInstance(null);
+ }
+
+ @Override
+ public void start() {
+ }
+
+ @Override
+ public void stop() {
+ }
+
+ @Override
+ public void status(String msg_) {
+ System.out.println(msg_);
+ log.debug("GUI " + msg_);
+ }
+
+ @Override
+ public void error(String err_) {
+ System.out.println("Error " + err_);
+ log.error("Error " + err_);
+ }
+
+ @Override
+ public void AddFrameRendered() {
+ this.framesRendered += 1;
+ System.out.println("frame rendered: " + this.framesRendered);
+
+ }
+
+ @Override
+ public void framesRemaining(int n_) {
+ System.out.println("frame remaining: " + n_);
+ }
+
+}
diff --git a/src/com/sheepit/client/standalone/Worker.java b/src/com/sheepit/client/standalone/Worker.java
new file mode 100644
index 0000000..1e10a36
--- /dev/null
+++ b/src/com/sheepit/client/standalone/Worker.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2010-2014 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.standalone;
+
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.CmdLineParser;
+import static org.kohsuke.args4j.ExampleMode.REQUIRED;
+import org.kohsuke.args4j.Option;
+
+import java.io.File;
+import java.net.Authenticator;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.LinkedList;
+import com.sheepit.client.Client;
+import com.sheepit.client.Configuration;
+import com.sheepit.client.Configuration.ComputeType;
+import com.sheepit.client.Gui;
+import com.sheepit.client.Log;
+import com.sheepit.client.Pair;
+import com.sheepit.client.ShutdownHook;
+import com.sheepit.client.hardware.gpu.GPU;
+import com.sheepit.client.hardware.gpu.GPUDevice;
+import com.sheepit.client.network.ProxyAuthenticator;
+
+public class Worker {
+ @Option(name = "-server", usage = "Render-farm server, default https://www.sheepit-renderfarm.com", metaVar = "URL", required = false)
+ private String server = "https://www.sheepit-renderfarm.com";
+
+ @Option(name = "-login", usage = "User's login", metaVar = "LOGIN", required = true)
+ private String login = "";
+
+ @Option(name = "-password", usage = "User's password", metaVar = "PASSWORD", required = true)
+ private String password = "";
+
+ @Option(name = "-cache-dir", usage = "Cache/Working directory. Caution, everything in it not related to the render-farm will be removed", metaVar = "/tmp/cache", required = false)
+ private String cache_dir = null;
+
+ @Option(name = "-max-uploading-job", usage = "", metaVar = "1", required = false)
+ private int max_upload = -1;
+
+ @Option(name = "-gpu", usage = "CUDA name of the GPU used for the render, for example CUDA_0", metaVar = "CUDA_0", required = false)
+ private String gpu_device = null;
+
+ @Option(name = "-compute-method", usage = "CPU: only use cpu, GPU: only use gpu, CPU_GPU: can use cpu and gpu (not at the same time) if -gpu is not use it will not use the gpu", metaVar = "CPU_GPU", required = false)
+ private String method = null;
+
+ @Option(name = "-cores", usage = "Number of core/thread to use for the render", metaVar = "3", required = false)
+ private int nb_cores = -1;
+
+ @Option(name = "--verbose", usage = "Display log", required = false)
+ private boolean print_log = false;
+
+ @Option(name = "-request-time", usage = "H1:M1-H2:M2,H3:M3-H4:M4 Use the 24h format. For example to request job between 2am-8.30am and 5pm-11pm you should do --request-time 2:00-8:30,17:00-23:00 Caution, it's the requesting job time to get a project not the working time", metaVar = "2:00-8:30,17:00-23:00", required = false)
+ private String request_time = null;
+
+ @Option(name = "-proxy", usage = "URL of the proxy", metaVar = "http://login:password@host:port", required = false)
+ private String proxy = null;
+
+ @Option(name = "-extras", usage = "Extras data push on the authentication request", required = false)
+ private String extras = null;
+
+ @Option(name = "--version", usage = "Display application version", required = false)
+ private boolean display_version = false;
+
+ public static void main(String[] args) {
+ new Worker().doMain(args);
+ }
+
+ public void doMain(String[] args) {
+ CmdLineParser parser = new CmdLineParser(this);
+ try {
+ parser.parseArgument(args);
+ }
+ catch (CmdLineException e) {
+ System.err.println(e.getMessage());
+ System.err.println("Usage: ");
+ parser.printUsage(System.err);
+ System.err.println();
+ System.err.println("Example: java " + this.getClass().getName() + " " + parser.printExample(REQUIRED));
+ return;
+ }
+
+ if (display_version) {
+ Configuration config = new Configuration(null, "", "");
+ System.out.println("Version: " + config.getJarVersion());
+ return;
+ }
+
+ ComputeType compute_method = ComputeType.CPU_GPU;
+ Configuration config = new Configuration(null, login, password);
+ config.setPrintLog(print_log);
+
+ if (cache_dir != null) {
+ File a_dir = new File(cache_dir);
+ if (a_dir.isDirectory() && a_dir.canWrite()) {
+ config.setCacheDir(a_dir);
+ }
+ }
+
+ if (max_upload != -1) {
+ if (max_upload <= 0) {
+ System.err.println("Error: max upload should be a greater than zero");
+ return;
+ }
+ config.setMaxUploadingJob(max_upload);
+ }
+
+ if (gpu_device != null) {
+ String cuda_str = "CUDA_";
+ if (gpu_device.startsWith(cuda_str) == false) {
+ System.err.println("CUDA_DEVICE should look like 'CUDA_X' where X is a number");
+ return;
+ }
+ try {
+ Integer.parseInt(gpu_device.substring(cuda_str.length()));
+ }
+ catch (NumberFormatException en) {
+ System.err.println("CUDA_DEVICE should look like 'CUDA_X' where X is a number");
+ return;
+ }
+ GPUDevice gpu = GPU.getGPUDevice(gpu_device);
+ if (gpu == null) {
+ System.err.println("GPU unknown");
+ System.exit(2);
+ }
+ config.setUseGPU(gpu);
+ }
+
+ if (request_time != null) {
+ String[] intervals = request_time.split(",");
+ if (intervals != null) {
+ config.requestTime = new LinkedList>();
+
+ SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm");
+ for (String interval : intervals) {
+ String[] times = interval.split("-");
+ if (times != null && times.length == 2) {
+ Calendar start = Calendar.getInstance();
+ Calendar end = Calendar.getInstance();
+
+ try {
+ start.setTime(timeFormat.parse(times[0]));
+ end.setTime(timeFormat.parse(times[1]));
+ }
+ catch (ParseException e) {
+ System.err.println("Error: wrong format in request time");
+ System.exit(2);
+ }
+
+ if (start.before(end)) {
+ config.requestTime.add(new Pair(start, end));
+ }
+ else {
+ System.err.println("Error: wrong request time " + times[0] + " is after " + times[1]);
+ System.exit(2);
+ }
+ }
+ }
+ }
+ }
+
+ if (nb_cores < -1) {
+ System.err.println("Error: use-number-core should be a greater than zero");
+ return;
+ }
+ else {
+ config.setUseNbCores(nb_cores);
+ }
+
+ if (method != null) {
+ if (method.equalsIgnoreCase("cpu")) {
+ compute_method = ComputeType.CPU_ONLY;
+ }
+ else if (method.equalsIgnoreCase("gpu")) {
+ compute_method = ComputeType.GPU_ONLY;
+ }
+ else if (method.equalsIgnoreCase("cpu_gpu") || method.equalsIgnoreCase("gpu_cpu")) {
+ compute_method = ComputeType.CPU_GPU;
+ }
+ else {
+ System.err.println("Error: compute-method unknown");
+ System.exit(2);
+ }
+ }
+
+ if (proxy != null) {
+ try {
+ URL url = new URL(proxy);
+ String userinfo = url.getUserInfo();
+ if (userinfo != null) {
+ String[] elements = userinfo.split(":");
+ if (elements.length == 2) {
+ String proxy_user = elements[0];
+ String proxy_password = elements[1];
+
+ if (proxy_user != null && proxy_password != null) {
+ Authenticator.setDefault(new ProxyAuthenticator(proxy_user, proxy_password));
+ }
+ }
+ }
+
+ System.setProperty("http.proxyHost", url.getHost());
+ System.setProperty("http.proxyPort", Integer.toString(url.getPort()));
+
+ System.setProperty("https.proxyHost", url.getHost());
+ System.setProperty("https.proxyPort", Integer.toString(url.getPort()));
+ }
+ catch (MalformedURLException e) {
+ System.err.println("Error: wrong url for proxy");
+ System.err.println(e);
+ System.exit(2);
+ }
+ }
+
+ if (extras != null) {
+ config.setExtras(extras);
+ }
+
+ if (compute_method == ComputeType.CPU_ONLY) { // the client was to render with cpu but on the server side project type are cpu+gpu or gpu prefered but never cpu only
+ compute_method = ComputeType.CPU_GPU;
+ config.setComputeMethod(compute_method);
+ config.setUseGPU(null); // remove the GPU
+ }
+ else {
+ config.setComputeMethod(compute_method); // doing it here because it have to be done after the setUseGPU
+ }
+
+ Log.getInstance(config).debug("client version " + config.getJarVersion());
+
+ Gui gui = new GuiText();
+ Client cli = new Client(gui, config, server);
+
+ ShutdownHook hook = new ShutdownHook(cli);
+ hook.attachShutDownHook();
+
+ cli.run();
+ cli.stop();
+ }
+}