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'); } } ?>