/** * * @todo root path workaround * @todo partial copy * * @throws Exception * @param $update_list array[][string]string * @param $update_list []['source'] array[][string]string Source path or URI * @param $update_list []['target'] array[][string]string Target path * @param $update_list []['slug'] array[][string]string Update item slug (optional item identity) * @param $update_list []['md5'] array[][string]string MD5 of source archive (optional) * * @return array[][string]mixed * @return array[]['skipped']boolean * @return array[]['source']mixed * @return array[]['source']mixed * @return array[]['source']mixed * @return array[]['source']mixed * @return array[]['source']mixed * @return array[]['source']mixed */ public function update($update_list) { $update_path = self::$update_path . $this->thread_id . '/'; $download_path = $update_path . 'download/'; try { $this->formatUpdateList($update_list, $update_path); $this->envSet(); $resume = false; self::$ob_skip = false; //TODO write statistics into stage operations //TODO allow skip some update parts switch ($resume) { case false: case self::STAGE_FLUSH: case self::STAGE_NONE: $this->setFullState(null); case self::STAGE_PREPARE: foreach ($update_list as $chunk_id => &$update) { if ($update['dependent']) { $update['current_size'] = false; } else { $this->current_chunk_id = isset($update['slug']) ? $update['slug'] : $chunk_id; $update['current_size'] = $this->run(self::STAGE_PREPARE, $update['pass'], $download_path, $update['extract_path'], $update['target']); } } unset($update); case self::STAGE_COPY: foreach ($update_list as $chunk_id => &$update) { if ($update['skipped']) { continue; } if (file_exists(self::$root_path . $update['target']) && !$update['dependent']) { $this->current_chunk_id = isset($update['slug']) ? $update['slug'] : $chunk_id; $result = $this->run(self::STAGE_COPY, $update['pass'], $update['target'], $update['extract_path'], $update['current_size']); if (!$result && $update['pass']) { $update['skipped'] = true; } } } unset($update); case self::STAGE_DOWNLOAD: foreach ($update_list as $chunk_id => &$update) { if ($update['skipped']) { continue; } $this->current_chunk_id = isset($update['slug']) ? $update['slug'] : $chunk_id; //TODO check name for $download_path $update['archive'] = $this->run(self::STAGE_DOWNLOAD, $update['pass'], $update['source'], $download_path, isset($update['md5']) ? $update['md5'] : false); if (!$update['archive'] && $update['pass']) { $update['skipped'] = true; } } unset($update); case self::STAGE_EXTRACT: foreach ($update_list as $chunk_id => &$update) { if ($update['skipped']) { continue; } $this->current_chunk_id = isset($update['slug']) ? $update['slug'] : $chunk_id; $result = $this->run(self::STAGE_EXTRACT, $update['pass'], $update['archive'], $update['extract_path'], $update['target']); if (!$result && $update['pass']) { $update['skipped'] = true; } } unset($update); case self::STAGE_REPLACE: foreach ($update_list as $chunk_id => &$update) { if ($update['skipped'] || $update['dependent']) { continue; } $this->current_chunk_id = isset($update['slug']) ? $update['slug'] : $chunk_id; $update['backup'] = $this->run(self::STAGE_REPLACE, $update['pass'], $update['extract_path'], $update['target']); if (!$update['backup'] && $update['pass']) { $update['skipped'] = true; } } unset($update); case self::STAGE_CLEANUP: foreach ($update_list as $chunk_id => &$update) { $this->current_chunk_id = isset($update['slug']) ? $update['slug'] : $chunk_id; $paths = array(); $paths[$update_path] = true; $cache_path = 'wa-cache/apps/' . ($this->current_chunk_id == 'wa-system' ? 'webasyst' : $this->current_chunk_id); $paths[$cache_path] = true; $this->run(self::STAGE_CLEANUP, false, $paths); } unset($update); case self::STAGE_VERIFY: foreach ($update_list as $chunk_id => &$update) { if ($update['skipped']) { continue; } if (!isset($update['verify'])) { continue; } if (strpos($update['target'], 'wa-config') !== false) { $update['verify'] = false; } $this->current_chunk_id = isset($update['slug']) ? $update['slug'] : $chunk_id; $this->run(self::STAGE_VERIFY, false, $update['target'], isset($update['verify']) && $update['verify']); } unset($update); case self::STAGE_UPDATE: foreach ($update_list as $chunk_id => &$update) { if ($update['skipped']) { continue; } $this->current_chunk_id = isset($update['slug']) ? $update['slug'] : $chunk_id; $this->run(self::STAGE_UPDATE, false); } unset($update); break; default: throw new Exception("Invalid resume state {$resume}"); break; } //$this->current_stage = 'update_'.self::STATE_COMPLETE; //$this->current_chunk_id = 'total'; //$this->setState(); $this->writeLog(__METHOD__, self::LOG_DEBUG, compact('update_list')); self::$ob_skip = true; $this->envReset(); return $update_list; } catch (Exception $ex) { $this->cleanupPath($update_path, true); $this->writeLog($ex->getMessage(), self::LOG_WARNING, compact('update_list')); $this->envReset(); self::$ob_skip = true; throw $ex; } }
/** * * @todo root path workaround * @todo partial copy * * @throws Exception * @param $update_list <pre>array( * 'source'=>uri|path string, * 'md5'=>string, * 'target'=>string, * )</pre> * @return array() */ public function update($update_list) { try { $update_path = self::$update_path . $this->thread_id . '/'; $download_path = $update_path . 'download/'; $targets = array(); foreach ($update_list as &$update) { $update['target'] = self::formatPath($update['target']) . '/'; $update['target'] = preg_replace('@(^|/)\\.\\./@', '/', $update['target']); $update['extract_path'] = $update_path . 'update/' . $update['target']; if (!isset($update['pass'])) { $update['pass'] = false; } if (!isset($update['skipped'])) { $update['skipped'] = false; } $founded = false; foreach ($targets as $id => $target) { if (strpos($target, $update['target']) === 0) { $founded = true; if (strlen($target) > strlen($update['target'])) { $targets[$id] = $update['target']; } break; } elseif (strpos($update['target'], $target) === 0) { $founded = true; break; } } if (!$founded) { $targets[] = $update['target']; } unset($update); } foreach ($update_list as &$update) { $update['dependent'] = false; foreach ($targets as $id => $target) { if (strpos($update['target'], $target) === 0) { if (strlen($target) < strlen($update['target'])) { $update['dependent'] = true; } break; } } unset($update); } $this->writeLog(__METHOD__ . ' tree', self::LOG_DEBUG, array('targets' => $targets, 'update_list' => $update_list)); #sort uasort($update_list, array(__CLASS__, 'sortUpdateList')); $session_id = session_id(); if ($session_id) { if (function_exists('wa') && method_exists($wa = wa(), 'getStorage')) { $wa->getStorage()->close(); } else { session_write_close(); } } if ($this->log_level >= self::LOG_DEBUG) { $this->writeLog('callback', self::LOG_DEBUG, self::debug_backtrace_custom()); } $error_level = error_reporting(); $display_errors = ini_get('display_errors'); $error_reporting = ini_get('error_reporting'); @ini_set('display_errors', true); @ini_set('error_reporting', E_ALL & ~E_NOTICE); error_reporting(E_ALL & ~E_NOTICE); ignore_user_abort(true); $this->writeLog('Register error handler', self::LOG_TRACE, ob_start(__CLASS__ . '::obHandler')); self::$ob_skip = false; $resume = false; //TODO write statistics into stage operations //TODO allow skip some update parts switch ($resume) { case false: case self::STAGE_FLUSH: case self::STAGE_NONE: $this->setFullState(null); case self::STAGE_PREPARE: foreach ($update_list as $chunk_id => &$update) { if ($update['dependent']) { $update['current_size'] = false; } else { $this->current_chunk_id = isset($update['slug']) ? $update['slug'] : $chunk_id; $update['current_size'] = $this->run(self::STAGE_PREPARE, $update['pass'], $download_path, $update['extract_path'], $update['target']); } } unset($update); case self::STAGE_COPY: foreach ($update_list as $chunk_id => &$update) { if ($update['skipped']) { continue; } if (file_exists(self::$root_path . $update['target']) && !$update['dependent']) { $this->current_chunk_id = isset($update['slug']) ? $update['slug'] : $chunk_id; $result = $this->run(self::STAGE_COPY, $update['pass'], $update['target'], $update['extract_path'], $update['current_size']); if (!$result && $update['pass']) { $update['skipped'] = true; } } } unset($update); case self::STAGE_DOWNLOAD: foreach ($update_list as $chunk_id => &$update) { if ($update['skipped']) { continue; } $this->current_chunk_id = isset($update['slug']) ? $update['slug'] : $chunk_id; //TODO check name for $download_path $update['archive'] = $this->run(self::STAGE_DOWNLOAD, $update['pass'], $update['source'], $download_path, isset($update['md5']) ? $update['md5'] : false); if (!$update['archive'] && $update['pass']) { $update['skipped'] = true; } } unset($update); case self::STAGE_EXTRACT: foreach ($update_list as $chunk_id => &$update) { if ($update['skipped']) { continue; } $this->current_chunk_id = isset($update['slug']) ? $update['slug'] : $chunk_id; $result = $this->run(self::STAGE_EXTRACT, $update['pass'], $update['archive'], $update['extract_path'], $update['target']); if (!$result && $update['pass']) { $update['skipped'] = true; } } unset($update); case self::STAGE_REPLACE: foreach ($update_list as $chunk_id => &$update) { if ($update['skipped'] || $update['dependent']) { continue; } $this->current_chunk_id = isset($update['slug']) ? $update['slug'] : $chunk_id; $update['backup'] = $this->run(self::STAGE_REPLACE, $update['pass'], $update['extract_path'], $update['target']); if (!$update['backup'] && $update['pass']) { $update['skipped'] = true; } } unset($update); case self::STAGE_CLEANUP: foreach ($update_list as $chunk_id => &$update) { $this->current_chunk_id = isset($update['slug']) ? $update['slug'] : $chunk_id; $paths = array(); $paths[$update_path] = true; $cache_path = 'wa-cache/apps/' . ($this->current_chunk_id == 'wa-system' ? 'webasyst' : $this->current_chunk_id); $paths[$cache_path] = true; $this->run(self::STAGE_CLEANUP, false, $paths); } unset($update); case self::STAGE_VERIFY: foreach ($update_list as $chunk_id => &$update) { if ($update['skipped']) { continue; } if (!isset($update['verify'])) { continue; } if (strpos($update['target'], 'wa-config') !== false) { $update['verify'] = false; } $this->current_chunk_id = isset($update['slug']) ? $update['slug'] : $chunk_id; $this->run(self::STAGE_VERIFY, false, $update['target'], isset($update['verify']) && $update['verify']); } unset($update); case self::STAGE_UPDATE: foreach ($update_list as $chunk_id => &$update) { if ($update['skipped']) { continue; } $this->current_chunk_id = isset($update['slug']) ? $update['slug'] : $chunk_id; $this->run(self::STAGE_UPDATE, false); } unset($update); break; default: throw new Exception("Invalid resume state {$resume}"); break; } //$this->current_stage = 'update_'.self::STATE_COMPLETE; //$this->current_chunk_id = 'total'; //$this->setState(); $this->writeLog(__METHOD__, self::LOG_DEBUG, array('source' => $update_list)); self::$ob_skip = true; error_reporting($error_level); @ini_set('display_errors', $display_errors); @ini_set('error_reporting', $error_reporting); if ($session_id) { if ($wa) { $wa->getStorage()->open(); } else { session_start(); } } return $update_list; } catch (Exception $ex) { $this->cleanupPath($update_path, true); $this->writeLog($ex->getMessage(), self::LOG_WARNING, array('source' => $update_list)); if ($session_id) { if ($wa) { $wa->getStorage()->open(); } else { session_start(); } } self::$ob_skip = true; throw $ex; } }