diff --git a/.classpath b/.classpath
deleted file mode 100644
index c5a5a0fe..00000000
--- a/.classpath
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..4f21ac72
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,9 @@
+root = true
+
+# Unix-style newlines with a newline ending every file
+[*]
+end_of_line = lf
+insert_final_newline = true
+charset = utf-8
+indent_size = 4
+indent_style = tab
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..84822bd5
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,29 @@
+name: CI
+
+on:
+ push:
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Installed dependencies
+ run: |
+ sudo apt update
+ sudo apt install libxml2-utils
+ - name: Decrypt keystore
+ run: gpg --quiet --batch --yes --decrypt --passphrase="${{ secrets.KEYSTORE_ENCRYPTION_PASS }}" --output $HOME/keystore.jks keystore.gpg
+ - name: Build jar & exe
+ run: ./create_signed_release.sh $HOME/keystore.jks ${{ secrets.KEYSTORE_PASS }} ${{ secrets.CERT_ALIAS }}
+ env:
+ JAVA_HOME: /usr/lib/jvm/zulu-8-azure-amd64
+ - name: Upload a Build Artifact
+ uses: actions/upload-artifact@v2
+ with:
+ path: sheepit-*.exe
+ - name: Upload a Build Artifact
+ uses: actions/upload-artifact@v2
+ with:
+ path: sheepit-client*.jar
diff --git a/.gitignore b/.gitignore
index 9eca9b6c..345f0149 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,5 @@
+.idea/workspace.xml
+.gradle
build
-bin
+out
+
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 00000000..e0788816
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+sheepit-client
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 00000000..1e989f09
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 00000000..79ee123c
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 00000000..95a88ae4
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644
index 00000000..577be554
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 00000000..8da083f2
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 00000000..e860487e
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 00000000..35eb1ddf
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.project b/.project
deleted file mode 100644
index 2f82e766..00000000
--- a/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
- SheepitClient
-
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
-
- org.eclipse.jdt.core.javanature
-
-
diff --git a/.travis.yml b/.travis.yml
index 4d52d60d..e48d4a77 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,9 +1,10 @@
language: java
-script: ant
+dist: trusty
os:
- linux
+
# safelist
branches:
only:
diff --git a/README.md b/README.md
index bb267e24..fddc0a39 100644
--- a/README.md
+++ b/README.md
@@ -8,25 +8,24 @@ SheepIt Render Farm Client is an *Open Source* client for the distributed render
## Compilation
-You need Java 1.7 or more. (OpenJDK and Oracle are both supported).
-You also need [ant](http://ant.apache.org/).
-To create the jar file, simply type `ant` in the project's root directory.
+You will need Java 1.7 or higher. (OpenJDK and Oracle are both supported).
+To create the jar file, simply type `./gradlew shadowJar` on linux/OSX and `gradlew.bat shadowJar` on Windows in the project's root directory.
## Usage
-Once you have a jar file, you can view the usage by running:
+Once you have the jar file, you can see how to use it by running:
- java -jar bin/sheepit-client.jar --help
+ java -jar build/libs/sheepit-client.jar --help
When you are doing development work, you can use a mirror of the main site specially made for demo/dev. The mirror is located at **http://sandbox.sheepit-renderfarm.com**, and you can use it by passing `-server http://sandbox.sheepit-renderfarm.com` to your invocation of the client.
-At the command line ui (-ui text / -ui oneLine) you could type in the following commands and press enter to controll the client:
+At the command line ui (-ui text / -ui oneLine) you could type in the following commands and press enter to control the client:
-* status: to get the current status of the client (paused, stoped, ...)
-* priority : to set the renderer process priority
-* block: to block the current project
+* status: get the current status of the client (paused, stoped, etc.)
+* priority : set the renderer process priority
+* block: block the current project
* pause: pause the client to request new jobs after the current frame has finished to render
* resume: resume the client after it was paused
* stop: stop the client after the current frame has finished
* cancel: cancel the stop request
-* quit: stops the client directly without finishing the current frame
+* quit: stop the client directly without finishing the current frame
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 00000000..8e2f5c40
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,70 @@
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.github.jengelman.gradle.plugins:shadow:4.0.4'
+ }
+}
+apply plugin: 'java'
+apply plugin: 'com.github.johnrengelman.shadow'
+
+sourceCompatibility = '1.7'
+targetCompatibility = '1.7'
+
+compileJava {
+ // Suppress warnings about internal api usage - https://stackoverflow.com/a/19553686/6238618
+ options.fork = true
+ options.forkOptions.executable = 'javac'
+ options.compilerArgs << '-XDignore.symbol.file'
+}
+
+shadowJar {
+ exclude 'OSGI-OPT/' // args4j garbage
+}
+
+repositories {
+ jcenter()
+ mavenCentral()
+}
+
+dependencies {
+ compileOnly 'org.projectlombok:lombok:1.18.12'
+ annotationProcessor 'org.projectlombok:lombok:1.18.12'
+
+ compile 'args4j:args4j:2.33'
+ compile 'net.lingala.zip4j:zip4j:1.3.3'
+ compile 'net.java.dev.jna:jna-platform:5.0.0'
+ compile 'org.simpleframework:simple-xml:2.7.1'
+ compile 'javax.xml.bind:jaxb-api:2.3.1'
+ implementation 'com.formdev:flatlaf:0.30'
+ implementation 'com.squareup.okhttp3:okhttp:4.7.2'
+ implementation 'com.squareup.okhttp3:okhttp-urlconnection:4.7.2'
+}
+
+jar {
+ manifest {
+ attributes "Main-Class": "com.sheepit.client.standalone.Worker"
+ }
+
+ from {
+ configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
+ }
+}
+
+sourceSets {
+ main {
+ java {
+ srcDirs = ['src']
+ }
+ resources {
+ srcDirs = ['resources']
+ }
+ }
+
+ test {
+ java {
+ srcDirs = ['test']
+ }
+ }
+}
diff --git a/build.xml b/build.xml
deleted file mode 100644
index cc22327b..00000000
--- a/build.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/code_style_formator.xml b/code_style_formator.xml
deleted file mode 100644
index 54e6aefe..00000000
--- a/code_style_formator.xml
+++ /dev/null
@@ -1,291 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/create_signed_release.sh b/create_signed_release.sh
new file mode 100755
index 00000000..0b5012ce
--- /dev/null
+++ b/create_signed_release.sh
@@ -0,0 +1,71 @@
+#!/bin/bash -e
+
+# Requires xmllint (part of `libxml2-utils`), svn, gpg, git (duh), curl and p7zip
+# JAVA_HOME must be set to a java 8 JDK
+#
+# Usage: ./create_signed_release.sh $PATH_TO_KEYSTORE $PASSWORD $CERTIFICATE_ALIAS
+
+
+
+echo ""
+echo "### Setup ###"
+
+keystore="$1"
+password="$2"
+cert_alias="$3"
+git_url="https://github.com/laurent-clouet/sheepit-client"
+svn_trunk_url="$git_url/trunk"
+pwd=`pwd`
+tmp_dir=`mktemp -d`
+jvm_name="jdk-11.0.6+10-jre"
+
+wrapper_dir="$tmp_dir/wrapper"
+git clone $git_url $wrapper_dir
+cd $wrapper_dir
+git checkout wrapper
+cd $pwd
+
+echo ""
+echo "### Generate version ###"
+
+svn_info=`svn info $svn_trunk_url --xml`
+echo "$svn_info"
+echo ""
+svn_revision=`echo $svn_info | xmllint --xpath "string(/info/entry/@revision)" -`
+echo "Revision: $svn_revision"
+version="6.$svn_revision.0"
+echo "Version: $version"
+
+
+echo ""
+echo "### Create jar ###"
+
+signed_jar="$pwd/sheepit-client-$version.jar"
+echo $version > ./resources/VERSION
+./gradlew shadowJar
+unsigned_jar="$pwd/build/libs/sheepit-client-all.jar"
+echo "Unsigned .jar: $unsigned_jar"
+echo "Signing"
+jarsigner -tsa http://timestamp.digicert.com -keystore $keystore -storepass $password -signedjar $signed_jar $unsigned_jar $cert_alias
+
+
+echo ""
+echo "### Build exe ###"
+
+exe="$pwd/sheepit-$version.exe"
+7z x -o$tmp_dir $wrapper_dir/$jvm_name.zip
+mv $tmp_dir/$jvm_name $tmp_dir/jre
+rm -rf $tmp_dir/jre/include $tmp_dir/jre/src.zip
+cp -f $signed_jar $tmp_dir/jre/sheepit-client.jar
+
+cd $tmp_dir/jre
+7z a $tmp_dir/application.7z .
+cd $tmp_dir
+cp $wrapper_dir/config.cfg config.cfg
+cp $wrapper_dir/starter.sfx starter.sfx
+cat starter.sfx config.cfg application.7z > $exe
+cd $pwd
+
+echo ""
+echo ".jar: $signed_jar"
+echo ".exe: $exe"
diff --git a/extern/args4j.jar b/extern/args4j.jar
deleted file mode 100644
index e188b3df..00000000
Binary files a/extern/args4j.jar and /dev/null differ
diff --git a/extern/jaxb-api.jar b/extern/jaxb-api.jar
deleted file mode 100644
index 0817c083..00000000
Binary files a/extern/jaxb-api.jar and /dev/null differ
diff --git a/extern/jaxb-core.jar b/extern/jaxb-core.jar
deleted file mode 100644
index 864ba311..00000000
Binary files a/extern/jaxb-core.jar and /dev/null differ
diff --git a/extern/jaxb-impl.jar b/extern/jaxb-impl.jar
deleted file mode 100644
index 02f50ab5..00000000
Binary files a/extern/jaxb-impl.jar and /dev/null differ
diff --git a/extern/jna.jar b/extern/jna.jar
deleted file mode 100644
index 9038048d..00000000
Binary files a/extern/jna.jar and /dev/null differ
diff --git a/extern/platform.jar b/extern/platform.jar
deleted file mode 100644
index 4b3d567c..00000000
Binary files a/extern/platform.jar and /dev/null differ
diff --git a/extern/zip4j.jar b/extern/zip4j.jar
deleted file mode 100644
index 66a19e49..00000000
Binary files a/extern/zip4j.jar and /dev/null differ
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..490fda85
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..ff0aac17
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Apr 10 00:43:49 CST 2020
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
new file mode 100755
index 00000000..2fe81a7d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,183 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# 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
+#
+# https://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.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 00000000..62bd9b9c
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,103 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/keystore.gpg b/keystore.gpg
new file mode 100644
index 00000000..9f358217
Binary files /dev/null and b/keystore.gpg differ
diff --git a/protocol.txt b/protocol.txt
index f5ef32de..06f48e76 100644
--- a/protocol.txt
+++ b/protocol.txt
@@ -34,7 +34,6 @@ A path is provided for error, job request, job validation, download needed file,
The maximum duration between two heartbeats in seconds is given by the attribute "max-period".
-
@@ -123,7 +122,7 @@ if fileformat != 'BMP' and fileformat != 'PNG' and fileformat != 'JPEG' and file
=== Job validation ===
-Url: use the request type "validate-job" from the configuration answer.
+Url: use the url url of job's node for 'validationurl'.
Parameter as GET or POST:
* job: Job ID
* frame: Job's frame number
diff --git a/resources/frame_compute_method.jpg b/resources/frame_compute_method.jpg
new file mode 100644
index 00000000..d65bd7fa
Binary files /dev/null and b/resources/frame_compute_method.jpg differ
diff --git a/resources/frame_power_detection.jpg b/resources/frame_power_detection.jpg
new file mode 100644
index 00000000..caf1d75c
Binary files /dev/null and b/resources/frame_power_detection.jpg differ
diff --git a/resources/icon-sprites.png b/resources/icon-sprites.png
new file mode 100644
index 00000000..b1a95222
Binary files /dev/null and b/resources/icon-sprites.png differ
diff --git a/resources/icon.png b/resources/icon.png
deleted file mode 100644
index 88a9d5e8..00000000
Binary files a/resources/icon.png and /dev/null differ
diff --git a/resources/sheepit-logo.png b/resources/sheepit-logo.png
new file mode 100644
index 00000000..bbaeff6e
Binary files /dev/null and b/resources/sheepit-logo.png differ
diff --git a/resources/title.png b/resources/title.png
deleted file mode 100644
index e52fffde..00000000
Binary files a/resources/title.png and /dev/null differ
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 00000000..4a88bede
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'sheepit-client'
diff --git a/src/com/sheepit/client/Client.java b/src/com/sheepit/client/Client.java
index d8f82adb..00b18be7 100644
--- a/src/com/sheepit/client/Client.java
+++ b/src/com/sheepit/client/Client.java
@@ -2,7 +2,7 @@
* Copyright (C) 2010-2014 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -27,6 +27,8 @@
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
+import java.util.Observable;
+import java.util.Observer;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadLocalRandom;
@@ -43,18 +45,22 @@
import com.sheepit.client.exception.FermeExceptionServerOverloaded;
import com.sheepit.client.exception.FermeExceptionSessionDisabled;
import com.sheepit.client.exception.FermeServerDown;
+import com.sheepit.client.hardware.cpu.CPU;
import com.sheepit.client.os.OS;
-public class Client {
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data public class Client {
private Gui gui;
private Server server;
- private Configuration config;
+ private Configuration configuration;
private Log log;
private Job renderingJob;
private Job previousJob;
- private BlockingQueue jobsToValidate;
+ private BlockingQueue jobsToValidate;
private boolean isValidatingJob;
- private long start_time;
+ private long startTime;
private boolean disableErrorSending;
private boolean running;
@@ -62,56 +68,40 @@ public class Client {
private int maxDownloadFileAttempts = 5;
- 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);
+ private int uploadQueueSize;
+ private long uploadQueueVolume;
+ private int noJobRetryIter;
+
+ public Client(Gui gui_, Configuration configuration, String url_) {
+ this.configuration = configuration;
+ this.server = new Server(url_, this.configuration, this);
+ this.log = Log.getInstance(this.configuration);
this.gui = gui_;
this.renderingJob = null;
this.previousJob = null;
- this.jobsToValidate = new ArrayBlockingQueue(1024);
+ this.jobsToValidate = new ArrayBlockingQueue(5);
this.isValidatingJob = false;
this.disableErrorSending = false;
this.running = false;
this.suspended = false;
+
+ this.uploadQueueSize = 0;
+ this.uploadQueueVolume = 0;
+ this.noJobRetryIter = 0;
}
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 Server getServer() {
- return this.server;
- }
-
- public Log getLog() {
- return this.log;
- }
-
- public long getStartTime() {
- return this.start_time;
+ return String.format("Client (configuration %s, server %s)", this.configuration, this.server);
}
public int run() {
- if (this.config.checkOSisSupported() == false) {
+ if (this.configuration.checkOSisSupported() == false) {
this.gui.error(Error.humanString(Error.Type.OS_NOT_SUPPORTED));
return -3;
}
- if (this.config.checkCPUisSupported() == false) {
+ if (this.configuration.checkCPUisSupported() == false) {
this.gui.error(Error.humanString(Error.Type.CPU_NOT_SUPPORTED));
return -4;
}
@@ -123,7 +113,7 @@ public int run() {
step = this.log.newCheckPoint();
this.gui.status("Starting");
- this.config.cleanWorkingDirectory();
+ this.configuration.cleanWorkingDirectory();
Error.Type ret;
ret = this.server.getConfiguration();
@@ -136,7 +126,7 @@ public int run() {
return -1;
}
- this.start_time = new Date().getTime();
+ this.startTime = new Date().getTime();
this.server.start(); // for staying alive
// create a thread which will send the frame
@@ -148,229 +138,270 @@ public void run() {
Thread thread_sender = new Thread(runnable_sender);
thread_sender.start();
- while (this.running == true) {
- this.renderingJob = null;
- synchronized (this) {
- while (this.suspended) {
- wait();
- }
- }
- 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());
+ do {
+ while (this.running == true) {
+ this.renderingJob = null;
+ synchronized (this) {
+ if (this.suspended) {
+ this.gui.status("Client paused", true);
+ }
+ while (this.suspended) {
+ wait();
}
- catch (InterruptedException e3) {
+ }
+ 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));
+ long wait = next_request.getTimeInMillis() - now.getTime();
+ if (wait < 0) {
+ // it means the client has to wait until the next day
+ wait += 24 * 3600 * 1000;
+ }
+ try {
+ Thread.sleep(wait);
+ }
+ 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 have enough right to render scene");
+ return -2;
+ }
+ catch (FermeExceptionSessionDisabled e) {
+ this.gui.error(Error.humanString(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 (IllegalArgumentException e3) {
- this.log.error("Client::run sleepA failed " + e3);
+ }
+ catch (FermeExceptionNoRendererAvailable e) {
+ this.gui.error(Error.humanString(Error.Type.RENDERER_NOT_AVAILABLE));
+ // should wait forever to actually display the message to the user
+ while (true) {
+ try {
+ Thread.sleep(100000);
+ }
+ catch (InterruptedException e1) {
+ }
}
}
- this.gui.status("Requesting Job");
- this.renderingJob = this.server.requestJob();
- }
- catch (FermeExceptionNoRightToRender e) {
- this.gui.error("User does not have enough right to render scene");
- return -2;
- }
- catch (FermeExceptionSessionDisabled e) {
- this.gui.error(Error.humanString(Error.Type.SESSION_DISABLED));
- // should wait forever to actually display the message to the user
- while (true) {
+ catch (FermeExceptionNoSession e) {
+ this.log.debug("User has no session and needs to re-authenticate");
+ ret = this.server.getConfiguration();
+ if (ret != Error.Type.OK) {
+ this.renderingJob = null;
+ }
+ else {
+ this.startTime = new Date().getTime(); // reset start session time because the server did it
+ 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 (FermeServerDown e) {
+ int wait = ThreadLocalRandom.current().nextInt(10, 30 + 1); // max is exclusive
+ int time_sleep = 1000 * 60 * wait;
+ this.gui.status(String.format("Cannot connect to the server. Please check your connectivity. Will try again at %tR",
+ new Date(new Date().getTime() + time_sleep)));
try {
- Thread.sleep(100000);
+ Thread.sleep(time_sleep);
}
catch (InterruptedException e1) {
+ return -3;
}
+ this.log.removeCheckPoint(step);
+ continue; // go back to ask job
}
- }
- catch (FermeExceptionNoRendererAvailable e) {
- this.gui.error(Error.humanString(Error.Type.RENDERER_NOT_AVAILABLE));
- // should wait forever to actually display the message to the user
- while (true) {
+ catch (FermeExceptionServerOverloaded e) {
+ int wait = ThreadLocalRandom.current().nextInt(10, 30 + 1); // max is exclusive
+ int time_sleep = 1000 * 60 * wait;
+ this.gui.status(String.format("The server is overloaded and cannot allocate a job. Will try again at %tR",
+ new Date(new Date().getTime() + time_sleep)));
try {
- Thread.sleep(100000);
+ Thread.sleep(time_sleep);
}
catch (InterruptedException e1) {
+ return -3;
}
+ this.log.removeCheckPoint(step);
+ continue; // go back to ask job
}
- }
- catch (FermeExceptionNoSession e) {
- this.log.debug("User has no session need to re-authenticate");
- ret = this.server.getConfiguration();
- if (ret != Error.Type.OK) {
- this.renderingJob = null;
- }
- else {
- this.start_time = new Date().getTime(); // reset start session time because the server did it
+ catch (FermeExceptionServerInMaintenance e) {
+ int wait = ThreadLocalRandom.current().nextInt(20, 30 + 1); // max is exclusive
+ int time_sleep = 1000 * 60 * wait;
+ this.gui.status(String.format("The server is under maintenance and cannot allocate a job. Will try again at %tR",
+ new Date(new Date().getTime() + time_sleep)));
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();
+ Thread.sleep(time_sleep);
}
- catch (FermeException e1) {
- this.renderingJob = null;
+ catch (InterruptedException e1) {
+ return -3;
}
+ this.log.removeCheckPoint(step);
+ continue; // go back to ask job
}
- }
- catch (FermeServerDown e) {
- int wait = ThreadLocalRandom.current().nextInt(10, 30 + 1); // max is exclusive
- int time_sleep = 1000 * 60 * wait;
- this.gui.status(String.format("Can not connect to server. Please check your connectivity. Will retry in %s minutes", wait));
- try {
- Thread.sleep(time_sleep);
+ catch (FermeExceptionBadResponseFromServer e) {
+ int wait = ThreadLocalRandom.current().nextInt(15, 30 + 1); // max is exclusive
+ int time_sleep = 1000 * 60 * wait;
+ this.gui.status(String.format("Bad answer from the server. Will try again at %tR", new Date(new Date().getTime() + time_sleep)));
+ try {
+ Thread.sleep(time_sleep);
+ }
+ catch (InterruptedException e1) {
+ return -3;
+ }
+ this.log.removeCheckPoint(step);
+ continue; // go back to ask job
}
- catch (InterruptedException e1) {
- return -3;
+ catch (FermeException e) {
+ this.gui.error("Client::run exception requestJob (1) " + e.getMessage());
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ e.printStackTrace(pw);
+ this.log.debug("Client::run exception " + e + " stacktrace: " + sw.toString());
+ this.sendError(step);
+ this.log.removeCheckPoint(step);
+ continue;
}
- continue; // go back to ask job
- }
- catch (FermeExceptionServerOverloaded e) {
- int wait = ThreadLocalRandom.current().nextInt(10, 30 + 1); // max is exclusive
- int time_sleep = 1000 * 60 * wait;
- this.gui.status(String.format("Server is overloaded and cannot give frame to render. Will retry in %s minutes", wait));
- try {
- Thread.sleep(time_sleep);
+
+ if (this.renderingJob == null) { // no job
+ int[] retrySchemeInSeconds = { 300000, 480000, 720000, 900000, 1200000 }; // 5, 8, 12, 15 and 20 minutes
+
+ int time_sleep = retrySchemeInSeconds[(this.noJobRetryIter < retrySchemeInSeconds.length) ?
+ this.noJobRetryIter++ :
+ (retrySchemeInSeconds.length - 1)];
+ this.gui.status(String.format("No job available. Will try again at %tR", new Date(new Date().getTime() + time_sleep)));
+ int time_slept = 0;
+ while (time_slept < time_sleep && this.running == true) {
+ try {
+ Thread.sleep(250);
+ }
+ catch (InterruptedException e) {
+ return -3;
+ }
+ time_slept += 250;
+ }
+ this.log.removeCheckPoint(step);
+ continue; // go back to ask job
}
- catch (InterruptedException e1) {
- return -3;
+
+ this.log.debug("Got work to do id: " + this.renderingJob.getId() + " frame: " + this.renderingJob.getFrameNumber());
+
+ // As the server allocated a new job to this client, reset the no_job waiting algorithm
+ this.noJobRetryIter = 0;
+
+ ret = this.work(this.renderingJob);
+ if (ret == Error.Type.RENDERER_KILLED) {
+ Job frame_to_reset = this.renderingJob; // copy it because the sendError will take ~5min to execute
+ this.renderingJob = null;
+ this.gui.error(Error.humanString(ret));
+ this.sendError(step, frame_to_reset, ret);
+ this.log.removeCheckPoint(step);
+ continue;
}
- continue; // go back to ask job
- }
- catch (FermeExceptionServerInMaintenance e) {
- int wait = ThreadLocalRandom.current().nextInt(20, 30 + 1); // max is exclusive
- int time_sleep = 1000 * 60 * wait;
- this.gui.status(String.format("Server is in maintenance and cannot give frame to render. Will retry in %s minutes", wait));
- try {
- Thread.sleep(time_sleep);
+
+ if (ret == Error.Type.NO_SPACE_LEFT_ON_DEVICE) {
+ Job frame_to_reset = this.renderingJob; // copy it because the sendError will take ~5min to execute
+ this.renderingJob = null;
+ this.gui.error(Error.humanString(ret));
+ this.sendError(step, frame_to_reset, ret);
+ this.log.removeCheckPoint(step);
+ return -50;
}
- catch (InterruptedException e1) {
- return -3;
+
+ if (ret != Error.Type.OK) {
+ Job frame_to_reset = this.renderingJob; // copy it because the sendError will take ~5min to execute
+ this.renderingJob = null;
+ this.gui.error(Error.humanString(ret));
+ this.sendError(step, frame_to_reset, ret);
+ this.log.removeCheckPoint(step);
+ continue;
}
- continue; // go back to ask job
- }
- catch (FermeExceptionBadResponseFromServer e) {
- int wait = ThreadLocalRandom.current().nextInt(15, 30 + 1); // max is exclusive
- int time_sleep = 1000 * 60 * wait;
- this.gui.status(String.format("Bad answer from server. Will retry in %s minutes", wait));
- try {
- Thread.sleep(time_sleep);
+
+ if (this.renderingJob.isSynchronousUpload() == true) { // power or compute_method job, need to upload right away
+ this.gui.status(String.format("Uploading frame (%.2fMB)", (this.renderingJob.getOutputImageSize() / 1024.0 / 1024.0)));
+
+ ret = confirmJob(this.renderingJob, step);
+ if (ret != Error.Type.OK) {
+ gui.error("Client::run problem with confirmJob (returned " + ret + ")");
+ sendError(step, this.renderingJob, Error.Type.VALIDATION_FAILED);
+ }
}
- catch (InterruptedException e1) {
- return -3;
+ else {
+ this.gui.status(String.format("Queuing frame for upload (%.2fMB)", (this.renderingJob.getOutputImageSize() / 1024.0 / 1024.0)));
+
+ this.jobsToValidate.add(new QueuedJob(step, this.renderingJob));
+
+ this.uploadQueueSize++;
+ this.uploadQueueVolume += this.renderingJob.getOutputImageSize();
+ this.gui.displayUploadQueueStats(uploadQueueSize, uploadQueueVolume);
+
+ this.renderingJob = null;
}
- continue; // go back to ask job
- }
- catch (FermeException e) {
- this.gui.error("Client::run exception requestJob (1) " + e.getMessage());
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
- e.printStackTrace(pw);
- this.log.debug("Client::run exception " + e + " stacktrace: " + sw.toString());
- this.sendError(step);
- continue;
- }
-
- if (this.renderingJob == null) { // no job
- int wait = ThreadLocalRandom.current().nextInt(10, 30 + 1); // max is exclusive
- int time_sleep = 1000 * 60 * wait;
- Date wakeup_time = new Date(new Date().getTime() + time_sleep);
- this.gui.status(String.format("No job available. Sleeping for %d minutes (will wake up at %tR)", wait, wakeup_time));
- this.gui.displayStats(new Stats());
- this.suspended = true;
- int time_slept = 0;
- while (time_slept < time_sleep && this.running == true) {
- try {
- Thread.sleep(250);
- }
- catch (InterruptedException e) {
- return -3;
+
+ if (this.shouldWaitBeforeRender() == true) {
+ this.gui.status("Sending frames. Please wait");
+
+ while (this.shouldWaitBeforeRender() == true) {
+ try {
+ Thread.sleep(4000); // wait a little bit
+ }
+ catch (InterruptedException e3) {
+ }
}
- time_slept += 250;
}
- this.suspended = false;
- 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.RENDERER_KILLED) {
this.log.removeCheckPoint(step);
- continue;
}
- if (ret == Error.Type.NO_SPACE_LEFT_ON_DEVICE) {
- Job frame_to_reset = this.renderingJob; // copy it because the sendError will take ~5min to execute
- this.renderingJob = null;
- this.gui.error(Error.humanString(ret));
- this.sendError(step, frame_to_reset, ret);
- this.log.removeCheckPoint(step);
- return -50;
- }
-
- if (ret != Error.Type.OK) {
- Job frame_to_reset = this.renderingJob; // copy it because the sendError will take ~5min to execute
- this.renderingJob = null;
- this.gui.error(Error.humanString(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::run problem with confirmJob (returned " + ret + ")");
- sendError(step);
- }
- else {
- gui.AddFrameRendered();
- }
- }
- 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.isEmpty() == false) {
+ // If we reach this point is bc the main loop (the one that controls all the workflow) has exited
+ // due to user requesting to exit the App and we are just waiting for the upload queue to empty
+ // If the user cancels the exit, then this.running will be true and the main loop will take
+ // control again
try {
Thread.sleep(2300); // wait a little bit
+ this.gui.status("Uploading rendered frames before exiting. Please wait");
}
catch (InterruptedException e3) {
}
+
+ // This loop will remain valid until all the background uploads have
+ // finished (unless the stop() method has been triggered)
}
+ while (this.uploadQueueSize > 0);
}
catch (Exception e1) {
// no exception should be raised in the actual launcher (applet or standalone)
@@ -390,24 +421,30 @@ public synchronized int stop() {
this.disableErrorSending = true;
if (this.renderingJob != null) {
+ this.gui.status("Stopping");
+
if (this.renderingJob.getProcessRender().getProcess() != null) {
this.renderingJob.setAskForRendererKill(true);
OS.getOS().kill(this.renderingJob.getProcessRender().getProcess());
}
}
- // this.config.workingDirectory.delete();
- this.config.removeWorkingDirectory();
+ // this.configuration.workingDirectory.delete();
+ this.configuration.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
+ if (this.server.getPage("logout").isEmpty() == false) {
+ this.gui.status("Disconnecting from SheepIt server");
+
+ 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 {
@@ -421,12 +458,9 @@ public synchronized int stop() {
return 0;
}
- public boolean isSuspended() {
- return this.suspended;
- }
-
public void suspend() {
suspended = true;
+ this.gui.status("Client will pause when the current job finishes", true);
}
public synchronized void resume() {
@@ -444,30 +478,45 @@ public void cancelStop() {
this.running = true;
}
- public boolean isRunning() {
- return this.running;
- }
-
public int senderLoop() {
- int step = log.newCheckPoint();
- Error.Type ret;
+ int step = -1;
+ Error.Type ret = null;
while (true) {
- Job job_to_send;
+ QueuedJob queuedJob = null;
try {
- job_to_send = jobsToValidate.take();
- this.log.debug("will validate " + job_to_send);
- //gui.status("Sending frame");
- ret = confirmJob(job_to_send);
+ queuedJob = jobsToValidate.take();
+ step = queuedJob.checkpoint; // retrieve the checkpoint attached to the job
+
+ this.log.debug(step, "will validate " + queuedJob.job);
+
+ ret = confirmJob(queuedJob.job, step);
if (ret != Error.Type.OK) {
this.gui.error(Error.humanString(ret));
- this.log.debug("Client::senderLoop confirm failed, ret: " + ret);
- sendError(step);
- }
- else {
- gui.AddFrameRendered();
+ this.log.debug(step, "Client::senderLoop confirm failed, ret: " + ret);
}
}
catch (InterruptedException e) {
+ this.log.error(step, "Client::senderLoop Exception " + e.getMessage());
+ }
+ finally {
+ if (ret != Error.Type.OK) {
+ if (queuedJob.job != null) {
+ sendError(step, queuedJob.job, ret);
+ }
+ else {
+ sendError(step);
+ }
+ }
+
+ // Remove the checkpoint information
+ log.removeCheckPoint(step);
+
+ this.uploadQueueSize--;
+ if (queuedJob.job != null) {
+ this.uploadQueueVolume -= queuedJob.job.getOutputImageSize();
+ }
+
+ this.gui.displayUploadQueueStats(this.uploadQueueSize, this.uploadQueueVolume);
}
}
}
@@ -489,6 +538,34 @@ protected void sendError(int step_, Job job_to_reset_, Error.Type error) {
temp_file.deleteOnExit();
FileOutputStream writer = new FileOutputStream(temp_file);
+ // Create a header with the information summarised for easier admin error analysis
+ Configuration conf = this.configuration;
+ CPU cpu = OS.getOS().getCPU();
+
+ StringBuilder logHeader = new StringBuilder()
+ .append("====================================================================================================\n")
+ .append(String.format("%s / %s / %s / SheepIt v%s\n", conf.getLogin(), conf.getHostname(), OS.getOS().name(), conf.getJarVersion()))
+ .append(String.format("%s x%d %.1f GB RAM\n", cpu.name(), conf.getNbCores(), conf.getMaxMemory() / 1024.0 / 1024.0));
+
+ if (conf.getComputeMethod() == Configuration.ComputeType.GPU || conf.getComputeMethod() == Configuration.ComputeType.CPU_GPU) {
+ logHeader
+ .append(String.format("%s %.1f GB VRAM\n", conf.getGPUDevice().getModel(), conf.getGPUDevice().getMemory() / 1024.0 / 1024.0 / 1024.0));
+ }
+
+ logHeader.append("====================================================================================================\n");
+ if (job_to_reset_ != null) {
+ logHeader.append(String.format("Project ::: %s\n", job_to_reset_.getName())).append(String.format("Project id: %s frame: %s\n", job_to_reset_.getId(), job_to_reset_.getFrameNumber()))
+ .append(String.format("blender ::: %s\n\n", job_to_reset_.getBlenderLongVersion())).append(String.format("ERROR Type :: %s\n", error));
+ }
+ else {
+ logHeader.append("Project ::: No project allocated.\n")
+ .append(String.format("ERROR Type :: %s\n", (error != null ? error : "N/A")));
+ }
+ logHeader.append("====================================================================================================\n\n");
+
+ // Insert the info at the beginning of the error log
+ writer.write(logHeader.toString().getBytes());
+
ArrayList logs = this.log.getForCheckPoint(step_);
for (String line : logs) {
writer.write(line.getBytes());
@@ -498,12 +575,13 @@ protected void sendError(int step_, Job job_to_reset_, Error.Type error) {
writer.close();
String args = "?type=" + (error == null ? "" : error.getValue());
if (job_to_reset_ != null) {
- args += "&frame=" + job_to_reset_.getFrameNumber() + "&job=" + job_to_reset_.getId() + "&render_time=" + job_to_reset_.getProcessRender().getDuration() + "&memoryused=" + job_to_reset_.getProcessRender().getMemoryUsed();
+ args += "&frame=" + job_to_reset_.getFrameNumber() + "&job=" + job_to_reset_.getId() + "&render_time=" + job_to_reset_.getProcessRender()
+ .getDuration() + "&memoryused=" + job_to_reset_.getProcessRender().getMemoryUsed();
if (job_to_reset_.getExtras() != null && job_to_reset_.getExtras().isEmpty() == false) {
args += "&extras=" + job_to_reset_.getExtras();
}
}
- this.server.HTTPSendFile(this.server.getPage("error") + args, temp_file.getAbsolutePath());
+ this.server.HTTPSendFile(this.server.getPage("error") + args, temp_file.getAbsolutePath(), step_);
temp_file.delete();
}
catch (Exception e) {
@@ -514,7 +592,8 @@ protected void sendError(int step_, Job job_to_reset_, Error.Type error) {
// no exception should be raised to actual launcher (applet or standalone)
}
- if (error != null && (error == Error.Type.RENDERER_CRASHED || error == Error.Type.RENDERER_KILLED_BY_USER || error == Error.Type.RENDERER_KILLED_BY_SERVER)) {
+ if (error != null && (error == Error.Type.RENDERER_CRASHED || error == Error.Type.RENDERER_KILLED_BY_USER
+ || error == Type.RENDERER_KILLED_BY_USER_OVER_TIME || error == Error.Type.RENDERER_KILLED_BY_SERVER)) {
// do nothing, we can ask for a job right away
}
else {
@@ -527,17 +606,16 @@ protected void sendError(int step_, Job job_to_reset_, Error.Type error) {
}
/**
- *
* @return the date of the next request, or null if there is not delay (null <=> now)
*/
public Calendar nextJobRequest() {
- if (this.config.requestTime == null) {
+ if (this.configuration.getRequestTime() == null) {
return null;
}
else {
Calendar next = null;
Calendar now = Calendar.getInstance();
- for (Pair interval : this.config.requestTime) {
+ for (Pair interval : this.configuration.getRequestTime()) {
Calendar start = (Calendar) now.clone();
Calendar end = (Calendar) now.clone();
start.set(Calendar.SECOND, 00);
@@ -551,15 +629,8 @@ public Calendar nextJobRequest() {
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;
- }
- }
+ if (next == null || (start.before(next) && start.after(now))) {
+ next = start;
}
}
@@ -567,7 +638,7 @@ public Calendar nextJobRequest() {
}
}
- public Error.Type work(Job ajob) {
+ public Error.Type work(final Job ajob) {
int ret;
gui.setRenderingProjectName(ajob.getName());
@@ -599,65 +670,87 @@ public Error.Type work(Job ajob) {
return Error.Type.NO_SPACE_LEFT_ON_DEVICE;
}
- File scene_file = new File(ajob.getScenePath());
+ final File scene_file = new File(ajob.getScenePath());
File renderer_file = new File(ajob.getRendererPath());
if (scene_file.exists() == false) {
gui.setRenderingProjectName("");
- this.log.error("Client::work job preparation failed (scene file '" + scene_file.getAbsolutePath() + "' does not exist)");
+ this.log.error("Client::work job preparation failed (scene file '" + scene_file.getAbsolutePath()
+ + "' does not exist), cleaning directory in hope to recover");
+ this.configuration.cleanWorkingDirectory();
return Error.Type.MISSING_SCENE;
}
if (renderer_file.exists() == false) {
gui.setRenderingProjectName("");
- this.log.error("Client::work job preparation failed (renderer file '" + renderer_file.getAbsolutePath() + "' does not exist)");
- return Error.Type.MISSING_RENDER;
+ this.log.error("Client::work job preparation failed (renderer file '" + renderer_file.getAbsolutePath()
+ + "' does not exist), cleaning directory in hope to recover");
+ this.configuration.cleanWorkingDirectory();
+ return Error.Type.MISSING_RENDERER;
}
- Error.Type err = ajob.render();
+ Observer removeSceneDirectoryOnceRenderHasStartedObserver = new Observer() {
+ @Override public void update(Observable observable, Object o) {
+ // only remove the .blend since it's most important data
+ // and it's the only file we are sure will not be needed anymore
+ scene_file.delete();
+ }
+ };
+
+ Error.Type err = ajob.render(removeSceneDirectoryOnceRenderHasStartedObserver);
gui.setRenderingProjectName("");
gui.setRemainingTime("");
gui.setRenderingTime("");
gui.setComputeMethod("");
if (err != Error.Type.OK) {
this.log.error("Client::work problem with runRenderer (ret " + err + ")");
+ if (err == Error.Type.RENDERER_CRASHED_PYTHON_ERROR) {
+ this.log.error("Client::work failed with python error, cleaning directory in hope to recover");
+ this.configuration.cleanWorkingDirectory();
+ }
return err;
}
+ removeSceneDirectory(ajob);
+
return Error.Type.OK;
}
protected int downloadSceneFile(Job ajob_) throws FermeExceptionNoSpaceLeftOnDevice {
- return this.downloadFile(ajob_, ajob_.getSceneArchivePath(), ajob_.getSceneMD5(), String.format("%s?type=job&job=%s", this.server.getPage("download-archive"), ajob_.getId()), "project");
+ return this.downloadFile(ajob_, ajob_.getSceneArchivePath(), ajob_.getSceneMD5(),
+ String.format("%s?type=job&job=%s", this.server.getPage("download-archive"), ajob_.getId()), "project");
}
protected int downloadExecutable(Job ajob) throws FermeExceptionNoSpaceLeftOnDevice {
- return this.downloadFile(ajob, ajob.getRendererArchivePath(), ajob.getRenderMd5(), String.format("%s?type=binary&job=%s", this.server.getPage("download-archive"), ajob.getId()), "renderer");
+ return this.downloadFile(ajob, ajob.getRendererArchivePath(), ajob.getRendererMD5(),
+ String.format("%s?type=binary&job=%s", this.server.getPage("download-archive"), ajob.getId()), "renderer");
}
private int downloadFile(Job ajob, String local_path, String md5_server, String url, String download_type) throws FermeExceptionNoSpaceLeftOnDevice {
File local_path_file = new File(local_path);
- String update_ui = "Downloading " + download_type + " %s %%";
+ String update_ui = "Downloading " + download_type;
if (local_path_file.exists() == true) {
this.gui.status("Reusing cached " + download_type);
return 0;
}
- this.gui.status("Downloading " + download_type);
+ this.gui.status(String.format("Downloading %s", download_type), 0, 0);
// must download the archive
int ret = this.server.HTTPGetFile(url, local_path, this.gui, update_ui);
+
+ // Try to check the download file even if a download error has occurred (MD5 file check will delete the file if partially downloaded)
boolean md5_check = this.checkFile(ajob, local_path, md5_server);
int attempts = 1;
while ((ret != 0 || md5_check == false) && attempts < this.maxDownloadFileAttempts) {
if (ret != 0) {
- this.gui.error("Client::downloadFile problem with Server.HTTPGetFile returned " + ret);
+ this.gui.error(String.format("Unable to download %s (error %d). Retrying now", download_type, ret));
this.log.debug("Client::downloadFile problem with Server.HTTPGetFile (return: " + ret + ") removing local file (path: " + local_path + ")");
}
else if (md5_check == false) {
- this.gui.error("Client::downloadFile problem with Client::checkFile mismatch on md5");
+ this.gui.error(String.format("Verification of downloaded %s has failed. Retrying now", download_type));
this.log.debug("Client::downloadFile problem with Client::checkFile mismatch on md5, removing local file (path: " + local_path + ")");
}
local_path_file.delete();
@@ -669,7 +762,8 @@ else if (md5_check == false) {
attempts++;
if ((ret != 0 || md5_check == false) && attempts >= this.maxDownloadFileAttempts) {
- this.log.debug("Client::downloadFile failed after " + this.maxDownloadFileAttempts + " attempts, removing local file (path: " + local_path + "), stopping...");
+ this.log.debug("Client::downloadFile failed after " + this.maxDownloadFileAttempts + " attempts, removing local file (path: " + local_path
+ + "), stopping...");
local_path_file.delete();
return -9;
}
@@ -689,13 +783,19 @@ private boolean checkFile(Job ajob, String local_path, String md5_server) {
String md5_local = Utils.md5(local_path);
if (md5_local.equals(md5_server) == false) {
- this.log.error("Client::checkFile mismatch on md5 local: '" + md5_local + "' server: '" + md5_server + "' (local size: " + new File(local_path).length() + ")");
+ this.log.error(
+ "Client::checkFile mismatch on md5 local: '" + md5_local + "' server: '" + md5_server + "' (local size: " + new File(local_path).length()
+ + ")");
return false;
}
return true;
}
+ protected void removeSceneDirectory(Job ajob) {
+ Utils.delete(new File(ajob.getSceneDirectory()));
+ }
+
protected int prepareWorkingDirectory(Job ajob) throws FermeExceptionNoSpaceLeftOnDevice {
int ret;
String renderer_archive = ajob.getRendererArchivePath();
@@ -713,8 +813,10 @@ protected int prepareWorkingDirectory(Job ajob) throws FermeExceptionNoSpaceLeft
// unzip the archive
ret = Utils.unzipFileIntoDirectory(renderer_archive, renderer_path, null, log);
if (ret != 0) {
- this.log.error("Client::prepareWorkingDirectory, error(1) with Utils.unzipFileIntoDirectory(" + renderer_archive + ", " + renderer_path + ") returned " + ret);
- this.gui.error("Client::prepareWorkingDirectory, error with Utils.unzipFileIntoDirectory of the renderer (returned " + ret + ")");
+ this.log.error(
+ "Client::prepareWorkingDirectory, error(1) with Utils.unzipFileIntoDirectory(" + renderer_archive + ", " + renderer_path + ") returned "
+ + ret);
+ this.gui.error(String.format("Unable to extract the renderer (error %d)", ret));
return -1;
}
@@ -739,10 +841,12 @@ protected int prepareWorkingDirectory(Job ajob) throws FermeExceptionNoSpaceLeft
scene_path_file.mkdir();
// unzip the archive
- ret = Utils.unzipFileIntoDirectory(scene_archive, scene_path, ajob.getSceneArchivePassword(), log);
+ ret = Utils.unzipFileIntoDirectory(scene_archive, scene_path, ajob.getPassword(), log);
if (ret != 0) {
- this.log.error("Client::prepareWorkingDirectory, error(2) with Utils.unzipFileIntoDirectory(" + scene_archive + ", " + scene_path + ") returned " + ret);
- this.gui.error("Client::prepareWorkingDirectory, error with Utils.unzipFileIntoDirectory of the scene (returned " + ret + ")");
+ this.log.error(
+ "Client::prepareWorkingDirectory, error(2) with Utils.unzipFileIntoDirectory(" + scene_archive + ", " + scene_path + ") returned "
+ + ret);
+ this.gui.error(String.format("Unable to extract the scene (error %d)", ret));
return -2;
}
}
@@ -750,35 +854,35 @@ protected int prepareWorkingDirectory(Job ajob) throws FermeExceptionNoSpaceLeft
return 0;
}
- protected Error.Type confirmJob(Job ajob) {
- String extras_config = "";
- RenderProcess process = ajob.getProcessRender();
- if (process != null && process.getCoresUsed() > 0) {
- extras_config = "&cores=" + process.getCoresUsed();
- }
-
- String url_real = String.format("%s?job=%s&frame=%s&rendertime=%d&memoryused=%s&extras=%s%s", this.server.getPage("validate-job"), ajob.getId(), ajob.getFrameNumber(), ajob.getProcessRender().getDuration(), ajob.getProcessRender().getMemoryUsed(), ajob.getExtras(), extras_config);
+ protected Error.Type confirmJob(Job ajob, int checkpoint) {
+ String url_real = String.format("%s&rendertime=%d&memoryused=%s", ajob.getValidationUrl(), ajob.getProcessRender().getDuration(),
+ ajob.getProcessRender().getMemoryUsed());
+ this.log.debug(checkpoint, "Client::confirmeJob url " + url_real);
+ this.log.debug(checkpoint, "path frame " + ajob.getOutputImagePath());
this.isValidatingJob = true;
int nb_try = 1;
int max_try = 3;
ServerCode ret = ServerCode.UNKNOWN;
+ Type confirmJobReturnCode = Error.Type.OK;
+ retryLoop:
while (nb_try < max_try && ret != ServerCode.OK) {
- ret = this.server.HTTPSendFile(url_real, ajob.getOutputImagePath());
+ ret = this.server.HTTPSendFile(url_real, ajob.getOutputImagePath(), checkpoint);
switch (ret) {
case OK:
// no issue, exit the loop
- nb_try = max_try;
- break;
+ break retryLoop;
case JOB_VALIDATION_ERROR_SESSION_DISABLED:
case JOB_VALIDATION_ERROR_BROKEN_MACHINE:
- return Type.SESSION_DISABLED;
-
+ confirmJobReturnCode = Error.Type.SESSION_DISABLED;
+ break retryLoop;
+
case JOB_VALIDATION_ERROR_MISSING_PARAMETER:
// no point to retry the request
- return Error.Type.UNKNOWN;
-
+ confirmJobReturnCode = Error.Type.UNKNOWN;
+ break retryLoop;
+
default:
// do nothing, try to do a request on the next loop
break;
@@ -787,28 +891,28 @@ protected Error.Type confirmJob(Job ajob) {
nb_try++;
if (ret != ServerCode.OK && nb_try < max_try) {
try {
- this.log.debug("Sleep for 32s before trying to re-upload the frame");
+ this.log.debug(checkpoint, "Sleep for 32s before trying to re-upload the frame");
Thread.sleep(32000);
}
catch (InterruptedException e) {
- return Error.Type.UNKNOWN;
+ confirmJobReturnCode = Error.Type.UNKNOWN;
}
}
}
+
this.isValidatingJob = false;
+ this.previousJob = ajob;
+
+ if (confirmJobReturnCode == Error.Type.OK) {
+ gui.AddFrameRendered();
+ }
// we can remove the frame file
File frame = new File(ajob.getOutputImagePath());
frame.delete();
ajob.setOutputImagePath(null);
- this.isValidatingJob = false;
- this.previousJob = ajob;
- return Error.Type.OK;
- }
-
- public Job getPreviousJob() {
- return this.previousJob;
+ return confirmJobReturnCode;
}
protected boolean shouldWaitBeforeRender() {
@@ -816,6 +920,16 @@ protected boolean shouldWaitBeforeRender() {
if (this.isValidatingJob) {
concurrent_job++;
}
- return (concurrent_job >= this.config.maxUploadingJob());
+ return (concurrent_job >= this.configuration.getMaxUploadingJob());
+ }
+
+ /****************
+ * Inner class that will hold the queued jobs. The constructor accepts two parameters:
+ * @int checkpoint - the checkpoint associated with the job (to add any additional log to the render output)
+ * @Job job - the job to be validated
+ */
+ @AllArgsConstructor class QueuedJob {
+ final private int checkpoint;
+ final private Job job;
}
}
diff --git a/src/com/sheepit/client/Configuration.java b/src/com/sheepit/client/Configuration.java
index 172d703e..d87cd6aa 100644
--- a/src/com/sheepit/client/Configuration.java
+++ b/src/com/sheepit/client/Configuration.java
@@ -2,7 +2,7 @@
* Copyright (C) 2010-2014 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -34,35 +34,38 @@
import com.sheepit.client.hardware.cpu.CPU;
import com.sheepit.client.hardware.gpu.GPUDevice;
import com.sheepit.client.os.OS;
+import lombok.Data;
-public class Configuration {
+@Data public class Configuration {
public enum ComputeType {
CPU_GPU, CPU, GPU
} // accept job for ...
private String configFilePath;
- public File workingDirectory;
- public File storageDirectory; // for permanent storage (binary archive)
- public boolean userHasSpecifiedACacheDir;
- public String static_exeDirName;
+ private File workingDirectory;
+ private File storageDirectory; // for permanent storage (binary archive)
+ private boolean userHasSpecifiedACacheDir;
+ private String static_exeDirName;
private String login;
private String password;
private String proxy;
private int maxUploadingJob;
private int nbCores;
- private int maxMemory; // max memory allowed for render
+ private long maxMemory; // max memory allowed for render
private int maxRenderTime; // max render time per frame allowed
private int priority;
private ComputeType computeMethod;
private GPUDevice GPUDevice;
+ private int renderbucketSize;
private boolean detectGPUs;
private boolean printLog;
- public List> requestTime;
+ private List> requestTime;
private String extras;
private boolean autoSignIn;
+ private boolean useSysTray;
private String UIType;
- private int tileSize;
private String hostname;
+ private String theme;
public Configuration(File cache_dir_, String login_, String password_) {
this.configFilePath = null;
@@ -78,6 +81,7 @@ public Configuration(File cache_dir_, String login_, String password_) {
this.priority = 19; // default lowest
this.computeMethod = null;
this.GPUDevice = null;
+ this.renderbucketSize = -1;
this.userHasSpecifiedACacheDir = false;
this.detectGPUs = true;
this.workingDirectory = null;
@@ -87,86 +91,14 @@ public Configuration(File cache_dir_, String login_, String password_) {
this.requestTime = null;
this.extras = "";
this.autoSignIn = false;
+ this.useSysTray = true;
this.UIType = null;
- this.tileSize = -1; // ie not set
+ this.theme = null;
}
-
public String toString() {
return String.format("Configuration (workingDirectory '%s')", this.workingDirectory.getAbsolutePath());
}
-
- public String getConfigFilePath() {
- return this.configFilePath;
- }
-
- public void setConfigPath(String val) {
- this.configFilePath = val;
- }
-
- public String login() {
- return this.login;
- }
-
- public void setLogin(String login_) {
- this.login = login_;
- }
-
- public String password() {
- return this.password;
- }
-
- public void setPassword(String password_) {
- this.password = password_;
- }
-
- public String getProxy() {
- return this.proxy;
- }
-
- public void setProxy(String url) {
- this.proxy = url;
- }
-
- public int maxUploadingJob() {
- return this.maxUploadingJob;
- }
-
- public GPUDevice getGPUDevice() {
- return this.GPUDevice;
- }
-
- public boolean getDetectGPUs() {
- return this.detectGPUs;
- }
-
- public void setMaxUploadingJob(int max) {
- this.maxUploadingJob = max;
- }
-
- public void setUseNbCores(int nbcores) {
- this.nbCores = nbcores;
- }
-
- public int getNbCores() {
- return this.nbCores;
- }
-
- public void setMaxMemory(int max) {
- this.maxMemory = max;
- }
-
- public int getMaxMemory() {
- return this.maxMemory;
- }
-
- public void setMaxRenderTime(int max) {
- this.maxRenderTime = max;
- }
-
- public int getMaxRenderTime() {
- return this.maxRenderTime;
- }
public void setUsePriority(int priority) {
if (priority > 19)
@@ -178,38 +110,10 @@ public void setUsePriority(int priority) {
}
- public int getPriority() {
- return this.priority;
- }
-
- 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;
- }
-
- public void setDetectGPUs(boolean val) {
- this.detectGPUs = val;
- }
-
- public void setComputeMethod(ComputeType meth) {
- this.computeMethod = meth;
- }
-
public void setCacheDir(File cache_dir_) {
removeWorkingDirectory();
if (cache_dir_ == null) {
@@ -221,7 +125,7 @@ public void setCacheDir(File cache_dir_) {
this.workingDirectory.mkdir();
this.workingDirectory.deleteOnExit();
- // since there is no working directory and the client will be working in the system temp directory,
+ // since there is no working directory and the client will be working in the system temp directory,
// we can also set up a 'permanent' directory for immutable files (like renderer binary)
this.storageDirectory = new File(this.workingDirectory.getParent() + File.separator + "sheepit_binary_cache");
@@ -259,12 +163,8 @@ public File getStorageDir() {
}
}
- public boolean getUserHasSpecifiedACacheDir() {
- return this.userHasSpecifiedACacheDir;
- }
-
public File getCacheDirForSettings() {
- if (this.getUserHasSpecifiedACacheDir() == false) {
+ if (this.userHasSpecifiedACacheDir == false) {
return null;
}
else {
@@ -273,46 +173,6 @@ public File getCacheDirForSettings() {
}
}
- public void setExtras(String str) {
- this.extras = str;
- }
-
- public String getExtras() {
- return this.extras;
- }
-
- public void setAutoSignIn(boolean v) {
- this.autoSignIn = v;
- }
-
- public boolean getAutoSignIn() {
- return this.autoSignIn;
- }
-
- public void setUIType(String ui) {
- this.UIType = ui;
- }
-
- public String getUIType() {
- return this.UIType;
- }
-
- public void setTileSize(int size) {
- this.tileSize = size;
- }
-
- public int getTileSize() {
- return this.tileSize;
- }
-
- public void setHostname(String hostname_) {
- this.hostname = hostname_;
- }
-
- public String getHostname() {
- return this.hostname;
- }
-
public String getDefaultHostname() {
try {
return InetAddress.getLocalHost().getHostName();
@@ -417,7 +277,7 @@ public String getJarVersion() {
InputStream versionStream = Client.class.getResourceAsStream(versionPath);
if (versionStream == null) {
System.err.println("Configuration::getJarVersion Failed to get version file");
- return "";
+ return "6.0.0";
}
try {
@@ -429,7 +289,7 @@ public String getJarVersion() {
}
catch (IOException ex) {
System.err.println("Configuration::getJarVersion error while reading manifest file (" + versionPath + "): " + ex.getMessage());
- return "";
+ return "6.0.0";
}
}
diff --git a/src/com/sheepit/client/Error.java b/src/com/sheepit/client/Error.java
index b4ab5c4a..5389b811 100644
--- a/src/com/sheepit/client/Error.java
+++ b/src/com/sheepit/client/Error.java
@@ -2,7 +2,7 @@
* Copyright (C) 2013-2014 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -22,36 +22,14 @@
public class Error {
public enum Type {
// id have to be kept synchronised with the server side.
- OK(0),
- UNKNOWN(99),
- WRONG_CONFIGURATION(1),
- AUTHENTICATION_FAILED(2),
- TOO_OLD_CLIENT(3),
- SESSION_DISABLED(4),
- RENDERER_NOT_AVAILABLE(5),
- MISSING_RENDER(6),
- MISSING_SCENE(7),
- NOOUTPUTFILE(8),
- DOWNLOAD_FILE(9),
- CAN_NOT_CREATE_DIRECTORY(10),
- NETWORK_ISSUE(11),
- RENDERER_CRASHED(12),
- RENDERER_CRASHED_PYTHON_ERROR(24),
- RENDERER_OUT_OF_VIDEO_MEMORY(13),
- RENDERER_OUT_OF_MEMORY(21),
- RENDERER_KILLED(14),
- RENDERER_KILLED_BY_USER(20),
- RENDERER_KILLED_BY_USER_OVER_TIME(23),
- RENDERER_KILLED_BY_SERVER(22),
- RENDERER_MISSING_LIBRARIES(15),
- FAILED_TO_EXECUTE(16),
- OS_NOT_SUPPORTED(17),
- CPU_NOT_SUPPORTED(18),
- GPU_NOT_SUPPORTED(19),
+ OK(0), UNKNOWN(99), WRONG_CONFIGURATION(1), AUTHENTICATION_FAILED(2), TOO_OLD_CLIENT(3), SESSION_DISABLED(4), RENDERER_NOT_AVAILABLE(
+ 5), MISSING_RENDERER(6), MISSING_SCENE(7), NOOUTPUTFILE(8), DOWNLOAD_FILE(9), CAN_NOT_CREATE_DIRECTORY(10), NETWORK_ISSUE(11), RENDERER_CRASHED(
+ 12), RENDERER_CRASHED_PYTHON_ERROR(24), RENDERER_OUT_OF_VIDEO_MEMORY(13), RENDERER_OUT_OF_MEMORY(21), RENDERER_KILLED(
+ 14), RENDERER_KILLED_BY_USER(20), RENDERER_KILLED_BY_USER_OVER_TIME(23), RENDERER_KILLED_BY_SERVER(22), RENDERER_MISSING_LIBRARIES(
+ 15), FAILED_TO_EXECUTE(16), OS_NOT_SUPPORTED(17), CPU_NOT_SUPPORTED(18), GPU_NOT_SUPPORTED(19), VALIDATION_FAILED(25),
// internal error handling
- NO_SPACE_LEFT_ON_DEVICE(100),
- ERROR_BAD_RESPONSE(101),
+ NO_SPACE_LEFT_ON_DEVICE(100), ERROR_BAD_RESPONSE(101),
;
private final int id;
@@ -66,36 +44,23 @@ public int getValue() {
}
public enum ServerCode {
- OK(0),
- UNKNOWN(999),
+ 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_EXPIRED(103),
- CONFIGURATION_ERROR_MISSING_PARAMETER(104),
+ CONFIGURATION_ERROR_NO_CLIENT_VERSION_GIVEN(100), CONFIGURATION_ERROR_CLIENT_TOO_OLD(101), CONFIGURATION_ERROR_AUTH_FAILED(
+ 102), CONFIGURATION_ERROR_WEB_SESSION_EXPIRED(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_REQUEST_ERROR_RENDERER_NOT_AVAILABLE(205),
- JOB_REQUEST_SERVER_IN_MAINTENANCE(206),
- JOB_REQUEST_SERVER_OVERLOADED(207),
+ 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_REQUEST_ERROR_RENDERER_NOT_AVAILABLE(205), JOB_REQUEST_SERVER_IN_MAINTENANCE(
+ 206), JOB_REQUEST_SERVER_OVERLOADED(207),
- 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
+ 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_BAD_RESPONSE(3),
- ERROR_REQUEST_FAILED(5);
+ ERROR_NO_ROOT(2), ERROR_BAD_RESPONSE(3), ERROR_REQUEST_FAILED(5);
private final int id;
@@ -128,15 +93,15 @@ public static Type ServerCodeToType(ServerCode sc) {
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_EXPIRED:
return Type.WRONG_CONFIGURATION;
-
+
case JOB_REQUEST_ERROR_SESSION_DISABLED:
case JOB_VALIDATION_ERROR_SESSION_DISABLED:
return Type.SESSION_DISABLED;
-
+
case JOB_REQUEST_ERROR_RENDERER_NOT_AVAILABLE:
return Type.RENDERER_NOT_AVAILABLE;
@@ -181,6 +146,8 @@ public static String humanString(Type in) {
return "The server has disabled your session. Your client may have generated a broken frame (GPU not compatible, not enough RAM/VRAM, etc).";
case RENDERER_NOT_AVAILABLE:
return "No renderer are available on the server for your machine.";
+ case MISSING_RENDERER:
+ return "Unable to locate the Blender renderer within the binary download.";
case OS_NOT_SUPPORTED:
return "Operating System not supported.";
case CPU_NOT_SUPPORTED:
diff --git a/src/com/sheepit/client/Gui.java b/src/com/sheepit/client/Gui.java
index dabaa223..71df53d5 100644
--- a/src/com/sheepit/client/Gui.java
+++ b/src/com/sheepit/client/Gui.java
@@ -2,7 +2,7 @@
* Copyright (C) 2010-2013 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -26,6 +26,14 @@ public interface Gui {
public void status(String msg_);
+ public void status(String msg_, boolean overwriteSuspendedMsg);
+
+ public void status(String msg_, int progress);
+
+ public void status(String msg_, int progress, long size);
+
+ public void updateTrayIcon(Integer percentage_);
+
public void setRenderingProjectName(String name_);
public void setRemainingTime(String time_);
@@ -34,10 +42,14 @@ public interface Gui {
public void displayStats(Stats stats);
+ public void displayUploadQueueStats(int queueSize, long queueVolume);
+
public void error(String err_);
public void AddFrameRendered();
+ public void successfulAuthenticationEvent(String publickey);
+
public void setClient(Client cli);
public void setComputeMethod(String computeMethod_);
diff --git a/src/com/sheepit/client/Job.java b/src/com/sheepit/client/Job.java
index a04c4de2..3939c9c3 100644
--- a/src/com/sheepit/client/Job.java
+++ b/src/com/sheepit/client/Job.java
@@ -2,7 +2,7 @@
* Copyright (C) 2010-2014 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -35,50 +35,65 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Observable;
+import java.util.Observer;
import java.util.TimeZone;
import java.util.Timer;
import java.util.TimerTask;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
import com.sheepit.client.Configuration.ComputeType;
import com.sheepit.client.Error.Type;
import com.sheepit.client.hardware.gpu.GPUDevice;
import com.sheepit.client.hardware.gpu.opencl.OpenCL;
import com.sheepit.client.os.OS;
+import lombok.Data;
+import lombok.Getter;
-public class Job {
+@Data public class Job {
public static final String UPDATE_METHOD_BY_REMAINING_TIME = "remainingtime";
public static final String UPDATE_METHOD_BLENDER_INTERNAL_BY_PART = "blenderinternal";
public static final String UPDATE_METHOD_BY_TILE = "by_tile";
- private String numFrame;
+ public static final int SHOW_BASE_ICON = -1;
+
+ private String frameNumber;
private String sceneMD5;
private String rendererMD5;
private String id;
- private String pictureFilename;
+ private String outputImagePath;
+ private long outputImageSize;
private String path; // path inside of the archive
private String rendererCommand;
+ private String validationUrl;
private String script;
private boolean useGPU;
private String name;
private String password;
private String extras;
private String updateRenderingStatusMethod;
+ private String blenderShortVersion;
+ private String blenderLongVersion;
private boolean synchronousUpload;
private RenderProcess render;
private boolean askForRendererKill;
private boolean userBlockJob;
private boolean serverBlockJob;
private Gui gui;
- private Configuration config;
+ private Configuration configuration;
private Log log;
- public Job(Configuration config_, Gui gui_, Log log_, String id_, String frame_, String path_, boolean use_gpu, String command_, String script_, String sceneMd5_, String rendererMd5_, String name_, String password_, String extras_, boolean synchronous_upload_, String update_method_) {
- config = config_;
+ public Job(Configuration config_, Gui gui_, Log log_, String id_, String frame_, String path_, boolean use_gpu, String command_, String validationUrl_,
+ String script_, String sceneMd5_, String rendererMd5_, String name_, String password_, String extras_, boolean synchronous_upload_,
+ String update_method_) {
+ configuration = config_;
id = id_;
- numFrame = frame_;
+ frameNumber = frame_;
path = path_;
useGPU = use_gpu;
rendererCommand = command_;
+ validationUrl = validationUrl_;
sceneMD5 = sceneMd5_;
rendererMD5 = rendererMd5_;
name = name_;
@@ -86,7 +101,8 @@ public Job(Configuration config_, Gui gui_, Log log_, String id_, String frame_,
extras = extras_;
synchronousUpload = synchronous_upload_;
gui = gui_;
- pictureFilename = null;
+ outputImagePath = null;
+ outputImageSize = 0;
script = script_;
updateRenderingStatusMethod = update_method_;
askForRendererKill = false;
@@ -94,6 +110,8 @@ public Job(Configuration config_, Gui gui_, Log log_, String id_, String frame_,
serverBlockJob = false;
log = log_;
render = new RenderProcess();
+ blenderShortVersion = null;
+ blenderLongVersion = null;
}
public void block() {
@@ -110,83 +128,9 @@ public RenderProcess getProcessRender() {
}
public String toString() {
- return String.format("Job (numFrame '%s' sceneMD5 '%s' rendererMD5 '%s' ID '%s' pictureFilename '%s' jobPath '%s' gpu %s name '%s' extras '%s' updateRenderingStatusMethod '%s' render %s)", numFrame, sceneMD5, rendererMD5, id, pictureFilename, path, useGPU, name, extras, updateRenderingStatusMethod, render);
- }
-
- 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 void setAskForRendererKill(boolean val) {
- askForRendererKill = val;
- }
-
- public boolean getAskForRendererKill() {
- return askForRendererKill;
- }
-
- public void setUserBlockJob(boolean val) {
- userBlockJob = val;
- }
-
- public boolean getUserBlockJob() {
- return userBlockJob;
- }
-
- public void setServerBlockJob(boolean val) {
- serverBlockJob = val;
- }
-
- public boolean getServerBlockJob() {
- return serverBlockJob;
- }
-
- public String getRenderCommand() {
- return rendererCommand;
- }
-
- public boolean getUseGPU() {
- return useGPU;
- }
-
- public String getName() {
- return name;
- }
-
- public void setOutputImagePath(String path) {
- pictureFilename = path;
- }
-
- public String getOutputImagePath() {
- return pictureFilename;
+ return String
+ .format("Job (numFrame '%s' sceneMD5 '%s' rendererMD5 '%s' ID '%s' pictureFilename '%s' jobPath '%s' gpu %s name '%s' extras '%s' updateRenderingStatusMethod '%s' render %s)",
+ frameNumber, sceneMD5, rendererMD5, id, outputImagePath, path, useGPU, name, extras, updateRenderingStatusMethod, render);
}
public String getPrefixOutputImage() {
@@ -194,7 +138,7 @@ public String getPrefixOutputImage() {
}
public String getRendererDirectory() {
- return config.workingDirectory.getAbsolutePath() + File.separator + rendererMD5;
+ return configuration.getWorkingDirectory().getAbsolutePath() + File.separator + rendererMD5;
}
public String getRendererPath() {
@@ -202,11 +146,11 @@ public String getRendererPath() {
}
public String getRendererArchivePath() {
- return config.getStorageDir().getAbsolutePath() + File.separator + rendererMD5 + ".zip";
+ return configuration.getStorageDir().getAbsolutePath() + File.separator + rendererMD5 + ".zip";
}
public String getSceneDirectory() {
- return config.workingDirectory.getAbsolutePath() + File.separator + sceneMD5;
+ return configuration.getWorkingDirectory().getAbsolutePath() + File.separator + sceneMD5;
}
public String getScenePath() {
@@ -214,18 +158,10 @@ public String getScenePath() {
}
public String getSceneArchivePath() {
- return config.workingDirectory.getAbsolutePath() + File.separator + sceneMD5 + ".zip";
- }
-
- public String getSceneArchivePassword() {
- return password;
- }
-
- public boolean simultaneousUploadIsAllowed() {
- return synchronousUpload;
+ return configuration.getWorkingDirectory().getAbsolutePath() + File.separator + sceneMD5 + ".zip";
}
- public Error.Type render() {
+ public Error.Type render(Observer renderStarted) {
gui.status("Rendering");
RenderProcess process = getProcessRender();
Timer timerOfMaxRenderTime = null;
@@ -233,26 +169,31 @@ public Error.Type render() {
// When sending Ctrl+C to the terminal it also get's sent to all subprocesses e.g. also the render process.
// The java program handles Ctrl+C but the renderer quits on Ctrl+C.
// This script causes the renderer to ignore Ctrl+C.
- String ignore_signal_script= "import signal\n"
- + "def hndl(signum, frame):\n"
- + " pass\n"
- + "signal.signal(signal.SIGINT, hndl)\n";
- if (getUseGPU() && config.getGPUDevice() != null && config.getComputeMethod() != ComputeType.CPU) {
- core_script = "sheepit_set_compute_device(\"" + config.getGPUDevice().getType() + "\", \"GPU\", \"" + config.getGPUDevice().getId() + "\")\n";
+ String ignore_signal_script = "import signal\n" + "def hndl(signum, frame):\n" + " pass\n" + "signal.signal(signal.SIGINT, hndl)\n";
+ if (isUseGPU() && configuration.getGPUDevice() != null && configuration.getComputeMethod() != ComputeType.CPU) {
+ // If using a GPU, check the proper tile size
+ int tileSize = configuration.getGPUDevice().getRenderbucketSize();
+
+ core_script = "sheepit_set_compute_device(\"" + configuration.getGPUDevice().getType() + "\", \"GPU\", \"" + configuration.getGPUDevice().getId()
+ + "\")\n";
+ core_script += String.format("bpy.context.scene.render.tile_x = %1$d\nbpy.context.scene.render.tile_y = %1$d\n", tileSize);
+
+ log.debug(String.format("Rendering bucket size set to %1$dx%1$d pixels", tileSize));
gui.setComputeMethod("GPU");
}
else {
+ // Otherwise (CPU), fix the tile size to 32x32px
core_script = "sheepit_set_compute_device(\"NONE\", \"CPU\", \"CPU\")\n";
+ core_script += String.format("bpy.context.scene.render.tile_x = %1$d\nbpy.context.scene.render.tile_y = %1$d\n", 32);
gui.setComputeMethod("CPU");
}
core_script += ignore_signal_script;
- core_script += String.format("bpy.context.scene.render.tile_x = %1$d\nbpy.context.scene.render.tile_y = %1$d\n", getTileSize());
File script_file = null;
- String command1[] = getRenderCommand().split(" ");
+ String command1[] = getRendererCommand().split(" ");
int size_command = command1.length + 2; // + 2 for script
- if (config.getNbCores() > 0) { // user has specified something
+ if (configuration.getNbCores() > 0) { // user has specified something
size_command += 2;
}
@@ -260,13 +201,14 @@ public Error.Type render() {
Map new_env = new HashMap();
- new_env.put("BLENDER_USER_CONFIG", config.workingDirectory.getAbsolutePath().replace("\\", "\\\\"));
- new_env.put("CORES", Integer.toString(config.getNbCores()));
- new_env.put("PRIORITY", Integer.toString(config.getPriority()));
+ new_env.put("BLENDER_USER_CONFIG", configuration.getWorkingDirectory().getAbsolutePath().replace("\\", "\\\\"));
+ new_env.put("CORES", Integer.toString(configuration.getNbCores()));
+ new_env.put("PRIORITY", Integer.toString(configuration.getPriority()));
new_env.put("PYTHONPATH", ""); // make sure blender is using the embedded python, if not it could create "Fatal Python error: Py_Initialize"
new_env.put("PYTHONHOME", "");// make sure blender is using the embedded python, if not it could create "Fatal Python error: Py_Initialize"
- if (getUseGPU() && config.getGPUDevice() != null && config.getComputeMethod() != ComputeType.CPU && OpenCL.TYPE.equals(config.getGPUDevice().getType())) {
+ if (isUseGPU() && configuration.getGPUDevice() != null && configuration.getComputeMethod() != ComputeType.CPU && OpenCL.TYPE
+ .equals(configuration.getGPUDevice().getType())) {
new_env.put("CYCLES_OPENCL_SPLIT_KERNEL_TEST", "1");
this.updateRenderingStatusMethod = UPDATE_METHOD_BY_TILE; // don't display remaining time
}
@@ -278,7 +220,7 @@ public Error.Type render() {
command.add("-P");
try {
- script_file = File.createTempFile("script_", "", config.workingDirectory);
+ script_file = File.createTempFile("script_", "", configuration.getWorkingDirectory());
File file = new File(script_file.getAbsolutePath());
FileWriter txt;
txt = new FileWriter(file);
@@ -303,13 +245,13 @@ public Error.Type render() {
case ".e":
command.add(getRendererPath());
// the number of cores has to be put after the binary and before the scene arg
- if (config.getNbCores() > 0) {
+ if (configuration.getNbCores() > 0) {
command.add("-t");
- command.add(Integer.toString(config.getNbCores()));
+ command.add(Integer.toString(configuration.getNbCores()));
}
break;
case ".o":
- command.add(config.workingDirectory.getAbsolutePath() + File.separator + getPrefixOutputImage());
+ command.add(configuration.getWorkingDirectory().getAbsolutePath() + File.separator + getPrefixOutputImage());
break;
case ".f":
command.add(getFrameNumber());
@@ -321,60 +263,91 @@ public Error.Type render() {
}
try {
+ renderStartedObservable event = new renderStartedObservable(renderStarted);
String line;
log.debug(command.toString());
OS os = OS.getOS();
- process.setCoresUsed(config.getNbCores());
+ process.setCoresUsed(configuration.getNbCores());
process.start();
getProcessRender().setProcess(os.exec(command, new_env));
BufferedReader input = new BufferedReader(new InputStreamReader(getProcessRender().getProcess().getInputStream()));
- if (config.getMaxRenderTime() > 0) {
+ if (configuration.getMaxRenderTime() > 0) {
timerOfMaxRenderTime = new Timer();
timerOfMaxRenderTime.schedule(new TimerTask() {
- @Override
- public void run() {
+ @Override public void run() {
RenderProcess process = getProcessRender();
if (process != null) {
- long duration = (new Date().getTime() - process.getStartTime() ) / 1000; // in seconds
- if (config.getMaxRenderTime() > 0 && duration > config.getMaxRenderTime()) {
+ long duration = (new Date().getTime() - process.getStartTime()) / 1000; // in seconds
+ if (configuration.getMaxRenderTime() > 0 && duration > configuration.getMaxRenderTime()) {
+ setAskForRendererKill(true);
log.debug("Killing render because process duration");
OS.getOS().kill(process.getProcess());
- setAskForRendererKill(true);
}
}
}
- }, config.getMaxRenderTime() * 1000 + 2000); // +2s to be sure the delay is over
+ }, configuration.getMaxRenderTime() * 1000 + 2000); // +2s to be sure the delay is over
}
- long last_update_status = 0;
log.debug("renderer output");
try {
+ int progress = -1;
+
+ Pattern tilePattern = Pattern.compile(" (Rendered|Path Tracing Tile|Rendering) (\\d+)\\s?\\/\\s?(\\d+)( Tiles| samples|,)");
+
+ // Initialise the progress bar in the icon and the UI (0% completed at this time)
+ gui.updateTrayIcon(0);
+ gui.status("Preparing scene", 0);
+
while ((line = input.readLine()) != null) {
log.debug(line);
+ // Process lines until the version is loaded (usually first or second line of log)
+ if (blenderLongVersion == null) {
+ Pattern blenderPattern = Pattern.compile("Blender (([0-9]{1,3}\\.[0-9]{0,3}).*)$");
+ Matcher blendDetectedVersion = blenderPattern.matcher(line);
+
+ if (blendDetectedVersion.find()) {
+ blenderLongVersion = blendDetectedVersion.group(1);
+ blenderShortVersion = blendDetectedVersion.group(2);
+ }
+ }
+
+ progress = computeRenderingProgress(line, tilePattern, progress);
+
updateRenderingMemoryPeak(line);
- if (config.getMaxMemory() != -1 && process.getMemoryUsed() > config.getMaxMemory()) {
- log.debug("Blocking render because process ram used (" + process.getMemoryUsed() + "k) is over user setting (" + config.getMaxMemory() + "k)");
+ if (configuration.getMaxMemory() != -1 && process.getMemoryUsed() > configuration.getMaxMemory()) {
+ log.debug("Blocking render because process ram used (" + process.getMemoryUsed() + "k) is over user setting (" + configuration
+ .getMaxMemory() + "k)");
OS.getOS().kill(process.getProcess());
process.finish();
if (script_file != null) {
script_file.delete();
}
+
+ // Once the process is finished (either finished successfully or with an error) move back to
+ // base icon (isolated S with no progress bar)
+ gui.updateTrayIcon(Job.SHOW_BASE_ICON);
+
return Error.Type.RENDERER_OUT_OF_MEMORY;
}
- if ((new Date().getTime() - last_update_status) > 2000) { // only call the update every two seconds
- updateRenderingStatus(line);
- last_update_status = new Date().getTime();
- }
+ updateRenderingStatus(line);
Type error = detectError(line);
if (error != Error.Type.OK) {
if (script_file != null) {
script_file.delete();
}
+
+ // Put back base icon
+ gui.updateTrayIcon(Job.SHOW_BASE_ICON);
+
return error;
}
+
+ if (event.isStarted() == false && (process.getMemoryUsed() > 0 || process.getRemainingDuration() > 0)) {
+ event.doNotifyIsStarted();
+ }
}
input.close();
}
@@ -382,6 +355,10 @@ public void run() {
// most likely The handle is invalid
log.error("Job::render exception(B) (silent error) " + err1);
}
+
+ // Put back base icon
+ gui.updateTrayIcon(Job.SHOW_BASE_ICON);
+
log.debug("end of rendering");
}
catch (Exception err) {
@@ -413,24 +390,24 @@ public boolean accept(File dir, String name) {
}
};
- File[] files = config.workingDirectory.listFiles(textFilter);
+ File[] files = configuration.getWorkingDirectory().listFiles(textFilter);
- if (getAskForRendererKill()) {
+ if (isAskForRendererKill()) {
log.debug("Job::render been asked to end render");
- long duration = (new Date().getTime() - process.getStartTime() ) / 1000; // in seconds
- if (config.getMaxRenderTime() > 0 && duration > config.getMaxRenderTime()) {
- log.debug("Render killed because process duration (" + duration + "s) is over user setting (" + config.getMaxRenderTime() + "s)");
+ long duration = (new Date().getTime() - process.getStartTime()) / 1000; // in seconds
+ if (configuration.getMaxRenderTime() > 0 && duration > configuration.getMaxRenderTime()) {
+ log.debug("Render killed because process duration (" + duration + "s) is over user setting (" + configuration.getMaxRenderTime() + "s)");
return Error.Type.RENDERER_KILLED_BY_USER_OVER_TIME;
}
if (files.length != 0) {
new File(files[0].getAbsolutePath()).delete();
}
- if (getServerBlockJob()) {
+ if (isServerBlockJob()) {
return Error.Type.RENDERER_KILLED_BY_SERVER;
}
- if (getUserBlockJob()) {
+ if (isUserBlockJob()) {
return Error.Type.RENDERER_KILLED_BY_USER;
}
return Error.Type.RENDERER_KILLED;
@@ -446,7 +423,7 @@ public boolean accept(File dir, String name) {
catch (Exception e) {
e.printStackTrace();
}
- File crash_file = new File(config.workingDirectory + File.separator + basename + ".crash.txt");
+ File crash_file = new File(configuration.getWorkingDirectory() + File.separator + basename + ".crash.txt");
if (crash_file.exists()) {
log.error("Job::render crash file found => the renderer crashed");
crash_file.delete();
@@ -462,7 +439,8 @@ public boolean accept(File dir, String name) {
}
else {
setOutputImagePath(files[0].getAbsolutePath());
- log.debug("Job::render pictureFilename: '" + getOutputImagePath() + "'");
+ this.outputImageSize = new File(getOutputImagePath()).length();
+ log.debug(String.format("Job::render pictureFilename: %s, size: %d'", getOutputImagePath(), this.outputImageSize));
}
File scene_dir = new File(getSceneDirectory());
@@ -476,6 +454,26 @@ public boolean accept(File dir, String name) {
return Error.Type.OK;
}
+ private int computeRenderingProgress(String line, Pattern tilePattern, int currentProgress) {
+ Matcher standardTileInfo = tilePattern.matcher(line);
+ int newProgress = currentProgress;
+
+ if (standardTileInfo.find()) {
+ int tileJustProcessed = Integer.parseInt(standardTileInfo.group(2));
+ int totalTilesInJob = Integer.parseInt(standardTileInfo.group(3));
+
+ newProgress = Math.abs((tileJustProcessed * 100) / totalTilesInJob);
+ }
+
+ // Only update the tray icon and the screen if percentage has changed
+ if (newProgress != currentProgress) {
+ gui.updateTrayIcon(newProgress);
+ gui.status("Rendering", newProgress);
+ }
+
+ return newProgress;
+ }
+
private void updateRenderingStatus(String line) {
if (getUpdateRenderingStatusMethod() != null && getUpdateRenderingStatusMethod().equals(Job.UPDATE_METHOD_BLENDER_INTERNAL_BY_PART)) {
String search = " Part ";
@@ -483,7 +481,7 @@ private void updateRenderingStatus(String line) {
if (index != -1) {
String buf = line.substring(index + search.length());
String[] parts = buf.split("-");
- if (parts != null && parts.length == 2) {
+ if (parts.length == 2) {
try {
int current = Integer.parseInt(parts[0]);
int total = Integer.parseInt(parts[1]);
@@ -491,6 +489,7 @@ private void updateRenderingStatus(String line) {
long end_render = (new Date().getTime() - this.render.getStartTime()) * total / current;
Date date = new Date(end_render);
gui.setRemainingTime(String.format("%s %% (%s)", (int) (100.0 - 100.0 * current / total), Utils.humanDuration(date)));
+ getProcessRender().setRemainingDuration((int) (date.getTime() / 1000));
return;
}
}
@@ -533,13 +532,13 @@ else if (getUpdateRenderingStatusMethod() == null || getUpdateRenderingStatusMet
}
}
}
- else if (getUpdateRenderingStatusMethod() == null || getUpdateRenderingStatusMethod().equals(Job.UPDATE_METHOD_BY_TILE)) {
+ else if (getUpdateRenderingStatusMethod().equals(Job.UPDATE_METHOD_BY_TILE)) {
String search = " Tile ";
int index = line.lastIndexOf(search);
if (index != -1) {
String buf = line.substring(index + search.length());
String[] parts = buf.split("/");
- if (parts != null && parts.length == 2) {
+ if (parts.length == 2) {
try {
int current = Integer.parseInt(parts[0]);
int total = Integer.parseInt(parts[1]);
@@ -565,12 +564,12 @@ private void updateRenderingMemoryPeak(String line) {
int end = element.indexOf(')');
if (end > 0) {
try {
- long mem = Utils.parseNumber(element.substring(1, end).trim());
+ long mem = Utils.parseNumber(element.substring(1, end).trim()) / 1000; // internal use of ram is in kB
if (mem > getProcessRender().getMemoryUsed()) {
getProcessRender().setMemoryUsed(mem);
}
}
- catch (IllegalStateException e) {
+ catch (IllegalStateException | NumberFormatException e) {
// failed to parseNumber
}
}
@@ -580,12 +579,12 @@ private void updateRenderingMemoryPeak(String line) {
int end = element.indexOf('|');
if (end > 0) {
try {
- long mem = Utils.parseNumber(element.substring(1, end).trim());
+ long mem = Utils.parseNumber(element.substring(1, end).trim()) / 1000; // internal use of ram is in kB
if (mem > getProcessRender().getMemoryUsed()) {
getProcessRender().setMemoryUsed(mem);
}
}
- catch (IllegalStateException e) {
+ catch (IllegalStateException | NumberFormatException e) {
// failed to parseNumber
}
}
@@ -596,7 +595,7 @@ private void updateRenderingMemoryPeak(String line) {
private Type detectError(String line) {
- if (line.indexOf("CUDA error: Out of memory") != -1) {
+ if (line.contains("CUDA error: Out of memory")) {
// Fra:151 Mem:405.91M (0.00M, Peak 633.81M) | Mem:470.26M, Peak:470.26M | Scene, RenderLayer | Updating Device | Writing constant memory
// Fra:151 Mem:405.91M (0.00M, Peak 633.81M) | Mem:470.26M, Peak:470.26M | Scene, RenderLayer | Path Tracing Tile 0/135, Sample 0/200
// Fra:151 Mem:405.91M (0.00M, Peak 633.81M) | Mem:470.82M, Peak:470.82M | Scene, RenderLayer | Path Tracing Tile 1/135, Sample 0/200
@@ -615,7 +614,7 @@ private Type detectError(String line) {
// Blender quit
return Type.RENDERER_OUT_OF_VIDEO_MEMORY;
}
- else if (line.indexOf("CUDA error at cuCtxCreate: Out of memory") != -1) {
+ else if (line.contains("CUDA error at cuCtxCreate: Out of memory")) {
// renderer output
// CUDA error at cuCtxCreate: Out of memory
// Refer to the Cycles GPU rendering documentation for possible solutions:
@@ -638,7 +637,7 @@ else if (line.indexOf("CUDA error at cuCtxCreate: Out of memory") != -1) {
// end of rendering
return Type.RENDERER_OUT_OF_VIDEO_MEMORY;
}
- else if (line.indexOf("CUDA error: Launch exceeded timeout in") != -1) {
+ else if (line.contains("CUDA error: Launch exceeded timeout in")) {
// Fra:420 Mem:102.41M (0.00M, Peak 215.18M) | Remaining:01:08.44 | Mem:176.04M, Peak:199.23M | Scene, RenderLayer | Path Tracing Tile 2/24, Sample 10/14
// Fra:420 Mem:102.41M (0.00M, Peak 215.18M) | Remaining:01:07.08 | Mem:175.48M, Peak:199.23M | Scene, RenderLayer | Path Tracing Tile 2/24, Sample 14/14
// Fra:420 Mem:102.41M (0.00M, Peak 215.18M) | Remaining:01:07.11 | Mem:176.04M, Peak:199.23M | Scene, RenderLayer | Path Tracing Tile 3/24, Sample 0/14
@@ -698,7 +697,7 @@ else if (line.indexOf("CUDA error: Launch exceeded timeout in") != -1) {
// end of rendering
return Type.RENDERER_OUT_OF_VIDEO_MEMORY;
}
- else if (line.indexOf("CUDA error: Invalid value in cuTexRefSetAddress(") != -1) {
+ else if (line.contains("CUDA error: Invalid value in cuTexRefSetAddress(")) {
// Fra:83 Mem:1201.77M (0.00M, Peak 1480.94M) | Time:00:59.30 | Mem:894.21M, Peak:894.21M | color 3, RenderLayer | Updating Mesh | Copying Strands to device
// Fra:83 Mem:1316.76M (0.00M, Peak 1480.94M) | Time:01:02.84 | Mem:1010.16M, Peak:1010.16M | color 3, RenderLayer | Cancel | CUDA error: Invalid value in cuTexRefSetAddress(NULL, texref, cuda_device_ptr(mem.device_pointer), size)
// Error: CUDA error: Invalid value in cuTexRefSetAddress(NULL, texref, cuda_device_ptr(mem.device_pointer), size)
@@ -709,7 +708,7 @@ else if (line.indexOf("CUDA error: Invalid value in cuTexRefSetAddress(") != -1)
// http://www.blender.org/manual/render/cycles/gpu_rendering.html
return Error.Type.RENDERER_OUT_OF_VIDEO_MEMORY;
}
- else if (line.indexOf("CUDA error: Launch failed in cuCtxSynchronize()") != -1) {
+ else if (line.contains("CUDA error: Launch failed in cuCtxSynchronize()")) {
// Fra:60 Mem:278.24M (0.00M, Peak 644.01M) | Time:05:08.95 | Remaining:00:03.88 | Mem:210.79M, Peak:210.79M | Scene, W Laser | Path Tracing Tile 16/18, Sample 36/36
// Fra:60 Mem:278.24M (0.00M, Peak 644.01M) | Time:05:08.96 | Remaining:00:00.82 | Mem:211.04M, Peak:211.04M | Scene, W Laser | Path Tracing Tile 17/18, Sample 36/36
// Fra:60 Mem:278.24M (0.00M, Peak 644.01M) | Time:05:08.96 | Mem:211.11M, Peak:211.11M | Scene, W Laser | Path Tracing Tile 18/18
@@ -725,7 +724,19 @@ else if (line.indexOf("CUDA error: Launch failed in cuCtxSynchronize()") != -1)
// CUDA error: Launch failed in cuMemFree(cuda_device_ptr(mem.device_pointer)), line 615
return Error.Type.RENDERER_OUT_OF_VIDEO_MEMORY;
}
- else if (line.indexOf("CUDA device supported only with compute capability") != -1) {
+ else if (line.contains("CUDA error: Illegal address in cuCtxSynchronize()")) {
+ // Fra:124 Mem:434.77M (0.00M, Peak 435.34M) | Time:25:50.81 | Remaining:01:10:05.16 | Mem:175.14M, Peak:265.96M | Scene, RenderLayer | Path Tracing Tile 34/135, Sample 800/800, Denoised 17 tiles
+ // Fra:124 Mem:432.71M (0.00M, Peak 435.34M) | Time:25:50.81 | Remaining:01:10:04.95 | Mem:264.84M, Peak:266.90M | Scene, RenderLayer | Path Tracing Tile 34/135, Sample 800/800, Denoised 18 tiles
+ // Fra:124 Mem:434.77M (0.00M, Peak 435.34M) | Time:25:50.82 | Remaining:01:07:20.83 | Mem:266.90M, Peak:266.90M | Scene, RenderLayer | Path Tracing Tile 35/135, Sample 800/800, Denoised 18 tiles
+ // Fra:124 Mem:432.71M (0.00M, Peak 435.34M) | Time:25:50.82 | Remaining:01:07:20.63 | Mem:356.60M, Peak:358.67M | Scene, RenderLayer | Path Tracing Tile 35/135, Sample 800/800, Denoised 19 tiles
+ // Fra:124 Mem:434.77M (0.00M, Peak 435.34M) | Time:25:50.82 | Remaining:01:04:45.63 | Mem:358.67M, Peak:358.67M | Scene, RenderLayer | Path Tracing Tile 36/135, Sample 800/800, Denoised 19 tiles
+ // Fra:124 Mem:432.71M (0.00M, Peak 435.34M) | Time:25:50.82 | Remaining:01:04:45.45 | Mem:448.37M, Peak:450.43M | Scene, RenderLayer | Path Tracing Tile 36/135, Sample 800/800, Denoised 20 tiles
+ // Fra:124 Mem:434.77M (0.00M, Peak 435.34M) | Time:25:50.83 | Remaining:01:02:18.83 | Mem:450.43M, Peak:450.43M | Scene, RenderLayer | Path Tracing Tile 37/135, Sample 800/800, Denoised 20 tiles
+ // CUDA error: Illegal address in cuCtxSynchronize(), line 1372
+ // Refer to the Cycles GPU rendering documentation for possible solutions:
+ return Error.Type.RENDERER_OUT_OF_VIDEO_MEMORY;
+ }
+ else if (line.contains("CUDA device supported only with compute capability")) {
// found bundled python: /tmp/xx/2.73/python
// read blend: /tmp/xx/compute-method.blend
// Fra:340 Mem:7.64M (0.00M, Peak 8.23M) | Mem:0.00M, Peak:0.00M | Scene, RenderLayer | Synchronizing object | Sun
@@ -746,7 +757,7 @@ else if (line.indexOf("CUDA device supported only with compute capability") != -
// Blender quit
return Type.GPU_NOT_SUPPORTED;
}
- else if (line.indexOf("terminate called after throwing an instance of 'boost::filesystem::filesystem_error'") != -1) {
+ else if (line.contains("terminate called after throwing an instance of 'boost::filesystem::filesystem_error'")) {
// Fra:2103 Mem:29.54M (0.00M, Peak 29.54M) | Time:00:00.24 | Mem:1.64M, Peak:1.64M | Scene, RenderLayer | Updating Mesh | Computing attributes
// Fra:2103 Mem:29.54M (0.00M, Peak 29.54M) | Time:00:00.24 | Mem:1.64M, Peak:1.64M | Scene, RenderLayer | Updating Mesh | Copying Attributes to device
// Fra:2103 Mem:29.54M (0.00M, Peak 29.54M) | Time:00:00.24 | Mem:1.97M, Peak:1.97M | Scene, RenderLayer | Updating Scene BVH | Building
@@ -759,7 +770,7 @@ else if (line.indexOf("terminate called after throwing an instance of 'boost::fi
// what(): boost::filesystem::create_directory: Permission denied: "/var/local/cache"
return Error.Type.NOOUTPUTFILE;
}
- else if (line.indexOf("terminate called after throwing an instance of 'std::bad_alloc'") != -1) {
+ else if (line.contains("terminate called after throwing an instance of 'std::bad_alloc'")) {
// Fra:80 Mem:1333.02M (0.00M, Peak 1651.23M) | Mem:780.37M, Peak:780.37M | Scene, RenderLayer | Updating Mesh BVH Plane.083 171/2 | Building BVH
// Fra:80 Mem:1333.02M (0.00M, Peak 1651.23M) | Mem:780.37M, Peak:780.37M | Scene, RenderLayer | Updating Mesh BVH Mesh 172/2 | Building BVH
// Fra:80 Mem:1333.02M (0.00M, Peak 1651.23M) | Mem:780.37M, Peak:780.37M | Scene, RenderLayer | Updating Mesh BVH Mesh 172/2 | Packing BVH triangles and strands
@@ -770,26 +781,26 @@ else if (line.indexOf("terminate called after throwing an instance of 'std::bad_
// what(): std::bad_alloc
return Error.Type.RENDERER_OUT_OF_MEMORY;
}
- else if (line.indexOf("what(): std::bad_alloc") != -1) {
+ else if (line.contains("what(): std::bad_alloc")) {
// Fra:7 Mem:1247.01M (0.00M, Peak 1247.01M) | Time:00:28.84 | Mem:207.63M, Peak:207.63M | Scene, RenderLayer | Updating Scene BVH | Building BVH 93%, duplicates 0%terminate called recursively
// terminate called after throwing an instance of 'St9bad_alloc'
// what(): std::bad_alloc
// scandir: Cannot allocate memory
return Error.Type.RENDERER_OUT_OF_MEMORY;
}
- else if (line.indexOf("EXCEPTION_ACCESS_VIOLATION") != -1) {
+ else if (line.contains("EXCEPTION_ACCESS_VIOLATION")) {
// Fra:638 Mem:342.17M (63.28M, Peak 735.33M) | Time:00:07.65 | Remaining:02:38.28 | Mem:246.91M, Peak:262.16M | scene_top_01_90, chip_top_view_scene_01 | Path Tracing Tile 57/2040, Denoised 0 tiles
// Fra:638 Mem:342.32M (63.28M, Peak 735.33M) | Time:00:07.70 | Remaining:02:38.20 | Mem:247.05M, Peak:262.16M | scene_top_01_90, chip_top_view_scene_01 | Path Tracing Tile 58/2040, Denoised 0 tiles
// Error: EXCEPTION_ACCESS_VIOLATION
return Error.Type.RENDERER_CRASHED;
}
- else if (line.indexOf("Fatal Python error: Py_Initialize") != -1) {
+ else if (line.contains("Fatal Python error: Py_Initialize")) {
// Fatal Python error: Py_Initialize: unable to load the file system codec
// ImportError: No module named 'encodings'
// Current thread 0x0000388c (most recent call first):
return Error.Type.RENDERER_CRASHED_PYTHON_ERROR;
}
- else if (line.indexOf("Calloc returns null") != -1) {
+ else if (line.contains("Calloc returns null")) {
// Fra:1 Mem:976.60M (0.00M, Peak 1000.54M) | Time:00:01.34 | Mem:0.00M, Peak:0.00M | Scene, RenderLayer | Synchronizing object | Left
// Calloc returns null: len=7186416 in CDMLoopUV, total 2145859048
// Calloc returns null: len=7186416 in CDMLoopUV, total 2145859048
@@ -797,7 +808,7 @@ else if (line.indexOf("Calloc returns null") != -1) {
// Writing: /home/user/.sheepit/LEFT packed.crash.txt
return Error.Type.RENDERER_OUT_OF_MEMORY;
}
- else if (line.indexOf("Malloc returns null") != -1) {
+ else if (line.contains("Malloc returns null")) {
// Fra:1 Mem:976.60M (0.00M, Peak 1000.54M) | Time:00:01.34 | Mem:0.00M, Peak:0.00M | Scene, RenderLayer | Synchronizing object | Left
// Calloc returns null: len=7186416 in CDMLoopUV, total 2145859048
// Calloc returns null: len=7186416 in CDMLoopUV, total 2145859048
@@ -805,7 +816,7 @@ else if (line.indexOf("Malloc returns null") != -1) {
// Writing: /home/user/.sheepit/LEFT packed.crash.txt
return Error.Type.RENDERER_OUT_OF_MEMORY;
}
- else if (line.indexOf("CUDA kernel compilation failed") != -1 || line.indexOf("Refer to the Cycles GPU rendering documentation for possible solutions:" != -1) {
+ else if (line.contains("CUDA kernel compilation failed")) {
// Fra:1 Mem:200.70M (0.00M, Peak 378.15M) | Time:00:01.02 | Mem:0.00M, Peak:0.00M | Scene, RenderLayer | Synchronizing object | Sun.001
// Fra:1 Mem:200.70M (0.00M, Peak 378.15M) | Time:00:01.02 | Mem:0.00M, Peak:0.00M | Scene, RenderLayer | Synchronizing object | Sun.002
// Fra:1 Mem:200.70M (0.00M, Peak 378.15M) | Time:00:01.02 | Mem:0.00M, Peak:0.00M | Scene, RenderLayer | Initializing
@@ -827,17 +838,19 @@ else if (line.indexOf("CUDA kernel compilation failed") != -1 || line.indexOf("R
return Type.OK;
}
- public int getTileSize() {
- if (config.getTileSize() == -1) { // not set
- int size = 32; // CPU
- GPUDevice gpu = this.config.getGPUDevice();
- if (getUseGPU() && gpu != null) {
- return gpu.getRecommandedTileSize();
- }
- return size;
+ public static class renderStartedObservable extends Observable {
+
+ @Getter private boolean isStarted;
+
+ public renderStartedObservable(Observer observer) {
+ super();
+ addObserver(observer);
}
- else {
- return config.getTileSize();
+
+ public void doNotifyIsStarted() {
+ setChanged();
+ notifyObservers();
+ isStarted = true;
}
}
}
diff --git a/src/com/sheepit/client/Log.java b/src/com/sheepit/client/Log.java
index 37d29456..6ef8d587 100644
--- a/src/com/sheepit/client/Log.java
+++ b/src/com/sheepit/client/Log.java
@@ -2,7 +2,7 @@
* Copyright (C) 2011-2014 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -39,31 +39,52 @@ 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");
+ this.dateFormat = new SimpleDateFormat("dd-MM HH:mm:ss");
}
public void debug(String msg_) {
- this.append("debug", msg_);
+ this.debug(-1, msg_);
+ }
+
+ public void debug(int point_, String msg_) {
+ this.append(point_, "debug", msg_);
}
public void info(String msg_) {
- this.append("info", msg_);
+ this.info(-1, msg_);
+ }
+
+ public void info(int point_, String msg_) {
+ this.append(point_, "info", msg_);
}
public void error(String msg_) {
- this.append("error", msg_);
+ this.error(-1, msg_);
+ }
+
+ public void error(int point_, String msg_) {
+ this.append(point_, "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);
+ private synchronized void append(int point_, String level_, String msg_) {
+ String line = null;
+
+ try {
+ int checkpointToWrite = (point_ > 0 ? point_ : this.lastCheckPoint);
+
+ if (msg_.equals("") == false) {
+ line = this.dateFormat.format(new java.util.Date()) + " (" + level_ + ") " + msg_;
+ if (this.checkpoints.containsKey(checkpointToWrite) && this.checkpoints.get(checkpointToWrite) != null) {
+ this.checkpoints.get(checkpointToWrite).add(line);
+ }
+ if (this.printStdOut == true) {
+ System.out.println(line);
+ }
}
}
+ catch (Exception e) {
+ // Nothing to do here. Just allow the thread to continue
+ }
}
public int newCheckPoint() {
@@ -89,7 +110,7 @@ public static synchronized Log getInstance(Configuration config) {
if (instance == null) {
boolean print = false;
if (config != null) {
- print = config.getPrintLog();
+ print = config.isPrintLog();
}
instance = new Log(print);
}
diff --git a/src/com/sheepit/client/Pair.java b/src/com/sheepit/client/Pair.java
index 47643d0b..5b4fee8e 100644
--- a/src/com/sheepit/client/Pair.java
+++ b/src/com/sheepit/client/Pair.java
@@ -19,35 +19,34 @@
import java.util.Objects;
/**
-* Container to ease passing around a pair of two objects. This object provides a sensible
-* implementation of equals(), returning true if equals() is true on each of the contained
-* objects.
-*/
+ * Container to ease passing around a pair 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
- */
+ * 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) {
+ * 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;
}
@@ -56,22 +55,21 @@ public boolean equals(Object o) {
}
/**
- * Compute a hash code using the hash codes of the underlying objects
- *
- * @return a hashcode of the Pair
- */
- @Override
- public int hashCode() {
+ * 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.
+ * 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
- */
+ * @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/RenderProcess.java b/src/com/sheepit/client/RenderProcess.java
index 108ea84d..105f4452 100644
--- a/src/com/sheepit/client/RenderProcess.java
+++ b/src/com/sheepit/client/RenderProcess.java
@@ -2,7 +2,7 @@
* Copyright (C) 2015 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -19,82 +19,47 @@
package com.sheepit.client;
+import lombok.Data;
+
import java.util.Date;
-public class RenderProcess {
- private long start;
- private long end;
- private int remainingDuration;
+@Data public class RenderProcess {
+ private long startTime;
+ private long endTime;
+ private int remainingDuration; // in seconds
private long memoryUsed; // in kB
private int coresUsed;
private Process process;
public RenderProcess() {
process = null;
- start = -1;
- end = -1;
+ startTime = -1;
+ endTime = -1;
memoryUsed = 0;
coresUsed = 0;
remainingDuration = 0;
}
- public void setMemoryUsed(long val) {
- memoryUsed = val;
- }
-
- public long getMemoryUsed() {
- return memoryUsed;
- }
-
- public void setCoresUsed(int val) {
- coresUsed = val;
- }
-
- public int getCoresUsed() {
- return coresUsed;
- }
-
- public long getStartTime() {
- return start;
- }
-
- public long getEndTime() {
- return end;
- }
-
/**
- *
* @return duration in seconds
*/
public int getDuration() {
- if (start != -1 && end != -1) {
- return (int) ((end - start) / 1000);
+ if (startTime != -1 && endTime != -1) {
+ return (int) ((endTime - startTime) / 1000);
}
- else if (start != -1) {
- return (int) ((new Date().getTime() - start) / 1000);
+ else if (startTime != -1) {
+ return (int) ((new Date().getTime() - startTime) / 1000);
}
return 0;
}
- /**
- *
- * @return duration in seconds
- */
- public int getRemainingDuration() {
- return remainingDuration;
- }
-
- public void setRemainingDuration(int val) {
- remainingDuration = val;
- }
-
public void finish() {
- end = new Date().getTime();
+ endTime = new Date().getTime();
process = null;
}
public void start() {
- start = new Date().getTime();
+ startTime = new Date().getTime();
}
public int exitValue() {
@@ -118,12 +83,4 @@ public int exitValue() {
}
return value;
}
-
- public void setProcess(Process val) {
- process = val;
- }
-
- public Process getProcess() {
- return process;
- }
}
diff --git a/src/com/sheepit/client/Server.java b/src/com/sheepit/client/Server.java
index 05fb7b74..b5bbd1f5 100644
--- a/src/com/sheepit/client/Server.java
+++ b/src/com/sheepit/client/Server.java
@@ -2,7 +2,7 @@
* Copyright (C) 2010-2014 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -19,60 +19,46 @@
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.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
-import java.net.ConnectException;
-import java.net.CookieHandler;
-import java.net.CookieManager;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.NoRouteToHostException;
-import java.net.URL;
-import java.net.URLEncoder;
-import java.security.KeyManagementException;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
+import java.net.*;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
import java.util.Date;
-import java.util.HashMap;
import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
-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 lombok.Getter;
+import org.simpleframework.xml.core.Persister;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
-import org.xml.sax.SAXException;
+import okhttp3.Call;
+import okhttp3.FormBody;
+import okhttp3.HttpUrl;
+import okhttp3.MediaType;
+import okhttp3.MultipartBody;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import okhttp3.JavaNetCookieJar;
import com.sheepit.client.Configuration.ComputeType;
import com.sheepit.client.Error.ServerCode;
+import com.sheepit.client.datamodel.CacheFileMD5;
+import com.sheepit.client.datamodel.FileMD5;
+import com.sheepit.client.datamodel.HeartBeatInfos;
+import com.sheepit.client.datamodel.JobInfos;
+import com.sheepit.client.datamodel.JobValidation;
+import com.sheepit.client.datamodel.RequestEndPoint;
+import com.sheepit.client.datamodel.ServerConfig;
import com.sheepit.client.exception.FermeException;
import com.sheepit.client.exception.FermeExceptionBadResponseFromServer;
import com.sheepit.client.exception.FermeExceptionNoRendererAvailable;
@@ -85,11 +71,15 @@
import com.sheepit.client.exception.FermeServerDown;
import com.sheepit.client.os.OS;
-public class Server extends Thread implements HostnameVerifier, X509TrustManager {
+
+public class Server extends Thread {
private String base_url;
+ private final OkHttpClient httpClient;
+
+ @Getter private ServerConfig serverConfig;
+
private Configuration user_config;
private Client client;
- private HashMap pages;
private Log log;
private long lastRequestTime;
private int keepmealive_duration; // time in ms
@@ -99,13 +89,14 @@ public Server(String url_, Configuration user_config_, Client client_) {
this.base_url = url_;
this.user_config = user_config_;
this.client = client_;
- this.pages = new HashMap();
this.log = Log.getInstance(this.user_config);
this.lastRequestTime = 0;
this.keepmealive_duration = 15 * 60 * 1000; // default 15min
- CookieManager cookies = new CookieManager();
- CookieHandler.setDefault(cookies);
+ // OkHttp performs best when we create a single OkHttpClient instance and reuse it for all of the HTTP calls. This is because each client holds its own
+ // connection pool and thread pools.Reusing connections and threads reduces latency and saves memory. Conversely, creating a client for each request
+ // wastes resources on idle pools.
+ this.httpClient = getOkHttpClient();
}
public void run() {
@@ -117,38 +108,44 @@ public void stayAlive() {
long current_time = new Date().getTime();
if ((current_time - this.lastRequestTime) > this.keepmealive_duration) {
try {
- String args = "";
+ HttpUrl.Builder urlBuilder = Objects.requireNonNull(HttpUrl.parse(this.getPage("keepmealive"))).newBuilder();
+
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().isEmpty() == false) {
- args += "&extras=" + this.client.getRenderingJob().getExtras();
+ Job job = this.client.getRenderingJob();
+
+ urlBuilder.addQueryParameter("frame", job.getFrameNumber()).addQueryParameter("job", job.getId());
+ if (job.getExtras() != null && !job.getExtras().isEmpty()) {
+ urlBuilder.addQueryParameter("extras", job.getExtras());
}
- if (this.client.getRenderingJob().getProcessRender() != null) {
- args += "&rendertime=" + this.client.getRenderingJob().getProcessRender().getDuration();
- args += "&remainingtime=" + this.client.getRenderingJob().getProcessRender().getRemainingDuration();
+
+ RenderProcess process = job.getProcessRender();
+ if (process != null) {
+ urlBuilder.addQueryParameter("rendertime", String.valueOf(process.getDuration()))
+ .addQueryParameter("remainingtime", String.valueOf(process.getRemainingDuration()));
}
}
- HttpURLConnection connection = this.HTTPRequest(this.getPage("keepmealive") + args);
+ Response response = this.HTTPRequest(urlBuilder);
- if (connection.getResponseCode() == HttpURLConnection.HTTP_OK && connection.getContentType().startsWith("text/xml")) {
- DataInputStream in = new DataInputStream(connection.getInputStream());
+ if (response.code() == HttpURLConnection.HTTP_OK && response.body().contentType().toString().startsWith("text/xml")) {
+ String in = response.body().string();
+
try {
- Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in);
- ServerCode ret = Utils.statusIsOK(document, "keepmealive");
- if (ret == ServerCode.KEEPMEALIVE_STOP_RENDERING) {
+ HeartBeatInfos heartBeartInfos = new Persister().read(HeartBeatInfos.class, in);
+ ServerCode serverCode = ServerCode.fromInt(heartBeartInfos.getStatus());
+ if (serverCode == ServerCode.KEEPMEALIVE_STOP_RENDERING) {
this.log.debug("Server::stayAlive server asked 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().getProcessRender().getProcess() != null) {
+ if (this.client != null && this.client.getRenderingJob() != null
+ && this.client.getRenderingJob().getProcessRender().getProcess() != null) {
this.client.getRenderingJob().setServerBlockJob(true);
this.client.getRenderingJob().setAskForRendererKill(true);
OS.getOS().kill(this.client.getRenderingJob().getProcessRender().getProcess());
}
}
}
- catch (SAXException e) {
- }
- catch (ParserConfigurationException e) {
+ catch (Exception e) { // for the read
+ this.log.debug("Server::stayAlive Exception " + e);
}
}
}
@@ -178,95 +175,61 @@ public void stayAlive() {
}
public String toString() {
- return String.format("Server (base_url '%s', user_config %s, pages %s", this.base_url, this.user_config, this.pages);
+ return String.format("Server (base_url '%s', user_config %s", this.base_url, this.user_config);
}
public Error.Type getConfiguration() {
OS os = OS.getOS();
- HttpURLConnection connection = null;
+ String publickey = null;
try {
- String url_remote = this.base_url + "/server/config.php";
- String parameters = String.format("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&hostname=%s&ui=%s&extras=%s",
- URLEncoder.encode(this.user_config.login(), "UTF-8"),
- URLEncoder.encode(this.user_config.password(), "UTF-8"),
- URLEncoder.encode(os.getCPU().family(), "UTF-8"),
- URLEncoder.encode(os.getCPU().model(), "UTF-8"),
- URLEncoder.encode(os.getCPU().name(), "UTF-8"),
- ((this.user_config.getNbCores() == -1) ? os.getCPU().cores() : this.user_config.getNbCores()),
- URLEncoder.encode(os.name(), "UTF-8"),
- os.getMemory(),
- URLEncoder.encode(os.getCPU().arch(), "UTF-8"),
- this.user_config.getJarVersion(),
- URLEncoder.encode(this.user_config.getHostname(), "UTF-8"),
- this.client.getGui().getClass().getSimpleName(),
- this.user_config.getExtras());
- this.log.debug("Server::getConfiguration url " + url_remote);
+ HttpUrl.Builder remoteURL = Objects.requireNonNull(HttpUrl.parse(this.base_url + "/server/config.php")).newBuilder();
+ FormBody formBody = new FormBody.Builder()
+ .add("login", user_config.getLogin())
+ .add("password", user_config.getPassword())
+ .add("cpu_family", os.getCPU().family())
+ .add("cpu_model", os.getCPU().model())
+ .add("cpu_model_name", os.getCPU().name())
+ .add("cpu_cores", String.valueOf(user_config.getNbCores() == -1 ? os.getCPU().cores() : user_config.getNbCores()))
+ .add("os", os.name())
+ .add("ram", String.valueOf(os.getMemory()))
+ .add("bits", os.getCPU().arch())
+ .add("version", user_config.getJarVersion())
+ .add("hostname", user_config.getHostname())
+ .add("ui", client.getGui().getClass().getSimpleName())
+ .add("extras", user_config.getExtras())
+ .build();
- connection = this.HTTPRequest(url_remote, parameters);
- int r = connection.getResponseCode();
- String contentType = connection.getContentType();
+ this.log.debug("Server::getConfiguration url " + remoteURL.build().toString());
+
+ Response response = this.HTTPRequest(remoteURL, formBody);
+ int r = response.code();
+ String contentType = response.body().contentType().toString();
if (r == HttpURLConnection.HTTP_OK && contentType.startsWith("text/xml")) {
- DataInputStream in = new DataInputStream(connection.getInputStream());
- Document document = null;
+ String in = response.body().string();
+ serverConfig = new Persister().read(ServerConfig.class, in);
- 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;
+ if (ServerCode.fromInt(serverConfig.getStatus()) != ServerCode.OK) {
+ return Error.ServerCodeToType(ServerCode.fromInt(serverConfig.getStatus()));
}
- ServerCode ret = Utils.statusIsOK(document, "config");
- if (ret != ServerCode.OK) {
- return Error.ServerCodeToType(ret);
+ publickey = serverConfig.getPublickey();
+ if (publickey.isEmpty()) {
+ publickey = null;
}
-
- 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'");
- return Error.Type.WRONG_CONFIGURATION;
+ else {
+ this.user_config.setPassword(publickey);
}
- 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' has 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 if (r == HttpURLConnection.HTTP_OK && contentType.startsWith("text/html")) {
- return Error.Type.ERROR_BAD_RESPONSE;
- }
- 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: exception UnknownHostException " + e);
+ return Error.Type.NETWORK_ISSUE;
+ }
catch (UnsupportedEncodingException e) {
this.log.error("Server::getConfiguration: exception UnsupportedEncodingException " + e);
return Error.Type.UNKNOWN;
@@ -275,11 +238,13 @@ else if (r == HttpURLConnection.HTTP_OK && contentType.startsWith("text/html"))
this.log.error("Server::getConfiguration: exception IOException " + e);
return Error.Type.UNKNOWN;
}
- finally {
- if (connection != null) {
- connection.disconnect();
- }
+ catch (Exception e) {
+ this.log.error("Server::getConfiguration: exception Exception " + e);
+ return Error.Type.UNKNOWN;
}
+
+ this.client.getGui().successfulAuthenticationEvent(publickey);
+
return Error.Type.OK;
}
@@ -287,211 +252,110 @@ public Job requestJob() throws FermeException {
this.log.debug("Server::requestJob");
String url_contents = "";
- HttpURLConnection connection = null;
try {
OS os = OS.getOS();
- int maxMemory = this.user_config.getMaxMemory();
- int freeMemory = os.getFreeMemory();
+ long maxMemory = this.user_config.getMaxMemory();
+ long freeMemory = os.getFreeMemory();
if (maxMemory < 0) {
maxMemory = freeMemory;
}
else if (freeMemory > 0 && maxMemory > 0) {
maxMemory = Math.min(maxMemory, freeMemory);
}
- String url = String.format("%s?computemethod=%s&cpu_cores=%s&ram_max=%s&rendertime_max=%s", this.getPage("request-job"), this.user_config.computeMethodToInt(), ((this.user_config.getNbCores() == -1) ? os.getCPU().cores() : this.user_config.getNbCores()), maxMemory, this.user_config.getMaxRenderTime());
- if (this.user_config.getComputeMethod() != ComputeType.CPU && 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() + "&gpu_type=" + this.user_config.getGPUDevice().getType();
- }
- connection = this.HTTPRequest(url, this.generateXMLForMD5cache());
+ HttpUrl.Builder urlBuilder = Objects.requireNonNull(HttpUrl.parse(this.getPage("request-job"))).newBuilder()
+ .addQueryParameter("computemethod", String.valueOf(user_config.computeMethodToInt()))
+ .addQueryParameter("cpu_cores", String.valueOf(user_config.getNbCores() == -1 ? os.getCPU().cores() : user_config.getNbCores()))
+ .addQueryParameter("ram_max", String.valueOf(maxMemory))
+ .addQueryParameter("rendertime_max", String.valueOf(user_config.getMaxRenderTime()));
- int r = connection.getResponseCode();
- String contentType = connection.getContentType();
+ if (user_config.getComputeMethod() != ComputeType.CPU && user_config.getGPUDevice() != null) {
+ urlBuilder.addQueryParameter("gpu_model", user_config.getGPUDevice().getModel())
+ .addQueryParameter("gpu_ram", String.valueOf(user_config.getGPUDevice().getMemory()))
+ .addQueryParameter("gpu_type", user_config.getGPUDevice().getType());
+ }
+
+ Response response = this.HTTPRequest(urlBuilder, RequestBody.create(MediaType.parse("application/xml"), this.generateXMLForMD5cache()));
+
+ int r = response.code();
+ String contentType = response.body().contentType().toString();
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);
- }
+ String in = response.body().string();
- 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_RENDERER_NOT_AVAILABLE) {
- throw new FermeExceptionNoRendererAvailable();
- }
- else if (ret == ServerCode.JOB_REQUEST_ERROR_SESSION_DISABLED) {
- throw new FermeExceptionSessionDisabled();
- }
- else if (ret == ServerCode.JOB_REQUEST_SERVER_IN_MAINTENANCE) {
- throw new FermeExceptionServerInMaintenance();
- }
- else if (ret == ServerCode.JOB_REQUEST_SERVER_OVERLOADED) {
- throw new FermeExceptionServerOverloaded();
- }
- 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");
+ JobInfos jobData = new Persister().read(JobInfos.class, in);
- Element a_node = null;
- NodeList ns = null;
-
- ns = document.getElementsByTagName("stats");
- if (ns.getLength() == 0) {
- throw new FermeException("error requestJob: parseXML failed, no 'frame' node");
- }
- a_node = (Element) ns.item(0);
+ handleFileMD5DeleteDocument(jobData.getFileMD5s());
- int remaining_frames = 0;
- int credits_earned = 0;
- int credits_earned_session = 0;
- int waiting_project = 0;
- int connected_machine = 0;
- if (a_node.hasAttribute("frame_remaining") && a_node.hasAttribute("credits_total") && a_node.hasAttribute("credits_session") && a_node.hasAttribute("waiting_project") && a_node.hasAttribute("connected_machine")) {
- remaining_frames = Integer.parseInt(a_node.getAttribute("frame_remaining"));
- credits_earned = Integer.parseInt(a_node.getAttribute("credits_total"));
- credits_earned_session = Integer.parseInt(a_node.getAttribute("credits_session"));
- waiting_project = Integer.parseInt(a_node.getAttribute("waiting_project"));
- connected_machine = Integer.parseInt(a_node.getAttribute("connected_machine"));
+ if (jobData.getSessionStats() != null) {
+ this.client.getGui().displayStats(
+ new Stats(jobData.getSessionStats().getRemainingFrames(), jobData.getSessionStats().getPointsEarnedByUser(),
+ jobData.getSessionStats().getPointsEarnedOnSession(), jobData.getSessionStats().getRenderableProjects(),
+ jobData.getSessionStats().getWaitingProjects(), jobData.getSessionStats().getConnectedMachines()));
}
- 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'");
+ ServerCode serverCode = ServerCode.fromInt(jobData.getStatus());
+ if (serverCode != ServerCode.OK) {
+ switch (serverCode) {
+ case JOB_REQUEST_NOJOB:
+ return null;
+ case JOB_REQUEST_ERROR_NO_RENDERING_RIGHT:
+ throw new FermeExceptionNoRightToRender();
+ case JOB_REQUEST_ERROR_DEAD_SESSION:
+ throw new FermeExceptionNoSession();
+ case JOB_REQUEST_ERROR_SESSION_DISABLED:
+ throw new FermeExceptionSessionDisabled();
+ case JOB_REQUEST_ERROR_INTERNAL_ERROR:
+ throw new FermeExceptionBadResponseFromServer();
+ case JOB_REQUEST_ERROR_RENDERER_NOT_AVAILABLE:
+ throw new FermeExceptionNoRendererAvailable();
+ case JOB_REQUEST_SERVER_IN_MAINTENANCE:
+ throw new FermeExceptionServerInMaintenance();
+ case JOB_REQUEST_SERVER_OVERLOADED:
+ throw new FermeExceptionServerOverloaded();
+ default:
+ throw new FermeException("error requestJob: status is not ok (it's " + serverCode + ")");
+ }
}
- Element renderer_node = (Element) ns.item(0);
String script = "import bpy\n";
-
// blender 2.7x
script += "try:\n";
- script += "\tbpy.context.user_preferences.filepaths.temporary_directory = \"" + this.user_config.workingDirectory.getAbsolutePath().replace("\\", "\\\\") + "\"\n";
+ script += "\tbpy.context.user_preferences.filepaths.temporary_directory = \"" + this.user_config.getWorkingDirectory().getAbsolutePath()
+ .replace("\\", "\\\\") + "\"\n";
script += "except AttributeError:\n";
script += "\tpass\n";
// blender 2.80
script += "try:\n";
- script += "\tbpy.context.preferences.filepaths.temporary_directory = \"" + this.user_config.workingDirectory.getAbsolutePath().replace("\\", "\\\\") + "\"\n";
+ script += "\tbpy.context.preferences.filepaths.temporary_directory = \"" + this.user_config.getWorkingDirectory().getAbsolutePath()
+ .replace("\\", "\\\\") + "\"\n";
script += "except AttributeError:\n";
script += "\tpass\n";
- try {
- ns = job_node.getElementsByTagName("script");
- if (ns.getLength() != 0) {
- Element a_node3 = (Element) ns.item(0);
- script += a_node3.getTextContent();
- }
- }
- catch (Exception e) {
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
- e.printStackTrace(pw);
- this.log.debug("Server::requestJob Exception " + e + " stacktrace: " + sw.toString());
- }
+ script += jobData.getRenderTask().getScript();
- String[] job_node_require_attribute = { "id", "archive_md5", "path", "use_gpu", "frame", "name", "extras", "password" };
- String[] renderer_node_require_attribute = { "md5", "commandline" };
+ String validationUrl = URLDecoder.decode(jobData.getRenderTask().getValidationUrl(), "UTF-8");
- 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);
- boolean synchronous_upload = true;
- if (job_node.hasAttribute("synchronous_upload")) {
- synchronous_upload = (job_node.getAttribute("synchronous_upload").compareTo("1") == 0);
- }
-
- String frame_extras = "";
- if (job_node.hasAttribute("extras")) {
- frame_extras = job_node.getAttribute("extras");
- }
-
- String update_method = null;
- if (renderer_node.hasAttribute("update_method")) {
- update_method = renderer_node.getAttribute("update_method");
- }
-
- Job a_job = new Job(
- this.user_config,
- this.client.getGui(),
- this.client.getLog(),
- job_node.getAttribute("id"),
- job_node.getAttribute("frame"),
- job_node.getAttribute("path").replace("/", File.separator),
- use_gpu,
- renderer_node.getAttribute("commandline"),
- script,
- job_node.getAttribute("archive_md5"),
- renderer_node.getAttribute("md5"),
- job_node.getAttribute("name"),
- job_node.getAttribute("password"),
- frame_extras,
- synchronous_upload,
- update_method
- );
-
- this.client.getGui().displayStats(new Stats(remaining_frames, credits_earned, credits_earned_session, waiting_project, connected_machine));
+ Job a_job = new Job(this.user_config, this.client.getGui(), this.client.getLog(), jobData.getRenderTask().getId(),
+ jobData.getRenderTask().getFrame(), jobData.getRenderTask().getPath().replace("/", File.separator),
+ jobData.getRenderTask().getUseGpu() == 1, jobData.getRenderTask().getRendererInfos().getCommandline(), validationUrl, script,
+ jobData.getRenderTask().getArchive_md5(), jobData.getRenderTask().getRendererInfos().getMd5(), jobData.getRenderTask().getName(),
+ jobData.getRenderTask().getPassword(), jobData.getRenderTask().getExtras(), jobData.getRenderTask().getSynchronous_upload().equals("1"),
+ jobData.getRenderTask().getRendererInfos().getUpdate_method());
return a_job;
}
else {
System.out.println("Server::requestJob url " + url_contents + " r " + r + " contentType " + contentType);
- if (r == HttpURLConnection.HTTP_UNAVAILABLE || r == HttpURLConnection. HTTP_CLIENT_TIMEOUT) {
+ if (r == HttpURLConnection.HTTP_UNAVAILABLE || r == HttpURLConnection.HTTP_CLIENT_TIMEOUT) {
// most likely varnish is up but apache down
throw new FermeServerDown();
}
else if (r == HttpURLConnection.HTTP_OK && contentType.startsWith("text/html")) {
throw new FermeExceptionBadResponseFromServer();
}
- 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("");
+ System.out.println(response.body().string());
}
}
catch (FermeException e) {
@@ -500,108 +364,96 @@ else if (r == HttpURLConnection.HTTP_OK && contentType.startsWith("text/html"))
catch (NoRouteToHostException e) {
throw new FermeServerDown();
}
+ catch (UnknownHostException e) {
+ throw new FermeServerDown();
+ }
catch (Exception e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
throw new FermeException("error requestJob: unknown exception " + e + " stacktrace: " + sw.toString());
}
- 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 Response HTTPRequest(String url) throws IOException {
+ HttpUrl.Builder httpUrlBuilder = Objects.requireNonNull(HttpUrl.parse(url)).newBuilder();
+ return this.HTTPRequest(httpUrlBuilder, 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.setInstanceFollowRedirects(true);
- connection.setRequestMethod("GET");
+ public Response HTTPRequest(HttpUrl.Builder httpUrlBuilder) throws IOException {
+ return this.HTTPRequest(httpUrlBuilder, null);
+ }
+
+ public Response HTTPRequest(HttpUrl.Builder httpUrlBuilder, RequestBody data_) throws IOException {
+ String url = httpUrlBuilder.build().toString();
+ Request.Builder builder = new Request.Builder();
+ builder.url(url);
- 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) {
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
- e.printStackTrace(pw);
- this.log.debug("Server::HTTPRequest NoSuchAlgorithmException " + e + " stacktrace: " + sw.toString());
- return null;
- }
- catch (KeyManagementException e) {
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
- e.printStackTrace(pw);
- this.log.debug("Server::HTTPRequest KeyManagementException " + e + " stacktrace: " + sw.toString());
- return null;
- }
- }
+ this.log.debug("Server::HTTPRequest url(" + url + ")");
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();
+ builder.post(data_);
}
- // actually use the connection to, in case of timeout, generate an exception
- connection.getResponseCode();
-
- this.lastRequestTime = new Date().getTime();
+ Request request = builder.build();
+ Response response = null;
- return connection;
+ try {
+ response = httpClient.newCall(request).execute();
+
+ if (!response.isSuccessful()) {
+ throw new IOException("Unexpected code " + response);
+ }
+
+ this.lastRequestTime = new Date().getTime();
+ return response;
+ }
+ catch (IOException e) {
+ throw new IOException("Unexpected response from HTTP Stack" + e.getMessage());
+ }
}
public int HTTPGetFile(String url_, String destination_, Gui gui_, String status_) throws FermeExceptionNoSpaceLeftOnDevice {
// the destination_ parent directory must exist
try {
- HttpURLConnection httpCon = this.HTTPRequest(url_);
+ Response response = this.HTTPRequest(url_);
- InputStream inStrm = httpCon.getInputStream();
- if (httpCon.getResponseCode() != HttpURLConnection.HTTP_OK) {
- this.log.error("Server::HTTPGetFile(" + url_ + ", ...) HTTP code is not " + HttpURLConnection.HTTP_OK + " it's " + httpCon.getResponseCode());
+ if (response.code() != HttpURLConnection.HTTP_OK) {
+ this.log.error("Server::HTTPGetFile(" + url_ + ", ...) HTTP code is not " + HttpURLConnection.HTTP_OK + " it's " + response.code());
return -1;
}
- int size = httpCon.getContentLength();
+
long start = new Date().getTime();
+ InputStream is = response.body().byteStream();
+ OutputStream output = new FileOutputStream(destination_);
- FileOutputStream fos = new FileOutputStream(destination_);
- byte[] ch = new byte[512 * 1024];
- int nb;
+ long size = response.body().contentLength();
+ byte[] buffer = new byte[8 * 1024];
+ int len = 0;
long written = 0;
- long last_gui_update = 0; // size in byte
- while ((nb = inStrm.read(ch)) != -1) {
- fos.write(ch, 0, nb);
- written += nb;
- if ((written - last_gui_update) > 1000000) { // only update the gui every 1MB
- gui_.status(String.format(status_, (int) (100.0 * written / size)));
- last_gui_update = written;
+ long lastUpd = 0; // last GUI progress update
+
+ while ((len = is.read(buffer)) != -1) {
+ output.write(buffer, 0, len);
+ written += len;
+
+ if ((written - lastUpd) > 1000000) { // only update the gui every 1MB
+ gui_.status(status_, (int) (100.0 * written / size), written);
+ lastUpd = written;
}
}
- fos.close();
- inStrm.close();
+
+ output.flush();
+ output.close();
+ is.close();
+
+ gui_.status(status_, 100, size);
+
long end = new Date().getTime();
this.log.debug(String.format("File downloaded at %.1f kB/s, written %d B", ((float) (size / 1000)) / ((float) (end - start) / 1000), written));
this.lastRequestTime = new Date().getTime();
+
return 0;
}
catch (Exception e) {
@@ -613,331 +465,163 @@ public int HTTPGetFile(String url_, String destination_, Gui gui_, String status
e.printStackTrace(new PrintWriter(sw));
this.log.error("Server::HTTPGetFile Exception " + e + " stacktrace " + sw.toString());
}
- this.log.debug("Server::HTTPGetFile(" + url_ + ", ...) will failed (end of function)");
+ this.log.debug(String.format("Server::HTTPGetFile(%s) did fail", url_));
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;
+ public ServerCode HTTPSendFile(String surl, String file1, int checkpoint) {
+ this.log.debug(checkpoint, "Server::HTTPSendFile(" + surl + "," + file1 + ")");
try {
- FileInputStream fileInputStream = new FileInputStream(new File(exsistingFileName));
- URL url = new URL(urlString);
+ String fileMimeType = Files.probeContentType(Paths.get(file1));
+
+ MediaType MEDIA_TYPE = MediaType.parse(fileMimeType); // e.g. "image/png"
- conn = (HttpURLConnection) url.openConnection();
- conn.setDoInput(true);
- conn.setDoOutput(true);
- conn.setInstanceFollowRedirects(true);
- conn.setUseCaches(false);
+ RequestBody uploadContent = new MultipartBody.Builder().setType(MultipartBody.FORM)
+ .addFormDataPart("file", new File(file1).getName(), RequestBody.create(new File(file1), MEDIA_TYPE)).build();
- conn.setRequestMethod("POST");
- conn.setRequestProperty("Connection", "Keep-Alive");
- conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
+ Request request = new Request.Builder().url(surl).post(uploadContent).build();
- if (urlString.startsWith("https://")) {
+ Call call = httpClient.newCall(request);
+ Response response = call.execute();
+
+ int r = response.code();
+ String contentType = response.body().contentType().toString();
+
+ if (r == HttpURLConnection.HTTP_OK && contentType.startsWith("text/xml")) {
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) {
-
+ String in = response.body().string();
+ JobValidation jobValidation = new Persister().read(JobValidation.class, in);
+
+ this.lastRequestTime = new Date().getTime();
+
+ ServerCode serverCode = ServerCode.fromInt(jobValidation.getStatus());
+ if (serverCode != ServerCode.OK) {
+ this.log.error(checkpoint, "Server::HTTPSendFile wrong status (is " + serverCode + ")");
+ return serverCode;
}
- return ServerCode.UNKNOWN;
}
- catch (KeyManagementException e) {
- this.log.error("Server::HTTPSendFile, exception KeyManagementException " + e);
- try {
- fileInputStream.close();
- }
- catch (Exception e1) {
-
- }
- return ServerCode.UNKNOWN;
+ catch (Exception e) { // for the .read
+ e.printStackTrace();
}
+
+ return ServerCode.OK;
}
-
- 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);
+ else if (r == HttpURLConnection.HTTP_OK && contentType.startsWith("text/html")) {
+ return ServerCode.ERROR_BAD_RESPONSE;
+ }
+ else {
+ this.log.error(String.format("Server::HTTPSendFile Unknown response received from server: %s", response.body().string()));
}
- dos.writeBytes(lineEnd);
- dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
- fileInputStream.close();
- dos.flush();
- dos.close();
- }
- catch (MalformedURLException e) {
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
- e.printStackTrace(pw);
- this.log.error("Server::HTTPSendFile, MalformedURLException " + e + " stacktrace " + sw.toString());
return ServerCode.UNKNOWN;
}
catch (IOException e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
- this.log.error("Server::HTTPSendFile, IOException " + e + " stacktrace " + sw.toString());
+ this.log.error(checkpoint, String.format("Server::HTTPSendFile Error in upload process. Exception %s stacktrace ", e.getMessage(), sw.toString()));
return ServerCode.UNKNOWN;
}
catch (OutOfMemoryError e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
- this.log.error("Server::HTTPSendFile, OutOfMemoryError " + e + " stacktrace " + sw.toString());
+ this.log.error(checkpoint, "Server::HTTPSendFile, OutOfMemoryError " + e + " stacktrace " + sw.toString());
return ServerCode.JOB_VALIDATION_ERROR_UPLOAD_FAILED;
}
catch (Exception e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
- this.log.error("Server::HTTPSendFile, Exception " + e + " stacktrace " + sw.toString());
- return ServerCode.UNKNOWN;
- }
-
- int r;
- try {
- r = conn.getResponseCode();
- }
- catch (IOException e) {
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
- e.printStackTrace(pw);
- this.log.debug("Server::HTTPSendFile IOException " + e + " stacktrace: " + sw.toString());
+ this.log.error(checkpoint, "Server::HTTPSendFile, Exception " + e + " stacktrace " + sw.toString());
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 e) {
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
- e.printStackTrace(pw);
- this.log.debug("Server::HTTPSendFile IOException " + e + " stacktrace: " + sw.toString());
- return ServerCode.UNKNOWN;
- }
- Document document = null;
+ }
+
+ private String generateXMLForMD5cache() {
+ List md5s = new ArrayList<>();
+ for (File local_file : this.user_config.getLocalCacheFiles()) {
try {
- document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in);
- }
- catch (SAXException e) {
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
- e.printStackTrace(pw);
- this.log.debug("Server::HTTPSendFile SAXException " + e + " stacktrace: " + sw.toString());
- return ServerCode.UNKNOWN;
- }
- catch (IOException e) {
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
- e.printStackTrace(pw);
- this.log.debug("Server::HTTPSendFile IOException " + e + " stacktrace: " + sw.toString());
- return ServerCode.UNKNOWN;
- }
- catch (ParserConfigurationException e) {
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
- e.printStackTrace(pw);
- this.log.debug("Server::HTTPSendFile ParserConfigurationException " + e + " stacktrace: " + sw.toString());
- return ServerCode.UNKNOWN;
+ 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);
+ FileMD5 fileMD5 = new FileMD5();
+ fileMD5.setMd5(name);
+
+ md5s.add(fileMD5);
+ }
}
-
- 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;
+ catch (StringIndexOutOfBoundsException e) { // because the file does not have an . its path
}
- return ServerCode.OK;
}
- else if (r == HttpURLConnection.HTTP_OK && contentType.startsWith("text/html")) {
- return ServerCode.ERROR_BAD_RESPONSE;
+
+ CacheFileMD5 cache = new CacheFileMD5();
+ cache.setMd5s(md5s);
+
+ final Persister persister = new Persister();
+ try (StringWriter writer = new StringWriter()) {
+ persister.write(cache, writer);
+ return writer.toString();
}
- else {
- try {
- inStream = new BufferedReader(new InputStreamReader(conn.getInputStream()));
-
- String str;
- while ((str = inStream.readLine()) != null) {
- System.out.println(str);
- System.out.println("");
+ catch (final Exception e) {
+ log.debug("Failed to dump md5s " + e);
+ return "";
+ }
+ }
+
+ private void handleFileMD5DeleteDocument(List fileMD5s) {
+ if (fileMD5s != null && fileMD5s.isEmpty() == false) {
+ for (FileMD5 fileMD5 : fileMD5s) {
+ if ("delete".equals(fileMD5.getAction()) && fileMD5.getMd5() != null && fileMD5.getMd5().isEmpty() == false) {
+ String path = this.user_config.getWorkingDirectory().getAbsolutePath() + File.separatorChar + fileMD5.getMd5();
+ 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));
}
- inStream.close();
- }
- catch (IOException ioex) {
}
}
- return ServerCode.UNKNOWN;
}
- public byte[] getLastRender() {
- try {
- HttpURLConnection httpCon = this.HTTPRequest(this.getPage("last-render-frame"));
-
- InputStream inStrm = httpCon.getInputStream();
- if (httpCon.getResponseCode() != HttpURLConnection.HTTP_OK) {
- this.log.debug("Server::getLastRender code not ok " + httpCon.getResponseCode());
- return null;
- }
- int size = httpCon.getContentLength();
-
- if (size <= 0) {
- this.log.debug("Server::getLastRender size is negative (size: " + size + ")");
- return null;
- }
-
- byte[] ret = new byte[size];
- byte[] ch = new byte[512 * 1024];
- int n = 0;
- int i = 0;
- while ((n = inStrm.read(ch)) != -1) {
- System.arraycopy(ch, 0, ret, i, n);
- i += n;
+ public String getPage(String key) {
+ if (this.serverConfig != null) {
+ RequestEndPoint endpoint = this.serverConfig.getRequestEndPoint(key);
+ if (endpoint != null) {
+ return this.base_url + endpoint.getPath();
}
- inStrm.close();
- return ret;
- }
- catch (Exception e) {
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
- e.printStackTrace(pw);
- this.log.debug("Server::getLastRender Exception " + e + " stacktrace: " + sw.toString());
}
- return null;
+ return "";
}
- private String generateXMLForMD5cache() {
- String xml_str = null;
+ private OkHttpClient getOkHttpClient() {
try {
- DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
- DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
- Document document_cache = docBuilder.newDocument();
+ OkHttpClient.Builder builder = new OkHttpClient.Builder();
- Element rootElement = document_cache.createElement("cache");
- document_cache.appendChild(rootElement);
+ CookieManager cookieManager = new CookieManager();
+ cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
+ builder.cookieJar(new JavaNetCookieJar(cookieManager)); // Cookie store to maintain the session across calls
- 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
- }
- }
+ builder.connectTimeout(30, TimeUnit.SECONDS); // Cancel the HTTP Request if the connection to server takes more than 10 seconds
+ builder.writeTimeout(60, TimeUnit.SECONDS); // Cancel the upload if the client cannot send any byte in 60 seconds
- 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));
- }
- }
+ // If the user has selected a proxy, then we must increase the download timeout. Reason being the way proxies work. To download a large file (i.e.
+ // a 500MB job), the proxy must first download the file to the proxy cache and then the information is sent fast to the SheepIt client. From a client
+ // viewpoint, the HTTP connection will make the CONNECT step really fast but then the time until the fist byte is received (the time measured by
+ // readTimeout) will be really long (minutes). Without a proxy in the middle, a connection that does receive nothing in 60 seconds might be
+ // considered a dead connection.
+ if (this.user_config.getProxy() != null) {
+ builder.readTimeout(10, TimeUnit.MINUTES); // Proxy enabled - 10 minutes
+ }
+ else {
+ builder.readTimeout(1, TimeUnit.MINUTES); // No proxy - 60 seconds max
}
+
+ return builder.build();
}
- }
-
- public String getPage(String key) {
- if (this.pages.containsKey(key)) {
- return this.base_url + this.pages.get(key);
+ catch (Exception e) {
+ throw new RuntimeException(e);
}
- 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/SettingsLoader.java b/src/com/sheepit/client/SettingsLoader.java
index 553bc87c..445462bd 100644
--- a/src/com/sheepit/client/SettingsLoader.java
+++ b/src/com/sheepit/client/SettingsLoader.java
@@ -2,7 +2,7 @@
* Copyright (C) 2015 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -32,28 +32,32 @@
import java.util.Properties;
import java.util.Set;
-import com.sheepit.client.Configuration;
import com.sheepit.client.Configuration.ComputeType;
import com.sheepit.client.hardware.gpu.GPU;
import com.sheepit.client.hardware.gpu.GPUDevice;
+import lombok.Setter;
public class SettingsLoader {
private String path;
private String login;
- private String password;
+
+ @Setter private String password;
+
private String proxy;
private String hostname;
private String computeMethod;
private String gpu;
+ private String renderbucketSize;
private String cores;
private String ram;
private String renderTime;
private String cacheDir;
private String autoSignIn;
+ private String useSysTray;
private String ui;
- private String tileSize;
- private int priority;
+ private String theme;
+ private int priority;
public SettingsLoader(String path_) {
if (path_ == null) {
@@ -64,7 +68,9 @@ public SettingsLoader(String path_) {
}
}
- public SettingsLoader(String path_, String login_, String password_, String proxy_, String hostname_, ComputeType computeMethod_, GPUDevice gpu_, int cores_, int maxRam_, int maxRenderTime_, String cacheDir_, boolean autoSignIn_, String ui_, String tileSize_, int priority_) {
+ public SettingsLoader(String path_, String login_, String password_, String proxy_, String hostname_, ComputeType computeMethod_, GPUDevice gpu_,
+ int renderbucketSize_, int cores_, long maxRam_, int maxRenderTime_, String cacheDir_, boolean autoSignIn_, boolean useSysTray_, String ui_,
+ String theme_, int priority_) {
if (path_ == null) {
path = getDefaultFilePath();
}
@@ -77,14 +83,16 @@ public SettingsLoader(String path_, String login_, String password_, String prox
hostname = hostname_;
cacheDir = cacheDir_;
autoSignIn = String.valueOf(autoSignIn_);
+ useSysTray = String.valueOf(useSysTray_);
ui = ui_;
- tileSize = tileSize_;
priority = priority_;
+ theme = theme_;
+
if (cores_ > 0) {
cores = String.valueOf(cores_);
}
if (maxRam_ > 0) {
- ram = String.valueOf(maxRam_);
+ ram = String.valueOf(maxRam_) + "k";
}
if (maxRenderTime_ > 0) {
renderTime = String.valueOf(maxRenderTime_);
@@ -100,6 +108,10 @@ public SettingsLoader(String path_, String login_, String password_, String prox
if (gpu_ != null) {
gpu = gpu_.getId();
}
+
+ if (renderbucketSize_ >= 32) {
+ renderbucketSize = String.valueOf(renderbucketSize_);
+ }
}
public static String getDefaultFilePath() {
@@ -129,6 +141,10 @@ public void saveFile() {
prop.setProperty("compute-gpu", gpu);
}
+ if (renderbucketSize != null) {
+ prop.setProperty("renderbucket-size", renderbucketSize);
+ }
+
if (cores != null) {
prop.setProperty("cores", cores);
}
@@ -161,12 +177,16 @@ public void saveFile() {
prop.setProperty("auto-signin", autoSignIn);
}
+ if (useSysTray != null) {
+ prop.setProperty("use-systray", useSysTray);
+ }
+
if (ui != null) {
prop.setProperty("ui", ui);
}
- if (tileSize != null) {
- prop.setProperty("tile-size", tileSize);
+ if (theme != null) {
+ prop.setProperty("theme", theme);
}
prop.store(output, null);
@@ -208,13 +228,15 @@ public void loadFile() {
this.hostname = null;
this.computeMethod = null;
this.gpu = null;
+ this.renderbucketSize = null;
this.cacheDir = null;
this.autoSignIn = null;
+ this.useSysTray = null;
this.ui = null;
- this.tileSize = null;
this.priority = 19; // must be the same default as Configuration
this.ram = null;
this.renderTime = null;
+ this.theme = null;
if (new File(path).exists() == false) {
return;
@@ -238,6 +260,10 @@ public void loadFile() {
this.gpu = prop.getProperty("compute-gpu");
}
+ if (prop.containsKey("renderbucket-size")) {
+ this.renderbucketSize = prop.getProperty("renderbucket-size");
+ }
+
if (prop.containsKey("cpu-cores")) { // backward compatibility
this.cores = prop.getProperty("cpu-cores");
}
@@ -274,12 +300,16 @@ public void loadFile() {
this.autoSignIn = prop.getProperty("auto-signin");
}
+ if (prop.containsKey("use-systray")) {
+ this.useSysTray = prop.getProperty("use-systray");
+ }
+
if (prop.containsKey("ui")) {
this.ui = prop.getProperty("ui");
}
- if (prop.containsKey("tile-size")) {
- this.tileSize = prop.getProperty("tile-size");
+ if (prop.containsKey("theme")) {
+ this.theme = prop.getProperty("theme");
}
if (prop.containsKey("priority")) {
@@ -312,10 +342,10 @@ public void merge(Configuration config) {
loadFile();
- if (config.login().isEmpty() && login != null) {
+ if (config.getLogin().isEmpty() && login != null) {
config.setLogin(login);
}
- if (config.password().isEmpty() && password != null) {
+ if (config.getPassword().isEmpty() && password != null) {
config.setPassword(password);
}
@@ -331,7 +361,8 @@ public void merge(Configuration config) {
config.setUsePriority(priority);
}
try {
- if ((config.getComputeMethod() == null && computeMethod != null) || (computeMethod != null && config.getComputeMethod() != ComputeType.valueOf(computeMethod))) {
+ if ((config.getComputeMethod() == null && computeMethod != null) || (computeMethod != null && config.getComputeMethod() != ComputeType
+ .valueOf(computeMethod))) {
config.setComputeMethod(ComputeType.valueOf(computeMethod));
}
}
@@ -342,22 +373,41 @@ public void merge(Configuration config) {
if (config.getGPUDevice() == null && gpu != null) {
GPUDevice device = GPU.getGPUDevice(gpu);
if (device != null) {
- config.setUseGPU(device);
+ config.setGPUDevice(device);
+
+ // If the user has indicated a render bucket size at least 32x32 px, overwrite the config file value
+ if (config.getRenderbucketSize() >= 32) {
+ config.getGPUDevice().setRenderbucketSize(config.getRenderbucketSize()); // Update size
+ }
+ else {
+ // If the configuration file does have any value
+ if (renderbucketSize != null) {
+ config.getGPUDevice().setRenderbucketSize(Integer.valueOf(renderbucketSize));
+ }
+ else {
+ // Don't do anything here as the GPU get's a default value when it's initialised
+ // The configuration will take the default GPU value
+ }
+ }
+
+ // And now update the client configuration with the new value
+ config.setRenderbucketSize(config.getGPUDevice().getRenderbucketSize());
}
}
+
if (config.getNbCores() == -1 && cores != null) {
- config.setUseNbCores(Integer.valueOf(cores));
+ config.setNbCores(Integer.valueOf(cores));
}
if (config.getMaxMemory() == -1 && ram != null) {
- config.setMaxMemory(Integer.valueOf(ram));
+ config.setMaxMemory(Utils.parseNumber(ram) / 1000); // internal ram value is in kB
}
if (config.getMaxRenderTime() == -1 && renderTime != null) {
config.setMaxRenderTime(Integer.valueOf(renderTime));
}
- if (config.getUserHasSpecifiedACacheDir() == false && cacheDir != null) {
+ if (config.isUserHasSpecifiedACacheDir() == false && cacheDir != null) {
config.setCacheDir(new File(cacheDir));
}
@@ -365,15 +415,27 @@ public void merge(Configuration config) {
config.setUIType(ui);
}
- if (config.getTileSize() == -1 && tileSize != null) {
- config.setTileSize(Integer.valueOf(tileSize));
+ if (config.getTheme() == null) {
+ if (this.theme != null) {
+ config.setTheme(this.theme);
+ }
+ else {
+ config.setTheme("light");
+ }
+ }
+
+ // if the user has invoked the app with --no-systray, then we just overwrite the existing configuration with (boolean)false. If no parameter has been
+ // specified and the settings file contains use-systray=false, then deactivate as well.
+ if (!config.isUseSysTray() || (config.isUseSysTray() && useSysTray != null && useSysTray.equals("false"))) {
+ config.setUseSysTray(false);
}
- config.setAutoSignIn(Boolean.valueOf(autoSignIn));
+ config.setAutoSignIn(Boolean.parseBoolean(autoSignIn));
}
- @Override
- public String toString() {
- return "SettingsLoader [path=" + path + ", login=" + login + ", password=" + password + ", computeMethod=" + computeMethod + ", gpu=" + gpu + ", cacheDir=" + cacheDir + "priority="+priority+"]";
+ @Override public String toString() {
+ return String.format(
+ "SettingsLoader [path=%s, login=%s, password=%s, computeMethod=%s, gpu=%s, renderbucket-size=%s, cacheDir=%s, theme=%s, priority=%d, autosign=%s, usetray=%s]",
+ path, login, password, computeMethod, gpu, renderbucketSize, cacheDir, theme, priority, autoSignIn, useSysTray);
}
}
diff --git a/src/com/sheepit/client/ShutdownHook.java b/src/com/sheepit/client/ShutdownHook.java
index 7b8ac75a..79744a8a 100644
--- a/src/com/sheepit/client/ShutdownHook.java
+++ b/src/com/sheepit/client/ShutdownHook.java
@@ -2,7 +2,7 @@
* Copyright (C) 2013 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -28,8 +28,7 @@ public ShutdownHook(Client client_) {
public void attachShutDownHook() {
Runtime.getRuntime().addShutdownHook(new Thread() {
- @Override
- public void run() {
+ @Override public void run() {
_client.stop();
}
});
diff --git a/src/com/sheepit/client/Stats.java b/src/com/sheepit/client/Stats.java
index 5b3c66b8..c14d81ae 100644
--- a/src/com/sheepit/client/Stats.java
+++ b/src/com/sheepit/client/Stats.java
@@ -2,7 +2,7 @@
* Copyright (C) 2016 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -23,13 +23,15 @@ public class Stats {
private int remainingFrame;
private int creditsEarned;
private int creditsEarnedSession;
+ private int renderableProject;
private int waitingProject;
private int connectedMachine;
- public Stats(int frame, int credits, int creditsSession, int waitings, int machines) {
+ public Stats(int frame, int credits, int creditsSession, int renderables, int waitings, int machines) {
remainingFrame = frame;
creditsEarned = credits;
creditsEarnedSession = creditsSession;
+ renderableProject = renderables;
waitingProject = waitings;
connectedMachine = machines;
}
@@ -38,6 +40,7 @@ public Stats() {
remainingFrame = 0;
creditsEarned = 0;
creditsEarnedSession = 0;
+ renderableProject = 0;
waitingProject = 0;
connectedMachine = 0;
}
@@ -54,6 +57,10 @@ public int getCreditsEarned() {
return creditsEarned;
}
+ public int getRenderableProject() {
+ return renderableProject;
+ }
+
public int getWaitingProject() {
return waitingProject;
}
@@ -62,8 +69,8 @@ public int getConnectedMachine() {
return connectedMachine;
}
- @Override
- public String toString() {
- return "Stats [remainingFrame=" + remainingFrame + ", creditsEarned=" + creditsEarned + ", creditsEarnedSession=" + creditsEarnedSession + ", waitingProject=" + waitingProject + ", connectedMachine=" + connectedMachine + "]";
+ @Override public String toString() {
+ return "Stats [remainingFrame=" + remainingFrame + ", creditsEarned=" + creditsEarned + ", creditsEarnedSession=" + creditsEarnedSession
+ + ", renderableProject=" + renderableProject + ", waitingProject=" + waitingProject + ", connectedMachine=" + connectedMachine + "]";
}
}
diff --git a/src/com/sheepit/client/Utils.java b/src/com/sheepit/client/Utils.java
index 5ada0196..cb28cb14 100644
--- a/src/com/sheepit/client/Utils.java
+++ b/src/com/sheepit/client/Utils.java
@@ -2,7 +2,7 @@
* Copyright (C) 2010-2014 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -20,8 +20,6 @@
package com.sheepit.client;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
@@ -51,7 +49,8 @@
import com.sheepit.client.exception.FermeExceptionNoSpaceLeftOnDevice;
public class Utils {
- public static int unzipFileIntoDirectory(String zipFileName_, String destinationDirectory, String password, Log log) throws FermeExceptionNoSpaceLeftOnDevice {
+ public static int unzipFileIntoDirectory(String zipFileName_, String destinationDirectory, String password, Log log)
+ throws FermeExceptionNoSpaceLeftOnDevice {
try {
ZipFile zipFile = new ZipFile(zipFileName_);
UnzipParameters unzipParameters = new UnzipParameters();
@@ -146,6 +145,10 @@ public static void delete(File file) {
file.delete();
}
+ /**
+ * Parse a number string to a number.
+ * Input can be as "32", "10k", "100K", "100G", "1.3G", "0.4T"
+ */
public static long parseNumber(String in) {
in = in.trim();
in = in.replaceAll(",", ".");
@@ -158,18 +161,22 @@ public static long parseNumber(String in) {
m.find();
int scale = 1;
switch (m.group(2).charAt(0)) {
+ case 'T':
+ case 't':
+ scale = 1000 * 1000 * 1000 * 1000;
+ break;
case 'G':
- scale *= 1000;
case 'g':
- scale *= 1000;
+ scale = 1000 * 1000 * 1000;
+ break;
case 'M':
- scale *= 1000;
case 'm':
- scale *= 1000;
+ scale = 1000 * 1000;
+ break;
case 'K':
+ case 'k':
+ scale = 1000;
break;
- default:
- return 0;
}
return Math.round(Double.parseDouble(m.group(1)) * scale);
}
@@ -184,10 +191,10 @@ public static String humanDuration(Date date) {
String output = "";
if (hours > 0) {
- output += hours + "h";
+ output += hours + "h ";
}
if (minutes > 0) {
- output += minutes + "min";
+ output += minutes + "min ";
}
if (seconds > 0) {
output += seconds + "s";
diff --git a/src/com/sheepit/client/datamodel/CacheFileMD5.java b/src/com/sheepit/client/datamodel/CacheFileMD5.java
new file mode 100644
index 00000000..8cd91d60
--- /dev/null
+++ b/src/com/sheepit/client/datamodel/CacheFileMD5.java
@@ -0,0 +1,16 @@
+package com.sheepit.client.datamodel;
+
+import lombok.Data;
+import lombok.ToString;
+import org.simpleframework.xml.ElementList;
+import org.simpleframework.xml.Root;
+
+import java.util.List;
+
+@Root(strict = false, name = "cache") @Data @ToString public class CacheFileMD5 {
+
+ @ElementList(inline = true) private List md5s;
+
+ public CacheFileMD5() {
+ }
+}
diff --git a/src/com/sheepit/client/datamodel/FileMD5.java b/src/com/sheepit/client/datamodel/FileMD5.java
new file mode 100644
index 00000000..7469e17d
--- /dev/null
+++ b/src/com/sheepit/client/datamodel/FileMD5.java
@@ -0,0 +1,16 @@
+package com.sheepit.client.datamodel;
+
+import lombok.Data;
+import lombok.ToString;
+import org.simpleframework.xml.Attribute;
+import org.simpleframework.xml.Root;
+
+@Root(strict = false, name = "file") @Data @ToString public class FileMD5 {
+
+ @Attribute private String md5;
+
+ @Attribute(required = false) private String action;
+
+ public FileMD5() {
+ }
+}
diff --git a/src/com/sheepit/client/datamodel/HeartBeatInfos.java b/src/com/sheepit/client/datamodel/HeartBeatInfos.java
new file mode 100644
index 00000000..950f135d
--- /dev/null
+++ b/src/com/sheepit/client/datamodel/HeartBeatInfos.java
@@ -0,0 +1,10 @@
+package com.sheepit.client.datamodel;
+
+import lombok.Getter;
+import lombok.ToString;
+import org.simpleframework.xml.Attribute;
+import org.simpleframework.xml.Root;
+
+@Root(strict = false, name = "keepmealive") @ToString public class HeartBeatInfos {
+ @Attribute @Getter private int status;
+}
diff --git a/src/com/sheepit/client/datamodel/JobInfos.java b/src/com/sheepit/client/datamodel/JobInfos.java
new file mode 100644
index 00000000..b4c879aa
--- /dev/null
+++ b/src/com/sheepit/client/datamodel/JobInfos.java
@@ -0,0 +1,24 @@
+package com.sheepit.client.datamodel;
+
+import lombok.Getter;
+import lombok.ToString;
+import org.simpleframework.xml.Attribute;
+import org.simpleframework.xml.Element;
+import org.simpleframework.xml.ElementList;
+import org.simpleframework.xml.Root;
+
+import java.util.List;
+
+@Root(strict = false, name = "jobrequest") @ToString public class JobInfos {
+
+ @Attribute @Getter private int status;
+
+ @Element(name = "stats", required = false) @Getter private SessionStats sessionStats;
+
+ @Element(name = "job", required = false) @Getter() private RenderTask renderTask;
+
+ @ElementList(name = "file", inline = true, required = false) @Getter private List fileMD5s;
+
+ public JobInfos() {
+ }
+}
diff --git a/src/com/sheepit/client/datamodel/JobValidation.java b/src/com/sheepit/client/datamodel/JobValidation.java
new file mode 100644
index 00000000..01749a5b
--- /dev/null
+++ b/src/com/sheepit/client/datamodel/JobValidation.java
@@ -0,0 +1,14 @@
+package com.sheepit.client.datamodel;
+
+import lombok.Getter;
+import lombok.ToString;
+import org.simpleframework.xml.Attribute;
+import org.simpleframework.xml.Root;
+
+@Root(strict = false, name = "jobvalidate") @ToString public class JobValidation {
+
+ @Attribute @Getter private int status;
+
+ public JobValidation() {
+ }
+}
diff --git a/src/com/sheepit/client/datamodel/RenderTask.java b/src/com/sheepit/client/datamodel/RenderTask.java
new file mode 100644
index 00000000..c1e34384
--- /dev/null
+++ b/src/com/sheepit/client/datamodel/RenderTask.java
@@ -0,0 +1,38 @@
+package com.sheepit.client.datamodel;
+
+import lombok.Getter;
+import lombok.ToString;
+import org.simpleframework.xml.Attribute;
+import org.simpleframework.xml.Element;
+import org.simpleframework.xml.Root;
+
+@Root(strict = false, name = "job") @ToString public class RenderTask {
+
+ @Attribute(name = "id") @Getter private String id;
+
+ @Attribute(name = "use_gpu") @Getter private int useGpu;
+
+ @Attribute(name = "archive_md5") @Getter private String archive_md5;
+
+ @Attribute(name = "path") @Getter private String path;
+
+ @Attribute(name = "frame") @Getter private String frame;
+
+ @Attribute(name = "synchronous_upload") @Getter private String synchronous_upload;
+
+ @Attribute(name = "extras") @Getter private String extras;
+
+ @Attribute(name = "validation_url") @Getter private String validationUrl;
+
+ @Attribute(name = "name") @Getter private String name;
+
+ @Attribute(name = "password") @Getter private String password;
+
+ @Element(name = "renderer") @Getter private RendererInfos rendererInfos;
+
+ @Element(name = "script", data = true) @Getter private String script;
+
+ public RenderTask() {
+
+ }
+}
diff --git a/src/com/sheepit/client/datamodel/RendererInfos.java b/src/com/sheepit/client/datamodel/RendererInfos.java
new file mode 100644
index 00000000..c352a9ba
--- /dev/null
+++ b/src/com/sheepit/client/datamodel/RendererInfos.java
@@ -0,0 +1,19 @@
+package com.sheepit.client.datamodel;
+
+import lombok.Getter;
+import lombok.ToString;
+import org.simpleframework.xml.Attribute;
+import org.simpleframework.xml.Root;
+
+@Root(strict = false, name = "renderer") @ToString public class RendererInfos {
+
+ @Attribute(name = "md5") @Getter private String md5;
+
+ @Attribute(name = "commandline") @Getter private String commandline;
+
+ @Attribute(name = "update_method") @Getter private String update_method;
+
+ public RendererInfos() {
+
+ }
+}
diff --git a/src/com/sheepit/client/datamodel/RequestEndPoint.java b/src/com/sheepit/client/datamodel/RequestEndPoint.java
new file mode 100644
index 00000000..6c512924
--- /dev/null
+++ b/src/com/sheepit/client/datamodel/RequestEndPoint.java
@@ -0,0 +1,18 @@
+package com.sheepit.client.datamodel;
+
+import lombok.Getter;
+import lombok.ToString;
+import org.simpleframework.xml.Attribute;
+import org.simpleframework.xml.Root;
+
+@Root(strict = false, name = "request") @ToString public class RequestEndPoint {
+
+ @Attribute @Getter private String type;
+
+ @Attribute @Getter private String path;
+
+ @Attribute(name = "max-period", required = false) @Getter private int maxPeriod;
+
+ public RequestEndPoint() {
+ }
+}
diff --git a/src/com/sheepit/client/datamodel/ServerConfig.java b/src/com/sheepit/client/datamodel/ServerConfig.java
new file mode 100644
index 00000000..71b84be8
--- /dev/null
+++ b/src/com/sheepit/client/datamodel/ServerConfig.java
@@ -0,0 +1,32 @@
+package com.sheepit.client.datamodel;
+
+import lombok.Getter;
+import lombok.ToString;
+import org.simpleframework.xml.Attribute;
+import org.simpleframework.xml.ElementList;
+import org.simpleframework.xml.Root;
+
+import java.util.List;
+
+@Root(strict = false, name = "config") @ToString public class ServerConfig {
+
+ @Attribute @Getter private int status;
+
+ @Attribute(required = false) @Getter private String publickey;
+
+ @ElementList(name = "request", inline = true, required = false) private List requestEndPoints;
+
+ public ServerConfig() {
+ }
+
+ public RequestEndPoint getRequestEndPoint(String type) {
+ if (requestEndPoints != null) {
+ for (RequestEndPoint endPoint : requestEndPoints) {
+ if (type.equals(endPoint.getType())) {
+ return endPoint;
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/com/sheepit/client/datamodel/SessionStats.java b/src/com/sheepit/client/datamodel/SessionStats.java
new file mode 100644
index 00000000..ae03c199
--- /dev/null
+++ b/src/com/sheepit/client/datamodel/SessionStats.java
@@ -0,0 +1,25 @@
+package com.sheepit.client.datamodel;
+
+import lombok.Getter;
+import lombok.ToString;
+import org.simpleframework.xml.Attribute;
+import org.simpleframework.xml.Root;
+
+@Root(strict = false, name = "stats") @ToString public class SessionStats {
+
+ @Attribute(name = "credits_session") @Getter private int pointsEarnedOnSession;
+
+ @Attribute(name = "credits_total") @Getter private int pointsEarnedByUser;
+
+ @Attribute(name = "frame_remaining") @Getter private int remainingFrames;
+
+ @Attribute(name = "waiting_project") @Getter private int waitingProjects;
+
+ @Attribute(name = "renderable_project", required = false) @Getter private int renderableProjects;
+
+ @Attribute(name = "connected_machine") @Getter private int connectedMachines;
+
+ public SessionStats() {
+
+ }
+}
diff --git a/src/com/sheepit/client/exception/FermeException.java b/src/com/sheepit/client/exception/FermeException.java
index 6eda6828..c0799b93 100644
--- a/src/com/sheepit/client/exception/FermeException.java
+++ b/src/com/sheepit/client/exception/FermeException.java
@@ -2,7 +2,7 @@
* Copyright (C) 2010-2013 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
diff --git a/src/com/sheepit/client/exception/FermeExceptionBadResponseFromServer.java b/src/com/sheepit/client/exception/FermeExceptionBadResponseFromServer.java
index 010aeed1..1e5347f2 100644
--- a/src/com/sheepit/client/exception/FermeExceptionBadResponseFromServer.java
+++ b/src/com/sheepit/client/exception/FermeExceptionBadResponseFromServer.java
@@ -2,7 +2,7 @@
* Copyright (C) 2016 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
diff --git a/src/com/sheepit/client/exception/FermeExceptionNoRendererAvailable.java b/src/com/sheepit/client/exception/FermeExceptionNoRendererAvailable.java
index 054ef0f6..7682444b 100644
--- a/src/com/sheepit/client/exception/FermeExceptionNoRendererAvailable.java
+++ b/src/com/sheepit/client/exception/FermeExceptionNoRendererAvailable.java
@@ -2,7 +2,7 @@
* Copyright (C) 2017 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
diff --git a/src/com/sheepit/client/exception/FermeExceptionNoRightToRender.java b/src/com/sheepit/client/exception/FermeExceptionNoRightToRender.java
index 3d5ec6ed..fdfad57c 100644
--- a/src/com/sheepit/client/exception/FermeExceptionNoRightToRender.java
+++ b/src/com/sheepit/client/exception/FermeExceptionNoRightToRender.java
@@ -2,7 +2,7 @@
* Copyright (C) 2013 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
diff --git a/src/com/sheepit/client/exception/FermeExceptionNoSession.java b/src/com/sheepit/client/exception/FermeExceptionNoSession.java
index 4b6e17e6..08eaf3dd 100644
--- a/src/com/sheepit/client/exception/FermeExceptionNoSession.java
+++ b/src/com/sheepit/client/exception/FermeExceptionNoSession.java
@@ -2,7 +2,7 @@
* Copyright (C) 2013 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
diff --git a/src/com/sheepit/client/exception/FermeExceptionNoSpaceLeftOnDevice.java b/src/com/sheepit/client/exception/FermeExceptionNoSpaceLeftOnDevice.java
index 71c048e4..abd557f3 100644
--- a/src/com/sheepit/client/exception/FermeExceptionNoSpaceLeftOnDevice.java
+++ b/src/com/sheepit/client/exception/FermeExceptionNoSpaceLeftOnDevice.java
@@ -2,7 +2,7 @@
* Copyright (C) 2016 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
diff --git a/src/com/sheepit/client/exception/FermeExceptionServerInMaintenance.java b/src/com/sheepit/client/exception/FermeExceptionServerInMaintenance.java
index 6779ef8a..13804cea 100644
--- a/src/com/sheepit/client/exception/FermeExceptionServerInMaintenance.java
+++ b/src/com/sheepit/client/exception/FermeExceptionServerInMaintenance.java
@@ -2,7 +2,7 @@
* Copyright (C) 2016 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
diff --git a/src/com/sheepit/client/exception/FermeExceptionServerOverloaded.java b/src/com/sheepit/client/exception/FermeExceptionServerOverloaded.java
index a41a67e4..22d6eba4 100644
--- a/src/com/sheepit/client/exception/FermeExceptionServerOverloaded.java
+++ b/src/com/sheepit/client/exception/FermeExceptionServerOverloaded.java
@@ -2,7 +2,7 @@
* Copyright (C) 2016 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
diff --git a/src/com/sheepit/client/exception/FermeExceptionSessionDisabled.java b/src/com/sheepit/client/exception/FermeExceptionSessionDisabled.java
index 3997ec1b..81733c14 100644
--- a/src/com/sheepit/client/exception/FermeExceptionSessionDisabled.java
+++ b/src/com/sheepit/client/exception/FermeExceptionSessionDisabled.java
@@ -2,7 +2,7 @@
* Copyright (C) 2013 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
diff --git a/src/com/sheepit/client/exception/FermeServerDown.java b/src/com/sheepit/client/exception/FermeServerDown.java
index 00f3295f..9ccd534b 100644
--- a/src/com/sheepit/client/exception/FermeServerDown.java
+++ b/src/com/sheepit/client/exception/FermeServerDown.java
@@ -2,7 +2,7 @@
* Copyright (C) 2015 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -21,7 +21,6 @@
/**
* Server down (server side error) or unreachable (client side error)
- *
*/
public class FermeServerDown extends FermeException {
public FermeServerDown() {
diff --git a/src/com/sheepit/client/hardware/cpu/CPU.java b/src/com/sheepit/client/hardware/cpu/CPU.java
index 0d4ea7c4..49f59721 100644
--- a/src/com/sheepit/client/hardware/cpu/CPU.java
+++ b/src/com/sheepit/client/hardware/cpu/CPU.java
@@ -2,7 +2,7 @@
* Copyright (C) 2010-2014 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
diff --git a/src/com/sheepit/client/hardware/gpu/GPU.java b/src/com/sheepit/client/hardware/gpu/GPU.java
index 9866d42d..08dd2757 100644
--- a/src/com/sheepit/client/hardware/gpu/GPU.java
+++ b/src/com/sheepit/client/hardware/gpu/GPU.java
@@ -2,7 +2,7 @@
* Copyright (C) 2013-2014 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -63,7 +63,7 @@ public static List listModels() {
public static List listDevices(Configuration config) {
if (devices == null) {
- if (config.getDetectGPUs()) {
+ if (config.isDetectGPUs()) {
generate();
}
else {
@@ -74,8 +74,8 @@ public static List listDevices(Configuration config) {
return devices;
}
- public static GPUDevice getGPUDevice(String device_model) {
- if (device_model == null) {
+ public static GPUDevice getGPUDevice(String deviceId) {
+ if (deviceId == null) {
return null;
}
@@ -88,7 +88,7 @@ public static GPUDevice getGPUDevice(String device_model) {
}
for (GPUDevice dev : devices) {
- if (device_model.equals(dev.getId()) || device_model.equals(dev.getModel())) {
+ if (deviceId.equals(dev.getId()) || deviceId.equals(dev.getOldId())) {
return dev;
}
}
diff --git a/src/com/sheepit/client/hardware/gpu/GPUDevice.java b/src/com/sheepit/client/hardware/gpu/GPUDevice.java
index b138a517..58d1513a 100644
--- a/src/com/sheepit/client/hardware/gpu/GPUDevice.java
+++ b/src/com/sheepit/client/hardware/gpu/GPUDevice.java
@@ -2,7 +2,7 @@
* Copyright (C) 2013-2014 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -19,24 +19,36 @@
package com.sheepit.client.hardware.gpu;
+import com.sheepit.client.hardware.gpu.nvidia.Nvidia;
+import com.sheepit.client.hardware.gpu.opencl.OpenCL;
+
public class GPUDevice {
private String type;
private String model;
private long memory; // in B
+ private int renderBucketSize;
private String id;
+ private String oldId; // for backward compatibility
+
public GPUDevice(String type, String model, long ram, String id) {
this.type = type;
this.model = model;
this.memory = ram;
this.id = id;
+ this.renderBucketSize = 32;
+ }
+
+ public GPUDevice(String type, String model, long ram, String id, String oldId) {
+ this(type, model, ram, id);
+ this.oldId = oldId;
}
public String getType() {
return type;
}
-
+
public void setType(String type) {
this.type = type;
}
@@ -65,15 +77,50 @@ public void setId(String id) {
this.id = id;
}
- @Override
- public String toString() {
- return "GPUDevice [type=" + type + ", model='" + model + "', memory=" + memory + ", id=" + id + "]";
+ public String getOldId() {
+ return oldId;
}
- public int getRecommandedTileSize() {
- // GPU
- // if the vram is lower than 1G reduce the size of tile to avoid black output
- return (getMemory() > 1073741824L) ? 256 : 128;
+ public void setOldId(String id) {
+ this.oldId = id;
}
+ public int getRenderbucketSize() {
+ return this.renderBucketSize;
+ }
+
+ public void setRenderbucketSize(int proposedRenderbucketSize) {
+ int renderBucketSize = 32;
+ GPULister gpu;
+
+ if (type.equals("CUDA")) {
+ gpu = new Nvidia();
+ }
+ else if (type.equals("OPENCL")) {
+ gpu = new OpenCL();
+ }
+ else {
+ // If execution takes this branch is because we weren't able to detect the proper GPU technology or
+ // because is a new one (different from CUDA and OPENCL). In that case, move into the safest position
+ // of 32x32 pixel tile sizes
+ System.out.println("GPUDevice::setRenderbucketSize Unable to detect GPU technology. Render bucket size set to 32x32 pixels");
+ this.renderBucketSize = 32;
+ return;
+ }
+
+ if (proposedRenderbucketSize >= 32) {
+ if (proposedRenderbucketSize <= gpu.getMaximumRenderBucketSize(getMemory())) {
+ renderBucketSize = proposedRenderbucketSize;
+ }
+ else {
+ renderBucketSize = gpu.getRecommendedRenderBucketSize(getMemory());
+ }
+ }
+
+ this.renderBucketSize = renderBucketSize;
+ }
+
+ @Override public String toString() {
+ return "GPUDevice [type=" + type + ", model='" + model + "', memory=" + memory + ", id=" + id + ", renderbucketSize=" + renderBucketSize + "]";
+ }
}
diff --git a/src/com/sheepit/client/hardware/gpu/GPULister.java b/src/com/sheepit/client/hardware/gpu/GPULister.java
index 8ef034c3..9aecafa3 100644
--- a/src/com/sheepit/client/hardware/gpu/GPULister.java
+++ b/src/com/sheepit/client/hardware/gpu/GPULister.java
@@ -4,4 +4,8 @@
public interface GPULister {
public abstract List getGpus();
+
+ public abstract int getRecommendedRenderBucketSize(long memory);
+
+ public abstract int getMaximumRenderBucketSize(long memory);
}
diff --git a/src/com/sheepit/client/hardware/gpu/nvidia/CUDA.java b/src/com/sheepit/client/hardware/gpu/nvidia/CUDA.java
index fbf17f85..38129f16 100644
--- a/src/com/sheepit/client/hardware/gpu/nvidia/CUDA.java
+++ b/src/com/sheepit/client/hardware/gpu/nvidia/CUDA.java
@@ -2,7 +2,7 @@
* Copyright (C) 2013-2014 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -27,17 +27,18 @@ 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
- */
+ * @return: CUDA_SUCCESS, CUDA_ERROR_DEINITIALIZED, CUDA_ERROR_NOT_INITIALIZED, CUDA_ERROR_INVALID_CONTEXT, CUDA_ERROR_INVALID_VALUE
+ */
public int cuDeviceGetCount(IntByReference count);
public int cuDeviceGetName(byte[] name, int len, int dev);
- public int cuDeviceGet (IntByReference device, int ordinal);
+ public int cuDeviceGet(IntByReference device, int ordinal);
- public int cuDeviceGetAttribute (IntByReference pi, int attrib, int dev );
+ public int cuDeviceGetAttribute(IntByReference pi, int attrib, int dev);
public int cuDeviceTotalMem_v2(LongByReference bytes, int dev);
+
public int cuDeviceTotalMem(LongByReference bytes, int dev);
}
diff --git a/src/com/sheepit/client/hardware/gpu/nvidia/CUDeviceAttribute.java b/src/com/sheepit/client/hardware/gpu/nvidia/CUDeviceAttribute.java
index 2529e352..20256d5d 100644
--- a/src/com/sheepit/client/hardware/gpu/nvidia/CUDeviceAttribute.java
+++ b/src/com/sheepit/client/hardware/gpu/nvidia/CUDeviceAttribute.java
@@ -2,7 +2,7 @@
* Copyright (C) 2018 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -28,12 +28,12 @@ public class CUDeviceAttribute {
* PCI bus ID of the device
*/
public static final int CU_DEVICE_ATTRIBUTE_PCI_BUS_ID = 33;
-
+
/**
* PCI device ID of the device
*/
public static final int CU_DEVICE_ATTRIBUTE_PCI_DEVICE_ID = 34;
-
+
/**
* PCI domain ID of the device
*/
diff --git a/src/com/sheepit/client/hardware/gpu/nvidia/CUresult.java b/src/com/sheepit/client/hardware/gpu/nvidia/CUresult.java
index cca5d4c3..70c42b9c 100644
--- a/src/com/sheepit/client/hardware/gpu/nvidia/CUresult.java
+++ b/src/com/sheepit/client/hardware/gpu/nvidia/CUresult.java
@@ -66,18 +66,18 @@ public class CUresult {
/**
* This indicates profiling APIs are called while application is running
- * in visual profiler mode.
+ * 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.
+ * 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.
+ * @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;
@@ -85,8 +85,8 @@ public class CUresult {
* 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
+ * @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;
@@ -95,8 +95,8 @@ public class CUresult {
* 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
+ * @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;
@@ -203,7 +203,7 @@ public class CUresult {
/**
* 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
+ * 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;
@@ -307,7 +307,7 @@ public class CUresult {
/**
* 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
+ * enabled yet via ::cuCtxEnablePeerAccess(), or that
* ::cuCtxDisablePeerAccess() is trying to disable peer access
* which has not been enabled yet.
*/
@@ -347,15 +347,15 @@ public class CUresult {
/**
* 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
+ * 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
+ * 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;
diff --git a/src/com/sheepit/client/hardware/gpu/nvidia/Nvidia.java b/src/com/sheepit/client/hardware/gpu/nvidia/Nvidia.java
index 0d98b960..a146027b 100644
--- a/src/com/sheepit/client/hardware/gpu/nvidia/Nvidia.java
+++ b/src/com/sheepit/client/hardware/gpu/nvidia/Nvidia.java
@@ -1,11 +1,8 @@
package com.sheepit.client.hardware.gpu.nvidia;
-import java.util.HashMap;
-import java.util.LinkedList;
+import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
-import com.sheepit.client.hardware.gpu.nvidia.CUDeviceAttribute;
import com.sheepit.client.hardware.gpu.GPUDevice;
import com.sheepit.client.hardware.gpu.GPULister;
import com.sheepit.client.os.OS;
@@ -16,8 +13,7 @@
public class Nvidia implements GPULister {
public static String TYPE = "CUDA";
- @Override
- public List getGpus() {
+ @Override public List getGpus() {
OS os = OS.getOS();
String path = os.getCUDALib();
if (path == null) {
@@ -25,7 +21,7 @@ public List getGpus() {
}
CUDA cudalib = null;
try {
- cudalib = (CUDA) Native.loadLibrary(path, CUDA.class);
+ cudalib = (CUDA) Native.load(path, CUDA.class);
}
catch (java.lang.UnsatisfiedLinkError e) {
return null;
@@ -64,24 +60,37 @@ public List getGpus() {
return null;
}
- List devices = new LinkedList();
+ List devices = new ArrayList<>(count.getValue());
- HashMap devicesWithPciId = new HashMap(count.getValue());
for (int num = 0; num < count.getValue(); num++) {
IntByReference aDevice = new IntByReference();
- result = cudalib.cuDeviceGet(aDevice, num);
+ result = cudalib.cuDeviceGet(aDevice, num);
if (result != CUresult.CUDA_SUCCESS) {
System.out.println("Nvidia::getGpus cuDeviceGet failed (ret: " + CUresult.stringFor(result) + ")");
continue;
}
+ IntByReference pciDomainId = new IntByReference();
IntByReference pciBusId = new IntByReference();
- result = cudalib.cuDeviceGetAttribute(pciBusId, CUDeviceAttribute.CU_DEVICE_ATTRIBUTE_PCI_BUS_ID, aDevice.getValue());
+ IntByReference pciDeviceId = new IntByReference();
+ result = cudalib.cuDeviceGetAttribute(pciDomainId, CUDeviceAttribute.CU_DEVICE_ATTRIBUTE_PCI_DOMAIN_ID, aDevice.getValue());
+ if (result != CUresult.CUDA_SUCCESS) {
+ System.out
+ .println("Nvidia::getGpus cuDeviceGetAttribute for CU_DEVICE_ATTRIBUTE_PCI_DOMAIN_ID failed (ret: " + CUresult.stringFor(result) + ")");
+ continue;
+ }
+ result = cudalib.cuDeviceGetAttribute(pciBusId, CUDeviceAttribute.CU_DEVICE_ATTRIBUTE_PCI_BUS_ID, aDevice.getValue());
if (result != CUresult.CUDA_SUCCESS) {
System.out.println("Nvidia::getGpus cuDeviceGetAttribute for CU_DEVICE_ATTRIBUTE_PCI_BUS_ID failed (ret: " + CUresult.stringFor(result) + ")");
continue;
}
+ result = cudalib.cuDeviceGetAttribute(pciDeviceId, CUDeviceAttribute.CU_DEVICE_ATTRIBUTE_PCI_DEVICE_ID, aDevice.getValue());
+ if (result != CUresult.CUDA_SUCCESS) {
+ System.out
+ .println("Nvidia::getGpus cuDeviceGetAttribute for CU_DEVICE_ATTRIBUTE_PCI_DEVICE_ID failed (ret: " + CUresult.stringFor(result) + ")");
+ continue;
+ }
byte name[] = new byte[256];
@@ -105,19 +114,23 @@ public List getGpus() {
return null;
}
- devicesWithPciId.put(pciBusId.getValue(), new GPUDevice(TYPE, new String(name).trim(), ram.getValue(), "FAKE"));
+ String blenderId = String
+ .format("CUDA_%s_%04x:%02x:%02x", new String(name).trim(), pciDomainId.getValue(), pciBusId.getValue(), pciDeviceId.getValue());
+ GPUDevice gpu = new GPUDevice(TYPE, new String(name).trim(), ram.getValue(), blenderId);
+ // for backward compatibility generate a CUDA_N id
+ gpu.setOldId(TYPE + "_" + num);
+ devices.add(gpu);
}
- // generate proper cuda id
- // in theory a set to environment "CUDA_DEVICE_ORDER=PCI_BUS_ID" should be enough but it didn't work
- int i = 0;
- for (Map.Entry entry : devicesWithPciId.entrySet()){
- GPUDevice aDevice = entry.getValue();
- aDevice.setId(TYPE + "_" + Integer.toString(i));
- devices.add(aDevice);
- i++;
- }
return devices;
}
+ @Override public int getRecommendedRenderBucketSize(long memory) {
+ // Optimal CUDA-based GPUs Renderbucket algorithm
+ return (memory > 1073741824L) ? 256 : 128;
+ }
+
+ @Override public int getMaximumRenderBucketSize(long memory) {
+ return (memory > 1073741824L) ? 512 : 128;
+ }
}
diff --git a/src/com/sheepit/client/hardware/gpu/opencl/OpenCL.java b/src/com/sheepit/client/hardware/gpu/opencl/OpenCL.java
index 294a05a7..e9fd1490 100644
--- a/src/com/sheepit/client/hardware/gpu/opencl/OpenCL.java
+++ b/src/com/sheepit/client/hardware/gpu/opencl/OpenCL.java
@@ -2,7 +2,7 @@
* Copyright (C) 2013-2014 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -34,13 +34,12 @@
public class OpenCL implements GPULister {
public static String TYPE = "OPENCL";
- @Override
- public List getGpus() {
+ @Override public List getGpus() {
OpenCLLib lib = null;
String path = "OpenCL";
try {
- lib = (OpenCLLib) Native.loadLibrary(path, OpenCLLib.class);
+ lib = (OpenCLLib) Native.load(path, OpenCLLib.class);
}
catch (java.lang.UnsatisfiedLinkError e) {
System.out.println("OpenCL::getGpus failed(A) to load OpenCL lib (path: " + path + ")");
@@ -105,13 +104,16 @@ public List getGpus() {
}
for (int j = 0; j < device_count.getValue(); j++) {
- String platform_vendor = getInfoPlatform(lib, plateforms[i], OpenCLLib.CL_PLATFORM_VENDOR);
- if (platform_vendor != null && platform_vendor.toLowerCase().equals("advanced micro devices, inc.")) { // opencl is only used for amd gpus
- String name = getInfodeviceString(lib, devices[j], OpenCLLib.CL_DEVICE_BOARD_NAME_AMD);
- long vram = getInfodeviceLong(lib, devices[j], OpenCLLib.CL_DEVICE_GLOBAL_MEM_SIZE);
- if (name != null && vram > 0) {
- available_devices.add(new GPUDevice(TYPE, name, vram, TYPE + "_" + id));
+ String name = getInfodeviceString(lib, devices[j], OpenCLLib.CL_DEVICE_BOARD_NAME_AMD);
+ String platform_name = getInfoPlatform(lib, plateforms[i], OpenCLLib.CL_PLATFORM_NAME);
+ long vram = getInfodeviceLong(lib, devices[j], OpenCLLib.CL_DEVICE_GLOBAL_MEM_SIZE);
+ if (name != null && vram > 0) {
+ if (name.equals("Radeon RX Vega")) {
+ name += " " + getInfodeviceLong(lib, devices[j], OpenCLLib.CL_DEVICE_MAX_COMPUTE_UNITS);
}
+ GPUDevice gpu = new GPUDevice(TYPE, name, vram, getBlenderId(lib, devices[j], platform_name, name));
+ gpu.setOldId(TYPE + "_" + id);
+ available_devices.add(gpu);
}
id++;
}
@@ -120,6 +122,15 @@ public List getGpus() {
return available_devices;
}
+ @Override public int getRecommendedRenderBucketSize(long memory) {
+ // Optimal CUDA-based GPUs Renderbucket algorithm
+ return (memory > 1073741824L) ? 256 : 128;
+ }
+
+ @Override public int getMaximumRenderBucketSize(long memory) {
+ return (memory > 1073741824L) ? 2048 : 128;
+ }
+
private static String getInfodeviceString(OpenCLLib lib, CLDeviceId.ByReference device, int type) {
byte name[] = new byte[256];
@@ -158,4 +169,16 @@ private static String getInfoPlatform(OpenCLLib lib, CLPlatformId.ByReference pl
return new String(name).trim();
}
+
+ private static String getBlenderId(OpenCLLib lib, CLDeviceId.ByReference device, String platform, String name) {
+ byte topology[] = new byte[24];
+
+ int status = lib.clGetDeviceInfo(device, lib.CL_DEVICE_TOPOLOGY_AMD, 24, topology, null);
+ if (status != OpenCLLib.CL_SUCCESS) {
+ System.out.println("OpenCL::getBlenderId failed(I) status: " + status);
+ return "";
+ }
+
+ return String.format("%s_%s_%s_%02x:%02x.%01x", TYPE, platform, name, topology[21], topology[22], topology[23]);
+ }
}
diff --git a/src/com/sheepit/client/hardware/gpu/opencl/OpenCLLib.java b/src/com/sheepit/client/hardware/gpu/opencl/OpenCLLib.java
index f9c23cc0..5ef585ba 100644
--- a/src/com/sheepit/client/hardware/gpu/opencl/OpenCLLib.java
+++ b/src/com/sheepit/client/hardware/gpu/opencl/OpenCLLib.java
@@ -2,7 +2,7 @@
* Copyright (C) 2013-2014 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -32,6 +32,7 @@ public interface OpenCLLib extends Library {
public static final int CL_DEVICE_NOT_FOUND = -1;
public static final int CL_PLATFORM_VENDOR = 0x0903;
+ public static final int CL_PLATFORM_NAME = 0x0902;
// cl_device_type
public static final int CL_DEVICE_TYPE_DEFAULT = (1 << 0);
@@ -43,14 +44,19 @@ public interface OpenCLLib extends Library {
// cl_device_info
public static final int CL_DEVICE_NAME = 0x102B;
+ public static final int CL_DEVICE_VENDOR = 0x102C;
+ public static final int CL_DEVICE_VERSION = 0x102D;
+ public static final int CL_DEVICE_MAX_COMPUTE_UNITS = 0x1002;
public static final int CL_DEVICE_GLOBAL_MEM_SIZE = 0x101F;
public static final int CL_DEVICE_BOARD_NAME_AMD = 0x4038;
+ public static final int CL_DEVICE_TOPOLOGY_AMD = 0x4037;
public int clGetPlatformIDs(int num_entries, CLPlatformId.ByReference[] platforms, IntByReference num_platforms);
public int clGetPlatformInfo(CLPlatformId.ByReference platform, int param_name, long param_value_size, byte[] destination, long size_ret[]);
- public int clGetDeviceIDs(CLPlatformId.ByReference platform, int param_name, int num_entries, CLDeviceId.ByReference[] devices, IntByReference device_count);
+ public int clGetDeviceIDs(CLPlatformId.ByReference platform, int param_name, int num_entries, CLDeviceId.ByReference[] devices,
+ IntByReference device_count);
public int clGetDeviceInfo(CLDeviceId.ByReference device, int param_name, long param_value_size, byte[] destination, long size_ret[]);
@@ -60,8 +66,7 @@ public static class ByReference extends CLPlatformId implements Structure.ByRefe
public int id;
- @Override
- protected List getFieldOrder() {
+ @Override protected List getFieldOrder() {
return Arrays.asList(new String[] { "id" });
}
}
@@ -72,8 +77,7 @@ public static class ByReference extends CLDeviceId implements Structure.ByRefere
public int id;
- @Override
- protected List getFieldOrder() {
+ @Override protected List getFieldOrder() {
return Arrays.asList(new String[] { "id" });
}
}
diff --git a/src/com/sheepit/client/network/Proxy.java b/src/com/sheepit/client/network/Proxy.java
index 4a580dc2..ecf70125 100644
--- a/src/com/sheepit/client/network/Proxy.java
+++ b/src/com/sheepit/client/network/Proxy.java
@@ -2,7 +2,7 @@
* Copyright (C) 2015 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -22,8 +22,6 @@
import java.net.MalformedURLException;
import java.net.URL;
import java.net.Authenticator;
-import com.sheepit.client.network.Proxy;
-import com.sheepit.client.network.ProxyAuthenticator;
public class Proxy {
diff --git a/src/com/sheepit/client/network/ProxyAuthenticator.java b/src/com/sheepit/client/network/ProxyAuthenticator.java
index 196cba40..ca2aca1e 100644
--- a/src/com/sheepit/client/network/ProxyAuthenticator.java
+++ b/src/com/sheepit/client/network/ProxyAuthenticator.java
@@ -2,7 +2,7 @@
* Copyright (C) 2014 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
diff --git a/src/com/sheepit/client/os/FreeBSD.java b/src/com/sheepit/client/os/FreeBSD.java
index 43f0f28b..ade4c3c3 100644
--- a/src/com/sheepit/client/os/FreeBSD.java
+++ b/src/com/sheepit/client/os/FreeBSD.java
@@ -2,7 +2,7 @@
* Copyright (C) 2010-2015 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -24,6 +24,7 @@
import java.util.List;
import java.util.Map;
import java.io.BufferedReader;
+import java.io.InputStream;
import java.io.InputStreamReader;
import com.sheepit.client.Log;
@@ -31,24 +32,21 @@
public class FreeBSD extends OS {
private final String NICE_BINARY_PATH = "nice";
- private Boolean hasNiceBinary;
+ private final String ID_COMMAND_INVOCATION = "id -u";
public FreeBSD() {
super();
- this.hasNiceBinary = null;
}
public String name() {
return "freebsd";
}
- @Override
- public String getRenderBinaryPath() {
+ @Override public String getRenderBinaryPath() {
return "rend.exe";
}
- @Override
- public CPU getCPU() {
+ @Override public CPU getCPU() {
CPU ret = new CPU();
try {
Runtime r = Runtime.getRuntime();
@@ -110,8 +108,7 @@ public CPU getCPU() {
return ret;
}
- @Override
- public int getMemory() {
+ @Override public long getMemory() {
try {
Runtime r = Runtime.getRuntime();
Process p = r.exec("sysctl -n hw.usermem");
@@ -124,7 +121,7 @@ public int getMemory() {
return 0;
}
Long mem_byte = Long.parseLong(line.trim());
- return (int) (mem_byte / Long.valueOf(1024));
+ return mem_byte / Long.valueOf(1024);
}
catch (IOException e) {
Log.getInstance(null).debug("OS::FreeBSD::getMemory exception " + e);
@@ -133,18 +130,15 @@ public int getMemory() {
return 0;
}
- @Override
- public int getFreeMemory() {
+ @Override public long getFreeMemory() {
return -1;
}
- @Override
- public String getCUDALib() {
+ @Override public String getCUDALib() {
return "cuda";
}
- @Override
- public Process exec(List command, Map env_overight) throws IOException {
+ @Override public Process exec(List command, Map env_overight) 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
@@ -161,10 +155,7 @@ public Process exec(List command, Map env_overight) thro
}
List actual_command = command;
- if (this.hasNiceBinary == null) {
- this.checkNiceAvailability();
- }
- if (this.hasNiceBinary.booleanValue()) {
+ if (checkNiceAvailability()) {
// launch the process in lowest priority
if (env_overight != null) {
actual_command.add(0, env_overight.get("PRIORITY"));
@@ -189,17 +180,42 @@ public Process exec(List command, Map env_overight) thro
return builder.start();
}
- private void checkNiceAvailability() {
+ @Override public boolean getSupportHighPriority() {
+ try {
+ ProcessBuilder builder = new ProcessBuilder();
+ builder.command("bash", "-c", ID_COMMAND_INVOCATION);
+ builder.redirectErrorStream(true);
+
+ Process process = builder.start();
+ InputStream is = process.getInputStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+
+ String userLevel = null;
+ if ((userLevel = reader.readLine()) != null) {
+ // Root user in *ix systems -independently of the alias used to login- has a id value of 0. On top of being a user with root capabilities,
+ // to support changing the priority the nice tool must be accessible from the current user
+ return (userLevel.equals("0")) & checkNiceAvailability();
+ }
+ }
+ catch (IOException e) {
+ System.err.println(String.format("ERROR FreeBSD::getSupportHighPriority Unable to execute id command. IOException %s", e.getMessage()));
+ }
+
+ return false;
+ }
+
+ @Override public boolean checkNiceAvailability() {
ProcessBuilder builder = new ProcessBuilder();
builder.command(NICE_BINARY_PATH);
builder.redirectErrorStream(true);
+
Process process = null;
+ boolean hasNiceBinary = false;
try {
process = builder.start();
- this.hasNiceBinary = true;
+ hasNiceBinary = true;
}
catch (IOException e) {
- this.hasNiceBinary = false;
Log.getInstance(null).error("Failed to find low priority binary, will not launch renderer in normal priority (" + e + ")");
}
finally {
@@ -207,5 +223,6 @@ private void checkNiceAvailability() {
process.destroy();
}
}
+ return hasNiceBinary;
}
}
diff --git a/src/com/sheepit/client/os/Linux.java b/src/com/sheepit/client/os/Linux.java
index e9f8a5a6..6aacfb1a 100644
--- a/src/com/sheepit/client/os/Linux.java
+++ b/src/com/sheepit/client/os/Linux.java
@@ -2,7 +2,7 @@
* Copyright (C) 2010-2014 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -18,7 +18,10 @@
*/
package com.sheepit.client.os;
+import java.io.BufferedReader;
import java.io.File;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
@@ -30,24 +33,21 @@
public class Linux extends OS {
private final String NICE_BINARY_PATH = "nice";
- private Boolean hasNiceBinary;
+ private final String ID_COMMAND_INVOCATION = "id -u";
public Linux() {
super();
- this.hasNiceBinary = null;
}
public String name() {
return "linux";
}
- @Override
- public String getRenderBinaryPath() {
+ @Override public String getRenderBinaryPath() {
return "rend.exe";
}
- @Override
- public CPU getCPU() {
+ @Override public CPU getCPU() {
CPU ret = new CPU();
try {
String filePath = "/proc/cpuinfo";
@@ -87,8 +87,7 @@ public CPU getCPU() {
return ret;
}
- @Override
- public int getMemory() {
+ @Override public long getMemory() {
try {
String filePath = "/proc/meminfo";
Scanner scanner = new Scanner(new File(filePath));
@@ -116,8 +115,7 @@ public int getMemory() {
return 0;
}
- @Override
- public int getFreeMemory() {
+ @Override public long getFreeMemory() {
try {
String filePath = "/proc/meminfo";
Scanner scanner = new Scanner(new File(filePath));
@@ -136,7 +134,8 @@ public int getFreeMemory() {
scanner.close();
}
catch (java.lang.NoClassDefFoundError e) {
- System.err.println("OS::Linux::getFreeMemory error " + e + " mostly because Scanner class was introducted by Java 5 and you are running a lower version");
+ System.err.println(
+ "OS::Linux::getFreeMemory error " + e + " mostly because Scanner class was introducted by Java 5 and you are running a lower version");
}
catch (Exception e) {
e.printStackTrace();
@@ -145,33 +144,31 @@ public int getFreeMemory() {
return 0;
}
- @Override
- public String getCUDALib() {
+ @Override public String getCUDALib() {
return "cuda";
}
- @Override
- public Process exec(List command, Map env_overight) 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
-
+ @Override public Process exec(List command, Map env_overight) throws IOException {
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.get(0))).getParent() + File.separator + "lib";
- if (has_ld_library_path == false) {
- new_env.put("LD_LIBRARY_PATH", lib_dir);
- }
- else {
- new_env.put("LD_LIBRARY_PATH", new_env.get("LD_LIBRARY_PATH") + ":" + lib_dir);
+ // if Blender is already loading an OpenGL library, don't need to load Blender's default one (it will
+ // create system incompatibilities). If no OpenGL library is found, then load the one included in the binary
+ // zip file
+ if (isOpenGLAlreadyInstalled(command.get(0)) == false) {
+ Boolean has_ld_library_path = new_env.containsKey("LD_LIBRARY_PATH");
+
+ String lib_dir = (new File(command.get(0))).getParent() + File.separator + "lib";
+ if (has_ld_library_path == false) {
+ new_env.put("LD_LIBRARY_PATH", lib_dir);
+ }
+ else {
+ new_env.put("LD_LIBRARY_PATH", new_env.get("LD_LIBRARY_PATH") + ":" + lib_dir);
+ }
}
List actual_command = command;
- if (this.hasNiceBinary == null) {
- this.checkNiceAvailability();
- }
- if (this.hasNiceBinary.booleanValue()) {
+ if (checkNiceAvailability()) {
// launch the process in lowest priority
if (env_overight != null) {
actual_command.add(0, env_overight.get("PRIORITY"));
@@ -196,30 +193,42 @@ public Process exec(List command, Map env_overight) thro
return builder.start();
}
- @Override
- public boolean getSupportHighPriority() {
- // only the root user can create process with high (negative nice) value
- String logname = System.getenv("LOGNAME");
- String user = System.getenv("USER");
-
- if ((logname != null && logname.equals("root")) || (user != null && user.equals("root"))) {
- return true;
+ @Override public boolean getSupportHighPriority() {
+ try {
+ ProcessBuilder builder = new ProcessBuilder();
+ builder.command("bash", "-c", ID_COMMAND_INVOCATION);
+ builder.redirectErrorStream(true);
+
+ Process process = builder.start();
+ InputStream is = process.getInputStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+
+ String userLevel = null;
+ if ((userLevel = reader.readLine()) != null) {
+ // Root user in *ix systems -independently of the alias used to login- has a id value of 0. On top of being a user with root capabilities,
+ // to support changing the priority the nice tool must be accessible from the current user
+ return (userLevel.equals("0")) & checkNiceAvailability();
+ }
+ }
+ catch (IOException e) {
+ System.err.println(String.format("ERROR Linux::getSupportHighPriority Unable to execute id command. IOException %s", e.getMessage()));
}
return false;
}
- protected void checkNiceAvailability() {
+ @Override public boolean checkNiceAvailability() {
ProcessBuilder builder = new ProcessBuilder();
builder.command(NICE_BINARY_PATH);
builder.redirectErrorStream(true);
+
Process process = null;
+ boolean hasNiceBinary = false;
try {
process = builder.start();
- this.hasNiceBinary = true;
+ hasNiceBinary = true;
}
catch (IOException e) {
- this.hasNiceBinary = false;
Log.getInstance(null).error("Failed to find low priority binary, will not launch renderer in normal priority (" + e + ")");
}
finally {
@@ -227,5 +236,47 @@ protected void checkNiceAvailability() {
process.destroy();
}
}
+ return hasNiceBinary;
+ }
+
+ protected boolean isOpenGLAlreadyInstalled(String pathToRendEXE) {
+ ProcessBuilder processBuilder = new ProcessBuilder();
+ processBuilder.command("bash", "-c", "ldd '" + pathToRendEXE + "'"); // support for paths with an space
+ processBuilder.redirectErrorStream(true);
+
+ try {
+ Process process = processBuilder.start();
+
+ BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+
+ String line;
+ StringBuilder screenOutput = new StringBuilder();
+ while ((line = reader.readLine()) != null) {
+ // check the shared libraries that Blender is loading at run time. If it already loads an existing
+ // version of OpenGL (ie the one shipped with NVIDIA drivers) then return false to avoid the client
+ // replacing them (and glitching the EEVEE render). Otherwise return true and load the /lib folder
+ // to ensure that Blender works correctly
+ if (line.toLowerCase().contains("libgl.so")) {
+ return !line.toLowerCase().contains("not found");
+ }
+
+ // In case of error we can later check the screen output from ldd
+ screenOutput.append(line);
+ }
+
+ int exitCode = process.waitFor();
+ if (exitCode != 0) {
+ System.err.println(String.format("ERROR Linux::isOpenGLAlreadyInstalled Unable to execute ldd command. Exit code %d", exitCode));
+ System.err.println(String.format("Screen output from ldd execution: %s", screenOutput.toString()));
+ }
+ }
+ catch (IOException e) {
+ System.err.println(String.format("ERROR Linux::isOpenGLAreadyInstalled Unable to execute ldd command. IOException %s", e.getMessage()));
+ }
+ catch (InterruptedException e) {
+ System.err.println(String.format("ERROR Linux::isOpenGLAreadyInstalled Unable to execute ldd command. InterruptedException %s", e.getMessage()));
+ }
+
+ return false;
}
}
diff --git a/src/com/sheepit/client/os/Mac.java b/src/com/sheepit/client/os/Mac.java
index 0d1bd340..fdcdd41a 100644
--- a/src/com/sheepit/client/os/Mac.java
+++ b/src/com/sheepit/client/os/Mac.java
@@ -2,7 +2,7 @@
* Copyright (C) 2010-2014 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -21,6 +21,7 @@
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
import java.util.Map;
@@ -30,24 +31,21 @@
public class Mac extends OS {
private final String NICE_BINARY_PATH = "nice";
- private Boolean hasNiceBinary;
+ private final String ID_COMMAND_INVOCATION = "id -u";
public Mac() {
super();
- this.hasNiceBinary = null;
}
public String name() {
return "mac";
}
- @Override
- public String getRenderBinaryPath() {
+ @Override public String getRenderBinaryPath() {
return "Blender" + File.separator + "blender.app" + File.separator + "Contents" + File.separator + "MacOS" + File.separator + "blender";
}
- @Override
- public CPU getCPU() {
+ @Override public CPU getCPU() {
CPU ret = new CPU();
String command = "sysctl machdep.cpu.family machdep.cpu.brand_string";
@@ -97,8 +95,7 @@ public CPU getCPU() {
return ret;
}
- @Override
- public int getMemory() {
+ @Override public long getMemory() {
String command = "sysctl hw.memsize";
Process p = null;
@@ -113,7 +110,7 @@ public int getMemory() {
if (line.startsWith(option)) {
String memory = line.substring(option.length()).trim(); // memory in bytes
- return (int) (Long.parseLong(memory) / 1024);
+ return Long.parseLong(memory) / 1024;
}
}
input.close();
@@ -140,18 +137,13 @@ public int getMemory() {
return -1;
}
- @Override
- public int getFreeMemory() {
+ @Override public long getFreeMemory() {
return -1;
}
- @Override
- public Process exec(List command, Map env) throws IOException {
+ @Override public Process exec(List command, Map env) throws IOException {
List actual_command = command;
- if (this.hasNiceBinary == null) {
- this.checkNiceAvailability();
- }
- if (this.hasNiceBinary.booleanValue()) {
+ if (checkNiceAvailability()) {
// launch the process in lowest priority
if (env != null) {
actual_command.add(0, env.get("PRIORITY"));
@@ -173,22 +165,46 @@ public Process exec(List command, Map env) throws IOExce
return builder.start();
}
- @Override
- public String getCUDALib() {
+ @Override public String getCUDALib() {
return "/usr/local/cuda/lib/libcuda.dylib";
}
- protected void checkNiceAvailability() {
+ @Override public boolean getSupportHighPriority() {
+ try {
+ ProcessBuilder builder = new ProcessBuilder();
+ builder.command("bash", "-c", ID_COMMAND_INVOCATION);
+ builder.redirectErrorStream(true);
+
+ Process process = builder.start();
+ InputStream is = process.getInputStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+
+ String userLevel = null;
+ if ((userLevel = reader.readLine()) != null) {
+ // Root user in *ix systems -independently of the alias used to login- has a id value of 0. On top of being a user with root capabilities,
+ // to support changing the priority the nice tool must be accessible from the current user
+ return (userLevel.equals("0")) & checkNiceAvailability();
+ }
+ }
+ catch (IOException e) {
+ System.err.println(String.format("ERROR Mac::getSupportHighPriority Unable to execute id command. IOException %s", e.getMessage()));
+ }
+
+ return false;
+ }
+
+ @Override public boolean checkNiceAvailability() {
ProcessBuilder builder = new ProcessBuilder();
builder.command(NICE_BINARY_PATH);
builder.redirectErrorStream(true);
+
Process process = null;
+ boolean hasNiceBinary = false;
try {
process = builder.start();
- this.hasNiceBinary = true;
+ hasNiceBinary = true;
}
catch (IOException e) {
- this.hasNiceBinary = false;
Log.getInstance(null).error("Failed to find low priority binary, will not launch renderer in normal priority (" + e + ")");
}
finally {
@@ -196,5 +212,6 @@ protected void checkNiceAvailability() {
process.destroy();
}
}
+ return hasNiceBinary;
}
}
diff --git a/src/com/sheepit/client/os/OS.java b/src/com/sheepit/client/os/OS.java
index 5430398d..04a337f2 100644
--- a/src/com/sheepit/client/os/OS.java
+++ b/src/com/sheepit/client/os/OS.java
@@ -2,7 +2,7 @@
* Copyright (C) 2010-2014 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -31,9 +31,9 @@ public abstract class OS {
public abstract CPU getCPU();
- public abstract int getMemory();
+ public abstract long getMemory();
- public abstract int getFreeMemory();
+ public abstract long getFreeMemory();
public abstract String getRenderBinaryPath();
@@ -41,9 +41,9 @@ public String getCUDALib() {
return null;
}
- public boolean getSupportHighPriority() {
- return true;
- }
+ public abstract boolean getSupportHighPriority();
+
+ public abstract boolean checkNiceAvailability();
public Process exec(List command, Map env) throws IOException {
ProcessBuilder builder = new ProcessBuilder(command);
diff --git a/src/com/sheepit/client/os/Windows.java b/src/com/sheepit/client/os/Windows.java
index 905f0383..bc564ef8 100644
--- a/src/com/sheepit/client/os/Windows.java
+++ b/src/com/sheepit/client/os/Windows.java
@@ -2,7 +2,7 @@
* Copyright (C) 2010-2014 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -37,13 +37,11 @@ public String name() {
return "windows";
}
- @Override
- public String getRenderBinaryPath() {
+ @Override public String getRenderBinaryPath() {
return "rend.exe";
}
- @Override
- public CPU getCPU() {
+ @Override public CPU getCPU() {
CPU ret = new CPU();
try {
String[] identifier = java.lang.System.getenv("PROCESSOR_IDENTIFIER").split(" ");
@@ -88,12 +86,11 @@ public CPU getCPU() {
return ret;
}
- @Override
- public int getMemory() {
+ @Override public long getMemory() {
try {
MEMORYSTATUSEX _memory = new MEMORYSTATUSEX();
if (Kernel32.INSTANCE.GlobalMemoryStatusEx(_memory)) {
- return (int) (_memory.ullTotalPhys.longValue() / 1024); // size in KB
+ return _memory.ullTotalPhys.longValue() / 1024; // size in KB
}
}
catch (Exception e) {
@@ -102,32 +99,29 @@ public int getMemory() {
return 0;
}
- @Override
- public int getFreeMemory() {
+ @Override public long getFreeMemory() {
try {
MEMORYSTATUSEX _memory = new MEMORYSTATUSEX();
if (Kernel32.INSTANCE.GlobalMemoryStatusEx(_memory)) {
- return (int) (_memory.ullAvailPhys.longValue() / 1024); // size in KB
+ return _memory.ullAvailPhys.longValue() / 1024; // size in KB
}
}
catch (Exception e) {
e.printStackTrace();
- }
-
+ }
+
return -1;
}
- @Override
- public String getCUDALib() {
+ @Override public String getCUDALib() {
return "nvcuda";
}
- @Override
- public Process exec(List command, Map env) throws IOException {
+ @Override public Process exec(List command, Map env) 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 = (Kernel32Lib) Native.load(Kernel32Lib.path, Kernel32Lib.class);
kernel32lib.SetErrorMode(Kernel32Lib.SEM_NOGPFAULTERRORBOX);
}
catch (java.lang.UnsatisfiedLinkError e) {
@@ -216,8 +210,7 @@ int getPriorityClass(int priority) {
return process_class;
}
- @Override
- public boolean kill(Process process) {
+ @Override public boolean kill(Process process) {
if (process != null) {
WinProcess wproc = new WinProcess(process);
wproc.kill();
@@ -225,4 +218,13 @@ public boolean kill(Process process) {
}
return false;
}
+
+ @Override public boolean getSupportHighPriority() {
+ return true;
+ }
+
+ @Override public boolean checkNiceAvailability() {
+ // In windows, nice is not required and therefore we return always true to show the slider in the Settings GUI
+ return true;
+ }
}
diff --git a/src/com/sheepit/client/os/windows/Kernel32Lib.java b/src/com/sheepit/client/os/windows/Kernel32Lib.java
index 51a30d4a..78805c9b 100644
--- a/src/com/sheepit/client/os/windows/Kernel32Lib.java
+++ b/src/com/sheepit/client/os/windows/Kernel32Lib.java
@@ -1,6 +1,6 @@
/* 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
@@ -61,7 +61,8 @@ public interface Kernel32Lib extends Library {
/**
* 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()));
+ 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.
@@ -151,9 +152,10 @@ public PROCESSENTRY32(Pointer memory) {
*/
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" });
+ @Override protected List getFieldOrder() {
+ return Arrays
+ .asList("dwSize", "cntUsage", "th32ProcessID", "th32DefaultHeapID", "th32ModuleID", "cntThreads", "th32ParentProcessID", "pcPriClassBase",
+ "dwFlags", "szExeFile");
}
}
diff --git a/src/com/sheepit/client/os/windows/WinProcess.java b/src/com/sheepit/client/os/windows/WinProcess.java
index 78ed2a8e..36c61230 100644
--- a/src/com/sheepit/client/os/windows/WinProcess.java
+++ b/src/com/sheepit/client/os/windows/WinProcess.java
@@ -2,7 +2,7 @@
* Copyright (C) 2013 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -49,7 +49,7 @@ public WinProcess() {
this.pid = -1;
this.kernel32lib = null;
try {
- this.kernel32lib = (Kernel32Lib) Native.loadLibrary(Kernel32Lib.path, Kernel32Lib.class);
+ this.kernel32lib = (Kernel32Lib) Native.load(Kernel32Lib.path, Kernel32Lib.class);
}
catch (java.lang.UnsatisfiedLinkError e) {
System.out.println("WinProcess::construct " + e);
@@ -64,7 +64,7 @@ public WinProcess() {
private static boolean processHasGetPid() {
try {
- if (Process.class.getMethod("pid", null) != null) {
+ if (Process.class.getMethod("pid") != null) {
return true;
}
}
@@ -75,7 +75,7 @@ private static boolean processHasGetPid() {
private static long getPid(Process process) {
try {
- return (long) Process.class.getMethod("pid", null).invoke(process, null);
+ return (long) Process.class.getMethod("pid").invoke(process);
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
}
@@ -84,13 +84,14 @@ private static long getPid(Process process) {
private static WinNT.HANDLE getHandleByPid(int pid_) throws IOException {
WinNT.HANDLE handle = Kernel32.INSTANCE.OpenProcess(0x0400 | // PROCESS_QUERY_INFORMATION
- 0x0800 | // PROCESS_SUSPEND_RESUME
- 0x0001 | // PROCESS_TERMINATE
- 0x0200 | // PROCESS_SET_INFORMATION
- 0x00100000, // SYNCHRONIZE
+ 0x0800 | // PROCESS_SUSPEND_RESUME
+ 0x0001 | // PROCESS_TERMINATE
+ 0x0200 | // PROCESS_SET_INFORMATION
+ 0x00100000, // SYNCHRONIZE
false, pid_);
if (handle == null) {
- throw new IOException("OpenProcess failed: " + Kernel32Util.formatMessageFromLastErrorCode(Kernel32.INSTANCE.GetLastError()) + " (pid: " + pid_ + ")");
+ throw new IOException(
+ "OpenProcess failed: " + Kernel32Util.formatMessageFromLastErrorCode(Kernel32.INSTANCE.GetLastError()) + " (pid: " + pid_ + ")");
}
return handle;
}
@@ -130,8 +131,7 @@ public WinProcess(int pid_) throws IOException {
this.pid = pid_;
}
- @Override
- protected void finalize() throws Throwable {
+ @Override protected void finalize() throws Throwable {
if (this.handle != null) {
// Kernel32.INSTANCE.CloseHandle(this.handle); // do not close the handle because the parent Process object might still be alive
this.handle = null;
diff --git a/src/com/sheepit/client/standalone/GuiSwing.java b/src/com/sheepit/client/standalone/GuiSwing.java
index 6fdaaaf8..b4e15ce3 100644
--- a/src/com/sheepit/client/standalone/GuiSwing.java
+++ b/src/com/sheepit/client/standalone/GuiSwing.java
@@ -2,7 +2,7 @@
* Copyright (C) 2010-2014 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -25,29 +25,39 @@
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
-import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowStateListener;
+import java.awt.image.BufferedImage;
import java.net.URL;
import java.util.Timer;
import java.util.TimerTask;
+import java.io.IOException;
+
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.EmptyBorder;
+import javax.imageio.ImageIO;
import com.sheepit.client.Client;
import com.sheepit.client.Configuration;
import com.sheepit.client.Gui;
+import com.sheepit.client.SettingsLoader;
import com.sheepit.client.Stats;
import com.sheepit.client.standalone.swing.activity.Settings;
import com.sheepit.client.standalone.swing.activity.Working;
+import lombok.Getter;
+import lombok.Setter;
+
+import com.formdev.flatlaf.FlatLightLaf; // Required for dark & light mode
+import com.formdev.flatlaf.FlatDarkLaf;
+import com.formdev.flatlaf.FlatLaf;
public class GuiSwing extends JFrame implements Gui {
public static final String type = "swing";
@@ -69,6 +79,11 @@ public enum ActivityType {
private boolean waitingForAuthentication;
private Client client;
+ private BufferedImage iconSprites;
+ private BufferedImage[] trayIconSprites;
+
+ @Getter @Setter private SettingsLoader settingsLoader;
+
private ThreadClient threadClient;
public GuiSwing(boolean useSysTray_, String title_) {
@@ -78,8 +93,7 @@ public GuiSwing(boolean useSysTray_, String title_) {
waitingForAuthentication = true;
new Timer().scheduleAtFixedRate(new TimerTask() {
- @Override
- public void run() {
+ @Override public void run() {
if (activityWorking != null) {
activityWorking.updateTime();
}
@@ -87,15 +101,7 @@ public void run() {
}, 2 * 1000, 2 * 1000);
}
- @Override
- public void start() {
- try {
- UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
- }
- catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e1) {
- e1.printStackTrace();
- }
-
+ @Override public void start() {
if (useSysTray) {
try {
sysTray = SystemTray.getSystemTray();
@@ -114,14 +120,23 @@ public void windowStateChanged(WindowEvent e) {
}
}
- URL iconUrl = getClass().getResource("/icon.png");
- if (iconUrl != null) {
- ImageIcon img = new ImageIcon(iconUrl);
- setIconImage(img.getImage());
+ // load the images sprite and split into individual images
+ URL spriteSequenceUrl = getClass().getResource("/icon-sprites.png");
+
+ if (spriteSequenceUrl != null) {
+ try {
+ iconSprites = ImageIO.read(spriteSequenceUrl);
+ trayIconSprites = new BufferedImage[101 * 1]; // sprite sheet has 101 images in 1 column
+
+ setIconImage(extractImageFromSprite(-1)); // sprite 0 is standard Sheep It! icon
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
}
setTitle(title);
- setSize(520, 680);
+ setSize(520, 760);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
@@ -135,6 +150,21 @@ public void windowStateChanged(WindowEvent e) {
this.showActivity(ActivityType.SETTINGS);
+ try {
+ if (client.getConfiguration().getTheme().equals("light")) {
+ UIManager.setLookAndFeel(new FlatLightLaf());
+ }
+ else if (client.getConfiguration().getTheme().equals("dark")) {
+ UIManager.setLookAndFeel(new FlatDarkLaf());
+ }
+
+ // Apply the selected theme to swing components
+ FlatLaf.updateUI();
+ }
+ catch (UnsupportedLookAndFeelException e1) {
+ e1.printStackTrace();
+ }
+
while (waitingForAuthentication) {
try {
synchronized (this) {
@@ -147,46 +177,53 @@ public void windowStateChanged(WindowEvent e) {
}
}
- @Override
- public void stop() {
+ @Override public void stop() {
System.exit(0);
}
- @Override
- public void status(String msg_) {
+ @Override public void status(String msg_) {
+ status(msg_, false);
+ }
+
+ @Override public void status(String msg_, boolean overwriteSuspendedMsg) {
if (activityWorking != null) {
- this.activityWorking.setStatus(msg_);
+ this.activityWorking.setStatus(msg_, overwriteSuspendedMsg);
}
}
- @Override
- public void setRenderingProjectName(String name_) {
+ @Override public void status(String msg, int progress) {
+ if (activityWorking != null) {
+ this.activityWorking.setStatus(String.format("%s %d%%", msg, progress));
+ }
+ }
+
+ @Override public void status(String msg, int progress, long size) {
+ this.status(msg, progress);
+ }
+
+ @Override public void setRenderingProjectName(String name_) {
if (activityWorking != null) {
this.activityWorking.setRenderingProjectName(name_);
}
}
- @Override
- public void error(String msg_) {
- status(msg_);
+ @Override public void error(String msg_) {
+ status(msg_, true);
}
- @Override
- public void setRemainingTime(String time_) {
+ @Override public void setRemainingTime(String time_) {
if (activityWorking != null) {
this.activityWorking.setRemainingTime(time_);
}
}
- @Override
- public void setRenderingTime(String time_) {
+ @Override public void setRenderingTime(String time_) {
if (activityWorking != null) {
this.activityWorking.setRenderingTime(time_);
}
}
- @Override
- public void AddFrameRendered() {
+ @Override public void AddFrameRendered() {
framesRendered++;
if (activityWorking != null) {
@@ -197,25 +234,27 @@ public void AddFrameRendered() {
}
}
- @Override
- public void displayStats(Stats stats) {
+ @Override public void displayStats(Stats stats) {
if (activityWorking != null) {
this.activityWorking.displayStats(stats);
}
}
- @Override
- public Client getClient() {
+ @Override public void displayUploadQueueStats(int queueSize, long queueVolume) {
+ if (activityWorking != null) {
+ this.activityWorking.displayUploadQueueStats(queueSize, queueVolume);
+ }
+ }
+
+ @Override public Client getClient() {
return client;
}
- @Override
- public void setClient(Client cli) {
+ @Override public void setClient(Client cli) {
client = cli;
}
- @Override
- public void setComputeMethod(String computeMethod) {
+ @Override public void setComputeMethod(String computeMethod) {
this.activityWorking.setComputeMethod(computeMethod);
}
@@ -223,6 +262,15 @@ public Configuration getConfiguration() {
return client.getConfiguration();
}
+ @Override public void successfulAuthenticationEvent(String publickey) {
+ if (settingsLoader != null) {
+ if (publickey != null) {
+ settingsLoader.setPassword(publickey);
+ }
+ settingsLoader.saveFile();
+ }
+ }
+
public void setCredentials(String contentLogin, String contentPassword) {
client.getConfiguration().setLogin(contentLogin);
client.getConfiguration().setPassword(contentPassword);
@@ -287,14 +335,13 @@ public void restoreFromTray() {
public TrayIcon getTrayIcon() {
final PopupMenu trayMenu = new PopupMenu();
- URL iconUrl = getClass().getResource("/icon.png");
- Image img = Toolkit.getDefaultToolkit().getImage(iconUrl);
+ // on start, show the base icon
+ Image img = extractImageFromSprite(-1);
final TrayIcon icon = new TrayIcon(img);
MenuItem exit = new MenuItem("Exit");
exit.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
+ @Override public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
@@ -302,8 +349,7 @@ public void actionPerformed(ActionEvent e) {
MenuItem open = new MenuItem("Open...");
open.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
+ @Override public void actionPerformed(ActionEvent e) {
restoreFromTray();
}
});
@@ -311,8 +357,7 @@ public void actionPerformed(ActionEvent e) {
MenuItem settings = new MenuItem("Settings...");
settings.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
+ @Override public void actionPerformed(ActionEvent e) {
restoreFromTray();
showActivity(ActivityType.SETTINGS);
}
@@ -324,8 +369,7 @@ public void actionPerformed(ActionEvent e) {
icon.setToolTip("SheepIt! Client");
icon.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
+ @Override public void actionPerformed(ActionEvent e) {
restoreFromTray();
}
});
@@ -334,9 +378,36 @@ public void actionPerformed(ActionEvent e) {
}
+ private Image extractImageFromSprite(int spriteNumber) {
+ // Sprite structure
+ // Image 0: base sprite
+ // Images 1-101: progress bar percentage from 0 to 100
+ //
+ // Always add +1 to the icon requested.
+ // -1 turns into 0 (base sprite with no progress bar)
+ // 0 to 101 turns into 1 to 101 (progress sequence starts in sprite 1 and ends on sprite 101)
+ ImageIcon img = new ImageIcon(iconSprites.getSubimage(0, (spriteNumber + 1) * 114, 114, 114));
+
+ return img.getImage();
+ }
+
+ @Override public void updateTrayIcon(Integer percentage) {
+ // update the app icon on the app bar
+ Image img = extractImageFromSprite(percentage);
+ setIconImage(img);
+
+ // if the app supports the system tray, update as well
+ if (sysTray != null && SystemTray.isSupported()) {
+ if (trayIcon != null) {
+ trayIcon.setImage(img);
+ trayIcon.setImageAutoSize(true); // use this method to ensure that icon is refreshed when on
+ // the tray
+ }
+ }
+ }
+
public class ThreadClient extends Thread {
- @Override
- public void run() {
+ @Override public void run() {
if (GuiSwing.this.client != null) {
GuiSwing.this.client.run();
}
diff --git a/src/com/sheepit/client/standalone/GuiText.java b/src/com/sheepit/client/standalone/GuiText.java
index ad116e17..d65a8c8c 100644
--- a/src/com/sheepit/client/standalone/GuiText.java
+++ b/src/com/sheepit/client/standalone/GuiText.java
@@ -2,7 +2,7 @@
* Copyright (C) 2010-2014 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -21,7 +21,6 @@
import com.sheepit.client.Client;
import com.sheepit.client.Gui;
-import com.sheepit.client.Job;
import com.sheepit.client.Log;
import com.sheepit.client.Stats;
import com.sheepit.client.standalone.text.CLIInputActionHandler;
@@ -30,24 +29,31 @@
import sun.misc.Signal;
import sun.misc.SignalHandler;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Date;
+
public class GuiText implements Gui {
public static final String type = "text";
private int framesRendered;
private int sigIntCount = 0;
-
private Log log;
+ private DateFormat df;
+ private String eta;
private Client client;
public GuiText() {
this.framesRendered = 0;
this.log = Log.getInstance(null);
+ this.df = new SimpleDateFormat("MMM dd HH:mm:ss");
+ this.eta = "";
}
- @Override
- public void start() {
+ @Override public void start() {
if (client != null) {
CLIInputObserver cli_input_observer = new CLIInputObserver(client);
@@ -56,8 +62,7 @@ public void start() {
cli_input_observer_thread.start();
Signal.handle(new Signal("INT"), new SignalHandler() {
- @Override
- public void handle(Signal signal) {
+ @Override public void handle(Signal signal) {
sigIntCount++;
if (sigIntCount == 4) {
@@ -84,65 +89,123 @@ else if (client.isRunning() && client.isSuspended() == false) {
}
}
- @Override
- public void stop() {
+ @Override public void stop() {
Runtime.getRuntime().halt(0);
}
- @Override
- public void status(String msg_) {
- System.out.println(msg_);
+ @Override public void updateTrayIcon(Integer percentage) {
+ }
+
+ @Override public void status(String msg_) {
+ status(msg_, false);
+ }
+
+ @Override public void status(String msg_, boolean overwriteSuspendedMsg) {
log.debug("GUI " + msg_);
+
+ if (client != null && client.isSuspended()) {
+ if (overwriteSuspendedMsg) {
+ System.out.println(String.format("%s %s", this.df.format(new Date()), msg_));
+ }
+ }
+ else {
+ System.out.println(String.format("%s %s", this.df.format(new Date()), msg_));
+ }
+ }
+
+ @Override public void status(String msg, int progress) {
+ this.status(msg, progress, 0);
+ }
+
+ @Override public void status(String msg, int progress, long size) {
+ System.out.print("\r");
+ System.out.print(String.format("%s %s", this.df.format(new Date()), showProgress(msg, progress, size)));
}
- @Override
- public void error(String err_) {
- System.out.println("Error " + err_);
+ @Override public void error(String err_) {
+ System.out.println(String.format("ERROR: %s %s", this.df.format(new Date()), err_));
log.error("Error " + err_);
}
- @Override
- public void AddFrameRendered() {
+ @Override public void AddFrameRendered() {
this.framesRendered += 1;
- System.out.println("Frames rendered: " + this.framesRendered);
+ System.out.println(String.format("%s Frames rendered: %d", this.df.format(new Date()), this.framesRendered));
}
- @Override
- public void displayStats(Stats stats) {
- System.out.println("Frames remaining: " + stats.getRemainingFrame());
- System.out.println("Credits earned: " + stats.getCreditsEarnedDuringSession());
+ @Override public void displayStats(Stats stats) {
+ System.out.println(String.format("%s Frames remaining: %d", this.df.format(new Date()), stats.getRemainingFrame()));
+ System.out.println(String.format("%s Credits earned: %d", this.df.format(new Date()), stats.getCreditsEarnedDuringSession()));
}
- @Override
- public void setRenderingProjectName(String name_) {
+ @Override public void displayUploadQueueStats(int queueSize, long queueVolume) {
+ // No need to check if the queue is not empty to show the volume bc this line is always shown at the end
+ // of the render process in text GUI (unless an error occurred, where the file is uploaded synchronously)
+ System.out.println(String.format("%s Queued uploads: %d (%.2fMB)", this.df.format(new Date()), queueSize, (queueVolume / 1024.0 / 1024.0)));
+ }
+
+ @Override public void setRenderingProjectName(String name_) {
if (name_ != null && name_.isEmpty() == false) {
- System.out.println("Rendering project \"" + name_ + "\"");
+ System.out.println(String.format("%s Rendering project \"%s\"", this.df.format(new Date()), name_));
}
}
- @Override
- public void setRemainingTime(String time_) {
- System.out.println("Rendering (remaining " + time_ + ")");
+ @Override public void setRemainingTime(String time_) {
+ this.eta = time_;
}
- @Override
- public void setRenderingTime(String time_) {
- System.out.println("Rendering " + time_);
+ @Override public void setRenderingTime(String time_) {
+ System.out.println(String.format("%s Rendering %s", this.df.format(new Date()), time_));
}
- @Override
- public void setClient(Client cli) {
+ @Override public void setClient(Client cli) {
client = cli;
}
- @Override
- public void setComputeMethod(String computeMethod) {
- System.out.println("Compute method: " + computeMethod);
+ @Override public void setComputeMethod(String computeMethod) {
+ System.out.println(String.format("%s Compute method: %s", this.df.format(new Date()), computeMethod));
}
- @Override
- public Client getClient() {
+ @Override public Client getClient() {
return client;
}
+ @Override public void successfulAuthenticationEvent(String publickey) {
+
+ }
+
+ private String showProgress(String message, int progress, long size) {
+ StringBuilder progressBar = new StringBuilder(140);
+
+ if (progress < 100) {
+ progressBar
+ .append(message)
+ .append(" ")
+ .append(String.join("", Collections.nCopies(progress == 0 ? 2 : 2 - (int) (Math.log10(progress)), " ")))
+ .append(String.format("%d%% [", progress))
+ .append(String.join("", Collections.nCopies((int)(progress/5), "=")))
+ .append('>')
+ .append(String.join("", Collections.nCopies(20 - (int)(progress / 5), " ")))
+ .append(']');
+
+ if (size > 0) {
+ progressBar.append(String.format(" %dMB", (size / 1024 / 1024)));
+ }
+
+ if (!this.eta.equals("")) {
+ progressBar.append(String.format(" ETA %s", this.eta));
+ }
+
+ progressBar.append(String.join("", Collections.nCopies(60 - progressBar.length(), " ")));
+ }
+ // If progress has reached 100%
+ else {
+ progressBar
+ .append(message)
+ .append(" done")
+ .append(String.join("", Collections.nCopies(60 - progressBar.length(), " ")))
+ .append("\n");
+ }
+
+ return progressBar.toString();
+ }
}
diff --git a/src/com/sheepit/client/standalone/GuiTextOneLine.java b/src/com/sheepit/client/standalone/GuiTextOneLine.java
index 044d5d82..0c6e8d2d 100644
--- a/src/com/sheepit/client/standalone/GuiTextOneLine.java
+++ b/src/com/sheepit/client/standalone/GuiTextOneLine.java
@@ -2,7 +2,7 @@
* Copyright (C) 2015 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -21,7 +21,6 @@
import com.sheepit.client.Client;
import com.sheepit.client.Gui;
-import com.sheepit.client.Job;
import com.sheepit.client.Stats;
import com.sheepit.client.standalone.text.CLIInputActionHandler;
import com.sheepit.client.standalone.text.CLIInputObserver;
@@ -29,6 +28,11 @@
import sun.misc.Signal;
import sun.misc.SignalHandler;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Date;
+
public class GuiTextOneLine implements Gui {
public static final String type = "oneLine";
@@ -37,10 +41,15 @@ public class GuiTextOneLine implements Gui {
private int remaining;
private String creditsEarned;
private int sigIntCount = 0;
+ private DateFormat df;
private String computeMethod;
private String status;
private String line;
+ private String eta;
+
+ private int uploadQueueSize;
+ private long uploadQueueVolume;
private boolean exiting = false;
@@ -54,10 +63,13 @@ public GuiTextOneLine() {
status = "";
computeMethod = "";
line = "";
+ uploadQueueSize = 0;
+ uploadQueueVolume = 0;
+ df = new SimpleDateFormat("MMM dd HH:mm:ss");
+ eta = "";
}
- @Override
- public void start() {
+ @Override public void start() {
if (client != null) {
CLIInputObserver cli_input_observer = new CLIInputObserver(client);
@@ -66,8 +78,7 @@ public void start() {
cli_input_observer_thread.start();
Signal.handle(new Signal("INT"), new SignalHandler() {
- @Override
- public void handle(Signal signal) {
+ @Override public void handle(Signal signal) {
sigIntCount++;
if (sigIntCount == 5) {
@@ -90,82 +101,141 @@ else if (client.isRunning() && client.isSuspended() == false) {
}
}
- @Override
- public void stop() {
+ @Override public void stop() {
Runtime.getRuntime().halt(0);
}
- @Override
- public void status(String msg_) {
- status = msg_;
+ @Override public void updateTrayIcon(Integer percentage) {
+ }
+
+ @Override public void status(String msg_) {
+ status(msg_, false);
+ }
+
+ @Override public void status(String msg_, boolean overwriteSuspendedMsg) {
+ if (client != null && client.isSuspended()) {
+ if (overwriteSuspendedMsg) {
+ status = msg_;
+ updateLine();
+ }
+ }
+ else {
+ status = msg_;
+ updateLine();
+ }
+ }
+
+ @Override public void status(String msg, int progress) {
+ this.status(msg, progress, 0);
+ }
+
+ @Override public void status(String msg, int progress, long size) {
+ status = showProgress(msg, progress, size);
updateLine();
}
- @Override
- public void setRenderingProjectName(String name_) {
+ @Override public void setRenderingProjectName(String name_) {
if (name_ == null || name_.isEmpty()) {
project = "";
}
else {
- project = "Project \"" + name_ + "\" |";
+ project = name_ + " |";
}
updateLine();
}
- @Override
- public void error(String msg_) {
+ @Override public void error(String msg_) {
status = "Error " + msg_;
updateLine();
}
- @Override
- public void AddFrameRendered() {
+ @Override public void AddFrameRendered() {
rendered += 1;
updateLine();
}
- @Override
- public void displayStats(Stats stats) {
+ @Override public void displayStats(Stats stats) {
remaining = stats.getRemainingFrame();
creditsEarned = String.valueOf(stats.getCreditsEarnedDuringSession());
updateLine();
}
- @Override
- public void setRemainingTime(String time_) {
- status = "(remaining " + time_ + ")";
+ @Override public void displayUploadQueueStats(int queueSize, long queueVolume) {
+ this.uploadQueueSize = queueSize;
+ this.uploadQueueVolume = queueVolume;
+ }
+
+ @Override public void setRemainingTime(String time_) {
+ this.eta = time_;
updateLine();
}
- @Override
- public void setRenderingTime(String time_) {
+ @Override public void setRenderingTime(String time_) {
status = "Rendering " + time_;
updateLine();
}
- @Override
- public void setClient(Client cli) {
+ @Override public void setClient(Client cli) {
client = cli;
}
- @Override
- public void setComputeMethod(String computeMethod_) {
+ @Override public void setComputeMethod(String computeMethod_) {
computeMethod = computeMethod_;
}
- @Override
- public Client getClient() {
+ @Override public Client getClient() {
return client;
}
+ @Override public void successfulAuthenticationEvent(String publickey) {
+
+ }
+
private void updateLine() {
int charToRemove = line.length();
System.out.print("\r");
- line = String.format("Frames: %d Points: %s | %s %s %s", rendered, creditsEarned != null ? creditsEarned : "unknown", project, computeMethod, status + (exiting ? " (Exiting after this frame)" : ""));
+
+ line = String.format("%s Frames: %d Points: %s | Upload Queue: %d%s | %%s %s %s", df.format(new Date()), rendered,
+ creditsEarned != null ? creditsEarned : "unknown", this.uploadQueueSize,
+ (this.uploadQueueSize > 0 ? String.format(" (%.2fMB)", (this.uploadQueueVolume / 1024.0 / 1024.0)) : ""), computeMethod,
+ status + (exiting ? " (Exiting after all frames are uploaded)" : ""));
+
+ if (line.length() + project.length() > 120) {
+ // If the line without the project name is already >120 characters (might happen if the user has thousands of frames and millions of points in the
+ // session + is exiting after all frames are uploaded) then set the line to 117c to avoid a negative number exception in substring function
+ int lineLength = (line.length() >= 120 ? 117 : line.length());
+ line = String.format(line, project.substring(0, 117 - lineLength) + "...");
+ }
+ else {
+ line = String.format(line, project);
+ }
+
System.out.print(line);
for (int i = line.length(); i <= charToRemove; i++) {
System.out.print(" ");
}
}
+
+ private String showProgress(String message, int progress, long size) {
+ StringBuilder progressBar = new StringBuilder(140);
+ progressBar
+ .append(message)
+ .append(String.join("", Collections.nCopies(progress == 0 ? 2 : 2 - (int) (Math.log10(progress)), " ")))
+ .append(String.format(" %d%%%% [", progress))
+ .append(String.join("", Collections.nCopies((int) (progress / 10), "=")))
+ .append('>')
+ .append(String.join("", Collections.nCopies(10 - (int) (progress / 10), " ")))
+ .append(']');
+
+ if (size > 0) {
+ progressBar.append(String.format(" %dMB", (size / 1024 / 1024)));
+ }
+
+ if (!this.eta.equals("")) {
+ progressBar.append(String.format(" ETA %s", this.eta));
+ }
+
+ return progressBar.toString();
+ }
}
diff --git a/src/com/sheepit/client/standalone/ListGpuParameterHandler.java b/src/com/sheepit/client/standalone/ListGpuParameterHandler.java
index 86f88846..2f62b08d 100644
--- a/src/com/sheepit/client/standalone/ListGpuParameterHandler.java
+++ b/src/com/sheepit/client/standalone/ListGpuParameterHandler.java
@@ -2,7 +2,7 @@
* Copyright (C) 2017 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -37,12 +37,12 @@ public ListGpuParameterHandler(CmdLineParser parser, OptionDef option, Setter
super(parser, option, setter);
}
- @Override
- public int parseArguments(Parameters params) throws CmdLineException {
+ @Override public int parseArguments(Parameters params) throws CmdLineException {
List gpus = GPU.listDevices(new Configuration(null, null, null));
if (gpus != null) {
for (GPUDevice gpu : gpus) {
- System.out.println("Id : " + gpu.getId());
+ System.out.println("GPU_ID : " + gpu.getOldId());
+ System.out.println("Long ID : " + gpu.getId());
System.out.println("Model : " + gpu.getModel());
System.out.println("Memory, MB: " + (int) (gpu.getMemory() / (1024 * 1024)));
System.out.println();
@@ -53,8 +53,7 @@ public int parseArguments(Parameters params) throws CmdLineException {
return 0;
}
- @Override
- public String getDefaultMetaVariable() {
+ @Override public String getDefaultMetaVariable() {
return null;
}
}
diff --git a/src/com/sheepit/client/standalone/VersionParameterHandler.java b/src/com/sheepit/client/standalone/VersionParameterHandler.java
index e1de0ac8..501b3841 100644
--- a/src/com/sheepit/client/standalone/VersionParameterHandler.java
+++ b/src/com/sheepit/client/standalone/VersionParameterHandler.java
@@ -2,7 +2,7 @@
* Copyright (C) 2015 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -33,16 +33,14 @@ public VersionParameterHandler(CmdLineParser parser, OptionDef option, Setter
super(parser, option, setter);
}
- @Override
- public int parseArguments(Parameters params) throws CmdLineException {
+ @Override public int parseArguments(Parameters params) throws CmdLineException {
Configuration config = new Configuration(null, "", "");
System.out.println("Version: " + config.getJarVersion());
System.exit(0);
return 0;
}
- @Override
- public String getDefaultMetaVariable() {
+ @Override public String getDefaultMetaVariable() {
return null;
}
}
diff --git a/src/com/sheepit/client/standalone/Worker.java b/src/com/sheepit/client/standalone/Worker.java
index 550f445d..63a773ea 100644
--- a/src/com/sheepit/client/standalone/Worker.java
+++ b/src/com/sheepit/client/standalone/Worker.java
@@ -2,7 +2,7 @@
* Copyright (C) 2010-2014 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -21,8 +21,8 @@
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
-import static org.kohsuke.args4j.ExampleMode.REQUIRED;
import org.kohsuke.args4j.Option;
+import org.kohsuke.args4j.OptionHandlerFilter;
import java.io.File;
import java.net.MalformedURLException;
@@ -30,6 +30,7 @@
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;
@@ -38,6 +39,7 @@
import com.sheepit.client.Pair;
import com.sheepit.client.SettingsLoader;
import com.sheepit.client.ShutdownHook;
+import com.sheepit.client.Utils;
import com.sheepit.client.hardware.gpu.GPU;
import com.sheepit.client.hardware.gpu.GPUDevice;
import com.sheepit.client.hardware.gpu.nvidia.Nvidia;
@@ -45,71 +47,52 @@
import com.sheepit.client.network.Proxy;
public class Worker {
- @Option(name = "-server", usage = "Render-farm server, default https://client.sheepit-renderfarm.com", metaVar = "URL", required = false)
- private String server = "https://client.sheepit-renderfarm.com";
+ @Option(name = "-server", usage = "Render-farm server, default https://client.sheepit-renderfarm.com", metaVar = "URL", required = false) private String server = "https://client.sheepit-renderfarm.com";
- @Option(name = "-login", usage = "User's login", metaVar = "LOGIN", required = false)
- private String login = "";
+ @Option(name = "-login", usage = "User's login", metaVar = "LOGIN", required = false) private String login = "";
- @Option(name = "-password", usage = "User's password", metaVar = "PASSWORD", required = false)
- private String password = "";
+ @Option(name = "-password", usage = "User's password", metaVar = "PASSWORD", required = false) 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 = "-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 = "Name of the GPU used for the render, for example CUDA_0 for Nvidia or OPENCL_0 for AMD/Intel card", metaVar = "CUDA_0", required = false) private String gpu_device = null;
- @Option(name = "-gpu", usage = "Name of the GPU used for the render, for example CUDA_0 for Nvidia or OPENCL_0 for AMD/Intel card", metaVar = "CUDA_0", required = false)
- private String gpu_device = null;
+ @Option(name = "--no-gpu", usage = "Don't detect GPUs", required = false) private boolean no_gpu_detection = false;
- @Option(name = "--no-gpu", usage = "Don't detect GPUs", required = false)
- private boolean no_gpu_detection = false;
+ @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", required = false) private String method = 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", required = false)
- private String method = null;
+ @Option(name = "-cores", usage = "Number of cores/threads to use for the render", metaVar = "3", required = false) private int nb_cores = -1;
- @Option(name = "-cores", usage = "Number of cores/threads to use for the render", metaVar = "3", required = false)
- private int nb_cores = -1;
+ @Option(name = "-memory", usage = "Maximum memory allow to be used by renderer, number with unit (800M, 2G, ...)", required = false) private String max_ram = null;
- @Option(name = "-memory", usage = "Maximum memory allow to be used by renderer (in MB)", required = false)
- private int max_ram = -1;
+ @Option(name = "-rendertime", usage = "Maximum time allow for each frame (in minutes)", required = false) private int max_rendertime = -1;
- @Option(name = "-rendertime", usage = "Maximum time allow for each frame (in minute)", required = false)
- private int max_rendertime = -1;
+ @Option(name = "--verbose", usage = "Display log", required = false) private boolean print_log = false;
- @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 = "-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 = "-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 = "-extras", usage = "Extras data push on the authentication request", required = false)
- private String extras = null;
+ @Option(name = "-ui", usage = "Specify the user interface to use, default '" + GuiSwing.type + "', available '" + GuiTextOneLine.type + "', '"
+ + GuiText.type + "', '" + GuiSwing.type + "' (graphical)", required = false) private String ui_type = null;
- @Option(name = "-ui", usage = "Specify the user interface to use, default '" + GuiSwing.type + "', available '" + GuiTextOneLine.type + "', '" + GuiText.type + "', '" + GuiSwing.type + "' (graphical)", required = false)
- private String ui_type = null;
+ @Option(name = "-config", usage = "Specify the configuration file", required = false) private String config_file = null;
- @Option(name = "-config", usage = "Specify the configuration file", required = false)
- private String config_file = null;
+ @Option(name = "--version", usage = "Display application version", required = false, handler = VersionParameterHandler.class) private VersionParameterHandler versionHandler;
- @Option(name = "--version", usage = "Display application version", required = false, handler = VersionParameterHandler.class)
- private VersionParameterHandler versionHandler;
+ @Option(name = "--show-gpu", usage = "Print available CUDA devices and exit", required = false, handler = ListGpuParameterHandler.class) private ListGpuParameterHandler listGpuParameterHandler;
- @Option(name = "--show-gpu", usage = "Print available CUDA devices and exit", required = false, handler = ListGpuParameterHandler.class)
- private ListGpuParameterHandler listGpuParameterHandler;
+ @Option(name = "--no-systray", usage = "Don't use SysTray", required = false) private boolean useSysTray = false;
- @Option(name = "--no-systray", usage = "Don't use systray", required = false)
- private boolean no_systray = false;
+ @Option(name = "-priority", usage = "Set render process priority (19 lowest to -19 highest)", required = false) private int priority = 19;
- @Option(name = "-priority", usage = "Set render process priority (19 lowest to -19 highest)", required = false)
- private int priority = 19;
-
- @Option(name = "-title", usage = "Custom title for the GUI Client", required = false)
- private String title = "SheepIt Render Farm";
+ @Option(name = "-title", usage = "Custom title for the GUI Client", required = false) private String title = "SheepIt Render Farm";
+
+ @Option(name = "-theme", usage = "Specify the theme to use for the graphical client, default 'light', available 'light', 'dark'", required = false) private String theme = null;
+
+ @Option(name = "-renderbucket-size", usage = "Set a custom GPU renderbucket size (32 for 32x32px, 64 for 64x64px, and so on). NVIDIA GPUs support a maximum renderbucket size of 512x512 pixel, while AMD GPUs support a maximum 2048x2048 pixel renderbucket size. Minimum renderbucket size is 32 pixels for all GPUs", required = false) private int renderbucketSize = -1;
public static void main(String[] args) {
new Worker().doMain(args);
@@ -125,7 +108,7 @@ public void doMain(String[] args) {
System.err.println("Usage: ");
parser.printUsage(System.err);
System.err.println();
- System.err.println("Example: java " + this.getClass().getName() + " " + parser.printExample(REQUIRED));
+ System.err.println("Example: java " + this.getClass().getName() + " " + parser.printExample(OptionHandlerFilter.REQUIRED));
return;
}
@@ -133,7 +116,7 @@ public void doMain(String[] args) {
Configuration config = new Configuration(null, login, password);
config.setPrintLog(print_log);
config.setUsePriority(priority);
- config.setDetectGPUs(! no_gpu_detection);
+ config.setDetectGPUs(!no_gpu_detection);
if (cache_dir != null) {
File a_dir = new File(cache_dir);
@@ -143,45 +126,37 @@ public void doMain(String[] args) {
}
}
- 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);
- }
+ // We set a hard limit of 3 concurrent uploads. As the server doesn't allocate more than 3 concurrent jobs to
+ // a single session to avoid any client taking too many frames and not validating them, we throttle the uploads.
+ // If we don't set this limit, in a computer with slow uploads the server will return a "no job available" when
+ // the 4th concurrent job is requested and that will put the client in "wait" mode for some random time. To
+ // avoid that situation we set this limit.
+ config.setMaxUploadingJob(3);
+
+ // Store the SysTray preference from the user. Please note that we must ! the value of the variable because the way args4j works. If the --no-systray
+ // parameter is detected, args4j will store (boolean)true in the useSysTray variable but we want to store (boolean)false in the configuration class
+ // for further checks.
+ config.setUseSysTray(!useSysTray);
if (gpu_device != null) {
if (gpu_device.startsWith(Nvidia.TYPE) == false && gpu_device.startsWith(OpenCL.TYPE) == false) {
- System.err.println("CUDA_DEVICE should look like '" + Nvidia.TYPE + "_X' or '" + OpenCL.TYPE + "_X' where X is a number");
- return;
- }
- String family = "";
- try {
- if (gpu_device.startsWith(Nvidia.TYPE)) {
- family = Nvidia.TYPE;
- }
- else if (gpu_device.startsWith(OpenCL.TYPE)) {
- family = OpenCL.TYPE;
- }
- Integer.parseInt(gpu_device.substring(family.length() + 1)); // for the _
- }
- catch (NumberFormatException en) {
- System.err.println("Gpu device code should look like '" + family + "_X' where X is a number");
- return;
+ System.err.println("ERROR: The entered GPU_ID is invalid. The GPU_ID should look like '" + Nvidia.TYPE + "_#' or '" + OpenCL.TYPE
+ + "_#'. Please use the proper GPU_ID from the GPU list below\n");
+ showGPUList(parser);
}
+
GPUDevice gpu = GPU.getGPUDevice(gpu_device);
if (gpu == null) {
- System.err.println("GPU unknown");
- System.exit(2);
+ System.err.println("ERROR: The entered GPU_ID is invalid. Please use the proper GPU_ID from the GPU list below\n");
+ showGPUList(parser);
}
- config.setUseGPU(gpu);
+ config.setGPUDevice(gpu);
}
if (request_time != null) {
String[] intervals = request_time.split(",");
if (intervals != null) {
- config.requestTime = new LinkedList>();
+ config.setRequestTime(new LinkedList>());
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm");
for (String interval : intervals) {
@@ -195,15 +170,17 @@ else if (gpu_device.startsWith(OpenCL.TYPE)) {
end.setTime(timeFormat.parse(times[1]));
}
catch (ParseException e) {
- System.err.println("Error: wrong format in request time");
+ System.err.println(String.format(
+ "ERROR: The entered time slot (-request-time parameter) doesn't seem to be valid. Please check the format is correct [%s]",
+ e.getMessage()));
System.exit(2);
}
if (start.before(end)) {
- config.requestTime.add(new Pair(start, end));
+ config.getRequestTime().add(new Pair(start, end));
}
else {
- System.err.println("Error: wrong request time " + times[0] + " is after " + times[1]);
+ System.err.println(String.format("ERROR: The start (%s) time must be earlier than the finish (%s) time", times[0], times[1]));
System.exit(2);
}
}
@@ -212,15 +189,22 @@ else if (gpu_device.startsWith(OpenCL.TYPE)) {
}
if (nb_cores < -1 || nb_cores == 0) { // -1 is the default
- System.err.println("Error: use-number-core should be a greater than zero");
+ System.err.println("ERROR: The entered number of CPU cores (-cores parameter) is not valid. Please enter a number greater than zero");
return;
}
else {
- config.setUseNbCores(nb_cores);
+ config.setNbCores(nb_cores);
}
- if (max_ram > 0) {
- config.setMaxMemory(max_ram * 1000);
+ if (max_ram != null) {
+ try {
+ config.setMaxMemory(Utils.parseNumber(max_ram) / 1000); // internal value are in kB
+ }
+ catch (java.lang.IllegalStateException e) {
+ System.err.println(
+ String.format("ERROR: The entered value of maximum memory (-memory parameter) doesn't seem to be a valid number [%s]", e.getMessage()));
+ return;
+ }
}
if (max_rendertime > 0) {
@@ -232,7 +216,9 @@ else if (gpu_device.startsWith(OpenCL.TYPE)) {
compute_method = ComputeType.valueOf(method);
}
catch (IllegalArgumentException e) {
- System.err.println("Error: compute-method unknown");
+ System.err.println(String.format(
+ "ERROR: The entered compute method (-compute-method parameter) is not valid. Available values are CPU, GPU or CPU_GPU [%s]",
+ e.getMessage()));
System.exit(2);
}
}
@@ -250,8 +236,8 @@ else if (gpu_device.startsWith(OpenCL.TYPE)) {
Proxy.set(proxy);
}
catch (MalformedURLException e) {
- System.err.println("Error: wrong url for proxy");
- System.err.println(e);
+ System.err.println(String.format("ERROR: The entered proxy URL (-proxy parameter) doesn't seem to have the right format. Please check it [%s]",
+ e.getMessage()));
System.exit(2);
}
}
@@ -261,40 +247,54 @@ else if (gpu_device.startsWith(OpenCL.TYPE)) {
}
if (compute_method == ComputeType.CPU && config.getGPUDevice() != null) {
- System.err.println("You choose to only use the CPU but a GPU was also provided. You can not do both.");
- System.err.println("Aborting");
+ System.err.println(
+ "ERROR: The compute method is set to use CPU only, but a GPU has also been specified. Change the compute method to CPU_GPU or remove the GPU");
System.exit(2);
}
else if (compute_method == ComputeType.CPU_GPU && config.getGPUDevice() == null) {
- System.err.println("You choose to only use the CPU and GPU but no GPU device was provided.");
- System.err.println("Aborting");
+ System.err.println(
+ "ERROR: The compute method is set to use both CPU and GPU, but no GPU has been specified. Change the compute method to CPU or add a GPU (via -gpu parameter)");
System.exit(2);
}
else if (compute_method == ComputeType.GPU && config.getGPUDevice() == null) {
- System.err.println("You choose to only use the GPU but no GPU device was provided.");
- System.err.println("Aborting");
+ System.err.println("ERROR: The compute method is set to use GPU only, but not GPU has been specified. Please add a GPU (via -gpu parameter)");
System.exit(2);
}
else if (compute_method == ComputeType.CPU) {
- config.setUseGPU(null); // remove the GPU
+ config.setGPUDevice(null); // remove the GPU
}
config.setComputeMethod(compute_method);
+ // Change the default configuration if the user has specified a minimum renderbucket size of 32
+ if (renderbucketSize >= 32) {
+ // Send the proposed renderbucket size and check if viable
+ config.setRenderbucketSize(renderbucketSize);
+ }
+
if (ui_type != null) {
config.setUIType(ui_type);
}
+ if (theme != null) {
+ if (!theme.equals("light") && !theme.equals("dark")) {
+ System.err.println("ERROR: The entered theme (-theme parameter) doesn't exist. Please choose either 'light' or 'dark'");
+ System.exit(2);
+ }
+
+ config.setTheme(this.theme);
+ }
+
if (config_file != null) {
if (new File(config_file).exists() == false) {
- System.err.println("Configuration file not found.");
- System.err.println("Aborting");
+ System.err.println(
+ "ERROR: The entered configuration file (-config parameter) cannot be loaded. Please check that you've entered an existing filename");
System.exit(2);
}
- config.setConfigPath(config_file);
- new SettingsLoader(config_file).merge(config);
+ config.setConfigFilePath(config_file);
}
+ new SettingsLoader(config_file).merge(config);
Log.getInstance(config).debug("client version " + config.getJarVersion());
Gui gui;
@@ -304,8 +304,9 @@ else if (compute_method == ComputeType.CPU) {
}
switch (type) {
case GuiTextOneLine.type:
- if (config.getPrintLog()) {
- System.out.println("OneLine UI can not be used if verbose mode is enabled");
+ if (config.isPrintLog()) {
+ System.err.println(
+ "ERROR: The oneLine UI and the --verbose parameter cannot be used at the same time. Please either change the ui to text or remove the verbose mode");
System.exit(2);
}
gui = new GuiTextOneLine();
@@ -316,11 +317,11 @@ else if (compute_method == ComputeType.CPU) {
default:
case GuiSwing.type:
if (java.awt.GraphicsEnvironment.isHeadless()) {
- System.out.println("Graphical ui can not be launch.");
- System.out.println("You should set a DISPLAY or use a text ui (with -ui " + GuiTextOneLine.type + " or -ui " + GuiText.type + ").");
+ System.err.println("ERROR: Your current configuration doesn't support graphical UI.");
+ System.err.println("Please use one of the text-based UIs provided (using -ui " + GuiTextOneLine.type + " or -ui " + GuiText.type + ")");
System.exit(3);
}
- gui = new GuiSwing(no_systray == false, title);
+ gui = new GuiSwing(config.isUseSysTray(), title);
break;
}
Client cli = new Client(gui, config, server);
@@ -331,4 +332,13 @@ else if (compute_method == ComputeType.CPU) {
gui.start();
}
+
+ private void showGPUList(CmdLineParser parser) {
+ try {
+ parser.parseArgument("--show-gpu");
+ }
+ catch (CmdLineException e) {
+ System.err.println(String.format("ERROR: Unable to parse the provided parameter [%s]", e.getMessage()));
+ }
+ }
}
diff --git a/src/com/sheepit/client/standalone/swing/activity/Activity.java b/src/com/sheepit/client/standalone/swing/activity/Activity.java
index 537ad139..15a996f2 100644
--- a/src/com/sheepit/client/standalone/swing/activity/Activity.java
+++ b/src/com/sheepit/client/standalone/swing/activity/Activity.java
@@ -2,7 +2,7 @@
* Copyright (C) 2015 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
diff --git a/src/com/sheepit/client/standalone/swing/activity/Settings.java b/src/com/sheepit/client/standalone/swing/activity/Settings.java
index 263fbb89..80c236c0 100644
--- a/src/com/sheepit/client/standalone/swing/activity/Settings.java
+++ b/src/com/sheepit/client/standalone/swing/activity/Settings.java
@@ -2,7 +2,7 @@
* Copyright (C) 2015 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -19,6 +19,7 @@
package com.sheepit.client.standalone.swing.activity;
+import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
@@ -26,15 +27,18 @@
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
+import java.awt.image.BufferedImage;
import java.io.File;
import java.net.MalformedURLException;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
+import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
@@ -43,20 +47,31 @@
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
+import javax.swing.JRadioButton;
import javax.swing.JSlider;
import javax.swing.JSpinner;
import javax.swing.JTextField;
+import javax.swing.UIManager;
+import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.SpinnerNumberModel;
+import com.formdev.flatlaf.FlatLaf;
+import com.formdev.flatlaf.FlatLightLaf;
+import com.formdev.flatlaf.FlatDarkLaf;
+
import com.sheepit.client.Configuration;
import com.sheepit.client.Configuration.ComputeType;
import com.sheepit.client.SettingsLoader;
import com.sheepit.client.hardware.cpu.CPU;
import com.sheepit.client.hardware.gpu.GPU;
import com.sheepit.client.hardware.gpu.GPUDevice;
+import com.sheepit.client.hardware.gpu.GPULister;
+import com.sheepit.client.hardware.gpu.nvidia.Nvidia;
+import com.sheepit.client.hardware.gpu.opencl.OpenCL;
import com.sheepit.client.network.Proxy;
import com.sheepit.client.os.OS;
import com.sheepit.client.standalone.GuiSwing;
+import com.sheepit.client.standalone.swing.components.CollapsibleJPanel;
public class Settings implements Activity {
private static final String DUMMY_CACHE_DIR = "Auto detected";
@@ -70,6 +85,9 @@ public class Settings implements Activity {
private JFileChooser cacheDirChooser;
private JCheckBox useCPU;
private List useGPUs;
+ private JCheckBox useSysTray;
+ private JLabel renderbucketSizeLabel;
+ private JSlider renderbucketSize;
private JSlider cpuCores;
private JSlider ram;
private JSpinner renderTime;
@@ -77,15 +95,16 @@ public class Settings implements Activity {
private JTextField proxy;
private JTextField hostname;
+ private ButtonGroup themeOptionsGroup;
+ private JRadioButton lightMode;
+ private JRadioButton darkMode;
+
private JCheckBox saveFile;
private JCheckBox autoSignIn;
JButton saveButton;
private boolean haveAutoStarted;
-
- private JTextField tileSizeValue;
- private JLabel tileSizeLabel;
- private JCheckBox customTileSize;
+ private boolean useSysTrayPrevState;
public Settings(GuiSwing parent_) {
parent = parent_;
@@ -94,19 +113,39 @@ public Settings(GuiSwing parent_) {
haveAutoStarted = false;
}
- @Override
- public void show() {
+ @Override public void show() {
Configuration config = parent.getConfiguration();
new SettingsLoader(config.getConfigFilePath()).merge(config);
+ useSysTrayPrevState = config.isUseSysTray();
+
+ applyTheme(config.getTheme()); // apply the proper theme (light/dark)
List gpus = GPU.listDevices(config);
+ useGPUs.clear(); // Empty the auxiliary list (used in the list of checkboxes)
GridBagConstraints constraints = new GridBagConstraints();
int currentRow = 0;
- ImageIcon image = new ImageIcon(getClass().getResource("/title.png"));
+
+ JLabel labelImage;
+ try {
+ // Include the version of the app as a watermark in the SheepIt logo.
+ final BufferedImage watermark = ImageIO.read(getClass().getResource("/sheepit-logo.png"));
+
+ Graphics gph = watermark.getGraphics();
+ gph.setFont(gph.getFont().deriveFont(12f));
+ gph.drawString("v" + config.getJarVersion(), 335, 120);
+ gph.dispose();
+
+ labelImage = new JLabel(new ImageIcon(watermark));
+ }
+ catch (Exception e) {
+ // If something fails, we just show the SheepIt logo (without any watermark)
+ ImageIcon image = new ImageIcon(getClass().getResource("/sheepit-logo.png"));
+ labelImage = new JLabel(image);
+ }
+
constraints.fill = GridBagConstraints.CENTER;
- JLabel labelImage = new JLabel(image);
constraints.gridwidth = 2;
constraints.gridx = 0;
constraints.gridy = currentRow;
@@ -114,18 +153,23 @@ public void show() {
++currentRow;
+ constraints.gridy = currentRow;
+ parent.getContentPane().add(new JLabel(" "), constraints); // Add a separator between logo and first panel
+
+ currentRow++;
+
// authentication
- JPanel authentication_panel = new JPanel(new GridLayout(2, 2));
+ CollapsibleJPanel authentication_panel = new CollapsibleJPanel(new GridLayout(2, 2));
authentication_panel.setBorder(BorderFactory.createTitledBorder("Authentication"));
JLabel loginLabel = new JLabel("Username:");
login = new JTextField();
- login.setText(parent.getConfiguration().login());
+ login.setText(parent.getConfiguration().getLogin());
login.setColumns(20);
login.addKeyListener(new CheckCanStart());
JLabel passwordLabel = new JLabel("Password:");
password = new JPasswordField();
- password.setText(parent.getConfiguration().password());
+ password.setText(parent.getConfiguration().getPassword());
password.setColumns(10);
password.addKeyListener(new CheckCanStart());
@@ -140,13 +184,43 @@ public void show() {
constraints.fill = GridBagConstraints.HORIZONTAL;
parent.getContentPane().add(authentication_panel, constraints);
+ // Theme selection panel
+ CollapsibleJPanel themePanel = new CollapsibleJPanel(new GridLayout(1, 3));
+ themePanel.setBorder(BorderFactory.createTitledBorder("Theme"));
+
+ themeOptionsGroup = new ButtonGroup();
+
+ lightMode = new JRadioButton("Light");
+ lightMode.setActionCommand("light");
+ lightMode.setSelected(config.getTheme().equals("light"));
+ lightMode.addActionListener(new ApplyThemeAction());
+
+ darkMode = new JRadioButton("Dark");
+ darkMode.setActionCommand("dark");
+ darkMode.setSelected(config.getTheme().equals("dark"));
+ darkMode.addActionListener(new ApplyThemeAction());
+
+ themePanel.add(lightMode);
+ themePanel.add(darkMode);
+
+ // Group both radio buttons to allow only one selected
+ themeOptionsGroup.add(lightMode);
+ themeOptionsGroup.add(darkMode);
+
+ currentRow++;
+ constraints.gridx = 0;
+ constraints.gridy = currentRow;
+ constraints.gridwidth = 2;
+
+ parent.getContentPane().add(themePanel, constraints);
+
// directory
- JPanel directory_panel = new JPanel(new GridLayout(1, 3));
+ CollapsibleJPanel directory_panel = new CollapsibleJPanel(new GridLayout(1, 3));
directory_panel.setBorder(BorderFactory.createTitledBorder("Cache"));
JLabel cacheLabel = new JLabel("Working directory:");
directory_panel.add(cacheLabel);
String destination = DUMMY_CACHE_DIR;
- if (config.getUserHasSpecifiedACacheDir()) {
+ if (config.isUserHasSpecifiedACacheDir()) {
destination = config.getCacheDirForSettings().getName();
}
@@ -175,7 +249,7 @@ public void show() {
// compute devices
GridBagLayout gridbag = new GridBagLayout();
GridBagConstraints compute_devices_constraints = new GridBagConstraints();
- JPanel compute_devices_panel = new JPanel(gridbag);
+ CollapsibleJPanel compute_devices_panel = new CollapsibleJPanel(gridbag);
compute_devices_panel.setBorder(BorderFactory.createTitledBorder("Compute devices"));
@@ -206,34 +280,87 @@ else if (method == ComputeType.GPU) {
gridbag.setConstraints(useCPU, compute_devices_constraints);
compute_devices_panel.add(useCPU);
- for (GPUDevice gpu : gpus) {
- JCheckBoxGPU gpuCheckBox = new JCheckBoxGPU(gpu);
- gpuCheckBox.setToolTipText(gpu.getId());
- if (gpuChecked) {
- GPUDevice config_gpu = config.getGPUDevice();
- if (config_gpu != null && config_gpu.getId().equals(gpu.getId())) {
- gpuCheckBox.setSelected(gpuChecked);
+ if (gpus.size() > 0) {
+ renderbucketSizeLabel = new JLabel("Renderbucket size:");
+ renderbucketSize = new JSlider();
+ renderbucketSize.setMajorTickSpacing(1);
+ renderbucketSize.setMinorTickSpacing(1);
+ renderbucketSize.setPaintTicks(true);
+ renderbucketSize.setPaintLabels(true);
+
+ renderbucketSizeLabel.setVisible(false);
+ renderbucketSize.setVisible(false);
+
+ for (GPUDevice gpu : gpus) {
+ JCheckBoxGPU gpuCheckBox = new JCheckBoxGPU(gpu);
+ gpuCheckBox.setToolTipText(gpu.getId());
+ if (gpuChecked) {
+ GPUDevice config_gpu = config.getGPUDevice();
+ if (config_gpu != null && config_gpu.getId().equals(gpu.getId())) {
+ gpuCheckBox.setSelected(gpuChecked);
+ renderbucketSizeLabel.setVisible(true);
+ renderbucketSize.setVisible(true);
+ }
}
+ gpuCheckBox.addActionListener(new GpuChangeAction());
+
+ compute_devices_constraints.gridy++;
+ gridbag.setConstraints(gpuCheckBox, compute_devices_constraints);
+ compute_devices_panel.add(gpuCheckBox);
+ useGPUs.add(gpuCheckBox);
}
- gpuCheckBox.addActionListener(new GpuChangeAction());
+ // Initialisation values will apply if we are not able to detect the proper GPU technology or
+ // because is a new one (different from CUDA and OPENCL). In that case, move into a safe position
+ // of 32x32 pixel render bucket and a maximum of 128x128 pixel for the "unknown GPU"
+ int maxRenderbucketSize = 128;
+ int recommendedBucketSize = 32;
+
+ if (config.getComputeMethod() == ComputeType.GPU || config.getComputeMethod() == ComputeType.CPU_GPU) {
+ GPULister gpu;
+
+ if (config.getGPUDevice().getType().equals("CUDA")) {
+ gpu = new Nvidia();
+ maxRenderbucketSize = gpu.getMaximumRenderBucketSize(config.getGPUDevice().getMemory());
+ recommendedBucketSize = gpu.getRecommendedRenderBucketSize(config.getGPUDevice().getMemory());
+ }
+ else if (config.getGPUDevice().getType().equals("OPENCL")) {
+ gpu = new OpenCL();
+ maxRenderbucketSize = gpu.getMaximumRenderBucketSize(config.getGPUDevice().getMemory());
+ recommendedBucketSize = gpu.getRecommendedRenderBucketSize(config.getGPUDevice().getMemory());
+ }
+ }
+
+ buildRenderBucketSizeSlider(maxRenderbucketSize, config.getRenderbucketSize() != -1 ?
+ ((int) (Math.log(config.getRenderbucketSize()) / Math.log(2))) - 5 :
+ ((int) (Math.log(recommendedBucketSize) / Math.log(2))) - 5);
+
+ compute_devices_constraints.weightx = 1.0 / gpus.size();
+ compute_devices_constraints.gridx = 0;
compute_devices_constraints.gridy++;
- gridbag.setConstraints(gpuCheckBox, compute_devices_constraints);
- compute_devices_panel.add(gpuCheckBox);
- useGPUs.add(gpuCheckBox);
+
+ gridbag.setConstraints(renderbucketSizeLabel, compute_devices_constraints);
+ compute_devices_panel.add(renderbucketSizeLabel);
+
+ compute_devices_constraints.gridx = 1;
+ compute_devices_constraints.weightx = 1.0;
+
+ gridbag.setConstraints(renderbucketSize, compute_devices_constraints);
+ compute_devices_panel.add(new JLabel(" "), compute_devices_constraints); // Add a space between lines
+ compute_devices_panel.add(renderbucketSize);
}
CPU cpu = new CPU();
if (cpu.cores() > 1) { // if only one core is available, no need to show the choice
double step = 1;
- double display = (double)cpu.cores() / step;
+ double display = (double) cpu.cores() / step;
while (display > 10) {
step += 1.0;
- display = (double)cpu.cores() / step;
+ display = (double) cpu.cores() / step;
}
cpuCores = new JSlider(1, cpu.cores());
- cpuCores.setMajorTickSpacing((int)(step));
+ cpuCores.setMajorTickSpacing((int) (step));
cpuCores.setMinorTickSpacing(1);
cpuCores.setPaintTicks(true);
cpuCores.setPaintLabels(true);
@@ -251,18 +378,19 @@ else if (method == ComputeType.GPU) {
compute_devices_constraints.weightx = 1.0;
gridbag.setConstraints(cpuCores, compute_devices_constraints);
+ compute_devices_panel.add(new JLabel(" "), compute_devices_constraints); // Add a space between lines
compute_devices_panel.add(cpuCores);
}
// max ram allowed to render
OS os = OS.getOS();
- int all_ram = os.getMemory();
+ int all_ram = (int) os.getMemory();
ram = new JSlider(0, all_ram);
int step = 1000000;
- double display = (double)all_ram / (double)step;
+ double display = (double) all_ram / (double) step;
while (display > 10) {
step += 1000000;
- display = (double)all_ram / (double)step;
+ display = (double) all_ram / (double) step;
}
Hashtable labelTable = new Hashtable();
for (int g = 0; g < all_ram; g += step) {
@@ -272,7 +400,7 @@ else if (method == ComputeType.GPU) {
ram.setLabelTable(labelTable);
ram.setPaintTicks(true);
ram.setPaintLabels(true);
- ram.setValue(config.getMaxMemory() != -1 ? config.getMaxMemory() : os.getMemory());
+ ram.setValue((int) (config.getMaxMemory() != -1 ? config.getMaxMemory() : os.getMemory()));
JLabel ramLabel = new JLabel("Memory:");
compute_devices_constraints.weightx = 1.0 / gpus.size();
@@ -286,6 +414,7 @@ else if (method == ComputeType.GPU) {
compute_devices_constraints.weightx = 1.0;
gridbag.setConstraints(ram, compute_devices_constraints);
+ compute_devices_panel.add(new JLabel(" "), compute_devices_constraints); // Add a space between lines
compute_devices_panel.add(ram);
parent.getContentPane().add(compute_devices_panel, constraints);
@@ -298,7 +427,11 @@ else if (method == ComputeType.GPU) {
priority.setPaintTicks(true);
priority.setPaintLabels(true);
priority.setValue(config.getPriority());
- JLabel priorityLabel = new JLabel(high_priority_support ? "Priority (High <-> Low):" : "Priority (Normal <-> Low):" );
+ JLabel priorityLabel = new JLabel(high_priority_support ? "Priority (High <-> Low):" : "Priority (Normal <-> Low):");
+
+ boolean showPrioritySlider = os.checkNiceAvailability();
+ priority.setVisible(showPrioritySlider);
+ priorityLabel.setVisible(showPrioritySlider);
compute_devices_constraints.weightx = 1.0 / gpus.size();
compute_devices_constraints.gridx = 0;
@@ -320,9 +453,16 @@ else if (method == ComputeType.GPU) {
parent.getContentPane().add(compute_devices_panel, constraints);
// other
- JPanel advanced_panel = new JPanel(new GridLayout(5, 2));
+ CollapsibleJPanel advanced_panel = new CollapsibleJPanel(new GridLayout(4, 2));
advanced_panel.setBorder(BorderFactory.createTitledBorder("Advanced options"));
+ JLabel useSysTrayLabel = new JLabel("Minimize to SysTray");
+
+ useSysTray = new JCheckBox();
+ useSysTray.setSelected(config.isUseSysTray());
+ advanced_panel.add(useSysTrayLabel);
+ advanced_panel.add(useSysTray);
+
JLabel proxyLabel = new JLabel("Proxy:");
proxyLabel.setToolTipText("http://login:password@host:port");
proxy = new JTextField();
@@ -345,35 +485,11 @@ else if (method == ComputeType.GPU) {
if (parent.getConfiguration().getMaxRenderTime() > 0) {
val = parent.getConfiguration().getMaxRenderTime() / 60;
}
- renderTime = new JSpinner(new SpinnerNumberModel(val,0,1000,1));
+ renderTime = new JSpinner(new SpinnerNumberModel(val, 0, 1000, 1));
advanced_panel.add(renderTimeLabel);
advanced_panel.add(renderTime);
- JLabel customTileSizeLabel = new JLabel("Custom render tile size:");
- customTileSize = new JCheckBox("", config.getTileSize() != -1);
- advanced_panel.add(customTileSizeLabel);
- advanced_panel.add(customTileSize);
-
- customTileSize.addActionListener(new TileSizeChange());
- tileSizeLabel = new JLabel("Tile Size:");
-
- tileSizeValue = new JTextField();
- int fromConfig = parent.getConfiguration().getTileSize();
- if (fromConfig == -1) {
- if (parent.getConfiguration().getGPUDevice() != null) {
- fromConfig = parent.getConfiguration().getGPUDevice().getRecommandedTileSize();
- }
- else {
- fromConfig = 32;
- }
- }
- tileSizeValue.setText(Integer.toString(fromConfig));
- hideCustomTileSize(config.getTileSize() != -1, false);
-
- advanced_panel.add(tileSizeLabel);
- advanced_panel.add(tileSizeValue);
-
currentRow++;
constraints.gridx = 0;
constraints.gridy = currentRow;
@@ -386,7 +502,7 @@ else if (method == ComputeType.GPU) {
saveFile = new JCheckBox("Save settings", true);
general_panel.add(saveFile);
- autoSignIn = new JCheckBox("Auto sign in", config.getAutoSignIn());
+ autoSignIn = new JCheckBox("Auto sign in", config.isAutoSignIn());
autoSignIn.addActionListener(new AutoSignInChangeAction());
general_panel.add(autoSignIn);
@@ -396,6 +512,11 @@ else if (method == ComputeType.GPU) {
constraints.gridwidth = 2;
parent.getContentPane().add(general_panel, constraints);
+ currentRow++;
+ constraints.gridy = currentRow;
+ parent.getContentPane().add(new JLabel(" "), constraints); // Add a separator between last checkboxes and button
+
+ currentRow++;
String buttonText = "Start";
if (parent.getClient() != null) {
if (parent.getClient().isRunning()) {
@@ -410,20 +531,38 @@ else if (method == ComputeType.GPU) {
constraints.gridx = 0;
constraints.gridy = currentRow;
parent.getContentPane().add(saveButton, constraints);
+
+ // Increase the size of the app Window to ensure it shows all the information with the Advanced Options panel opened.
+ parent.setSize(520,850);
- if (haveAutoStarted == false && config.getAutoSignIn() && checkDisplaySaveButton()) {
+ if (haveAutoStarted == false && config.isAutoSignIn() && checkDisplaySaveButton()) {
// auto start
haveAutoStarted = true;
new SaveAction().actionPerformed(null);
}
}
- public void hideCustomTileSize(boolean hidden, boolean displayWarning) {
- tileSizeValue.setVisible(hidden);
- tileSizeLabel.setVisible(hidden);
- if (customTileSize.isSelected() == true && displayWarning) {
- JOptionPane.showMessageDialog(parent.getContentPane(), "These settings should only be changed if you are an advanced user.
Improper settings may lead to invalid and slower renders!", "Warning: Advanced Users Only!", JOptionPane.WARNING_MESSAGE);
+ private void buildRenderBucketSizeSlider(int maxRenderbucketSize, int selectedBucketSize) {
+ Hashtable renderbucketSizeTable = new Hashtable();
+
+ // We "take logs" to calculate the exponent to fill the slider. The logarithm, or log, of a number reflects
+ // what power you need to raise a certain base to in order to get that number. In this case, as we are
+ // offering increments of 2^n, the formula will be:
+ //
+ // log(tile size in px)
+ // exponent = --------------------
+ // log(2)
+ //
+ int steps = (int) (Math.log(maxRenderbucketSize) / Math.log(2));
+
+ for (int i = 5; i <= steps; i++) {
+ renderbucketSizeTable.put((i - 5), new JLabel(String.format("%.0f", Math.pow(2, i))));
}
+
+ renderbucketSize.setMinimum(0);
+ renderbucketSize.setMaximum(renderbucketSizeTable.size() - 1);
+ renderbucketSize.setLabelTable(renderbucketSizeTable);
+ renderbucketSize.setValue(selectedBucketSize);
}
public boolean checkDisplaySaveButton() {
@@ -437,23 +576,33 @@ public boolean checkDisplaySaveButton() {
selected = false;
}
- if (customTileSize.isSelected()) {
- try {
- Integer.parseInt(tileSizeValue.getText().replaceAll(",", ""));
+ saveButton.setEnabled(selected);
+ return selected;
+ }
+
+ private void applyTheme(String theme_) {
+ try {
+ if (theme_.equals("light")) {
+ UIManager.setLookAndFeel(new FlatLightLaf());
}
- catch (NumberFormatException e) {
- selected = false;
+ else if (theme_.equals("dark")) {
+ UIManager.setLookAndFeel(new FlatDarkLaf());
}
+
+ // Apply the new theme
+ FlatLaf.updateUI();
+ }
+ catch (UnsupportedLookAndFeelException e1) {
+ e1.printStackTrace();
}
- saveButton.setEnabled(selected);
- return selected;
}
class ChooseFileAction implements ActionListener {
- @Override
- public void actionPerformed(ActionEvent arg0) {
- JOptionPane.showMessageDialog(parent.getContentPane(), "The working directory has to be dedicated directory.
Caution, everything not related to SheepIt-Renderfarm will be removed.
You should create a directory specifically for it.", "Warning: files will be removed!", JOptionPane.WARNING_MESSAGE);
+ @Override public void actionPerformed(ActionEvent arg0) {
+ JOptionPane.showMessageDialog(parent.getContentPane(),
+ "The working directory has to be dedicated directory.
Caution, everything not related to SheepIt-Renderfarm will be removed.
You should create a directory specifically for it.",
+ "Warning: files will be removed!", JOptionPane.WARNING_MESSAGE);
int returnVal = cacheDirChooser.showOpenDialog(parent.getContentPane());
if (returnVal == JFileChooser.APPROVE_OPTION) {
File file = cacheDirChooser.getSelectedFile();
@@ -465,20 +614,51 @@ public void actionPerformed(ActionEvent arg0) {
class CpuChangeAction implements ActionListener {
- @Override
- public void actionPerformed(ActionEvent e) {
+ @Override public void actionPerformed(ActionEvent e) {
checkDisplaySaveButton();
}
}
class GpuChangeAction implements ActionListener {
- @Override
- public void actionPerformed(ActionEvent e) {
+ @Override public void actionPerformed(ActionEvent e) {
+ renderbucketSizeLabel.setVisible(false);
+ renderbucketSize.setVisible(false);
+
+ int counter = 0;
for (JCheckBox box : useGPUs) {
+ if (!box.isSelected()) {
+ box.setSelected(false);
+ }
+ else {
+ GPULister gpu;
+ int maxRenderbucketSize = 128; // Max default render bucket size
+ int recommendedBucketSize = 32; // Default recommended render bucket size
+
+ if (useGPUs.get(counter).getGPUDevice().getType().equals("CUDA")) {
+ gpu = new Nvidia();
+ maxRenderbucketSize = gpu.getMaximumRenderBucketSize(useGPUs.get(counter).getGPUDevice().getMemory());
+ recommendedBucketSize = gpu.getRecommendedRenderBucketSize(useGPUs.get(counter).getGPUDevice().getMemory());
+ }
+ else if (useGPUs.get(counter).getGPUDevice().getType().equals("OPENCL")) {
+ gpu = new OpenCL();
+ maxRenderbucketSize = gpu.getMaximumRenderBucketSize(useGPUs.get(counter).getGPUDevice().getMemory());
+ recommendedBucketSize = gpu.getRecommendedRenderBucketSize(useGPUs.get(counter).getGPUDevice().getMemory());
+ }
+
+ buildRenderBucketSizeSlider(maxRenderbucketSize, ((int) (Math.log(recommendedBucketSize) / Math.log(2))) - 5);
+
+ renderbucketSizeLabel.setVisible(true);
+ renderbucketSize.setVisible(true);
+ }
+
+ // Simulate a radio button behavior with check buttons while only 1 GPU
+ // can be selected at a time
if (box.equals(e.getSource()) == false) {
box.setSelected(false);
}
+
+ counter++;
}
checkDisplaySaveButton();
}
@@ -486,27 +666,22 @@ public void actionPerformed(ActionEvent e) {
class AutoSignInChangeAction implements ActionListener {
- @Override
- public void actionPerformed(ActionEvent e) {
+ @Override public void actionPerformed(ActionEvent e) {
if (autoSignIn.isSelected()) {
saveFile.setSelected(true);
}
}
}
- class TileSizeChange implements ActionListener {
-
- @Override
- public void actionPerformed(ActionEvent e) {
- boolean custom = customTileSize.isSelected();
- hideCustomTileSize(custom, true);
+ class ApplyThemeAction implements ActionListener {
+ @Override public void actionPerformed(ActionEvent e) {
+ applyTheme(themeOptionsGroup.getSelection().getActionCommand());
}
}
class SaveAction implements ActionListener {
- @Override
- public void actionPerformed(ActionEvent e) {
+ @Override public void actionPerformed(ActionEvent e) {
if (parent == null) {
return;
}
@@ -516,6 +691,9 @@ public void actionPerformed(ActionEvent e) {
return;
}
+ if (themeOptionsGroup.getSelection().getActionCommand() != null)
+ config.setTheme(themeOptionsGroup.getSelection().getActionCommand());
+
if (cacheDir != null) {
File fromConfig = config.getStorageDir();
if (fromConfig != null && fromConfig.getAbsolutePath().equals(cacheDir.getAbsolutePath()) == false) {
@@ -546,7 +724,12 @@ else if (useCPU.isSelected() && selected_gpu != null) {
config.setComputeMethod(method);
if (selected_gpu != null) {
- config.setUseGPU(selected_gpu);
+ config.setGPUDevice(selected_gpu);
+ }
+
+ int renderbucket_size = -1;
+ if (renderbucketSize != null) {
+ renderbucket_size = (int) Math.pow(2, (renderbucketSize.getValue() + 5));
}
int cpu_cores = -1;
@@ -555,10 +738,10 @@ else if (useCPU.isSelected() && selected_gpu != null) {
}
if (cpu_cores > 0) {
- config.setUseNbCores(cpu_cores);
+ config.setNbCores(cpu_cores);
}
- int max_ram = -1;
+ long max_ram = -1;
if (ram != null) {
max_ram = ram.getValue();
}
@@ -569,7 +752,7 @@ else if (useCPU.isSelected() && selected_gpu != null) {
int max_rendertime = -1;
if (renderTime != null) {
- max_rendertime = (Integer)renderTime.getValue() * 60;
+ max_rendertime = (Integer) renderTime.getValue() * 60;
config.setMaxRenderTime(max_rendertime);
}
@@ -588,26 +771,10 @@ else if (useCPU.isSelected() && selected_gpu != null) {
}
}
- String tile = null;
- if (customTileSize.isSelected() && tileSizeValue != null) {
- try {
- tile = tileSizeValue.getText().replaceAll(",", "");
- config.setTileSize(Integer.parseInt(tile));
- }
- catch (NumberFormatException e1) {
- System.err.println("Error: wrong tile format");
- System.err.println(e1);
- System.exit(2);
- }
- }
- else {
- config.setTileSize(-1); // no tile
- }
-
parent.setCredentials(login.getText(), new String(password.getPassword()));
String cachePath = null;
- if (config.getUserHasSpecifiedACacheDir() && config.getCacheDirForSettings() != null) {
+ if (config.isUserHasSpecifiedACacheDir() && config.getCacheDirForSettings() != null) {
cachePath = config.getCacheDirForSettings().getAbsolutePath();
}
@@ -617,7 +784,20 @@ else if (useCPU.isSelected() && selected_gpu != null) {
}
if (saveFile.isSelected()) {
- new SettingsLoader(config.getConfigFilePath(), login.getText(), new String(password.getPassword()), proxyText, hostnameText, method, selected_gpu, cpu_cores, max_ram, max_rendertime, cachePath, autoSignIn.isSelected(), GuiSwing.type, tile, priority.getValue()).saveFile();
+ parent.setSettingsLoader(
+ new SettingsLoader(config.getConfigFilePath(), login.getText(), new String(password.getPassword()), proxyText, hostnameText, method,
+ selected_gpu, renderbucket_size, cpu_cores, max_ram, max_rendertime, cachePath, autoSignIn.isSelected(), useSysTray.isSelected(),
+ GuiSwing.type, themeOptionsGroup.getSelection().getActionCommand(), priority.getValue()));
+
+ // wait for successful authentication (to store the public key)
+ // or do we already have one?
+ if (parent.getClient().getServer().getServerConfig() != null && parent.getClient().getServer().getServerConfig().getPublickey() != null) {
+ parent.getSettingsLoader().saveFile();
+ }
+
+ if (useSysTrayPrevState != useSysTray.isSelected()) {
+ JOptionPane.showMessageDialog(null, "You must restart the SheepIt app for the SysTray change to take effect");
+ }
}
}
}
@@ -637,17 +817,14 @@ public GPUDevice getGPUDevice() {
public class CheckCanStart implements KeyListener {
- @Override
- public void keyPressed(KeyEvent arg0) {
+ @Override public void keyPressed(KeyEvent arg0) {
}
- @Override
- public void keyReleased(KeyEvent arg0) {
+ @Override public void keyReleased(KeyEvent arg0) {
checkDisplaySaveButton();
}
- @Override
- public void keyTyped(KeyEvent arg0) {
+ @Override public void keyTyped(KeyEvent arg0) {
}
}
diff --git a/src/com/sheepit/client/standalone/swing/activity/Working.java b/src/com/sheepit/client/standalone/swing/activity/Working.java
index 15e05056..dea04405 100644
--- a/src/com/sheepit/client/standalone/swing/activity/Working.java
+++ b/src/com/sheepit/client/standalone/swing/activity/Working.java
@@ -2,7 +2,7 @@
* Copyright (C) 2015 Laurent CLOUET
* Author Laurent CLOUET
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -27,9 +27,9 @@
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
-import java.io.ByteArrayInputStream;
+import java.awt.Image;
+import java.io.File;
import java.io.IOException;
-import java.io.InputStream;
import java.text.DecimalFormat;
import java.util.Date;
@@ -39,17 +39,17 @@
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
+import javax.swing.JOptionPane;
import javax.swing.JPanel;
+import javax.swing.UIManager;
import javax.swing.Spring;
import javax.swing.SpringLayout;
import com.sheepit.client.Client;
import com.sheepit.client.Job;
-import com.sheepit.client.RenderProcess;
-import com.sheepit.client.Server;
+import com.sheepit.client.Log;
import com.sheepit.client.Stats;
import com.sheepit.client.Utils;
-import com.sheepit.client.os.OS;
import com.sheepit.client.standalone.GuiSwing;
import com.sheepit.client.standalone.GuiSwing.ActivityType;
@@ -57,6 +57,7 @@ public class Working implements Activity {
private GuiSwing parent;
private JLabel statusContent;
+ private String previousStatus;
private JLabel renderedFrameContent;
private JLabel remainingFrameContent;
private JLabel lastRenderTime;
@@ -69,15 +70,19 @@ public class Working implements Activity {
private JLabel currrent_project_progression_value;
private JLabel current_project_compute_method_value;
private JLabel user_info_points_total_value;
+ private JLabel renderable_projects_value;
private JLabel waiting_projects_value;
private JLabel connected_machines_value;
private JLabel user_info_total_rendertime_this_session_value;
+ private JLabel userInfoQueuedUploadsAndSizeValue;
+ private String currentTheme;
+ private Log log;
public Working(GuiSwing parent_) {
parent = parent_;
statusContent = new JLabel("Init");
- renderedFrameContent = new JLabel("");
+ renderedFrameContent = new JLabel("0");
remainingFrameContent = new JLabel("");
creditEarned = new JLabel("");
current_project_name_value = new JLabel("");
@@ -85,15 +90,47 @@ public Working(GuiSwing parent_) {
currrent_project_progression_value = new JLabel("");
current_project_compute_method_value = new JLabel("");
user_info_points_total_value = new JLabel("");
+ renderable_projects_value = new JLabel("");
waiting_projects_value = new JLabel("");
connected_machines_value = new JLabel("");
user_info_total_rendertime_this_session_value = new JLabel("");
lastRenderTime = new JLabel("");
lastRender = new JLabel("");
+ userInfoQueuedUploadsAndSizeValue = new JLabel("0");
+ currentTheme = UIManager.getLookAndFeel().getName(); // Capture the theme on component instantiation
+ previousStatus = "";
+ log = Log.getInstance(parent_.getConfiguration());
}
- @Override
- public void show() {
+ @Override public void show() {
+ // If the stored theme and the UIManager's theme doesn't match is bc the user has changed it
+ if (!currentTheme.equals(UIManager.getLookAndFeel().getName())) {
+ // And, as the user has changed the theme, then we must recreate all the UI elements with session data
+ // Reason being they are defined as class variables and therefore created once when the object
+ // is created the first time.
+ // As the Java swing engine applies the "look & feel" at creation time, we need to "re-create" the
+ // objects to ensure they have the right theme colors.
+ statusContent = new JLabel(statusContent.getText());
+ renderedFrameContent = new JLabel(renderedFrameContent.getText());
+ remainingFrameContent = new JLabel(remainingFrameContent.getText());
+ creditEarned = new JLabel(creditEarned.getText());
+ current_project_name_value = new JLabel(current_project_name_value.getText());
+ current_project_duration_value = new JLabel(current_project_duration_value.getText());
+ currrent_project_progression_value = new JLabel(currrent_project_progression_value.getText());
+ current_project_compute_method_value = new JLabel(current_project_compute_method_value.getText());
+ user_info_points_total_value = new JLabel(user_info_points_total_value.getText());
+ renderable_projects_value = new JLabel(renderable_projects_value.getText());
+ waiting_projects_value = new JLabel(waiting_projects_value.getText());
+ connected_machines_value = new JLabel(connected_machines_value.getText());
+ user_info_total_rendertime_this_session_value = new JLabel(user_info_total_rendertime_this_session_value.getText());
+ lastRenderTime = new JLabel(lastRenderTime.getText());
+ lastRender = new JLabel(lastRender.getText());
+ userInfoQueuedUploadsAndSizeValue = new JLabel(userInfoQueuedUploadsAndSizeValue.getText());
+
+ // set the new theme as the current one
+ currentTheme = UIManager.getLookAndFeel().getName();
+ }
+
// current project
JPanel current_project_panel = new JPanel(new SpringLayout());
current_project_panel.setBorder(BorderFactory.createTitledBorder("Project"));
@@ -125,7 +162,9 @@ public void show() {
JLabel user_info_credits_this_session = new JLabel("Points earned: ", JLabel.TRAILING);
JLabel user_info_total_rendertime_this_session = new JLabel("Duration: ", JLabel.TRAILING);
+ JLabel user_info_pending_uploads_and_size = new JLabel("Queued uploads: ", JLabel.TRAILING);
JLabel user_info_rendered_frame_this_session = new JLabel("Rendered frames: ", JLabel.TRAILING);
+ JLabel global_static_renderable_project = new JLabel("Renderable projects: ", JLabel.TRAILING);
session_info_panel.add(user_info_credits_this_session);
session_info_panel.add(creditEarned);
@@ -133,6 +172,12 @@ public void show() {
session_info_panel.add(user_info_rendered_frame_this_session);
session_info_panel.add(renderedFrameContent);
+ session_info_panel.add(user_info_pending_uploads_and_size);
+ session_info_panel.add(userInfoQueuedUploadsAndSizeValue);
+
+ session_info_panel.add(global_static_renderable_project);
+ session_info_panel.add(renderable_projects_value);
+
session_info_panel.add(user_info_total_rendertime_this_session);
session_info_panel.add(user_info_total_rendertime_this_session_value);
@@ -142,7 +187,7 @@ public void show() {
JLabel global_stats_machine_connected = new JLabel("Machines connected: ", JLabel.TRAILING);
JLabel global_stats_remaining_frame = new JLabel("Remaining frames: ", JLabel.TRAILING);
- JLabel global_stats_waiting_project = new JLabel("Remaining projects: ", JLabel.TRAILING);
+ JLabel global_stats_waiting_project = new JLabel("Active projects: ", JLabel.TRAILING);
JLabel global_stats_user_points = new JLabel("User's points: ", JLabel.TRAILING);
global_stats_panel.add(global_stats_waiting_project);
@@ -160,14 +205,14 @@ public void show() {
// last frame
JPanel last_frame_panel = new JPanel();
last_frame_panel.setLayout(new BoxLayout(last_frame_panel, BoxLayout.Y_AXIS));
- last_frame_panel.setBorder(BorderFactory.createTitledBorder("Last rendered frame"));
+ last_frame_panel.setBorder(BorderFactory.createTitledBorder("Last uploaded frame"));
lastRender.setIcon(new ImageIcon(new BufferedImage(200, 120, BufferedImage.TYPE_INT_ARGB)));
lastRender.setAlignmentX(Component.CENTER_ALIGNMENT);
lastRenderTime.setAlignmentX(Component.CENTER_ALIGNMENT);
last_frame_panel.add(lastRenderTime);
last_frame_panel.add(lastRender);
- ImageIcon image = new ImageIcon(getClass().getResource("/title.png"));
+ ImageIcon image = new ImageIcon(getClass().getResource("/sheepit-logo.png"));
JLabel labelImage = new JLabel(image);
labelImage.setAlignmentX(Component.CENTER_ALIGNMENT);
parent.getContentPane().add(labelImage);
@@ -177,10 +222,10 @@ public void show() {
JButton settingsButton = new JButton("Settings");
settingsButton.addActionListener(new SettingsAction());
- pauseButton = new JButton("Pause");
+ pauseButton = new JButton("Pause getting new jobs");
Client client = parent.getClient();
if (client != null && client.isSuspended()) {
- pauseButton.setText("Resume");
+ pauseButton.setText("Resume getting jobs");
}
pauseButton.addActionListener(new PauseAction());
@@ -188,7 +233,7 @@ public void show() {
JButton blockJob = new JButton("Block this project");
blockJob.addActionListener(new blockJobAction());
- exitAfterFrame = new JButton("Exit after this frame");
+ exitAfterFrame = new JButton("Exit");
exitAfterFrame.addActionListener(new ExitAfterAction());
buttonsPanel.add(settingsButton);
@@ -202,22 +247,44 @@ public void show() {
global_constraints.weightx = 1;
global_constraints.gridx = 0;
+ parent.getContentPane().add(new JLabel(" "), global_constraints); // Add a separator between logo and first panel
parent.getContentPane().add(current_project_panel, global_constraints);
parent.getContentPane().add(global_stats_panel, global_constraints);
parent.getContentPane().add(session_info_panel, global_constraints);
parent.getContentPane().add(last_frame_panel, global_constraints);
+ parent.getContentPane().add(new JLabel(" "), global_constraints); // Add a separator between last panel and buttons
parent.getContentPane().add(buttonsPanel, global_constraints);
Spring widthLeftColumn = getBestWidth(current_project_panel, 4, 2);
widthLeftColumn = Spring.max(widthLeftColumn, getBestWidth(global_stats_panel, 4, 2));
- widthLeftColumn = Spring.max(widthLeftColumn, getBestWidth(session_info_panel, 3, 2));
+ widthLeftColumn = Spring.max(widthLeftColumn, getBestWidth(session_info_panel, 4, 2));
alignPanel(current_project_panel, 5, 2, widthLeftColumn);
alignPanel(global_stats_panel, 4, 2, widthLeftColumn);
- alignPanel(session_info_panel, 3, 2, widthLeftColumn);
+ alignPanel(session_info_panel, 5, 2, widthLeftColumn);
+
+ // Set the proper size for the Working (if coming from Settings screen, the window size will be too big for the content!)
+ parent.setSize(520, 760);
}
public void setStatus(String msg_) {
- statusContent.setText("" + msg_ + "");
+ setStatus(msg_, false);
+ }
+
+ public void setStatus(String msg_, boolean overwriteSuspendedMsg) {
+ Client client = parent.getClient();
+
+ if (!msg_.equals("")) {
+ this.previousStatus = msg_;
+ }
+
+ if (client != null && client.isSuspended()) {
+ if (overwriteSuspendedMsg) {
+ statusContent.setText("" + msg_ + "");
+ }
+ }
+ else {
+ statusContent.setText("" + msg_ + "");
+ }
}
public void setRenderingProjectName(String msg_) {
@@ -241,18 +308,41 @@ public void displayStats(Stats stats) {
remainingFrameContent.setText(df.format(stats.getRemainingFrame()));
creditEarned.setText(df.format(stats.getCreditsEarnedDuringSession()));
user_info_points_total_value.setText(df.format(stats.getCreditsEarned()));
+ renderable_projects_value.setText(df.format(stats.getRenderableProject()));
waiting_projects_value.setText(df.format(stats.getWaitingProject()));
connected_machines_value.setText(df.format(stats.getConnectedMachine()));
+
updateTime();
}
+ public void displayUploadQueueStats(int queueSize, long queueVolume) {
+ userInfoQueuedUploadsAndSizeValue.setText(
+ String.format("%d%s%s", queueSize, (queueSize > 0 ? String.format(" (%.2fMB) ", (queueVolume / 1024.0 / 1024.0)) : ""),
+ (queueSize == this.parent.getConfiguration().getMaxUploadingJob() ? "- Queue full!" : "")));
+
+ // If the user has requested to exit, then we need to update the JButton with the queue size
+ if (this.exitAfterFrame.getText().startsWith("Cancel")) {
+ Client client = parent.getClient();
+
+ if (client != null) {
+ if (client.isRunning()) {
+ queueSize++;
+ }
+ }
+
+ exitAfterFrame.setText(String.format("Cancel exit (%s frame%s to go)", queueSize, (queueSize > 1 ? "s" : "")));
+ }
+ }
+
public void updateTime() {
if (this.parent.getClient().getStartTime() != 0) {
- user_info_total_rendertime_this_session_value.setText(Utils.humanDuration(new Date((new Date().getTime() - this.parent.getClient().getStartTime()))));
+ user_info_total_rendertime_this_session_value
+ .setText(Utils.humanDuration(new Date((new Date().getTime() - this.parent.getClient().getStartTime()))));
}
Job job = this.parent.getClient().getRenderingJob();
if (job != null && job.getProcessRender() != null && job.getProcessRender().getStartTime() > 0) {
- current_project_duration_value.setText("" + Utils.humanDuration(new Date((new Date().getTime() - job.getProcessRender().getStartTime()))) + "");
+ current_project_duration_value
+ .setText("" + Utils.humanDuration(new Date((new Date().getTime() - job.getProcessRender().getStartTime()))) + "");
}
else {
current_project_duration_value.setText("");
@@ -268,26 +358,43 @@ public void showLastRender() {
Client client = parent.getClient();
if (client != null) {
Job lastJob = client.getPreviousJob();
- Server server = client.getServer();
- if (server != null) {
- byte[] data = server.getLastRender();
- if (data != null) {
- InputStream is = new ByteArrayInputStream(data);
+ if (lastJob != null) {
+ ImageIcon icon = null;
+ int idInt = Integer.parseInt(lastJob.getId());
+ if (idInt == 1) {
+ icon = new ImageIcon(getClass().getResource("/frame_compute_method.jpg"));
+ }
+ else if (idInt < 20) {
+ icon = new ImageIcon(getClass().getResource("/frame_power_detection.jpg"));
+ }
+ else {
try {
- BufferedImage image = ImageIO.read(is);
- if (image != null) {
- lastRender.setIcon(new ImageIcon(image));
- if (lastJob != null) {
- // don't use lastJob.getProcessRender().getDuration() due to timezone
- if (lastJob.getProcessRender().getDuration() > 1) {
- lastRenderTime.setText("Render time : " + Utils.humanDuration(new Date(lastJob.getProcessRender().getEndTime() - lastJob.getProcessRender().getStartTime())));
- }
- }
+ String path = lastJob.getOutputImagePath();
+
+ BufferedImage img = ImageIO.read(new File(path));
+ float width = img.getWidth();
+ float height = img.getHeight();
+ float factor = 1.0f;
+ if (height > 200) {
+ factor = 200f / height;
}
+ if (width * factor > 200) {
+ factor = Math.min(factor, 200f / width);
+ }
+ icon = new ImageIcon(img.getScaledInstance((int) (width * factor), (int) (height * factor), Image.SCALE_FAST));
}
catch (IOException e) {
- System.out.println("Working::showLastRender() exception " + e);
- e.printStackTrace();
+ log.error(String.format("Working::showLastRender() Unable to load/preview rendered frame [%s]. Exception %s",
+ lastJob.getOutputImagePath(), e.getMessage()));
+ }
+ }
+
+ if (icon != null) {
+ lastRender.setIcon(icon);
+ // don't use lastJob.getProcessRender().getDuration() due to timezone
+ if (lastJob.getProcessRender().getDuration() > 1) {
+ lastRenderTime.setText("Render time : " + Utils
+ .humanDuration(new Date(lastJob.getProcessRender().getEndTime() - lastJob.getProcessRender().getStartTime())));
}
}
}
@@ -355,25 +462,25 @@ private SpringLayout.Constraints getConstraintsForCell(int row, int col, Contain
class PauseAction implements ActionListener {
- @Override
- public void actionPerformed(ActionEvent e) {
+ @Override public void actionPerformed(ActionEvent e) {
Client client = parent.getClient();
if (client != null) {
if (client.isSuspended()) {
- pauseButton.setText("Pause");
+ pauseButton.setText("Pause getting new jobs");
client.resume();
+ setStatus(previousStatus);
}
else {
- pauseButton.setText("Resume");
+ pauseButton.setText("Resume getting jobs");
client.suspend();
+ setStatus("");
}
}
}
}
class SettingsAction implements ActionListener {
- @Override
- public void actionPerformed(ActionEvent e) {
+ @Override public void actionPerformed(ActionEvent e) {
if (parent != null) {
parent.showActivity(ActivityType.SETTINGS);
}
@@ -381,16 +488,32 @@ public void actionPerformed(ActionEvent e) {
}
class ExitAfterAction implements ActionListener {
- @Override
- public void actionPerformed(ActionEvent e) {
+ @Override public void actionPerformed(ActionEvent e) {
Client client = parent.getClient();
if (client != null) {
if (client.isRunning()) {
- exitAfterFrame.setText("Cancel exit");
- client.askForStop();
+ String[] exitJobOptions = { "Exit after current Jobs", "Exit Immediately", "Do Nothing" };
+ int jobsQueueSize = client.getUploadQueueSize() + (client.isRunning() ? 1 : 0);
+
+ int userDecision = JOptionPane.showOptionDialog(null, String.format(
+ "You have %d frame%s being uploaded or rendered. Do you want to finish the jobs or exit now?.\n\n",
+ jobsQueueSize, // Add the current frame to the total count ONLY if the client is running
+ (jobsQueueSize > 1 ? "s" : ""), (jobsQueueSize > 1 ? (jobsQueueSize + " ") : ""), (jobsQueueSize > 1 ? "s" : "")),
+ "Exit Now or Later", JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, exitJobOptions,
+ exitJobOptions[2]); // Make the "Do nothing" button the default one to avoid mistakes
+
+ if (userDecision == 0) {
+ exitAfterFrame.setText(String.format("Cancel exit (%s frame%s to go)", jobsQueueSize, (jobsQueueSize > 1 ? "s" : "")));
+
+ client.askForStop();
+ }
+ else if (userDecision == 1) {
+ client.stop();
+ System.exit(0);
+ }
}
else {
- exitAfterFrame.setText("Exit after this frame");
+ exitAfterFrame.setText("Exit");
client.cancelStop();
}
}
@@ -398,8 +521,7 @@ public void actionPerformed(ActionEvent e) {
}
class blockJobAction implements ActionListener {
- @Override
- public void actionPerformed(ActionEvent e) {
+ @Override public void actionPerformed(ActionEvent e) {
Client client = parent.getClient();
if (client != null) {
Job job = client.getRenderingJob();
@@ -409,5 +531,4 @@ public void actionPerformed(ActionEvent e) {
}
}
}
-
}
diff --git a/src/com/sheepit/client/standalone/swing/components/CollapsibleJPanel.java b/src/com/sheepit/client/standalone/swing/components/CollapsibleJPanel.java
new file mode 100644
index 00000000..facd97f5
--- /dev/null
+++ b/src/com/sheepit/client/standalone/swing/components/CollapsibleJPanel.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2015 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.swing.components;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.LayoutManager;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseEvent;
+import javax.swing.BorderFactory;
+
+import javax.swing.JPanel;
+import javax.swing.border.Border;
+import javax.swing.border.TitledBorder;
+
+public class CollapsibleJPanel extends JPanel {
+
+ private boolean isCompnentsVisible = true;
+ private int originalHeight;
+ private String borderTitle = "";
+ private int COLLAPSED_HEIGHT = 22;
+ private boolean[] originalVisibilty;
+
+ public CollapsibleJPanel(LayoutManager layoutManager) {
+ setLayout(layoutManager);
+ addMouseListener(new onClickHandler());
+ }
+
+ public void setCollapsed(boolean aFlag) {
+ if (aFlag) {
+ hideComponents();
+ }
+ else {
+ showComponents();
+ }
+ }
+
+ public void toggleCollapsed() {
+ if (isCompnentsVisible) {
+ setCollapsed(true);
+ }
+ else {
+ setCollapsed(false);
+ }
+ }
+
+ private void hideComponents() {
+
+ Component[] components = getComponents();
+
+ originalVisibilty = new boolean[components.length];
+
+ // Hide all componens on panel
+ for (int i = 0; i < components.length; i++) {
+ originalVisibilty[i] = components[i].isVisible();
+ components[i].setVisible(false);
+ }
+
+ setHeight(COLLAPSED_HEIGHT);
+
+ // Add '+' char to end of border title
+ //setBorder(BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(), borderTitle + " + "));
+ setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), " + " + borderTitle));
+
+ // Update flag
+ isCompnentsVisible = false;
+ }
+
+ private void showComponents() {
+
+ Component[] components = getComponents();
+
+ // Set all components in panel to visible
+ for (int i = 0; i < components.length; i++) {
+ components[i].setVisible(originalVisibilty[i]);
+ }
+
+ setHeight(originalHeight);
+
+ // Add '-' char to end of border title
+ setBorder(BorderFactory.createTitledBorder(" - " + borderTitle));
+
+ // Update flag
+ isCompnentsVisible = true;
+ }
+
+ private void setHeight(int height) {
+ setPreferredSize(new Dimension(getPreferredSize().width, height));
+ setMinimumSize(new Dimension(getMinimumSize().width, height));
+ setMaximumSize(new Dimension(getMaximumSize().width, height));
+ }
+
+ @Override public Component add(Component component) { // Need this to get the original height of panel
+
+ Component returnComponent = super.add(component);
+
+ originalHeight = getPreferredSize().height;
+
+ return returnComponent;
+ }
+
+ @Override public void setBorder(Border border) { // Need this to get the border title
+
+ if (border instanceof TitledBorder && (borderTitle == "")) {
+ borderTitle = ((TitledBorder) border).getTitle();
+
+ ((TitledBorder) border).setTitle(" - " + borderTitle);
+ }
+
+ super.setBorder(border);
+ }
+
+ public class onClickHandler implements MouseListener {
+
+ @Override public void mouseClicked(MouseEvent e) {
+ }
+
+ @Override public void mousePressed(MouseEvent e) {
+ if (e.getPoint().y < COLLAPSED_HEIGHT) { // Only if click is on top of panel
+ ((CollapsibleJPanel) e.getComponent()).toggleCollapsed();
+ }
+ }
+
+ @Override public void mouseEntered(MouseEvent e) {
+ }
+
+ @Override public void mouseExited(MouseEvent e) {
+ }
+
+ @Override public void mouseReleased(MouseEvent e) {
+ }
+
+ }
+}
diff --git a/src/com/sheepit/client/standalone/text/CLIInputActionHandler.java b/src/com/sheepit/client/standalone/text/CLIInputActionHandler.java
index f5d4d84c..039bb69b 100644
--- a/src/com/sheepit/client/standalone/text/CLIInputActionHandler.java
+++ b/src/com/sheepit/client/standalone/text/CLIInputActionHandler.java
@@ -2,7 +2,7 @@
* Copyright (C) 2017 Laurent CLOUET
* Author Rolf Aretz Lap
*
- * This program is free software; you can redistribute it and/or
+ * 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.
@@ -25,8 +25,7 @@
public class CLIInputActionHandler implements CLIInputListener {
- @Override
- public void commandEntered(Client client, String command) {
+ @Override public void commandEntered(Client client, String command) {
int priorityLength = "priority".length();
//prevent Null Pointer at next step
@@ -89,7 +88,7 @@ void changePriority(Client client, String newPriority) {
}
}
-
+
void displayStatus(Client client) {
if (client.isSuspended()) {
System.out.println("Status: paused");
diff --git a/src/com/sheepit/client/standalone/text/CLIInputListener.java b/src/com/sheepit/client/standalone/text/CLIInputListener.java
index 89a67b81..090aacd6 100644
--- a/src/com/sheepit/client/standalone/text/CLIInputListener.java
+++ b/src/com/sheepit/client/standalone/text/CLIInputListener.java
@@ -2,7 +2,7 @@
* Copyright (C) 2017 Laurent CLOUET
* Author Rolf Aretz Lap
*
- * This program is free software; you can redistribute it and/or
+ * 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.
diff --git a/src/com/sheepit/client/standalone/text/CLIInputObserver.java b/src/com/sheepit/client/standalone/text/CLIInputObserver.java
index ea70f5fd..593e3ec4 100644
--- a/src/com/sheepit/client/standalone/text/CLIInputObserver.java
+++ b/src/com/sheepit/client/standalone/text/CLIInputObserver.java
@@ -2,7 +2,7 @@
* Copyright (C) 2017 Laurent CLOUET
* Author Rolf Aretz Lap
*
- * This program is free software; you can redistribute it and/or
+ * 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.