2014-11-20 13:21:19 +00:00
/ *
* Copyright ( C ) 2010 - 2014 Laurent CLOUET
* Author Laurent CLOUET < laurent . clouet @nopnop.net >
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; version 2
* of the License .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 , USA .
* /
package com.sheepit.client ;
import java.io.File ;
import java.io.FileOutputStream ;
import java.io.IOException ;
import java.io.PrintWriter ;
import java.io.StringWriter ;
import java.util.ArrayList ;
import java.util.Calendar ;
import java.util.Date ;
2019-08-07 18:40:02 +02:00
import java.util.Observable ;
import java.util.Observer ;
2014-11-20 13:21:19 +00:00
import java.util.concurrent.ArrayBlockingQueue ;
import java.util.concurrent.BlockingQueue ;
2018-03-24 20:25:18 +01:00
import java.util.concurrent.ThreadLocalRandom ;
2014-11-20 13:21:19 +00:00
import com.sheepit.client.Error.ServerCode ;
import com.sheepit.client.Error.Type ;
import com.sheepit.client.exception.FermeException ;
2016-09-11 13:46:12 +02:00
import com.sheepit.client.exception.FermeExceptionBadResponseFromServer ;
2017-06-06 13:38:48 +02:00
import com.sheepit.client.exception.FermeExceptionNoRendererAvailable ;
2014-11-20 13:21:19 +00:00
import com.sheepit.client.exception.FermeExceptionNoRightToRender ;
import com.sheepit.client.exception.FermeExceptionNoSession ;
2016-05-02 13:26:11 +02:00
import com.sheepit.client.exception.FermeExceptionNoSpaceLeftOnDevice ;
2016-03-16 14:07:42 +01:00
import com.sheepit.client.exception.FermeExceptionServerInMaintenance ;
import com.sheepit.client.exception.FermeExceptionServerOverloaded ;
2014-11-20 13:21:19 +00:00
import com.sheepit.client.exception.FermeExceptionSessionDisabled ;
2015-07-08 19:44:38 +01:00
import com.sheepit.client.exception.FermeServerDown ;
2014-11-20 13:21:19 +00:00
import com.sheepit.client.os.OS ;
2019-08-07 22:17:59 +02:00
import lombok.Data ;
@Data
2014-11-20 13:21:19 +00:00
public class Client {
private Gui gui ;
private Server server ;
2019-08-07 22:17:59 +02:00
private Configuration configuration ;
2014-11-20 13:21:19 +00:00
private Log log ;
private Job renderingJob ;
2016-10-31 15:27:20 +01:00
private Job previousJob ;
2014-11-20 13:21:19 +00:00
private BlockingQueue < Job > jobsToValidate ;
private boolean isValidatingJob ;
2019-08-07 22:17:59 +02:00
private long startTime ;
2014-11-20 13:21:19 +00:00
private boolean disableErrorSending ;
private boolean running ;
2014-12-02 20:28:03 +00:00
private boolean suspended ;
2014-11-20 13:21:19 +00:00
2015-05-05 18:35:25 +02:00
private int maxDownloadFileAttempts = 5 ;
2020-04-20 01:01:43 +10:00
private int uploadQueueSize ;
private long uploadQueueVolume ;
2019-08-07 22:17:59 +02:00
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 ) ;
2014-11-20 13:21:19 +00:00
this . gui = gui_ ;
this . renderingJob = null ;
2016-10-31 15:27:20 +01:00
this . previousJob = null ;
2014-11-20 13:21:19 +00:00
this . jobsToValidate = new ArrayBlockingQueue < Job > ( 1024 ) ;
this . isValidatingJob = false ;
this . disableErrorSending = false ;
2016-02-08 13:06:33 +01:00
this . running = false ;
2014-12-02 20:28:03 +00:00
this . suspended = false ;
2020-04-20 01:01:43 +10:00
this . uploadQueueSize = 0 ;
this . uploadQueueVolume = 0 ;
2014-11-20 13:21:19 +00:00
}
public String toString ( ) {
2019-08-07 22:17:59 +02:00
return String . format ( " Client (configuration %s, server %s) " , this . configuration , this . server ) ;
2016-10-12 00:34:51 +02:00
}
2014-11-20 13:21:19 +00:00
public int run ( ) {
2019-08-07 22:17:59 +02:00
if ( this . configuration . checkOSisSupported ( ) = = false ) {
2014-12-17 21:47:31 +00:00
this . gui . error ( Error . humanString ( Error . Type . OS_NOT_SUPPORTED ) ) ;
return - 3 ;
}
2019-08-07 22:17:59 +02:00
if ( this . configuration . checkCPUisSupported ( ) = = false ) {
2014-12-17 21:47:31 +00:00
this . gui . error ( Error . humanString ( Error . Type . CPU_NOT_SUPPORTED ) ) ;
return - 4 ;
}
2016-02-08 13:06:33 +01:00
this . running = true ;
2014-11-20 13:21:19 +00:00
int step ;
try {
step = this . log . newCheckPoint ( ) ;
this . gui . status ( " Starting " ) ;
2019-08-07 22:17:59 +02:00
this . configuration . cleanWorkingDirectory ( ) ;
2014-11-20 13:21:19 +00:00
Error . Type ret ;
ret = this . server . getConfiguration ( ) ;
if ( ret ! = Error . Type . OK ) {
2014-12-16 23:02:14 +00:00
this . gui . error ( Error . humanString ( ret ) ) ;
2014-11-20 13:21:19 +00:00
if ( ret ! = Error . Type . AUTHENTICATION_FAILED ) {
Log . printCheckPoint ( step ) ;
}
return - 1 ;
}
2019-08-07 22:17:59 +02:00
this . startTime = new Date ( ) . getTime ( ) ;
2014-11-20 13:21:19 +00:00
this . server . start ( ) ; // for staying alive
2014-12-23 20:05:29 +01:00
// create a thread which will send the frame
2014-11-20 13:21:19 +00:00
Runnable runnable_sender = new Runnable ( ) {
public void run ( ) {
senderLoop ( ) ;
}
} ;
Thread thread_sender = new Thread ( runnable_sender ) ;
thread_sender . start ( ) ;
2020-04-27 22:28:04 +10:00
do {
while ( this . running = = true ) {
this . renderingJob = null ;
synchronized ( this ) {
if ( this . suspended ) {
this . gui . status ( " Client paused " , true ) ;
}
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 ) ) ;
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 ( ) ;
2020-04-26 23:35:05 +10:00
}
2020-04-27 22:28:04 +10:00
catch ( FermeExceptionNoRightToRender e ) {
this . gui . error ( " User does not have enough right to render scene " ) ;
return - 2 ;
2014-12-02 20:28:03 +00:00
}
2020-04-27 22:28:04 +10:00
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 ) {
}
2019-03-06 21:12:08 +01:00
}
2020-04-27 22:28:04 +10:00
}
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 ) {
}
2014-11-20 13:21:19 +00:00
}
2020-04-27 22:28:04 +10:00
}
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 ;
2014-11-20 13:21:19 +00:00
}
2020-04-27 22:28:04 +10:00
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 ;
}
2014-11-20 13:21:19 +00:00
}
}
2020-04-27 22:28:04 +10:00
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 ) ) ;
2014-11-20 13:21:19 +00:00
try {
2020-04-27 22:28:04 +10:00
Thread . sleep ( time_sleep ) ;
2014-11-20 13:21:19 +00:00
}
catch ( InterruptedException e1 ) {
2020-04-27 22:28:04 +10:00
return - 3 ;
2014-11-20 13:21:19 +00:00
}
2020-04-27 22:28:04 +10:00
continue ; // go back to ask job
2014-11-20 13:21:19 +00:00
}
2020-04-27 22:28:04 +10:00
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 ) ) ;
2017-06-06 13:38:48 +02:00
try {
2020-04-27 22:28:04 +10:00
Thread . sleep ( time_sleep ) ;
2017-06-06 13:38:48 +02:00
}
catch ( InterruptedException e1 ) {
2020-04-27 22:28:04 +10:00
return - 3 ;
2017-06-06 13:38:48 +02:00
}
2020-04-27 22:28:04 +10:00
continue ; // go back to ask job
2017-06-06 13:38:48 +02:00
}
2020-04-27 22:28:04 +10:00
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 ) ) ;
2014-11-20 13:21:19 +00:00
try {
2020-04-27 22:28:04 +10:00
Thread . sleep ( time_sleep ) ;
2014-11-20 13:21:19 +00:00
}
2020-04-27 22:28:04 +10:00
catch ( InterruptedException e1 ) {
return - 3 ;
2014-11-20 13:21:19 +00:00
}
2020-04-27 22:28:04 +10:00
continue ; // go back to ask job
2014-11-20 13:21:19 +00:00
}
2020-04-27 22:28:04 +10:00
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 ) ;
}
catch ( InterruptedException e1 ) {
return - 3 ;
}
continue ; // go back to ask job
2016-03-16 14:07:42 +01:00
}
2020-04-27 22:28:04 +10:00
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 ;
2016-03-16 14:07:42 +01:00
}
2020-04-27 22:28:04 +10:00
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 . suspended = true ;
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 . suspended = false ;
continue ; // go back to ask job
2016-03-16 14:07:42 +01:00
}
2020-04-27 22:28:04 +10:00
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 ;
2016-03-16 14:07:42 +01:00
}
2020-04-27 22:28:04 +10:00
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 ;
2015-07-08 19:44:38 +01:00
}
2020-04-27 22:28:04 +10:00
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 ;
2015-07-08 19:44:38 +01:00
}
2020-04-27 22:28:04 +10:00
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 ) ;
if ( ret ! = Error . Type . OK ) {
gui . error ( " Client::run problem with confirmJob (returned " + ret + " ) " ) ;
sendError ( step ) ;
}
2016-09-11 13:46:12 +02:00
}
2020-04-27 22:28:04 +10:00
else {
this . gui . status ( String . format ( " Queuing frame for upload (%.2fMB) " ,
( this . renderingJob . getOutputImageSize ( ) / 1024 . 0 / 1024 . 0 )
) ) ;
this . jobsToValidate . add ( this . renderingJob ) ;
this . uploadQueueSize + + ;
this . uploadQueueVolume + = this . renderingJob . getOutputImageSize ( ) ;
this . gui . displayUploadQueueStats ( uploadQueueSize , uploadQueueVolume ) ;
this . renderingJob = null ;
2016-09-11 13:46:12 +02:00
}
2020-04-27 22:28:04 +10:00
while ( this . shouldWaitBeforeRender ( ) = = true ) {
2014-11-20 13:21:19 +00:00
try {
2020-04-27 22:28:04 +10:00
Thread . sleep ( 4000 ) ; // wait a little bit
this . gui . status ( " Sending frames. Please wait " ) ;
2014-11-20 13:21:19 +00:00
}
2020-04-27 22:28:04 +10:00
catch ( InterruptedException e3 ) {
2014-11-20 13:21:19 +00:00
}
}
this . log . removeCheckPoint ( step ) ;
}
2020-04-27 22:28:04 +10:00
// 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
2014-11-20 13:21:19 +00:00
try {
Thread . sleep ( 2300 ) ; // wait a little bit
2020-04-27 22:28:04 +10:00
this . gui . status ( " Uploading rendered frames before exiting. Please wait " ) ;
2014-11-20 13:21:19 +00:00
}
catch ( InterruptedException e3 ) {
}
2020-04-27 22:28:04 +10:00
// This loop will remain valid until all the background uploads have
// finished (unless the stop() method has been triggered)
} while ( this . uploadQueueSize > 0 ) ;
2014-11-20 13:21:19 +00:00
}
catch ( Exception e1 ) {
2014-12-16 23:02:14 +00:00
// no exception should be raised in the actual launcher (applet or standalone)
2015-04-30 20:51:04 +01:00
StringWriter sw = new StringWriter ( ) ;
PrintWriter pw = new PrintWriter ( sw ) ;
e1 . printStackTrace ( pw ) ;
this . log . debug ( " Client::run exception(D) " + e1 + " stacktrace: " + sw . toString ( ) ) ;
2014-11-20 13:21:19 +00:00
return - 99 ; // the this.stop will be done after the return of this.run()
}
2015-08-05 19:58:11 +01:00
this . gui . stop ( ) ;
2014-11-20 13:21:19 +00:00
return 0 ;
}
public synchronized int stop ( ) {
this . running = false ;
this . disableErrorSending = true ;
if ( this . renderingJob ! = null ) {
2020-04-11 19:53:55 +10:00
this . gui . status ( " Stopping " ) ;
2015-04-27 20:10:36 +01:00
if ( this . renderingJob . getProcessRender ( ) . getProcess ( ) ! = null ) {
2014-12-03 20:09:50 +00:00
this . renderingJob . setAskForRendererKill ( true ) ;
2018-08-29 15:14:11 +02:00
OS . getOS ( ) . kill ( this . renderingJob . getProcessRender ( ) . getProcess ( ) ) ;
2014-11-20 13:21:19 +00:00
}
}
2019-08-07 22:17:59 +02:00
// this.configuration.workingDirectory.delete();
this . configuration . removeWorkingDirectory ( ) ;
2014-11-20 13:21:19 +00:00
if ( this . server = = null ) {
return 0 ;
}
2020-01-12 14:37:30 +01:00
if ( this . server . getPage ( " logout " ) . isEmpty ( ) = = false ) {
2020-04-11 19:53:55 +10:00
this . gui . status ( " Disconnecting from SheepIt server " ) ;
2020-01-12 14:37:30 +01:00
try {
this . server . HTTPRequest ( this . server . getPage ( " logout " ) ) ;
}
catch ( IOException e ) {
// nothing to do: if the logout failed that's ok
}
2014-11-20 13:21:19 +00:00
}
this . server . interrupt ( ) ;
try {
this . server . join ( ) ;
}
catch ( InterruptedException e ) {
}
this . server = null ;
return 0 ;
}
2014-12-02 20:28:03 +00:00
public void suspend ( ) {
suspended = true ;
2020-04-26 23:35:05 +10:00
this . gui . status ( " Client will pause when the current job finishes " , true ) ;
2014-12-02 20:28:03 +00:00
}
public synchronized void resume ( ) {
suspended = false ;
notify ( ) ;
}
2014-11-20 13:21:19 +00:00
public void askForStop ( ) {
2015-08-05 21:15:39 +01:00
this . log . debug ( " Client::askForStop " ) ;
2014-11-20 13:21:19 +00:00
this . running = false ;
}
2017-01-05 09:35:59 +01:00
2015-08-05 19:58:11 +01:00
public void cancelStop ( ) {
2015-08-05 21:15:39 +01:00
this . log . debug ( " Client::cancelStop " ) ;
2015-08-05 19:58:11 +01:00
this . running = true ;
}
2017-01-05 09:35:59 +01:00
2014-11-20 13:21:19 +00:00
public int senderLoop ( ) {
int step = log . newCheckPoint ( ) ;
Error . Type ret ;
while ( true ) {
Job job_to_send ;
try {
2015-01-23 17:18:17 +00:00
job_to_send = jobsToValidate . take ( ) ;
2014-11-20 13:21:19 +00:00
this . log . debug ( " will validate " + job_to_send ) ;
2020-04-20 01:01:43 +10:00
2014-11-20 13:21:19 +00:00
ret = confirmJob ( job_to_send ) ;
if ( ret ! = Error . Type . OK ) {
2014-12-16 23:02:14 +00:00
this . gui . error ( Error . humanString ( ret ) ) ;
2015-04-23 23:31:39 +01:00
this . log . debug ( " Client::senderLoop confirm failed, ret: " + ret ) ;
2014-11-20 13:21:19 +00:00
sendError ( step ) ;
}
2020-04-20 01:01:43 +10:00
this . uploadQueueSize - - ;
this . uploadQueueVolume - = job_to_send . getOutputImageSize ( ) ;
this . gui . displayUploadQueueStats ( this . uploadQueueSize , this . uploadQueueVolume ) ;
2014-11-20 13:21:19 +00:00
}
catch ( InterruptedException e ) {
}
}
}
protected void sendError ( int step_ ) {
this . sendError ( step_ , null , null ) ;
}
protected void sendError ( int step_ , Job job_to_reset_ , Error . Type error ) {
if ( this . disableErrorSending ) {
2014-12-23 20:05:29 +01:00
this . log . debug ( " Error sending is disabled, do not send log " ) ;
2014-11-20 13:21:19 +00:00
return ;
}
2015-04-29 21:21:17 +01:00
this . log . debug ( " Sending error to server (type: " + error + " ) " ) ;
2014-11-20 13:21:19 +00:00
try {
File temp_file = File . createTempFile ( " farm_ " , " " ) ;
temp_file . createNewFile ( ) ;
temp_file . deleteOnExit ( ) ;
FileOutputStream writer = new FileOutputStream ( temp_file ) ;
ArrayList < String > logs = this . log . getForCheckPoint ( step_ ) ;
for ( String line : logs ) {
writer . write ( line . getBytes ( ) ) ;
writer . write ( '\n' ) ;
}
writer . close ( ) ;
2015-03-11 17:55:44 +00:00
String args = " ?type= " + ( error = = null ? " " : error . getValue ( ) ) ;
2014-11-20 13:21:19 +00:00
if ( job_to_reset_ ! = null ) {
2017-03-29 21:46:10 +02:00
args + = " &frame= " + job_to_reset_ . getFrameNumber ( ) + " &job= " + job_to_reset_ . getId ( ) + " &render_time= " + job_to_reset_ . getProcessRender ( ) . getDuration ( ) + " &memoryused= " + job_to_reset_ . getProcessRender ( ) . getMemoryUsed ( ) ;
2015-01-28 20:14:59 +00:00
if ( job_to_reset_ . getExtras ( ) ! = null & & job_to_reset_ . getExtras ( ) . isEmpty ( ) = = false ) {
2014-11-20 13:21:19 +00:00
args + = " &extras= " + job_to_reset_ . getExtras ( ) ;
}
}
this . server . HTTPSendFile ( this . server . getPage ( " error " ) + args , temp_file . getAbsolutePath ( ) ) ;
temp_file . delete ( ) ;
}
2017-04-19 21:23:12 +02:00
catch ( Exception e ) {
StringWriter sw = new StringWriter ( ) ;
PrintWriter pw = new PrintWriter ( sw ) ;
e . printStackTrace ( pw ) ;
this . log . debug ( " Client::sendError Exception " + e + " stacktrace: " + sw . toString ( ) ) ;
2014-12-23 20:05:29 +01:00
// no exception should be raised to actual launcher (applet or standalone)
2014-11-20 13:21:19 +00:00
}
2016-11-01 19:44:09 +01:00
if ( error ! = null & & ( error = = Error . Type . RENDERER_CRASHED | | error = = Error . Type . RENDERER_KILLED_BY_USER | | error = = Error . Type . RENDERER_KILLED_BY_SERVER ) ) {
2014-11-20 13:21:19 +00:00
// do nothing, we can ask for a job right away
}
else {
try {
Thread . sleep ( 300000 ) ; // sleeping for 5min
}
catch ( InterruptedException e ) {
}
}
}
/ * *
2014-12-23 20:05:29 +01:00
* @return the date of the next request , or null if there is not delay ( null < = > now )
2014-11-20 13:21:19 +00:00
* /
public Calendar nextJobRequest ( ) {
2019-08-07 22:17:59 +02:00
if ( this . configuration . getRequestTime ( ) = = null ) {
2014-11-20 13:21:19 +00:00
return null ;
}
else {
Calendar next = null ;
Calendar now = Calendar . getInstance ( ) ;
2019-08-07 22:17:59 +02:00
for ( Pair < Calendar , Calendar > interval : this . configuration . getRequestTime ( ) ) {
2014-11-20 13:21:19 +00:00
Calendar start = ( Calendar ) now . clone ( ) ;
Calendar end = ( Calendar ) now . clone ( ) ;
start . set ( Calendar . SECOND , 00 ) ;
start . set ( Calendar . MINUTE , interval . first . get ( Calendar . MINUTE ) ) ;
start . set ( Calendar . HOUR_OF_DAY , interval . first . get ( Calendar . HOUR_OF_DAY ) ) ;
end . set ( Calendar . SECOND , 59 ) ;
end . set ( Calendar . MINUTE , interval . second . get ( Calendar . MINUTE ) ) ;
end . set ( Calendar . HOUR_OF_DAY , interval . second . get ( Calendar . HOUR_OF_DAY ) ) ;
if ( start . before ( now ) & & now . before ( end ) ) {
return null ;
}
2019-03-06 21:12:08 +01:00
if ( next = = null | | ( start . before ( next ) & & start . after ( now ) ) ) {
next = start ;
2014-11-20 13:21:19 +00:00
}
}
return next ;
}
}
2019-08-07 18:40:02 +02:00
public Error . Type work ( final Job ajob ) {
2014-11-20 13:21:19 +00:00
int ret ;
2016-10-12 00:34:51 +02:00
gui . setRenderingProjectName ( ajob . getName ( ) ) ;
2016-05-02 13:26:11 +02:00
try {
ret = this . downloadExecutable ( ajob ) ;
if ( ret ! = 0 ) {
2016-10-12 00:34:51 +02:00
gui . setRenderingProjectName ( " " ) ;
2016-05-02 13:26:11 +02:00
this . log . error ( " Client::work problem with downloadExecutable (ret " + ret + " ) " ) ;
return Error . Type . DOWNLOAD_FILE ;
}
ret = this . downloadSceneFile ( ajob ) ;
if ( ret ! = 0 ) {
2016-10-12 00:34:51 +02:00
gui . setRenderingProjectName ( " " ) ;
2016-05-02 13:26:11 +02:00
this . log . error ( " Client::work problem with downloadSceneFile (ret " + ret + " ) " ) ;
return Error . Type . DOWNLOAD_FILE ;
}
ret = this . prepareWorkingDirectory ( ajob ) ; // decompress renderer and scene archives
if ( ret ! = 0 ) {
2016-10-12 00:34:51 +02:00
gui . setRenderingProjectName ( " " ) ;
2016-05-02 13:26:11 +02:00
this . log . error ( " Client::work problem with this.prepareWorkingDirectory (ret " + ret + " ) " ) ;
return Error . Type . CAN_NOT_CREATE_DIRECTORY ;
}
2014-11-20 13:21:19 +00:00
}
2016-05-02 13:26:11 +02:00
catch ( FermeExceptionNoSpaceLeftOnDevice e ) {
2016-10-12 00:34:51 +02:00
gui . setRenderingProjectName ( " " ) ;
2016-05-02 13:26:11 +02:00
return Error . Type . NO_SPACE_LEFT_ON_DEVICE ;
2014-11-20 13:21:19 +00:00
}
2019-08-22 21:34:34 +02:00
final File scene_file = new File ( ajob . getScenePath ( ) ) ;
2014-11-20 13:21:19 +00:00
File renderer_file = new File ( ajob . getRendererPath ( ) ) ;
if ( scene_file . exists ( ) = = false ) {
2016-10-12 00:34:51 +02:00
gui . setRenderingProjectName ( " " ) ;
2019-02-23 11:30:52 +01:00
this . log . error ( " Client::work job preparation failed (scene file ' " + scene_file . getAbsolutePath ( ) + " ' does not exist), cleaning directory in hope to recover " ) ;
2019-08-07 22:17:59 +02:00
this . configuration . cleanWorkingDirectory ( ) ;
2014-11-20 13:21:19 +00:00
return Error . Type . MISSING_SCENE ;
}
if ( renderer_file . exists ( ) = = false ) {
2016-10-12 00:34:51 +02:00
gui . setRenderingProjectName ( " " ) ;
2019-02-23 11:30:52 +01:00
this . log . error ( " Client::work job preparation failed (renderer file ' " + renderer_file . getAbsolutePath ( ) + " ' does not exist), cleaning directory in hope to recover " ) ;
2019-08-07 22:17:59 +02:00
this . configuration . cleanWorkingDirectory ( ) ;
2019-02-09 05:41:46 +01:00
return Error . Type . MISSING_RENDERER ;
2014-11-20 13:21:19 +00:00
}
2019-08-07 18:40:02 +02:00
Observer removeSceneDirectoryOnceRenderHasStartedObserver = new Observer ( ) {
2020-04-27 22:28:04 +10:00
@Override
public void update ( Observable observable , Object o ) {
2019-08-22 21:34:34 +02:00
// 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 ( ) ;
2019-08-07 18:40:02 +02:00
}
} ;
Error . Type err = ajob . render ( removeSceneDirectoryOnceRenderHasStartedObserver ) ;
2016-10-12 00:34:51 +02:00
gui . setRenderingProjectName ( " " ) ;
gui . setRemainingTime ( " " ) ;
gui . setRenderingTime ( " " ) ;
2017-05-07 21:00:20 +02:00
gui . setComputeMethod ( " " ) ;
2014-11-20 13:21:19 +00:00
if ( err ! = Error . Type . OK ) {
this . log . error ( " Client::work problem with runRenderer (ret " + err + " ) " ) ;
2019-02-22 13:53:47 +01:00
if ( err = = Error . Type . RENDERER_CRASHED_PYTHON_ERROR ) {
this . log . error ( " Client::work failed with python error, cleaning directory in hope to recover " ) ;
2019-08-07 22:17:59 +02:00
this . configuration . cleanWorkingDirectory ( ) ;
2019-02-22 13:53:47 +01:00
}
2014-11-20 13:21:19 +00:00
return err ;
}
2019-08-07 17:02:52 +02:00
removeSceneDirectory ( ajob ) ;
2014-11-20 13:21:19 +00:00
return Error . Type . OK ;
}
2016-06-25 13:45:09 +02:00
protected int downloadSceneFile ( Job ajob_ ) throws FermeExceptionNoSpaceLeftOnDevice {
2016-10-12 00:34:51 +02:00
return this . downloadFile ( ajob_ , ajob_ . getSceneArchivePath ( ) , ajob_ . getSceneMD5 ( ) , String . format ( " %s?type=job&job=%s " , this . server . getPage ( " download-archive " ) , ajob_ . getId ( ) ) , " project " ) ;
2014-11-20 13:21:19 +00:00
}
2016-06-25 13:45:09 +02:00
protected int downloadExecutable ( Job ajob ) throws FermeExceptionNoSpaceLeftOnDevice {
2019-08-07 22:17:59 +02:00
return this . downloadFile ( ajob , ajob . getRendererArchivePath ( ) , ajob . getRendererMD5 ( ) , String . format ( " %s?type=binary&job=%s " , this . server . getPage ( " download-archive " ) , ajob . getId ( ) ) , " renderer " ) ;
2015-04-28 18:46:04 +01:00
}
2016-06-25 13:45:09 +02:00
private int downloadFile ( Job ajob , String local_path , String md5_server , String url , String download_type ) throws FermeExceptionNoSpaceLeftOnDevice {
2015-04-28 18:46:04 +01:00
File local_path_file = new File ( local_path ) ;
2016-01-13 09:47:41 +01:00
String update_ui = " Downloading " + download_type + " %s %% " ;
2014-11-20 13:21:19 +00:00
2015-05-05 18:35:25 +02:00
if ( local_path_file . exists ( ) = = true ) {
2016-01-13 09:47:41 +01:00
this . gui . status ( " Reusing cached " + download_type ) ;
2015-05-05 18:35:25 +02:00
return 0 ;
}
2016-01-13 09:47:41 +01:00
this . gui . status ( " Downloading " + download_type ) ;
2015-05-05 18:35:25 +02:00
// must download the archive
int ret = this . server . HTTPGetFile ( url , local_path , this . gui , update_ui ) ;
boolean md5_check = this . checkFile ( ajob , local_path , md5_server ) ;
int attempts = 1 ;
while ( ( ret ! = 0 | | md5_check = = false ) & & attempts < this . maxDownloadFileAttempts ) {
2014-11-20 13:21:19 +00:00
if ( ret ! = 0 ) {
2020-04-11 19:53:55 +10:00
this . gui . error ( String . format ( " Unable to download %s (error %d). Retrying now " , download_type , ret ) ) ;
2016-07-29 23:25:05 +02:00
this . log . debug ( " Client::downloadFile problem with Server.HTTPGetFile (return: " + ret + " ) removing local file (path: " + local_path + " ) " ) ;
2015-05-05 18:35:25 +02:00
}
else if ( md5_check = = false ) {
2020-04-11 19:53:55 +10:00
this . gui . error ( String . format ( " Verification of downloaded %s has failed. Retrying now " , download_type ) ) ;
2015-05-05 18:35:25 +02:00
this . log . debug ( " Client::downloadFile problem with Client::checkFile mismatch on md5, removing local file (path: " + local_path + " ) " ) ;
}
local_path_file . delete ( ) ;
this . log . debug ( " Client::downloadFile failed, let's try again ( " + ( attempts + 1 ) + " / " + this . maxDownloadFileAttempts + " ) ... " ) ;
ret = this . server . HTTPGetFile ( url , local_path , this . gui , update_ui ) ;
md5_check = this . checkFile ( ajob , local_path , md5_server ) ;
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... " ) ;
2015-04-28 18:47:02 +01:00
local_path_file . delete ( ) ;
2014-11-20 13:21:19 +00:00
return - 9 ;
}
}
2015-05-05 18:35:25 +02:00
return 0 ;
}
private boolean checkFile ( Job ajob , String local_path , String md5_server ) {
File local_path_file = new File ( local_path ) ;
if ( local_path_file . exists ( ) = = false ) {
this . log . error ( " Client::checkFile cannot check md5 on a nonexistent file (path: " + local_path + " ) " ) ;
return false ;
}
2015-04-28 18:46:04 +01:00
String md5_local = Utils . md5 ( local_path ) ;
2014-11-20 13:21:19 +00:00
2015-04-28 18:46:04 +01:00
if ( md5_local . equals ( md5_server ) = = false ) {
2015-05-06 20:00:52 +01:00
this . log . error ( " Client::checkFile mismatch on md5 local: ' " + md5_local + " ' server: ' " + md5_server + " ' (local size: " + new File ( local_path ) . length ( ) + " ) " ) ;
2015-05-05 18:35:25 +02:00
return false ;
2014-11-20 13:21:19 +00:00
}
2015-05-05 18:35:25 +02:00
return true ;
2014-11-20 13:21:19 +00:00
}
2019-08-07 17:02:52 +02:00
protected void removeSceneDirectory ( Job ajob ) {
Utils . delete ( new File ( ajob . getSceneDirectory ( ) ) ) ;
}
2014-11-20 13:21:19 +00:00
2016-05-02 13:26:11 +02:00
protected int prepareWorkingDirectory ( Job ajob ) throws FermeExceptionNoSpaceLeftOnDevice {
2014-11-20 13:21:19 +00:00
int ret ;
String renderer_archive = ajob . getRendererArchivePath ( ) ;
String renderer_path = ajob . getRendererDirectory ( ) ;
File renderer_path_file = new File ( renderer_path ) ;
if ( renderer_path_file . exists ( ) ) {
// Directory already exists -> do nothing
}
else {
2016-01-14 14:48:04 +01:00
this . gui . status ( " Extracting renderer " ) ;
2014-11-20 13:21:19 +00:00
// we create the directory
renderer_path_file . mkdir ( ) ;
// unzip the archive
2018-06-30 01:41:54 +02:00
ret = Utils . unzipFileIntoDirectory ( renderer_archive , renderer_path , null , log ) ;
2014-11-20 13:21:19 +00:00
if ( ret ! = 0 ) {
2018-04-22 20:01:53 +02:00
this . log . error ( " Client::prepareWorkingDirectory, error(1) with Utils.unzipFileIntoDirectory( " + renderer_archive + " , " + renderer_path + " ) returned " + ret ) ;
2020-04-11 19:53:55 +10:00
this . gui . error ( String . format ( " Unable to extract the renderer (error %d) " , ret ) ) ;
2014-11-20 13:21:19 +00:00
return - 1 ;
}
2016-10-05 23:01:03 +02:00
try {
File f = new File ( ajob . getRendererPath ( ) ) ;
f . setExecutable ( true ) ;
}
2017-01-05 09:35:59 +01:00
catch ( SecurityException e ) {
2016-10-05 23:01:03 +02:00
}
2014-11-20 13:21:19 +00:00
}
String scene_archive = ajob . getSceneArchivePath ( ) ;
String scene_path = ajob . getSceneDirectory ( ) ;
File scene_path_file = new File ( scene_path ) ;
if ( scene_path_file . exists ( ) ) {
// Directory already exists -> do nothing
}
else {
2016-01-14 14:48:04 +01:00
this . gui . status ( " Extracting project " ) ;
2014-11-20 13:21:19 +00:00
// we create the directory
scene_path_file . mkdir ( ) ;
// unzip the archive
2019-08-07 22:17:59 +02:00
ret = Utils . unzipFileIntoDirectory ( scene_archive , scene_path , ajob . getPassword ( ) , log ) ;
2014-11-20 13:21:19 +00:00
if ( ret ! = 0 ) {
2018-04-22 20:01:53 +02:00
this . log . error ( " Client::prepareWorkingDirectory, error(2) with Utils.unzipFileIntoDirectory( " + scene_archive + " , " + scene_path + " ) returned " + ret ) ;
2020-04-11 19:53:55 +10:00
this . gui . error ( String . format ( " Unable to extract the scene (error %d) " , ret ) ) ;
2014-11-20 13:21:19 +00:00
return - 2 ;
}
}
return 0 ;
}
protected Error . Type confirmJob ( Job ajob ) {
2020-04-14 17:35:54 +02:00
String url_real = String . format ( " %s&rendertime=%d&memoryused=%s " , ajob . getValidationUrl ( ) , ajob . getProcessRender ( ) . getDuration ( ) , ajob . getProcessRender ( ) . getMemoryUsed ( ) ) ;
this . log . debug ( " Client::confirmeJob url " + url_real ) ;
this . log . debug ( " path frame " + ajob . getOutputImagePath ( ) ) ;
2014-11-20 13:21:19 +00:00
this . isValidatingJob = true ;
int nb_try = 1 ;
int max_try = 3 ;
ServerCode ret = ServerCode . UNKNOWN ;
while ( nb_try < max_try & & ret ! = ServerCode . OK ) {
ret = this . server . HTTPSendFile ( url_real , ajob . getOutputImagePath ( ) ) ;
switch ( ret ) {
case OK :
// no issue, exit the loop
nb_try = max_try ;
break ;
case JOB_VALIDATION_ERROR_SESSION_DISABLED :
case JOB_VALIDATION_ERROR_BROKEN_MACHINE :
return Type . SESSION_DISABLED ;
case JOB_VALIDATION_ERROR_MISSING_PARAMETER :
// no point to retry the request
return Error . Type . UNKNOWN ;
default :
// do nothing, try to do a request on the next loop
break ;
}
nb_try + + ;
if ( ret ! = ServerCode . OK & & nb_try < max_try ) {
try {
this . log . debug ( " Sleep for 32s before trying to re-upload the frame " ) ;
Thread . sleep ( 32000 ) ;
}
catch ( InterruptedException e ) {
return Error . Type . UNKNOWN ;
}
}
}
2020-04-14 17:32:22 +02:00
2014-11-20 13:21:19 +00:00
this . isValidatingJob = false ;
2020-04-14 17:32:22 +02:00
this . previousJob = ajob ;
gui . AddFrameRendered ( ) ;
2014-11-20 13:21:19 +00:00
// we can remove the frame file
File frame = new File ( ajob . getOutputImagePath ( ) ) ;
frame . delete ( ) ;
ajob . setOutputImagePath ( null ) ;
2020-04-14 17:32:22 +02:00
2014-11-20 13:21:19 +00:00
return Error . Type . OK ;
}
protected boolean shouldWaitBeforeRender ( ) {
int concurrent_job = this . jobsToValidate . size ( ) ;
if ( this . isValidatingJob ) {
concurrent_job + + ;
}
2019-08-07 22:17:59 +02:00
return ( concurrent_job > = this . configuration . getMaxUploadingJob ( ) ) ;
2014-11-20 13:21:19 +00:00
}
}