开启Mac OS X Snow Leopard的NTFS原生读写

在Mac OS X下一直使用MacFUSE和NTFS-3G来访问ntfs分区,这次装了Snow leopard 10.6.2之后出现fusefs.kext can’t load的错误,从官方得知目前macfuse在snow leopard下有问题,在网上看到这篇贴子提到Snow Leopard其实原生就支持NTFS分区的读写,真是个好消息。

开启的方法有两种:
一种是在/etc/fstab文件里增加挂载选项,基本步骤是:
1,卸载NTFS-3G或者其他读写ntfs分区的软件
2,打开终端$ diskutil info /Volumes/分区名 或者使用磁盘工具获得分区的UUID
3,编辑/etc/fstab文件,增加一行 UUID=分区的UUID none ntfs rw
4,如果还有其他的分区要挂载,则继续上面的步骤2-3增加其他分区

这种方式比较烦琐,第二种方法就比较简单。
大家应该都注意过系统已启动就会自动挂载所有的ntfs分区,这个命令在/sbin/mount_ntfs
这个方法所要做的就是给这个默认的挂载命令增加可读写参数,按如下步骤在终端命令行操作:
$ sudo mv /sbin/mount_ntfs /sbin/mount_ntfs.orig
$ sudo vim /sbin/mount_ntfs
编辑这个文件,加入如下内容:
#!/bin/sh
/sbin/mount_ntfs.orig -o rw “$@“ #这里调用默认的挂载命令,不过增加了 rw参数,开启读写

保存这个文件,然后:
$ sudo chown root:wheel /sbin/mount_ntfs
$ sudo chmod 755 /sbin/mount_ntfs

然后重启,启动后,你应该就能得到一个可以自由读写的ntfs分区了。不过声明一点,这个功能据说不稳定,不知道是因为版权原因还是真的很不稳定,苹果默认没有开启这个功能。不过我更相信苹果。

========
后话:刚好公司配了一台Dell E6400,偶尔看到有人在上面装了一个Leopard,于是手痒也在上面装了一个,如果有人也有这台笔记本也可以试试看,不过我装好的系统还是有问题:

1,触摸屏一碰就乱跑,我直接禁用就好了,反正也不喜欢用。
2,关机和重启不断电,每天关机的次数也有限,也就无所谓了。
3,声音控制不了,只能在具体的应用程序里控制。
其实问题还是挺多的,不过基本上的使用我觉得还是没什么问题的,触摸屏的问题比较烦人,还好我不喜欢用触摸屏。如果有人也想尝试可以试试看。我用的安装文件是 Snow_Leopard_10.6.1-10.6.2_SSE2_SSE3_Intel_AMD_by_Hazard.iso, 至于安装方法PCbeta上有很多的教程可以参考,摸索一下大概都没有什么问题,安装的时候一定要做好备份,因为我到目前已经因为装Mac OS X丢失了不下10次数据了,大部分情况下都是分区被合并。所以一定要小心一点。

在2009的尾巴上

09年最后一篇日志,充个数吧:)

哈哈,刚买了本《Orange S: 一个操作系统的实现》,这一年下来买了挺多的书,也从图书馆借了很多的书(马上就要毕业了,再也享受不到学校图书馆丰富及时的藏书了,计划以后每个月发工资都买一本自己喜欢的书),发现我读书最大的问题是多而不精,拿一本好书就使劲的翻,很多书都是花几天就翻完了,但是读下来之后总觉得收获不够,一些很需要实践的部分也没有及时联系,总感觉自己浮在表面上,只知其然。所以呢,这个2010年争取多读好书,踏踏实实的啃几本好书,然后就是要多动手啦。

计划2010年呢,能把耽搁很久的想法付诸行动,做出点东西来。希望有个好的开始吧!

Happy New Year!!!

支持IPv6的Tunnelblick For Mac OS X OpenVPN客户端

yegle那里买了OpenVPN服务 ,用着还挺不错,我也在教育网,所以只能使用支持IPv6版的客户端,openvpn默认不支持IPv6,不过yegle提供了相应的IPv6补丁,在Linux下以及Windows下使用的都挺好,最近又在折腾电脑,装了个Snow Leopard,基本没什么问题了,除了我的ATI 2600 XT硬件加速暂时无解外其他的都挺爽,使用了yegle推荐的Tunnelblick, 总是连接不上,它提示让我查看日志,但是根本就找不到地方看日志,直接cd 到 Tunnelblick的包里面直接执行openvpn命令,提示不支持udp6 ,又是不支持 Ipv6,本想直接自己编译一个openvpn,但是想想那样就太不clean了,编译成app的话可以通用,还能共享给有需要的人多好啊,去google code checkout了一份代码,直接编译,错误百出,我是在Snow Leopard上编译的,仔细查看原来需要MacOSX10.4u的SDK 重新安装10.4的SDK还是未果,后来发现SDK的安装目录居然不一样。。自己手动拷贝过去也不行。。 切换到傲Leopard下安装,折腾了好久终于编译成功,废话太多了。呵呵,共享出来给需要的人吧:)

猛击这个连接 下载http://code.google.com/p/tunnelblick-ipv6/downloads/list
可惜的是yegle不再提供IPv6用户的续费了,不过我到期之后差不多也要从学校滚蛋了。
我提供的这个版本的tunnelblick的配置文件位置是 ~/Library/openvpn 最新版的配置放在 ~/Library/Application Support/Tunnelblick/Configuration目录里面。

马上2009年就要过去了。最近很久都没有更新日志了,其实之间也想写一些东西,但是都丢在草稿箱里没写完。论文还有很多没有写完,马上就要交了,要抵制住诱惑乖乖写论文。

在PHP中检查PHP文件是否有语法错误

之前在当当的时候的一个项目中用到了一个简单的模板引擎,其实也是借鉴discuz来做的模板引擎,很简单,它所作的事情就是把一些自定义的标签编译成php代码。已经说了很简单了,所以编译的时候也名优进行模板语法的检查,那么在开发过程中就会出现编译出来的php文件有语法问题,有语法问题没有关系,我修改重新编译一下就好了。首先不能在每次请求的时候都把php模板重新编译一下,会严重影响性能,折中的处理时在每个编译好的php文件末尾检查一下该模板文件是否已经修改过,根据设定的更新频率,如果又需要则重新编译模板文件,现在的问题是编译出来的php文件自己有语法错误,根本执行不到模板检查那一步,所以即使修改了模板文件中的问题也不会重新编译。 所以我想寻找一种简单的方法来检查生成的php文件是否合法。不合法就重新编译,这样开发过程中就不用出现错误就得手动删除缓存文件了。

在网上找了一下。刚开始以为 token_get_all()函数能处理语法错误的问题,结果发现,它只是做简单的词法分析。没有办法。后来到论坛上去问了一下

http://groups.google.com/group/professional-php/browse_thread/thread/b8581f6b07b10ff0/2601a63c406bb1c1?lnk=gst&q=reeze#2601a63c406bb1c1

有人告诉我有这样一个函数 php_check_syntax() http://www.php.net/manual/en/function.php-check-syntax.php 我想问题就这么坚决了。。我真应该RTF(Read The Fuck Mannual). 仔细一看。这个函数已近被弃用了:
Note: For technical reasons, this function is deprecated and removed from PHP. Instead, use php -l somefile.php from the commandline.

这个technical reason 到底是什么呢? 先不管了,以后再慢慢研究,反正不能使用这个方法就对了。
他们的建议是使用命令行$php -l filename.php 来检查语法。
Gary Every给了我一个代码片段参考:

在命令行下检查问题也不大。如果我要放在在线应用呢? 这就涉及到可移植性的问题了。首先是操作系统,然后就是环境变量。这样的话就会依赖于服务器端的配置。在http://www.php.net/manual/en/function.php-check-syntax.php 上有人贴出了自己的php_check_syntax()函数实现。
有的采用的就是上面的命令行的方法。
后面有提到使用eval的方法来验证。eval方法会执行传入的代码, 如果代码有语法错误则会抛出parser error, 可以使用’@'错误抑制符去掉错误信息,eval和echo一样并不是函数,不能使用变量函数的方法调用比如:
$func = ‘eval’
$func()这样的调用就是无效的。它会提示没有eval函数,如果你自己定义这么一个函数也是有问题的。因为eval是一个关键字。
eval调用和include差不多,如果被包含文件中没有明确return就返回null。如果直接eval我们需要检查的文件会造成被检查的文件内代码被执行,这可不是我们想要的,我们只需要检查一下这个文件的语法是否正确。 我们可以在要检查的文件之前添加return 语句,让代码提前跳出,那么后面的代码就不会执行了。好的,就这么干。代码如下:
checker.php

if(!function_exists('php_check_syntax')) {
function php_check_syntax($file_name, &$error_message = null) {
$file_content = file_get_contents($file_name);

$check_code = "return true; ?>";
$file_content = $check_code . $file_content . "

if(!@eval($file_content)) {
$error_message = "file: " . realpath($file_name) . " have syntax error";
return false;
}

return true;
}
}

if(!php_check_syntax("file.php", $msg)) {
echo $msg;
}
else {
echo "Woohoo, OK!";
}

file.php

foreach:: a => b
?>

因为Parse error 是没法被 set_error_handler处理函数处理的。这个异常没办法catch到。所以才使用了@来抑制错误。这带来的问题就是我们无法得到详细的错误信息。 不过目前我需要的功能也只是检查语法是否正确。不正确的话重新编译模板文件,就这么简单,至于语法错误,在显示网页的时候自然会看得到。
最好的办法就是这个被遗弃的php_check_syntax这个方法回到php中。下次再研究下他们是出于什么原因把这个函数去掉的。

实现一个简单在线HTML编辑器

一直没有仔细研究过在线HTML编辑器,以前以为编辑功能很复杂,需要用大量的JavaScript来模拟编辑器的效果,以前都是使用一些开源的HTML编辑器,HTML在各网上随处可见,发表文章,评论。最近自己想做一个类似Things这样的Web版的应用,需要一个想Google  Notebook(可惜的是现在已经停止开发了) 那样的编辑功能,看看现在网上的这些编辑器都庞大了,都是一些自己根本用不到的功能,其实我的需求很简单:简单的编辑既可以,并且需要轻量级一些,因为页面上可能需要开很多个编辑器实例。 所有想自己也来研究一下,看看能不能自己开发一个。

几天前花了一个晚上用firebug看了下Google Docs是怎么做。第一个遇到的问题就是如何让光标停在鼠标点击所在得地方。我刚开始一味都是js模拟出来的,这得有多复杂啊。并且还要兼容各个浏览器,天啊!后来上网一搜发现,浏览器早就想到了我们会有这样的一个需求。其实很简单,两条语句就可以说明HTML编辑器的最为核心的部分:

document.designMode = 'On';
document.contentEditable = true;

参考Mozilla上的这篇文章,介绍了HTML编辑的基本信息,要自己DIY一个常用功能的HTML编辑,这篇文章已经够你用的了。

在你的网页中嵌入这两条语句试试看:),你就会发现你的网站整个得都变的可以编辑了。可以随便乱修改。 不过放心,这样修改并不会破坏你的网站, 当然你也不希望你的整个网站是可以编辑的。例如我们只希望别人发布一条评论,只希望评论输入框可以输入。 要实现这样的效果可以有两种做法:

第一种就是使用直接让某个元素变成可编辑的例如:

index.html

Test TextEditor

Hi, HTML Editor!

Your comment



点击该区域后该区域的内容就变成可以编辑的了,这是我们就是对其进行简单的编辑。你可能会觉得直接编辑的功能太简单了,比如想要插入链接或者图片什么的。就没有办法了。这些功能浏览器并没有帮我们做好,不过实现这些功能也不麻烦, 参考上面Mozilla的文档。浏览器都提供了常用的功能API。

一般的编辑器都会提供一个工具栏之类的按钮来编辑文本内容。比如我使用的WordPress提供的编辑器:

wordpress提供的编辑器

wordpress提供的编辑器

可以对文字内容进行操作,加粗下划线,字体,对齐等等,并且提供可视化以及HTML编辑模式。

这个和FCKeditor 以及tinymce之类的编辑器使用的编辑方式和上面我提到的直接编辑html对象的方法不一样,他们使用的是iframe,使用iframe有很多好处,iframe中的文档和当前文档并不会因为样式或者HTML结构而影响到彼此,所以大部分的编辑器都是使用这种方式。它们基本的方式都是:

  1. 在页面中使用一个不可见的字段比如:“input, textarea”之类表单字段,他们的值就是需要编辑的内容。
  2. 页面初始化好,比如载入编辑器相关的一些脚本,也是就是window.onload好以后。创建一个iframe来显示可编辑区域,iframe初始化好以后读取父窗(相对于这个iframe)口的这个不可见表单值的内容,使之成为iframe中的body的html,也就是把所有需要编辑的内容添加到iframe中
  3. iframe中的内容初始化好以后,在iframe中执行上面提到的:

    document.designMode = 'On';
    document.contentEditable = true;

    把iframe整个窗口变成可以编辑的
  4. 进行编辑,这时候的编辑可能需要一个工具栏,基于同样的原因,一般工具栏也会是一个iframe,显示它们自己的编辑按钮。编辑是就利用浏览器提供的接口来对ifame中的内容进行编辑
  5. 父窗口中提交表单之前或者你需要的地方需要把编辑器中编辑完的内容回写到你的表单字段中去,否则编辑结果没有保存写来就没有意义了。

基本原理就是这样。要做出这样一个东西来,需要的就是一些细活了。要想做出一个FCk这样好用的编辑器也不是那么简单的。但是至少我们知道它是怎么运作的。 这就够了。

花了点时间做了一个简单的编辑器,真正要用的话很多的细节还是需要好好处理的,代码没有怎么清理,是变想边写,不是很完整。

需要的同学可以下来参考参考。
猛击  >> 这里 << 下载代码。

基于var_export 和 include返回值的缓存方案

前一篇文章我们研究了include调用返回值的问题,并指出可以通过这种方式来完成序列化相同的功能,现在我就来研究一下这种方法的可行性和效率,因为直接的返回php值肯定是比unserialize()函数要快。

第一步我们来研究下怎么将php对象持久化的保存起来。下面是我定义的一些变量:


private $_var;
public $pub = array('pub value', 3, 4);
public function __constructor($var)
{
$this->_var = $var;
}

public function show()
{
echo $this->_var;
}
}

$string = "It's a string...";
$array = array(1, 2, 'key' => 'value', array('sub-array'));
$number = 135345.55;
$class = new MyClass('class var');

//通过serialize()方法我们可以将他们持久化比如:
echo serialize($string); //s:16:"It's a string...";
echo serialize($array); //a:4:{i:0;i:1;i:1;i:2;s:3:"key";s:5:"value";i:2;a:1:{i:0;s:9:"sub-array";}}
echo serialize($number); //d:135345.5499999999883584678173065185546875;
echo serialize($class); //O:7:"MyClass":2:{s:13:"MyClass_var";N;s:3:"pub";a:3:{i:0;s:9:"pub value";i:1;i:3;i:2;i:4;}}
// 我们可以将这些序列化的结果存到文件中,在需要的时候unserialize()返回得到相应的值,但是现在我不会这么做。

前篇文章提到了通过include返回值来直接取得php值对象,首先我们要把值保存起来,因为我们要通过include来包含它,首先遇到的问题就是我们的序列化函数必须要生成合法的php表达式才行,否则include是无法得到相应的返回值的
比如我们要序列化 字符串 “abcd” 我们可以这么做

file_puts_content("data.php", "return 'abcd';");
//然后这样取得相应的值
$string = include "data.php";
echo $string; // 它应该输出 abcd

那数组怎么办呢?比如上面的数组。我们可以自己编写这个序列化函数

function encode($var){
if (is_array($var)) {
$code = 'array(';
foreach ($var as $key => $value) {
$code .= "'$key'=>".encode($value).',';
}
$code = chop($code, ','); //remove unnecessary coma
$code .= ')';
return $code;
} else {
if (is_string($var)) {
return "'".$var."'";
} elseif (is_bool($var)) {
return ($var ? 'TRUE' : 'FALSE');
} elseif (is_numeric($var)) {
return "$var";
}
else
{
return 'NULL';
}
}
}

这个函数可以将字符串,数组以及数字变成合法的php表达式。
比如:
file_put_contents(“data.php”, “<?phpn return ” . encode($array) . “;n”);
data.php文件的结果是:

return array ( 0 => 1, 1 => 2, 'key' => 'value', 2 => array ( 0 => 'sub-array', ), )array(4) { [0]=> int(1) [1]=> int(2) ["key"]=> string(5) "value" [2]=> array(1) { [0]=> string(9) "sub-array" } }

我们的目的达到了。可以直接的通过include这个文件来得到我们的值。
但是现在有个问题,我们没有序列化对象类型的值,这个该怎么处理呢?
一个类对象有对象的状态和对象的行为,行为在类定义完以后就确定了,所以每个类的实例的行为都是一样的。所以我们可以不考虑,我们只需要考虑类对象的状态就可以了,简单来讲就是类的属性状态需要保存起来。那怎么样得到一个类的属性呢?
经过一番搜寻以后发现一个函数
get_object_vars
(PHP 4, PHP 5)get_object_vars — 返回由对象属性组成的关联数组
这个函数可以获得对象的属性关联数组,也就只可以得到对象的状态,但是对象的属性有各种访问控制,get_object_vars()函数在对象外访问只能得到对象的公开属性,而无法得到私有属性,这样的话我们就无法得到对象的全部状态,不可行,但是在对象内可以得到对象的所有属性,那我们可不可以在对象内定义一个___get_properties()方法来返回这些状态呢。
给类增加这样一个方法

public function __get_properties()
{
return get_obj_vars($this);
}

这样我们就可以得到类的所有属性了。第一步算是完成了,我们得到状态该怎么重新恢复出来呢?要在对象外部给对象设置属性我们只有两种情况:一种是这个属性是公开属性,我们可以直接赋值 比如: $obj->prop = $value; 如果是私有属性我们则需要自己增加setter方法比如 setProp($value);方法,来设置。但是这就会遇到一个问题,我们的属性要么是公开属性,要么必须要有setter方法来设置,很多情况下我们不希望给类增加这么多没有实际用处的方法,也为了封装性,不会有这么多的setter方法。虽然我们能得到对象的状态,但是却无法恢复对象状态,这样的话,我们的序列化方法也就没有什么意义了。我们探索到现在算是失败了。
解决办法:var_export()函数。
在看symfony代码的时候发现了这个函数,手册是这么描述的:
———
var_export
(PHP 4 >= 4.2.0, PHP 5)var_export — 输出或返回一个变量的字符串表示
描述
mixed var_export ( mixed expression [, bool return] )

此函数返回关于传递给该函数的变量的结构信息,它和 var_dump() 类似,不同的是其返回的表示是合法的 PHP 代码。

您可以通过将函数的第二个参数设置为 TRUE,从而返回变量的表示。

这个就是我们想要的那个函数,我们来看看这个函数是怎么使用的。

//变量继续使用上面定义的变量
echo var_export($string); //'It's a string...'
echo var_export($array); /* array (0 => 1, 1 => 2, 'key' => 'value', 2 => array (0 => 'sub-array', ),)*/
echo var_export($number); // 135345.55
echo var_export($class); /* MyClass::__set_state(array('_var' => NULL, 'pub' => array (0 => 'pub value', 1 => 3, 2 => 4)) */

我们可以看到生成的都是合法的PHP表达式。通过设置第二个参数为true,就可以将返回结果赋值给变量比如$new_array_string = var_export($array, TRUE);然后将这个结果写入文件持久化

file_put_contents(“data.php”, “public function __constructor($var)
{
$this->_var = $var;
}

public function show()
{
echo $this->_var;
}
public static function __set_state(array $array)
{
$tmp = new MyClass();
foreach($array as $key => $value)
{
$this->$key = $value;
}
}
}

// 一些变量
$string = “It’s a string…”;
$array = array(1, 2, ‘key’ => ‘value’, array(‘sub-array’));
$number = 135345.55;
$class = new MyClass(‘class var’)

// 缓存
cache(“string.data.php”, $string); // 当然扩展名不一定非得php, 文件名我也只是简单的处理
cache(“array.data.php”, $array);
// 等等。。。

// 获取数据,当然也可以在其他文件中来获取。
$class = cache_get(“class.data.php”);

参考:http://www.thoughtlabs.com/2008/02/02/phps-mystical-__set_state-method/?dsq=12016456

gmail新功能之标签改进

今天刚登陆gmail,又发现新的惊喜:

new

很久以前gmail就支持了给邮件加标签的功能,但是到后面基本没有用到,主要有两个原因: 加标签麻烦,以前标签的位置有点低,即使是拖到最上面还是很低,比较麻烦,二是因为以前给邮件加标签似乎只能通过菜单栏来加标签,现在可以直接通过拖放给邮件加标签,如下图:

label

现在标签的位置和收件箱紧靠着,同时可以把不常用的标签隐藏起来,标签可以重排序,gmail的beta是实实在在的beta,永远不满足,我喜欢:)

1

PHP中include语句的返回值及避免序列化开销的方法

以前乱翻symfony生成的缓存文件的时候看到很多类似:


"value"); ?>

这种表达式,当时并没有怎么在意,今天研究symfony代码的时候看到这样一句代码


classes = include($file); ?>;

一直都是通过include require来包含文件,但是从来没有使用过他的返回值,印象中include返回的无非是true或者false吧。今天自己看了下文档,发现在php文件中是可以直接调用return的。比如:


// return.php
$value = array('haha', 1, 3);
return $value;
?>
// get_return.php
$value = include("return.php");

echo $value; // 输出 Array, 因为$value 是从return.php返回的一个数组
?>

其实项目中很少情况需要这样的返回方法。如果想要从return.php中得到返回值一般是通过调用return.php中所调用的函数来得到。

在symfony中这种方式就很合理,如果大家熟悉symfony的话,应该知道,symfony运行起来以后会在cache目录下生成系统配置文件的缓存 ,诚然可以通过序列化的方式来缓存这些信息,但是反序列化是需要消耗资源的。通过这种方式来做持久化也是个不错的选择.

PHP5.2.6中无法在exception_handler函数中抛出异常

在PHP bugs列表中也找到这个bug,但是似乎没有被处理,bug提出的时间是2005年,不知道新版本的有没有解决。
PHP:5.2.6
OS: Mac OS Leopard 10.5.7
Server: Apache 2.2

这个代码就有问题:
function e_handler($e)
{
throw new Exception();
}
set_exception_handler(‘e_handler’);
throw new Exception();

这将会导致
Fatal error: Exception thrown without a stack frame in Unknown on line 0

Update: 这应该属于设计问题,如果在exception_handler()函数中可以抛出异常,而这个异常又会继续调用exception_handler(),这样下去就会出现死循环,这就是为什么程序会出错的原因吧。
建议在exception_handler函数体内的代码包在try catch中,避免意外抛出异常导致出这个问题

function e_handler($e)
{
try
{
throw new Exception();
}
catch (Exception $e)
{
echo “catched…”;
}
}
set_exception_handler(‘e_handler’);

throw new Exception();

Mac下我常用的软件

习惯了Mac OS X Leopard以后发现自己已经离不开它了。像很多Linuxer一样除了上网银以外统统不用Windows,玩个CS什么的也都用Wine了,我很笨,目前在Mac 上和Ubuntu下都没有Wine成功过 :( “, 下面就介绍一些我认为在Mac下非常使用的软件吧。
1. QuickSilver

只需要Command+Space 再加上几个字母就可以方便的你的程序,通过一些插件QuickSilver能极大的提高我们的效率,强烈推荐,唯一不足的是这款软件的作者已经不继续维护这款软件了,他被Google请去做另外一款和这个类似的软件Google Quick Search Box 试用了一下还不够成熟,目前和QS比起来还是差太多了。

2. TextMate

这款软件就不用多介绍了吧,强大的编辑器。开发进度有点慢,作者对软件质量要求很高,不许诺2.0版本到底何时能出来,很多人都以为作者停止了开发,前不久作者发布了这篇文章,声明开发仍在继续,感兴趣的同学可以看这里看看TextMate2.0到底有哪些改进。这篇在2007年就发布了。实在过的有点久了。

3.Things

GTD绝佳软件,缺点不具有网络同步功能

4.Evernote

笔记软件,很方便,之前我也用Evernote来做GTD发现管理器来很不方便,后来发现上面提到的Things就放弃用Evernote做GTD了,Evernote做笔记绝对顺手,他同时提供Windows 和Mac OS 两个版本,还有Web版的,跨平台很有优势。免费用户提供40M得空间,对我来说这其实绝对够用。

5.Adium

多协议IM客户端支持:Gtalk, MSN, …QQ(因为QQ的协议是不公开的,所以可能定期会抽风,上QQ也可以用官方的 QQ for Mac)..

6.Tweetie

Twitter就靠他了,说不出那里好,就是很好用。免费版有广告。还好基本不影响用户体验。

7.iChm

看chm的电子书就靠它了。支持搜索

8.RescueTime

这个软件也有各个操作系统的版本,收集自己的时间分配,然后会有个汇总。

9.AppZapper

卸载软件用它来卸载还是比较干净的。方便快捷

10.Monolingual

帮你清除一些不需要的语言信息,节省磁盘空间。