文档学习

有中文看中文 :-)

入门

先看 node等版本

node --version
v11.8.0
npm --version
6.12.0
npx --version
10.2.0

安装

npm install --global gulp-cli

npx mkdirp my-project && cd my-project

npm init && npm install --save-dev gulp

检查版本

gulp --version
CLI version: 2.2.0
Local version: 4.0.2

创建配置 touch gulpfile.js

写入

1
2
3
4
function defaultTask(cb) {
// place code for your default task here
cb();
}

exports.default = defaultTask

执行 gulp完成

任务

查看任务gulp --tasks

多个任务可以通过series()parallel()来合并, 其中series是按顺序串行,parallel是并行,

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const { series } = require('gulp');

// `clean` 函数并未被导出(export),因此被认为是私有任务(private task)。
// 它仍然可以被用在 `series()` 组合中。
function clean(cb) {
// body omitted
cb();
}

// `build` 函数被导出(export)了,因此它是一个公开任务(public task),并且可以被 `gulp` 命令直接调用。
// 它也仍然可以被用在 `series()` 组合中。
function build(cb) {
// body omitted
cb();
}

exports.build = build;
exports.default = series(clean, build);

可以任意深度嵌套!

异步执行

可以返回 stream、promise、event emitter、child process 或 observable ,否则使用callback

stream

1
2
3
4
5
6
7
8
const { src, dest } = require('gulp');

function streamTask() {
return src('*.js')
.pipe(dest('output'));
}

exports.default = streamTask;

promise

1
2
3
4
5
function promiseTask() {
return Promise.resolve('the value is ignored');
}

exports.default = promiseTask;

event emitter

1
2
3
4
5
6
7
8
9
10
const { EventEmitter } = require('events');

function eventEmitterTask() {
const emitter = new EventEmitter();
// Emit has to happen async otherwise gulp isn't listening yet
setTimeout(() => emitter.emit('finish'), 250);
return emitter;
}

exports.default = eventEmitterTask;

child process

1
2
3
4
5
6
7
const { exec } = require('child_process');

function childProcessTask() {
return exec('date');
}

exports.default = childProcessTask;

observable

1
2
3
4
5
6
7
const { Observable } = require('rxjs');

function observableTask() {
return Observable.of(1, 2, 3);
}

exports.default = observableTask;

callback

1
2
3
4
5
6
function callbackError(cb) {
// `cb()` should be called by some async work
cb(new Error('kaboom'));
}

exports.default = callbackError;

gulp 不再支持同步任务(Synchronous tasks)了。因为同步任务常常会导致难以调试的细微错误,例如忘记从任务(task)中返回 stream。

流Stream

常见流使用:src,dest,pipe()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const { src, dest } = require('gulp');
const babel = require('gulp-babel');
const uglify = require('gulp-uglify');
const rename = require('gulp-rename');

exports.default = function() {
return src('src/*.js')
.pipe(babel())
.pipe(src('vendor/*.js'))
.pipe(dest('output/'))
.pipe(uglify())
.pipe(rename({ extname: '.min.js' }))
.pipe(dest('output/'));
}

Glob 详解

glob 是由普通字符和/或通配字符组成的字符串,用于匹配文件路径。可以利用一个或多个 glob 在文件系统中定位文件。

至少一个匹配项目

分隔符永远是/不分系统

\\用于转译,如 \\*表示普通的星号 不是通配符

避免使用path, __dirname,__filename来创建glob

单个星号 不能匹配层级文件夹

两个星号 能匹配任意层级文件夹

取反!

['script/**/*.js', '!scripts/vendor/', 'scripts/vendor/react.js']

['**/*.js', '!node_modules/']

注意书写 会影响执行速度

插件 plugins

https://gulpjs.com/plugins/

实质是流转换器 插件应当总是用来转换文件的。其他操作都应该使用(非插件的) Node 模块或库来实现。

条件插件 gulp-if

文件监控 watch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const { watch, series } = require('gulp');

function clean(cb) {
// body omitted
cb();
}

function javascript(cb) {
// body omitted
cb();
}

function css(cb) {
// body omitted
cb();
}

// 可以只关联一个任务
watch('src/*.css', css);
// 或者关联一个任务组合
watch('src/*.js', series(clean, javascript));

指定监控的事件’add’、’addDir’、’change’、’unlink’、’unlinkDir’、’ready’、’error’。此外,还有一个 ‘all’ 事件,它表示除 ‘ready’ 和 ‘error’ 之外的所有事件。

1
2
3
4
5
6
7
const { watch } = require('gulp');

// 所有事件都将被监控
watch('src/*.js', { events: 'all' }, function(cb) {
// body omitted
cb();
});

如需在第一次文件修改之前执行,也就是调用 watch() 之后立即执行,请将 ignoreInitial 参数设置为 false。

watch(...,{ignoreInitial:false},...)

延迟 { delay: 500 }

API

Vinyl

Vinyl 对象 (the virtual file objects)

文件元数据作为 Node 的 fs.Stats 实例提供。它是 Vinyl 实例的 stat 属性,并在内部用于确定 Vinyl 对象是否表示目录或符号链接(symbolic link)。当写入文件系统时,权限和时间值将从 Vinyl 对象的 stat 属性同步。

src()

dest()

symlink()

lastRun()

series()

parallel()

watch()

task()

registry()

tree()

Vinyl

Vinyl.isVinyl()

Vinyl.isCustomProp()

参考

https://www.gulpjs.com.cn/docs/getting-started/quick-start/

What

因为一些不可描述的原因,dropbox能用但是速度感人

现在需要一款能够 在 android+Linux+iPadOS三端做同步的

iPad上的Documents配上ssh可以完成Linux+iPadOS的同步, // android似乎并不行

google drive就和dropbox是同样的问题

baiduYunPan ??? 如果你不怕你的个人资料有一天变成 “根据相关法律法规” 的话 // 不过值得一提的是 这玩意支持linux了哦 虽然目测是个 浏览器壳 当然也就没有自动备份同步功能

然后搜了搜开源的 ownCloud 和 nextCloud ,看介绍基本是一家的

还有人说 用svn/git来搞,你确定喜欢在手机和ipad上输命令?

开干

client就不说了,各个平台下下来安装就行,说说 server

我目前的电脑环境Ubuntu18.04 x86_64 Linux 4.15.0-65-generic , bash 4.4.20 , 15925MiB

警告第一条命令将会进入root,进入后你将有所有权限,请自重

sudo -s
apt install apache2
service apache2 start
apt install mariadb-server
mysql_secure_installation
apt install php libapache2-mod-php php-mysql php-gd php-json php-mysql php-curl php-mbstring php-intl php-imagick php-xml php-zip
apt install phpmyadmin
ln -s /etc/phpmyadmin/apache.conf /etc/apache2/conf-available/phpmyadmin.conf
a2enconf phpmyadmin
a2enmod ssl
a2ensite default-ssl
service apache2 reload
mariadb
MariaDB [(none)]> CREATE DATABASE nextcloud;
MariaDB [(none)]> CREATE USER nextcloud IDENTIFIED BY 'p@ssc0de';
MariaDB [(none)]> grant usage on *.* TO nextcloud@localhost IDENTIFIED BY 'p@ssc0de';
MariaDB [(none)]> GRANT ALL privileges ON nextcloud.* TO nextcloud@localhost;
MariaDB [(none)]> FLUSH PRIVILEGES;

访问 https://localhost/phpmyadmin/

(用户名,密码)=(nextcloud,p@ssc0de)

https://download.nextcloud.com/server/releases/nextcloud-17.0.0.zip下载最新的zip包 解压到 /var/www/html/nextcloud

chown -R www-data:www-data nextcloud/

通过https://localhost/nextcloud即可访问 设置admin 密码,填写刚刚的配置即可

手机连接会无权限,跟着提示修改/var/www/html/nextcloud/config/config.php的配置即可

失败的尝试

下载wget https://raw.githubusercontent.com/nextcloud/vm/master/nextcloud_install_production.sh

然后sudo bash nextcloud_install_production.sh

https://github.com/nextcloud/server 下源码或者下releases

nextCloud 额外的应用

把搜到的release包解压到/var/www/html/nextcloud/apps

index.php/settings/apps中启用

例如

1
2
3
4
5
calendar.tar.gz
checksum.tar.gz
files_readmemd.tar.gz
richdocuments.tar.gz
spreed-7.0.2.tar.gz

参考

https://docs.nextcloud.com/server/17/admin_manual/installation/source_installation.html#example-installation-on-ubuntu-18-04-lts-server

https://github.com/nextcloud/server.git

https://nextcloud.com/install/#instructions-server

https://askubuntu.com/questions/387062/how-to-solve-the-phpmyadmin-not-found-issue-after-upgrading-php-and-apache

https://www.youtube.com/watch?v=QXfsi0pwgYw

总结

现在看来主要问题在于 直接snap的 会和当前本地开得一堆服务或多或少冲突?? 我查运行状态 都是在运行,查端口 没看到监听

另外一个就是 文件夹权限要搞成 www-data:www-data

在一个就是mysql的db和账户创建,只玩过CURD,很久没摸也忘了

客户端口没啥难度

emmmmmmmm 似乎也不能在ipados上搞… 只能网页勉强

Docker

  1. 本地建立文件夹 /data/nextcloudserver/ 给它存东西用
  2. 一条docker命令docker run -d -p 8080:80 -v /data/nextcloudserver/:/var/www/html --name=nextcloudserver nextcloud ,注意docker的习惯冒号左边是宿主机器的东西,右边是容器内的东西,这里映射了端口和磁盘

这样就可以跑了,本地访问都是ui操作,没啥好说的

  1. 配置config/config.php中的trusted_domains (无脑的话root进,或者docker起个busybox连接一下),注意这里的写法是星号来匹配应该192.168.*.*,而不是192.168.0.0/24, 保存就行

或者官方的仓库下

https://github.com/nextcloud/docker/blob/master 文件夹.examples/docker-compose/insecure/mariadb/apache

执行docker-compose up -d

官方仓库自签https 证书

内网要用的话,可以考虑。毕竟没有CA中心,管内网的还是有办法搞你XD

.examples/docker-compose/with-nginx-proxy-self-signed-ssl/mariadb/fpm

sed玩得熟的可以sed替换哈

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
diff --git a/.examples/docker-compose/with-nginx-proxy-self-signed-ssl/mariadb/fpm/db.env b/.examples/docker-compose/with-nginx-proxy-self-signed-ssl/mariadb/fpm/db.env
index a436605..e9872a4 100644
--- a/.examples/docker-compose/with-nginx-proxy-self-signed-ssl/mariadb/fpm/db.env
+++ b/.examples/docker-compose/with-nginx-proxy-self-signed-ssl/mariadb/fpm/db.env
@@ -1,3 +1,3 @@
-MYSQL_PASSWORD=
+MYSQL_PASSWORD=123
MYSQL_DATABASE=nextcloud
MYSQL_USER=nextcloud
diff --git a/.examples/docker-compose/with-nginx-proxy-self-signed-ssl/mariadb/fpm/docker-compose.yml b/.examples/docker-compose/with-nginx-proxy-self-signed-ssl/mariadb/fpm/docker-compose.yml
index 3d60f7e..0d9fa81 100644
--- a/.examples/docker-compose/with-nginx-proxy-self-signed-ssl/mariadb/fpm/docker-compose.yml
+++ b/.examples/docker-compose/with-nginx-proxy-self-signed-ssl/mariadb/fpm/docker-compose.yml
@@ -8,7 +8,7 @@ services:
volumes:
- db:/var/lib/mysql
environment:
- - MYSQL_ROOT_PASSWORD=
+ - MYSQL_ROOT_PASSWORD=123
env_file:
- db.env

@@ -30,7 +30,7 @@ services:
volumes:
- nextcloud:/var/www/html:ro
environment:
- - VIRTUAL_HOST=
+ - VIRTUAL_HOST=nextcloud.cromarmot.com
depends_on:
- app
networks:
@@ -59,11 +59,11 @@ services:
volumes:
- certs:/certs
environment:
- - SSL_SUBJECT=servhostname.local
- - CA_SUBJECT=my@example.com
- - SSL_KEY=/certs/servhostname.local.key
- - SSL_CSR=/certs/servhostname.local.csr
- - SSL_CERT=/certs/servhostname.local.crt
+ - SSL_SUBJECT=nextcloud.cromarmot.com
+ - CA_SUBJECT=cromarmot@example.com
+ - SSL_KEY=/certs/nextcloud.cromarmot.com.key
+ - SSL_CSR=/certs/nextcloud.cromarmot.com.csr
+ - SSL_CERT=/certs/nextcloud.cromarmot.com.crt
networks:
- proxy-tier

然后你需要在要连接的电脑上把hosts文件增加 ip到nextcloud.cromarmot.com 的映射

执行docker-compose up -d

移除

执行docker-compose down

注意,只会移除container和network,不会移除volume, 如果要移除,要手动移除

What

基本讲就是 throttle(截流)和debounce(去抖)

功能都是为了避免快速反复触发

比如 用户快速点击按钮

假设设置为1s

throttle相当于,执行的最小间隔为1s

debounce也是执行最小间隔为1s,但是假设用户持续的一直快速点击,函数不会执行,需要等到最后一次点击后过1s才执行

直接看lodash的

throttle:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function throttle(func, wait, options) {
let leading = true
let trailing = true

if (typeof func !== 'function') {
throw new TypeError('Expected a function')
}
if (isObject(options)) {
leading = 'leading' in options ? !!options.leading : leading
trailing = 'trailing' in options ? !!options.trailing : trailing
}
return debounce(func, wait, {
leading,
trailing,
'maxWait': wait
})
}

export default throttle

debounce:

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
function debounce(func, wait, options) {
let lastArgs,
lastThis,
maxWait,
result,
timerId,
lastCallTime

let lastInvokeTime = 0
let leading = false
let maxing = false
let trailing = true

// Bypass `requestAnimationFrame` by explicitly setting `wait=0`.
const useRAF = (!wait && wait !== 0 && typeof root.requestAnimationFrame === 'function')

if (typeof func !== 'function') {
throw new TypeError('Expected a function')
}
wait = +wait || 0
if (isObject(options)) {
leading = !!options.leading
maxing = 'maxWait' in options
maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait
trailing = 'trailing' in options ? !!options.trailing : trailing
}

function invokeFunc(time) {
const args = lastArgs
const thisArg = lastThis

lastArgs = lastThis = undefined
lastInvokeTime = time
result = func.apply(thisArg, args)
return result
}

function startTimer(pendingFunc, wait) {
if (useRAF) {
root.cancelAnimationFrame(timerId)
return root.requestAnimationFrame(pendingFunc)
}
return setTimeout(pendingFunc, wait)
}

function cancelTimer(id) {
if (useRAF) {
return root.cancelAnimationFrame(id)
}
clearTimeout(id)
}

function leadingEdge(time) {
// Reset any `maxWait` timer.
lastInvokeTime = time
// Start the timer for the trailing edge.
timerId = startTimer(timerExpired, wait)
// Invoke the leading edge.
return leading ? invokeFunc(time) : result
}

function remainingWait(time) {
const timeSinceLastCall = time - lastCallTime
const timeSinceLastInvoke = time - lastInvokeTime
const timeWaiting = wait - timeSinceLastCall

return maxing
? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
: timeWaiting
}

function shouldInvoke(time) {
const timeSinceLastCall = time - lastCallTime
const timeSinceLastInvoke = time - lastInvokeTime

// Either this is the first call, activity has stopped and we're at the
// trailing edge, the system time has gone backwards and we're treating
// it as the trailing edge, or we've hit the `maxWait` limit.
return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
(timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait))
}

function timerExpired() {
const time = Date.now()
if (shouldInvoke(time)) {
return trailingEdge(time)
}
// Restart the timer.
timerId = startTimer(timerExpired, remainingWait(time))
}

function trailingEdge(time) {
timerId = undefined

// Only invoke if we have `lastArgs` which means `func` has been
// debounced at least once.
if (trailing && lastArgs) {
return invokeFunc(time)
}
lastArgs = lastThis = undefined
return result
}

function cancel() {
if (timerId !== undefined) {
cancelTimer(timerId)
}
lastInvokeTime = 0
lastArgs = lastCallTime = lastThis = timerId = undefined
}

function flush() {
return timerId === undefined ? result : trailingEdge(Date.now())
}

function pending() {
return timerId !== undefined
}

function debounced(...args) {
const time = Date.now()
const isInvoking = shouldInvoke(time)

lastArgs = args
lastThis = this
lastCallTime = time

if (isInvoking) {
if (timerId === undefined) {
return leadingEdge(lastCallTime)
}
if (maxing) {
// Handle invocations in a tight loop.
timerId = startTimer(timerExpired, wait)
return invokeFunc(lastCallTime)
}
}
if (timerId === undefined) {
timerId = startTimer(timerExpired, wait)
}
return result
}
debounced.cancel = cancel
debounced.flush = flush
debounced.pending = pending
return debounced
}

export default debounce

直接的参数区别

1
2
3
4
5
6
7
8
9
10
throttle

let leading = true
let maxing = true && maxWait
let trailing = true

debounce
let leading = false
let maxing = false
let trailing = true

Vue 有一个官方教程中的使用方法如下

https://cn.vuejs.org/v2/guide/computed.html#%E4%BE%A6%E5%90%AC%E5%99%A8

之前我也写过一个 在methods中 计算出一个函数,但是那样的返回和计算不能使用箭头函数,因为运行函数和执行的this不同,ide也报错,XD 虽然可用

参考

https://github.com/lodash/lodash/blob/master/throttle.js

https://github.com/lodash/lodash/blob/master/debounce.js

What

想看一下 某个包的文件

查看 usb连接/模拟器 的设备 adb devices

连接一个具体的设备 adb -s <设备id> shell

列出手机里的安装包pm list packages 当然 grep命令也可以用

比如你要查看的包为com.xxx.yyy那么 run-as com.xxx.yyy

cd ls grep cat这些命令是有的 并没有vi XD,不过有cat 基本能看文件了

// 搜到了 单个文件 cat 出来的 但没搜到文件夹的

// 也有间接先cat到sdcard再pull 再删sdcard

// 还搜到了一个 用 adb backup 把整个app搞出来,再 转换成tar的 [见下面链接]

// 当然 还有一堆说root的 emmm 告辞

// Stetho?

参考

https://stackoverflow.com/questions/53634246/android-get-all-installed-packages-using-adb

https://gist.github.com/davidnunez/1404789

https://stackoverflow.com/questions/1043322/why-do-i-get-access-denied-to-data-folder-when-using-adb?rq=1

https://stackoverflow.com/questions/7399028/android-adb-permission-denied

https://stackoverflow.com/questions/15558353/how-can-one-pull-the-private-data-of-ones-own-android-app

https://stackoverflow.com/questions/9017073/is-it-possible-to-see-application-data-from-adb-shell-the-same-way-i-see-it-moun/16461386

https://stackoverflow.com/questions/15558353/how-can-one-pull-the-private-data-of-ones-own-android-app/31504263#31504263