array (
'type' => 'CHAR',
'length' => 6,
'not_null' => true,
'default' => ''
),
'owner' => array (
'type' => 'INT',
'length' => 10,
'unsigned' => true,
'not_null' => true,
'default' => 0,
'refers_to' => 'users.id'
),
'name' => array (
'type' => 'VARCHAR',
'length' => 255
),
'module' => array (
'type' => 'VARCHAR',
'length' => 255,
'not_null' => true,
'default' => ''
),
'bundler' => array (
'type' => 'VARCHAR',
'length' => 255,
'not_null' => true
),
'flags' => array (
'type' => 'VARCHAR',
'length' => 255,
'not_null' => true
),
'backend' => array (
'type' => 'VARCHAR',
'length' => 255
),
'status' => array (
'type' => 'ENUM',
'length' => '\'queued\',\'uploading\',\'cancel\',\'complete\',\'canceled\',\'building\',\'bundling\'',
'not_null' => true
),
'build_step' => array (
'type' => 'TINYINT',
'length' => 4,
'unsigned' => true
),
'num_steps' => array (
'type' => 'TINYINT',
'length' => 4,
'unsigned' => true
),
'ctime' => array (
'type' => 'INT',
'length' => 10,
'unsigned' => true
),
'start' => array (
'type' => 'INT',
'length' => 10,
'unsigned' => true
),
'finish' => array (
'type' => 'INT',
'length' => 10,
'unsigned' => true
)
);
// Returns HTML code describing this build's status (for human consumption)
public function display() {
global $S;
$format='D j M Y G:i:s T';
$perms=!$this->has_flag('p') || owner_or_admin($this->id);
$html='
'.(isset($this->name) && strlen($this->name)?htmlentities($this->name):'Unnamed Build').' ';
if ($this->has_flag('f'))
$html.='
[failed] ';
$links=array();
switch ($this->status) {
case 'queued':
$total=query('SELECT COUNT(*) FROM `builds` WHERE `status`="queued"')->fetch(PDO::FETCH_COLUMN);
$num=query('SELECT COUNT(*) FROM `builds` WHERE `status`="queued" AND `ctime` <= '.$this->ctime)->fetch(PDO::FETCH_COLUMN);
$html.="
[queued ($num/$total)]";
break;
case 'uploading':
$html.='
[uploading]';
if ($perms) $links['Build log']="build/$this->id";
break;
case 'cancel':
$html.='
[pending cancellation]';
if ($perms) $links['Build log']="build/$this->id";
break;
case 'building':
case 'bundling':
$html.='
['.$this->status.' ('.$this->build_step.'/'.$this->num_steps.')]';
if ($perms) {
//$links['Watch']="build/$this->id/live";
$links['Build Log']="build/$this->id";
}
break;
case 'complete':
$url="build/$this->id/history";
if ($perms && $S['request'] != $url) {
$r=query('SELECT COUNT(*) as `count`, MAX(`time`) as `time` FROM `downloads` WHERE `build`="'.$this->id.'"')->fetch(PDO::FETCH_ASSOC);
$d=($r['count']?'
':'').$r['count'].' download'.($r['count'] != 1?'s':'').($r['count']?($perms?'':'').'
(last at '.date($format, $r['time']).')':'');
} else
$d='';
$html.='
'.$d.'[successful]';
$links['Download image']="build/$this->id/download";
if ($perms) $links['Build log']="build/$this->id";
break;
case 'canceled':
$html.='
[canceled]';
if ($perms) $links['Build log']="build/$this->id";
break;
default:
$html.='
[UNKNOWN STATUS: '.$this->status.']';
}
if ($perms) {
if ($this->status == 'canceled' || $this->status == 'queued' || $this->status == 'complete' || $this->has_flag('f'))
$links['Delete']="build/$this->id/delete";
elseif ($this->status != 'cancel')
$links['Cancel']="build/$this->id/cancel";
}
if ($links) {
foreach ($links as $label => $url) {
if ($S['request'] == $url)
unset($links[$label]);
else
$links[$label]='
'.htmlentities($label).'';
}
if ($links)
$html.='
'.implode(' • ', $links).'';
}
if (isset($this->ctime)) {
$html.='
Submitted for build at: '.date($format, $this->ctime).'
';
if (isset($this->start)) {
$html.='Build started at: '.date($format, $this->start).'
';
if (isset($this->finish)) {
$html.='Build finished at: '.date($format, $this->finish).'
Total build time: '.display_time($this->finish-$this->start).'';
} else {
$html.='Running for: '.display_time(time()-$this->start).'';
}
} else {
$html.='Queued for: '.display_time(time()-$this->ctime).'';
}
$html.='
';
}
$html.='
';
return $html;
}
public function delete() {
global $S;
query('DELETE FROM `buildlogs` WHERE `build`="'.$this->id.'"');
query('DELETE FROM `tasks` WHERE `build`="'.$this->id.'"');
query('DELETE FROM `buildopts` WHERE `build`="'.$this->id.'"');
query('DELETE FROM `downloads` WHERE `build`="'.$this->id.'"');
foreach (glob(COMPLETED."/build-.$this->id.*") as $file)
unlink($file);
parent::delete();
}
public function build($workdir) {
global $S;
try {
$opts=$this->get_opts();
$S['build_steps']=array();
if (!is_readable(BACKEND."/modules/$this->module/build.php"))
throw_exception("No build script for module $this->module");
$imagedir=require(BACKEND."/modules/$this->module/build.php");
switch ($this->status) {
case 'queued':
$this->status='building';
$this->build_step=0;
$this->num_steps=count($S['build_steps']);
$this->write();
case 'building':
$step=$this->build_step;
break;
default:
return $imagedir;
}
if (!is_dir($workdir))
log_status('Create work directory '.$workdir, mkdir($workdir, 0700));
while ($step < count($S['build_steps'])) {
require(BACKEND."/modules/$this->module/{$S['build_steps'][$step]}.php");
$step++;
$this->build_step=$step;
$this->write();
if ($this->is_canceled()) return false;
}
return $imagedir;
} catch(Exception $e) {
log_msg('Caught exception: '.$e->getMessage());
end_internal_task(1);
$this->failed();
return false;
}
}
public function bundle($imagedir, $workdir) {
global $S;
try {
$opts=$this->get_opts();
if (!is_readable(BACKEND."/bundlers/$this->bundler/bundle.php"))
throw_exception("No bundle script for bundler $this->bundler");
$S['build_steps']=array();
$file=require(BACKEND."/bundlers/$this->bundler/bundle.php");
switch($this->status) {
case 'building':
$this->status='bundling';
$this->build_step=0;
$this->num_steps=count($S['build_steps']);
$this->write();
case 'bundling':
$step=$this->build_step;
break;
default:
return $file;
}
while ($step < count($S['build_steps'])) {
require(BACKEND."/bundlers/$this->bundler/{$S['build_steps'][$step]}.php");
$step++;
$this->build_step=$step;
$this->write();
if ($this->is_canceled()) return false;
}
return $file;
} catch (Exception $e) {
log_msg('Caught exception: '.$e->getMessage());
end_internal_task(1);
$this->failed();
return false;
}
}
public function upload($file) {
$this->status='uploading';
$this->write();
$key=randstring(30);
$this->set_opt('uploadkey', $key);
$c=curl_init(url('backend/upload_image'));
curl_setopt($c, CURLOPT_POST, 1);
curl_setopt($c, CURLOPT_POSTFIELDS, array(
'build' => $this->id,
'key' => $key,
'file' => "@$file"
));
curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($c);
if (trim($result) == 'Upload successful') {
$this->status='complete';
$this->finish=time();
$this->write();
} else
$this->failed();
return $result;
}
private function failed() {
$this->set_flag('f');
$this->finish=time();
$this->write();
}
public function is_canceled() {
$this->load();
return ($this->status == 'cancel');
}
}
?>