_settingsInit(); } /** * sets fields from default-vals * * @param $transfer */ function settingsDefault($transfer = "") { global $cfg; // transfer vars if ($transfer != "") $this->_setVarsForTransfer($transfer); // common vars $this->hash = getTransferHash($this->transfer); $this->datapath = getTransferDatapath($this->transfer); $this->savepath = getTransferSavepath($this->transfer); $this->running = 0; $this->rate = $cfg["max_upload_rate"]; $this->drate = $cfg["max_download_rate"]; $this->maxuploads = $cfg["max_uploads"]; $this->superseeder = $cfg["superseeder"]; $this->runtime = $cfg["die_when_done"]; $this->sharekill = $cfg["sharekill"]; $this->minport = $cfg["minport"]; $this->maxport = $cfg["maxport"]; $this->maxcons = $cfg["maxcons"]; $this->rerequest = $cfg["rerequest_interval"]; } /** * load settings * * @param $transfer * @return boolean */ function settingsLoad($transfer = "") { global $db; // transfer vars if ($transfer != "") $this->_setVarsForTransfer($transfer); // common vars $sql = "SELECT * FROM tf_transfers WHERE transfer = ".$db->qstr($this->transfer); $result = $db->Execute($sql); if ($db->ErrorNo() != 0) dbError($sql); if ($row = $result->FetchRow()) { $this->hash = $row["hash"]; $this->datapath = $row["datapath"]; $this->savepath = $row["savepath"]; $this->running = $row["running"]; $this->rate = $row["rate"]; $this->drate = $row["drate"]; $this->maxuploads = $row["maxuploads"]; $this->superseeder = $row["superseeder"]; $this->runtime = $row["runtime"]; $this->sharekill = $row["sharekill"]; $this->minport = $row["minport"]; $this->maxport = $row["maxport"]; $this->maxcons = $row["maxcons"]; $this->rerequest = $row["rerequest"]; // loaded return true; } else { // not loaded return false; } } /** * save settings */ function settingsSave() { global $db; // Messy - a not exists would prob work better deleteTransferSettings($this->transfer); // insert $sql = "INSERT INTO tf_transfers " ."(" ."transfer," ."type," ."client," ."hash," ."datapath," ."savepath," ."running," ."rate," ."drate," ."maxuploads," ."superseeder," ."runtime," ."sharekill," ."minport," ."maxport," ."maxcons," ."rerequest" .") VALUES (" . $db->qstr($this->transfer)."," . $db->qstr($this->type)."," . $db->qstr($this->client)."," . $db->qstr($this->hash)."," . $db->qstr($this->datapath)."," . $db->qstr($this->savepath)."," . $db->qstr($this->running)."," . $db->qstr($this->rate)."," . $db->qstr($this->drate)."," . $db->qstr($this->maxuploads)."," . $db->qstr($this->superseeder)."," . $db->qstr($this->runtime)."," . $db->qstr($this->sharekill)."," . $db->qstr($this->minport)."," . $db->qstr($this->maxport)."," . $db->qstr($this->maxcons)."," . $db->qstr($this->rerequest) .")"; $db->Execute($sql); if ($db->ErrorNo() != 0) dbError($sql); // set transfers-cache cacheTransfersSet(); return true; } /** * stops a client * * @param $transfer name of the transfer * @param $kill kill-param (optional) * @param $transferPid transfer Pid (optional) */ function stop($transfer, $kill = false, $transferPid = 0) { // set vars $this->_setVarsForTransfer($transfer); // stop the client $this->_stop($kill, $transferPid); } /** * deletes a transfer * * @param $transfer name of the transfer * @return boolean of success */ function delete($transfer) { // set vars $this->_setVarsForTransfer($transfer); // delete return $this->_delete(); } /** * gets current transfer-vals of a transfer * * @param $transfer * @return array with downtotal and uptotal */ function getTransferCurrent($transfer) { global $transfers; // transfer from stat-file $sf = new StatFile($transfer); return array("uptotal" => $sf->uptotal, "downtotal" => $sf->downtotal); } /** * gets current transfer-vals of a transfer. optimized version * * @param $transfer * @param $tid of the transfer * @param $sfu stat-file-uptotal of the transfer * @param $sfd stat-file-downtotal of the transfer * @return array with downtotal and uptotal */ function getTransferCurrentOP($transfer, $tid, $sfu, $sfd) { return array("uptotal" => $sfu, "downtotal" => $sfd); } /** * gets total transfer-vals of a transfer * * @param $transfer * @return array with downtotal and uptotal */ function getTransferTotal($transfer) { global $db, $transfers; $retVal = array(); // transfer from db $sql = "SELECT uptotal,downtotal FROM tf_transfer_totals WHERE tid = ".$db->qstr(getTransferHash($transfer)); $result = $db->Execute($sql); $row = $result->FetchRow(); if (empty($row)) { $retVal["uptotal"] = 0; $retVal["downtotal"] = 0; } else { $retVal["uptotal"] = $row["uptotal"]; $retVal["downtotal"] = $row["downtotal"]; } // transfer from stat-file $sf = new StatFile($transfer); $retVal["uptotal"] += $sf->uptotal; $retVal["downtotal"] += $sf->downtotal; return $retVal; } /** * gets total transfer-vals of a transfer. optimized version * * @param $transfer * @param $tid of the transfer * @param $sfu stat-file-uptotal of the transfer * @param $sfd stat-file-downtotal of the transfer * @return array with downtotal and uptotal */ function getTransferTotalOP($transfer, $tid, $sfu, $sfd) { global $transfers; $retVal = array(); $retVal["uptotal"] = (isset($transfers['totals'][$tid]['uptotal'])) ? $transfers['totals'][$tid]['uptotal'] + $sfu : $sfu; $retVal["downtotal"] = (isset($transfers['totals'][$tid]['downtotal'])) ? $transfers['totals'][$tid]['downtotal'] + $sfd : $sfd; return $retVal; } /** * gets ary of running clients (via call to ps) * * @return array */ function runningProcesses() { global $cfg; $retAry = array(); $screenStatus = shell_exec("ps x -o pid='' -o ppid='' -o command='' -ww | ".$cfg['bin_grep']." ".tfb_shellencode($this->binClient)." | ".$cfg['bin_grep']." ".tfb_shellencode($cfg["transfer_file_path"])." | ".$cfg['bin_grep']." -v grep"); $arScreen = array(); $tok = strtok($screenStatus, "\n"); while ($tok) { array_push($arScreen, $tok); $tok = strtok("\n"); } $arySize = sizeof($arScreen); for ($i = 0; $i < $arySize; $i++) { if(strpos($arScreen[$i], $this->binClient) !== false) { $pinfo = new ProcessInfo($arScreen[$i]); if (intval($pinfo->ppid) == 1) { if (!strpos($pinfo->cmdline, "rep ". $this->binSystem) > 0) { if (!strpos($pinfo->cmdline, "ps x") > 0) { array_push($retAry, array( 'client' => $this->client, 'pinfo' => $pinfo->pid." ".$pinfo->cmdline ) ); } } } } } return $retAry; } /** * get info of running clients (via call to ps) * * @return string */ function runningProcessInfo() { global $cfg; // ps-string $screenStatus = shell_exec("ps x -o pid='' -o ppid='' -o command='' -ww | ".$cfg['bin_grep']." ".tfb_shellencode($this->binClient)." | ".$cfg['bin_grep']." ".tfb_shellencode($cfg["transfer_file_path"])." | ".$cfg['bin_grep']." -v grep"); $arScreen = array(); $tok = strtok($screenStatus, "\n"); while ($tok) { array_push($arScreen, $tok); $tok = strtok("\n"); } $cProcess = array(); $cpProcess = array(); $pProcess = array(); $ProcessCmd = array(); for ($i = 0; $i < sizeof($arScreen); $i++) { if (strpos($arScreen[$i], $this->binClient) !== false) { $pinfo = new ProcessInfo($arScreen[$i]); if (intval($pinfo->ppid) == 1) { if (!strpos($pinfo->cmdline, "rep ". $this->binSystem) > 0) { if (!strpos($pinfo->cmdline, "ps x") > 0) { array_push($pProcess,$pinfo->pid); $rt = RunningTransfer::getInstance($pinfo->pid." ".$pinfo->cmdline, $this->client); array_push($ProcessCmd, $rt->transferowner."\t".$rt->transferFile); } } } else { if (!strpos($pinfo->cmdline, "rep ". $this->binSystem) > 0) { if (!strpos($pinfo->cmdline, "ps x") > 0) { array_push($cProcess, $pinfo->pid); array_push($cpProcess, $pinfo->ppid); } } } } } $retVal = " --- Running Processes ---\n"; $retVal .= " Parents : " . count($pProcess) . "\n"; $retVal .= " Children : " . count($cProcess) . "\n"; $retVal .= "\n"; $retVal .= " PID \tOwner\tTransfer File\n"; foreach ($pProcess as $key => $value) $retVal .= " " . $value . "\t" . $ProcessCmd[$key] . "\n"; $retVal .= "\n"; return $retVal; } /** * writes a message to the per-transfer-logfile * * @param $message * @param $withTS */ function logMessage($message, $withTS = true) { // return if transfer-file-field not set if ($this->transferFilePath == "") return false; // log if ($handle = @fopen($this->transferFilePath.".log", "a+")) { $content = ($withTS) ? @date("[Y/m/d - H:i:s]")." ".$message : $message; $resultSuccess = (@fwrite($handle, $content) !== false); @fclose($handle); return $resultSuccess; } return false; } // ========================================================================= // protected methods // ========================================================================= /** * sets all fields depending on "transfer"-value * * @param $transfer */ function _setVarsForTransfer($transfer) { global $cfg; $this->transfer = $transfer; $this->transferFilePath = $cfg["transfer_file_path"].$this->transfer; $this->owner = getOwner($transfer); } /** * sets common settings-fields */ function _settingsInit() { global $cfg; // customize settings if ($cfg['transfer_customize_settings'] == 2) $customize_settings = 1; elseif ($cfg['transfer_customize_settings'] == 1 && $cfg['isAdmin']) $customize_settings = 1; else $customize_settings = 0; // init default-settings $this->settingsDefault(); // only read request-vars if enabled if ($customize_settings == 1) { // rate $reqvar = tfb_getRequestVar('max_upload_rate'); if ($reqvar != "") $this->rate = $reqvar; // drate $reqvar = tfb_getRequestVar('max_download_rate'); if ($reqvar != "") $this->drate = $reqvar; // maxuploads $reqvar = tfb_getRequestVar('max_uploads'); if ($reqvar != "") $this->maxuploads = $reqvar; // superseeder $reqvar = tfb_getRequestVar('superseeder'); if ($reqvar != "") $this->superseeder = $reqvar; // runtime $reqvar = tfb_getRequestVar('die_when_done'); if ($reqvar != "") $this->runtime = $reqvar; // sharekill $reqvar = tfb_getRequestVar('sharekill'); if ($reqvar != "") $this->sharekill = $reqvar; // minport $reqvar = tfb_getRequestVar('minport'); if (!empty($reqvar)) $this->minport = $reqvar; // maxport $reqvar = tfb_getRequestVar('maxport'); if (!empty($reqvar)) $this->maxport = $reqvar; // maxcons $reqvar = tfb_getRequestVar('maxcons'); if ($reqvar != "") $this->maxcons = $reqvar; // rerequest $reqvar = tfb_getRequestVar('rerequest'); if ($reqvar != "") $this->rerequest = $reqvar; } // savepath if ($cfg["showdirtree"] == 1) $this->savepath = tfb_getRequestVar('savepath'); // skip_hash_check $this->skip_hash_check = tfb_getRequestVar('skiphashcheck'); } /** * init start of a client. * * @param $interactive * @param $enqueue * @param $setPort * @param $recalcSharekill */ function _init($interactive, $enqueue = false, $setPort = false, $recalcSharekill = false) { global $cfg; // request-vars / defaults / database if ($interactive) { // interactive, get vars from request vars $this->settingsInit(); } else { // non-interactive, load settings from db $this->skip_hash_check = $cfg["skiphashcheck"]; // load settings, default if settings could not be loaded (fresh transfer) if ($this->settingsLoad() !== true) $this->settingsDefault(); } // queue if ($enqueue) { $this->queue = ($cfg['isAdmin']) ? $enqueue : true; } else { $this->queue = false; } // savepath-check if (empty($this->savepath)) $this->savepath = ($cfg["enable_home_dirs"] != 0) ? $cfg['path'].$this->owner."/" : $cfg['path'].$cfg["path_incoming"]."/"; else $this->savepath = checkDirPathString($this->savepath); // check target-directory, create if not present if (!(checkDirectory($this->savepath, 0777))) { $this->state = CLIENTHANDLER_STATE_ERROR; $msg = "Error checking savepath ".$this->savepath; array_push($this->messages, $msg); AuditAction($cfg["constants"]["error"], $msg); $this->logMessage($msg."\n", true); // write error to stat $sf = new StatFile($this->transfer, $this->owner); $sf->time_left = 'Error'; $sf->write(); return false; } // umask $this->umask = ($cfg["enable_umask"] != 0) ? " umask 0000;" : ""; // nice $this->nice = ($cfg["nice_adjust"] != 0) ? "nice -n ".$cfg["nice_adjust"]." " : ""; // set param for sharekill $this->sharekill = intval($this->sharekill); // recalc sharekill if ($recalcSharekill) { if ($this->_recalcSharekill() === false) return false; } else { $this->sharekill_param = $this->sharekill; $this->logMessage("setting sharekill-param to ".$this->sharekill_param."\n", true); } // set port if start (only if not queue) if (($setPort) && (!$this->queue)) { if ($this->_setClientPort() === false) return false; } // get current transfer $transferTotals = $this->getTransferCurrent($this->transfer); //XFER: before a transfer start/restart save upload/download xfer to SQL if ($cfg['enable_xfer'] == 1) Xfer::save($this->owner,($transferTotals["downtotal"]),($transferTotals["uptotal"])); // update totals for this transfer $this->_updateTotals(); // set state $this->state = CLIENTHANDLER_STATE_READY; } /** * start a client. */ function _start() { global $cfg; if ($this->state != CLIENTHANDLER_STATE_READY) { $this->state = CLIENTHANDLER_STATE_ERROR; array_push($this->messages , "Error. ClientHandler in wrong state on start-request."); // write error to stat $sf = new StatFile($this->transfer, $this->owner); $sf->time_left = 'Error'; $sf->write(); // return return; } // Save transfer settings $this->settingsSave(); // flush session-cache (trigger transfers-cache-set on next page-load) cacheFlush($cfg['user']); // write the session to close so older version of PHP will not hang @session_write_close(); // sf $sf = new StatFile($this->transfer, $this->owner); // queue or start ? if ($this->queue) { // queue if (FluxdQmgr::isRunning()) { // write stat-file $sf->queue(); // send command FluxdQmgr::enqueueTransfer($this->transfer, $cfg['user']); // log AuditAction($cfg["constants"]["queued_transfer"], $this->transfer); $this->logMessage("transfer enqueued : ".$this->transfer."\n", true); } else { $msg = "queue-request (".$this->transfer."/".$cfg['user'].") but Qmgr not active"; array_push($this->messages , $msg); AuditAction($cfg["constants"]["error"], $msg); $this->logMessage($msg."\n", true); } // set flag $this->running = 0; } else { // start // write stat-file $sf->start(); // log the command $this->logMessage("executing command : \n".$this->command."\n", true); // startup $this->callResult = exec($this->command); AuditAction($cfg["constants"]["start_torrent"], $this->transfer); // set flag $this->running = 1; // wait until transfer is up waitForTransfer($this->transfer, true, 20); } if (empty($this->messages)) { // set state $this->state = CLIENTHANDLER_STATE_OK; } else { // error $this->state = CLIENTHANDLER_STATE_ERROR; $msg = "error starting client. messages :\n"; $msg .= implode("\n", $this->messages); $this->logMessage($msg."\n", true); // write error to stat $sf->time_left = 'Error'; $sf->write(); } } /** * stop a client * * @param $kill kill-param (optional) * @param $transferPid transfer Pid (optional) */ function _stop($kill = false, $transferPid = 0) { global $cfg; // log AuditAction($cfg["constants"]["stop_transfer"], $this->transfer); // send quit-command to client CommandHandler::add($this->transfer, "q"); CommandHandler::send($this->transfer); // wait until transfer is down waitForTransfer($this->transfer, false, 25); // one more second sleep(1); // flag the transfer as stopped (in db) stopTransferSettings($this->transfer); // set transfers-cache cacheTransfersSet(); // see if the transfer process is hung. $running = $this->runningProcesses(); $isHung = false; foreach ($running as $rng) { $rt = RunningTransfer::getInstance($rng['pinfo'], $this->client); if ($rt->transferFile == $this->transfer) { $isHung = true; AuditAction($cfg["constants"]["error"], "Possible Hung Process for ".$rt->transferFile." (".$rt->processId.")"); } } // kill-request if ($kill && $isHung) { AuditAction($cfg["constants"]["kill_transfer"], $this->transfer); // set pid if (!empty($transferPid)) { // test for valid pid-var if (preg_match('/^[0-9]+$/D', $transferPid)) { $this->pid = $transferPid; } else { $this->state = CLIENTHANDLER_STATE_ERROR; AuditAction($cfg["constants"]["error"], "INVALID PID: ".$transferPid); array_push($this->messages, "INVALID PID: ".$transferPid); return false; } } else { $this->pid = getTransferPid($this->transfer);; } // kill it require_once('inc/defines/defines.signals.php'); if ($this->pid > 0) $this->callResult = posix_kill($this->pid, SIGKILL); // try to remove the pid file @unlink($this->transferFilePath.".pid"); } } /** * deletes a transfer * * @return boolean */ function _delete() { global $cfg; // delete if (($cfg["user"] == $this->owner) || $cfg['isAdmin']) { // XFER: before deletion save upload/download xfer data to SQL if ($cfg['enable_xfer'] == 1) { $transferTotals = $this->getTransferCurrent($this->transfer); Xfer::save($this->owner, $transferTotals["downtotal"], $transferTotals["uptotal"]); } // update totals $this->_updateTotals(); // remove settings from db deleteTransferSettings($this->transfer); // client-cache $this->deleteCache($this->transfer); // command-clean CommandHandler::clean($this->transfer); // remove meta-file if (@file_exists($this->transferFilePath)) @unlink($this->transferFilePath); // remove stat-file if (@file_exists($this->transferFilePath.".stat")) @unlink($this->transferFilePath.".stat"); // if exist remove pid file if (@file_exists($this->transferFilePath.".pid")) @unlink($this->transferFilePath.".pid"); // if exist remove log-file if (@file_exists($this->transferFilePath.".log")) @unlink($this->transferFilePath.".log"); // if exist remove prio-file if (@file_exists($this->transferFilePath.".prio")) @unlink($this->transferFilePath.".prio"); AuditAction($cfg["constants"]["delete_transfer"], $this->transfer); return true; } else { AuditAction($cfg["constants"]["error"], "ILLEGAL DELETE: ".$this->transfer); return false; } } // ========================================================================= // private methods // ========================================================================= /** * updates totals of a transfer */ function _updateTotals() { global $db; $tid = getTransferHash($this->transfer); $transferTotals = $this->getTransferTotal($this->transfer); $sql = ($db->GetOne("SELECT 1 FROM tf_transfer_totals WHERE tid = ".$db->qstr($tid))) ? "UPDATE tf_transfer_totals SET uptotal = ".$db->qstr($transferTotals["uptotal"]).", downtotal = ".$db->qstr($transferTotals["downtotal"])." WHERE tid = ".$db->qstr($tid) : "INSERT INTO tf_transfer_totals (tid,uptotal,downtotal) VALUES (".$db->qstr($tid).",".$db->qstr($transferTotals["uptotal"]).",".$db->qstr($transferTotals["downtotal"]).")"; $db->Execute($sql); // set transfers-cache cacheTransfersSet(); } /** * recalc sharekill * * @return boolean */ function _recalcSharekill() { global $cfg; $this->logMessage("recalc sharekill for ".$this->transfer."\n", true); if ($this->sharekill == 0) { // nice, we seed forever $this->sharekill_param = 0; $this->logMessage("seed forever\n", true); // return return true; } elseif ($this->sharekill > 0) { // recalc sharekill /* get size */ // try stat-file first $sf = new StatFile($this->transfer, $this->owner); $transferSize = (empty($sf->size)) ? 0 : floatval($sf->size); // try to reget if stat was empty if ($transferSize <= 0) $transferSize = floatval(getTransferSize($this->transfer)); // if still no size just pass thru the param if ($transferSize <= 0) { // message $msg = "data-size = '".$transferSize."' when recalcing share-kill for ".$this->transfer.", setting sharekill-param to ".$this->sharekill_param; array_push($this->messages , $msg); // debug-log if ($cfg['debuglevel'] > 0) AuditAction($cfg["constants"]["debug"], $msg); // log $this->logMessage($msg."\n", true); // set sharekill param $this->sharekill_param = $this->sharekill; // return return true; } /* get vars */ // get totals $totalAry = $this->getTransferTotal($this->transfer); $upTotal = floatval($totalAry["uptotal"]); $downTotal = floatval($totalAry["downtotal"]); // check totals if (($upTotal < 0) || ($downTotal < 0)) { // message $msg = "problems getting totals (upTotal: ".$upTotal."; downTotal: ".$downTotal.") when recalcing share-kill for ".$this->transfer.", setting sharekill-param to ".$this->sharekill_param; array_push($this->messages , $msg); // debug-log if ($cfg['debuglevel'] > 0) AuditAction($cfg["constants"]["debug"], $msg); // log $this->logMessage($msg."\n", true); // set sharekill param $this->sharekill_param = $this->sharekill; // return return true; } // wanted $upWanted = ($this->sharekill / 100) * $transferSize; // check wanted if ($upWanted <= 0) { // message $msg = "problems calculating wanted upload (upWanted: ".$upWanted.") when recalcing share-kill for ".$this->transfer.", setting sharekill-param to ".$this->sharekill_param; array_push($this->messages , $msg); // debug-log if ($cfg['debuglevel'] > 0) AuditAction($cfg["constants"]["debug"], $msg); // log $this->logMessage($msg."\n", true); // set sharekill param $this->sharekill_param = $this->sharekill; // return return true; } // share percentage $sharePercentage = ($upTotal / $transferSize) * 100; // check percentage if (($sharePercentage < 0) || ($sharePercentage >= 2147483647)) { // message $msg = "problems calculating share percentage (sharePercentage: ".$sharePercentage.") when recalcing share-kill for ".$this->transfer.", setting sharekill-param to ".$this->sharekill_param; array_push($this->messages , $msg); // debug-log if ($cfg['debuglevel'] > 0) AuditAction($cfg["constants"]["debug"], $msg); // log $this->logMessage($msg."\n", true); // set sharekill param $this->sharekill_param = $this->sharekill; // return return true; } /* check */ if (($upTotal >= $upWanted) && ($downTotal >= $transferSize)) { // just pass thru param if runtime is true if ($this->runtime == "True") { $this->sharekill_param = $this->sharekill; $this->logMessage("setting sharekill-param to ".$this->sharekill_param."\n", true); // return return true; } // we already have seeded at least wanted percentage. skip start of client // set state $this->state = CLIENTHANDLER_STATE_NULL; // message $msg = "skip ".$this->transfer." due to share-ratio (has: ".@number_format($sharePercentage, 2)."; set:".$this->sharekill."; upTotal: ".$upTotal."; upWanted: ".$upWanted.")"; array_push($this->messages , $msg); // debug-log if ($cfg['debuglevel'] > 0) AuditAction($cfg["constants"]["debug"], $msg); // log $this->logMessage($msg."\n", true); // write Skipped to stat $sf = new StatFile($this->transfer, $this->owner); $sf->time_left = 'Skipped'; $sf->write(); // return return false; } else { // not done seeding wanted percentage $this->sharekill_param = intval(ceil($this->sharekill - $sharePercentage)); // sanity-check. if ($this->sharekill_param < 1) $this->sharekill_param = 1; $this->logMessage("recalcing sharekill. wanted: ".$this->sharekill."; done: ".$sharePercentage."\n", true); $this->logMessage("setting sharekill-param to ".$this->sharekill_param."\n", true); // return return true; } } else { $this->sharekill_param = $this->sharekill; $this->logMessage("setting sharekill-param to ".$this->sharekill_param."\n", true); // return return true; } // return return true; } /** * gets available port and sets port field * * @return boolean */ function _setClientPort() { global $cfg; $portString = netstatPortList(); $portAry = explode("\n", $portString); $this->port = intval($this->minport); while (1) { if (in_array($this->port, $portAry)) $this->port += 1; else return true; if ($this->port > $this->maxport) { // state $this->state = CLIENTHANDLER_STATE_ERROR; // message $msg = "All ports in use."; array_push($this->messages , $msg); AuditAction($cfg["constants"]["error"], $msg); $this->logMessage($msg."\n", true); // write error to stat $sf = new StatFile($this->transfer, $this->owner); $sf->time_left = 'Error'; $sf->write(); // return return false; } } return false; } } // end class ?>