├── .gitignore
├── .travis.yml
├── README.md
├── app
├── application.php
├── bucket.php
├── control
│ ├── agent.php
│ └── import.php
├── controller.php
├── daemon.php
├── dispatcher.php
├── model
│ ├── consist.php
│ ├── router.php
│ ├── server.php
│ └── table.php
├── queque.php
├── security.php
├── setting.php
├── task.php
├── task
│ ├── checktable.php
│ ├── delete.php
│ ├── example.php
│ ├── import.php
│ ├── ready.php
│ └── rsplit.php
├── worker.php
└── worker
│ ├── checktable.php
│ ├── cleaner.php
│ ├── monitor.php
│ ├── processor.php
│ ├── test.php
│ └── uniquebuilder.php
├── before_install.sh
├── bin
└── run.php
├── build.xml
├── build
├── http
│ └── test_dispatcher_time_out.php
├── parse_array_list.php
├── parse_object_list.php
├── parse_server_list.php
├── phing
│ ├── init.xml
│ ├── makeconf.xml
│ ├── release.xml
│ └── unittest.xml
└── tpl
│ ├── alert.ini
│ ├── default.properties
│ ├── myfox.ini
│ ├── mysql.ini
│ ├── mysql_test.ini
│ └── security_example.ini
├── database.sql
├── etc
└── .gitignore
├── lib
├── TestShell.php
├── alert.php
├── autoload.php
├── blackhole.php
├── cache
│ └── apc.php
├── config.php
├── config
│ └── ini.php
├── context.php
├── debug
│ ├── pool.php
│ └── timer.php
├── exception.php
├── factory.php
├── fetcher
│ ├── ftp.php
│ └── scp.php
├── fileset.php
├── fsplit.php
├── livebox.php
├── log.php
├── mysql.php
└── parser
│ └── url.php
├── release.properties
├── resource
└── bash
│ ├── gateway_call_myfox_import.sh
│ └── myfoxctl.sh
├── test
└── unit
│ ├── AlertTest.php
│ ├── ApcTest.php
│ ├── AutoLoadTest.php
│ ├── BlackHoleTest.php
│ ├── BucketTest.php
│ ├── ChecktableTest.php
│ ├── CleanerTest.php
│ ├── ConfigTest.php
│ ├── ConsistTest.php
│ ├── ContextTest.php
│ ├── ControllerTest.php
│ ├── DaemonTest.php
│ ├── DebugTest.php
│ ├── DispatcherTest.php
│ ├── FactoryTest.php
│ ├── FilesetTest.php
│ ├── FsplitTest.php
│ ├── LiveBoxTest.php
│ ├── LogTest.php
│ ├── MonitorTest.php
│ ├── MysqlTest.php
│ ├── ProcessorTest.php
│ ├── QuequeTest.php
│ ├── RouterTest.php
│ ├── SecurityTest.php
│ ├── ServerTest.php
│ ├── SettingTest.php
│ ├── TableTest.php
│ ├── TaskTest.php
│ ├── UniquebuilderTest.php
│ ├── UrlTest.php
│ ├── autoload
│ └── com
│ │ ├── aleafs
│ │ ├── autoloadordertestclass.php
│ │ ├── autoloadtestcaseclassnamenotmatched.php
│ │ └── autoloadtestclass.php
│ │ └── autoloadordertestclass.php
│ ├── factory
│ └── test.php
│ ├── ini
│ └── config_test.ini
│ └── resource
│ ├── mirror_import_data_file.txt
│ └── numsplit_import_data_file.txt
└── www
└── index.php
/.gitignore:
--------------------------------------------------------------------------------
1 | _private.*
2 | bin/*.sh
3 | build/code_coverage/
4 | build/unit_test/
5 | etc/*.ini
6 | test/unit/ini/*.ini
7 | test/unit/tmp/
8 | resource/agent
9 | resource/bash/route_sync_diff.sh
10 | test/unit/resource/route.diff
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 | php:
3 | - 5.3
4 |
5 | before_script:
6 | - "mysql -uroot < database.sql"
7 | - "/bin/bash ./before_install.sh &> /dev/null"
8 |
9 | script: cp -f ./build/phing.php ./phing/bin/ && ./phing/bin/phing
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 简介 #
2 |
3 | * Myfox是淘宝数据产品部开发的一套应用在OLAP系统上的分布式MySQL集群代理服务. 原始的海量
4 | 数据通常在Hadoop等分布式计算平台上得到了一定程度的计算,然后通过预先定义的分库分表规
5 | 则(route_type)装入不同的mysql服务中;前段应用程序需要从这些分散的mysql中查询数据时,
6 | 通过Myfox代理,实现透明的访问。
7 |
8 | * Myfox的典型应用场景是OLAP系统. 数据在大部分时间内是静态的,数据通过后台离线的方式批量
9 | 写入. 需要实时更新的数据我们不建议通过Myfox来实现;Myfox目前也确实没这个能力,可预见的
10 | 未来应该也不会支持这样的需求。
11 |
12 | * 您看到的这个源码包只是 Myfox 中负责路由计算和数据装载的部分,提供在线透明查询的SQL代理
13 | 部分我们用Node.js做了重新实现,您可以在另外一个源码包里找到.
14 |
15 | # 设计思想 #
16 |
17 | * Myfox的数据装载服务本质上是一个基于HTTP协议和MySQL存储的任务队列系统。在淘宝内部,Myfox
18 | 数据运行在几个虚拟机上,数据是在Hadoop平台上计算并且根据Myfox的路由规则进行切分的。然后
19 | 通过Myfox提供的API远程提交数据装载任务。
20 |
21 | * Myfox用一个常驻进程来处理外部提交的数据装载任务,或者其他任务。这个进程您可以通过下列命
22 | 令来启动:
23 |
24 | ```bash
25 | $ nohup /usr/bin/php bin/run.php processor &
26 | ```
27 |
28 | # 代码结构 #
29 |
30 | ```bash
31 | .
32 | |-- READEME 自述文件
33 | |-- app Myfox自身类代码
34 | |-- bin 后台运行脚本的启动命令
35 | |-- build build文件,采用phing进行代码build
36 | |-- build.xml
37 | |-- etc 配置文件
38 | |-- lib Myfox无关的PHP Class代码
39 | |-- release.properties release模式的properties文件
40 | |-- resource 部署在Hadoop系统上的shell脚本模版
41 | |-- test 测试代码
42 | `-- www htdocs目录, index.php作为统一入口程序
43 | ```
44 |
45 | # 环境依赖 #
46 |
47 | * HTTP服务器,且支持php;推荐nginx + php-fpm;
48 |
49 | * php >= 5.3.3, 且需要 mysqli (with mysqlnd)、json、apc、pcntl、ib_binary、ftp和spl支持;
50 |
51 | * 元数据和路由库(MySQL),建议采用M/S结构,支持InnoDB;
52 |
53 | * 数据节点(MySQL),不小于2个. 支持读写账号的分离,支持远程LOAD DATA;
54 |
55 | * 数据文件切分服务器,默认通过ftp进行文件传输,因此需要ftp支持;
56 |
57 | # 部署与配置 #
58 |
59 | * 我们假设:
60 |
61 | > 元数据和路由库MySQL部署在172.0.0.1 (master) 和 172.0.0.2 (slave)上;
62 | > 有两个数据节点(MySQL),分别是192.168.1.1 和 192.168.1.2;
63 | > 所有MySQL通过3306端口对外服务,写账号为db_write,读账号为db_read,密码均为123456;
64 |
65 | * mysql -h172.0.0.1 -P3306 -udb_write -p123456 < database.sql
66 |
67 | * 配置修改:
68 |
69 | 整个项目在开发过程中采用phing来进行build,开发、单元测试以及release所用到的不同配置都用
70 | phing来管理。因此,对于配置文件的管理,我仍然建议你采用phing进行.
71 |
72 | > etc/myfox.ini 是主配置文件,其中 url.prefix 需要根据你的nginx配置而指定;
73 |
74 | > etc/mysql.ini 是元数据和路由库的配置文件,这一数据库实例在代码中以default命名.
75 |
76 | * 测试数据装入:
77 |
78 | ```bash
79 | $ sh bin/gateway_call_myfox_import.sh -tnumsplit_v2 -rthedate=20110610,cid=1 test/unit/resource/numsplit_import_data_file.txt
80 | $ sh bin/gateway_call_myfox_import.sh -tmirror_v2 test/unit/resource/mirror_import_data_file.txt 1
81 | ```
82 |
83 | # Contributors #
84 |
85 | ```
86 | project: Myfox-load-module
87 | commits: 57
88 | files : 113
89 | authors:
90 | 53 aleafs 93.0%
91 | 4 Zhiqiang Zhao 7.0%
92 | ```
93 |
94 |
--------------------------------------------------------------------------------
/app/application.php:
--------------------------------------------------------------------------------
1 | |
7 | // +------------------------------------------------------------------------+
8 | //
9 | // $Id: application.php 18 2010-04-13 15:40:37Z zhangxc83 $
10 |
11 | namespace Myfox\App;
12 |
13 | class Application
14 | {
15 |
16 | /* {{{ 静态变量 */
17 |
18 | private static $autoloaded = false;
19 |
20 | /* }}} */
21 |
22 | /* {{{ public static void setAutoLoad() */
23 | /**
24 | * 自动加载初始化
25 | *
26 | * @access public static
27 | * @return void
28 | */
29 | public static function setAutoLoad()
30 | {
31 | if (self::$autoloaded) {
32 | return;
33 | }
34 |
35 | require_once __DIR__ . '/../lib/autoload.php';
36 |
37 | \Myfox\Lib\AutoLoad::init();
38 | \Myfox\Lib\AutoLoad::register('myfox\\app', __DIR__);
39 |
40 | self::$autoloaded = true;
41 | }
42 | /* }}} */
43 |
44 | /* {{{ public static void init() */
45 | /**
46 | * 各个对象初始化
47 | *
48 | * @access public static
49 | * @return void
50 | */
51 | public static function init($ini)
52 | {
53 | self::setAutoLoad();
54 |
55 | /**
56 | * @配置文件
57 | */
58 | \Myfox\Lib\Config::register('default', $ini);
59 | $config = \Myfox\Lib\Config::instance('default');
60 |
61 | /**
62 | * @数据库
63 | */
64 | foreach ((array)$config->get('mysql') AS $name => $file) {
65 | \Myfox\Lib\Mysql::register($name, $file);
66 | }
67 |
68 | /**
69 | * @日志对象
70 | */
71 | foreach ((array)$config->get('log') AS $name => $url) {
72 | \Myfox\Lib\Factory::registerLog($name, $url);
73 | }
74 |
75 | /**
76 | * @告警提醒
77 | */
78 | \Myfox\Lib\Alert::init(__DIR__ . '/../etc/alert.ini');
79 | }
80 | /* }}} */
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/app/bucket.php:
--------------------------------------------------------------------------------
1 | |
7 | // +------------------------------------------------------------------------+
8 | //
9 | // $Id: bucket.php 22 2010-04-15 16:28:45Z zhangxc83 $
10 | //
11 | namespace Myfox\App;
12 |
13 | class Bucket
14 | {
15 |
16 | /* {{{ 成员变量 */
17 |
18 | private $cubage;
19 |
20 | private $maxcnt;
21 |
22 | private $cargos; /**< 原始数据 */
23 |
24 | private $slices; /**< 切片数据 */
25 |
26 | private $marks; /**< 使用标记 */
27 |
28 | /* }}} */
29 |
30 | /* {{{ public void __construct() */
31 | /**
32 | * 构造函数
33 | *
34 | * @access public
35 | * @return void
36 | */
37 | public function __construct($cubage, $float = 0)
38 | {
39 | $this->cubage = (int)abs($cubage);
40 | if (empty($this->cubage)) {
41 | $this->cubage = 2000000;
42 | }
43 | $this->maxcnt = (int)((1 + abs($float)) * $this->cubage);
44 |
45 | $this->cargos = array();
46 | $this->slices = array();
47 | }
48 | /* }}} */
49 |
50 | /* {{{ public void push() */
51 | /**
52 | * 压入一行数据
53 | *
54 | * @access public
55 | * @param Array $row
56 | * @param Integer $size
57 | * @return void
58 | */
59 | public function push($row, $size)
60 | {
61 | if ($size < 1) {
62 | return;
63 | }
64 |
65 | $offset = count($this->cargos);
66 | $this->cargos[$offset] = $row;
67 | foreach ($this->split($size) AS $zone) {
68 | $this->slices[] = array(
69 | 'size' => $zone,
70 | 'key' => $offset,
71 | );
72 | }
73 | }
74 | /* }}} */
75 |
76 | /* {{{ public Array allot() */
77 | /**
78 | * 分配
79 | *
80 | * @access public
81 | * @return Array
82 | */
83 | public function allot()
84 | {
85 | if (!usort($this->slices, function($a, $b) {return $a['size'] > $b['size'] ? -1 : 1;})) {
86 | return false;
87 | }
88 |
89 | $return = array();
90 | $this->marks = array();
91 | foreach ($this->slices AS $key => $zone) {
92 | if (!empty($this->marks[$key])) {
93 | continue;
94 | }
95 | $this->marks[$key] = true;
96 | $bucket = array(
97 | array(
98 | 'size' => $zone['size'],
99 | 'data' => $this->cargos[$zone['key']],
100 | ),
101 | );
102 | $offset = $key;
103 | $remain = $this->maxcnt - $zone['size'];
104 | while ($remain > 0) {
105 | $offset = $this->search($remain, $offset);
106 | if ($offset < 0) {
107 | break;
108 | }
109 |
110 | $bucket[] = array(
111 | 'size' => $this->slices[$offset]['size'],
112 | 'data' => $this->cargos[$this->slices[$offset]['key']],
113 | );
114 | $this->marks[$offset] = true;
115 | $remain -= $this->slices[$offset]['size'];
116 | }
117 |
118 | $return[] = $bucket;
119 | }
120 |
121 | return $return;
122 | }
123 | /* }}} */
124 |
125 | /* {{{ private Array split() */
126 | /**
127 | * 数据切片
128 | *
129 | * @access private
130 | * @param Integer $count
131 | * @return Array
132 | */
133 | private function split($count)
134 | {
135 | $split = array();
136 | $count = max(0, (int)$count);
137 | while ($count > $this->maxcnt) {
138 | $count -= $this->cubage;
139 | $split[] = $this->cubage;
140 | }
141 | $split[] = $count;
142 |
143 | return $split;
144 | }
145 | /* }}} */
146 |
147 | /* {{{ private Integer search() */
148 | /**
149 | * 二分法查找第一个小于$number的值
150 | *
151 | * @access private
152 | * @param Integer $count
153 | * @return Integer
154 | */
155 | private function search($number, $left = 0, $right = -1)
156 | {
157 | $count = count($this->slices);
158 | $right = ($right < 0) ? $count : (int)$right;
159 | $middle = (int)(($left + $right) / 2);
160 | $comp = $this->slices[$middle]['size'];
161 | if ($comp <= $number && ($middle - 1 <= $left || $this->slices[$middle - 1]['size'] > $number)) {
162 | for ($i = $middle; $i < $count; $i++) {
163 | if (empty($this->marks[$i])) {
164 | return $i;
165 | }
166 | }
167 |
168 | return -1;
169 | }
170 |
171 | if (abs($right - $left) <= 1) {
172 | return -1;
173 | }
174 |
175 | if ($comp > $number) {
176 | return $this->search($number, $middle, $right);
177 | }
178 |
179 | return $this->search($number, $left, $middle);
180 | }
181 | /* }}} */
182 |
183 | }
184 |
185 |
--------------------------------------------------------------------------------
/app/control/agent.php:
--------------------------------------------------------------------------------
1 | |
7 | // +------------------------------------------------------------------------+
8 | //
9 |
10 | namespace Myfox\App\Control;
11 |
12 | use \Myfox\Lib\Context;
13 | use \Myfox\App\Security;
14 |
15 | class Agent extends \Myfox\App\Controller
16 | {
17 |
18 | /* {{{ private static void output() */
19 | /**
20 | * 消息输出
21 | *
22 | * @access private static
23 | * @return void
24 | */
25 | private static function output($code, $message, $data = '')
26 | {
27 | @header('Content-Type: text/plain;charset=utf-8');
28 | $header = sprintf("[%u] %s", $code, trim($message));
29 | echo $header, "\n", is_scalar($data) ? (string)$data : json_encode($data);
30 | }
31 | /* }}} */
32 |
33 | /* {{{ private static Integer access() */
34 | private static function access()
35 | {
36 | $secure = new \Myfox\App\Security(__DIR__ . '/../../etc/secure/agent.ini');
37 | return $secure->priority(Context::userip());
38 | }
39 | /* }}} */
40 |
41 | /* {{{ public void actionQueque() */
42 | /**
43 | * 获取agent任务队列
44 | *
45 | * @access public
46 | * @return void
47 | */
48 | public function actionQueque($gets, $data = null)
49 | {
50 | }
51 | /* }}} */
52 |
53 | /* {{{ public void actionCommit() */
54 | /**
55 | * 更新队列状态
56 | *
57 | * @access public
58 | * @return void
59 | */
60 | public function actionCommit($gets, $data)
61 | {
62 | }
63 | /* }}} */
64 |
65 | /* {{{ public void actionCreatetable() */
66 | /**
67 | * 创建表
68 | *
69 | * @access public
70 | * @return void
71 | */
72 | public function actionCreatetable($gets, $data = null)
73 | {
74 | }
75 | /* }}} */
76 |
77 | /* {{{ public void actionDroptable() */
78 | /**
79 | * 删除表
80 | *
81 | * @access public
82 | * @return void
83 | */
84 | public function actionDroptable($gets, $data = null)
85 | {
86 | }
87 | /* }}} */
88 |
89 | }
90 |
91 |
--------------------------------------------------------------------------------
/app/controller.php:
--------------------------------------------------------------------------------
1 | |
7 | // +------------------------------------------------------------------------+
8 | //
9 | // $Id: controller.php 22 2010-04-15 16:28:45Z zhangxc83 $
10 |
11 | namespace Myfox\App;
12 |
13 | class Controller
14 | {
15 |
16 | /* {{{ public void __construct() */
17 | /**
18 | * 构造函数
19 | *
20 | * @access public
21 | * @return void
22 | */
23 | public function __construct()
24 | {
25 | }
26 | /* }}} */
27 |
28 | /* {{{ public void execute() */
29 | /**
30 | * 执行请求
31 | *
32 | * @access public
33 | * @return void
34 | */
35 | public function execute($action, $param, $post = null)
36 | {
37 | $action = strtolower(trim($action));
38 | if (empty($action)) {
39 | $action = 'index';
40 | }
41 |
42 | $method = sprintf('action%s', ucfirst($action));
43 | if (!method_exists($this, $method)) {
44 | throw new \Myfox\Lib\Exception(sprintf('Undefined action named as "%s"', $action));
45 | }
46 |
47 | return $this->$method($param, $post);
48 | }
49 | /* }}} */
50 |
51 | /* {{{ protected void actionIndex() */
52 | /**
53 | * 默认动作
54 | *
55 | * @access protected
56 | * @return void
57 | */
58 | protected function actionIndex($param, $post = null)
59 | {
60 | echo '';
61 | }
62 | /* }}} */
63 |
64 | /* {{{ protected static vars() */
65 | /**
66 | * 从一组变量中获取某个值
67 | *
68 | * @access protected static
69 | * @return Mixture
70 | */
71 | protected static function vars($name /*, $set1, $set2, ...*/)
72 | {
73 | $args = func_get_args();
74 | $name = trim(array_shift($args));
75 |
76 | foreach ($args AS $map) {
77 | if (isset($map[$name])) {
78 | return $map[$name];
79 | }
80 | }
81 |
82 | return null;
83 | }
84 | /* }}} */
85 |
86 | }
87 |
88 |
--------------------------------------------------------------------------------
/app/dispatcher.php:
--------------------------------------------------------------------------------
1 | |
7 | // +------------------------------------------------------------------------+
8 | //
9 | // $Id: dispatcher.php 18 2010-04-13 15:40:37Z zhangxc83 $
10 |
11 | namespace Myfox\App;
12 |
13 | class Dispatcher
14 | {
15 |
16 | /* {{{ 成员变量 */
17 |
18 | private $url;
19 |
20 | private $log;
21 |
22 | private $prefix;
23 |
24 | private $config;
25 |
26 | private static $timeout = false;
27 |
28 | /* }}} */
29 |
30 | /* {{{ public static void run() */
31 | /**
32 | * 处理器入口
33 | *
34 | * @access public static
35 | * @return void
36 | */
37 | public static function run($ini, $url, $post = null)
38 | {
39 | $dsp = new self($ini);
40 | $dsp->dispach($url, $post);
41 |
42 | if (empty($GLOBALS['__in_debug_tools']) && function_exists('fastcgi_finish_request')) {
43 | fastcgi_finish_request();
44 | }
45 | }
46 | /* }}} */
47 |
48 | /* {{{ public void shutdownCallBack() */
49 | /**
50 | * 请求结束时的回调函数
51 | *
52 | * @access public
53 | * @return void
54 | */
55 | public function shutdownCallBack()
56 | {
57 | if (true === self::$timeout) {
58 | $this->log->error('RUN_TIMEOUT', array(
59 | 'url' => $this->url,
60 | ));
61 | }
62 | }
63 | /* }}} */
64 |
65 | /* {{{ private void __construct() */
66 | /**
67 | * 构造函数
68 | *
69 | * @access public
70 | * @param String $ini
71 | * @return void
72 | */
73 | private function __construct($ini)
74 | {
75 | require_once __DIR__ . '/application.php';
76 | Application::init($ini);
77 |
78 | $this->config = \Myfox\Lib\Config::instance('default');
79 | $this->prefix = rtrim($this->config->get('url.prefix', ''), '/');
80 |
81 | $logurl = $this->config->get('log/default');
82 | if (empty($logurl)) {
83 | $this->log = new \Myfox\Lib\BlackHole();
84 | } else {
85 | $this->log = new \Myfox\Lib\Log($logurl);
86 | }
87 | }
88 | /* }}} */
89 |
90 | /* {{{ private void dispach() */
91 | /**
92 | * 分发处理
93 | *
94 | * @access private
95 | * @return void
96 | */
97 | private function dispach($url, $post = null)
98 | {
99 | $this->url = preg_replace(
100 | sprintf('/^\/?%s/is', strtr($this->prefix, array('/' => '\\/'))),
101 | '', $url, 1
102 | );
103 |
104 | $url = new \Myfox\Lib\Parser\Url($this->url);
105 | $module = $url->module();
106 | if (empty($module)) {
107 | $ctrl = sprintf('Myfox\App\Controller');
108 | } else {
109 | $ctrl = sprintf('Myfox\App\Control\%s', ucfirst(strtolower($module)));
110 | }
111 |
112 | set_time_limit($this->config->get('run.timeout', 30));
113 | register_shutdown_function(array(&$this, 'shutdownCallBack'));
114 | try {
115 | self::$timeout = true;
116 |
117 | parse_str($post, $post);
118 | $ctrl = new $ctrl();
119 | $ctrl->execute($url->action(), $url->param(), array_map('trim', $post));
120 | $this->log->debug('REQUEST', array(
121 | 'url' => $this->url,
122 | 'post' => $post,
123 | ));
124 | self::$timeout = false;
125 | } catch (\Exception $e) {
126 | self::$timeout = false;
127 | $this->log->error('EXCEPTION', array(
128 | 'url' => $this->url,
129 | 'post' => $post,
130 | 'error' => $e->getMessage(),
131 | ));
132 | }
133 | }
134 | /* }}} */
135 |
136 | }
137 |
138 |
--------------------------------------------------------------------------------
/app/model/consist.php:
--------------------------------------------------------------------------------
1 | |
7 | // +------------------------------------------------------------------------+
8 | //
9 | // $Id: consist.php 22 2010-04-15 16:28:45Z zhangxc83 $
10 | //
11 | //
12 | namespace Myfox\App\Model;
13 |
14 | use \Myfox\App\Model\Server;
15 |
16 | class Consist
17 | {
18 |
19 | /* {{{ 静态常量 */
20 |
21 | const COUNTS = 1;
22 | const BITXOR = 2;
23 |
24 | /* }}} */
25 |
26 | /* {{{ 静态变量 */
27 |
28 | private static $callers = array(
29 | self::COUNTS => 'counts',
30 | self::BITXOR => 'bitxor',
31 | );
32 |
33 | private static $columns = array();
34 |
35 | /* }}} */
36 |
37 | /* {{{ public static Mixture check() */
38 | /**
39 | * 检查多个服务器之间表的数据一致性
40 | *
41 | * @access public static
42 | * @return Mixture
43 | */
44 | public static function check($table, $server, $type = null)
45 | {
46 | if (empty($server)) {
47 | throw new \Myfox\Lib\Exception('Empty server list for consistency check');
48 | }
49 |
50 | $dbname = self::idxname($table);
51 | if (empty(self::$columns[$dbname]) && !self::describe($table, $server, $dbname)) {
52 | return true;
53 | }
54 |
55 | $querys = array();
56 | $type = empty($type) ? (self::COUNTS | self::BITXOR) : (int)$type;
57 | foreach (self::$callers AS $key => $call) {
58 | if (0 < ($key & $type)) {
59 | $querys[] = self::$call($table);
60 | }
61 | }
62 |
63 | if (empty($querys)) {
64 | return false;
65 | }
66 |
67 | $query = implode(' UNION ALL ', $querys);
68 | $pools = array();
69 | foreach ((array)$server AS $name) {
70 | $pools[$name] = Server::instance($name)->getlink()->async($query);
71 | }
72 |
73 | $result = array();
74 | foreach ($pools AS $name => $id) {
75 | $db = Server::instance($name)->getlink();
76 | foreach ((array)$db->getAll($db->wait($id)) AS $row) {
77 | $result[$name][$row['c0']] = $row['c1'];
78 | }
79 | }
80 |
81 | $item = reset($result);
82 | $index = key($result);
83 | foreach ($result AS $key => $val) {
84 | if ($key == $index) {
85 | continue;
86 | }
87 |
88 | if ($item != $val) {
89 | return $result;
90 | }
91 | }
92 |
93 | return true;
94 | }
95 | /* }}} */
96 |
97 | /* {{{ private static String idxname() */
98 | /**
99 | * 获取归一化的表键
100 | *
101 | * @access private static
102 | * @return String
103 | */
104 | private static function idxname($table)
105 | {
106 | $tbname = explode('.', $table);
107 | return strtolower(preg_replace('/_\d+$/', '', reset($tbname)));
108 | }
109 | /* }}} */
110 |
111 | /* {{{ private static void describe() */
112 | /**
113 | * 获取表结构
114 | *
115 | * @access private static
116 | * @return void
117 | */
118 | private static function describe($table, $server, $dbname)
119 | {
120 | foreach ((array)$server AS $name) {
121 | $mysql = Server::instance($name)->getlink();
122 | $column = $mysql->getAll($mysql->query(sprintf('DESC %s', $table)));
123 | if (!empty($column)) {
124 | break;
125 | }
126 | }
127 |
128 | if (empty($column)) {
129 | return false;
130 | }
131 |
132 | $pk = 'autokid';
133 | $cl = array();
134 | foreach ((array)$column AS $row) {
135 | if (0 === strcasecmp('PRI', $row['Key'])) {
136 | $pk = $row['Field'];
137 | }
138 |
139 | if (0 !== strcasecmp('auto_increment', $row['Extra'])) {
140 | $cl[] = $row['Field'];
141 | }
142 | }
143 |
144 | self::$columns[$dbname] = array(
145 | 'prikey' => $pk,
146 | 'column' => implode(',', $cl),
147 | );
148 |
149 | return true;
150 | }
151 | /* }}} */
152 |
153 | /* {{{ private static String counts() */
154 | /**
155 | * 求表的总行数
156 | *
157 | * @access private static
158 | * @return String
159 | */
160 | private static function counts($table)
161 | {
162 | return sprintf("SELECT 'counts' AS c0, COUNT(1) AS c1 FROM %s", $table);
163 | }
164 | /* }}} */
165 |
166 | /* {{{ private static String bitxor() */
167 | /**
168 | * 求表的BITXOR值
169 | *
170 | * @access private static
171 | * @return String
172 | */
173 | private static function bitxor($table)
174 | {
175 | $dbname = self::idxname($table);
176 | $query = sprintf(
177 | "SELECT crc32(CONCAT_WS('', %s)) AS a FROM %s ORDER BY %s DESC LIMIT 100",
178 | self::$columns[$dbname]['column'], $table, self::$columns[$dbname]['prikey']
179 | );
180 |
181 | return sprintf(
182 | "SELECT 'bitxor' AS c0, BIT_XOR(a) AS c1 FROM (%s) b",
183 | $query
184 | );
185 | }
186 | /* }}} */
187 |
188 | }
189 |
--------------------------------------------------------------------------------
/app/model/server.php:
--------------------------------------------------------------------------------
1 | |
5 | // +------------------------------------------------------------------------+
6 | //
7 | // $Id: server.php 18 2010-04-13 15:40:37Z zhangxc83 $
8 |
9 | namespace Myfox\App\Model;
10 |
11 | class Server
12 | {
13 |
14 | /* {{{ 静态常量 */
15 |
16 | const TYPE_REALITY = 1;
17 | const TYPE_ARCHIVE = 2;
18 |
19 | const STAT_ONLINE = 0;
20 | const STAT_PREPARE = 1;
21 | const STAT_ISDOWN = 9;
22 |
23 | /* }}} */
24 |
25 | /* {{{ 静态变量 */
26 |
27 | private static $objects = array();
28 |
29 | private static $mysql = null;
30 |
31 | /* }}} */
32 |
33 | /* {{{ 成员变量 */
34 |
35 | private $dbname = '';
36 |
37 | private $option = null;
38 |
39 | /* }}} */
40 |
41 | /* {{{ public static Object instance() */
42 | /**
43 | * 获取一个实例
44 | *
45 | * @access public static
46 | * @return Object
47 | */
48 | public static function instance($name)
49 | {
50 | $name = strtolower(trim($name));
51 | if (empty(self::$objects[$name])) {
52 | self::$objects[$name] = new self($name);
53 | }
54 |
55 | return self::$objects[$name];
56 | }
57 | /* }}} */
58 |
59 | /* {{{ public Mixture option() */
60 | /**
61 | * 返回属性
62 | *
63 | * @access public
64 | * @return Mixture
65 | */
66 | public function option($key, $default = null)
67 | {
68 | $this->init();
69 | $key = strtolower(trim($key));
70 | return isset($this->option[$key]) ? $this->option[$key] : $default;
71 | }
72 | /* }}} */
73 |
74 | /* {{{ public Object getlink() */
75 | /**
76 | * 获取数据库连接
77 | *
78 | * @access public
79 | * @return object
80 | */
81 | public function getlink()
82 | {
83 | $this->init();
84 | $id = sprintf('__%s', $this->dbname);
85 |
86 | try {
87 | return \Myfox\Lib\Mysql::instance($id);
88 | } catch (\Exception $e) {
89 | \Myfox\Lib\Mysql::register($id, array(
90 | 'persist' => false,
91 | 'master' => array(sprintf(
92 | 'mysql://%s:%s@%s:%d', rawurlencode($this->option['write_user']),
93 | rawurlencode($this->option('write_pass')),
94 | $this->option['conn_host'], $this->option['conn_port']
95 | )),
96 | 'slave' => array(sprintf(
97 | 'mysql://%s:%s@%s:%d', rawurlencode($this->option['read_user']),
98 | rawurlencode($this->option('read_pass')),
99 | $this->option['conn_host'], $this->option['conn_port']
100 | )),
101 | ));
102 |
103 | return \Myfox\Lib\Mysql::instance($id);
104 | }
105 | }
106 | /* }}} */
107 |
108 | /* {{{ private void __construct() */
109 | /**
110 | * 构造函数
111 | *
112 | * @access private
113 | * @return void
114 | */
115 | private function __construct($name)
116 | {
117 | if (empty(self::$mysql)) {
118 | self::$mysql = \Myfox\Lib\Mysql::instance('default');
119 | }
120 |
121 | $this->dbname = $name;
122 | $this->option = null;
123 | }
124 | /* }}} */
125 |
126 | /* {{{ private void init() */
127 | /**
128 | * 初始化server对象
129 | *
130 | * @access private
131 | * @return void
132 | */
133 | private function init()
134 | {
135 | if (null !== $this->option) {
136 | return;
137 | }
138 |
139 | $this->option = self::$mysql->getRow(self::$mysql->query(sprintf(
140 | "SELECT * FROM %shost_list WHERE host_name = '%s'",
141 | self::$mysql->option('prefix'), self::$mysql->escape($this->dbname)
142 | )));
143 | if (empty($this->option)) {
144 | throw new \Myfox\Lib\Exception(sprintf(
145 | 'Undefined mysql server named as "%s"', $this->dbname
146 | ));
147 | }
148 | $this->option = array_change_key_case((array)$this->option, CASE_LOWER);
149 | }
150 | /* }}} */
151 |
152 | }
153 |
--------------------------------------------------------------------------------
/app/security.php:
--------------------------------------------------------------------------------
1 | |
9 | // +------------------------------------------------------------------------+
10 |
11 | namespace Myfox\App;
12 |
13 | use \Myfox\Lib\Config;
14 |
15 | class Security
16 | {
17 |
18 | /* {{{ 成员变量 */
19 |
20 | private $config;
21 |
22 | /* }}} */
23 |
24 | /* {{{ public void __construct() */
25 | /**
26 | *
27 | * 构造函数
28 | *
29 | * @access public
30 | * @return void
31 | */
32 | public function __construct($ini)
33 | {
34 | $this->config = new Config($ini);
35 | }
36 | /* }}} */
37 |
38 | /* {{{ public Boolean priority() */
39 | /**
40 | * 获取优先级
41 | *
42 | * @access public
43 | * @return Boolean true or false
44 | */
45 | public function priority($remote)
46 | {
47 | $allows = (array)$this->config->get('priority');
48 | if (isset($allows[$remote])) {
49 | return $allows[$remote];
50 | }
51 |
52 | foreach ($allows AS $rule => $value) {
53 | if (self::ipmatch($remote, $rule)) {
54 | return $value;
55 | }
56 | }
57 |
58 | return false;
59 | }
60 | /* }}} */
61 |
62 | /* {{{ public Mixture modify() */
63 | /**
64 | * 修改参数
65 | *
66 | * @access public
67 | * @return Mixture
68 | */
69 | public function modify($param)
70 | {
71 | return (array)$this->config->get('option') + (array)$param;
72 | }
73 | /* }}} */
74 |
75 | /* {{{ private static Boolean ipmatch() */
76 | /**
77 | * 判断IP地址匹配
78 | *
79 | * @access private static
80 | * @return Boolean true or false
81 | */
82 | private static function ipmatch($ip, $str)
83 | {
84 | $ps = -1;
85 | $at = array_filter(explode('*', trim($str)));
86 | foreach ($at AS $tk) {
87 | $ps = strpos($ip, $tk, $ps + 1);
88 | if (false === $ps) {
89 | return false;
90 | }
91 | }
92 |
93 | return true;
94 | }
95 | /* }}} */
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/app/setting.php:
--------------------------------------------------------------------------------
1 | |
7 | // +------------------------------------------------------------------------+
8 | //
9 | // $Id: setting.php 22 2010-04-15 16:28:45Z zhangxc83 $
10 |
11 | namespace Myfox\App;
12 |
13 | class Setting
14 | {
15 |
16 | /* {{{ 静态变量 */
17 |
18 | public static $queries = 0;
19 |
20 | private static $mysql;
21 |
22 | private static $expire = 60;
23 |
24 | private static $option = array();
25 |
26 | /* }}} */
27 |
28 | /* {{{ public static void init() */
29 | /**
30 | * 初始化
31 | *
32 | * @access public static
33 | * @return void
34 | */
35 | public static function init($expire = 60)
36 | {
37 | self::$expire = (int)$expire;
38 | if (empty(self::$mysql)) {
39 | self::$mysql = \Myfox\Lib\Mysql::instance('default');
40 | }
41 | }
42 | /* }}} */
43 |
44 | /* {{{ public static Mixture get() */
45 | /**
46 | * 获取配置值
47 | *
48 | * @access public static
49 | * @return Mixture
50 | */
51 | public static function get($key, $own = '')
52 | {
53 | $now = microtime(true);
54 | $idx = self::idx($key, $own);
55 | if (!isset(self::$option[$idx]) || $now > self::$option[$idx]['t']) {
56 | self::init(self::$expire);
57 |
58 | $option = self::$mysql->getAll(self::$mysql->query(sprintf(
59 | "SELECT cfgname,ownname,cfgvalue FROM %ssettings WHERE ownname='%s'",
60 | self::$mysql->option('prefix', ''),
61 | self::$mysql->escape($own)
62 | )));
63 | foreach ((array)$option AS $row) {
64 | self::$option[self::idx($row['cfgname'], $row['ownname'])] = array(
65 | 't' => $now + self::$expire,
66 | 'v' => $row['cfgvalue'],
67 | );
68 | }
69 | self::$queries++;
70 | }
71 |
72 | return isset(self::$option[$idx]['v']) ? self::$option[$idx]['v'] : null;
73 | }
74 | /* }}} */
75 |
76 | /* {{{ public static Boolean set() */
77 | /**
78 | * 设置配置值
79 | *
80 | * @access public static
81 | * @return Boolean true or false
82 | */
83 | public static function set($key, $value, $own = '', $comma = true)
84 | {
85 | unset(self::$option[self::idx($key, $own)]);
86 | self::init(self::$expire);
87 | self::$queries++;
88 |
89 | $time = date('Y-m-d H:i:s');
90 | $value = self::$mysql->escape($value);
91 | if ($comma) {
92 | $value = sprintf("'%s'", $value);
93 | }
94 |
95 | return self::$mysql->query(sprintf(
96 | "INSERT INTO %ssettings (cfgname,ownname,cfgvalue,addtime,modtime) VALUES ('%s','%s',%s,'%s','%s')" .
97 | " ON DUPLICATE KEY UPDATE modtime = '%s',cfgvalue=%s",
98 | self::$mysql->option('prefix', ''), self::$mysql->escape($key), self::$mysql->escape($own),
99 | $value, $time, $time, $time, $value
100 | ));
101 | }
102 | /* }}} */
103 |
104 | /* {{{ private static string idx() */
105 | /**
106 | * 组织KEY名字
107 | *
108 | * @access private static
109 | * @return String
110 | */
111 | private static function idx($key, $own)
112 | {
113 | return strtolower(sprintf('%s/%s', trim($own), trim($key)));
114 | }
115 | /* }}} */
116 |
117 | }
118 |
--------------------------------------------------------------------------------
/app/task/checktable.php:
--------------------------------------------------------------------------------
1 | |
10 | // +------------------------------------------------------------------------+
11 | //
12 | // $Id: checktable.php 18 2010-04-13 15:40:37Z zhangxc83 $
13 | //
14 |
15 | namespace Myfox\App\Task;
16 |
17 | use \Myfox\App\Model\Server;
18 |
19 | class Checktable extends \Myfox\App\Task
20 | {
21 |
22 | /* {{{ public Integer execute() */
23 | /**
24 | * 执行check table操作
25 | */
26 | public function execute()
27 | {
28 | if (!$this->isReady('host', 'path')) {
29 | return self::IGNO;
30 | }
31 |
32 | $exist = sprintf('DESC %s', self::$mysql->escape($this->option('path')));
33 | $query = sprintf('CHECK TABLE %s', self::$mysql->escape($this->option('path')));
34 |
35 | $return = self::FAIL;
36 | $ignore = array_flip(explode(',', (string)$this->status));
37 | foreach (explode(',', trim($this->option('host', '{}'))) AS $id) {
38 | if (!isset(self::$hosts[$id]) || isset($ignore[$id])) {
39 | continue;
40 | }
41 |
42 | $mysql = Server::instance(self::$hosts[$id]['name'])->getlink();
43 | if ((bool)$mysql->query($exist)) {
44 |
45 | // @see: http://dev.mysql.com/doc/refman/5.1/en/check-table.html
46 |
47 | $ok = false;
48 | foreach (array('FAST', 'MEDIUM', 'MEDIUM', 'MEDIUM', 'EXTENDED') AS $mode) {
49 | if (self::success($mysql->getAll($mysql->query(sprintf('%s %s', $query, $mode))))) {
50 | $ok = true;
51 | break;
52 | }
53 | }
54 |
55 | if (!$ok) {
56 | continue;
57 | }
58 | }
59 |
60 | $return = self::SUCC;
61 | $ignore[$id] = true;
62 | }
63 |
64 | return $return;
65 | }
66 | /* }}} */
67 |
68 | /* {{{ private static Boolean success() */
69 | /**
70 | * 判断check table是否成功
71 | *
72 | * @access private static
73 | * @return Boolean true or false
74 | */
75 | private static function success($result)
76 | {
77 | if (empty($result) || !is_array($result)) {
78 | return false;
79 | }
80 |
81 | foreach ((array)$result AS $row) {
82 | if (!isset($row['Msg_type']) || !isset($row['Msg_text'])) {
83 | continue;
84 | }
85 | if ('status' == $row['Msg_type'] && 0 != strcasecmp('OK', $row['Msg_text'])) {
86 | return false;
87 | }
88 | }
89 |
90 | return true;
91 | }
92 | /* }}} */
93 |
94 | }
95 |
96 |
--------------------------------------------------------------------------------
/app/task/delete.php:
--------------------------------------------------------------------------------
1 | |
5 | // +------------------------------------------------------------------------+
6 | //
7 | // $Id: delete.php 18 2010-04-13 15:40:37Z zhangxc83 $
8 | //
9 |
10 | namespace Myfox\App\Task;
11 |
12 | use \Myfox\App\Model\Server;
13 |
14 | class Delete extends \Myfox\App\Task
15 | {
16 |
17 | /* {{{ 成员变量 */
18 |
19 | private $optimize = false;
20 |
21 | private $dbpools = array();
22 |
23 | /* }}} */
24 |
25 | /* {{{ public Integer execute() */
26 | public function execute()
27 | {
28 | if (!$this->isReady('host', 'path')) {
29 | return self::IGNO;
30 | }
31 |
32 | self::metadata($flush);
33 |
34 | $where = trim((string)$this->option('where'));
35 | if ('' == $where) {
36 | $query = sprintf('DROP TABLE IF EXISTS %s', $this->option('path'));
37 | } else {
38 | $query = sprintf('DELETE FROM %s WHERE %s', $this->option('path'), $where);
39 | $this->optimize = true;
40 | }
41 |
42 | $ignore = array_flip(explode(',', (string)$this->status));
43 | foreach (explode(',', trim($this->option('host', '{}'))) AS $id) {
44 | if (!isset(self::$hosts[$id]) || isset($ignore[$id])) {
45 | continue;
46 | }
47 |
48 | $ignore[$id] = true;
49 | $server = self::$hosts[$id];
50 | $db = Server::instance($server['name'])->getlink();
51 | $this->dbpools[] = array($db, $db->async($query), $server['name']);
52 | }
53 |
54 | return self::WAIT;
55 | }
56 | /* }}} */
57 |
58 | /* {{{ public Integer wait() */
59 | public function wait()
60 | {
61 | $rt = self::SUCC;
62 | $sc = array();
63 | foreach ($this->dbpools AS $pool) {
64 | list($db, $key, $host) = $pool;
65 | if (false === $key || false === $db->wait($key)) {
66 | $rt = self::FAIL;
67 | $this->setError($db->lastError($key));
68 | } else {
69 | $sc[$host] = true;
70 | if ($this->optimize) {
71 | $db->async(sprintf('OPTIMIZE TABLE %s', $this->option('path')));
72 | }
73 | }
74 | }
75 |
76 | $this->dbpools = array();
77 | $this->result = implode(',', array_keys($sc));
78 |
79 | return $rt;
80 | }
81 | /* }}} */
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/app/task/example.php:
--------------------------------------------------------------------------------
1 | |
5 | // +------------------------------------------------------------------------+
6 | //
7 | // $Id: example.php 18 2010-04-13 15:40:37Z zhangxc83 $
8 | //
9 |
10 | namespace Myfox\App\Task;
11 |
12 | class Example extends \Myfox\App\Task
13 | {
14 |
15 | public $counter = 0;
16 |
17 | public function execute()
18 | {
19 | if (!$this->isReady('type')) {
20 | return self::FAIL;
21 | }
22 |
23 | $this->counter++;
24 | return self::SUCC;
25 | }
26 |
27 | public function wait()
28 | {
29 | $this->setError('None sense for wait');
30 | return self::FAIL;
31 | }
32 |
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/app/task/ready.php:
--------------------------------------------------------------------------------
1 | |
7 | // +------------------------------------------------------------------------+
8 | //
9 |
10 | namespace Myfox\App\Task;
11 |
12 | use \Myfox\App\Setting;
13 | use \Myfox\App\Model\Router;
14 |
15 | class Ready extends \Myfox\App\Task
16 | {
17 |
18 | /* {{{ public Integer wait() */
19 | public function wait()
20 | {
21 | return self::SUCC;
22 | }
23 | /* }}} */
24 |
25 | /* {{{ public Integer execute() */
26 | public function execute()
27 | {
28 | if (!$this->isReady('thedate', 'priority')) {
29 | return self::IGNO;
30 | }
31 |
32 | $date1 = date('Ymd', strtotime(Setting::get('last_date')));
33 | $date2 = date('Ymd', strtotime($this->option('thedate')));
34 | if ($date1 >= $date2) {
35 | return self::IGNO;
36 | }
37 |
38 | $mysql = \Myfox\Lib\Mysql::instance('default');
39 | $count = (int)$mysql->getOne($mysql->query(sprintf(
40 | 'SELECT COUNT(*) FROM %stask_queque WHERE autokid < %u AND priority <= %u '.
41 | "AND task_flag NOT IN (%d,%d) AND task_type='import'",
42 | $mysql->option('prefix'), $this->id, $this->option('priority'),
43 | \Myfox\App\Queque::FLAG_IGNO, \Myfox\App\Queque::FLAG_DONE
44 | )));
45 |
46 | if ($count > 0) {
47 | $this->setError(sprintf('Waiting for %d import task(s)', $count));
48 | return self::FAIL;
49 | }
50 |
51 | if (!Router::flush()) {
52 | $this->setError('flush route failed');
53 | return self::FAIL;
54 | }
55 |
56 | Setting::set('last_date', $date2, '', '集群数据最新日期');
57 |
58 | // TODO:
59 | // CALL nodefox to reload route info info cache
60 |
61 | return self::SUCC;
62 | }
63 | /* }}} */
64 |
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/app/task/rsplit.php:
--------------------------------------------------------------------------------
1 | |
7 | // +------------------------------------------------------------------------+
8 | //
9 | // $Id: rsplit.php 18 2010-04-13 15:40:37Z zhangxc83 $
10 | //
11 |
12 | namespace Myfox\App\Task;
13 |
14 | use \Myfox\Lib\Config;
15 | use \Myfox\Lib\Fileset;
16 | use \Myfox\Lib\Fsplit;
17 |
18 | use \Myfox\App\Queque;
19 | use \Myfox\App\Model\Router;
20 | use \Myfox\App\Model\Table;
21 |
22 | class Rsplit extends \Myfox\App\Task
23 | {
24 |
25 | /* {{{ public Integer execute() */
26 | /**
27 | * OPTIONS:
28 | * ----------------------------------------------------------------------
29 | * @file : complete file url, such as "ftp://user:pass@hostname/path"
30 | * @table : name of the logic table
31 | * @route : route value of the file, ex: thedate=20111001,cid=210
32 | * @lines :
33 | * ---------------------------------------------------------------------
34 | */
35 | public function execute()
36 | {
37 | if (!$this->isReady('table', 'route', 'lines', 'file', 'priority')) {
38 | return self::IGNO;
39 | }
40 |
41 | $table = Table::instance($this->option('table'));
42 | if (!$table->get('autokid')) {
43 | $this->setError(sprintf('Undefined table named as "%s".', $this->option('table')));
44 | return self::IGNO;
45 | }
46 |
47 | try {
48 | $routes = Router::set($this->option('table'), array(array(
49 | 'field' => Router::parse($this->option('route'), $this->option('table')),
50 | 'count' => (int)$this->option('lines')
51 | )));
52 | if (!is_array($routes)) {
53 | $this->setError('route failed.');
54 | return self::FAIL;
55 | }
56 |
57 | $config = Config::instance('default');
58 | $fname = Fileset::getfile($this->option('file'), $config->get('path/download'));
59 | if (empty($fname)) {
60 | $this->setError(sprintf('getfile:%s', Fileset::lastError()));
61 | return self::FAIL;
62 | }
63 |
64 | $splits = array();
65 | foreach ($routes AS $key => $bucket) {
66 | foreach ($bucket AS $item) {
67 | $splits[] = $item['rows'];
68 | }
69 | }
70 |
71 | $fsplit = new Fsplit($fname, "\n", 16777216);
72 | $chunks = $fsplit->split($splits, $config->get('path/filesplit'));
73 | if (empty($chunks)) {
74 | $this->setError(sprintf('fsplit failed,%s', $fsplit->lastError()));
75 | return self::FAIL;
76 | }
77 |
78 | if (preg_match('/^\w+:/i', $this->option('file'))) {
79 | @unlink($fname);
80 | }
81 |
82 | $result = array();
83 | $queque = Queque::instance();
84 | foreach ($routes AS $key => $bucket) {
85 | foreach ($bucket AS $item) {
86 | $fname = array_shift($chunks);
87 | if (empty($fname)) {
88 | break 2;
89 | }
90 |
91 | $info = array(
92 | 'table' => $this->option('table'),
93 | 'route' => $key,
94 | 'file' => $fname,
95 | 'bucket' => $item['table'],
96 | 'hosts' => $item['hosts'],
97 | );
98 |
99 | $option = array(
100 | 'openrace' => 0,
101 | 'priority' => (int)$this->option('priority') - 1,
102 | 'trytimes' => 3,
103 | 'task_flag' => Queque::FLAG_WAIT,
104 | 'adduser' => 'rsplit',
105 | );
106 |
107 | if (!$queque->insert('import', $info, -1, $option)) {
108 | $this->setError(sprintf('queque: %s', $queque->lastError()));
109 | return self::FAIL;
110 | }
111 |
112 | $result[] = $queque->lastId();
113 | }
114 | }
115 | $this->result = implode(',', $result);
116 | } catch (\Exception $e) {
117 | $this->setError($e->getMessage());
118 | return self::FAIL;
119 | }
120 |
121 | return self::SUCC;
122 | }
123 | /* }}} */
124 |
125 | }
126 |
127 |
--------------------------------------------------------------------------------
/app/worker.php:
--------------------------------------------------------------------------------
1 | |
9 | // +------------------------------------------------------------------------+
10 |
11 | namespace Myfox\App;
12 |
13 | class Worker
14 | {
15 |
16 | /* {{{ 成员变量 */
17 |
18 | protected $option = array();
19 |
20 | protected $log = null;
21 |
22 | /* }}} */
23 |
24 | /* {{{ public void __construct() */
25 | /**
26 | * 构造函数
27 | *
28 | * @access public
29 | * @return void
30 | */
31 | public function __construct($option)
32 | {
33 | $this->option = (array)$option + $this->option;
34 | $this->cleanup();
35 | $this->log = \Myfox\Lib\Factory::getLog('daemon');
36 | }
37 | /* }}} */
38 |
39 | /* {{{ public void cleanup() */
40 | /**
41 | * 临时数据清理接口
42 | *
43 | * @access public
44 | * @return void
45 | */
46 | public function cleanup()
47 | {
48 | }
49 | /* }}} */
50 |
51 | /* {{{ public Boolean execute() */
52 | /**
53 | * 执行
54 | *
55 | * @access public
56 | * @return Boolean true or false (next loop)
57 | */
58 | public function execute($loop = true)
59 | {
60 | return (bool)$loop;
61 | }
62 | /* }}} */
63 |
64 | /* {{{ public Integer interval() */
65 | /**
66 | * 下次运行sleep时间(ms)
67 | *
68 | * @access public
69 | * @return Integer
70 | */
71 | public function interval()
72 | {
73 | return 1;
74 | }
75 | /* }}} */
76 |
77 | /* {{{ public String locker() */
78 | /**
79 | * 进程锁名字
80 | *
81 | * @access public
82 | * @return String
83 | */
84 | public function locker()
85 | {
86 | return '';
87 | }
88 | /* }}} */
89 |
90 | /* {{{ public static void breakup() */
91 | /**
92 | * 处理系统信号的断点
93 | *
94 | * @access public static
95 | * @return void
96 | */
97 | public static function breakup()
98 | {
99 | if (function_exists('pcntl_signal_dispatch')) {
100 | pcntl_signal_dispatch();
101 | }
102 | }
103 | /* }}} */
104 |
105 | }
106 |
107 |
--------------------------------------------------------------------------------
/app/worker/checktable.php:
--------------------------------------------------------------------------------
1 | |
9 | // +------------------------------------------------------------------------+
10 |
11 | namespace Myfox\App\Worker;
12 |
13 | use \Myfox\App\Model\Server;
14 |
15 | class Checktable extends \Myfox\App\Worker
16 | {
17 |
18 | /* {{{ 成员变量 */
19 |
20 | protected $option = array(
21 | 'sleep' => 300000, /**< 300s */
22 | 'full' => false, /**< full check */
23 | );
24 |
25 | /* }}} */
26 |
27 | /* {{{ public String locker() */
28 | /**
29 | * 进程锁名字
30 | *
31 | * @access public
32 | * @return String
33 | */
34 | public function locker()
35 | {
36 | return 'checktable';
37 | }
38 | /* }}} */
39 |
40 | /* {{{ public Integer interval() */
41 | /**
42 | * 下次运行sleep时间(ms)
43 | *
44 | * @access public
45 | * @return Integer
46 | */
47 | public function interval()
48 | {
49 | return $this->option['sleep'];
50 | }
51 | /* }}} */
52 |
53 | /* {{{ public Boolean execute() */
54 | /**
55 | * 执行
56 | *
57 | * @access public
58 | * @return Boolean true or false (next loop)
59 | */
60 | public function execute($loop = true)
61 | {
62 | $mysql = \Myfox\Lib\Mysql::instance('default');
63 |
64 | $count = 0;
65 | $query = sprintf('SELECT host_id AS id, host_name AS name FROM %shost_list', $mysql->option('prefix'));
66 | foreach ((array)$mysql->getAll($mysql->query($query)) AS $host) {
67 | $db = Server::instance($host['name'])->getlink();
68 | try {
69 | $databases = $db->getAll($db->query('SHOW DATABASES'));
70 | } catch (\Exception $e) {
71 | $this->log->error('EXCEPTION', $e->getMessage());
72 | continue;
73 | }
74 |
75 | foreach ((array)$databases AS $dbname) {
76 | $dbname = reset($dbname);
77 | if (preg_match('/^(mysql|test|information_schema)$/', $dbname) || !preg_match('/^\w+_\d+$/', $dbname)) {
78 | continue;
79 | }
80 |
81 | foreach ((array)$db->getAll($db->query('SHOW TABLE STATUS FROM ' . $dbname)) AS $row) {
82 | $table = sprintf('%s.%s', $dbname, $row['Name']);
83 | $logvar = array(
84 | 'server' => $host['name'],
85 | 'table' => $table,
86 | 'engine' => $row['Engine'],
87 | 'create' => $row['Create_time'],
88 | 'update' => $row['Update_time'],
89 | 'check' => $row['Check_time'],
90 | );
91 |
92 | if (true !== $this->option['full'] && $row['Check_time'] > $row['Update_time'] && !empty($row['Engine'])) {
93 | $this->log->debug('CHECK_IGN1', $logvar);
94 | continue;
95 | }
96 |
97 | switch (self::realcheck($table, $db)) {
98 | case 0:
99 | $this->log->debug('CHECK_IGN2', $logvar);
100 | break;
101 |
102 | case 1:
103 | $this->log->notice('CHECK_OK', $logvar);
104 | break;
105 |
106 | default:
107 | $this->log->debug('CHECK_FAIL', $logvar);
108 | break;
109 | }
110 |
111 | if (((++$count) % 10) == 0) {
112 | self::breakup();
113 | }
114 | }
115 | }
116 | }
117 |
118 | return false;
119 | }
120 | /* }}} */
121 |
122 | /* {{{ private static Integer realcheck() */
123 | /**
124 | * really check table
125 | *
126 | * @access private static
127 | * @return Integer
128 | */
129 | private static function realcheck($table, $mysql)
130 | {
131 | $query = sprintf('SELECT * FROM %s LIMIT 1', $table);
132 | if ($mysql->query($query)/*|| false === stripos($mysql->lastError(), ' is marked as crashed')*/) {
133 | return 0;
134 | }
135 |
136 | foreach (array('FAST', 'MEDIUM', 'MEDIUM', 'MEDIUM', 'EXTENDED') AS $mode) {
137 | $check = sprintf('CHECK TABLE %s %s', $table, $mode);
138 | if (self::success($mysql->getAll($mysql->query($check)))) {
139 | break;
140 | }
141 | }
142 |
143 | return $mysql->query($query) ? 1 : -1;
144 | }
145 | /* }}} */
146 |
147 | /* {{{ private static Boolean success() */
148 | /**
149 | * 判断check table是否成功
150 | *
151 | * @access private static
152 | * @return Boolean true or false
153 | */
154 | private static function success($result)
155 | {
156 | if (empty($result) || !is_array($result)) {
157 | return false;
158 | }
159 |
160 | foreach ((array)$result AS $row) {
161 | if (!isset($row['Msg_type']) || !isset($row['Msg_text'])) {
162 | continue;
163 | }
164 | if ('status' == $row['Msg_type'] && 0 == strcasecmp('OK', $row['Msg_text'])) {
165 | return true;
166 | }
167 | }
168 |
169 | return false;
170 | }
171 | /* }}} */
172 |
173 | }
174 |
--------------------------------------------------------------------------------
/app/worker/cleaner.php:
--------------------------------------------------------------------------------
1 | |
9 | // +------------------------------------------------------------------------+
10 |
11 | namespace Myfox\App\Worker;
12 |
13 | use \Myfox\App\Model\Router;
14 | use \Myfox\App\Queque;
15 |
16 | class Cleaner extends \Myfox\App\Worker
17 | {
18 |
19 | /* {{{ public Boolean execute() */
20 | /**
21 | * 执行函数
22 | *
23 | * @access public
24 | * @return Boolean true or false : next loop ?
25 | */
26 | public function execute($loop = true)
27 | {
28 | self::clean_invalidated_routes();
29 | return false;
30 | }
31 | /* }}} */
32 |
33 | /* {{{ public static void clean_invalidated_routes() */
34 | /**
35 | * 清理失效的路由
36 | *
37 | * @access public static
38 | * @return void
39 | */
40 | public static function clean_invalidated_routes()
41 | {
42 | $mysql = \Myfox\Lib\Mysql::instance('default');
43 | $query = sprintf("SHOW TABLES LIKE '%sroute_info%%'", $mysql->option('prefix'));
44 | foreach ((array)$mysql->getAll($mysql->query($query)) AS $table) {
45 | $table = end($table);
46 | $query = sprintf(
47 | 'SELECT autokid,hosts_list,real_table,route_text FROM %s WHERE route_flag IN (%d) AND modtime < %d',
48 | $table, Router::FLAG_IS_DELETED, time() - 100000
49 | );
50 |
51 | $ids = array();
52 | foreach ((array)$mysql->getAll($mysql->query($query)) AS $row) {
53 | $hosts = trim($row['hosts_list'], '{}$');
54 | if (!empty($hosts)) {
55 | $where = array();
56 | foreach (Router::parse($row['route_text']) AS $k => $v) {
57 | $where[] = sprintf('%s = %s', $k, $v);
58 | }
59 | $info = array(
60 | 'host' => $hosts,
61 | 'path' => $row['real_table'],
62 | 'where' => implode(' AND ', $where),
63 | );
64 |
65 | if (!Queque::instance()->insert('delete', $info, 0, array('adduser' => 'cleaner'))) {
66 | continue;
67 | }
68 | }
69 | $ids[] = (int)$row['autokid'];
70 | }
71 |
72 | $mysql->query(sprintf(
73 | 'DELETE FROM %s WHERE (route_flag = %d AND addtime < %d)%s',
74 | $table, Router::FLAG_PRE_IMPORT, time() - 10 * 86400,
75 | empty($ids) ? '' : sprintf(' OR (autokid IN (%s))', implode(',', $ids))
76 | ));
77 | }
78 | }
79 | /* }}} */
80 |
81 | }
82 |
83 |
--------------------------------------------------------------------------------
/app/worker/processor.php:
--------------------------------------------------------------------------------
1 | |
9 | // +------------------------------------------------------------------------+
10 |
11 | namespace Myfox\App\Worker;
12 |
13 | use \Myfox\App\Queque;
14 |
15 | class Processor extends \Myfox\App\Worker
16 | {
17 |
18 | /* {{{ 静态变量 */
19 |
20 | /**
21 | * @状态映射表
22 | */
23 | private static $codemap = array(
24 | \Myfox\App\Task::SUCC => \Myfox\App\Queque::FLAG_DONE,
25 | \Myfox\App\Task::FAIL => \Myfox\App\Queque::FLAG_WAIT,
26 | \Myfox\App\Task::WAIT => \Myfox\App\Queque::FLAG_WAIT,
27 | \Myfox\App\Task::IGNO => \Myfox\App\Queque::FLAG_IGNO,
28 | );
29 |
30 | /* }}} */
31 |
32 | /* {{{ 成员变量 */
33 |
34 | protected $option = array(
35 | 'p' => null, /**< worker标记 */
36 | 'n' => 10, /**< 每次取多少条记录 */
37 | 't' => '', /**< 任务类型 */
38 | );
39 |
40 | private $tasks = array();
41 |
42 | /* }}} */
43 |
44 | /* {{{ public void __construct() */
45 | /**
46 | * 构造函数
47 | *
48 | * @access public
49 | * @return void
50 | */
51 | public function __construct($option)
52 | {
53 | parent::__construct($option);
54 | if (empty($this->option['p'])) {
55 | $this->option['p'] = ip2long(exec('hostname -i'));
56 | }
57 | }
58 | /* }}} */
59 |
60 | /* {{{ public void cleanup() */
61 | /**
62 | * 运行结束后数据清理
63 | *
64 | * @access public
65 | * @return void
66 | */
67 | public function cleanup()
68 | {
69 | $this->tasks = array();
70 | }
71 | /* }}} */
72 |
73 | /* {{{ public Boolean execute() */
74 | /**
75 | * 执行函数
76 | *
77 | * @access public
78 | * @return Boolean true or false : next loop ?
79 | */
80 | public function execute($loop = true)
81 | {
82 | $this->tasks = (array)Queque::instance()->fetch(
83 | $this->option['n'], $this->option['p'],
84 | Queque::FLAG_WAIT, $this->option['t']
85 | );
86 |
87 | if (2 > $this->option['n']) {
88 | $this->tasks = array($this->tasks);
89 | }
90 |
91 | foreach ($this->tasks AS $queque) {
92 | if (!self::lock($queque['id'])) {
93 | continue;
94 | }
95 |
96 | $runner = \Myfox\App\Task::create($queque);
97 | if (!($runner instanceof \Myfox\App\Task)) {
98 | $flag = Queque::FLAG_IGNO;
99 | $error = sprintf("Undefined task_type named as '%s'", $queque['type']);
100 | $status = '';
101 | } else {
102 | $flag = $runner->execute();
103 | while (\Myfox\App\Task::WAIT === $flag) {
104 | $flag = $runner->wait();
105 | }
106 | $flag = isset(self::$codemap[$flag]) ? self::$codemap[$flag] : Queque::FLAG_WAIT;
107 | $error = $runner->lastError();
108 | $status = $runner->result();
109 | }
110 |
111 | self::unlock($queque['id'], $flag, array(
112 | 'last_error' => $error,
113 | 'tmp_status' => $status,
114 | ));
115 | }
116 |
117 | return (bool)$loop;
118 | }
119 | /* }}} */
120 |
121 | /* {{{ public Integer interval() */
122 | /**
123 | * sleep时间, ms
124 | *
125 | * @access public
126 | * @return Integer
127 | */
128 | public function interval()
129 | {
130 | return empty($this->tasks) ? 120000 : 1;
131 | }
132 | /* }}} */
133 |
134 | /* {{{ private static Integer lock() */
135 | /**
136 | * 任务加锁
137 | *
138 | * @access private static
139 | * @return Integer
140 | */
141 | private static function lock($id)
142 | {
143 | return Queque::instance()->update($id, array(
144 | 'begtime' => sprintf(
145 | "IF(task_flag=%d, '%s', begtime)",
146 | Queque::FLAG_WAIT, date('Y-m-d H:i:s')
147 | ),
148 | 'task_flag' => Queque::FLAG_LOCK,
149 | ), array(
150 | 'begtime' => true,
151 | ));
152 | }
153 | /* }}} */
154 |
155 | /* {{{ public Boolean unlock() */
156 | /**
157 | * 完成后任务解锁
158 | *
159 | * @access public
160 | * @return Boolean true or false
161 | */
162 | private static function unlock($id, $flag = Queque::FLAG_DONE, $option = null, $comma = null)
163 | {
164 | return Queque::instance()->update($id, array(
165 | 'trytimes' => sprintf('IF(task_flag=%d && task_flag > 0,trytimes-1,trytimes)', Queque::FLAG_LOCK),
166 | 'endtime' => sprintf("IF(task_flag=%d,'%s',endtime)", Queque::FLAG_LOCK, date('Y-m-d H:i:s')),
167 | 'task_flag' => $flag,
168 | ) + (array)$option, array(
169 | 'trytimes' => true,
170 | 'endtime' => true,
171 | 'task_flag' => true,
172 | ) + (array)$comma);
173 | }
174 | /* }}} */
175 |
176 | }
177 |
178 |
--------------------------------------------------------------------------------
/app/worker/test.php:
--------------------------------------------------------------------------------
1 | |
9 | // +------------------------------------------------------------------------+
10 |
11 | namespace Myfox\App\Worker;
12 |
13 | class Test extends \Myfox\App\Worker
14 | {
15 |
16 | /* {{{ 静态变量 */
17 |
18 | public static $number = 0;
19 |
20 | /* }}} */
21 |
22 | /* {{{ public void __construct() */
23 | public function __construct($option)
24 | {
25 | parent::__construct((array)$option + array(
26 | 'locker' => '',
27 | ));
28 | }
29 | /* }}} */
30 |
31 | /* {{{ public Boolean execute() */
32 | /**
33 | * 执行函数
34 | *
35 | * @access public
36 | * @return Boolean true or false : next loop ?
37 | */
38 | public function execute($loop = true)
39 | {
40 | return (++self::$number < 5) && $this->option['loop'];
41 | }
42 | /* }}} */
43 |
44 | /* {{{ public Integer interval() */
45 | /**
46 | * sleep时间, ms
47 | *
48 | * @access public
49 | * @return Integer
50 | */
51 | public function interval()
52 | {
53 | return $this->option['sleep'];
54 | }
55 | /* }}} */
56 |
57 | /* {{{ public void cleanup() */
58 | /**
59 | * 运行结束后数据清理
60 | *
61 | * @access public
62 | * @return void
63 | */
64 | public function cleanup()
65 | {
66 | }
67 | /* }}} */
68 |
69 | /* {{{ public String locker() */
70 | /**
71 | * 进程锁名字
72 | *
73 | * @access public
74 | * @return String
75 | */
76 | public function locker()
77 | {
78 | return $this->option['locker'];
79 | }
80 | /* }}} */
81 |
82 | }
83 |
84 |
--------------------------------------------------------------------------------
/before_install.sh:
--------------------------------------------------------------------------------
1 | # !/bin/bash
2 | #
3 |
4 | declare __pwd=`pwd`
5 |
6 | # install apc
7 | cd ${__pwd} && wget http://pecl.php.net/get/APC-3.1.9.tgz && \
8 | tar zxvf APC-3.1.9.tgz && \
9 | cd APC-3.1.9/ && \
10 | phpize && \
11 | ./configure && make && make install && \
12 | echo "extension=\"apc.so\"" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"`
13 |
14 | # install igbinary
15 | cd ${__pwd} && wget http://pecl.php.net/get/igbinary-1.1.1.tgz && \
16 | tar zxvf igbinary-1.1.1.tgz && cd igbinary-1.1.1 && \
17 | phpize && ./configure CFLAGS="-O2 -g" --enable-igbinary && \
18 | make && make install && \
19 | echo "extension=\"igbinary.so\"" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"`
20 |
21 | # install phing
22 | cd ${__pwd} && mkdir phing && cd phing && \
23 | wget http://www.phing.info/get/phing-2.4.9.tgz && \
24 | tar zxvf phing-2.4.9.tgz
25 |
26 | cd ${__pwd} && ls -l
27 |
--------------------------------------------------------------------------------
/bin/run.php:
--------------------------------------------------------------------------------
1 | |
9 | // +--------------------------------------------------------------------+
10 | //
11 | // $Id: run.php 48 2010-12-20 15:58:11Z pengchun $
12 |
13 | require_once __DIR__ . '/../app/daemon.php';
14 |
15 | \Myfox\App\Daemon::run(
16 | __DIR__ . '/../etc/myfox.ini',
17 | $_SERVER['argv']
18 | );
19 |
20 |
--------------------------------------------------------------------------------
/build.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/build/http/test_dispatcher_time_out.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/build/phing/release.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | SVN tag name
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | All settings is correctly (y/n)
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/build/phing/unittest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/build/tpl/alert.ini:
--------------------------------------------------------------------------------
1 | prefix = "##run.mode##"
2 |
3 | [normal]
4 | command = "##alert.normal.command##"
5 |
6 | [urgence]
7 | command = "##alert.urgence.command##"
8 |
9 |
--------------------------------------------------------------------------------
/build/tpl/default.properties:
--------------------------------------------------------------------------------
1 | dir.root=##dir.root##
2 | run.mode=##run.mode##
3 |
4 | [svn]
5 | code.svn.url=##code.svn.url##
6 |
7 | [mysql]
8 | default.mysql.logurl=##default.mysql.logurl##
9 | default.mysql.dbname=##default.mysql.dbname##
10 | default.mysql.prefix=##default.mysql.prefix##
11 | default.mysql.master=##default.mysql.master##
12 | default.mysql.slave=##default.mysql.slave##
13 |
14 | [path]
15 | myfox.download.path=##myfox.download.path##
16 | myfox.filesplit.path=##myfox.filesplit.path##
17 |
18 | [bash]
19 | myfox.rpc.server=##myfox.rpc.server##
20 | myfox.rpc.agent=##myfox.rpc.agent##
21 | net.filename.prefix=##net.filename.prefix##
22 |
23 | [alert]
24 | alert.normal.command=##alert.normal.command##
25 | alert.urgence.command=##alert.urgence.command##
26 |
27 |
--------------------------------------------------------------------------------
/build/tpl/myfox.ini:
--------------------------------------------------------------------------------
1 | run.mode = "##run.mode##"
2 |
3 | ; 运行超时时间
4 | run.timeout = ##run.timeout##
5 |
6 | ; URL前缀
7 | url.prefix = "##url.prefix##"
8 |
9 | [log]
10 | default = "##log.url##"
11 |
12 | [mysql]
13 | ; 默认的MySQL数据库
14 | default = "##dir.root##/etc/mysql.ini"
15 |
16 | [path]
17 | ; 数据下载暂存路径
18 | download = "##myfox.download.path##"
19 |
20 | ; 数据文件切分路径
21 | filesplit = "##myfox.filesplit.path##"
22 |
23 |
--------------------------------------------------------------------------------
/build/tpl/mysql.ini:
--------------------------------------------------------------------------------
1 | ; 日志配置
2 | logurl = "##default.mysql.logurl##"
3 |
4 | ; 连接超时
5 | timeout = 1
6 |
7 | ; 持久连接
8 | persist = 1
9 |
10 | ; 字符集
11 | charset = "utf8"
12 |
13 | ; DB名字
14 | dbname = "##default.mysql.dbname##"
15 |
16 | ; 表名前缀
17 | prefix = "##default.mysql.prefix##"
18 |
19 | ; 主库配置
20 | [master]
21 | host1 = "##default.mysql.master##"
22 |
23 | ; 从库配置
24 | [slave]
25 | host1 = "##default.mysql.slave##"
26 |
27 |
--------------------------------------------------------------------------------
/build/tpl/mysql_test.ini:
--------------------------------------------------------------------------------
1 | ; 日志配置
2 | logurl = "##mysql.log.url##"
3 |
4 | ; 连接超时
5 | timeout = 3
6 |
7 | ; 持久连接
8 | persist = 0
9 |
10 | ; 字符集
11 | charset = "utf8"
12 |
13 | ; DB名字
14 | dbname = ""
15 |
16 | ; 表名前缀
17 | prefix = ""
18 |
19 | ; 主库配置
20 | [master]
21 | ; host = mysql://user_rw:password@host:port
22 |
23 | ; 从库配置
24 | [slave]
25 | host1 = mysql://user_ro:password@127.0.0.1:3306
26 |
27 |
--------------------------------------------------------------------------------
/build/tpl/security_example.ini:
--------------------------------------------------------------------------------
1 | ; 安全策略配置文件
2 |
3 | ; 队列属性
4 | [option]
5 |
6 | ; const FLAG_NEW = 0;
7 | ; const FLAG_WAIT = 100;
8 | ; const FLAG_LOCK = 200;
9 | ; const FLAG_IGNO = 300;
10 | ; const FLAG_DONE = 900;
11 | task_flag = 100
12 | trytimes = 3
13 |
14 | [priority]
15 | ; IP list
16 | ##security.allow.iplist##
17 |
18 |
--------------------------------------------------------------------------------
/etc/.gitignore:
--------------------------------------------------------------------------------
1 | *.ini
2 |
--------------------------------------------------------------------------------
/lib/TestShell.php:
--------------------------------------------------------------------------------
1 | |
7 | // +--------------------------------------------------------------------+
8 | //
9 | // $Id: TestShell.php 47 2010-04-26 05:27:46Z zhangxc83 $
10 |
11 | namespace Myfox\Lib;
12 |
13 | require_once(__DIR__ . '/autoload.php');
14 | require_once('PHPUnit/Framework/TestCase.php');
15 |
16 | date_default_timezone_set('Asia/Shanghai');
17 |
18 | class TestShell extends \PHPUnit_Framework_TestCase
19 | {
20 |
21 | protected function setUp()
22 | {
23 | parent::setUp();
24 |
25 | set_time_limit(0);
26 |
27 | \Myfox\Lib\AutoLoad::init();
28 | \Myfox\Lib\AutoLoad::register('myfox\\app', __DIR__ . '/../app');
29 |
30 | \Myfox\Lib\Cache\Apc::cleanAllCache();
31 | }
32 |
33 | protected static function getLogContents($file, $offs = -1)
34 | {
35 | $data = array_filter(array_map('trim', (array)@file($file)));
36 | $lines = count($data);
37 | $offs = $offs < 0 ? $lines + $offs : $offs;
38 | $offs = max(0, $offs);
39 |
40 | return $offs >= $lines ? end($data) : $data[$offs];
41 | }
42 |
43 | protected static function cleanTable($mysql, $prefix)
44 | {
45 | $table = null;
46 | $mysql = \Myfox\Lib\Mysql::instance($mysql);
47 | $query = sprintf("SHOW TABLES LIKE '%s%s%%'", $mysql->option('prefix'), trim($prefix));
48 | foreach ((array)$mysql->getAll($mysql->query($query)) AS $table) {
49 | $table = end($table);
50 | if (preg_match('/(_merge)$/', $table)) {
51 | continue;
52 | }
53 | $mysql->query(sprintf('TRUNCATE TABLE %s', $table));
54 | }
55 |
56 | return $table;
57 | }
58 |
59 | }
60 |
61 |
--------------------------------------------------------------------------------
/lib/alert.php:
--------------------------------------------------------------------------------
1 | |
9 | // +------------------------------------------------------------------------+
10 |
11 | namespace Myfox\Lib;
12 |
13 | use \Myfox\Lib\Config;
14 |
15 | class Alert
16 | {
17 |
18 | /* {{{ 静态常量 */
19 |
20 | const NORMAL = 1;
21 |
22 | const URGENCE = 2;
23 |
24 | /* }}} */
25 |
26 | /* {{{ 静态变量 */
27 |
28 | private static $inited = false;
29 |
30 | private static $config = null;
31 |
32 | /* }}} */
33 |
34 | /* {{{ public static void init() */
35 | /**
36 | * 配置初始化方法
37 | *
38 | * @access public static
39 | * @return void
40 | */
41 | public static function init($ini)
42 | {
43 | self::$config = new Config($ini);
44 | self::$inited = true;
45 | }
46 | /* }}} */
47 |
48 | /* {{{ public static void push() */
49 | /**
50 | * 压入报警
51 | *
52 | * @access public static
53 | * @return void
54 | */
55 | public static function push($title, $level = self::NORMAL)
56 | {
57 | if (!self::$inited) {
58 | return;
59 | }
60 |
61 | $level = (int)$level;
62 | $title = sprintf('[%s] %s', self::$config->get('prefix'), trim($title));
63 |
64 | if (self::URGENCE & $level) {
65 | $rt = self::call('urgence', $title, $error);
66 | if (empty($rt)) {
67 | $title = sprintf('%s (%s)', $title, $error);
68 | $level = self::NORMAL;
69 | }
70 | }
71 |
72 | if ((self::NORMAL & $level) && !self::call('normal', $title, $error)) {
73 | printf("[%s] %s\n", date('Y-m-d H:i:s'), $title);
74 | }
75 | }
76 | /* }}} */
77 |
78 | /* {{{ private static Boolean call() */
79 | /**
80 | * alert命令调用
81 | *
82 | * @access private static
83 | * @return Boolean true or false
84 | */
85 | private static function call($name, $title, &$error)
86 | {
87 | $caller = self::$config->get(sprintf('%s/command', trim($name)));
88 | if (empty($caller)) {
89 | $error = 'undefined command for ' . $name;
90 | return false;
91 | }
92 |
93 | $error = system(strtr($caller, array(
94 | '{TITLE}' => escapeshellcmd($title),
95 | )), $code);
96 |
97 | return empty($code) ? true : false;
98 | }
99 | /* }}} */
100 |
101 | /* {{{ private void __construct() */
102 | /**
103 | * 构造函数占位
104 | *
105 | * @access private
106 | * @return void
107 | */
108 | private function __construct()
109 | {
110 | }
111 | /* }}} */
112 |
113 | }
114 |
115 |
--------------------------------------------------------------------------------
/lib/autoload.php:
--------------------------------------------------------------------------------
1 | |
7 | // +------------------------------------------------------------------------+
8 | //
9 | // $Id: autoload.php 22 2010-04-15 16:28:45Z zhangxc83 $
10 |
11 | namespace Myfox\Lib;
12 |
13 | class AutoLoad
14 | {
15 |
16 | /* {{{ 静态变量 */
17 | /**
18 | * @路径解析规则
19 | */
20 | private static $rules = array();
21 |
22 | private static $order = array();
23 |
24 | private static $sorted = false;
25 |
26 | private static $index = 0;
27 |
28 | /* }}} */
29 |
30 | /* {{{ public static void init() */
31 | /**
32 | * 初始化
33 | *
34 | * @access public static
35 | * @return void
36 | */
37 | public static function init()
38 | {
39 | self::register('myfox\\lib', __DIR__);
40 |
41 | spl_autoload_register(array(__CLASS__, 'callback'));
42 | }
43 | /* }}} */
44 |
45 | /* {{{ public static void register() */
46 | /*
47 | * 注册路径解析规则
48 | *
49 | * @access public static
50 | * @param String $key
51 | * @param String $dir
52 | * @param String $pre = null
53 | * @return void
54 | */
55 | public static function register($key, $dir, $pre = null)
56 | {
57 | $dir = realpath($dir);
58 | if (empty($dir) || !is_dir($dir)) {
59 | return;
60 | }
61 |
62 | /**
63 | * 有unset动作,所以得用这个变量来标记曾经有过多少个元素
64 | * 避免register / unregister 导致排序不稳定
65 | */
66 | self::$index++;
67 |
68 | $key = self::normalize($key);
69 | $pre = self::normalize($pre);
70 | $idx = 1000 * self::$index;
71 | if (!empty($pre) && isset(self::$order[$pre])) {
72 | /**
73 | * AutoLoad::register('root', '...');
74 | * AutoLoad::register('son1', '...', 'root');
75 | * AutoLoad::register('son2', '...', 'root');
76 | */
77 | $idx = self::$order[$pre] - intval(1000 / self::$index + 0.5); /*< 稳定排序 */
78 | self::$sorted = false;
79 | }
80 |
81 | self::$rules[$key] = $dir;
82 | self::$order[$key] = $idx;
83 | }
84 | /* }}} */
85 |
86 | /* {{{ public static void unregister() */
87 | /**
88 | * 注销路径解析规则
89 | *
90 | * @access public static
91 | * @param String $name
92 | * @return void
93 | */
94 | public static function unregister($name)
95 | {
96 | $name = self::normalize($name);
97 | if (isset(self::$rules[$name])) {
98 | unset(self::$rules[$name]);
99 | unset(self::$order[$name]);
100 | }
101 | }
102 | /* }}} */
103 |
104 | /* {{{ public static void removeAllRules() */
105 | /**
106 | * @清理所有规则
107 | *
108 | * @access public static
109 | * @return void
110 | */
111 | public static function removeAllRules()
112 | {
113 | self::$rules = array();
114 | self::$order = array();
115 | self::$index = 0;
116 | self::$sorted = false;
117 | }
118 | /* }}} */
119 |
120 | /* {{{ public static void callback() */
121 | /**
122 | * 自动加载回调函数
123 | *
124 | * @access public static
125 | * @param String $class
126 | * @return void
127 | */
128 | public static function callback($class)
129 | {
130 | $ordina = $class;
131 | $class = preg_replace('/[\/\\\]{1,}/', '/', $class);
132 | $index = strrpos($class, '/');
133 |
134 | if (false === $index) {
135 | require_once(__DIR__ . '/exception.php');
136 | throw new \Myfox\Lib\Exception(sprintf('Unregistered namespace when class "%s" defined.', $class));
137 | }
138 |
139 | if (!self::$sorted && array_multisort(self::$order, self::$rules, SORT_ASC, SORT_NUMERIC)) {
140 | self::$sorted = true;
141 | }
142 |
143 | $path = strtolower(substr($class, 0, $index));
144 | $name = strtolower(substr($class, $index + 1));
145 |
146 | reset(self::$rules);
147 | foreach (self::$rules AS $key => $dir) {
148 | if (0 !== strpos($path, $key)) {
149 | continue;
150 | }
151 |
152 | $file = $dir . substr($path, strlen($key)) . '/' . $name . '.php';
153 | if (is_file($file)) {
154 | require $file;
155 | } else {
156 | require_once(__DIR__ . '/exception.php');
157 | throw new \Myfox\Lib\Exception(sprintf('File "%s" Not Found.', $file));
158 | }
159 |
160 | if (!class_exists($ordina) && !interface_exists($ordina)) {
161 | require_once(__DIR__ . '/exception.php');
162 | throw new \Myfox\Lib\Exception(sprintf('Class "%s" Not Found in "%s".', $ordina, $file));
163 | }
164 |
165 | return;
166 | }
167 |
168 | require_once(__DIR__ . '/exception.php');
169 | throw new \Myfox\Lib\Exception(sprintf('Class "%s" Not Found.', $ordina));
170 | }
171 | /* }}} */
172 |
173 | /* {{{ private static String normalize() */
174 | /**
175 | * 规则归一化处理
176 | *
177 | * @access private static
178 | * @param String $name
179 | * @return String
180 | */
181 | private static function normalize($name)
182 | {
183 | $name = preg_replace('/[\/\\\]+/', '/', preg_replace('/\s+/', '/', $name));
184 | return strtolower(trim($name, '/'));
185 | }
186 | /* }}} */
187 |
188 | }
189 |
190 |
--------------------------------------------------------------------------------
/lib/blackhole.php:
--------------------------------------------------------------------------------
1 | |
7 | // +------------------------------------------------------------------------+
8 | //
9 | // $Id: blackhole.php 22 2010-04-15 16:28:45Z zhangxc83 $
10 |
11 | namespace Myfox\Lib;
12 |
13 | class Blackhole
14 | {
15 |
16 | public function __call($name, $args)
17 | {
18 | }
19 |
20 | public static function __callStatic($name, $args)
21 | {
22 | }
23 |
24 | public function __get($key)
25 | {
26 | }
27 |
28 | public function __set($key, $value)
29 | {
30 | }
31 |
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/lib/config.php:
--------------------------------------------------------------------------------
1 | |
7 | // +--------------------------------------------------------------------+
8 | //
9 | // $Id: config.php 1 2010-06-01 03:52:51Z zhangxc83 $
10 | //
11 |
12 | namespace Myfox\Lib;
13 |
14 | class Config
15 | {
16 |
17 | /* {{{ 静态变量 */
18 |
19 | private static $alias = array();
20 |
21 | private static $objects = array();
22 |
23 | /* }}} */
24 |
25 | /* {{{ 成员变量 */
26 |
27 | private $scheme;
28 |
29 | private $params = null;
30 |
31 | /* }}} */
32 |
33 | /* {{{ public static Object instance() */
34 | /**
35 | * 根据名字获取实例
36 | *
37 | * @access public static
38 | * @return Object
39 | */
40 | public static function instance($name)
41 | {
42 | $name = self::normalize($name);
43 | if (empty(self::$objects[$name])) {
44 | if (empty(self::$alias[$name])) {
45 | throw new \Myfox\Lib\Exception(sprintf(
46 | 'Undefined config name as "%s"', $name
47 | ));
48 | }
49 | self::$objects[$name] = new self(self::$alias[$name]);
50 | }
51 |
52 | return self::$objects[$name];
53 | }
54 | /* }}} */
55 |
56 | /* {{{ public static void register() */
57 | /**
58 | * 注册别名
59 | *
60 | * @access public static
61 | * @return void
62 | */
63 | public static function register($name, $url)
64 | {
65 | self::$alias[self::normalize($name)] = trim($url);
66 | }
67 | /* }}} */
68 |
69 | /* {{{ public static void removeAllNames() */
70 | /**
71 | * 清理所有的对象
72 | *
73 | * @access public static
74 | * @return void
75 | */
76 | public static function removeAllNames()
77 | {
78 | self::$objects = array();
79 | self::$alias = array();
80 | }
81 | /* }}} */
82 |
83 | /* {{{ public Mixture get() */
84 | /**
85 | * 获取配置值
86 | *
87 | * @access public
88 | * @return void
89 | */
90 | public function get($key, $default = null)
91 | {
92 | if (null === $this->params) {
93 | $this->params = (array)self::load($this->scheme);
94 | }
95 |
96 | $key = trim($key);
97 | if (isset($this->params[$key])) {
98 | return $this->params[$key];
99 | }
100 |
101 | if ('' == $key) {
102 | return $this->params;
103 | }
104 |
105 | $val = $this->params;
106 | foreach (explode('/', $key) AS $id) {
107 | $id = trim($id);
108 | if (!isset($val[$id])) {
109 | return $default;
110 | }
111 | $val = $val[$id];
112 | }
113 |
114 | return $val;
115 | }
116 | /* }}} */
117 |
118 | /* {{{ public void __construct() */
119 | /**
120 | * 构造函数
121 | *
122 | * @access public
123 | * @return void
124 | */
125 | public function __construct($url, $name = null)
126 | {
127 | $this->scheme = trim($url);
128 | $this->params = null;
129 | if (!empty($name)) {
130 | self::register($name, $url);
131 | }
132 | }
133 | /* }}} */
134 |
135 | /* {{{ private static String normalize() */
136 | /**
137 | * 名字归一化
138 | *
139 | * @access private static
140 | * @return String
141 | */
142 | private static function normalize($name)
143 | {
144 | return strtolower(preg_replace('/\s+/', '', $name));
145 | }
146 | /* }}} */
147 |
148 | /* {{{ private static Mixture load() */
149 | /**
150 | * 加载数据
151 | *
152 | * @access private static
153 | * @return Mixture
154 | */
155 | private static function load($url)
156 | {
157 | $url = parse_url($url);
158 | if (empty($url) || empty($url['path'])) {
159 | return false;
160 | }
161 |
162 | $class = explode('.', trim($url['path'], "\x00..\x20."));
163 | $class = sprintf('%s\%s', __CLASS__, ucfirst(strtolower(end($class))));
164 | $object = new $class($url);
165 |
166 | return $object->parse();
167 | }
168 | /* }}} */
169 |
170 | }
171 |
172 |
--------------------------------------------------------------------------------
/lib/config/ini.php:
--------------------------------------------------------------------------------
1 | |
7 | // +--------------------------------------------------------------------+
8 | //
9 | // $Id: ini.php 1 2010-06-01 03:52:51Z zhangxc83 $
10 | //
11 |
12 | namespace Myfox\Lib\Config;
13 |
14 | class Ini
15 | {
16 |
17 | /* {{{ 成员变量 */
18 |
19 | private $inifile;
20 | private $section;
21 |
22 | /* }}} */
23 |
24 | /* {{{ public void __construct() */
25 | /**
26 | * 构造函数
27 | *
28 | * @access public
29 | * @param Mixture $option
30 | * @return void
31 | */
32 | public function __construct($option)
33 | {
34 | if (is_string($option)) {
35 | $option = parse_url($option);
36 | }
37 | if (empty($option['path'])) {
38 | throw new \Myfox\Lib\Exception('Uncomplete ini option');
39 | }
40 |
41 | $this->inifile = trim($option['path']);
42 | $this->section = true;
43 | }
44 | /* }}} */
45 |
46 | /* {{{ public Mixture parse() */
47 | /**
48 | * 解析返回结果
49 | *
50 | * @access public
51 | * @return Mixture
52 | */
53 | public function parse()
54 | {
55 | return parse_ini_file($this->inifile, $this->section);
56 | }
57 | /* }}} */
58 |
59 | }
60 |
61 |
--------------------------------------------------------------------------------
/lib/context.php:
--------------------------------------------------------------------------------
1 | |
7 | // +------------------------------------------------------------------------+
8 | //
9 | // $Id: context.php 4 2010-03-09 05:20:36Z zhangxc83 $
10 |
11 | namespace Myfox\Lib;
12 |
13 | class Context
14 | {
15 |
16 | /* {{{ 静态变量 */
17 |
18 | /**
19 | * @数据列表
20 | */
21 | private static $data = array();
22 |
23 | /* }}} */
24 |
25 | /* {{{ public static void register() */
26 | /**
27 | * 注册一个变量
28 | *
29 | * @access public static
30 | * @param String $key
31 | * @param Mixture $val
32 | * @return void
33 | */
34 | public static function register($key, $val)
35 | {
36 | self::$data[(string)$key] = $val;
37 | }
38 | /* }}} */
39 |
40 | /* {{{ public static void unregister() */
41 | /**
42 | * 注销一个变量
43 | *
44 | * @access public static
45 | * @param String $key
46 | * @return void
47 | */
48 | public static function unregister($key)
49 | {
50 | $key = (string)$key;
51 | if (isset(self::$data[$key])) {
52 | unset(self::$data[$key]);
53 | }
54 | }
55 | /* }}} */
56 |
57 | /* {{{ public static void cleanAllContext() */
58 | /**
59 | * 清理所有上下文数据
60 | *
61 | * @access public static
62 | * @return void
63 | */
64 | public static function cleanAllContext()
65 | {
66 | self::$data = array();
67 | }
68 | /* }}} */
69 |
70 | /* {{{ public static Mixture get() */
71 | /**
72 | * 获取变量
73 | *
74 | * @access public static
75 | * @param String $key
76 | * @param Mixture $default : default null
77 | * @return Mixture
78 | */
79 | public static function get($key, $default = null)
80 | {
81 | $key = (string)$key;
82 | if (isset(self::$data[$key])) {
83 | return self::$data[$key];
84 | }
85 |
86 | return $default;
87 | }
88 | /* }}} */
89 |
90 | /* {{{ public static Mixture addr() */
91 | /**
92 | * 获取本地IP地址
93 | *
94 | * @access public static
95 | * @return Mixture
96 | */
97 | public static function addr($int = false)
98 | {
99 | if (null === ($ip = self::get('__addr__'))) {
100 | $ip = trim(exec('hostname -i'));
101 | self::register('__addr__', $ip);
102 | }
103 |
104 | return $int ? sprintf('%u', ip2long($ip)) : $ip;
105 | }
106 | /* }}} */
107 |
108 | /* {{{ public static Mixture userip() */
109 | /**
110 | * 获取当前用户IP
111 | *
112 | * @access public static
113 | * @param Boolean $bolInt (default false)
114 | * @return String or Integer
115 | */
116 | public static function userip($bolInt = false)
117 | {
118 | if (null === ($ret = self::get('__ip__'))) {
119 | $ret = self::_userip();
120 | self::register('__ip__', $ret);
121 | }
122 |
123 | return $bolInt ? sprintf('%u', ip2long($ret)) : $ret;
124 | }
125 | /* }}} */
126 |
127 | /* {{{ public static Integer pid() */
128 | /**
129 | * 获取当前进程号
130 | *
131 | * @access public static
132 | * @return Integer
133 | */
134 | public static function pid()
135 | {
136 | if (null === ($ret = self::get('__pid__'))) {
137 | $ret = is_callable('posix_getpid') ? posix_getpid() : getmypid();
138 | self::register('__pid__', $ret);
139 | }
140 |
141 | return $ret;
142 | }
143 | /* }}} */
144 |
145 | /* {{{ private static String _userip() */
146 | /**
147 | * 读取用户实际IP
148 | *
149 | * @access private static
150 | * @return String
151 | */
152 | private static function _userip()
153 | {
154 | $check = array(
155 | 'HTTP_VIA',
156 | 'HTTP_X_FORWARDED_FOR',
157 | 'HTTP_CLIENT_IP',
158 | 'REMOTE_ADDR',
159 | );
160 |
161 | foreach ($check AS $key) {
162 | if (empty($_SERVER[$key])) {
163 | continue;
164 | }
165 |
166 | if (!preg_match_all('/\d+\.\d+\.\d+.\d+/', $_SERVER[$key], $match)) {
167 | continue;
168 | }
169 |
170 | return end($match[0]);
171 | }
172 |
173 | return 'unknown';
174 | }
175 | /* }}} */
176 |
177 | }
178 |
179 |
--------------------------------------------------------------------------------
/lib/debug/pool.php:
--------------------------------------------------------------------------------
1 | |
7 | // +--------------------------------------------------------------------+
8 | //
9 | // $Id: pool.php 2010-04-23 zhangxc83 Exp $
10 |
11 | namespace Myfox\Lib\Debug;
12 |
13 | class Pool
14 | {
15 |
16 | /* {{{ 静态变量 */
17 |
18 | private static $open = false;
19 |
20 | private static $debug = array();
21 |
22 | /* }}} */
23 |
24 | /* {{{ public static void init() */
25 | /**
26 | * 切换debug开关
27 | *
28 | * @access public static
29 | * @param Boolean $open
30 | * @return void
31 | */
32 | public static function init($open)
33 | {
34 | self::$open = (bool)$open;
35 | }
36 | /* }}} */
37 |
38 | /* {{{ public static void push() */
39 | /**
40 | * 压入debug数据
41 | *
42 | * @access public static
43 | * @param String $key
44 | * @param Mixture $val
45 | * @return void
46 | */
47 | public static function push($key, $val)
48 | {
49 | if (!self::$open) {
50 | return false;
51 | }
52 |
53 | if (!isset(self::$debug[$key])) {
54 | self::$debug[$key] = $val;
55 | return $val;
56 | }
57 |
58 | if (!is_array(self::$debug[$key])) {
59 | self::$debug[$key] = array(self::$debug[$key]);
60 | }
61 | self::$debug[$key][] = $val;
62 |
63 | return count(self::$debug[$key]);
64 | }
65 | /* }}} */
66 |
67 | /* {{{ public static void clean() */
68 | /**
69 | * 清理所有debug数据
70 | *
71 | * @access public static
72 | * @return void
73 | */
74 | public static function clean()
75 | {
76 | self::$debug = array();
77 | }
78 | /* }}} */
79 |
80 | /* {{{ public static String dump() */
81 | /**
82 | * 打出debug数据
83 | *
84 | * @access public static
85 | * @param String $key (default null)
86 | * @return String
87 | */
88 | public static function dump($key = null)
89 | {
90 | if (null === $key) {
91 | return var_export(self::$debug, true);
92 | }
93 |
94 | if (!isset(self::$debug[$key])) {
95 | return 'NULL';
96 | }
97 |
98 | return var_export(self::$debug[$key], true);
99 | }
100 | /* }}} */
101 |
102 | }
103 |
104 |
--------------------------------------------------------------------------------
/lib/debug/timer.php:
--------------------------------------------------------------------------------
1 | |
7 | // +--------------------------------------------------------------------+
8 | //
9 | // $Id: timer.php 48 2010-04-26 15:58:11Z zhangxc83 $
10 |
11 | namespace Myfox\Lib\Debug;
12 |
13 | class Timer
14 | {
15 |
16 | /* {{{ 静态变量 */
17 |
18 | private static $opened = false;
19 |
20 | private static $timer = array();
21 |
22 | /* }}} */
23 |
24 | /* {{{ public static void init() */
25 | /**
26 | * 初始化计时器
27 | *
28 | * @access public static
29 | * @param Boolean $open
30 | * @return void
31 | */
32 | public static function init($open)
33 | {
34 | self::$opened = (bool)$open;
35 | }
36 | /* }}} */
37 |
38 | /* {{{ public static void start() */
39 | /**
40 | * 开始计时
41 | *
42 | * @access public static
43 | * @param String $key
44 | * @return void
45 | */
46 | public static function start($key)
47 | {
48 | if (self::$opened) {
49 | self::$timer[self::normarlize($key)] = microtime(true);
50 | }
51 | }
52 | /* }}} */
53 |
54 | /* {{{ public static Mixture elapsed() */
55 | /**
56 | * 获取耗时
57 | *
58 | * @access public static
59 | * @param String $key
60 | * @return Mixture
61 | */
62 | public static function elapsed($key)
63 | {
64 | $key = self::normarlize($key);
65 | if (empty(self::$timer[$key])) {
66 | return null;
67 | }
68 |
69 | $beg = self::$timer[$key];
70 | unset(self::$timer[$key]);
71 |
72 | return number_format(microtime(true) - $beg, 6);
73 | }
74 | /* }}} */
75 |
76 | /* {{{ private static string normarlize() */
77 | /**
78 | * KEY归一化
79 | *
80 | * @access private static
81 | * @param string $key
82 | * @return string
83 | */
84 | private static function normarlize($key)
85 | {
86 | return strtolower(trim($key));
87 | }
88 | /* }}} */
89 |
90 | }
91 |
92 |
--------------------------------------------------------------------------------
/lib/exception.php:
--------------------------------------------------------------------------------
1 | |
7 | // +--------------------------------------------------------------------+
8 | //
9 | // $Id: exception.php 2010-04-19 13:54:32 zhangxc83 Exp $
10 |
11 | namespace Myfox\Lib;
12 |
13 | class Exception extends \Exception
14 | {
15 |
16 | public function __construct($message, $code = 0)
17 | {
18 | parent::__construct($message, $code);
19 | }
20 |
21 | public function __toString()
22 | {
23 | return sprintf('%s : [%s] : %s', __CLASS__, $this->code, $this->message);
24 | }
25 |
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/lib/factory.php:
--------------------------------------------------------------------------------
1 | |
7 | // +------------------------------------------------------------------------+
8 | //
9 | // $Id: factory.php 22 2010-04-15 16:28:45Z zhangxc83 $
10 |
11 | namespace Myfox\Lib;
12 |
13 | class Factory
14 | {
15 |
16 | /* {{{ 静态变量 */
17 |
18 | private static $objects = array();
19 |
20 | private static $logpool = array();
21 |
22 | /* }}} */
23 |
24 | /* {{{ public static Object getObject() */
25 | /**
26 | * 获取一个类的指定实例
27 | *
28 | * @access public static
29 | * @param String $class
30 | * @param String $name
31 | * @return Object (refferrence)
32 | */
33 | public static function &getObject($class)
34 | {
35 | $args = func_get_args();
36 | $class = array_shift($args);
37 | $index = self::names($class, json_encode($args));
38 | if (empty(self::$objects[$index])) {
39 | if (!class_exists($class)) {
40 | \Myfox\Lib\AutoLoad::callback($class);
41 | }
42 |
43 | $rf = new \ReflectionClass($class);
44 | self::$objects[$index] = $rf->newInstanceArgs($args);
45 | }
46 |
47 | return self::$objects[$index];
48 | }
49 | /* }}} */
50 |
51 | /* {{{ public static void removeAllObject() */
52 | /**
53 | * 清理掉所有对象和注册信息
54 | *
55 | * @access public static
56 | * @param Boolean $reg (default false)
57 | * @return void
58 | */
59 | public static function removeAllObject($reg = false)
60 | {
61 | self::$objects = array();
62 | self::$logpool = array();
63 | }
64 | /* }}} */
65 |
66 | /* {{{ private static String names() */
67 | /**
68 | * 构造对象索引
69 | *
70 | * @access private static
71 | * @param String $class
72 | * @param String $name
73 | * @return String
74 | */
75 | private static function names($class, $name)
76 | {
77 | return sprintf('%s:%s', self::normalize($class), self::normalize($name));
78 | }
79 | /* }}} */
80 |
81 | /* {{{ private static String normalize() */
82 | /**
83 | * 类名归一化处理
84 | *
85 | * @access private static
86 | * @param String $class
87 | * @return String
88 | */
89 | private static function normalize($class)
90 | {
91 | $class = preg_replace('/\s+/', '', preg_replace('/[\/\\\]+/', '/', $class));
92 | return strtolower(trim($class, '/'));
93 | }
94 | /* }}} */
95 |
96 | /* {{{ public static void registerLog() */
97 | /**
98 | * 注册log对象
99 | */
100 | public static function registerLog($name, $url)
101 | {
102 | self::$logpool[strtolower(trim($name))] = new \Myfox\Lib\Log($url);
103 | }
104 | /* }}} */
105 |
106 | /* {{{ public static Object getLog() */
107 | /**
108 | * 根据名字获取日志对象
109 | *
110 | * @access public static
111 | * @return Object
112 | */
113 | public static function getLog($name)
114 | {
115 | $name = strtolower(trim($name));
116 | if (isset(self::$logpool[$name])) {
117 | return self::$logpool[$name];
118 | }
119 |
120 | return new \Myfox\Lib\BlackHole('Log:' . $name);
121 | }
122 | /* }}} */
123 |
124 | }
125 |
126 |
--------------------------------------------------------------------------------
/lib/fetcher/ftp.php:
--------------------------------------------------------------------------------
1 | |
7 | // +------------------------------------------------------------------------+
8 | //
9 | // $Id: ftp.php 22 2010-04-15 16:28:45Z zhangxc83 $
10 |
11 | namespace Myfox\Lib\Fetcher;
12 |
13 | class Ftp
14 | {
15 |
16 | /* {{{ 成员变量 */
17 |
18 | private $error = '';
19 |
20 | private $handle = null;
21 |
22 | private $option;
23 |
24 | private $tmout = 2; /**< 连接超时(s) */
25 |
26 | /* }}} */
27 |
28 | /* {{{ public void __construct() */
29 | /**
30 | * 构造函数
31 | *
32 | * @access public
33 | * @return void
34 | */
35 | public function __construct($option)
36 | {
37 | $this->option = (array)$option + array(
38 | 'host' => '',
39 | 'port' => 21,
40 | 'user' => 'anonymous',
41 | 'pass' => '',
42 | );
43 | if (!empty($this->option['query'])) {
44 | parse_str($this->option['query'], $mc);
45 | if (!empty($mc['timeout'])) {
46 | $this->tmout = (int)$mc['timeout'];
47 | }
48 | }
49 | }
50 | /* }}} */
51 |
52 | /* {{{ public void __destruct() */
53 | public function __destruct()
54 | {
55 | $this->close();
56 | }
57 | /* }}} */
58 |
59 | /* {{{ public Boolean fetch() */
60 | /**
61 | * 获取文件
62 | *
63 | * @access public
64 | * @return Boolean true or false
65 | */
66 | public function fetch($fname, $cache = true)
67 | {
68 | if (!$this->connect()) {
69 | return false;
70 | }
71 |
72 | // TODO:
73 | //
74 | // @ 基于md5文件的抓取 , 变更判断
75 | // @ 基于 filesize + filemtime 的变更判断
76 | if (!$this->get($this->option['path'], $fname)) {
77 | return false;
78 | }
79 |
80 | return true;
81 | }
82 | /* }}} */
83 |
84 | /* {{{ public Mixture lastError() */
85 | /**
86 | * 获取错误描述
87 | *
88 | * @access public
89 | * @return Mixture
90 | */
91 | public function lastError()
92 | {
93 | return $this->error;
94 | }
95 | /* }}} */
96 |
97 | /* {{{ private Boolean connect() */
98 | /**
99 | * 连接FTP服务器
100 | *
101 | * @access private
102 | * @return Boolean true or false
103 | */
104 | private function connect()
105 | {
106 | if (!empty($this->handle)) {
107 | return true;
108 | }
109 |
110 | $this->handle = ftp_connect($this->option['host'], $this->option['port'], $this->tmout);
111 | if (empty($this->handle)) {
112 | $this->error = sprintf(
113 | 'Connect failed with the host as "%s" on port %d',
114 | $this->option['host'], $this->option['port']
115 | );
116 | return false;
117 | }
118 |
119 | if (!ftp_login($this->handle, $this->option['user'], $this->option['pass'])) {
120 | $this->error = sprintf(
121 | 'Access Denied for user "%s", use password %s',
122 | $this->option['user'], empty($this->option['pass']) ? 'NO' : 'YES'
123 | );
124 | return false;
125 | }
126 | ftp_pasv($this->handle, true);
127 |
128 | return true;
129 | }
130 | /* }}} */
131 |
132 | /* {{{ private void close() */
133 | /**
134 | * 关闭连接
135 | *
136 | * @access private
137 | * @return void
138 | */
139 | private function close()
140 | {
141 | if (!empty($this->handle)) {
142 | ftp_close($this->handle);
143 | }
144 | $this->handle = null;
145 | }
146 | /* }}} */
147 |
148 | /* {{{ private Boolean get() */
149 | /**
150 | * 获取文件
151 | *
152 | * @access private
153 | * @return Boolean true or false
154 | */
155 | private function get($remote, $local)
156 | {
157 | $fname = sprintf('%s.%d', $local, getmypid());
158 | if (!ftp_get($this->handle, $fname, $remote, FTP_BINARY)) {
159 | @unlink($fname);
160 | $this->error = sprintf('');
161 | return false;
162 | }
163 |
164 | return rename($fname, $local);
165 | }
166 | /* }}} */
167 |
168 | }
169 |
--------------------------------------------------------------------------------
/lib/fetcher/scp.php:
--------------------------------------------------------------------------------
1 | |
7 | // +------------------------------------------------------------------------+
8 |
9 | namespace Myfox\Lib\Fetcher;
10 |
11 | class Scp
12 | {
13 |
14 | /* {{{ 成员变量 */
15 |
16 | private $error = '';
17 |
18 | private $option;
19 |
20 | /* }}} */
21 |
22 | /* {{{ public void __construct() */
23 | /**
24 | * 构造函数
25 | *
26 | * @access public
27 | * @return void
28 | */
29 | public function __construct($option)
30 | {
31 | if(!empty($option['query'])) {
32 | parse_str($option['query'], $query);
33 | $this->option = (array)$option + (array)$query;
34 | }
35 |
36 | $this->option = (array)$option + array(
37 | 'user' => get_current_user(),
38 | );
39 | }
40 | /* }}} */
41 |
42 | /* {{{ public Boolean fetch() */
43 | /**
44 | * 获取文件
45 | *
46 | * @access public
47 | * @return Boolean true or false
48 | */
49 | public function fetch($fname, $cache = true)
50 | {
51 | if (!$this->isChange($fname)) {
52 | return true;
53 | }
54 | return self::ssh(sprintf('scp %s@%s:%s %s',
55 | escapeshellcmd($this->option['user']),
56 | escapeshellcmd($this->option['host']),
57 | escapeshellcmd($this->option['path']),
58 | escapeshellcmd($fname)
59 | ), $this->error);
60 | }
61 | /* }}} */
62 |
63 | /* {{{ public Mixture lastError() */
64 | /**
65 | * 获取错误描述
66 | *
67 | * @access public
68 | * @return Mixture
69 | */
70 | public function lastError()
71 | {
72 | return $this->error;
73 | }
74 | /* }}} */
75 |
76 | /* {{{ private Boolean ssh() */
77 | /**
78 | * ssh
79 | *
80 | * @access private
81 | * @return Boolean true or false
82 | */
83 | private static function ssh($script, &$output)
84 | {
85 | $return = exec(sprintf('%s 2>&1', $script), $output, $rt);
86 | $output = !empty($return) ? $return : $output;
87 | return 0 === $rt ? true : false;
88 | }
89 | /* }}}*/
90 |
91 | /*{{{ private Boolean isChange()*/
92 | /**
93 | * 文件是否变化
94 | *
95 | * @access private
96 | * @return Boolean true or false
97 | */
98 |
99 | public function isChange($file)
100 | {
101 | if (!is_file($file)) {
102 | return true;
103 | }
104 | $rt = self::ssh(sprintf(
105 | 'ssh %s@%s "ls -l --full-time \"%s\""',
106 | escapeshellcmd($this->option['user']),
107 | escapeshellcmd($this->option['host']),
108 | escapeshellcmd($this->option['path'])
109 | ), $stat);
110 | if (false === $rt) {
111 | return true;
112 | }
113 |
114 | $stat = self::parsestat(trim($stat));
115 | if ($stat['size'] != filesize($file) || $stat['mtime'] >= filemtime($file)) {
116 | return true;
117 | }
118 | return false;
119 | }
120 | /*}}}*/
121 |
122 | /* {{{ public static Mixture parsestat() */
123 | /**
124 | * 解析ls -l --full-time的结果
125 | *
126 | * @access public static
127 | * @return Mixture
128 | */
129 | public static function parsestat($stat)
130 | {
131 | $stat = explode(' ', trim($stat));
132 | return array(
133 | 'size' => $stat[4],
134 | 'mtime' => strtotime(sprintf('%s %s', $stat[5], substr($stat[6], 0, 8), $stat[7])),
135 | );
136 | }
137 | /* }}} */
138 |
139 | }
140 |
--------------------------------------------------------------------------------
/lib/fileset.php:
--------------------------------------------------------------------------------
1 | |
7 | // +------------------------------------------------------------------------+
8 | //
9 | // $Id: fileset.php 22 2010-04-15 16:28:45Z zhangxc83 $
10 |
11 | namespace Myfox\Lib;
12 |
13 | class Fileset
14 | {
15 |
16 | /* {{{ 静态常量 */
17 |
18 | const TEMP_PATH = '/tmp/myfox/download';
19 |
20 | /* }}} */
21 |
22 | /* {{{ 静态变量 */
23 |
24 | private static $lastError = '';
25 |
26 | /* }}} */
27 |
28 | /* {{{ public static Mixture getfile() */
29 | /**
30 | * 从给定URL获取文件
31 | *
32 | * @access public static
33 | * @return Mixture
34 | */
35 | public static function getfile($url, $path = '', $cache = true)
36 | {
37 | $option = parse_url((string)$url);
38 | if (empty($option) || empty($option['path'])) {
39 | self::setError(sprintf('Unrecognized url as "%s" for fileset.', $url));
40 | return false;
41 | }
42 |
43 | if (empty($option['scheme'])) {
44 | $fname = realpath($option['path']);
45 | if (empty($fname)) {
46 | self::setError(sprintf('File not found as the path "%s"', $option['path']));
47 | return false;
48 | }
49 |
50 | return $fname;
51 | }
52 |
53 | if (empty($path)) {
54 | $fpath = self::TEMP_PATH;
55 | $fname = basename($option['path']);
56 | } elseif ('/' == substr($path, -1)) {
57 | $fpath = rtrim($path, '/');
58 | $fname = basename($option['path']);
59 | } else {
60 | $fpath = dirname($path);
61 | $fname = basename($path);
62 | }
63 |
64 | if (!is_dir($fpath) && !@mkdir($fpath, 0755, true)) {
65 | self::setError(sprintf('Path "%s" doesn\'t exist, and create failed.', $fpath));
66 | return false;
67 | }
68 |
69 | $class = sprintf('%s\\Fetcher\\%s', __NAMESPACE__, ucfirst($option['scheme']));
70 | try {
71 | $worker = new $class($option);
72 | $fname = sprintf('%s/%s', $fpath, $fname);
73 | if (!$worker->fetch($fname, $cache)) {
74 | self::setError($worker->lastError());
75 | return false;
76 | }
77 |
78 | return $fname;
79 | } catch (\Exception $e) {
80 | self::setError($e->getMessage());
81 | }
82 |
83 | return false;
84 | }
85 | /* }}} */
86 |
87 | /* {{{ public String lastError() */
88 | /**
89 | * 获取错误信息
90 | *
91 | * @access public
92 | * @return String
93 | */
94 | public static function lastError()
95 | {
96 | return self::$lastError;
97 | }
98 | /* }}} */
99 |
100 | /* {{{ private static void setError() */
101 | /**
102 | * 设置错误描述
103 | *
104 | * @access private static
105 | * @return void
106 | */
107 | private static function setError($error)
108 | {
109 | self::$lastError = trim($error);
110 | }
111 | /* }}} */
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/lib/livebox.php:
--------------------------------------------------------------------------------
1 | |
7 | // +--------------------------------------------------------------------+
8 | //
9 | // $Id: livebox.php 2010-04-23 zhangxc83 Exp $
10 |
11 | namespace Myfox\Lib;
12 |
13 | use \Myfox\Lib\Cache\Apc;
14 |
15 | class LiveBox
16 | {
17 |
18 | /* {{{ 静态常量 */
19 |
20 | const OFFS = 1;
21 |
22 | const PROF = 2;
23 |
24 | /* }}} */
25 |
26 | /* {{{ 成员变量 */
27 |
28 | private $host = array(); /**< 服务器列表 */
29 |
30 | private $offs = array(); /**< 不可用列表 */
31 |
32 | private $prof = array(); /**< 故障历史 */
33 |
34 | private $pool = null;
35 |
36 | private $flush = 0; /**< 刷新数据 */
37 |
38 | private $last = null; /**< 上次返回的服务器 */
39 |
40 | private $live = 300; /**< 自动存活检查时间 */
41 |
42 | private $cache = null; /**< 缓存服务 */
43 |
44 | /* }}} */
45 |
46 | /* {{{ public Object __construct() */
47 | /**
48 | * 构造函数
49 | *
50 | * @access public
51 | * @param String $token : 缓存前缀
52 | * @param Integer $live : default 300
53 | * @return Object $this
54 | */
55 | public function __construct($token, $live = 300)
56 | {
57 | $this->live = max(0, (int)$live);
58 | $this->flush = 0;
59 | if (function_exists('apc_add')) {
60 | $this->cache = new Apc($token, $this->live);
61 | } else {
62 | $this->cache = null;
63 | }
64 |
65 | if (!empty($this->cache)) {
66 | $this->offs = self::filterOffs($this->cache->get('offs'));
67 | $this->prof = $this->cache->get('prof');
68 | }
69 | }
70 | /* }}} */
71 |
72 | /* {{{ public Boolean __destruct() */
73 | /**
74 | * 析构函数
75 | *
76 | * @access public
77 | * @return Boolean true
78 | */
79 | public function __destruct()
80 | {
81 | if (empty($this->cache)) {
82 | return;
83 | }
84 |
85 | if (self::OFFS & $this->flush) {
86 | $this->cache->set('offs', self::filterOffs(array_merge(
87 | (array)$this->cache->get('offs'),
88 | (array)$this->offs
89 | )), (int)(1.2 * $this->live));
90 | }
91 |
92 | if (self::PROF & $this->flush) {
93 | }
94 | }
95 | /* }}} */
96 |
97 | /* {{{ public Object register() */
98 | /**
99 | * 添加一台服务器
100 | *
101 | * @access public
102 | * @param Mixture $host
103 | * @return Object $this
104 | */
105 | public function register($host, $id = null)
106 | {
107 | $this->host[(null === $id) ? count($this->host) : $id] = $host;
108 | $this->pool = null;
109 | return $this;
110 | }
111 | /* }}} */
112 |
113 | /* {{{ public Object setOffline() */
114 | /**
115 | * 标记一台服务器为不可用
116 | *
117 | * @access public
118 | * @param Mixture $host (default null)
119 | * @return Object $this
120 | */
121 | public function setOffline($id = null, $num = 1, $ttl = 120)
122 | {
123 | $tm = time();
124 | $id = (null === $id) ? $this->last : $id;
125 | if ($num >= 2) {
126 | $va = &$this->prof[$id][(int)($tm / 6)];
127 | $va = empty($va) ? 1 : $va + 1;
128 | $this->flush |= self::PROF;
129 | }
130 |
131 | if ($num < 2 || $va >= $num) {
132 | $this->offs[$id] = $tm + $this->live;
133 | $this->flush |= self::OFFS;
134 | }
135 | $this->pool = null;
136 |
137 | return $this;
138 | }
139 | /* }}} */
140 |
141 | /* {{{ public Object cleanAllCache() */
142 | /**
143 | * 清理所有的对象属性
144 | *
145 | * @access public
146 | * @return void
147 | */
148 | public function cleanAllCache()
149 | {
150 | $this->host = array();
151 | $this->offs = array();
152 | $this->prof = array();
153 | $this->pool = null;
154 | $this->last = null;
155 |
156 | if (!empty($this->cache)) {
157 | $this->cache->cleanAllCache();
158 | }
159 |
160 | return $this;
161 | }
162 | /* }}} */
163 |
164 | /* {{{ public Mixture fetch() */
165 | /**
166 | * 随机获取一台可用服务器
167 | *
168 | * @access public
169 | * @return Mixture
170 | */
171 | public function fetch()
172 | {
173 | if (null === $this->pool) {
174 | $this->pool = array_keys(array_diff_key($this->host, $this->offs));
175 | }
176 | if (empty($this->pool)) {
177 | $this->pool = array_keys($this->host);
178 | $this->offs = array();
179 | }
180 |
181 | if (empty($this->pool)) {
182 | return null;
183 | }
184 |
185 | $this->last = $this->pool[array_rand($this->pool)];
186 | return $this->host[$this->last];
187 | }
188 | /* }}} */
189 |
190 | /* {{{ private static Mixture filterOffs() */
191 | /**
192 | * 根据时间过滤不可用列表
193 | *
194 | * @access private static
195 | * @param Array $offs
196 | * @return Array
197 | */
198 | private static function filterOffs($offs)
199 | {
200 | $tsamp = time();
201 | $return = array();
202 | foreach ((array)$offs AS $host => $time) {
203 | if ($time <= $tsamp) {
204 | continue;
205 | }
206 | $return[$host] = $time;
207 | }
208 |
209 | return $return;
210 | }
211 | /* }}} */
212 |
213 | }
214 |
215 |
--------------------------------------------------------------------------------
/lib/parser/url.php:
--------------------------------------------------------------------------------
1 | |
7 | // +--------------------------------------------------------------------+
8 | //
9 | // $Id: apc.php 380 2011-01-07 10:18:20Z zhangxc83 $
10 |
11 | namespace Myfox\Lib\Parser;
12 |
13 | class Url
14 | {
15 |
16 | /* {{{ 成员变量 */
17 |
18 | private $url;
19 |
20 | private $module;
21 |
22 | private $action;
23 |
24 | private $param;
25 |
26 | /* }}} */
27 |
28 | /* {{{ public void __construct() */
29 | /**
30 | * 构造函数
31 | *
32 | * @access public
33 | * @param String $restUrl
34 | * @return void
35 | */
36 | public function __construct($url)
37 | {
38 | $this->url = trim(ltrim($url, '/'));
39 | $this->parse();
40 | }
41 | /* }}} */
42 |
43 | /* {{{ public Mixture __get() */
44 | /**
45 | * __get魔术方法
46 | *
47 | * @access public
48 | * @return Mixture
49 | */
50 | public function __get($key)
51 | {
52 | return isset($this->$key) ? $this->$key : null;
53 | }
54 | /* }}} */
55 |
56 | /* {{{ public String module() */
57 | /**
58 | * 返回module
59 | *
60 | * @access public
61 | * @return String
62 | */
63 | public function module()
64 | {
65 | return $this->module;
66 | }
67 | /* }}} */
68 |
69 | /* {{{ public String action() */
70 | /**
71 | * 返回action
72 | *
73 | * @access public
74 | * @return String
75 | */
76 | public function action()
77 | {
78 | return $this->action;
79 | }
80 | /* }}} */
81 |
82 | /* {{{ public Mixture param() */
83 | /**
84 | * 获取URL中的参数值
85 | *
86 | * @access public
87 | * @return Mixture
88 | */
89 | public function param($key = null, $default = null)
90 | {
91 | if (null === $key) {
92 | return $this->param;
93 | }
94 |
95 | return isset($this->param[$key]) ? $this->param[$key] : $default;
96 | }
97 | /* }}} */
98 |
99 | /* {{{ public static string build() */
100 | /**
101 | * 构造URL
102 | *
103 | * @access public static
104 | * @param String $module
105 | * @param String $action
106 | * @param Mixture $param : default null
107 | * @return String
108 | */
109 | public static function build($module, $action, $param = null)
110 | {
111 | $parts = array(
112 | self::escape($module),
113 | self::escape($action),
114 | );
115 | foreach ((array)$param AS $key => $val) {
116 | if (!is_scalar($val)) {
117 | continue;
118 | }
119 | $parts[] = sprintf('%s/%s', self::escape($key), urlencode($val));
120 | }
121 |
122 | return implode('/', $parts);
123 | }
124 | /* }}} */
125 |
126 | /* {{{ private void parse() */
127 | /**
128 | * URL解析程序
129 | *
130 | * @access private
131 | * @return void
132 | */
133 | private function parse()
134 | {
135 | $urls = explode('?', $this->url);
136 | $urls = array_values(array_filter(array_map('trim',
137 | explode('/', reset($urls))
138 | ), 'strlen'));
139 | $this->module = isset($urls[0]) ? self::escape($urls[0]) : '';
140 | $this->action = isset($urls[1]) ? self::escape($urls[1]) : '';
141 | $this->param = array();
142 |
143 | for ($i = 2, $max = count($urls); $i < $max; $i++) {
144 | $name = self::escape($urls[$i]);
145 | if (!isset($urls[++$i])) {
146 | $this->param[$name] = true;
147 | } else {
148 | $this->param[$name] = urldecode($urls[$i]);
149 | }
150 | }
151 | }
152 | /* }}} */
153 |
154 | /* {{{ private static String escape() */
155 | /**
156 | * 过滤URL中的非安全字符
157 | *
158 | * @access private static
159 | * @param String $str
160 | * @return String
161 | */
162 | private static function escape($str)
163 | {
164 | return trim(preg_replace('/[^\w]/is', '', $str));
165 | }
166 | /* }}} */
167 |
168 | }
169 |
--------------------------------------------------------------------------------
/release.properties:
--------------------------------------------------------------------------------
1 | dir.root=/home/pengchun/work/datacube/myfox/branches/open
2 | run.mode=online
3 |
4 | [svn]
5 | code.svn.url=SVN.URL.FOR.YOUR.CODES
6 |
7 | [mysql]
8 | default.mysql.logurl=log://notice.warn.error//home/pengchun/mysql.dev.log
9 | default.mysql.dbname=meta_myfox_config
10 | default.mysql.prefix=
11 | default.mysql.master=mysql://db_write:123456@172.0.0.1
12 | default.mysql.slave=mysql://db_read:123456@172.0.0.2
13 |
14 | [path]
15 | myfox.download.path=/disk1/var/myfox/download
16 | myfox.filesplit.path=/disk2/var/myfox/filesplit
17 |
18 | [bash]
19 | myfox.rpc.server=127.0.0.1:9999/pengchun/myfox2,localhost:9999/pengchun/myfox2
20 | myfox.rpc.agent=Myfox RPC Caller (0.8.1)
21 | net.filename.prefix=ftp://user:pass@{HOST}:21
22 |
23 | [alert]
24 | alert.normal.command=echo \"${TITLE}\" | /bin/mail -s alert YOURNAME@COMPANY.COM
25 | alert.urgence.command=短信报警
26 |
27 |
--------------------------------------------------------------------------------
/resource/bash/gateway_call_myfox_import.sh:
--------------------------------------------------------------------------------
1 | # ! /bin/bash
2 | #
3 | # vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: #
4 | # ----------------------------------------------------------------
5 | #
6 | # Myfox部署在 HDFS 网关机上的客户端
7 | #
8 | # @author: Aleafs
9 | # ----------------------------------------------------------------
10 |
11 | export LANG=en_US.UTF-8
12 |
13 | declare -r CFG_MYFOX_RPC_SERVER=(\
14 | ##myfox.rpc.server.list##
15 | )
16 | declare -r CFG_HTTP_USER_AGENT="##myfox.rpc.agent##"
17 |
18 | # @远程服务器获取本机文件的协议前缀
19 | declare -r CFG_FILENAME_PREFIX="##net.filename.prefix##"
20 |
21 | # {{{ function usage() #
22 | function usage() {
23 | cat <
25 |
26 | [OPTION]:
27 | -t logic table name in myfox, such as "numsplit"
28 | -r route value, such as "thedate=2011-06-10,cid=1"
29 | EOF
30 | }
31 | # }}} #
32 |
33 | # {{{ 运行时变量 #
34 |
35 | declare -r __RUN_ROOT__="$(dirname -- $(readlink -f -- ${BASH_SOURCE[0]}))"
36 | declare -r RUN_HTTP_OUT="${__RUN_ROOT__}/http.out"
37 | declare -r RUN_AGENT_IP=`hostname -i | awk -F. '{print lshift($1,24)+lshift($2,16)+lshift($3,8)+$4}'`
38 |
39 | if [ -z "${CFG_FILENAME_PREFIX}" ] ; then
40 | declare -r RUN_FILENAME_PREFIX=""
41 | else
42 | declare -r RUN_FILENAME_PREFIX=${CFG_FILENAME_PREFIX//\{HOST\}/`hostname -i`}
43 | fi
44 |
45 | declare OPT_TABLE_NAME=""
46 | declare OPT_ROUTE_TEXT=""
47 | while getopts 't:r:' opt ; do
48 | case ${opt} in
49 | t)
50 | OPT_TABLE_NAME="${OPTARG}";;
51 | r)
52 | OPT_ROUTE_TEXT="${OPTARG//=/:}";;
53 | *);;
54 | esac
55 | done
56 | shift $(($OPTIND - 1))
57 | declare OPT_FILE_NAME=$(readlink -f -- "${1}")
58 | declare OPT_IS_READY="${2}"
59 |
60 | if [ -z "${OPT_TABLE_NAME}" -o -z "${OPT_FILE_NAME}" ] ; then
61 | usage
62 | exit 100
63 | fi
64 |
65 | if [ ! -f ${OPT_FILE_NAME} ] ; then
66 | echo "No such file named as \"${OPT_FILE_NAME}\"."
67 | exit 200
68 | fi
69 |
70 | if [ -z "${OPT_IS_READY}" ] ; then
71 | OPT_IS_READY=0
72 | fi
73 |
74 | # }}} #
75 |
76 | # {{{ function log()
77 | function log() {
78 | printf "%s:\t[%s %s]\t{%d}\t%s\t%s\n" \
79 | `echo "${1}" | tr a-z A-Z` `date +"%Y-%m-%d %H:%M:%S"` \
80 | ${$} `echo ${2} | tr a-z A-Z` "${3}"
81 | }
82 | # }}}
83 |
84 | # {{{ function urlencode() #
85 | function urlencode() {
86 | echo "${1}" | xxd -plain | tr -d "\n" | sed 's/\(..\)/%\1/g'
87 | }
88 | # }}} #
89 |
90 | # {{{ function http() #
91 | function http() {
92 | local url="${1}"
93 | local ext="${2}"
94 |
95 | for prefix in ${CFG_MYFOX_RPC_SERVER[@]} ; do
96 | curl -s -m30 ${ext} -A"${CFG_HTTP_USER_AGENT}" -o"${RUN_HTTP_OUT}" "http://${prefix}${url}" &> /dev/null
97 | if [ ${?} -eq 0 -a `grep -c "\[0\] OK" ${RUN_HTTP_OUT}` -gt 0 ] ; then
98 | return 0
99 | fi
100 | done
101 |
102 | return 1
103 | }
104 | # }}} #
105 |
106 | # {{{ Call API Hello() #
107 |
108 | declare -i RUN_FILE_LINES=`wc -l ${OPT_FILE_NAME} | cut -d" " -f1`
109 |
110 | log "NOTICE" "START" "table=${OPT_TABLE_NAME}&route=${OPT_ROUTE_TEXT}&file=${OPT_FILE_NAME}&line=${RUN_FILE_LINES}"
111 |
112 | declare -r OPT_TABLE_NAME=`urlencode "${OPT_TABLE_NAME}"`
113 | declare -r OPT_ROUTE_TEXT=`urlencode "${OPT_ROUTE_TEXT}"`
114 | declare -r OPT_FILE_NAME=`urlencode "${RUN_FILENAME_PREFIX}/${OPT_FILE_NAME}"`
115 | declare -r RUN_POST_DATA="table=${OPT_TABLE_NAME}&route=${OPT_ROUTE_TEXT}&file=${OPT_FILE_NAME}&lines=${RUN_FILE_LINES}"
116 |
117 | http "/import/hello" "-d${RUN_POST_DATA}"
118 | if [ ${?} -ne 0 ] ; then
119 | log "FATAL" "RPC_HELLO_FAIL" "`head -n1 ${RUN_HTTP_OUT}`"
120 | exit 210
121 | fi
122 |
123 | log "NOTICE" "RPC_HELLO"
124 |
125 | if [ "${OPT_IS_READY}" -gt 0 ] ; then
126 | http "/import/ready"
127 | log "NOTICE" "RPC_READY"
128 | fi
129 |
130 | if [ -f "${RUN_HTTP_OUT}" ] ; then
131 | /bin/rm -f "${RUN_HTTP_OUT}"
132 | fi
133 |
134 | exit 0
135 |
136 | # }}} #
137 |
138 |
--------------------------------------------------------------------------------
/resource/bash/myfoxctl.sh:
--------------------------------------------------------------------------------
1 | # !/bin/bash
2 | # vim: set expandtab tabstop=2 shiftwidth=2 foldmethod=marker: #
3 |
4 | export LANG=en_US.UTF-8
5 |
6 | . /etc/init.d/functions
7 |
8 | declare -r MYFOX_RUN_MODE="##run.mode##"
9 | declare -r CFG_PHP_CLI_PATH="##php.cli.path##"
10 |
11 | declare -r __ROOT__="$(dirname -- $(dirname -- $(readlink -f -- ${0})))"
12 |
13 | # {{{ function usage() #
14 | function usage() {
15 | echo "Usage: ${0} {start|stop|restart}"
16 | exit 1;
17 | }
18 | # }}} #
19 |
20 | # {{{ function checkuser() #
21 | function checkuser() {
22 | if [ "##run.user##" != `id -nu` ] ; then
23 | echo "Only ${MYFOX_RUN_USER} is allowed to run myfox!"
24 | exit 2
25 | fi
26 | }
27 | checkuser
28 | # }}} #
29 |
30 | # {{{ function daemonpid() #
31 | function daemonpid() {
32 | ps uxwwww | grep "run.php" | grep -w "${1}" | awk '{print $2}'
33 | }
34 | # }}} #
35 |
36 | # {{{ function daemonstart() #
37 | function daemonstart() {
38 | local pid=`daemonpid ${1}`
39 | if [ -z "${pid}" ] ; then
40 | echo "Start ${1} process ... "
41 | nohup ${CFG_PHP_CLI_PATH} ${__ROOT__}/bin/run.php ${1} &
42 | if [ ${?} -ne 0 ] ; then
43 | echo_failure
44 | else
45 | echo_success
46 | fi
47 | else
48 | echo "${1} (pid: ${pid}) is running"
49 | fi
50 | }
51 | # }}} #
52 |
53 | # {{{ function daemonstop() #
54 | function daemonstop() {
55 | local pid=`daemonpid ${1}`
56 | if [ -z "${pid}" ] ; then
57 | echo "${1} is not running"
58 | else
59 | echo "Stop ${1} process ..."
60 | kill ${pid}
61 |
62 | for num in 1 1 2 3 ; do
63 | if [ ${num} -gt 0 ] ; then
64 | sleep ${num}
65 | fi
66 |
67 | pid=`daemonpid ${1}`
68 | if [ -z "${pid}" ] ; then
69 | echo_success
70 | break
71 | fi
72 | sleep 1
73 | done
74 |
75 | if [ ! -z "${pid}" ] ; then
76 | kill -9 ${pid}
77 | echo_success
78 | fi
79 | fi
80 | }
81 | # }}} #
82 |
83 | # {{{ function start() #
84 | function start() {
85 | daemonstart metasync
86 | }
87 | # }}} #
88 |
89 | # {{{ function stop() #
90 | function stop() {
91 | daemonstop metasync
92 | }
93 | # }}} #
94 |
95 | case "${1}" in
96 | start)
97 | start;;
98 | stop)
99 | stop;;
100 | restart)
101 | stop
102 | start
103 | ;;
104 | *)
105 | usage;;
106 | esac
107 |
--------------------------------------------------------------------------------
/test/unit/AlertTest.php:
--------------------------------------------------------------------------------
1 | assertContains(' [dev] test', ob_get_contents());
19 |
20 | @ob_end_clean();
21 | }
22 | /* }}} */
23 |
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/test/unit/ApcTest.php:
--------------------------------------------------------------------------------
1 | 'b', 'c' => array('d' => 'e'));
28 | $apc = new Apc(__METHOD__);
29 | $this->assertEquals(null, $apc->get('key1'), 'Apc should be empty.');
30 |
31 | $apc->set('key1', $val, 1);
32 | $apc->set('key2', $apc->get('key1'));
33 |
34 | $this->assertEquals($val, $apc->get('key1'), 'Apc set / get Error.');
35 |
36 | // XXX: 同一进程内apc不过期
37 | //$this->assertEquals(null, $apc->get('key1'), 'Apc should has been expired.');
38 | $this->assertEquals($val, $apc->get('key2'), 'Apc set / get Error.');
39 |
40 | $apc->delete('key1');
41 | $apc->delete('key2');
42 | $this->assertEquals(null, $apc->get('key2'), 'Apc should has been delete.');
43 | }
44 | /* }}} */
45 |
46 | /* {{{ public void test_should_apc_with_compress_works_fine() */
47 | public function test_should_apc_with_compress_works_fine()
48 | {
49 | $val = array('a' => 'b', 'c' => array('d' => 'e'));
50 | $apc = new Apc(__METHOD__, true);
51 | $this->assertEquals(null, $apc->get('key1'), 'Apc should be empty.');
52 |
53 | $apc->set('key1', $val, 1);
54 | $apc->set('key2', $apc->get('key1'));
55 |
56 | $this->assertEquals($val, $apc->get('key1'), 'Apc set / get Error with compress.');
57 | }
58 | /* }}} */
59 |
60 | /* {{{ public void test_should_cache_shell_works_fine() */
61 | public function test_should_cache_shell_works_fine()
62 | {
63 | $apc = new Apc(__METHOD__);
64 | $this->shell = 0;
65 |
66 | $this->assertEquals(md5(1), $apc->shell(array(&$this, 'loadShellData'), 1));
67 | $this->assertEquals(1, $this->shell);
68 |
69 | $this->assertEquals(md5(1), $apc->shell(array(&$this, 'loadShellData'), 1));
70 | $this->assertEquals(1, $this->shell);
71 |
72 | $this->assertEquals(md5(3), $apc->shell(array(&$this, 'loadShellData'), 3));
73 | $this->assertEquals(2, $this->shell);
74 | }
75 | /* }}} */
76 |
77 | /* {{{ public void test_should_apc_add_works_fine() */
78 | public function test_should_apc_add_works_fine()
79 | {
80 | $apc = new Apc(__METHOD__);
81 | $this->assertTrue($apc->add('key1', 'val1'));
82 | $this->assertFalse($apc->add('key1', 'val2'));
83 | }
84 | /* }}} */
85 |
86 | public function test_should_data_compress_works_fine()
87 | {
88 | $apc = new Apc(__METHOD__, true);
89 |
90 | $val = array(
91 | 'a' => 1111,
92 | 'b' => str_pad('a', Apc::COMPRESS_SIZE + 1),
93 | );
94 | $this->assertTrue($apc->set('key1', $val));
95 |
96 | // xxx: __destruct only for flush to apc
97 | $apc = null;
98 |
99 | $apc = new Apc(__METHOD__, true);
100 | $this->assertEquals($val, $apc->get('key1', false));
101 | }
102 |
103 | /* {{{ public Mixture loadShellData() */
104 | public function loadShellData($key)
105 | {
106 | $this->shell++;
107 | return md5($key);
108 | }
109 | /* }}} */
110 |
111 | }
112 |
113 |
--------------------------------------------------------------------------------
/test/unit/AutoLoadTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(1, \Com\Aleafs\AutoLoadTestClass::$requireTime, 'Class Load Failed.');
29 |
30 | $case2 = new \Com\Aleafs\AutoLoadTestClass();
31 | $this->assertEquals(1, \Com\Aleafs\AutoLoadTestClass::$requireTime, 'Class Load Duplicate.');
32 | $this->assertContains(
33 | strtr(__DIR__ . '/autoload/com/aleafs/autoloadtestclass.php', '\\', '/'),
34 | strtr($case2->path(), '\\', '/'),
35 | 'Class Load Error Rules.'
36 | );
37 | }
38 |
39 | public function test_should_class_loader_by_order_worked_fine()
40 | {
41 | AutoLoad::register('com', __DIR__ . '/autoload/com');
42 | AutoLoad::register('com\\\\aleafs1', __DIR__ . '/autoload/com', 'com');
43 | AutoLoad::register('com\\\\aleafs2', __DIR__ . '/autoload/com', 'com');
44 | AutoLoad::unregister('com/aleafs2');
45 | AutoLoad::register('com\\\\aleafs', __DIR__ . '/autoload/com', 'com');
46 |
47 | $case = new \Com\Aleafs\AutoLoadOrderTestClass();
48 | $this->assertEquals(
49 | strtr(__DIR__ . '/autoload/com/autoloadordertestclass.php', '\\', '/'),
50 | strtr($case->path(), '\\', '/'),
51 | 'Class Load by Order Error.'
52 | );
53 | }
54 |
55 | public function test_should_throw_file_not_found_when_cant_find_class_file()
56 | {
57 | AutoLoad::register('com', __DIR__ . '/autoload/com');
58 | try {
59 | $case1 = new \Com\I\Am\Not\Exists();
60 | } catch (\Exception $e) {
61 | $this->assertTrue($e instanceof \Myfox\Lib\Exception, 'Exception Type doesn\'t match,');
62 | $this->assertContains(
63 | sprintf('File "%s/autoload/com/i/am/not/exists.php', strtr(__DIR__, '\\', '/')),
64 | strtr($e->getMessage(), '\\', '/'),
65 | 'Exception Message doesn\'t match.'
66 | );
67 | }
68 | }
69 |
70 | public function test_should_throw_class_not_found_when_rule_not_defined()
71 | {
72 | AutoLoad::register('com', __DIR__ . '/autoload/com');
73 | try {
74 | $case1 = new \I\Am\Not\Exists();
75 | } catch (\Exception $e) {
76 | $this->assertTrue($e instanceof \Myfox\Lib\Exception, 'Exception Type doesn\'t match,');
77 | $this->assertContains(
78 | 'Class "I\Am\Not\Exists" Not Found',
79 | $e->getMessage(),
80 | 'Exception Message doesn\'t match.'
81 | );
82 | }
83 | }
84 |
85 | public function test_should_throw_class_not_found_when_class_not_in_file()
86 | {
87 | AutoLoad::register('com', __DIR__ . '/autoload/com');
88 | try {
89 | $case1 = new \Com\Aleafs\AutoLoadTestCaseClassNameNotMatched();
90 | } catch (\Exception $e) {
91 | $this->assertTrue($e instanceof \Myfox\Lib\Exception, 'Exception Type doesn\'t match,');
92 | $this->assertTrue(
93 | (bool)preg_match(
94 | '/^Class "(.+?)" NOT FOUND IN "(.+?)"/is',
95 | $e->getMessage()
96 | ),
97 | 'Exception Message doesn\'t match.'
98 | );
99 | }
100 | }
101 | }
102 |
103 |
--------------------------------------------------------------------------------
/test/unit/BlackHoleTest.php:
--------------------------------------------------------------------------------
1 | lalala = '我不起作用的';
16 |
17 | $this->assertNull($object->lalala);
18 | $this->assertNull($object->helloworld());
19 | $this->assertNull(Blackhole::staticHello());
20 | }
21 |
22 | }
23 |
24 |
25 |
--------------------------------------------------------------------------------
/test/unit/BucketTest.php:
--------------------------------------------------------------------------------
1 | push('aa', 0);
16 | $cargos = array(
17 | array('id' => 1, 'count' => 12),
18 | array('id' => 2, 'count' => 13),
19 | array('id' => 3, 'count' => 11),
20 | array('id' => 4, 'count' => 1),
21 | array('id' => 5, 'count' => 1),
22 | );
23 | foreach ($cargos AS $row) {
24 | $bucket->push($row, $row['count']);
25 | }
26 |
27 | $this->assertEquals(
28 | array(
29 | array(
30 | array(
31 | 'size' => 12,
32 | 'data' => $cargos[0],
33 | ),
34 | ),
35 | array(
36 | array(
37 | 'size' => 11,
38 | 'data' => $cargos[2],
39 | ),
40 | array(
41 | 'size' => 1,
42 | 'data' => $cargos[3],
43 | ),
44 | ),
45 | array(
46 | array(
47 | 'size' => 10,
48 | 'data' => $cargos[1],
49 | ),
50 | array(
51 | 'size' => 1,
52 | 'data' => $cargos[4],
53 | ),
54 | ),
55 | array(
56 | array(
57 | 'size' => 3,
58 | 'data' => $cargos[1],
59 | ),
60 | ),
61 | ),
62 | $bucket->allot()
63 | );
64 | }
65 | /* }}} */
66 |
67 | /* {{{ public void test_should_zero_cubage_works_fine() */
68 | /**
69 | * XXX : THIS IS A BUG CASE
70 | */
71 | public function test_should_zero_cubage_works_fine()
72 | {
73 | $bucket = new Bucket(0, 0.2);
74 | $bucket->push('aa', 0);
75 | $cargos = array(
76 | array('id' => 1, 'count' => 2410000),
77 | );
78 | foreach ($cargos AS $row) {
79 | $bucket->push($row, $row['count']);
80 | }
81 |
82 | $this->assertEquals(
83 | array(
84 | array(
85 | array(
86 | 'size' => 2000000,
87 | 'data' => $cargos[0],
88 | ),
89 | ),
90 | array(
91 | array(
92 | 'size' => 410000,
93 | 'data' => $cargos[0],
94 | ),
95 | ),
96 | ),
97 | $bucket->allot()
98 | );
99 | }
100 | /* }}} */
101 |
102 | }
103 |
104 |
--------------------------------------------------------------------------------
/test/unit/ChecktableTest.php:
--------------------------------------------------------------------------------
1 | 11,
28 | ));
29 | $this->assertEquals(11, $worker->interval());
30 | $this->assertEquals(false, $worker->execute());
31 | }
32 | /* }}} */
33 |
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/test/unit/CleanerTest.php:
--------------------------------------------------------------------------------
1 | route_table = self::cleanTable('default', 'route_info');
27 | $this->queque_table = self::cleanTable('default', 'task_queque');
28 | }
29 | /* }}} */
30 |
31 | /* {{{ public void test_should_clean_deleted_route_works_fine() */
32 | public function test_should_clean_deleted_route_works_fine()
33 | {
34 | $query = sprintf(
35 | 'INSERT INTO %s (route_flag,hosts_list,real_table,route_text,addtime,modtime) VALUES ' .
36 | "(%d,'{1,2,3}$$','a.t_1_1','cid:1,thedate:2222',%d,%d)," .
37 | "(%d,'{1,2,3}','a.t_1_2','cid:2,thedate:2222',%d,%d)," .
38 | "(%d,'{1,2,3}','a.t_1_3','',%d,%d)," .
39 | "(%d,'}$','a.t_1_4','cid:4,thedate:2222',%d,%d)," .
40 | "(%d,'}$','a.t_1_5','cid:5,thedate:2222',%d,%d)," .
41 | "(%d,'}$','a.t_1_6','cid:6,thedate:2222',%d,%d)",
42 | $this->route_table,
43 | Router::FLAG_IS_DELETED, time() - 100100, time() - 100100, // OK
44 | Router::FLAG_IS_DELETED, time() - 99900, time() - 100000, // modtime
45 | Router::FLAG_IS_DELETED, time() - 100100, time() - 100100, // where empty
46 | Router::FLAG_IS_DELETED, time() - 100100, time() - 100100, // empty hosts
47 | Router::FLAG_PRE_IMPORT, time() - 865000, time() - 100100, // delete
48 | Router::FLAG_PRE_IMPORT, time() - 863000, time() - 100100 // don't delete
49 | );
50 |
51 | $this->assertEquals(6, self::$mysql->query($query));
52 |
53 | Cleaner::clean_invalidated_routes();
54 |
55 | $this->assertEquals(array(
56 | array(
57 | 'real_table' => 'a.t_1_6',
58 | ),
59 | array(
60 | 'real_table' => 'a.t_1_2',
61 | ),
62 | ), self::$mysql->getAll(self::$mysql->query(sprintf(
63 | 'SELECT real_table FROM %s ORDER BY autokid DESC LIMIT 6', $this->route_table
64 | ))));
65 |
66 | $this->assertEquals(array(
67 | array(
68 | 'task_flag' => Queque::FLAG_WAIT,
69 | 'task_info' => json_encode(array(
70 | 'host' => '1,2,3',
71 | 'path' => 'a.t_1_3',
72 | 'where' => '',
73 | )),
74 | ),
75 | array(
76 | 'task_flag' => Queque::FLAG_WAIT,
77 | 'task_info' => json_encode(array(
78 | 'host' => '1,2,3',
79 | 'path' => 'a.t_1_1',
80 | 'where' => 'cid = 1 AND thedate = 2222',
81 | )),
82 | ),
83 | ), self::$mysql->getAll(self::$mysql->query(sprintf(
84 | "SELECT task_flag,task_info FROM %s WHERE task_type = 'delete' ORDER BY autokid DESC LIMIT 6",
85 | $this->queque_table
86 | ))));
87 |
88 | $clean = new Cleaner(array());
89 | $this->assertEquals(false, $clean->execute(false));
90 | }
91 | /* }}} */
92 |
93 | }
94 |
95 |
--------------------------------------------------------------------------------
/test/unit/ConfigTest.php:
--------------------------------------------------------------------------------
1 | assertTrue(false);
28 | } catch (\Exception $e) {
29 | $this->assertTrue($e instanceof \Myfox\Lib\Exception);
30 | $this->assertContains('Undefined config name as "i_am_not_exists"', $e->getMessage());
31 | }
32 |
33 | Config::register('confIg1', 'http://localhost/a.ini');
34 | $obj1 = Config::instance('config1');
35 | $obj2 = Config::instance('CONF Ig1');
36 | $this->assertEquals($obj1, $obj2);
37 | }
38 | /* }}} */
39 |
40 | /* {{{ public void test_should_config_with_ini_works_fine() */
41 | public function test_should_config_with_ini_works_fine()
42 | {
43 | $config = new Config(__DIR__ . '/ini/config_test.ini', 'unittest');
44 | $this->assertEquals('abc', $config->get('key1'));
45 | $this->assertEquals(array(
46 | 'a' => 1.23456789,
47 | 'b' => 9.87654321,
48 | ), $config->get('key2'));
49 | $this->assertEquals('null', $config->get('key3', 'null'));
50 |
51 | $config = Config::instance('unittest');
52 | $this->assertEquals('', $config->get('key2/a'));
53 | $this->assertEquals(9.87654321, $config->get('key2/b'));
54 | }
55 | /* }}} */
56 |
57 | }
58 |
59 |
--------------------------------------------------------------------------------
/test/unit/ConsistTest.php:
--------------------------------------------------------------------------------
1 | assertTrue(false);
30 | } catch (\Exception $e) {
31 | $this->assertTrue($e instanceof \Myfox\Lib\Exception);
32 | $this->assertContains("Empty server list for consistency check", $e->getMessage());
33 | }
34 |
35 | $this->assertTrue(Consist::check('i_am_not_exists.lalalla', array('edp1_9801', 'edp2_9902')));
36 | $this->assertTrue(Consist::check('mirror_0.mirror_583_2', array('edp1_9801', 'edp2_9902')));
37 | }
38 | /* }}} */
39 |
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/test/unit/ContextTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('val1', Context::get('key1', 'val2'));
31 | $this->assertEquals('default', Context::get('key2', 'default'));
32 | }
33 | /* }}} */
34 |
35 | /* {{{ public void test_should_get_correct_pid() */
36 | public function test_should_get_correct_pid()
37 | {
38 | $this->assertEquals(getmypid(), Context::pid());
39 | }
40 | /* }}} */
41 |
42 | /* {{{ public void test_should_get_correct_userip() */
43 | public function test_should_get_correct_userip()
44 | {
45 | $_SERVER = array();
46 | $this->assertEquals('unknown', Context::userip());
47 | $this->assertEquals(0, Context::userip(true));
48 |
49 | $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
50 | $this->assertEquals('unknown', Context::userip());
51 | $this->assertEquals(0, Context::userip(true));
52 |
53 | Context::cleanAllContext();
54 | $this->assertEquals('127.0.0.1', Context::userip());
55 | $this->assertEquals(ip2long('127.0.0.1'), Context::userip(true));
56 |
57 | $_SERVER['HTTP_X_FORWARDED_FOR'] = '202.111.111.123,59.66.192.112';
58 | Context::cleanAllContext();
59 | $this->assertEquals('59.66.192.112', Context::userip());
60 | $this->assertEquals(ip2long('59.66.192.112'), Context::userip(true));
61 | }
62 | /* }}} */
63 |
64 | /* {{{ public void test_should_addr_works_fine() */
65 | public function test_should_addr_works_fine()
66 | {
67 | $ip = trim(`hostname -i`);
68 | $this->assertEquals($ip, Context::addr());
69 | }
70 | /* }}} */
71 |
72 | }
73 |
74 |
--------------------------------------------------------------------------------
/test/unit/DaemonTest.php:
--------------------------------------------------------------------------------
1 | sigaction(SIGINT, function() use ($daemon) {
49 | echo "Got signal SIGINT\n";
50 | $daemon->freelock();
51 | });
52 |
53 | $time1 = microtime(true);
54 |
55 | ob_start();
56 | $daemon->dispatch();
57 | $this->assertEquals(true, 1000 * (microtime(true) - $time1 - 1) < 50);
58 | $this->assertContains('Got signal SIGINT', ob_get_contents());
59 | @ob_end_clean();
60 | }
61 | /* }}} */
62 |
63 | /* {{{ public void test_should_daemon_works_fine() */
64 | public function test_should_daemon_works_fine()
65 | {
66 | \Myfox\App\Worker\Test::$number = 0;
67 |
68 | $time1 = microtime(true);
69 | Daemon::run(self::$inifile, array(
70 | 'script',
71 | 'test',
72 | '--sleep',
73 | 10,
74 | '--loop'
75 | ));
76 | $this->assertEquals(
77 | true,
78 | abs(1000 * (microtime(true) - $time1) - 50) < 10
79 | );
80 | $this->assertEquals(
81 | 5,
82 | \Myfox\App\Worker\Test::$number
83 | );
84 | }
85 | /* }}} */
86 |
87 | /* {{{ public void test_should_parse_option_works_fine() */
88 | public function test_should_parse_option_works_fine()
89 | {
90 | $this->assertEquals(array(
91 | 'a' => 1,
92 | 'b' => 2,
93 | 'c' => true,
94 | 'f' => 0,
95 | 'data' => 2,
96 | 'debug' => true,
97 | 'eof' => 'abcdefg',
98 | ), Daemon::parse(array(
99 | '-a',
100 | '1',
101 | '-b2',
102 | '-c',
103 | '-f0',
104 | '--data',
105 | '2',
106 | '--debug',
107 | '--eof=abcdefg',
108 | )));
109 | }
110 | /* }}} */
111 |
112 | /* {{{ public void test_should_daemon_lock_works_fine() */
113 | public function test_should_daemon_lock_works_fine()
114 | {
115 | Daemon::run(self::$inifile, array(
116 | 'script',
117 | 'test',
118 | '--locker',
119 | 'test loCkeR ',
120 | '--sleep=1'
121 | ));
122 | Daemon::run(self::$inifile, array(
123 | 'script',
124 | 'test',
125 | '--locker',
126 | 'test locker',
127 | '--sleep=1'
128 | ));
129 | }
130 | /* }}} */
131 |
132 | }
133 |
134 |
--------------------------------------------------------------------------------
/test/unit/DebugTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(null, Timer::elapsed('key1'));
33 |
34 | Timer::init(true);
35 | $t1 = microtime(true);
36 | Timer::start('key1');
37 | usleep(300000); // 3ms
38 | $this->assertEquals(null, Timer::elapsed('key2'));
39 |
40 | $t2 = microtime(true);
41 | $tt = Timer::elapsed('key1');
42 | $this->assertTrue(10 > 1000 * (int)abs($tt - $t2 + $t1));
43 |
44 | $this->assertEquals(null, Timer::elapsed('key1'));
45 | }
46 | /* }}} */
47 |
48 | /* {{{ public void test_should_ignore_push_when_debug_not_open() */
49 | /**
50 | * 调试关闭时Push不起作用
51 | * @return void
52 | */
53 | public function test_should_ignore_push_when_debug_not_open()
54 | {
55 | Pool::init(false);
56 | $this->assertFalse(Pool::push('test', '啦啦啦啦'));
57 | $this->assertEquals('NULL', Pool::dump('test'));
58 | }
59 | /* }}} */
60 |
61 | /* {{{ public void test_should_push_message_works_fine() */
62 | /**
63 | * 测试开启状态下的push功能
64 | */
65 | public function test_should_push_message_works_fine()
66 | {
67 | Pool::init(true);
68 |
69 | $val = 'debug1';
70 | Pool::push('test1', $val);
71 | $this->assertEquals(var_export($val, true), Pool::dump('test1'));
72 |
73 | $exp = array(
74 | $val,
75 | array(
76 | 'text' => '我是中文',
77 | ),
78 | );
79 | Pool::push('test1', end($exp));
80 | $this->assertEquals(var_export($exp, true), Pool::dump('test1'));
81 |
82 | $obj = new Stdclass();
83 | $obj->val1 = 'key1';
84 | $obj->val2 = array('啦啦啦');
85 |
86 | Pool::push('test2', $obj);
87 | $this->assertEquals(var_export($obj, true), Pool::dump('test2'));
88 |
89 | $exp = array(
90 | 'test1' => $exp,
91 | 'test2' => $obj,
92 | );
93 | $this->assertEquals(var_export($exp, true), Pool::dump());
94 | }
95 | /* }}} */
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/test/unit/DispatcherTest.php:
--------------------------------------------------------------------------------
1 | inifile = __DIR__ . '/ini/myfox.ini';
21 | $config = new Config($this->inifile);
22 | $logurl = parse_url($config->get('log/default', ''));
23 |
24 | $this->prefix = rtrim($config->get('url.prefix', ''), '/');
25 | $this->logfile = $logurl['path'];
26 | @unlink($this->logfile);
27 | ob_start();
28 | }
29 |
30 | protected function tearDown()
31 | {
32 | @ob_end_clean();
33 | @unlink($this->logfile);
34 | parent::tearDown();
35 | }
36 |
37 | /* {{{ public void test_should_dispatcher_works_fine() */
38 | public function test_should_dispatcher_works_fine()
39 | {
40 | \Myfox\App\Dispatcher::run(
41 | __DIR__ . '/ini/myfox.ini',
42 | $this->prefix . '/',
43 | null
44 | );
45 |
46 | $this->assertContains('', ob_get_clean());
47 | $this->assertContains(
48 | "\tREQUEST\t-\t{\"url\":\"\/\",\"post\":[]}",
49 | self::getLogContents($this->logfile, -1)
50 | );
51 |
52 | \Myfox\App\Dispatcher::run(
53 | __DIR__ . '/ini/myfox.ini',
54 | $this->prefix . '/i_am_not_exist',
55 | 'a[]=b&c1=' . urlencode('//')
56 | );
57 | $this->assertContains(
58 | "\tEXCEPTION\t-\t{\"url\":\"\/i_am_not_exist\",\"post\":{\"a\":[\"b\"],\"c1\":\"\/\/\"},\"error\":",
59 | self::getLogContents($this->logfile, -1)
60 | );
61 | }
62 | /* }}} */
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/test/unit/FactoryTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('hello', $object->a);
30 | $this->assertEquals('default', $object->b);
31 | $object->b = 'world';
32 |
33 | $this->assertEquals($object, Factory::getObject(
34 | 'Myfox\\Test\\Factory\\Test', 'hello'
35 | ));
36 | }
37 | /* }}} */
38 |
39 | /* {{{ public void test_should_factory_get_log_works_fine() */
40 | public function test_should_factory_get_log_works_fine()
41 | {
42 | $log = Factory::getLog('Iamnotexist');
43 | $this->assertTrue($log instanceof \Myfox\Lib\Blackhole);
44 |
45 | Factory::registerLog('unittest', sprintf(
46 | 'log://debug.notice.warning.error/%s/tmp/test.log?buffer=0',
47 | __DIR__
48 | ));
49 |
50 | $log = Factory::getLog('UnittEst ');
51 | $this->assertTrue($log instanceof \Myfox\Lib\Log);
52 | }
53 | /* }}} */
54 |
55 | }
56 |
57 |
--------------------------------------------------------------------------------
/test/unit/FilesetTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(false, Fileset::getfile('/i/am/not/exists?query string does\'t effect'));
16 | $this->assertContains('File not found as the path "/i/am/not/exists"', Fileset::lastError());
17 | $this->assertEquals(__FILE__, Fileset::getfile(__FILE__));
18 | }
19 | /* }}} */
20 |
21 | /* {{{ public void test_should_getfile_exception_works_fine() */
22 | public function test_should_getfile_exception_works_fine()
23 | {
24 | $this->assertEquals(false, Fileset::getfile('http://www.taobao.com', '/i/am/not/exists/'));
25 | $this->assertContains('Unrecognized url as "http://www.taobao.com" for fileset.', Fileset::lastError());
26 |
27 | $this->assertEquals(false, Fileset::getfile('http://www.taobao.com/index.html', '/i/am/not/exists/taobao.txt'));
28 | $this->assertContains('Path "/i/am/not/exists" doesn\'t exist, and create failed.', Fileset::lastError());
29 |
30 | $this->assertEquals(false, Fileset::getfile('undefined://user:pass@ftp.a.com//b.txt'));
31 | $this->assertContains(sprintf(
32 | 'File "%s/lib/fetcher/undefined.php" Not Found.',
33 | realpath(__DIR__ . '/../../')
34 | ), Fileset::lastError());
35 | }
36 | /* }}} */
37 |
38 | /* {{{ public void test_should_get_file_from_ftp_works_fine() */
39 | public function test_should_get_file_from_ftp_works_fine()
40 | {
41 | $this->assertEquals(false, Fileset::getfile('ftp://www.baidu.com/aa.txt'));
42 | $this->assertContains('Connect failed with the host as "www.baidu.com" on port 21', Fileset::lastError());
43 |
44 | $fname = Fileset::getfile('ftp://ftp.adobe.com/license.txt?timeout=5');
45 | $this->assertEquals(sprintf('%s/license.txt', Fileset::TEMP_PATH), $fname);
46 | }
47 | /* }}} */
48 |
49 | /* {{{ public void test_should_get_file_from_scp_works_fine() */
50 | public function test_should_get_file_from_scp_works_fine()
51 | {
52 | #exec('ssh-keygen -t rsa');
53 | /* 信任关系打通 设置免登 */
54 | exec('ssh-copy-id -i ~/.ssh/id_rsa.pub 10.232.128.63');
55 | #$user = get_current_user();
56 | $user = array_shift(explode(' ', trim(exec('who am i'))));
57 | $fname = sprintf('Myfox_scp_test%s.txt', getmypid());
58 |
59 | $lpath = sprintf('/tmp/myfox/download/Myfox_scp_test%s.txt', getmypid());
60 | $rpath = sprintf('/home/%s/Myfox_scp_test%s.txt', $user, getmypid());
61 |
62 | $url = sprintf('scp://%s@10.232.128.63%s', $user, $rpath);
63 |
64 | exec(sprintf('ssh %s@10.232.128.63 "rm -rf %s"', $user, $rpath));
65 | @unlink($lpath);
66 |
67 |
68 | $this->assertEquals(false, Fileset::getfile($url));
69 | $this->assertContains('No such file or directory', Fileset::lastError());
70 |
71 | exec(sprintf('ssh %s@10.232.128.63 "touch %s"', $user, $rpath));
72 | sleep(2);
73 | $this->assertEquals( $lpath, Fileset::getfile($url));
74 |
75 | $scp = new Scp(array(
76 | 'scheme' => 'scp',
77 | 'host' => '10.232.128.63',
78 | 'user' => $user,
79 | 'path' => $rpath,
80 | ));
81 |
82 | $this->assertEquals( false, $scp->isChange($lpath));
83 |
84 | exec(sprintf('ssh %s@10.232.128.63 "echo \"test data\" >> %s"', $user, $rpath));
85 |
86 | $this->assertEquals( $lpath, Fileset::getfile($url));
87 | $content = file_get_contents($lpath);
88 | $this->assertContains('test data', $content);
89 | }
90 | /* }}} */
91 | }
92 |
--------------------------------------------------------------------------------
/test/unit/FsplitTest.php:
--------------------------------------------------------------------------------
1 | 10000,
86 | __DIR__ . '/tmp/fsplit_test.txt_1' => 10000,
87 | __DIR__ . '/tmp/fsplit_test.txt_2' => 6000,
88 | );
89 |
90 | $fname = __DIR__ . '/tmp/fsplit_test.txt';
91 | $this->assertTrue(self::prepare_test_file($fname, 27000));
92 | $this->assertEquals(
93 | array_keys($expect),
94 | Fsplit::chunk($fname, array_values($expect), __DIR__ . '/tmp')
95 | );
96 |
97 | $total = 0;
98 | foreach ($expect AS $fname => $lines) {
99 | $realn = self::fileline($fname);
100 | $total += $realn;
101 | if (false !== next($expect)) {
102 | $this->assertTrue($lines * 0.95 < $realn && $lines * 1.05 > $realn);
103 | }
104 | }
105 | $this->assertEquals(27000, $total);
106 | }
107 | /* }}} */
108 |
109 | /* {{{ public void test_should_ignore_chunks_more_than_total_line() */
110 | public function test_should_ignore_chunks_more_than_total_line()
111 | {
112 | $fname = __DIR__ . '/tmp/fsplit_test.txt';
113 | $this->assertTrue(self::prepare_test_file($fname, 2700));
114 | $this->assertEquals(array(
115 | __DIR__ . '/tmp/fsplit_test.txt_0',
116 | __DIR__ . '/tmp/fsplit_test.txt_1',
117 | ), Fsplit::chunk($fname, array(2000, 1000, 1000), __DIR__ . '/tmp'));
118 | }
119 | /* }}} */
120 |
121 | /* {{{ public void test_should_get_last_error_works_fine() */
122 | public function test_should_get_last_error_works_fine()
123 | {
124 | $object = new Fsplit('/i/am/not/exits');
125 | $this->assertEquals(false, $object->split(array(100)));
126 | $this->assertContains('No such file named as "/i/am/not/exits"', $object->lastError());
127 |
128 | $object = new Fsplit(__FILE__);
129 | $this->assertEquals(false, $object->split(array(100), '/created/denied'));
130 | $this->assertContains('Directory "/created/denied" created failed', $object->lastError());
131 |
132 | $fname = __DIR__ . '/tmp/fsplit_test.txt';
133 | !is_dir(dirname($fname)) && @mkdir(dirname($fname), 0755, true);
134 | file_put_contents($fname, 'bbbbbbb');
135 |
136 | $object = new Fsplit($fname);
137 | $this->assertEquals(false, $object->split(array(100), __DIR__ . '/tmp'));
138 | $this->assertContains('Unrecognized text formmat, or line size larger than ' . Fsplit::BUFFER_SIZE, $object->lastError());
139 | }
140 | /* }}} */
141 |
142 | }
143 |
144 |
--------------------------------------------------------------------------------
/test/unit/LiveBoxTest.php:
--------------------------------------------------------------------------------
1 | |
9 | // +--------------------------------------------------------------------+
10 | //
11 | // $Id: LiveBoxTest.php 47 2010-04-26 05:27:46Z zhangxc83 $
12 |
13 | use \Myfox\Lib\LiveBox;
14 | require_once(__DIR__ . '/../../lib/TestShell.php');
15 |
16 | class LiveBoxTest extends \Myfox\Lib\TestShell
17 | {
18 |
19 | protected function setUp()
20 | {
21 | parent::setUp();
22 | $this->pool = new LiveBox(__CLASS__);
23 | }
24 |
25 | protected function tearDown()
26 | {
27 | if (!empty($this->pool)) {
28 | $this->pool->cleanAllCache();
29 | unset($this->pool);
30 | }
31 |
32 | parent::tearDown();
33 | }
34 |
35 | /* {{{ public function test_should_right_select_by_random() */
36 | public function test_should_random_select_works_fine()
37 | {
38 | $hosts = array(
39 | '127.0.0.1:1234',
40 | '127.0.0.1:1234',
41 | '127.0.0.1:1235',
42 | '127.0.0.1:1236',
43 | '127.0.0.1:1237',
44 | '127.0.0.1:1238',
45 | '127.0.0.1:1239',
46 | );
47 |
48 | foreach ($hosts AS $host) {
49 | $this->pool->register($host);
50 | }
51 | $this->pool->register('I\m not exists');
52 |
53 | $result = array();
54 | for ($i = 0; $i < 10000; $i++) {
55 | $host = $this->pool->fetch();
56 | if (!preg_match('/^[\d\.]+:\d+$/is', $host)) { /**< 模拟连接 */
57 | $this->pool->setOffline();
58 | }
59 |
60 | if (!isset($result[$host])) {
61 | $result[$host] = 1;
62 | } else {
63 | $result[$host]++;
64 | }
65 | }
66 | $this->assertTrue($result['I\m not exists'] < 5, 'setOffline Doesn\t work.');
67 |
68 | $total = 10000 / count($hosts);
69 | $weight = array();
70 | foreach ($hosts AS $host) {
71 | $weight[$host] = isset($weight[$host]) ? $weight[$host] + 1 : 1;
72 | }
73 |
74 | foreach ($weight AS $host => $wt) {
75 | $this->assertTrue(
76 | ($result[$host] >= 0.8 * $wt * $total) && ($result[$host] <= 1.2 * $wt * $total),
77 | sprintf('Host "%s" random selector error.', $host)
78 | );
79 | }
80 |
81 | }
82 | /* }}} */
83 |
84 | /* {{{ public function test_should_reset_when_all_offline() */
85 | public function test_should_reset_when_all_offline()
86 | {
87 | $this->pool->register('www.baidu.com', 'baidu')
88 | ->register('www.google.com', 'google')
89 | ->setOffline('baidu')
90 | ->setOffline('google');
91 |
92 | $this->assertContains(
93 | $this->pool->fetch(),
94 | array(
95 | 'www.baidu.com',
96 | 'www.google.com',
97 | )
98 | );
99 | }
100 | /* }}} */
101 |
102 | /* {{{ public function test_should_live_time_works_fine() */
103 | public function test_should_live_time_works_fine()
104 | {
105 | if (!function_exists('apc_add')) {
106 | return;
107 | }
108 |
109 | $pool = new LiveBox(__CLASS__, 2);
110 | $pool->register('www.baidu.com', 'www.baidu.com')->setOffline('www.baidu.com');
111 | unset($pool);
112 |
113 | $pool = new LiveBox(__CLASS__);
114 | $pool->register('www.baidu.com', 'www.baidu.com')->register('www.google.com');
115 |
116 | for ($i = 0; $i < 10; $i++) {
117 | $this->assertTrue(
118 | $pool->fetch() != 'www.baidu.com',
119 | 'Offline host should NOT appear!'
120 | );
121 | }
122 | unset($pool);
123 |
124 | sleep(2);
125 | $pool = new LiveBox(__CLASS__);
126 | $pool->register('www.baidu.com', 'www.baidu.com')->register('www.google.com');
127 |
128 | $return = array();
129 | for ($i = 0; $i < 1000; $i++) {
130 | $host = $pool->fetch();
131 | if (!isset($return[$host])) {
132 | $return[$host] = 1;
133 | } else {
134 | $return[$host]++;
135 | }
136 | }
137 |
138 | $this->assertTrue(
139 | 400 <= $return['www.baidu.com'] &&
140 | $return['www.baidu.com'] <= 600
141 | );
142 | $this->assertTrue(
143 | 400 <= $return['www.google.com'] &&
144 | $return['www.google.com'] <= 600
145 | );
146 | }
147 | /* }}} */
148 |
149 | }
150 |
151 |
--------------------------------------------------------------------------------
/test/unit/MonitorTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(array(
32 | 'failed' => 0,
33 | 'pending' => 0,
34 | ), $status['']);
35 |
36 | self::$mysql->query(sprintf(
37 | "INSERT INTO %stask_queque (addtime,task_flag,trytimes) VALUES('%s',%u,1),('%s',%u,4),('%s',%u,0),('%s',%u,0)",
38 | self::$mysql->option('prefix'), date('Y-m-d H:i:s', time() - 301), \Myfox\App\Queque::FLAG_WAIT,
39 | date('Y-m-d H:i:s', time() - 290), \Myfox\App\Queque::FLAG_WAIT,
40 | date('Y-m-d H:i:s', time() - 290), \Myfox\App\Queque::FLAG_WAIT,
41 | date('Y-m-d H:i:s', time() - 390), \Myfox\App\Queque::FLAG_NEW
42 | ));
43 | $status = Monitor::queque_status_monitor();
44 | $this->assertEquals(array(
45 | 'failed' => 1,
46 | 'pending' => 1,
47 | ), $status['']);
48 |
49 | $monitor = new Monitor(array('sleep' => 22));
50 | $this->assertEquals(true, $monitor->execute(true));
51 | $this->assertEquals(22, $monitor->interval());
52 | }
53 | /* }}} */
54 |
55 | /* {{{ public void test_should_import_consist_works_fine() */
56 | public function test_should_import_consist_works_fine()
57 | {
58 | $hosts = self::$mysql->getAll(self::$mysql->query(sprintf(
59 | 'SELECT host_id, host_name FROM %shost_list WHERE host_type = %d',
60 | self::$mysql->option('prefix'), Server::TYPE_REALITY
61 | )));
62 |
63 | $create = "CREATE TABLE IF NOT EXISTS test.test_a (
64 | id int(10) unsigned not null auto_increment primary key,
65 | num1 int(10) not null default 0,
66 | char1 varchar(15) not null default ''
67 | ) ENGINE=MYISAM DEFAULT CHARSET=UTF8";
68 |
69 | $ids = array();
70 | foreach ($hosts AS $server) {
71 | $mysql = Server::instance($server['host_name'])->getlink();
72 | $mysql->query('DROP TABLE IF EXISTS test.test_a');
73 | $mysql->query($create);
74 | $this->assertEquals(3, $mysql->query(
75 | "INSERT INTO test.test_a (id,num1,char1) VALUES (1, 2, 'bbb'),(2,3,'cccc'),(3,4,'dddd')"
76 | ));
77 | $ids[] = (int)$server['host_id'];
78 | }
79 |
80 | $this->assertEquals(1, self::$mysql->query(sprintf(
81 | "INSERT INTO %sroute_info (real_table,hosts_list,table_name,modtime,route_flag)".
82 | " VALUES ('test.test_a', '%s', 'test',%d,%d)",
83 | self::$mysql->option('prefix'), implode(',', $ids), time(), \Myfox\App\Model\Router::FLAG_IMPORT_END
84 | )));
85 |
86 | \Myfox\App\Setting::set('monitor_consist_check', date('Y-m-d H:i:s', time() - 3600));
87 | $this->assertEquals(array(), Monitor::import_consist_monitor());
88 |
89 | $server = reset($hosts);
90 | Server::instance($server['host_name'])->getlink()->query(sprintf(
91 | 'DELETE FROM test.test_a ORDER BY id ASC LIMIT 1'
92 | ));
93 |
94 | \Myfox\App\Setting::set('monitor_consist_check', date('Y-m-d H:i:s', time() - 3600));
95 | $result = Monitor::import_consist_monitor();
96 |
97 | $this->assertTrue(!empty($result));
98 | $result = reset($result);
99 | $this->assertTrue(!empty($result['checks']));
100 | }
101 | /* }}} */
102 |
103 | }
104 |
105 |
--------------------------------------------------------------------------------
/test/unit/MysqlTest.php:
--------------------------------------------------------------------------------
1 | get('logurl', ''));
21 |
22 | $this->logfile = $logurl['path'];
23 | Mysql::removeAllNames();
24 |
25 | self::$mysql = new Mysql(__DIR__ . '/ini/mysql.ini');
26 | self::$mysql->query('DROP TABLE IF EXISTS only_for_test');
27 | self::$mysql->query('CREATE TABLE `only_for_test` (
28 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
29 | `content` varchar(32) DEFAULT NULL,
30 | PRIMARY KEY (`id`)
31 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8');
32 |
33 | @unlink($this->logfile);
34 | }
35 | /* }}} */
36 |
37 | /* {{{ protected void tearDown() */
38 | protected function tearDown()
39 | {
40 | @unlink($this->logfile);
41 | parent::tearDown();
42 | }
43 | /* }}} */
44 |
45 | /* {{{ public void test_should_mysql_factory_works_fine() */
46 | public function test_should_mysql_factory_works_fine()
47 | {
48 | try {
49 | $mysql = Mysql::instance('i_am_not_exists');
50 | $this->assertTrue(false);
51 | } catch (\Exception $e) {
52 | $this->assertTrue($e instanceof \Myfox\Lib\Exception);
53 | $this->assertContains('Undefined mysql instance named as "i_am_not_exists"', $e->getMessage());
54 | }
55 |
56 | $mysql = new Mysql('', 'mysql3');
57 | $this->assertEquals(Mysql::instance('MYSQl3'), $mysql);
58 |
59 | Mysql::register('test1', array(
60 | 'dbname' => 'test',
61 | 'prefix' => 'myfox_',
62 | ));
63 | $mysql = Mysql::instance('test1');
64 | $this->assertEquals('test', $mysql->option('dbname'));
65 | $this->assertEquals('myfox_', $mysql->option('prefix'));
66 | $this->assertEquals('utf8', $mysql->option('charset'));
67 | }
68 | /* }}} */
69 |
70 | /* {{{ public void test_should_simple_query_works_fine() */
71 | public function test_should_simple_query_works_fine()
72 | {
73 | $mysql = new Mysql(__DIR__ . '/ini/mysql_test.ini');
74 | try {
75 | $mysql->connectToSlave();
76 | } catch (\Exception $e) {
77 | $this->assertContains(
78 | "\tCONNECT_ERROR\t-\t{\"host\":\"127.0.0.1\",\"port\":3306,\"user\":\"user_ro\",\"pass\":\"**\",\"error\":",
79 | self::getLogContents($this->logfile, -1)
80 | );
81 | }
82 |
83 | $rs = self::$mysql->getAll(self::$mysql->query('SHOW DATABASES'));
84 | $this->assertContains("\tQUERY_OK\t-\t{\"sql\":\"SHOW DATABASES\"", self::getLogContents($this->logfile, -1));
85 | $this->assertContains(array('Database' => 'test'), $rs);
86 |
87 | $this->assertFalse(self::$mysql->query('I AM A WRONG QUERY'));
88 | $this->assertContains(
89 | "\tQUERY_ERROR\t-\t{\"sql\":\"I AM A WRONG QUERY\",\"async\":false,\"error\":",
90 | self::getLogContents($this->logfile, -1)
91 | );
92 |
93 | $this->assertEquals(1, self::$mysql->query('INSERT INTO only_for_test (content) VALUES ("aabbcc")'));
94 | $lastId = self::$mysql->lastId();
95 | $this->assertEquals($lastId, self::$mysql->getOne(self::$mysql->query(
96 | 'SELECT MAX(id) FROM only_for_test'
97 | )));
98 | self::$mysql->query('INSERT INTO only_for_test (content) VALUES ("aabbcc2")');
99 | $this->assertTrue($lastId < self::$mysql->lastId());
100 | }
101 | /* }}} */
102 |
103 | /* {{{ public void test_should_escape_works_fine() */
104 | public function test_should_escape_works_fine()
105 | {
106 | $this->assertEquals(array(
107 | 'a' => 'i\\\'m chinese',
108 | '\\\'' => '省',
109 | ), self::$mysql->escape(array(
110 | 'a' => 'i\'m chinese',
111 | "'" => '省',
112 | )));
113 | }
114 | /* }}} */
115 |
116 | /* {{{ public void test_should_async_query_works_fine() */
117 | public function test_should_async_query_works_fine()
118 | {
119 | $id = self::$mysql->async(
120 | 'INSERT INTO only_for_test (content) VALUES ("aabbcc")'
121 | );
122 | $this->assertEquals($id + 1, self::$mysql->async('SELECT MAX(id) FROM only_for_test'));
123 |
124 | $this->assertEquals(1, self::$mysql->wait($id));
125 | $this->assertEquals(1, self::$mysql->getOne(self::$mysql->wait($id + 1)));
126 | }
127 | /* }}} */
128 |
129 | /* {{{ public void test_should_mysql_reconnect_when_gone_away_works_fine() */
130 | public function test_should_mysql_reconnect_when_gone_away_works_fine()
131 | {
132 | $mysql = new Mysql(__DIR__ . '/ini/mysql.ini');
133 | $result = $mysql->getAll($mysql->query('SHOW DATABASES'));
134 | $this->assertEquals(0, $mysql->query(sprintf('KILL %d', $mysql->thread_id)));
135 | $this->assertEquals($result, $mysql->getAll($mysql->query('SHOW DATABASES')));
136 | }
137 | /* }}} */
138 |
139 | }
140 |
141 |
--------------------------------------------------------------------------------
/test/unit/ProcessorTest.php:
--------------------------------------------------------------------------------
1 | query(sprintf('TRUNCATE TABLE %stask_queque', self::$mysql->option('prefix', '')));
21 | }
22 | /* }}} */
23 |
24 | /* {{{ public void test_should_undefined_task_type_ignore_works_fine() */
25 | public function test_should_undefined_task_type_ignore_works_fine()
26 | {
27 | $this->assertTrue(Queque::instance()->insert(
28 | 'i am not defined', array(
29 | 'src' => 'http://www.taobao.com',
30 | ),
31 | 1, array(
32 | 'trytimes' => 2,
33 | 'adduser' => 'unittest',
34 | 'priority' => 201,
35 | )
36 | ));
37 |
38 | $worker = new Processor(array(
39 | 'n' => 1,
40 | ));
41 |
42 | $this->assertTrue($worker->execute());
43 | $this->assertEquals(1, $worker->interval());
44 |
45 | $queque = self::$mysql->getRow(self::$mysql->query(
46 | sprintf('SELECT * FROM %stask_queque LIMIT 1', self::$mysql->option('prefix', ''))
47 | ));
48 | $this->assertEquals(1, $queque['trytimes']);
49 | $this->assertEquals(Queque::FLAG_IGNO, $queque['task_flag']);
50 | $this->assertContains("Undefined task_type named as 'i am not defined'", $queque['last_error']);
51 | }
52 | /* }}} */
53 |
54 | }
55 |
56 |
--------------------------------------------------------------------------------
/test/unit/QuequeTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(null, $queque->fetch());
38 | $this->assertTrue($queque->insert(
39 | 'test ', array(
40 | 'src' => 'http://www.taobao.com',
41 | ),
42 | 1,
43 | array(
44 | 'trytimes' => 2,
45 | 'adduser' => 'unittest',
46 | 'priority' => 201,
47 | )
48 | ));
49 |
50 | $task = $queque->fetch(1, 2);
51 | $this->assertEquals(array(
52 | 'id' => 1,
53 | 'type' => 'test',
54 | 'status' => '',
55 | 'info' => json_encode(array(
56 | 'src' => 'http://www.taobao.com',
57 | )),
58 | ), $task);
59 |
60 | $this->assertEquals(1, $queque->update($task['id'], array(
61 | 'trytimes' => 'trytimes + 1',
62 | 'priority' => 202,
63 | ), array(
64 | 'trytimes' => true,
65 | )));
66 |
67 | $this->assertEquals(array(
68 | 'trytimes' => 3,
69 | 'priority' => 202,
70 | ), self::$mysql->getRow(self::$mysql->query(sprintf(
71 | 'SELECT trytimes, priority FROM %s.%stask_queque WHERE autokid = %d',
72 | self::$mysql->option('dbname', 'meta_myfox_config'),
73 | self::$mysql->option('prefix', ''),
74 | $task['id']
75 | ))));
76 | }
77 | /* }}} */
78 |
79 | /* {{{ public void test_should_close_race_queque_works_fine() */
80 | public function test_should_close_race_queque_works_fine()
81 | {
82 | $queque = Queque::instance();
83 | $this->assertEquals(null, $queque->fetch());
84 |
85 | $this->assertTrue($queque->insert(
86 | 'test ', array(
87 | 'src' => 'http://www.taobao.com',
88 | ),
89 | 1,
90 | array(
91 | 'trytimes' => 2,
92 | 'adduser' => 'unittest',
93 | 'priority' => 201,
94 | 'openrace' => 0,
95 | )
96 | ));
97 |
98 | $this->assertEquals(null, $queque->fetch(1, 2));
99 | $this->assertEquals(array(
100 | 'id' => 1,
101 | 'type' => 'test',
102 | 'status' => '',
103 | 'info' => json_encode(array(
104 | 'src' => 'http://www.taobao.com',
105 | )),
106 | ), $queque->fetch(1, 1));
107 | }
108 | /* }}} */
109 |
110 | /* {{{ public void test_should_queque_fetch_by_type_works_fine() */
111 | public function test_should_queque_fetch_by_type_works_fine()
112 | {
113 | $queque = Queque::instance();
114 | $this->assertTrue($queque->insert(
115 | 'test ', array(
116 | 'src' => 'http://www.taobao.com',
117 | ),
118 | 1,
119 | array(
120 | 'trytimes' => 2,
121 | 'adduser' => 'unittest',
122 | 'priority' => 201,
123 | )
124 | ));
125 | $this->assertTrue($queque->insert(
126 | 'test2', array(
127 | 'src' => 'http://www.taobao.com',
128 | ),
129 | 1,
130 | array(
131 | 'trytimes' => 2,
132 | 'adduser' => 'unittest',
133 | 'priority' => 200,
134 | )
135 | ));
136 |
137 | $task = $queque->fetch(1, 2, Queque::FLAG_WAIT, 'test');
138 | $this->assertEquals(array(
139 | 'id' => 1,
140 | 'type' => 'test',
141 | 'status' => '',
142 | 'info' => json_encode(array(
143 | 'src' => 'http://www.taobao.com',
144 | )),
145 | ), $task);
146 | }
147 | /* }}} */
148 |
149 | }
150 |
151 |
--------------------------------------------------------------------------------
/test/unit/SecurityTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(10, $sec->priority('127.0.0.2'));
16 | $this->assertEquals(911, $sec->priority('221.3.1.2'));
17 | $this->assertEquals(false, $sec->priority('221.31.1.2'));
18 |
19 | $this->assertEquals(array(
20 | 'task_flag' => 100,
21 | 'trytimes' => 3,
22 | 'tasktype' => 'asdf',
23 | 'i_am_none' => 'lalalal',
24 | ), $sec->modify(array(
25 | 'task_flag' => 11,
26 | 'trytimes' => 2,
27 | 'tasktype' => 'asdf',
28 | 'i_am_none' => 'lalalal',
29 | )));
30 | }
31 | /* }}} */
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/test/unit/ServerTest.php:
--------------------------------------------------------------------------------
1 | query(sprintf(
22 | "DELETE FROM %shost_list WHERE host_name LIKE 'unittest%%'",
23 | self::$mysql->option('prefix', '')
24 | ));
25 | }
26 | /* }}} */
27 |
28 | /* {{{ public void test_should_server_get_option_works_fine() */
29 | public function test_should_server_get_option_works_fine()
30 | {
31 | try {
32 | Server::instance('unittest01')->option('');
33 | $this->assertTrue(false);
34 | } catch (\Exception $e) {
35 | $this->assertTrue($e instanceof \Myfox\Lib\Exception);
36 | $this->assertContains('Undefined mysql server named as "unittest01"', $e->getMessage());
37 | }
38 |
39 | $this->assertEquals('edp1_9801', Server::instance('edp1_9801')->option('host_name'));
40 | $this->assertEquals(Server::TYPE_REALITY, Server::instance('edp1_9801')->option('host_type'));
41 | }
42 | /* }}} */
43 |
44 | /* {{{ public void test_should_server_get_link_works_fine() */
45 | public function test_should_server_get_link_works_fine()
46 | {
47 | $ob = Server::instance('edp1_9801')->getlink();
48 | $this->assertTrue($ob instanceof \Myfox\Lib\Mysql);
49 |
50 | $my = Server::instance('edp1_9801')->getlink();
51 | $this->assertEquals($ob, $my);
52 | }
53 | /* }}} */
54 |
55 | }
56 |
57 |
--------------------------------------------------------------------------------
/test/unit/SettingTest.php:
--------------------------------------------------------------------------------
1 | query(sprintf(
21 | 'DELETE FROM %ssettings WHERE ownname LIKE "unittest%%"',
22 | self::$mysql->option('prefix')
23 | ));
24 |
25 | Setting::$queries = 0;
26 | }
27 | /* }}} */
28 |
29 | /* {{{ protected void tearDown() */
30 | protected function tearDown()
31 | {
32 | parent::tearDown();
33 | }
34 | /* }}} */
35 |
36 | /* {{{ public void test_should_setting_set_and_get_works_fine() */
37 | public function test_should_setting_set_and_get_works_fine()
38 | {
39 | Setting::init(1);
40 |
41 | $this->assertEquals(0, Setting::$queries);
42 | $this->assertEquals(null, Setting::get('key1', 'unittest1'));
43 | $this->assertEquals(1, Setting::$queries);
44 |
45 | $this->assertEquals(1, Setting::set('key1', 'a am a hacker', 'unittest1'));
46 | $this->assertEquals(2, Setting::$queries);
47 |
48 | $this->assertEquals('a am a hacker', Setting::get('key1', 'unittest1'));
49 | $this->assertEquals(3, Setting::$queries);
50 |
51 | $this->assertEquals('a am a hacker', Setting::get('key1', 'unittest1'));
52 | $this->assertEquals(3, Setting::$queries);
53 |
54 | usleep(1050000);
55 |
56 | $this->assertEquals('a am a hacker', Setting::get('key1', 'unittest1'));
57 | $this->assertEquals(4, Setting::$queries);
58 |
59 | $this->assertEquals(2, Setting::set('key1', 'i am not a hacker', 'unittest1'));
60 | $this->assertEquals(5, Setting::$queries);
61 |
62 | $this->assertEquals('i am not a hacker', Setting::get('key1', 'unittest1'));
63 | $this->assertEquals(6, Setting::$queries);
64 | }
65 | /* }}} */
66 |
67 | /* {{{ public void test_should_setting_set_without_comma_works_fine() */
68 | public function test_should_setting_set_without_comma_works_fine()
69 | {
70 | Setting::init(0);
71 | $this->assertEquals(1, Setting::set('key2', 1, 'unittest1'));
72 | $this->assertEquals(1, Setting::get('key2', 'unittest1'));
73 |
74 | $this->assertEquals(2, Setting::set('key2', 'IF(cfgvalue + 0 > 0, 2, 3)', 'unittest1', false));
75 | $this->assertEquals(2, Setting::get('key2', 'unittest1'));
76 | }
77 | /* }}} */
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/test/unit/UrlTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('', $url->module());
30 | $this->assertEquals('', $url->action());
31 | $this->assertEquals(array(), $url->param);
32 |
33 | $url = new Url('test');
34 | $this->assertEquals('test', $url->module());
35 | $this->assertEquals('', $url->action());
36 | $this->assertEquals(array(), $url->param);
37 |
38 | $url = new Url('test//index/a/b/c/2');
39 | $this->assertEquals('test', $url->module());
40 | $this->assertEquals('index', $url->action());
41 | $this->assertEquals(array('a' => 'b', 'c' => '2'), $url->param());
42 |
43 | $url = new Url("test\t/in\ndex/__SQL__/b/c/2/d");
44 | $this->assertEquals('test', $url->module());
45 | $this->assertEquals('index', $url->action());
46 | $this->assertEquals(array(
47 | '__SQL__' => 'b', 'c' => '2', 'd' => true
48 | ), $url->param);
49 | }
50 | /* }}} */
51 |
52 | /* {{{ public void test_should_build_right_url_ok() */
53 | public function test_should_build_right_url_ok()
54 | {
55 | $this->assertEquals(
56 | 'module/action/name/' . urlencode('朱镕基') . '/age/2',
57 | Url::build('module', 'action', array(
58 | 'na me' => '朱镕基',
59 | 'age ' => 2,
60 | 'array' => array('我应该被忽略'),
61 | ))
62 | );
63 | }
64 | /* }}} */
65 |
66 | /* {{{ public void test_should_parse_zero_correctly() */
67 | /**
68 | * THIS IS A BUG CASE
69 | */
70 | public function test_should_parse_zero_correctly()
71 | {
72 | $url = new Url('test//index/a/b/c/0');
73 | $this->assertEquals(array('a' => 'b', 'c' => '0'), $url->param);
74 | }
75 | /* }}} */
76 |
77 | }
78 |
79 |
--------------------------------------------------------------------------------
/test/unit/autoload/com/aleafs/autoloadordertestclass.php:
--------------------------------------------------------------------------------
1 | a = $a;
15 | $this->b = $b;
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/test/unit/ini/config_test.ini:
--------------------------------------------------------------------------------
1 | ; 仅用于config类单元测试
2 |
3 | key1 = "abc"
4 | key2/a = false
5 |
6 | [key2]
7 | a = 1.23456789 ; 这里会被注释掉
8 | b = 9.87654321
9 |
--------------------------------------------------------------------------------
/test/unit/resource/mirror_import_data_file.txt:
--------------------------------------------------------------------------------
1 | 1 类目1
2 | 2 类目2
3 | 3 类目3
4 | 4 类目4
5 | 5 类目5
6 |
--------------------------------------------------------------------------------
/test/unit/resource/numsplit_import_data_file.txt:
--------------------------------------------------------------------------------
1 | 2011-06-10 1 299 0.42 line1
2 | 2011-06-10 1 399 0.42 line2
3 | 2011-06-10 1 499 0.42 line3
4 | 2011-06-10 1 599 0.42 line4
5 | 2011-06-10 1 699 0.42 line5
6 | 2011-06-10 1 979 0.42 line6
7 | 2011-06-10 1 998 0.42 line7
8 | 2011-06-10 1 345 0.42 line8
9 | 2011-06-10 1 217 0.42 line9
10 | 2011-06-10 1 342 0.42 line10
11 |
--------------------------------------------------------------------------------
/www/index.php:
--------------------------------------------------------------------------------
1 |