coolEx

Today will be better

gentoo vlc qt5环境下编译失败

之前在虚拟机里面安装了个gentoo,用来尝试安装kde5。在升级系统的时候,发现vlc一直编译失败(好像是phonon引入的)。查了下发现是vlc在编译的时候,发现了qt5,但是按照qt4的方式编译了,导致自身图形界面相关的类编译失败。
编译失败的信息:

1
2
3
4
5
6
7
make[6]: Entering directory '/var/tmp/portage/media-video/vlc-2.1.4/work/vlc-2.1.4/modules/gui/qt4'
../../../doltlibtool  --tag=CXX   --mode=compile x86_64-pc-linux-gnu-g++ -DHAVE_CONFIG_H -I. -I../../..  -DMODULE_NAME=$(p="libqt4_plugin_la-qt4.lo"; p="${p##*/}"; p="${p#lib}"; echo "${p%_plugin*}") -DMODULE_NAME_IS_$(p="libqt4_plugin_la-qt4.lo"; p="${p##*/}"; p="${p#lib}"; echo "${p%_plugin*}") -DMODULE_STRING=\"$(p="libqt4_plugin_la-qt4.lo"; p="${p##*/}"; p="${p#lib}"; echo "${p%_plugin*}")\" -D__PLUGIN__  -I../../../include -I../../../include  -I/usr/include/samba-4.0  -DQT_SHARED -I/usr/include/qt4 -I/usr/include/qt4/QtGui -I/usr/include/qt4 -I/usr/include/qt4/QtCore   -O2 -mprefer-avx128 -fomit-frame-pointer -pipe -march=bdver2 -mmmx -mno-3dnow -msse -msse2 -msse3 -mssse3 -msse4a -mcx16 -msahf -mno-movbe -maes -mno-sha -mpclmul -mpopcnt -mabm -mlwp -mfma -mfma4 -mxop -mbmi -mno-bmi2 -mtbm -mavx -mno-avx2 -msse4.2 -msse4.1 -mlzcnt -mno-rtm -mno-hle -mno-rdrnd -mf16c -mno-fsgsbase -mno-rdseed -mprfchw -mno-adx -mfxsr -mxsave -mno-xsaveopt -mno-avx512f -mno-avx512er -mno-avx512cd -mno-avx512pf -mno-prefetchwt1 --param l1-cache-size=16 --param l1-cache-line-size=64 --param l2-cache-size=2048 -mtune=bdver2 -fstack-protector-strong -Wall -Wextra -Wsign-compare -Wundef -Wpointer-arith -Wvolatile-register-var -fvisibility=hidden -c -o libqt4_plugin_la-qt4.lo `test -f 'qt4.cpp' || echo './'`qt4.cpp
In file included from dialogs/open.hpp:35:0,
                 from dialogs_provider.hpp:36,
                 from qt4.cpp:36:
./ui/open.h:14:29: fatal error: QtWidgets/QAction: No such file or directory
 #include <QtWidgets/QAction>

参考vlcgentoo的bug,发现已经有人提交了一个补丁。具体补丁文件在:这里

[redis设计与实现][3]基本数据结构——字典

Redis字典采用哈希表实现。
哈希表:

1
2
3
4
5
6
7
8
9
10
typedef struct dictht {
//哈希表数组
    dictEntry **table;
//哈希表大小
    unsigned long size;
//哈希表掩码,用于计算索引值,总是等于size - 1
    unsigned long size mask;
//已有的节点数量
    unsigned long used;
} dictht;

哈希表节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct dictEntry {
//键
    void *key;
//值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
//下一个哈希表节点
    struct dictEntry *next;
} dictEntry;

字典:

1
2
3
4
5
6
7
8
9
10
typedef struct dict {
//类型特定的函数
    dictType *type;
//私有数据
    void *privdata;
//哈希表
    dictht ht[2];
    long rehashidx; /* rehashing not in progress if rehashidx == -1 */
    int iterators; /* number of iterators currently running */
} dict;

* type属性是一个指向dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数
* privdata保存了需要传给类型特定函数的可选参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct dictType {
//计算哈希值的函数
    unsigned int (*hashFunction)(const void *key);
//复制键的函数
    void *(*keyDup)(void *privdata, const void *key);
//复制值的函数
    void *(*valDup)(void *privdata, const void *obj);
//对比键的函数
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);
//销毁键的函数
    void (*keyDestructor)(void *privdata, void *key);
//销毁值的函数
    void (*valDestructor)(void *privdata, void *obj);
} dictType;

ht属性包含两个项的数组。一般情况下只使用ht[0]哈希表,ht[1]哈希表只在对ht[0]进行rehash的时候才会使用。

哈希算法:
计算:
hash = dict->type->hashFunction(key)
index = hash & dict->ht[x].sizemask
当字典被用作数据库的底层实现,或者哈希键的底层实现时,Redis使用MurmurHash2(https://code.google.com/p/smhasher/wiki/MurmurHash2)算法计算键的哈希值。

int dictAdd(dict *d, void *key, void *val);

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
int dictAdd(dict *d, void *key, void *val)
{
    dictEntry *entry = dictAddRaw(d,key);

    if (!entry) return DICT_ERR;
    dictSetVal(d, entry, val);
    return DICT_OK;
}
dictEntry *dictAddRaw(dict *d, void *key)
{
    int index;
    dictEntry *entry;
    dictht *ht;
//#define dictIsRehashing(d) ((d)->rehashidx != -1)
    if (dictIsRehashing(d)) _dictRehashStep(d);

    /* Get the index of the new element, or -1 if
     * the element already exists. */

    if ((index = _dictKeyIndex(d, key)) == -1)
        return NULL;

    /* Allocate the memory and store the new entry */
    ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
    entry = zmalloc(sizeof(*entry));
    entry->next = ht->table[index];
    ht->table[index] = entry;
    ht->used++;

    /* Set the hash entry fields. */
    dictSetKey(d, entry, key);
    return entry;
}
static int _dictKeyIndex(dict *d, const void *key)
{
    unsigned int h, idx, table;
    dictEntry *he;

    /* Expand the hash table if needed */
    if (_dictExpandIfNeeded(d) == DICT_ERR)
        return -1;
    /* Compute the key hash value */
//#define dictHashKey(d, key) (d)->type->hashFunction(key)
    h = dictHashKey(d, key);
    for (table = 0; table <= 1; table++) {
        idx = h & d->ht[table].sizemask;
        /* Search if this slot does not already contain the given key */
        he = d->ht[table].table[idx];
        while(he) {
//比较key是否已经存在,已经存在返回-1
            if (dictCompareKeys(d, key, he->key))
                return -1;
            he = he->next;
        }
        if (!dictIsRehashing(d)) break;
    }
    return idx;
}
static int _dictExpandIfNeeded(dict *d)
{
    /* Incremental rehashing already in progress. Return. */
    if (dictIsRehashing(d)) return DICT_OK;

    /* If the hash table is empty expand it to the initial size. */
//#define DICT_HT_INITIAL_SIZE     4
    if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);

    /* If we reached the 1:1 ratio, and we are allowed to resize the hash
     * table (global setting) or we should avoid it but the ratio between
     * elements/buckets is over the "safe" threshold, we resize doubling
     * the number of buckets. */

//static unsigned int dict_force_resize_ratio = 5;
/*
dict_can_resize设置:
void updateDictResizePolicy(void) {
    if (server.rdb_child_pid == -1 && server.aof_child_pid == -1)
        dictEnableResize();
    else
        dictDisableResize();
}
当有同步硬盘进程的时候改成不能扩充
*/

    if (d->ht[0].used >= d->ht[0].size &&
        (dict_can_resize ||
         d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))
    {
        return dictExpand(d, d->ht[0].used*2);
    }
    return DICT_OK;
}
int dictExpand(dict *d, unsigned long size)
{
    dictht n; /* the new hash table */
    unsigned long realsize = _dictNextPower(size);

    /* the size is invalid if it is smaller than the number of
     * elements already inside the hash table */

    if (dictIsRehashing(d) || d->ht[0].used > size)
        return DICT_ERR;

    /* Allocate the new hash table and initialize all pointers to NULL */
    n.size = realsize;
    n.sizemask = realsize-1;
    n.table = zcalloc(realsize*sizeof(dictEntry*));
    n.used = 0;

    /* Is this the first initialization? If so it's not really a rehashing
     * we just set the first hash table so that it can accept keys. */

    if (d->ht[0].table == NULL) {
        d->ht[0] = n;
        return DICT_OK;
    }

    /* Prepare a second hash table for incremental rehashing */
    d->ht[1] = n;
    d->rehashidx = 0;
    return DICT_OK;
}
int dictReplace(dict *d, void *key, void *val);

/* Add an element, discarding the old if the key already exists.
 * Return 1 if the key was added from scratch, 0 if there was already an
 * element with such key and dictReplace() just performed a value update
 * operation. */

int dictReplace(dict *d, void *key, void *val)
{
    dictEntry *entry, auxentry;

    /* Try to add the element. If the key
     * does not exists dictAdd will suceed. */

    if (dictAdd(d, key, val) == DICT_OK)
        return 1;
    /* It already exists, get the entry */
    entry = dictFind(d, key);
    /* Set the new value and free the old one. Note that it is important
     * to do that in this order, as the value may just be exactly the same
     * as the previous one. In this context, think to reference counting,
     * you want to increment (set), and then decrement (free), and not the
     * reverse. */

    auxentry = *entry;
    dictSetVal(d, entry, val);
    dictFreeVal(d, &auxentry);
    return 0;
}
dictEntry *dictFind(dict *d, const void *key)
{
    dictEntry *he;
    unsigned int h, idx, table;

    if (d->ht[0].size == 0) return NULL; /* We don't have a table at all */
    if (dictIsRehashing(d)) _dictRehashStep(d);
    h = dictHashKey(d, key);
    for (table = 0; table <= 1; table++) {
        idx = h & d->ht[table].sizemask;
        he = d->ht[table].table[idx];
        while(he) {
            if (dictCompareKeys(d, key, he->key))
                return he;
            he = he->next;
        }
        if (!dictIsRehashing(d)) return NULL;
    }
    return NULL;
}

int dictRehash(dict *d, int n);

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
int dictRehash(dict *d, int n) {
    if (!dictIsRehashing(d)) return 0;

    while(n--) {
        dictEntry *de, *nextde;

        /* Check if we already rehashed the whole table... */
//已经完成hash,释放ht[0]。将ht[0]指向ht[1]
        if (d->ht[0].used == 0) {
            zfree(d->ht[0].table);
            d->ht[0] = d->ht[1];
            _dictReset(&d->ht[1]);
            d->rehashidx = -1;
            return 0;
        }

        /* Note that rehashidx can't overflow as we are sure there are more
         * elements because ht[0].used != 0 */

        assert(d->ht[0].size > (unsigned long)d->rehashed);
//如果rehash索引为空,跳过
        while(d->ht[0].table[d->rehashidx] == NULL) d->rehashidx++;
        de = d->ht[0].table[d->rehashidx];
        /* Move all the keys in this bucket from the old to the new hash HT */
//移动一个桶里面的所有key到新的哈希表
        while(de) {
            unsigned int h;

            nextde = de->next;
            /* Get the index in the new hash table */
            h = dictHashKey(d, de->key) & d->ht[1].sizemask;
            de->next = d->ht[1].table[h];
            d->ht[1].table[h] = de;
            d->ht[0].used--;
            d->ht[1].used++;
            de = nextde;
        }
        d->ht[0].table[d->rehashidx] = NULL;
        d->rehashidx++;
    }
    return 1;
}

//为了防止占用太多的CPU时间,限制一次rehash的CPU时间
int dictRehashMilliseconds(dict *d, int ms) {
    long long start = timeInMilliseconds();
    int rehashes = 0;

    while(dictRehash(d,100)) {
        rehashes += 100;
        if (timeInMilliseconds()-start > ms) break;
    }
    return rehashes;
}

调用者(redis.c):每次尝试渐进式rehash执行1ms

1
2
3
4
5
6
7
8
9
10
11
12
13
int incrementallyRehash(int dbid) {
    /* Keys dictionary */
    if (dictIsRehashing(server.db[dbid].dict)) {
        dictRehashMilliseconds(server.db[dbid].dict,1);
        return 1; /* already used our millisecond for this loop... */
    }
    /* Expires */
    if (dictIsRehashing(server.db[dbid].expires)) {
        dictRehashMilliseconds(server.db[dbid].expires,1);
        return 1; /* already used our millisecond for this loop... */
    }
    return 0;
}

[redis设计与实现][2]基本数据结构——链表

链表在Redis中的应用:

* 列表键
* 发布与订阅
* 慢查询
* 监视器等

链表节点:

1
2
3
4
5
typedef struct listNode {
    struct listNode *prev;
    struct listNode *next;
    void *value;
} listNode;

链表结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct list {
//头结点
    listNode *head;
//尾节点
    listNode *tail;
//节点复制函数
    void *(*dup)(void *ptr);
//节点释放函数
    void (*free)(void *ptr);
//节点对比函数
    int (*match)(void *ptr, void *key);
//链表长度
    unsigned long len;
} list;

Redis链表特性:

* 双向链表:prev,next指针
* 无环:终点节点指向NULL
* 带表头指针和表尾指针
* 带链表长度计数器
* 多态:数据采用void*指针,通过三个函数指针,来操作不同类型的值

[redis设计与实现][1]基本数据结构——sds

SDS(Simple Dynamic String):对C字符串的封装,可修改、可自动伸缩的字符串实现。Redis默认的字符串实现。

SDS定义:(sds.h)

1
2
3
4
5
struct sdshdr {
unsigned int len;
unsigned int free;
char buf[];
};

与C字符串的区别:

* 常数复杂度获取字符串长度(字符串长度已经记录在结构体中)
* 杜绝缓冲区溢出(每次操作前都会检查空间是否充足,自动扩张和收缩)
* 减少修改字符串带来的内存重分配次数:
*
* 空间预分配(提前预留空间)
* 惰性空间释放(释放的空间暂时保留,防止扩张)
* 二进制安全(不采用\0表示结束)
* 兼容部分C字符串函数(buf数组多保存了一个\0,用于兼容部分C字符串函数)

API:
typedef char *sds;
创建一个字符串:sds sdsnew(const char *init);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
sds sdsnew(const char *init) {
size_t initlen = (init == NULL) ? 0 : strlen(init);
return sdsnewlen(init, initlen);
}
sds sdsnewlen(const void *init, size_t initlen) {
struct sdshdr *sh;
//根据sdshdr结构分配内存,多一个用来放\0
if (init) {
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
} else {
sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
}
if (sh == NULL) return NULL;
sh->len = initlen;
sh->free = 0;
//初始字符串不为NULL,复制过去,然后最后补上\0
if (initlen && init)
memcpy(sh->buf, init, initlen);
sh->buf[initlen] = '\0';
return (char*)sh->buf;
}

拼接字符串:sds sdscat(sds s, const char *t);

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
sds sdscat(sds s, const char *t) {
return sdscatlen(s, t, strlen(t));
}
sds sdscatlen(sds s, const void *t, size_t len) {
struct sdshdr *sh;
size_t curlen = sdslen(s);

s = sdsMakeRoomFor(s,len);
if (s == NULL) return NULL;
sh = (void*) (s-(sizeof(struct sdshdr)));
memcpy(s+curlen, t, len);
sh->len = curlen+len;
sh->free = sh->free-len;
s[curlen+len] = '\0';
return s;
}
sds sdsMakeRoomFor(sds s, size_t addlen) {
struct sdshdr *sh, *newsh;
size_t free = sdsavail(s);
size_t len, newlen;

//仍然有空闲,直接返回
if (free >= addlen) return s;
len = sdslen(s);
sh = (void*) (s-(sizeof(struct sdshdr)));
newlen = (len+addlen);
//新的空间比最大分配空间小,扩容两倍
//#define SDS_MAX_PREALLOC (1024*1024)
if (newlen < SDS_MAX_PREALLOC) newlen *= 2; else newlen += SDS_MAX_PREALLOC; //重新分配空间:sdshdr+字符串长度+1(\0) newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1); if (newsh == NULL) return NULL; newsh->free = newlen - len;
return newsh->buf;
}
static inline size_t sdsavail(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
return sh->free;
}

mac gentoo-prefix安装git svn

之前参照yegal的文章在mac上安装了gentoo-prefix。但是在emerge git的时候,会发现如果增加了subversion这个USE,就会编译失败。

从编译失败的错误上,可以看出,编译失败的来源是svn相关的代码,然后错误是链接的时候提示一些符号找不到:

1
2
3
4
5
6
7
8
Undefined symbols for architecture x86_64:
  "_libintl_ngettext", referenced from:
      _show_date_relative in libgit.a(date.o)
  "_libintl_gettext", referenced from:
      _show_date_relative in libgit.a(date.o)
      _warn_on_inaccessible in libgit.a(wrapper.o)
      _xgetpwuid_self in libgit.a(wrapper.o)
ld: symbol(s) not found for architecture x86_64

大致可以看出,是intl相关的库没有链接。在gentoo的bugzilla上也查到了类似的bug。按照附件提供的补丁,需要判断当前系统为mac的时候,增加-lintl,以链接intl这个库。

除了这个库之外,还有一个iconv相关的符号找不到。bug里面没有描述。在另一台gentoo的机器上用e-file查询了之后,发现原生linux的iconv是由glibc提供的。但是prefix是不能自由安装glibc的库的。但是系统里面已经安装了dev-libs/libiconv这个包,提供了iconv相关的库。因此和前面一样,需要手工在链接的时候增加-liconv。

最后修改完的ebuild文件大致为:

1
2
3
4
5
6
7
8
9
10
--- git-1.9.2.ebuild.old    2014-04-20 15:10:34.000000000 +0800
+++ git-1.9.2.ebuild    2014-04-20 15:09:54.000000000 +0800
@@ -324,6 +324,7 @@
 
    if use subversion ; then
        cd "${S}"/contrib/svn-fe
+       [[ ${CHOST} = *-darwin* ]] && EXTLIBS="${EXTLIBS} -lintl -liconv"
        git_emake EXTLIBS="${EXTLIBS}" || die "emake svn-fe failed"
        if use doc ; then
            git_emake svn-fe.{1,html} || die "emake svn-fe.1 svn-fe.html failed"

redis误同步恢复

redis主备同步非常方便,通过slaveof命令即可同步。上周应用切换了redis,想让新的redis和旧的redis进行数据同步,结果把新redis中其他应用已经写入的所有数据都删除了。。。这里记录下恢复方法。

首先先恢复redis的dump文件。还好现在阿里云弹性计算集群给每个镜像每天都进行了备份,通过操作前一天的备份镜像,快照制作当时的整个redis目录复制了出来,作为恢复的基础。

然后将恢复回来的文件再复制一份备份,原先那份修改redis的配置文件,修改启动端口(我从默认的6379改成了6479),然后启动旧的redis,成功将当时的dump文件载入到新的redis实例中。

查询到redis有migrate命令,能够将key迁移到另外一个redis实例中(具体参考这里)。通过bash命令,循环的将备份的实例所有key,都试图通过migrate命令迁移到新的redis中。

迁移过程中,迁移成功的key,在旧的实例中会被删除,失败的key,可以看见失败原因都是key is busy。也就是说,要迁移的key在新的实例中已经存在了。

和使用方确定了所有已经存在的key,是hash类型的,都是要保留的数据。因此,通过一个简单的shell脚本,读取备份实例中hash类型key,并添加到新的redis中。bash脚本为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash
OLD_REDIS="./redis-cli -p 6479"
NEW_REDIS="./redis-cli"

KEYS=`$OLD_REDIS keys '*'`

for k in $KEYS
do
        key_type=`$OLD_REDIS type $k`
        if [ $key_type = "hash" ]; then
                hash_keys=`$OLD_REDIS HKEYS $k`
                for hash_key in $hash_keys
                do
                        hash_value=`$OLD_REDIS HGET $k $hash_key`
                        $NEW_REDIS HSET $k $hash_key $hash_value
                        echo "merge $k $hash_key to new redis"
                done
                #eval "$OLD_REDIS DEL $k"
        fi
done

逻辑非常简单,就是先通过keys命令获取到所有迁移失败的key,然后通过type命令获取类型。如果是hash类型的key,遍历所有的hash key,然后读取出一个key下所有的键值对,并通过hset命令放到新的redis实例中。最后将合并完成的key从备份实例中删除。这里由于业务上只需要合并hash类型的,其他容器类型(list, set等)也可以通过类似的方式做新老合并。

chrome插件远程加载js

chrome插件将js直接注入页面有两种方式,一种是通过Manifest文件中指定js文件,一种是通过chrome.tab.executeScript方式注入。具体可以参考这个官方文档

由于各种需求,需要将部分js从后端服务器中进行加载。所以只能通过tab.executeScript的方式。具体函数的定义,可以参考官方文档。大致就是能够给定一个文件或者一段代码,在指定的tab中运行。

首先,这个函数必须在background中执行,页面中的content-script本身,是没有权限调用chrome.tab api的。所以,如果页面中的js要执行,需要通过chrome插件的通信机制(port, message),通知background才能运行。

其次,运行的目标tab,可以指定,也可以传null,如果目标tab为null,则表示需要执行的tab是“当前tab”。所以如果使用了null,则在注入前千万不能换tab,否则就注入到其他tab中去了。

然后,参数InjectDetails中可以指定是执行code,还是file。如果是file,可以是本地文件(插件中打包的文件),也可以是远程文件。特别注意,通过file执行远程文件,有着严格的约束(参照这里):‘Currently, we allow whitelisting origins with the following schemes: HTTPS, chrome-extension, and chrome-extension-resource.’也就是说,只能执行插件内部打包的文件,或者基于https的远程地址。其他还有例外的就是给开发者用的localhost了。我们要加载的资源,不可能放在本地,也不可能打包到插件中,也没有办法去搞到https的证书。所以不能通过执行file的方式加载远程js。

要解决这个问题,只能通过执行code的方式。由于我们在manifest文件中配置了background拥有跨域请求资源的权限,我们可以直接在background中,远程通过xhr的方式获取js内容,然后进行code的执行。这样就能绕开之前遇到的无法执行http协议的js文件的问题。大致的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
    if (xhr.readyState == 4 && xhr.status == 200) {
        var code = xhr.responseText;
        chrome.tabs.executeScript(null, {code: code, allFrames: true}, function(){
            if(typeof callback === 'function') {
                callback();
            }
        });
    }
}
var ts = new Date().getTime();
var u;
if(url.indexOf('?') === -1){
    u = url + '?_t=' + ts;
} else {
    u = url + '&_t=' + ts;
}
xhr.open('GET',u,true);
xhr.send(null);

最后,还有一个小问题,就是InjectDetails中的allFrames参数。这个参数大致的作用,就是如果为true,js会在页面的所有frame中进行执行,否则就只在页面的top frame中进行执行。由于frame之间的document会互相隔离,所有如果要让frame之间能够通信,就需要设置成true,把代码注入到所有的frame中去。

boost spirit的简单使用

尝试通过cpp-netlib来做http服务器,但是这个库只能简单的解析http结构,像cookie等结构,都要自己解析,了解到spirit可以通过类似bnf范式格式定义字符串格式并解析。

boost本身有个类似的例子,解析的是通过分号或者&符号分割的键值对字符串,并放到对应的map中去。具体代码可以参照这里。所以基于这个代码,简单的进行修改之后,就能解析http cookie了。

首先,http cookie的格式,定义在rfc6265上。这里定义了服务器发送给浏览器的Set-Cookie头格式,和浏览器发给服务器的Cookie头的BNF范式。这里定义的太复杂,解析的时候没有考虑到这么多字符(特别是排除一些控制字符),大致的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
namespace parser
{
    namespace qi = boost::spirit::qi;
    typedef std::map<std::string, std::string> pairs_type;
   
    template <typename Iterator>
    struct cooke_sequence : qi::grammar<Iterator, pairs_type()> {
        qi::rule<Iterator, pairs_type()> query;
        qi::rule<Iterator, std::pair<std::string, std::string>()> pair;
        qi::rule<Iterator, std::string()> key, value;
       
        cooke_sequence() : cooke_sequence::base_type(query) {
            query =  pair >> *(qi::lit(';') >> pair);
            pair  =  *qi::lit(' ') >> key >> *qi::lit(' ') >> -('=' >> *qi::lit(' ') >> value >> *qi::lit(' '));
            key   =  qi::char_("a-zA-Z_%") >> *qi::char_("a-zA-Z_0-9%");
            value = +(qi::char_ - ';');
        }
    };
}

这里简化了key和value,特别是value,只要是非分号的,都能解析到value中。使用也非常简单:

1
2
3
4
5
6
7
8
namespace qi = boost::spirit::qi;
parser::cooke_sequence<std::string::const_iterator> p;
parser::pairs_type value;
if(qi::parse(c.begin(), c.end(), p, value)) {
    for(auto &entry : value) {
        cookies.insert(std::make_pair(entry.first, entry.second));
    }
}

直接实例化cooe_sequence,将cookie字符串传入,就可以解析成map,然后再放入到自己的结构体中。这里直接抄了示例中的代码,所以直接使用了map,一般cookie没必要排序,可以直接使用unordered_map,通过hash表存放。

minidlna支持共享rmvb文件

之前将盒子作为下载机,为了能够直接播放上面的电影和电视,就通过minidlna,将视频共享出来,这样能够支持通过nexus 10平板播放这些电影。但是,即使升级到了1.1.0版本,minidlna还是不能将设置的视频目录中的rmvb文件共享出来。

参照网上搜索到的让 minidlna 支持 rmvb、gb2312 mp3 标签这篇文章,根据里面的patch,修改了1.1.0版本的minidlna之后,可以在平板上看见rmvb文件了。

由于前文提到的补丁,是针对1.0版本的minidlna,参照补丁修改的位置,自己对补丁进行了修改,重新写了一个ebuild文件之后,搞定了。新的补丁文件:

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
diff -ur minidlna-1.1.0/metadata.c minidlna-1.1.0.new/metadata.c
--- minidlna-1.1.0/metadata.c   2013-03-09 08:03:03.000000000 +0800
+++ minidlna-1.1.0.new/metadata.c       2013-09-07 20:19:31.700278182 +0800
@@ -903,6 +903,8 @@
                        xasprintf(&m.mime, "video/x-matroska");
                else if( strcmp(ctx->iformat->name, "flv") == 0 )
                        xasprintf(&m.mime, "video/x-flv");
+               else if( strcmp(ctx->iformat->name, "rm") == 0 )
+                       asprintf(&m.mime, "video/x-pn-realvideo");
                if( m.mime )
                        goto video_no_dlna;
 
diff -ur minidlna-1.1.0/upnpglobalvars.h minidlna-1.1.0.new/upnpglobalvars.h
--- minidlna-1.1.0/upnpglobalvars.h     2013-04-05 07:39:12.000000000 +0800
+++ minidlna-1.1.0.new/upnpglobalvars.h 2013-09-07 20:21:40.564283420 +0800
@@ -168,7 +168,8 @@
        "http-get:*:audio/mp4:*," \
        "http-get:*:audio/x-wav:*," \
        "http-get:*:audio/x-flac:*," \
-       "http-get:*:application/ogg:*"
+       "http-get:*:application/ogg:*,"\
+       "http-get:*:video/x-pn-realvideo:*"
                                                                                                                                                                                   
 #define DLNA_FLAG_DLNA_V1_5      0x00100000                                                                                                                                        
 #define DLNA_FLAG_HTTP_STALLING  0x00200000                                                                                                                                        
diff -ur minidlna-1.1.0/utils.c minidlna-1.1.0.new/utils.c                                                                                                                          
--- minidlna-1.1.0/utils.c      2013-04-03 07:29:21.000000000 +0800                                                                                                                
+++ minidlna-1.1.0.new/utils.c  2013-09-07 20:18:40.796283001 +0800                                                                                                                
@@ -375,6 +375,7 @@                                                                                                                                                                
                ends_with(file, ".m2t") || ends_with(file, ".mkv")   ||                                                                                                            
                ends_with(file, ".vob") || ends_with(file, ".ts")    ||                                                                                                            
                ends_with(file, ".flv") || ends_with(file, ".xvid")  ||                                                                                                            
+               ends_with(file, ".rm")  || ends_with(file, ".rmvb")  ||                                                                                                            
 #ifdef TIVO_SUPPORT
                ends_with(file, ".TiVo") ||
 #endif

ebuild文件就是在src_prepare阶段,增加了:epatch “${FILESDIR}”/${PN}-1.1.0-rmvb.patch,打上这个补丁即可。

webtail——websocket

前面介绍了服务器端如何监听和增量读取文件,这里通过基于boost asio的websocketpp,实现了一个简单的websocket服务端,能够和浏览器进行通信,将读取到的文件通过websocket协议进行实时传送。

关于websocket的简单介绍,可以参考维基百科。websocket协议,在rfc6455中定义。

websocketpp对websocket和简单的http都进行了比较好的封装,只要实现几个handler,就可以完成对连接、消息等的操作和控制。主要需要处理的,可能有以下的handler:

1
2
3
4
typedef lib::function<void(connection_hdl)> open_handler;
typedef lib::function<void(connection_hdl)> close_handler;
typedef lib::function<void(connection_hdl)> http_handler;
typedef lib::function<void(connection_hdl,message_ptr)> message_handler

分别处理连接创建,连接关闭,http请求和消息请求。其中connection_hdl是连接的weak_ptr。

这里对websocket使用很简单,唯一的需求,就是维护已经建立的连接(既创建连接的时候记录,关闭连接的时候移出),然后通过将自己的回调注册到文件监控类中,实时的将消息推送到websocket客户端。

首先,维护一个set,用来保存当前已经建立的所有连接:

1
typedef std::set<websocketpp::connection_hdl,boost::owner_less<websocketpp::connection_hdl> > ConnectionSet;

然后在建立连接的时候插入到这个set中:

1
2
3
4
5
void WebSocketServer::onOpen ( websocketpp::connection_hdl hdl )
{
    boost::lock_guard<boost::mutex> lock(_mutex);
    _conns.insert(hdl);
}

在连接关闭的时候,移除连接:

1
2
3
4
5
void WebSocketServer::onClose ( websocketpp::connection_hdl hdl )
{
    boost::lock_guard<boost::mutex> lock(_mutex);
    _conns.erase(hdl);
}

另外,定义一个让filewatcher调用的回调:

1
2
3
4
5
6
7
8
void WebSocketServer::write ( const std::string& content )
{
    boost::lock_guard<boost::mutex> lock(_mutex);
    BOOST_AUTO(it, _conns.begin());
    for(; it != _conns.end(); ++it) {
        _s.send(*it, content, websocketpp::frame::opcode::text);
    }
}

这个回调很简单,就是当有读取到内容的时候,遍历连接集合,向每个连接发送具体的内容。

最后,为了方便用户访问,当发现是标准http请求的时候,我们返回一个简单的html,用于显示和建立websocket连接。如果不指定具体的http_handler,websocketpp在发现请求是http的时候,会返回http的426错误(Upgrade Required)。

1
2
3
4
5
6
7
void WebSocketServer::httpHandler ( websocketpp::connection_hdl hdl )
{
    server::connection_ptr connPtr = _s.get_con_from_hdl(hdl);;
   
    connPtr->set_status(websocketpp::http::status_code::ok);
    connPtr->set_body(htmlContent);
}

另外,还有一个小坑。按照websocketpp的example,在启动的时候都是直接调用server的listen函数,而且使用的都是只有端口号的那个实现。实际使用过程中,发现这个只有端口号的实现,直接使用了ipv6协议。虽说如果本机同时支持ipv6和ipv4的情况下,两个协议对应的端口都会监听,但是遇到了服务器上关闭了ipv6,会导致boost asio抛出address_family_not_supported异常,导致应用被迫退出。为了兼容这种方式,对这个异常进行了抓取,重新降级尝试ipv4协议,这样能够很好的在只有ipv4的服务器上进行使用。

1
2
3
4
5
6
7
8
9
try{
    _s.listen(port);
} catch(boost::system::system_error const& e) {
    if(e.code() == boost::asio::error::address_family_not_supported) {
        _s.listen(boost::asio::ip::tcp::v4(), port);
    }
} catch (...) {
    throw;
}