SQLAlchemy 使用记录

什么是 SQLAlchemy ?

The Python SQL Toolkit and Object Relational Mapper

create engine

首先 要创建 Engine 实例

sqlalchemy.create_engine(*args, **kwargs)

创建 mysql 连接

# driver mysql-python
DB_PATH = "mysql+mysqldb://root:password@host:port/dbname?charset=utf8mb4"
xchat_engine = create_engine(
    DB_PATH,
    echo =False,
    pool_size=40,
    pool_recycle=3600
)

连接的字符串 URL 格式 dialect[+driver]://user:password@host/dbname[?key=value..] ,其中

  • dialect 是使用的数据库 mysql, oracle, postgresql 等等
  • driver 是连接数据库需要的驱动 DBAPI,比如 psycopg2, pyodbc, cx_oracle 等等

另外这个字符串也可以使用 sqlalchemy.engine.url.URL 实例。

对于 MySQL 而言,driver 可以有如下选择

# default
engine = create_engine('mysql://scott:tiger@localhost/foo')

# mysql-python
engine = create_engine('mysql+mysqldb://scott:tiger@localhost/foo')
# sudo apt-get install libmysqlclient-dev
# pip install mysqlclient

# MySQL-connector-python
engine = create_engine('mysql+mysqlconnector://scott:tiger@localhost/foo')

# OurSQL
engine = create_engine('mysql+oursql://scott:tiger@localhost/foo')

# pymysql
engine = create_engine('mysql+pymysql://user:pass@localhost/foo')

Declare a Mapping

定义和数据库 Map 的实体

from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class SomeClass(Base):
    __tablename__ = 'some_table'
    id = Column(Integer, primary_key=True)
    name =  Column(String(50))

declarative_base() 方法会返回一个 base class,所有自己定义的 class 都要从此继承。关于定义类(表)中数据类型的选择,可以参考文末的对应关系。定义好之后 sqlalchemy.schema.Tablesqlalchemy.orm.mapper 会产生。结果可以从类变量获取

# access the mapped Table
SomeClass.__table__

# access the Mapper
SomeClass.__mapper__

如果想要自己定义的字段和数据库中字段相区别,可以在 Column 中定义 some_table_id

id = Column("some_table_id", Integer, primary_key=True)

通过 base class 定义的类包含一个 MetaData 通过这个 MetaData 可以定义 Table ,然后通过如下语句就可以直接在数据库中生成定义好的表。

engine = create_engine('sqlite://')
Base.metadata.create_all(engine)

创建 Map 类实例

加入定义了 User 类,创造一个实例

ed_user = User(name='ed', fullname='Ed Jones', password='edspassword')

创建 Session

做好了一切准备,可以和数据库进行交互,ORM 和数据库打交道的是 Session ,从之前定义的 engine 中创建 sessionmake

from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine)

这个 Session 实例,是一个 session 创造的工厂,之后所有的 session 都可以由他创建

session = Session()

这里创建的 session 实例还不会开启任何连接,当第一次被用到,session 会从 Engine 维护的连接池中获得一个连接,一直持有该连接知道 commit 所有的提交或者显式的关闭 session 对象。

Session 的生命周期模式,记住,Session 只是特定对象的一个工作区,限定到特定的数据库连接,

关于 Session 使用更多的问题 可以参考之前的文章。当然官网也有很多使用说明

Adding and Updating Objects

当有了 Session 之后,就可以通过 session 来操作数据库。比如

session.add(ed_user)

当把实例对象 add 到 session 之后,其实不会直接提交给数据库,这个时候只有 flush 操作,这个时候

our_user = session.query(User).filter_by(name='ed').first()

也能够将刚刚 add 的实例检索出来,真正的提交只有执行 session.commit() 之后。

如果发现单一插入数据库比较费时,那么在 SQLAlchemy 1.0.0 之后,可以使用新引入的特性批量操作。

在 Session 中有

  • Session.bulk_save_objects()
  • Session.bulk_insert_mappings()
  • and Session.bulk_update_mappings()

简单的例子:

s = Session()
objects = [
    User(name="u1"),
    User(name="u2"),
    User(name="u3")
]
s.bulk_save_objects(objects)
s.commit()

详情见官网 bulk operations

Querying

查询

# User objects
for instance in session.query(User).order_by(User.id):
    print(instance.name, instance.fullname)

# tuples
for name, fullname in session.query(User.name, User.fullname):
    print(name, fullname)

# named tuples, supplied by the KeyedTuple class can be treated much like an ordinary Python object
for row in session.query(User, User.name).all():
    print(row.User, row.name)
> <User(name='wendy', fullname='Wendy Williams', password='foobar')> wendy

# 控制返回字段
for row in session.query(User.name.label('name_label')).all():
    print(row.name_label)

from sqlalchemy.orm import aliased
user_alias = aliased(User, name='user_alias')
for row in session.query(user_alias, user_alias.name).all():
    print(row.user_alias)

for u in session.query(User).order_by(User.id)[1:3]:
    print(u)

for name, in session.query(User.name).\
    filter_by(fullname='Ed Jones'):
    print(name)

for name, in session.query(User.name).\
    filter(User.fullname=='Ed Jones'):
    print(name)

其他跟多的 filter 操作可以参考这里

Returning Lists and Scalars

在查询时可以通过这些方法来选择返回的数量

query = session.query(User).filter(User.name.like('%ed')).order_by(User.id)
query.all()         # return list
query.first()       # return first
query.one()         # multiple rows found raise an MultipleResultsFound, or no row found raise NoResultFound
query.one_or_none() #  if no results are found, it doesn’t raise an error just return None, however, it does raise an error if multiple results are found.
query.scalar()      # 调用 one() 方法,一旦成功,返回行的第一列

SQLAlchemy 同样也支持直接使用 Text 来写 sql 语句,具体可以参考官网

计数可以使用 count

session.query(User).filter(User.name.like('%ed')).count()

from sqlalchemy import func
session.query(func.count(User.name), User.name).group_by(User.name).all()

session.query(func.count(User.id)).scalar()

更多关于外键等等,可以查看官网:http://docs.sqlalchemy.org/en/latest/orm/tutorial.html

SQLAlchemy vs Python vs SQL 数据结构对应表

SQLAlchemy Python SQL
BigInteger int BIGINT
Integer int INTEGER
Boolean bool BOOLEAN or SMALLINT
Date datetime.date DATE (SQLite: STRING )
DateTime datetime.datetime DATETIME (SQLite: STRING )
Enum str ENUM or VARCHAR
Float float or Decimal FLOAT or REAL
Interval datetime.timedelta INTERVAL or DATE from epoch
LargeBinary byte BLOB or BYTEA
Numeric decimal.Decimal NUMERIC or DECIMAL
Unicode unicode UNICODE or VARCHAR
Text str CLOB or TEXT
Time datetime.time DATETIME

其他重要内容

在定义 Model 时定义表引擎和编码

在 model 中使用 __table_args__ 来定义

class User(BaseModel):
    __tablename__ = 'users'
    __table_args__ = {
        "mysql_engine": "InnoDB",    # 引擎
        "mysql_charset": "utf8"         # 编码
    }

    ...

模型属性名和表字段名不一样

在定义 Column 时指定

id = Column('id', Integer, primary_key=True, autoincrement=True)

定义多列组合唯一索引

有些时候定义表时需要多列组合唯一,比如

class User(Base):
    field1 = Column(Integer, primary_key=True)
    field2 = Column(Integer, primary_key=True)

那么可以

class User(Base):
    field1 = Column(Integer)
    field2 = Column(Integer)
    __table_args__ = (
        UniqueConstraint('field2', 'field1'),
        {},
    )

From: StackOverflow

编码问题

在使用 mysqldb 这个 driver 的时候莫名其妙遇到编码的问题,数据库,表,都是 utf8mb4,在 CentOS6 下面死活用不了 utf8mb4 作为连接编码

can't initialize character set utf8mb4 (path /usr/share/mysql/charsets/)

一直都是这个错误,但在我本地一切 OK,这时候我想是不是机器 driver 的问题,换成 pymysql 之后就 OK 了。

http://docs.sqlalchemy.org/en/latest/dialects/mysql.html#charset-selection

也是醉了。

reference

  • 《Essential SQLAlchemy 2nd Edition 2015》

2016-08-12 python , mysql , sqlalchemy , orm , sql

Android 人脸检测

最近项目使用,总结了目前 Android 中使用到的人脸检测技术,主要分成三部分来介绍:

  1. Android 原生支持人脸检测,从 API 1 开始就提供原生的人脸检测,但是该方法识别率相对较低
  2. 在后来的发展中 Google 将人脸识别技术放到了 Google Play Services 中,从 Google Play Service 7.8 之后提供的 Mobile Vision API 中我们可以使用新的人脸检测API。这个方法不仅能够识别有没有人脸,还能够识别人脸的区域,甚至在视频流中追踪相同的人脸,并且提供了一系列方法来获取眼睛、嘴巴、鼻子、脸颊的位置,利用这个 API 就能够在动态的视频中给人脸增加一些有趣的效果,加个胡子,带个帽子,等等,市面上有一些动态增加表情的 App 利用的似乎就是这个技术。
  3. Face++, 国内一家做人脸检测的提供商,不过其免费版服务需经过网络,也就意味着需要联网将照片上传到其服务器,然后他返回识别的结果回来。
  4. 其他服务,经过搜索 OpenCV 也能提供类似的服务,但是目前我还没有详细了解。 OpenCV 做计算机视觉有一定积累,相信识别准确率应该有保障。

接下来就依次介绍前三种人脸检测的方法。

Android 原生人脸检测API

android.media 包中的人脸检测API 有如下两个限制:

  1. Bitmap 必须以 Config.RGB_565 解码
  2. 输入的 Bitmap 宽度需要为偶数

只要注意这两个限制,另图片眼睛的距离不要太小,其他的代码核心的没几句话。

/**
 * There are some limitation in this 用android.media 包中识别人脸package.
 * 使用使用使Face Detection API's input Bitmap must :
 * <p/>
 * 1. config with Config.RGB_565<br/>
 * 2. Bitmap width must be even<br/>
 * <p/>
 * more details can be checked
 * http://stackoverflow.com/q/17640206/1820217
 *
 * @param bitmap Bitmap
 */
private void detectUsingNative(final Bitmap bitmap) {
    if (null == bitmap || isRunning) {
        if (listener != null) {
            listener.onFail();
        }
        return;
    }
    facesCount = 0;
    final android.media.FaceDetector faceDetector = new android.media.FaceDetector(bitmap.getWidth(), bitmap.getHeight(), MEDIA_MAX_DETECT_FACE_NUMBER);
    androidNativeFacesResults = new android.media.FaceDetector.Face[MEDIA_MAX_DETECT_FACE_NUMBER];
    final Handler handler = new Handler();
    thread = new Thread() {
        @Override
        public void run() {
            facesCount = faceDetector.findFaces(bitmap, androidNativeFacesResults);

            handler.post(new Runnable() {
                @Override
                public void run() {
                    if (listener != null) {
                        listener.onSuccess();
                    }
                }
            });

            isRunning = false;
        }
    };
    thread.start();
    isRunning = true;
}

Play Service 中人脸检测

Play Service 中的人脸检测是随着 Mobile Vision API 一同出现的,这个库中还有一些其他的API,比如识别二维码,识别文字等等,并且 Play Service 中的人脸识别更准确的说应该叫人脸追踪,在官方实现的 Demo 中,直接调用手机摄像,能够一直追踪镜头中的同一人头像。

以下是部分实现,详细代码可参考文末给出的 GitHub 代码。

/**
 * 使用 Play Service 中人脸检测
 *
 * @param bitmap Bitmap
 */
private void detectUsingGms(Bitmap bitmap) {
    if (null == bitmap) {
        if (listener != null) {
            listener.onFail();
        }
        return;
    }
    facesCount = 0;

    detector = new FaceDetector.Builder(context)
            .setTrackingEnabled(false)
            .setLandmarkType(FaceDetector.ALL_LANDMARKS)
            .setClassificationType(FaceDetector.ALL_CLASSIFICATIONS)
            .build();

    // This is a temporary workaround for a bug in the face detector with respect to operating
    // on very small images.  This will be fixed in a future release.  But in the near term, use
    // of the SafeFaceDetector class will patch the issue.
    Detector<Face> safeDetector = new SafeFaceDetector(detector);

    // Create a frame from the bitmap and run face detection on the frame.
    Frame frame = new Frame.Builder().setBitmap(bitmap).build();

    faces = safeDetector.detect(frame);

    if (!safeDetector.isOperational()) {
        // Note: The first time that an app using face API is installed on a device, GMS will
        // download a native library to the device in order to do detection.  Usually this
        // completes before the app is run for the first time.  But if that download has not yet
        // completed, then the above call will not detect any faces.
        //
        // isOperational() can be used to check if the required native library is currently
        // available.  The detector will automatically become operational once the library
        // download completes on device.

        if (listener != null) {
            listener.onFail();
        }
        return;
    }
    if (listener != null) {
        listener.onSuccess();
    }
}

Face++ 人脸检测服务

Face++ 提供了联网的人脸检测服务,需要到其网站上注册开发者账号获取API使用权限。其大概检测代码如下:

    /**
     * 使用 Face++ 人脸检测
     *
     * @param file File
     */
    private void detectUsingFacePlus(File file) {
        if (!file.exists() || isRunning) {
            if (listener != null) {
                listener.onFail();
            }
            return;
        }
        final PostParameters parameters = new PostParameters();
        parameters.setImg(file);
        final Handler handler = new Handler();
        facesCount = 0;
        thread = new Thread() {
            @Override
            public void run() {
                boolean hasFace = false;
                boolean detectSucceed = false;
                Log.d("FacePlusDetect", "Detect Request :" + parameters.toString());
                HttpRequests httpRequests = new HttpRequests(FACEPLUSPLUS_APIKEY, FACEPLUSPLUS_APISECRET, false, true);
                JSONObject result;
                try {
                    result = httpRequests.detectionDetect(parameters);
                    if (result != null) {
                        detectSucceed = true;
                        JSONArray faces = result.getJSONArray("face");
                        double imgWidth = result.getDouble("img_width");
                        double imgHeight = result.getDouble("img_height");
                        if (faces != null && faces.length() > 0 && null != listener) {
                            // Has face!!
                            facesCount = faces.length();
                            facePlusResults = new RectF[facesCount];
                            hasFace = true;
                            for (int i = 0; i < facesCount; i++ ){
                                float x, y, w, h;
                                facePlusResults[i] = new RectF();
                                // 需注意返回结果的center,width,height 都为0~100,百分比
                                x = (float) faces.getJSONObject(i).getJSONObject("position").getJSONObject("center").getDouble("x");
                                y = (float) faces.getJSONObject(i).getJSONObject("position").getJSONObject("center").getDouble("y");
                                w = (float) faces.getJSONObject(i).getJSONObject("position").getDouble("width");
                                h = (float) faces.getJSONObject(i).getJSONObject("position").getDouble("height");
                                float realx = (float) (x * imgWidth / 100);
                                float realy = (float) (y * imgHeight / 100);
                                float realw = (float) (w * imgWidth / 100);
                                float realh = (float) (h * imgHeight / 100);
                                facePlusResults[i].set(realx - realw /2,
                                        realy - realh / 2,
                                        realx + realw / 2,
                                        realy + realh / 2);
                            }
//                            String genderStr = playServiceFaces.getJSONObject(0).getJSONObject("attribute").getJSONObject("gender").getString("value");
//                            gender = Gender.getValueOf(genderStr);
                        } else {
                            hasFace = false;
//                            detectSucceed = true;
//                            gender = Gender.OTHER;
                        }
//                        Log.d("FacePlusDetect", "Detect Result : hasFace = " + hasFace + "; gender = " + gender.toString());
                    }
                } catch (FaceppParseException e) {
                    detectSucceed = false;
                    Log.d(TAG, "Detect FaceppParseException !");
                    e.printStackTrace();
                } catch (JSONException e) {
//                    if (hasFace) {
//                        gender = Gender.OTHER;
//                    }
                    Log.d(TAG, "Detect JSONException !");
                    e.printStackTrace();
                }

                if (detectSucceed) {
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            if (listener != null) {
                                listener.onSuccess();
                            }
                        }
                    });
                } else {
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            if (listener != null) {
                                listener.onFail();
                            }
                        }
                    });
                }

                isRunning = false;
            }
        };
        thread.start();
        isRunning = true;
    }

Detect Result

Face++ Detect

Detect result

FaceDetectDemo 代码可参考:https://github.com/einverne/AndroidFaceDetectDemo

Reference

全部代码可参考 : https://github.com/einverne/Android-Face-Recognition


2016-08-06 Android , AndroidDev

转换图片格式

安装 imagemagick 用到的工具在这个包中。

sudo apt install imagemagick

首先检查图片格式

identify temp.jpg

将一张图片转换格式

convert image.jpg image.png

批量转换图片

mogrify -format png /tmp/*.jpg

将目录下所有的 png 图片转换成 png 格式,并保存在同目录


2016-08-03 format , jpg , png , convert , linux , command

以十六进制查看文件的方法整理

使用 file 命令来检测 data 文件的类型

file data

zshbash 原生 echo 支持将十六进制 HEX 转成 ASCII

echo -e '\x68'

将二进制文件写入文件

printf "\x68\x65\x6c\x6c\x6f\x20\x77\x6f" | cat - oldfile > newfile

命令查看

Linux 下可以使用如下命令查看文件二进制

xxd filename.txt
hexdump filename.txt

编辑

GUI 二进制编辑器

sudo apt-get install ghex

biew

hexedit

使用二进制模式编辑文件

vim -b datafile

在 vim 编辑器中使用 :%!xxd 来将文件变成十六进制

使用 :%!xxd -r 来转变为原来的文件

十六进制模式下只有修改十六进制部分才会生效,右边可显示文本部分修改不会对文件有修改效果。


2016-08-02 linux , command

Clonezilla 克隆系统

之前遇到的一个问题,安装 Linux Mint 的系统分区快要满了,但是我又不想重装系统,于是就提出来这样的一个问题。当时整天得想着如何解决这样的一个问题比较好,于是有了这篇文章。当然也借由这篇文章讲述一个复杂问题的提出到解答的整个过程。其他类似问题的解决过程也是类似的。

一个问题的提出到解决

问题:整体备份 Linux 系统,免去重装系统,进行各种配置,以及安装各种应用的麻烦

问题相关:Windows 下有 Ghost 类似的工具可以协助完成 Windows 系统镜像的制作,并且可以完整恢复系统,而 Mac 下有 Time Machine 类似的工具,似乎可以还原整个系统。我想 Linux 下应该也存在类似的工具。

问题解决:经过 Google,Linux 下备份系统的方式可以有很多,我不想使用命令行,如果有现成工具最好,最后锁定关键词 “Clonezilla”,一款非常强大的备份工具,可以用来备份硬盘,分区到镜像,或者直接写入其他硬盘或者分区。

之后搜索 Clonezilla 相关教程以及使用,借助 YouTube 熟悉使用过程,下载 iso 镜像,安装硬盘,制作启动 U 盘,从 U 盘启动电脑,熟悉电脑硬盘的名称,sda1,sdb1,sdb2,sdc1,类似的名称,基本上 sda 就是一块硬盘,后面接的数字是分区,在使用 Clonezilla 的过程中一定要小心数据,目标一定要指定空分区,或者空磁盘,否则目标磁盘的数据会全部被清除。

在我的真实例子中,我的 Linux Mint 安装在一块硬盘的一个分区中,利用命令或者 GUI,查看该分区的名称,然后我的解决方法是给电脑新安装了一块 SSD,将光驱位换了。然后给该硬盘分区,并查看该分区的名称,然后利用学习到的 Clonezilla 来完成系统从一个分区到另一个分区的克隆。

clonezilla clone linux partition

从上图就可以看出,我是将 /dev/sdb8 分区拷贝到 /dev/sda1 分区。

如下使用 sudo blkid 查看 UUID。

/dev/sda1: UUID="a7a98d76-5dab-4272-8b9a-b82042b279c5" TYPE="ext4"
/dev/sdb1: LABEL="Program" UUID="000E3FDB00097ED7" TYPE="ntfs"
/dev/sdb5: LABEL="Document" UUID="000C3A300002F285" TYPE="ntfs"
/dev/sdb6: LABEL="Media" UUID="0005653100096CB5" TYPE="ntfs"
/dev/sdb7: UUID="ad6f91df-ba08-4fad-8efc-ac1254320e2d" TYPE="swap"
/dev/sdb8: UUID="5f920149-5676-46ef-b545-e50be77c65e2" TYPE="ext4"
/dev/sdc1: LABEL="System" UUID="EEBACEF9BACEBCF9" TYPE="ntfs"
/dev/sdc2: UUID="8A0005F80005EBCF" TYPE="ntfs"

在完成从分区到分区的克隆之后,修复系统磁盘 UUID 以及启动引导 grub。这里花费了一些时间去了解 grub 的启动过程。学习了一些命令。

sudo blkid   #查看磁盘 UUID
sudo fdisk -l  #查看磁盘

vim /etc/fstab  #磁盘的信息
vim /boot/grub/grub.cfg   #启动引导的信息
# 大部分的情况下不需要直接修改这两个文件,查看一下内容即可,使用其他命令更新,无需手动的修改里面的值。

update-grub2   #更新启动引导

由于我在使用 Clonezilla 备份恢复的时候使用的是从硬盘一块分区拷贝到另一块硬盘分区,所以在完成备份成功之后两款分区的 UUID 是一样的,这样就导致开机引导的时候总是回到原来的系统,需要给新的分区重新指定一个 UUID,然后更新 grub 才能完成启动到新的硬盘分区。可以通过下面的教程完成 UUID 的修改。

至于修改 Linux 分区的 UUID 其实是另一个问题,当完成从问题提出到解决的过程就能解决。

修改 Linux 分区 UUID

以下为翻译,原文见[参考]1

UUID 介绍

Linux 分区的 UUID 的全称是 Universally Unique IDentifier。 这个 ID 会被用在不同的地方用来标识硬盘分区。最常见的就是在 /etc/fstab 文件中, 这个文件用来再系统启动时挂载分区。以下是一个小例子:

# <file system> <mount point>   <type>  <options>       <dump>  <pass>
proc            /proc           proc    nodev,noexec,nosuid 0       0
# / was on /dev/sdc3 during installation
UUID=9467f4de-4231-401f-bcaa-fee718d49e85 /               ext4    errors=remount-ro 0       1
# swap was on /dev/sdb1 during installation
UUID=aabe7e48-2d11-421f-8609-7ea9d75e7f9b none            swap    sw              0       0

UUID 的理由

理论上来讲创建两个 UUID 相同的几率是非常小的,可以参考 Random UUID , 但是如果使用 DD 或者 Clonezilla 在同一台设备克隆并恢复了分区,那么就有可能导致完全一样的分区 ID。

使用以上两个命令将会创建两个一模一样的分区,包括 UUID。在这样的情况下,就需要手动更改 UUID。

修改 UUID

第一步,首先利用 sudo blkid 来获取分区标示。

/dev/sdb1: UUID="aabe7e48-2d11-421f-8609-7ea9d75e7f9b" TYPE="swap"
/dev/sdc1: UUID="9467f4de-4231-401f-bcaa-fee718d49e85" TYPE="ext4"
/dev/sdc3: UUID="93a54a4a-e0f5-4152-ae59-2245e8d16ee4" TYPE="ext4"
/dev/sde5: UUID="9467f4de-4231-401f-bcaa-fee718d49e85" TYPE="ext4"
/dev/sde6: LABEL="var" UUID="30433f28-1b79-4b4d-9985-fef5b1c886b5" TYPE="ext4"

如上图可以看出 /dev/sdc1/dev/sde5 的 UUID 完全一致。第二步,使用命令 uuidgen 产生新的 UUID

uuidgen
f0acce91-a416-474c-8a8c-43f3ed3768f9

最后将新的 UUID 应用到新的分区中,

sudo tune2fs /dev/sde5 -U f0acce91-a416-474c-8a8c-43f3ed3768f9

最后,更新 grub 来使用新的 UUID,以免导致系统的混乱。

reference


2016-08-01 clonezilla , linux , backup , clone , ssd , system

我使用的 Xposed module

什么都不多说,这个神器就如 Chrome 下的 Tampermonkey,有很多神奇的待发现。

官网地址:http://repo.xposed.info/

更新及 change log: http://forum.xda-developers.com/showthread.php?t=3034811

Xposed Framwork

安装之前

查看设备CPU架构

例如:Nexus 6 CPU芯片是 armv7,选择 arm 即可。 Play Store 上有一个 Hardware Info 的 APP,可以查看 CPU 架构。

然后需要确认手机的 SELinux 设置成 Permissive , 可以使用 SELinuxModeChanger 一款修改 SELinux 的应用。不过 N6 默认是 Permissive 的,也就不用修改了。

下载必要内容

准备好apk,和 zip 包,最好也下载好 xposed-uninstaller*.zip ,当刷入 zip 包之后无限启动的时候可以恢复。不过最好的方式还是通过 recovery 整体备份原来的数据 Nandroid backup 。数据是最重要的!!切记。

Android 6.0 的 framework zip 包在 官网 可以下到。 而 APK 在 Xda 论坛可以找到。

刷入

通过 recovery 刷入 flash zip 之后进入系统会很长时间,之后启动 Xposed Installer ,enable module 之后需要 soft reboot 。 切记需要点击 Xposed Installer 中的 reboot ,不能自己手工 reboot ,否则无法识别 module,掉进这个坑,重启无数次无效。

保留 modules

经过反复尝试保留下来的一些modules,

AdBlocker

屏蔽广告,另外一个可选择的屏蔽广告的 module 叫做 MinMinGuard,还没尝试,效果应该也很好。很多 ROM 集成的 Adaway 似乎也不错。

AppOpsXposed

权限管理,一张图解释所有

AppOpsXposed

Xposed Pokemon

模拟地址,但是 XPrivacy 也是可以有相同的功能的。但是最后还是另一个 module 叫做 Xposed Pokemon 好用。

GravityBox MM

可以定制很多部分,包括状态栏,导航栏,感觉最有用的就是可以定制长按 recent 按钮弹出小的 Launcher。然后自定义快速点击 recent 两次切换最近使用应用也是很有用的。还可以给状态栏加上网速监测。

自用上这个功能感觉已经不在需要刷其他ROM来支持一些特殊功能了,完全原生+Xposed 就已经让我很舒服了。

Greenify

休眠后台服务,很好的App。

No Lock Home

原生 Android 的 Smart Lock 只有根据蓝牙,GPS和侦测随身携带,这个 module 增加了可以根据 WIFI,或者 mac 地址或者 LAC CID 来增加信任的地点,感觉这个更加实用。日常实用 WIFI 的地方一般都是自己熟悉的地方,将那些地方的 WIFI AP name 或者 WIFI Mac 地址加入信任列表,这样就不用总在熟悉的地方解锁解锁解锁了。自己熟悉的地方也总不至于丢手机的吧。

WechatForwarder

很强大的转发,原先一直想要的转发功能都能搞定了。

WechatUnrecalled

微信不撤回

XInsta

下载 Instagram 的图片,自从 IFTTT 不让我自动下载图片之后就诞生了这种需求。也正是因为这个需求让我发现并使用了 Xposed,然后又间接的找到了很多好玩的 module ,不过后来又找到了自动下载 ins 照片到 Google Drive 的方法

运动修改器

修改运动记步频率,还挺神奇的。

在导航栏或者状态栏显示音乐波形,太赞了。

不太需要的 module

使用之后感觉不太需要的 module ,但是很强大的 module

XPrivacy

很复杂的但是很强大的权限管理,非常细节,但是对新手不好,使用非常复杂。对权限要求比较高的可以尝试一下。

链接: http://repo.xposed.info/module/biz.bokhorst.xprivacy

Flat Style Bar Indicators

高度自定义状态栏

Flat Style Colored Bars

和之前使用的 tint status bar 效果差不多,根据应用颜色改变状态栏和导航栏的颜色

Flat Style Colored Keyboard

自动改变键盘的颜色,我使用 TouchPal 所以也用不到。

Ifont

修改系统字体

Amplify

查看那些服务,禁用服务,省电

所有的 Xposed Module 都可以在 http://repo.xposed.info/module-overview 这里找到。当然 Xda 也有很多更新内容。


2016-07-15 Android , AndroidDev , Xposed , 整理合集

我使用的 Xposed module

什么都不多说,这个神器就如 Chrome 下的 Tampermonkey,有很多神奇的待发现。一句话概括 Xposed 就是 Android 上一款可以修改任意系统进程模式的框架,包括系统进程。下面介绍的 module 基本上都是 Xposed 框架下的插件,实现了各种神奇的功能,有些功能有被官方收录到应用官方功能中的,比如 Instagram 的图片放大功能,有些功能至今还在和官方捉迷藏的,比如微信抢红包插件,还有些功能增强了系统的扩展性,让整个手机能够高效的完成日常的工作,比如在信任WIFI下自动解锁屏幕等等功能。总之 Xposed 框架让整个 Android 系统上升了一个层次。

用 Xposed 的自我介绍来说,就是能够在无感知不接触任何 APK 的情况下修改系统或者应用的行为

Xposed is a framework for modules that can change the behavior of the system and apps without touching any APKs.

Xposed 是由 rovo89 开发和维护的一个项目, 官网地址:http://repo.xposed.info/, 源代码地址在 Github: https://github.com/rovo89/Xposed

更新及 change log 在 xda 论坛: http://forum.xda-developers.com/showthread.php?t=3034811

Xposed Framwork

安装之前

查看设备CPU架构

例如:Nexus 6 CPU芯片是 armv7,选择 arm 即可。 Play Store 上有一个 Hardware Info 的 APP,可以查看 CPU 架构。

然后需要确认手机的 SELinux 设置成 Permissive , 可以使用 SELinuxModeChanger 一款修改 SELinux 的应用。不过 N6 默认是 Permissive 的,也就不用修改了。

下载必要内容

准备好apk,和 zip 包,最好也下载好 xposed-uninstaller*.zip ,当刷入 zip 包之后无限启动的时候可以恢复。不过最好的方式还是通过 recovery 整体备份原来的数据 Nandroid backup 。数据是最重要的!!切记。

Android 6.0 的 framework zip 包在 官网 可以下到。 而 APK 在 Xda 论坛可以找到。

刷入

通过 recovery 刷入 flash zip 之后进入系统会很长时间,之后启动 Xposed Installer ,enable module 之后需要 soft reboot 。 切记需要点击 Xposed Installer 中的 reboot ,不能自己手工 reboot ,否则无法识别 module,掉进这个坑,重启无数次无效。

保留 modules

经过反复尝试保留下来的一些modules,

AdBlocker

屏蔽广告,另外一个可选择的屏蔽广告的 module 叫做 MinMinGuard,还没尝试,效果应该也很好。很多 ROM 集成的 Adaway 似乎也不错。

AppOpsXposed

权限管理,一张图解释所有

AppOpsXposed

Xposed Pokemon

模拟地址,但是 XPrivacy 也是可以有相同的功能的。但是最后还是另一个 module 叫做 Xposed Pokemon 好用。

GravityBox MM

可以定制很多部分,包括状态栏,导航栏,感觉最有用的就是可以定制长按 recent 按钮弹出小的 Launcher。然后自定义快速点击 recent 两次切换最近使用应用也是很有用的。还可以给状态栏加上网速监测。

自用上这个功能感觉已经不在需要刷其他ROM来支持一些特殊功能了,完全原生+Xposed 就已经让我很舒服了。

Greenify

休眠后台服务,很好的App。

No Lock Home

原生 Android 的 Smart Lock 只有根据蓝牙,GPS和侦测随身携带,这个 module 增加了可以根据 WIFI,或者 mac 地址或者 LAC CID 来增加信任的地点,感觉这个更加实用。日常实用 WIFI 的地方一般都是自己熟悉的地方,将那些地方的 WIFI AP name 或者 WIFI Mac 地址加入信任列表,这样就不用总在熟悉的地方解锁解锁解锁了。自己熟悉的地方也总不至于丢手机的吧。

20170805 更新 WeXposed

作者将原本的多个module合并为一个,现在集成了抢红包(自定义关键词过滤),阻止撤回,防朋友圈删除,扩展表情限制,筛子随机等等等等功能,非常强大

WechatForwarder
很强大的转发,原先一直想要的转发功能都能搞定了。

WechatUnrecalled
微信不撤回

XInsta

下载 Instagram 的图片,自从 IFTTT 不让我自动下载图片之后就诞生了这种需求。也正是因为这个需求让我发现并使用了 Xposed,然后又间接的找到了很多好玩的 module ,不过后来又找到了自动下载 ins 照片到 Google Drive 的方法

运动修改器

修改运动记步频率,还挺神奇的。

在导航栏或者状态栏显示音乐波形,太赞了。

No Lock Home

通过信任连接的 WIFI,或者 WIFI MAC 地址来自动解锁屏幕,感觉比Android自带的 SmartLock 要好用很多。Smart Lock 中的 Body Detect,还算有用,其他根据 GPS 来自动解锁必须得开着高精度GPS, 不仅耗电也不精确。

LocationReportEnabler

开启 Google 位置报告

网易云音乐插件

解锁网易云音乐的版权锁定。

地址在: https://github.com/bin456789/Unblock163MusicClient-Xposed/releases

不太需要的 module

使用之后感觉不太需要的 module ,但是很强大的 module

XPrivacy

很复杂的但是很强大的权限管理,非常细节,但是对新手不好,使用非常复杂。对权限要求比较高的可以尝试一下。

链接: http://repo.xposed.info/module/biz.bokhorst.xprivacy

Flat Style Bar Indicators

高度自定义状态栏

Flat Style Colored Bars

和之前使用的 tint status bar 效果差不多,根据应用颜色改变状态栏和导航栏的颜色

Flat Style Colored Keyboard

自动改变键盘的颜色,我使用 TouchPal 所以也用不到。

Ifont

修改系统字体

Amplify

查看那些服务,禁用服务,省电

所有的 Xposed Module 都可以在 http://repo.xposed.info/module-overview 这里找到。当然 Xda 也有很多更新内容。


2016-07-15 Android , AndroidDev , Xposed , 整理合集

Telegram 技巧

感觉是时候写一篇 Telegram 的安利文了。Telegram 简单介绍就是一款 IM, 及时聊天工具。当然其实他远远的超越了一个 IM,却依然保持了作为一个 IM 应有的速度和快捷。

对于基本功能,電腦玩物 Telegram 10 技 已经将 Telegram 的主要功能及使用技巧说得非常明白了,就不再多说了。其中我最喜欢的几点:

  • 跨平台,这也正是我一直坚持 Hangout 的原因之一,我不喜欢整天抱着手机,我在电脑前的时间可比拿手机的时间长,我也不希望我坐下之后需要花很长的时间,打开一个客户端输入密码,然后还要花一段时间同步消息,关键有些客户端之间还不能同步消息记录。而 Telegram 给我的体验就和 Hangout 一致,多个平台,多个设备几乎能够在同一时间受到消息,我也能够在任何一个客户端回复,并且所有客户端的消息都是同步的。
  • Bot ,聊天机器人,很久之前的 Gtalk 也是支持机器人的,对那个自动翻译的机器人还是略有记忆,只是后来就没有后来了。而 Telegram 正是将聊天机器人这个命题重新书写了。开放的 Bot 系统,让 Telegram 成为了一个全能的平台,他是一个 IM ,他也可以是一个翻译工具,只需要一个翻译 bot,他也可以是一个 RSS 阅读器,只需要一个 RSS 订阅 bot,他也可以是一个 Tinder,只需要一个约会 bot…… Telegram 可以变成想要的任何工具,更不说官方集成的 @gif, @bold,@sticker 等等 bot
  • 开放,作为一个 IM,应该能够包容万千也能够开放的分享,这就提到了 Telegram 支持的媒体内容,文字,链接,音频,视频,gif,表情贴图,能够想到的几乎所有内容都可以兼容,甚至有人直接 Telegram 来当音乐播放器。并且 Telegram 在图片以及 Gif 分享的时候做了很多的优化处理,我在日常使用中几乎没有感受到任何卡顿,甚至流量的消耗也在我可接受的范围。

以下就是几个非常吸引人的 Point:

Sticker

说了这么多,其实重点想要谈谈他的贴图以及 Bot 系统。首先是贴图,在刚上手 Telegram 的时候,我就被他丰富的表情震撼到了,就像他在 Blog 中所说, Telegram 觉得现存任何一个 IM 的表情系统设置都不是很理想,封闭,收费,并且糟糕透顶,于是 Telegram 大笔一挥自己做了一套系统,这是我迄今为止使用过最赞的表情贴图系统。尤其是在最近迷上 Pokemon Go 之后,更是找到了很多萌萌的小精灵。

pokemon sticker

从下面两个网站能够找到你想要的绝大多数表情,多到无法想象:

如果这两个网站都无法让你满足,Google,以及官方的 @sticker 机器人都可以帮你找到喜欢的表情。当然如果参与到更多的对话中,就能找到更多的表情。

Bots

几个神奇的内置 bot。在使用过程中竟然遇到了“向聊天群中添加 bot 这样的问题”,不过答案也很简单,拥有群管理权限时,直接添加成员,输入 @ + bot 的名字即可。

@gif

寻找 gif bot,使用最简单了,聊天时直接输入 @gif whatever I like , Telegram 会帮你搜索 whatever I like 字段的内容,点击分享即可。

@bold

Markdown bot 格式化输入

输入: @bold this is *bold* , this is _Italic_ , and this is `some code with *bold*` 。即可得到。

Telegram bold bot

@vote

@vote 用来创建投票

@like

用来创建 emoji based Like 投票。

@Stickerdownloadbot

发送给这个 bot 表情,他就自动将表情转换成 png 。

@sticker

inline bot,可以利用这个 bot 将普通的 emoji 转成其他表情贴图。

然后是一些其他好玩的第三方 bot:

  • @GroupButler_bot 用来管理群组的 bot,可以设置 rules , 管理垃圾消息等等
  • @storebot 用来发现其他 bot 的 bot
  • @utubebot 下载 YouTube 视频
  • @Instasave_bot 用来下载 instagram 的 bot

如何寻找有趣的 bot

网站

  • https://thereisabotforthat.com/
  • https://storebot.me/

或者通过 bot

  • https://telegram.me/storebot

SuperGroup

最为人熟知的就是 Telegram 的聊天群了,在一个没有社交关系基础的 Message App 上,最快吸引大众的就是这个 Group 了,通过群组来建立最初的社交基础。而最初的 Group 功能没有那么强大,管理员也只能是创建群的人,后来 Telegram 升级了一次 supergroup ,于是乎 supergroup 就已经拥有了 Message App 该有的功能,管理员权限,置顶消息,消息管理,搜索等等功能,甚至可以再群中添加 bot 来实现一些自定义的功能。

特别值得称道的便是,所有在 Telegram 中分享的内容都保存历史记录,虽然目前中文搜索有些问题,但是跨平台,消息同步已经让我无法离开这个应用了。

最后来记录一些有爱的群组:

Channel

频道的功能类似于微信的订阅号了,Channel 能实现一对多的通知,当然可以拿他当广播,但是和微信订阅号不同的时,Channel 是不能接收回复的。

一些值得关注一下的频道:

Client

最后推荐一个第三方 Android Client : Plus Messenger Link。 虽然官方的客户端已经足够完美了,但是因为加入的群比较多,经常无法找到对应的目的地,这个第三方客户端完美的解决了这个问题。


2016-07-14 telegram , im

Pokemon Go 简介

期待这款产品久矣,从开始玩 Ingress 开始,就一直想着这样一款基于地理现实的游戏将如何发展,至少从今天的火热程度看来相信 Niantic 之后的路不会那么坎坷。其实从逻辑上 Pokemon Go 只是在 Ingress 的逻辑上增加了一层皮肤,原先的 Portal 变成了 Pokestop,原先的 8 塔变成了道馆的等级,原先可以 Hack 的到装备,到 Pokemon Go 中依然可以利用 Spin Pokestop 来获取装备。区别在于 Ingress 一切都是从头开始,什么是 XM,什么是欧洲核子中心,这些都是 Niantic 自己的定义,而如今一切都变得熟悉,皮卡丘,精灵球,甚至野外遇见的每一只小精灵,卖萌的同时,一边也唤醒了很多人儿时的记忆。

这些天浸泡在 Telegram 学习了不少,写下作为笔记。今天刚刚知道原来皮卡丘是分雌雄的,尾巴心形开叉的是雌性,尾巴不分叉的是雄性。这让我自愧不如,当年其实也并没有很深入的追宠物小精灵,而如今一些记忆早就风干,唯一剩下的一点也就是主人公的名字—-小智,还有那只非常可爱的皮卡丘了。

以下是参考官方 Help 写下的一点笔记,不是全部内容的翻译,只是有些疑问我自己并不明白,于是就看了一眼 help。

写在开始之前

  • Respect the community.
  • Be respectful.
  • Respect privacy.
    • 尊重隐私
  • Adhere to the rules of the human world.
    • 不要进入未经允许不可进入的区域,即使那里出现了珍奇神奇宝贝
  • Avoid inappropriate content.
  • No cheating.
    • Methods of cheating, unfortunately, are limited only by cheaters’ imaginations, but include at a minimum the following: using modified or unofficial software; playing with multiple accounts (one account per player, please); sharing accounts; using tools or techniques to alter or falsify your location; or selling/trading accounts.
    • 不要使用修改过或者非官方的App;多账户;共享账户;使用工具伪造位置;售卖账户

Pokemon Go guidelines

说实话其实并不在乎Fake的玩家,游戏的目的是娱乐自己,而不影响别人,飞机确实影响了游戏性,官方也肯定会采取措施,只是长远来看,那些玩家并不会长期的玩这个游戏,只要我们坚持的更远我们就赢了。正如官方所说,作弊的方法 are limited only by imaginations ,自律自觉吧。

Getting Started

在开始的时候,有些内容其实完全不用讲,怎么移动小人(Avatar)?怎么获取物品?在游戏过程中有着非常详细的引导。也就不值得一说了。下面是一些游戏中会出现的术语(不包括全部,显而易见的就不列了):

Glossary

  • Candy 可以在捕获 Pokemon ,孵化宝宝,或者将 Pokemon 送给教授时获得。Candy 可以用来进化或者增强 Pokemon。
    • 抓怪获取 3 个
    • 送给教授 1 个
    • 孵蛋 10-20 个
  • Incense 蚊香,熏香?用来吸引 Pokemon。
  • Lure Module 短时间内可以吸引 Pokemon 到 Pokestop。
  • Defender Bonus 防御奖励,防御道馆可以按时领金币和 Stardust。
  • Potions 药水,恢复 Pokemon 的 HP。
  • Stardust 可以在抓取 Pokemon ,孵化宝宝 (Egg) ,或者在 Defender Bonus 的时候获取。
    • 抓怪 100
    • 孵蛋 600-800
    • 占领道馆 500/个 * 20h
  • Razz Berry 在遭遇野外 Pokemon 时,喂它们吃可以更容易抓取。

From: help

根据 Telegram 红队群@girigirigiri Eye 的详细介绍。

背包中物品

如下物品可能会出现:

  • Poke Ball, 精灵球,用来抓捕 Pokemon ,随着等级提升,有更加厉害的精灵球
  • Potions,药水,用来恢复受伤的 Pokemon
  • Berries,浆果,等级提升之后可能获得

背包有500容量。

pokeball

道馆与战斗

5级以上玩家可以解锁队伍,来大家一定要选红队。

Team Red

道馆

不多说了,道馆的等级由声望决定,等级越高能够容纳的 Pokemon 也越多。当同阵营玩家将Pokemon 放置道馆时,声望提升。地图上显示如下:

占有道馆

每个训练师只能在一个道馆放置一个 Pokemon ,并且这个 Pokemon 不能撤回自己的收集仓,直到它被踢出道馆,所以谨慎选择。

从对方阵营夺取道馆

通过打败对方阵营在道馆中的 Pokemon,在战斗中可以使用至多 6 个Pokemon。每一个被打败的 Pokemon 会导致对方阵营声望下降或者等级降低(reduce the Gym’s Prestige and potentially lowers the Gym’s level)。击败对方阵营的首领(Leader) 会显著降低对方道馆的声望(Prestige)。 当对方阵营的道馆声望为0时,可以从对方阵营夺取。道馆可以被多名训练师同时挑战。

在对战中可以有如下三种操作:

  • Fast Attack 快速攻击
    • 点击自己的 Pokemon 来进行快速攻击
    • 快速攻击补充特殊攻击条读数
  • Dodge attacks 闪躲攻击
    • 左右滑动来闪躲
  • Special Attack 特殊攻击
    • 当特殊攻击条读数充满时,长按 Pokemon 来进行特殊攻击
    • 特殊攻击可以导致较大伤害

每一只 Pokemon 都有 Hit Points (HP) ,在战斗中会消耗 HP,当 HP 消耗殆尽时 Pokemon 会晕倒,当所有的 Pokemon 都晕倒时,战斗结束。

From: pokemon help

增援队友道馆

每个道馆根据声望可以容纳1至10个 Pokemon。每个玩家只能放置一个 Pokemon。

从友方道馆训练 Pokemon

通过训练 Pokemon 来获取 XP 并且增加友方道馆的声望。通过友方道馆训练,训练越多,友方道馆声望越高。 友方道馆训练Pokemon 和战斗类似,只是在战斗失败时,Pokemon 不会晕倒(faint)。而是以 1 HP 返回到收集仓。

CP and HP

CP 是 Combat Power 缩写, HP 是 Hit Points 缩写。
所有 Pokemon 在捕获时有满的 HP, 但是在战斗中会消耗 HP。
每一个 Pokemon 在捕获时都有不同的 CP,CP 值决定神奇宝贝在战斗中的表现。
当训练师获取更多 XP,等级提升时,遇到的 Pokemon 能够捕获的 Pokemon CP 值也会相应增高。但是有一些 Pokemon 天然的低 CP。

From:Help

Q & A

什么是 Pokemon Go

Pokemon Go 是一款基于地理现实的手机游戏,由 Niantic,任天堂,The Pokemon Company合作开发发行。

参考:官网

等级的上限?

目前为止并没有看见官方文档有任何描述,不过有如下图:

level

如何快速的定位周围的 Pokemon?

附近的Pokemon 有图鉴可以看到,爪子的数量表示远近。

Pokemon nearby

关于 Pokemon 的稀有程度?

有一幅图完美的解决:

Pokemon where can see

能够孵化的小精灵?

如图:

孵化的小精灵

More

如果当你真的看到这里的时候,想要了解更多可以加入 Pokemon Go 红队的 Telegram

当然如果依然有不知道的内容,官方文档以及Google都会给你完整的答案。如果真的互联网上不存在你所讨论的问题,那去群里确实也只能是最后一个方法了。

更新一点声明,文中所用图片,部分来自 reddit , 部分来自 Telegram , 有一些来源我确实已经无法找到,如果有任何侵权行为,请联系我。


2016-07-10 产品体验 , Ingress , AR , Game , Niantic , Pokemon , PokemonGo

每天学习一个命令:nl 打印行号

nl 是 number lines of files, 命令用来在 linux 系统中打印文件中行号。nl 可以将输出的文件内容自动的加上行号,其默认的结果与 cat -n 有点不太一样, nl 可以将行号做比较多的显示设计,包括位数与是否自动补齐 0 等等的功能。

命令格式

nl 命令将文件内容输出到标准输出,并添加行号。

nl [options] [file]

命令参数:

-b  :指定行号指定的方式,主要有两种:
    -b a :表示不论是否为空行,也同样列出行号(类似 cat -n);
    -b t :如果有空行,空的那一行不要列出行号(默认值);
-n  :列出行号表示的方法,主要有三种:
    -n ln :行号在萤幕的最左方显示;
    -n rn :行号在自己栏位的最右方显示,且不加 0 ;
    -n rz :行号在自己栏位的最右方显示,且加 0 ;

-w   行号栏位的占用的位数
-p 在逻辑定界符处不重新开始计算

基本使用

3.命令功能:

nl 命令读取 File 参数(缺省情况下标准输入),计算输入中的行号,将计算过的行号写入标准输出。 在输出中,nl 命令根据您在命令行中指定的标志来计算左边的行。 输入文本必须写在逻辑页中。每个逻辑页有头、主体和页脚节(可以有空节)。 除非使用 -p 标志,nl 命令在每个逻辑页开始的地方重新设置行号。 可以单独为头、主体和页脚节设置行计算标志(例如,头和页脚行可以被计算然而文本行不能)。

使用实例

用 nl 列出文件的内容

nl /etc/passwd

说明:

文件中的空白行,nl 不会加上行号

用 nl 列出文件内容空行也加上行号

nl -b a /etc/passwd

让行号前面自动补上 0 统一输出格式

-w 参数是 number width 数字宽度,如果指定为 3 则是补齐三位

nl -b a -n rz file.log
nl -b a -n rz -w 3 log2014.log

说明:

nl -b a -n rz 命令行号默认为六位,要调整位数可以加上参数 -w 3 调整为 3 位。


2016-07-10 linux , nl , command

电子书

最近文章

  • The Silver Searcher 使用 Once I wrote a post about ack-grep, and Franklin Yu left a comment about the silver searcher. He said this tool is much faster than the ack-grep. So this post is about The Silver Searcher. As always, I will introduce what is the Silver Searcher, how to install and use it.
  • Maven 多仓库和镜像配置 多仓库配置 设置多仓库有两种方法,第一种直接在项目 POM 中定义
  • Asciidoctor Maven Plugin 使用 Asciidoctor Maven Plugin 这一款 maven 插件可以使用 Asciidoctor 将 AsciiDoc 文档转变成可读文档。
  • 威联通折腾篇十二:verysync 微力同步 很早之前有一款文件同步工具叫做 BtSync,后来改名字叫做 Resilio Sync,但是因为他太强大可以用来干很多坏事,比如曝光政府秘密文件啦,传播盗版啦,所以早早的就封了。墙内几乎是变的不可用了,所以后来有人(不太清楚是谁)搞了这个微力同步,可以支持多平台同步文件,速度快,点对点传输,并且支持的平台非常多,桌面版,移动端自不必说,NAS,路由器甚至也可以安装。
  • 网站推荐之 usesthis.com 在一篇效率工具的文章中偶然获知此网站,就像其网站写的标语那样: