39 Commits

Author SHA1 Message Date
ehlxr
c221fbc347 Merge branch 'master' into dev 2020-11-16 17:43:20 +08:00
ehlxr
ecf0676d67 fix bug in should-finish instruction 2020-11-16 16:58:56 +08:00
ehlxr
b4ad1e0e96 fix bug in should-finish instruction 2020-11-16 16:55:15 +08:00
ehlxr
195740fa3d support absolute dir 2020-11-11 15:19:32 +08:00
ehlxr
4f3ad86984 support absolute dir 2020-11-11 15:17:48 +08:00
ehlxr
4b3bf5e77c add profile config 2020-11-11 15:01:36 +08:00
ehlxr
bf4f156714 add ignore suffix of the listener file 2020-11-10 17:33:45 +08:00
ehlxr
6a59036070 add ignore suffix of the listener file 2020-11-10 17:24:14 +08:00
ehlxr
8dc1ebe6d9 add ignore suffix of the listener file 2020-11-10 17:13:49 +08:00
dengsgo
da54305188 增加 1.16 2020-10-17 10:29:33 +08:00
dengsgo
9e651d3023 优化 代码 2020-10-17 10:24:37 +08:00
dengsgo
605914d1e1 增加 1.16 2020-08-23 16:22:08 +08:00
dengsgo
86cf40564b 优化 文件扫描性能 2020-08-23 16:17:18 +08:00
dengsgo
819297118c 更新 changelog 2020-07-19 18:10:34 +08:00
dengsgo
a76d4cfe6c 增加 对信号的处理 2020-07-19 18:06:24 +08:00
dengsgo
14ba79c2b3 更新 README 2020-06-27 15:15:29 +08:00
dengsgo
c5f0649ab5 增加 pid文件进程感知 2020-06-27 15:11:55 +08:00
dengsgo
7c6f8f910b 增加 snap config 2020-04-22 15:40:09 +08:00
dengsgo
d07616bde9 rename 2020-03-16 11:19:13 +08:00
dengsgo
4f81692954 typo deamon->daemon 2020-03-16 11:16:14 +08:00
Deng.Liu
588af5143f Update README.md 2020-03-15 21:10:11 +08:00
Deng.Liu
2d8924b7fe Update go.yml 2020-03-15 21:02:32 +08:00
Deng.Liu
08fb7aabf8 Update go.yml 2020-03-15 20:56:56 +08:00
Deng.Liu
9f0a51ce0e Update go.yml 2020-03-15 20:55:31 +08:00
Deng.Liu
8a3a2d570b Update go.yml 2020-03-15 20:53:42 +08:00
Deng.Liu
e5405f4fa9 Update go.yml 2020-03-15 20:46:58 +08:00
Deng.Liu
9d9cce0f31 Create go.yml 2020-03-15 20:43:56 +08:00
dengsgo
c0364a62bc 发行 v1.15 2020-03-08 13:26:38 +08:00
dengsgo
a9758653ba 修改 changelog 2020-03-08 13:24:49 +08:00
dengsgo
b1c5f7da39 修改 readme mod 2020-03-05 15:37:36 +08:00
dengsgo
29fc62de90 更改 mod管理依赖 2020-03-05 15:31:47 +08:00
dengsgo
2d409fbce0 修改 log 2020-03-04 13:41:02 +08:00
dengsgo
550dc01dfb 修改 readme 2020-03-04 11:18:21 +08:00
dengsgo
d0492bad8c 更新 readme 2020-03-03 15:01:08 +08:00
dengsgo
55625d51bc 增加 支持 2020-03-03 14:57:35 +08:00
dengsgo
8ddc124f89 增加 支持 2020-03-03 14:54:12 +08:00
dengsgo
8b7042411c 修改 rekeased 2020-03-03 11:39:15 +08:00
dengsgo
b72caedcbb 重构 指令 2020-03-03 11:37:20 +08:00
dengsgo
f989590d14 修改 文案 2020-03-03 11:12:08 +08:00
18 changed files with 251 additions and 462 deletions

45
.github/workflows/go.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: Go
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.13
uses: actions/setup-go@v1
with:
go-version: 1.13
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Get dependencies
run: |
export GO111MODULE=on
go env -w GOPROXY=https://goproxy.io,direct
go get
- name: Build
run: go build
- name: Use
run: |
export PATH=/home/runner/work/fileboy/fileboy:$PATH
fileboy version
fileboy help
fileboy init
cat filegirl.yaml
fileboy exec
fileboy daemon
ls -al .fileboy.pid
ps aux | grep fileboy
fileboy stop

View File

@@ -20,7 +20,7 @@ script:
- fileboy init - fileboy init
- cat filegirl.yaml - cat filegirl.yaml
- fileboy exec - fileboy exec
- fileboy deamon - fileboy daemon
- ls -al .fileboy.pid - ls -al .fileboy.pid
- ps aux | grep fileboy - ps aux | grep fileboy
- fileboy stop - fileboy stop

View File

@@ -1,5 +1,27 @@
### Release v1.16
2020.08.23
- 优化 文件扫描性能
2020.07.19
- 增加 pid 文件处理
- 增加 信息处理
2020.03.16
- typo deamon->daemon
### Release v1.15 ### Release v1.15
2020.03.08
- 优化 指令模式
- 使用 mod 管理依赖
- go version >= 1.13
- 优化 一些细节
2020.01.02 2020.01.02
- 增加 指令配置项 `instruction`, 可以通过预定义的指令来控制 command 的行为 - 增加 指令配置项 `instruction`, 可以通过预定义的指令来控制 command 的行为

View File

@@ -10,8 +10,7 @@
# make build-start-mac 编译+启动 # make build-start-mac 编译+启动
init: init:
go get -u gopkg.in/yaml.v2 go get -u
go get -u gopkg.in/fsnotify/fsnotify.v1
build-mac: ;@echo "编译-mac版"; build-mac: ;@echo "编译-mac版";
CGO_ENABLED=0 GOARCH=amd64 GOOS=darwin go build -ldflags "-s -w" -o ./bin/fileboy-darwin-amd64.bin CGO_ENABLED=0 GOARCH=amd64 GOOS=darwin go build -ldflags "-s -w" -o ./bin/fileboy-darwin-amd64.bin

214
README.md
View File

@@ -1,215 +1,3 @@
## 项目说明 ## 项目说明
[![Build Status](https://travis-ci.org/dengsgo/fileboy.svg?branch=master)](https://travis-ci.org/dengsgo/fileboy) [![Go Report Card](https://goreportcard.com/badge/github.com/dengsgo/fileboy)](https://goreportcard.com/report/github.com/dengsgo/fileboy) 基于 [fileboy](https://github.com/dengsgo/fileboy) 魔改版
[简体中文](README.md) | [ENGLISH](README_EN.md)
fileboy文件变更监听通知系统使用 Go 编写。
适用于 Hot Reload 典型的如开发go项目无需每次手动执行 go build又比如前端 node 打包) 或者 系统监控的场景。
## 特性
- 极简的用法和配置
- 支持多平台Windows/Linux/MacOS
- 支持自定义文件监听范围,监听指定文件夹/不监听指定文件夹/指定后缀文件
- 支持自定义监控事件write/rename/remove/create/chmod
- 支持设置多条命令
- 命令支持变量占位符
- 支持冗余任务丢弃,自定义冗余任务范围
- 支持 http 通知
- 更多...
## 编译环境
go version 1.13
## 更新日志
[CHANGELOG](CHANGELOG.md)
## 运行
### 下载二进制文件
Github: [download v1.15](https://github.com/dengsgo/fileboy/releases)
Gitee: [dowmload v1.15](https://gitee.com/dengsgo/fileboy/releases)
下载已经编译好的对应平台二进制文件,重命名为`fileboy`, 加入系统 Path 中即可。
### 源码编译
clone 该项目,进入主目录,运行命令:
```shell
## 安装依赖
go get -u gopkg.in/fsnotify/fsnotify.v1
go get -u gopkg.in/yaml.v2
## 编译
go build
## 运行
./fileboy
```
## 使用
fileboy 的正常运行依赖于 `filegirl.yaml` 配置文件,因此首次在项目中使用需要初始化 `filegirl.yaml`
- 进入你想要 hot reload 的项目主目录下;
- 运行 `fileboy init`,会在该目录下生成 `filegirl.yaml`文件;
- 查看 `filegirl.yaml`,修改为适合自己项目的配置项;
- 运行 `fileboy`即可.
如果你定义了 `command -> exec`命令,想事先确认是否能正常执行,可以运行 `fileboy exec`命令,系统会尝试运行你的自定义命令。
你可以使用 `fileboy help`查看使用帮助。
## filegirl.yaml 配置文件说明
```yaml
# 主配置
core:
# 配置版本号
version: 1
# 监控配置
monitor:
# 要监听的目录
# test1 监听当前目录下 test1 目录
# test1/test2 监听当前目录下 test1/test2 目录
# test1,* 监听当前目录下 test1 目录及其所有子目录(递归)
# .,* 监听当前目录及其所有子目录(递归)
includeDirs:
- .,*
# 不监听的目录
# .idea 忽略.idea目录及其所有子目录的监听
exceptDirs:
- .idea
- .git
- .vscode
- node_modules
- vendor
# 监听文件的格式,此类文件更改会执行 command 中的命令
# .go 后缀为 .go 的文件更改,会执行 command 中的命令
# .* 所有的文件更改都会执行 command 中的命令
types:
- .go
# 监听的事件类型,发生此类事件才执行 command 中的命令
# 没有该配置默认监听所有事件
# write 写入文件事件
# rename 重命名文件事件
# remove 移除文件事件
# create 创建文件事件
# chmod 更新文件权限事件(类unix)
events:
- write
- rename
- remove
- create
- chmod
# 命令
command:
# 监听的文件有更改会执行的命令
# 可以有多条命令,会依次执行
# 如有多条命令,每条命令都会等待上一条命令执行完毕后才会执行
# 如遇交互式命令,允许外部获取输入
# 支持变量占位符,运行命令时会替换成实际值:
# {{file}} 文件名(如 a.txt 、test/test2/a.go)
# {{ext}} 文件后缀(如 .go)
# {{event}} 事件(上面的events, 如 write)
# {{changed}} 文件更新的本地时间戳(纳秒,如 1537326690523046400)
# 变量占位符使用示例cp {{file}} /root/sync -rf 、 myCommand --{{ext}} {{changed}}
exec:
- go version
- go env
# 文件变更后命令在xx毫秒后才会执行单位为毫秒
# 一个变更事件(A)如果在定义的延迟时间(t)内,又有新的文件变更事件(B)那么A会取消执行。
# B及以后的事件均依次类推直到事件Z在t内没有新事件产生Z 会执行
# 合理设置延迟时间,将有效减少冗余和重复任务的执行
# 如果不需要该特性,设置为 0
delayMillSecond: 2000
# 特殊指令
# 可以通过预定义的指令来控制 command 的行为,指令可以有多个
# exec-when-start fileboy启动就绪后自动执行一次 'exec' 定义的命令
# should-finish 触发执行 'exec' 时(C),如果上一次的命令(L)未退出(还在执行),会等待 L 退出,直到有明确 exit code 才会开始执行本次命令。
# 在等待 L 退出时,又有新事件触发了命令执行(N),则 C 执行取消,只会保留最后一次的 N 执行
# ignore-stdout 执行 'exec' 产生的 stdout 会被丢弃
# ignore-warn fileboy 自身的 warn 信息会被丢弃
# ignore-info fileboy 自身的 info 信息会被丢弃
# ignore-exec-error 执行 'exec' 出错仍继续执行下面的命令而不退出
instruction:
#- should-finish
#- exec-when-start
# 通知器
notifier:
# 文件更改会向该 url 发送请求POST 一段 json 文本数据)
# 触发请求的时机和执行 command 命令是一致的
# 请求超时 15 秒
# POST 格式:
# Content-Type: application/json;charset=UTF-8
# User-Agent: FileBoy Net Notifier v1.15
# Body: {"project_folder":"/project/path","file":"main.go","changed":1576567861913824940,"ext":".go","event":"write"}
# 例: http://example.com/notifier/fileboy-listener
# 不启用通知,请留空 ""
callUrl: ""
```
### TODO
- [x] 命令支持变量占位符
- [x] 支持多命令
- [x] 支持监听指定文件夹
- [x] 支持不监听指定文件夹
- [x] 支持监听指定后缀文件
- [x] 支持自定事件监听
- [x] 支持 http 通知
- [x] 支持冗余任务丢弃
- [ ] 支持 http 合并任务的通知
## QA
#### 很多框架都自带了 hot reload 的功能,为什么还要单独写个 fileboy 呢?
这个是一款通用的 hot reload 的软件,理论上适用于任何需要 hot reload 的场景,并不局限于语言层面上。只要灵活的配置 `filegirl.yaml`文件就行了。
#### fileboy 可以应用在那些具体的场景?
在开发中,我们很需要一款可以帮助我们自动打包编译的工具,那 fileboy 就非常适合这样的场景。比如 go 项目的热编译,让我们可以边修改代码边运行得到反馈。又比如 PHP Swoole 框架由于常驻进程的原因无法更改代码立即reload使用 fileboy 就可以辅助做到传统 PHP 开发的体验。
对于一些需要监控文件日志或者配置变动的场景, fileboy 同样适合。你可以事先编写好相应的通知报警脚本,然后定义`filegirl.yaml`中的`command`命令,交由 fileboy 自动运行监控报警。
#### 通知器在什么时候会发送 http 请求 ?
通知器发送 http 通知的前提是在配置文件中设置了 `callUrl` 参数(不为空即为已设置)。触发请求的时机和执行 command 命令是一致的,`command -> delayMillSecond` 参数对于触发器同样有效。请求超时默认15秒.
#### idea 下更改文件,为什么会执行两次或者多次 command ?
由于 idea 系列软件特殊的文件保存策略他会自动创建一些临时文件并且在需要时多次重写文件所以有时反映在文件上就是有多次的更改所以会出现这种情况。1.5之后的版本增加了 `delayMillSecond` 参数,可以解决这个问题。
#### filegirl.yaml 里面的 command 如何配置复杂命令?
fileboy 目前支持 `命令 + 参数`这种形式的 command而且 参数中不能有""符号或者有空格。如:
`go build`:支持;
`go env`:支持;
`php swoole start --daemon`:支持
`cat a.txt | grep "q" | wc -l`:不支持
对于不支持的命令,可以把它写到一个文件里,然后在 command 中执行这个文件来解决。
#### 为什么起名为 fileboy又把配置名叫做 filegirl
因为爱情~~ (◡ᴗ◡✿)
### 贡献者
> 排名不分先后
[@dengsgo](https://www.yoytang.com) <dengsgo@gmail.com>
[@itwesley](https://github.com/itwesley) <wcshen1126@gmail.com>
[@jason-gao](https://github.com/jason-gao) <3048789891@qq.com>

View File

@@ -1,163 +0,0 @@
## FILEBOY
[![Build Status](https://travis-ci.org/dengsgo/fileboy.svg?branch=master)](https://travis-ci.org/dengsgo/fileboy) [![Go Report Card](https://goreportcard.com/badge/github.com/dengsgo/fileboy)](https://goreportcard.com/report/github.com/dengsgo/fileboy)
[简体中文](README.md) | [ENGLISH](README_EN.md)
Fileboy, File Change Monitoring Notification System, written with Go.
For Hot Reload scenarios (typically for developing go projects without having to perform go build manually every time; for example, front-end node packaging) or system monitoring.
## FEATURES
- Minimalist usage and configuration
- Support multiple platforms, Windows/Linux/MacOS
- Supports custom file listening scope, listening for specified folders/not listening for specified folders/specified suffix files
- Support for custom monitoring events (write / rename / remove / create / chmod)
- Support for setting up multiple commands
- Command support variable placeholders
- Supporting redundant task discarding and customizing redundant task scope
- Supporting HTTP notifications
- more...
## COMPILE
go version 1.13
## CHANGELOG
[CHANGELOG](CHANGELOG.md)
## RUN
### BINARIES
Github: [download v1.15](https://github.com/dengsgo/fileboy/releases)
Gitee: [dowmload v1.15](https://gitee.com/dengsgo/fileboy/releases)
Download the compiled binary file of the corresponding platform, rename it `fileboy`, and add it to the system Path.
### SOURCE
Clone project, enter the project directory, run the command:
```shell
## installation dependency
go get-u gopkg.in/fsnotify/fsnotify.v1
go get-u gopkg.in/yaml.v2
## compile
go build
## run
./fileboy
```
## USAGE
The normal operation of fileboy depends on the `filegirl.yaml` configuration, so for the first time in a project, `filegirl.yaml` needs to be initialized.
- Enter the project home directory where you want hot reload;
- Running `fileboy init` will generate `filegirl. yaml` files in this directory.
- View `filegirl. yaml` and modify it to a configuration item suitable for your project.
- Run `fileboy`.
If you define the `command-> exec` command, you can run the `fileboy exec` command to confirm whether it can be executed properly in advance. The system will try to run your custom command.
You can use `fileboy help` to see help info.
## filegirl.yaml
```yaml
core:
# config version code
version: 1
# monitor section
monitor:
# directories to monitor
# test1 listen for the test1 directory in the project directory
# test1/test2 listen for the test1/test2 directory in the project directory
# test1,* listen for the test1 directory in the project directory and all its subdirectories (recursion)
# .,* listen for the project directory and all its subdirectories (recursion)
includeDirs:
- .,*
# Unmonitored directories
# .idea ignore listening to .idea directory and all its subdirectories
exceptDirs:
- .idea
- .git
- .vscode
- node_modules
- vendor
# the suffix of the listener file, which changes the file to execute commands
# .go file changes suffixed with .go execute commands
# .* all file changes execute commands in the command
types:
- .go
# the type of event to listen to. Only when such an event occurs can the command in command be executed
# without this configuration, all events will be monitored by default
# write write file event
# rename rename file event
# remove remove remove file event
# create create file event
# chmod update file permission event (UNIX like)
events:
- write
- rename
- remove
- create
- chmod
command:
# the files monitored have commands that change to be executed
# there can be multiple commands that will be executed in turn
# in case of interactive commands, allow external access to input
# variable placeholders are supported, and the actual values are replaced when the command is run:
# {{file}} (e.g: a.txt 、test/test2/a.go)
# {{ext}} (e.g: .go)
# {{event}} event(e.g: write)
# {{changed}} local timestamp for file updated(nanosecond,e.g 1537326690523046400)
# variable placeholders e.gcp {{file}} /root/sync -rf 、 myCommand --{{ext}} {{changed}}
exec:
- go version
- go env
# the command will not execute until XX milliseconds after the file changes
# a change event (A) cancels execution if there is a new file change event (B) within the defined delay time (t).
# B and subsequent events are analogized in turn until event Z does not produce new events within t, and Z executes.
# reasonable setting of delay time will effectively reduce redundancy and duplicate task execution.
# If this feature is not required, set to 0
delayMillSecond: 2000
# 特殊指令
# 可以通过预定义的指令来控制 command 的行为,指令可以有多个
# exec-when-start fileboy启动就绪后自动执行一次 'exec' 定义的命令
# should-finish 触发执行 'exec' 时(C),如果上一次的命令(L)未退出(还在执行),会等待 L 退出,直到有明确 exit code 才会开始执行本次命令。
# 在等待 L 退出时,又有新事件触发了命令执行(N),则 C 执行取消,只会保留最后一次的 N 执行
# ignore-stdout 执行 'exec' 产生的 stdout 会被丢弃
# ignore-warn fileboy 自身的 warn 信息会被丢弃
# ignore-info fileboy 自身的 info 信息会被丢弃
# ignore-exec-error 执行 'exec' 出错仍继续执行下面的命令而不退出
instruction:
#- should-finish
#- exec-when-start
notifier:
# file changes send requests to the URL (POST JSON text data)
# the timing of triggering the request is consistent with executing the command command
# timeout 15 second
# POST :
# Content-Type: application/json;charset=UTF-8
# User-Agent: FileBoy Net Notifier v1.15
# Body: {"project_folder":"/project/path","file":"main.go","changed":1576567861913824940,"ext":".go","event":"write"}
# e.g: http://example.com/notifier/fileboy-listener
# no notice is enabled. Please leave it blank. ""
callUrl: ""
```
### CONTRIBUTOR
[@dengsgo](https://www.yoytang.com) <dengsgo@gmail.com>
[@itwesley](https://github.com/itwesley) <wcshen1126@gmail.com>
[@jason-gao](https://github.com/jason-gao) <3048789891@qq.com>

View File

@@ -12,11 +12,11 @@ func getPidFile() string {
return projectFolder + "/.fileboy.pid" return projectFolder + "/.fileboy.pid"
} }
func runAsDeamon() (int, error) { func runAsDaemon() (int, error) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
logAndExit("daemons mode cannot run on windows.") logAndExit("daemons mode cannot run on windows.")
} }
err := stopDeamon() err := stopDaemon()
if err != nil { if err != nil {
logAndExit(err) logAndExit(err)
} }
@@ -24,22 +24,22 @@ func runAsDeamon() (int, error) {
if err != nil { if err != nil {
logAndExit("cannot found `fileboy` command in the PATH") logAndExit("cannot found `fileboy` command in the PATH")
} }
deamon := exec.Command("fileboy") daemon := exec.Command("fileboy")
deamon.Dir = projectFolder daemon.Dir = projectFolder
deamon.Env = os.Environ() daemon.Env = os.Environ()
deamon.Stdout = os.Stdout daemon.Stdout = os.Stdout
err = deamon.Start() err = daemon.Start()
if err != nil { if err != nil {
logAndExit(err) logAndExit(err)
} }
pid := deamon.Process.Pid pid := daemon.Process.Pid
if pid != 0 { if pid != 0 {
ioutil.WriteFile(getPidFile(), []byte(strconv.Itoa(pid)), 0644) ioutil.WriteFile(getPidFile(), []byte(strconv.Itoa(pid)), 0644)
} }
return pid, nil return pid, nil
} }
func stopDeamon() error { func stopDaemon() error {
bs, err := ioutil.ReadFile(getPidFile()) bs, err := ioutil.ReadFile(getPidFile())
if err != nil { if err != nil {
return nil return nil
@@ -48,3 +48,9 @@ func stopDeamon() error {
os.Remove(getPidFile()) os.Remove(getPidFile())
return nil return nil
} }
func stopSelf() {
pid := os.Getpid()
os.Remove(getPidFile())
_ = exec.Command("kill", strconv.Itoa(pid)).Run()
}

View File

@@ -6,9 +6,11 @@ import (
"log" "log"
"math/rand" "math/rand"
"os" "os"
"os/signal"
"path" "path"
"strconv" "strconv"
"strings" "strings"
"syscall"
"time" "time"
"gopkg.in/fsnotify/fsnotify.v1" "gopkg.in/fsnotify/fsnotify.v1"
@@ -53,33 +55,37 @@ type changedFile struct {
Event string Event string
} }
func parseConfig() { func parseConfig(filegirlYamlPath string) {
cfg = new(FileGirl) cfg = new(FileGirl)
fc, err := ioutil.ReadFile(getFileGirlPath()) fc, err := ioutil.ReadFile(filegirlYamlPath)
if err != nil { if err != nil {
logError("The filegirl.yaml file in", projectFolder, "is not exist! ", err) logError("the filegirl.yaml file in", projectFolder, "is not exist! ", err)
fmt.Print(firstRunHelp) fmt.Print(firstRunHelp)
logAndExit("Fileboy unable to run.") logAndExit("fileboy unable to run.")
} }
err = yaml.Unmarshal(fc, cfg) err = yaml.Unmarshal(fc, cfg)
if err != nil { if err != nil {
logAndExit("Parsed filegirl.yaml failed: ", err) logAndExit("parsed filegirl.yaml failed: ", err)
} }
if cfg.Core.Version > Version { if cfg.Core.Version > Version {
logAndExit("Current fileboy support max version : ", Version) logAndExit("current fileboy support max version : ", Version)
} }
// init map // init map
cfg.Monitor.TypesMap = map[string]bool{} cfg.Monitor.TypesMap = map[string]bool{}
cfg.Monitor.ExceptTypesMap = map[string]bool{}
cfg.Monitor.IncludeDirsMap = map[string]bool{} cfg.Monitor.IncludeDirsMap = map[string]bool{}
cfg.Monitor.ExceptDirsMap = map[string]bool{} cfg.Monitor.ExceptDirsMap = map[string]bool{}
cfg.Monitor.IncludeDirsRec = map[string]bool{} cfg.Monitor.IncludeDirsRec = map[string]bool{}
cfg.Command.InstructionMap = map[string]bool{} cfg.InstructionMap = map[string]bool{}
// convert to map // convert to map
for _, v := range cfg.Monitor.Types { for _, v := range cfg.Monitor.Types {
cfg.Monitor.TypesMap[v] = true cfg.Monitor.TypesMap[v] = true
} }
for _, v := range cfg.Command.Instruction { for _, v := range cfg.Instruction {
cfg.Command.InstructionMap[v] = true cfg.InstructionMap[v] = true
}
for _, v := range cfg.Monitor.ExceptTypes {
cfg.Monitor.ExceptTypesMap[v] = true
} }
log.Printf("%+v", cfg) log.Printf("%+v", cfg)
} }
@@ -90,8 +96,13 @@ func eventDispatcher(event fsnotify.Event) {
} }
ext := path.Ext(event.Name) ext := path.Ext(event.Name)
if len(cfg.Monitor.Types) > 0 && if len(cfg.Monitor.Types) > 0 &&
!keyInMonitorTypesMap(".*", cfg) && !keyInMonitorTypesMap(".*", cfg.Monitor.TypesMap) &&
!keyInMonitorTypesMap(ext, cfg) { !keyInMonitorTypesMap(ext, cfg.Monitor.TypesMap) {
return
}
if len(cfg.Monitor.ExceptTypes) > 0 &&
keyInMonitorTypesMap(ext, cfg.Monitor.ExceptTypesMap) {
return return
} }
@@ -111,14 +122,19 @@ func eventDispatcher(event fsnotify.Event) {
func addWatcher() { func addWatcher() {
logInfo("collecting directory information...") logInfo("collecting directory information...")
dirsMap := map[string]bool{} dirsMap := map[string]bool{}
for _, dir := range cfg.Monitor.ExceptDirs {
if dir == "." {
logAndExit("exceptDirs must is not project root path ! err path:", dir)
}
}
for _, dir := range cfg.Monitor.IncludeDirs { for _, dir := range cfg.Monitor.IncludeDirs {
darr := dirParse2Array(dir) darr := dirParse2Array(dir)
if len(darr) < 1 || len(darr) > 2 { if len(darr) < 1 || len(darr) > 2 {
logAndExit("filegirl section monitor dirs is error. ", dir) logAndExit("filegirl section monitor dirs is error. ", dir)
} }
if strings.HasPrefix(darr[0], "/") { //if strings.HasPrefix(darr[0], "/") {
logAndExit("dirs must be relative paths ! err path:", dir) // logAndExit("dirs must be relative paths ! err path:", dir)
} //}
if darr[0] == "." { if darr[0] == "." {
if len(darr) == 2 && darr[1] == "*" { if len(darr) == 2 && darr[1] == "*" {
// The highest priority // The highest priority
@@ -134,7 +150,8 @@ func addWatcher() {
dirsMap[projectFolder] = true dirsMap[projectFolder] = true
} }
} else { } else {
md := projectFolder + "/" + darr[0] //md := projectFolder + "/" + darr[0]
md := darr[0]
dirsMap[md] = true dirsMap[md] = true
if len(darr) == 2 && darr[1] == "*" { if len(darr) == 2 && darr[1] == "*" {
listFile(md, func(d string) { listFile(md, func(d string) {
@@ -145,16 +162,7 @@ func addWatcher() {
} }
} }
for _, dir := range cfg.Monitor.ExceptDirs {
if dir == "." {
logAndExit("exceptDirs must is not project root path ! err path:", dir)
}
p := projectFolder + "/" + dir
delete(dirsMap, p)
listFile(p, func(d string) {
delete(dirsMap, d)
})
}
for dir := range dirsMap { for dir := range dirsMap {
logInfo("watcher add -> ", dir) logInfo("watcher add -> ", dir)
err := watcher.Add(dir) err := watcher.Add(dir)
@@ -200,6 +208,15 @@ func initWatcher() {
} }
func watchChangeHandler(event fsnotify.Event) { func watchChangeHandler(event fsnotify.Event) {
// stop the fileboy daemon process when the .fileboy.pid file is changed
if event.Name == getPidFile() &&
(event.Op == fsnotify.Remove ||
event.Op == fsnotify.Write ||
event.Op == fsnotify.Rename) {
logUInfo("exit daemon process")
stopSelf()
return
}
if event.Op != fsnotify.Create && event.Op != fsnotify.Rename { if event.Op != fsnotify.Create && event.Op != fsnotify.Rename {
return return
} }
@@ -213,13 +230,7 @@ func watchChangeHandler(event fsnotify.Event) {
continue continue
} }
// check exceptDirs // check exceptDirs
has := false if hitDirs(event.Name, &cfg.Monitor.ExceptDirs) {
for _, v := range cfg.Monitor.ExceptDirs {
if strings.HasPrefix(event.Name, projectFolder+"/"+v) {
has = true
}
}
if has {
continue continue
} }
@@ -253,7 +264,7 @@ func parseArgs() {
switch { switch {
case len(os.Args) == 1: case len(os.Args) == 1:
show() show()
parseConfig() parseConfig(getFileGirlPath())
done := make(chan bool) done := make(chan bool)
initWatcher() initWatcher()
defer watcher.Close() defer watcher.Close()
@@ -265,8 +276,8 @@ func parseArgs() {
case len(os.Args) > 1: case len(os.Args) > 1:
c := os.Args[1] c := os.Args[1]
switch c { switch c {
case "deamon": case "deamon", "daemon":
pid, err := runAsDeamon() pid, err := runAsDaemon()
if err != nil { if err != nil {
logAndExit(err) logAndExit(err)
} }
@@ -274,7 +285,7 @@ func parseArgs() {
logUInfo("fileboy is ready. the main process will run as a daemons") logUInfo("fileboy is ready. the main process will run as a daemons")
return return
case "stop": case "stop":
err := stopDeamon() err := stopDaemon()
if err != nil { if err != nil {
logAndExit(err) logAndExit(err)
} }
@@ -283,33 +294,59 @@ func parseArgs() {
case "init": case "init":
_, err := ioutil.ReadFile(getFileGirlPath()) _, err := ioutil.ReadFile(getFileGirlPath())
if err == nil { if err == nil {
logError("Profile filegirl.yaml already exists.") logError("profile filegirl.yaml already exists.")
logAndExit("If you want to regenerate filegirl.yaml, delete it first") logAndExit("delete it first when you want to regenerate filegirl.yaml")
} }
err = ioutil.WriteFile(getFileGirlPath(), []byte(exampleFileGirl), 0644) err = ioutil.WriteFile(getFileGirlPath(), []byte(exampleFileGirl), 0644)
if err != nil { if err != nil {
logError("Profile filegirl.yaml create failed! ", err) logError("profile filegirl.yaml create failed! ", err)
return return
} }
logUInfo("Profile filegirl.yaml created ok") logUInfo("profile filegirl.yaml created ok")
return return
case "exec": case "exec":
parseConfig() parseConfig(getFileGirlPath())
newTaskMan(0, cfg.Notifier.CallUrl).run(new(changedFile)) newTaskMan(0, cfg.Notifier.CallUrl).run(new(changedFile))
return return
case "profile":
filegirlYamlPath := os.Args[2]
show()
parseConfig(filegirlYamlPath)
done := make(chan bool)
initWatcher()
defer watcher.Close()
if keyInInstruction(InstExecWhenStart) {
taskMan.run(new(changedFile))
}
<-done
return
case "version", "v", "-v", "--version": case "version", "v", "-v", "--version":
fmt.Println(versionDesc) fmt.Println(versionDesc)
case "help", "--help", "--h", "-h": case "help", "--help", "--h", "-h":
fmt.Print(helpStr) fmt.Print(helpStr)
default: default:
logAndExit("Unknown parameter, use 'fileboy help' to view available commands") logAndExit("unknown parameter, use 'fileboy help' to view available commands")
} }
return return
default: default:
logAndExit("Unknown parameters, use `fileboy help` show help info.") logAndExit("unknown parameters, use `fileboy help` show help info.")
} }
} }
func signalHandler() {
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
if taskMan != nil && taskMan.cmd != nil && taskMan.cmd.Process != nil {
if err := taskMan.cmd.Process.Kill(); err != nil {
logError("stopping the process failed: PID:", taskMan.cmd.ProcessState.Pid(), ":", err)
}
}
os.Exit(0)
}()
}
func getFileGirlPath() string { func getFileGirlPath() string {
return projectFolder + "/" + filegirlYamlName return projectFolder + "/" + filegirlYamlName
} }
@@ -332,5 +369,6 @@ func main() {
if err != nil { if err != nil {
logAndExit(err) logAndExit(err)
} }
signalHandler()
parseArgs() parseArgs()
} }

View File

@@ -6,11 +6,13 @@ type FileGirl struct {
} }
Monitor struct { Monitor struct {
Types []string `yaml:"types"` Types []string `yaml:"types"`
ExceptTypes []string `yaml:"exceptTypes"`
IncludeDirs []string `yaml:"includeDirs"` IncludeDirs []string `yaml:"includeDirs"`
ExceptDirs []string `yaml:"exceptDirs"` ExceptDirs []string `yaml:"exceptDirs"`
Events []string `yaml:"events"` Events []string `yaml:"events"`
// convert to // convert to
TypesMap map[string]bool `yaml:"-"` TypesMap map[string]bool `yaml:"-"`
ExceptTypesMap map[string]bool `yaml:"-"`
IncludeDirsMap map[string]bool `yaml:"-"` IncludeDirsMap map[string]bool `yaml:"-"`
ExceptDirsMap map[string]bool `yaml:"-"` ExceptDirsMap map[string]bool `yaml:"-"`
DirsMap map[string]bool `yaml:"-"` DirsMap map[string]bool `yaml:"-"`
@@ -18,14 +20,14 @@ type FileGirl struct {
IncludeDirsRec map[string]bool `yaml:"-"` IncludeDirsRec map[string]bool `yaml:"-"`
} }
Command struct { Command struct {
Instruction []string `yaml:"instruction"`
Exec []string `yaml:"exec"` Exec []string `yaml:"exec"`
DelayMillSecond int `yaml:"delayMillSecond"` DelayMillSecond int `yaml:"delayMillSecond"`
// convert to
InstructionMap map[string]bool `yaml:"-"`
} }
Notifier struct { Notifier struct {
CallUrl string `yaml:"callUrl"` CallUrl string `yaml:"callUrl"`
} }
Instruction []string `yaml:"instruction"`
// convert to
InstructionMap map[string]bool `yaml:"-"`
} }

10
go.mod Normal file
View File

@@ -0,0 +1,10 @@
module fileboy
go 1.13
require (
github.com/fsnotify/fsnotify v1.4.9 // indirect
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 // indirect
gopkg.in/fsnotify/fsnotify.v1 v1.4.7
gopkg.in/yaml.v2 v2.3.0
)

11
go.sum Normal file
View File

@@ -0,0 +1,11 @@
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPMJTiBfWycGh/u3UoO88=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo=
gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -61,7 +61,7 @@ func (n *NetNotifier) dispatch(params *postParams) {
return return
} }
req.Header.Set("Content-Type", "application/json;charset=UTF-8") req.Header.Set("Content-Type", "application/json;charset=UTF-8")
req.Header.Set("User-Agent", "FileBoy Net Notifier v1.15") req.Header.Set("User-Agent", "FileBoy Net Notifier v1.16")
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
logError("notifier call failed. err:", err) logError("notifier call failed. err:", err)

46
raw.go
View File

@@ -32,6 +32,11 @@ monitor:
types: types:
- .go - .go
# 不监听文件的格式,此类文件更改不会执行 command 中的命令
# .DS_Store 后缀为 .DS_Store 的文件更改,不会执行 command 中的命令
exceptTypes:
- .DS_Store
# 监听的事件类型,发生此类事件才执行 command 中的命令 # 监听的事件类型,发生此类事件才执行 command 中的命令
# 没有该配置默认监听所有事件 # 没有该配置默认监听所有事件
# write 写入文件事件 # write 写入文件事件
@@ -69,19 +74,6 @@ command:
# 如果不需要该特性,设置为 0 # 如果不需要该特性,设置为 0
delayMillSecond: 2000 delayMillSecond: 2000
# 特殊指令
# 可以通过预定义的指令来控制 command 的行为,指令可以有多个
# exec-when-start fileboy启动就绪后自动执行一次 'exec' 定义的命令
# should-finish 触发执行 'exec' 时(C),如果上一次的命令(L)未退出(还在执行),会等待 L 退出,直到有明确 exit code 才会开始执行本次命令。
# 在等待 L 退出时,又有新事件触发了命令执行(N),则 C 执行取消,只会保留最后一次的 N 执行
# ignore-stdout 执行 'exec' 产生的 stdout 会被丢弃
# ignore-warn fileboy 自身的 warn 信息会被丢弃
# ignore-info fileboy 自身的 info 信息会被丢弃
# ignore-exec-error 执行 'exec' 出错仍继续执行下面的命令而不退出
instruction:
#- should-finish
#- exec-when-start
# 通知器 # 通知器
notifier: notifier:
# 文件更改会向该 url 发送请求POST 一段 json 文本数据) # 文件更改会向该 url 发送请求POST 一段 json 文本数据)
@@ -89,11 +81,27 @@ notifier:
# 请求超时 15 秒 # 请求超时 15 秒
# POST 格式: # POST 格式:
# Content-Type: application/json;charset=UTF-8 # Content-Type: application/json;charset=UTF-8
# User-Agent: FileBoy Net Notifier v1.15 # User-Agent: FileBoy Net Notifier v1.16
# Body: {"project_folder":"/project/path","file":"main.go","changed":1576567861913824940,"ext":".go","event":"write"} # Body: {"project_folder":"/project/path","file":"main.go","changed":1576567861913824940,"ext":".go","event":"write"}
# 例: http://example.com/notifier/fileboy-listener # 例: http://example.com/notifier/fileboy-listener
# 不启用通知,请留空 "" # 不启用通知,请留空 ""
callUrl: "" callUrl: ""
# 特殊指令
instruction:
# 可以通过特殊的指令选项来控制 command 的行为,指令可以有多个
# 指令选项解释:
# exec-when-start fileboy启动就绪后自动执行一次 'exec' 定义的命令
# should-finish 触发执行 'exec' 时(C),如果上一次的命令(L)未退出(还在执行),会等待 L 退出(而不是强制 kill ),直到 L 有明确 exit code 才会开始执行本次命令。
# 在等待 L 退出时,又有新事件触发了命令执行(N),则 C 执行取消,只会保留最后一次的 N 执行
# ignore-stdout 执行 'exec' 产生的 stdout 会被丢弃
# ignore-warn fileboy 自身的 warn 信息会被丢弃
# ignore-info fileboy 自身的 info 信息会被丢弃
# ignore-exec-error 执行 'exec' 出错仍继续执行下面的命令而不退出
#- should-finish
#- exec-when-start
- ignore-warn
` `
var firstRunHelp = `第一次运行 fileboy ? var firstRunHelp = `第一次运行 fileboy ?
@@ -109,10 +117,12 @@ Usage of fileboy:
初始化 fileboy, 在当前目录生成 filegirl.yaml 配置文件 初始化 fileboy, 在当前目录生成 filegirl.yaml 配置文件
exec exec
尝试运行定义的 command 命令 尝试运行定义的 command 命令
deamon daemon
读取当前目录下的 filegirl.yaml 配置,以守护进程的方式运行在后台 读取当前目录下的 filegirl.yaml 配置,以守护进程的方式运行在后台
stop stop
停止守护进程 停止守护进程
profile
指定配置文件路径
version version
查看当前版本信息 查看当前版本信息
` `
@@ -136,13 +146,13 @@ var logo = `
_____ _ | | _____ ____) ) | | | |___| | _____ _ | | _____ ____) ) | | | |___| |
| ___) | | | | | ___) | __ (| | | |\_____/ | ___) | | | | | ___) | __ (| | | |\_____/
| | _| |_| |_____| |_____| |__) ) |___| | ___ | | _| |_| |_____| |_____| |__) ) |___| | ___
|_| (_____)_______)_______)______/ \_____/ (___) V1.15 |_| (_____)_______)_______)______/ \_____/ (___) V1.16
` `
var statement = `Dengsgo [dengsgo@gmail.com] Open Source with MIT License` var statement = `Dengsgo [dengsgo@gmail.com] Open Source with MIT License`
var versionDesc = ` var versionDesc = `
Version fileboy: v1.15 filegirl: v` + strconv.Itoa(Version) + ` Version fileboy: v1.16 filegirl: v` + strconv.Itoa(Version) + `
Released 2020.01.05 Released 2020.10.17
Licence MIT Licence MIT
Author dengsgo [dengsgo@gmail.com] Author dengsgo [dengsgo@gmail.com]
Website https://github.com/dengsgo/fileboy Website https://github.com/dengsgo/fileboy

BIN
resources/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

BIN
resources/jetbrains.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

8
snapcraft.yaml Normal file
View File

@@ -0,0 +1,8 @@
name: fileboy
vendor: dengsgo, https://github.com/dengsgo, <dengsgo@yoytang.com>
summary: File Change Monitoring Notification Tools.
description: File Change Monitoring Notification Tools. Please Visit https://github.com/dengsgo/fileboy
version: 1.15
icon: resources/icon.png
base: core18
grade: stable

View File

@@ -30,10 +30,10 @@ func newTaskMan(delay int, callUrl string) *TaskMan {
go func() { go func() {
for { for {
<-t.waitChan <-t.waitChan
if len(t.waitQueue) > 0 { if len(t.waitQueue) >= 1 {
cf := t.waitQueue[len(t.waitQueue)-1] cf := t.waitQueue[len(t.waitQueue)-1]
if len(t.waitQueue) > 1 { if len(t.waitQueue) > 1 {
logInfo("Number of redundant tasks dropped:", len(t.waitQueue)-1) logInfo("redundant tasks dropped:", len(t.waitQueue)-1)
} }
t.waitQueue = []*changedFile{} t.waitQueue = []*changedFile{}
go t.preRun(cf) go t.preRun(cf)
@@ -69,8 +69,8 @@ func (t *TaskMan) dispatcher(cf *changedFile) {
t.waitChan <- true t.waitChan <- true
return return
} }
logInfo("Waitting for the last task to finish") logInfo("waitting for the last task to finish")
logInfo("Number of waiting tasks:", len(t.waitQueue)) logInfo("waiting tasks:", len(t.waitQueue))
} else { } else {
t.preRun(cf) t.preRun(cf)
} }
@@ -113,7 +113,7 @@ func (t *TaskMan) run(cf *changedFile) {
} }
err = t.cmd.Wait() err = t.cmd.Wait()
if err != nil { if err != nil {
logWarn("command exec failed:", carr, err) logError("command exec failed:", carr, err)
if keyInInstruction(InstIgnoreExecError) { if keyInInstruction(InstIgnoreExecError) {
continue continue
} }

19
util.go
View File

@@ -8,13 +8,13 @@ import (
"strings" "strings"
) )
func keyInMonitorTypesMap(k string, cfg *FileGirl) bool { func keyInMonitorTypesMap(k string, maps map[string]bool) bool {
_, ok := cfg.Monitor.TypesMap[k] _, ok := maps[k]
return ok return ok
} }
func keyInInstruction(k string) bool { func keyInInstruction(k string) bool {
_, ok := cfg.Command.InstructionMap[k] _, ok := cfg.InstructionMap[k]
return ok return ok
} }
@@ -53,11 +53,24 @@ func dirParse2Array(s string) []string {
return r return r
} }
func hitDirs(d string, dirs *[]string) bool {
d += "/"
for _, v := range *dirs {
if strings.HasPrefix(d, projectFolder+"/"+v+"/") {
return true
}
}
return false
}
func listFile(folder string, fun func(string)) { func listFile(folder string, fun func(string)) {
files, _ := ioutil.ReadDir(folder) files, _ := ioutil.ReadDir(folder)
for _, file := range files { for _, file := range files {
if file.IsDir() { if file.IsDir() {
d := folder + "/" + file.Name() d := folder + "/" + file.Name()
if hitDirs(d, &cfg.Monitor.ExceptDirs) {
continue
}
fun(d) fun(d)
listFile(d, fun) listFile(d, fun)
} }