radovskyb / watcher Goto Github PK
View Code? Open in Web Editor NEWwatcher is a Go package for watching for files or directory changes without using filesystem events.
License: BSD 3-Clause "New" or "Revised" License
watcher is a Go package for watching for files or directory changes without using filesystem events.
License: BSD 3-Clause "New" or "Revised" License
Hi,
Is watcher able to list all new files created since last watcher run ? We have a situation we are continuously watching a directory and if the watcher was stopped interrupted and resume again, are anyway to to list all new files creation in between or any alternatives ?
Thank you !
When I modify multiple files in one shot (such as using "save all" in an IDE), multiple events are fired and the command is executed for each event. It would be nice if there is the ability to debounce the events so that if multiple files are modified together, the command is only executed once.
Hello,
@radovskyb Thank you very much for the great software!
I use Vagrant over Virtualbox with synced folder of type Virtualbox.
My host system is Windows 10 Creator's Update, my guest system is Ubuntu 16.04 with Go 1.8.3.
As described, watcher does not work with synced folders on Virtualbox if I trigger an event from the host OS in the synchronized folder while running watcher in the guest OS.
Is that logical? What should I do to workaround this?
Thank you! :)
Hello @radovskyb , hope you're fine, I'm having this error when I run a recursive watcher
error agregando directorio open C:\$RECYCLE.BIN\S-1-5-18: Access is denied.
that means error adding a directory, after that the file walker seems exit because it doesn't add any other file
i := 0
fmt.Println(len(w.WatchedFiles()))
fmt.Println("first 10 watched files are:")
for path,_ := range w.WatchedFiles(){
i+=1
fmt.Println(path)
if i>10{
break
}
}
//print 0
Could I ignore those files which cause error and keep running the file walker?
thank you so much!!!
Also, is it possible to add a new item to Event, like Source, so we can know which watch item in names related to the event.
isHidden_windows.go was throwing the error: "The system cannot find the path specified."
I discovered this was due to syscall.UTF16PtrFromString(pointer)
not working with file paths longer than 260 characters.*
I fixed it by changing syscall.UTF16PtrFromString(path)
to syscall.UTF16PtrFromString(fixLongPath(path))
, and adding an extra file containing this:
https://golang.org/src/os/path_windows.go?h=fixLongPath#L139
* https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file#maximum-path-length-limitation
Hi,
first of all, thanks for implementing a watcher, which is as small and concise, as I want it to be!!
I've found a little bug in my humble opinion, though:
If you start the watcher with keepalive flag and startcmd flag, and the command returns a non zero.
watcher -cmd="false" --keepalive --startcmd
Hi, radovskyb!
I am learning your code. I cannot understand these code :
go func() {
w.Wait()
w.TriggerEvent(watcher.Create, nil)
w.TriggerEvent(watcher.Remove, nil)
}()
watcher/example/basics/main.go
Lines 55 to 59 in 25a66c5
What I don't understand is why w.Wait()
can use more than once. I searched by google, but no useful answer. so, I hope get you help.
Thank you very much!
When encountering an error (in my case an ErrWatchedFileDeleted), the filewatcher seems to stop (even though it's not Closed, so I cannot restart it) and I'm not receiving any more events.
Is this a bug or am I not handling errors properly ?
My code looks very much like the examples provided, and I encounter this error because a temp file is quickly added and removed from the fs (and I just want to ignore it).
Thanks !
Apologies if it's a stupid question. My question is regarding this line in your code: https://github.com/radovskyb/watcher/blob/master/watcher.go#L197
Will this return files which are currently being copied? I work with very large files and I don't want this to fire off events before the file is actually done being created yet.
Thank you!
Hi, how can I know the renamed file name?
Where I rename a file, the event "created" is thrown but I get only the old name of the file. This is the code:
go func() {
defer wg.Done()
for {
select {
case event := <-w.Event:
fmt.Println(event)
fileType := func() string {
if event.IsDir() {
return "Directory"
} else {
return "File"
}
}
switch event.Op {
case watcher.Write:
s := fmt.Sprintf("Wrote %v: %v", fileType(), event.Name())
fmt.Println(s)
case watcher.Create:
s := fmt.Sprintf("Created %v: %v", fileType(), event.Name())
fmt.Println(s)
case watcher.Remove:
s := fmt.Sprintf("Removed %v: %v", fileType(), event.Name())
fmt.Println(s)
case watcher.Rename:
s := fmt.Sprintf("Renamed %v: %v", fileType(), event.Name())
fmt.Println(s)
case watcher.Chmod:
s := fmt.Sprintf("Chmoded %v: %v", fileType(), event.Name())
fmt.Println(s)
}
case err := <-w.Error:
log.Fatalln(err)
}
}
}()
I need to know the name of the file after it was renamed. I tried to comment out the line w.SetMaxEvents(1)
so I thought that All event would be sent but doesn't work
Thanks
Hello, hope you're great...I'm trying to follow the sample but I've two problems here
thank you so much
As per issue title, I believe it would be good to modify the events to return both the current file as well as the new one.
@radovskyb Currently watcher stops working when you delete the watched file/directory. It's a bumper for my upcoming release.
It gives an error.
2017-07-21 12:01:02.729 ERROR Watch error:error: watched file or folder deleted
Doesn't return any events there after. And If you call w.Close()
at this moment, program gets stuck.
Please let me know if you need further info.
PS: wether you call Close
or not, watcher stops working
It would seem that on windows there is no rename event but rather the following:
So there is never a RENAME event actually ever firing.
Is there any way we can close the watcher? I was thinking that since we have Start
, we could also have a Close
method.
There's a module for Node.js called chokidar
(https://github.com/paulmillr/chokidar) who does the following:
Methods and events
[...]
.close(): Removes all listeners from watched files
In my opinion, it'd make sense to have this in watcher
too.
Ignor hiden files not working on windows.
I belive strings.HasPrefix(info.Name(), ".") work on linux, but not on windows.
On windows system app get stok for example on c:\System Volume Information
How to avoid this problem?
2018/11/22 17:14:23 error: (EXTRA *os.PathError=open \......\d$\System Volume Information: Access is denied.)
WatchedFiles() creates a possible race condition. Reading the returned map while simultaneously calling any functions that mutate files (Add/AddRecursive/Remove/RemoveRecursive) is not thread safe. Locking the mutex in WatchedFiles does not help.
Hi...would be great if you could support a deep parameter, what do you think???
thank you...
I have a few questions regarding the library itself
I have a scenario, where I want to move a large file (4GB) to an S3 bucket once it's written to a watched directory on the local disk. The process of writing a file to the local directory is time-consuming.
I am waiting for the Create event and then I run 'move to s3 bucket' process, which will, unfortunately, only upload a portion of the file (as the rest of the file is still being written to the disk).
I have tested using Create and Write event, but those two trigger before a file is completed.
Look like we'd need close_write event to be implemented into the watcher, so it works similar to :
inotifywait -e close_write /path/to/file
https://unix.stackexchange.com/questions/176387/move-file-but-only-if-its-closed
I noticed that by default, calling Add()
on a directory will also watch any sub-directories, but not files within those sub-directories. That means if I have a directory structure like this:
โโโ root_dir
โย ย โโโ sub_dir
and then do this:
w := watcher.New()
...
w.Add('/root_dir')
And then run touch root_dir/sub_dir
, a WRITE
event is fired.
Just wondering.... is this by design? Or should nested dirs be ignored unless added explicitly or recursively?
This code will reproduce the problem:
package main
import (
"log"
"time"
"github.com/radovskyb/watcher"
)
const watchDir = "/Users/ay/tmp/watch"
func main() {
w := watcher.New()
go func() {
for {
select {
case event := <-w.Event:
log.Println(event, event.Name(), event.ModTime())
case err := <-w.Error:
panic(err)
}
}
}()
if err := w.Add(watchDir); err != nil {
panic(err)
}
if err := w.Start(time.Millisecond * 100); err != nil {
panic(err)
}
}
Run it, then in another terminal cd
to the watched directory (/Users/ay/tmp/watch
in this case) and execute touch 1; rm 1
, which will miss both events, or touch 1; echo "test" > 1
, which will miss the CREATE event.
Would be handy to be able to filter recursivley watching newly created files based on regular expressions. When watching a folder recursively it would be nice to be able to not set watches on subfolders or files that get created but match a regular expression added to the filters list.
Would it be possible for the watcher API to support Globs as arguments to the Add
and Ignore
methods? This would make watching specific directories for only certain file types much easier:
w.Add("./**/*.go")
Or is there a way to achieve this with the current interface?
It would be nice if there is an option to terminate the previous invocation of the command when the command is still executing and a file changes. This would be extremely useful when the command is a task that takes a while to complete and we do not wish to wait for it to finish and want to invoke the command immediate again.
Hi,
I want notificaiton to email. How it work via email?
Thanks.
It would be sweet to have an option to clear the screen so it doesn't create a busy looking list for while you're re-running tests. Via https://github.com/tj/go/blob/master/ansi/ansi.go#L6-L10
Currently shorter output of course just continues to push the text lower and lower, just takes a bit more time to figure out what is the relevant output.
How to figure out that a watching process started?
I have such construction. It don't like it. But it's pretty fast for me.
w := watcher.New()
...
go func () {
time.Sleep(time.Second) // now I use timer for sure
cmd := exec.Command(
"python",
"emulate_renderer.py",
d.req,
)
err := cmd.Run()
}()
// Start the watching process - it'll check for changes every 100ms.
if err := w.Start(time.Millisecond * 100); err != nil {
println(err.Error())
... // handling
}
...
Set some block element like chan int
w := watcher.New()
...
go func () {
<-w.Started // block until watcher will be started
cmd := exec.Command(
"python",
"emulate_renderer.py",
d.req,
)
err := cmd.Run()
}()
// Start the watching process - it'll check for changes every 100ms.
if err := w.Start(time.Millisecond * 100); err != nil {
println(err.Error())
... // handling
}
...
But channel is not very good for it. (I mean not so easy understandable)
Maybe more explicit will be approach like wg.Wait()
I saw that you wrote that you wanted to rewrite all Start code #6 (comment). Otherwise I could do try to figure out it and do a pull request.
Thanks for your work!
Sorry am very new to Go
based on the code below when an event occurred the first event will be "WRITE" from the parent folder followed by the event source.
Is there a way to identify the initial event
// Package watcher hey
package watcher
import (
"fmt"
"log"
"time"
"github.com/radovskyb/watcher"
)
//ExampleNewWatcher work
func ExampleNewWatcher() {
w := watcher.New()
// SetMaxEvents to 1 to allow at most 1 event's to be received
// on the Event channel per watching cycle.
//
// If SetMaxEvents is not set, the default is to send all events.
// w.SetMaxEvents(1)
// Only notify rename and move events.
w.FilterOps(watcher.Rename, watcher.Write, watcher.Create, watcher.Remove)
go func() {
for {
select {
case event := <-w.Event:
// if name := event.Op; name == watcher.Write {
fmt.Println(event.Path) // Print the event's info.
// fmt.Println(watcher.Write)
// }
case err := <-w.Error:
log.Fatalln(err)
case <-w.Closed:
return
}
}
}()
// Watch test_folder recursively for changes.
if err := w.AddRecursive("/home/akumzy/Documents"); err != nil {
log.Fatalln(err)
}
// Print a list of all of the files and folders currently
// being watched and their paths.
for path, f := range w.WatchedFiles() {
fmt.Printf("%s: %s\n", path, f.Name())
}
fmt.Println()
// Start the watching process - it'll check for changes every 100ms.
if err := w.Start(time.Millisecond * 100); err != nil {
log.Fatalln(err)
}
}
I spent 20 minutes to figure out why the basic example doesn't work when I write to a file. Then I found these lines:
watcher/example/basics/main.go
Lines 14 to 21 in 0d9d326
So only one event per second is allowed, and only renaming and moving are shown.
This will easily make beginners confused, because I suppose the basic example should notify every event for me like a Hello-World for a file notification library.
If it is necessary to show these advanced use, It's better to comment them.
@radovskyb Let me introduce myself. My name is Jeeva, I'm the author of aah framework.
For upcoming release v0.7
of my framework I'm working on Hot Reload functionality. I have been doing study and research around monitoring file and directory changes for a while. I know, there is a library fsnotify
, it is good one. Then I come across your library, pretty interesting. Good work.
I have evaluated your library for aah framework purpose and went through your code file. After that I'm leaning towards your library.
I would like to clarify following-
git tag
via gopkg.in
.If above points are not feasible (I'm hoping not), are you okay with forking your library into go-aah org.
Please let me know. Thanks.
Currently the-cmd
script is run once for each event, even when -pipe=true
is specified.
It would be great if there were an option to send all events from a single interval to the cmds stdin in one call, so that the cmd can do batch processing.
Thanks for implementing this tool. I've been trying to get watcher to follow up what is happening in a specific directory, and simply just print number of files left. I added the counter shell to the -cmd, but directly after first change happens in the directory, I get a SIGSEGV error.
watcher -dotfiles=false -recursive=false -cmd="./countzips.sh"
Watching 40 files
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x4b550c]
goroutine 6 [running]:
os.(*ProcessState).os.success(...)
/usr/local/go/src/os/exec_posix.go:74
os.(*ProcessState).Success(...)
/usr/local/go/src/os/exec.go:150
main.main.func1(0xc42007a240, 0xc42008c000, 0xc420058280, 0x7ffd9483e732, 0xe, 0xc42000a070, 0x1, 0x1, 0xc4200160a4, 0xc4200160a5)
$HOME/go/src/github.com/radovskyb/watcher/cmd/watcher/main.go:76 +0x49c
created by main.main
$HOME/go/src/github.com/radovskyb/watcher/cmd/watcher/main.go:56 +0x39a
go version go1.10.3 linux/amd64
The countzips.sh contains just ls -1 *.zip | wc -l
Is there something wrong with our Go installation, or did I just misunderstand how to utilize that cmd parameter? (Tried also having a go script for the counter, but got same message with that, too.) All help is greatly appreciated.
Hey, would you accept a PR to add support for an --ignore
flag? It would take a comma-separated list of paths which would not trigger the command specified by the user.
I little bit have mess in my head. I will modify it. Feel free ask questions before.
I will push code. It's not compilable and absolutely not workable:warning:. Just ideas.
update Sorry , there are a lot of mistypes.
Work flow got a awful monster.
Infinite loop is out of control.
Mutex hell.
I reviewed ec08eb0.
Your loop got more tricky.
I started to implement this patternhttps://blog.golang.org/pipelines. And implemented it as possible.
But after I've finished I got that it's redundant. Code got clearer with pipeline. But there are no really
advantages of using gorutines. WRITE and CHMOD operation can be parralel but these operations so light.
Gorutines are overhead for them. Only one advantage I liked
f, ok = <-w.pipeline // if chanel was closed ok was false
if ok {
} else {
return
}
But it can be easily done via extra explicit chanel.
CREATE, REMOVE, RENAME operation require be consequences. And anyway necessary have extra explicit chanel for
canceling them on the fly.
I decided that it's not good idea implement pipelines. But with using pipelines I got a lot of ideas.
First I describe my realization.
Check gorutines part only for example. Gorutines are redundant.
I believe that any call to os much longer then using any golang construction
Remove all mutexes ass possible. There are really some hell!
I start rewrite from Start function
func (w *Watcher) Start2(pollInterval time.Duration) error {
if pollInterval <= time.Millisecond {
pollInterval = time.Millisecond * 100
}
tick := time.Tick(pollInterval)
for i := 0; i < 4; i++ {
w.wg.Add(1)
go w.handler()
}
w.wg.Done()
for {
select {
case <-tick:
w.iteration()
case <-updateState:
w.someHow()
case <-w.close:
close(w.pipeline)
w.Destroy() //or w.Clean()
return
}
}
}
3.1 Laconic ass possible.
3.2 All managing via channels!
3.3 No mutexes!
3.4 No time.Sleep only ticker
3.5 ticker local . All stuff that can be local should be local
3.6 I don't sure really how much gorutines were necessary. In reality gorutines don't necessary.
func (w *Watcher) Iteration() {
w.retrieveFileList2() // I send fileInfo directly in retrieveFileList2
w.created = map[string]os.FileInfo{}
w.removed = w.files // should be links
w.wg.Wait()
// RENAMED, REMOVED, CREATED
// it should be sync operation I think
// RENAMED
// compare w.created and rest of items in w.files
// if event exist delete() item from both maps
// REMOVED
// check rest of items in w.files
// CREATED
// created check w.created
w.Event <- Event{}
}
type _fileInfo struct {
pathID string
fileInfo os.FileInfo
}
....
if err == nil {
for k, v := range list {
fileList[k] = v
for _k, _v := range list {
w.pipeline <- _fileInfo{_k, _v}
w.Add(1)
}
}
continue
}
I creted extratype _fileInfo for sending all data inside channel
func (w *Watcher) handler() {
var f _fileInfo
var ok bool
for {
f, ok = <-w.pipeline
if ok {
// check name in previous files
if fileInfo, found := w.files[f.key]; found {
// check Write and Chmode
// CHANGED
println(fileInfo[""].Name())
w.Event <- Event{}
delete(w.files, f.key)
} else {
w.created[f.key] = f.value
}
w.wg.Done()
} else {
return
}
}
}
I propose use the same flow but without gorutines and pipelane channel.
All files could be stored in map[string]os.FileInfo. Where key is absolute path to file. Now you store it as map[string]map[string]os.FileInfo.
It's too complicated and bad for algorithms.
I will suppose that w.files is *map[string]os.FileInfo
I don't believe that it will it a lot of memory. But definitely simpler and faster.
When you check WRITE and CHMOD. It should be done in the same time.
Hi..I'm curious: It's possible show the full path for the file change, right now with the samples I only get the file name
import sys
for line in sys.stdin:
print (line + " - python")
thanks!!
First thanks this is sweet! but for running tests I noticed if a test fails it'll exit entirely, an option to remain active would be handy
Hi
I am trying to build and restart a webserver on every file change using the command go run basic-page.go
It seems to work the first time I make a file change but then watcher hangs after this and it does not build and restart the webserver on any other changes.
This seems to only happen when i use this particular command go run basic-page.go
Any ideas why the watcher cannot run this command
Here is some code to help recreate the situation
watcher.go
package watcher
import (
"bytes"
"fmt"
"log"
"os/exec"
"time"
"github.com/radovskyb/watcher"
)
func command(binaryName string, args []string, entryPoint string) *exec.Cmd {
binary, lookErr := exec.LookPath(binaryName)
if lookErr != nil {
panic(lookErr)
}
cmd := exec.Command(binary, args...)
cmd.Dir = entryPoint
errBuf := &bytes.Buffer{}
cmd.Stderr = errBuf
err := cmd.Run()
if err != nil {
log.Printf("Command finished with error: %v", err)
}
return cmd
}
func main() {
w := watcher.New()
w.IgnoreHiddenFiles(true)
go func() {
for {
select {
case event := <-w.Event:
command("go", []string{"run", "webserver.go"}, ".")
fmt.Println(event)
case err := <-w.Error:
log.Fatalln(err)
case <-w.Closed:
return
}
}
}()
// Watch this folder for changes.
if err := w.Add("."); err != nil {
log.Fatalln(err)
}
fmt.Println()
// Start the watching process - it'll check for changes every 100ms.
if err := w.Start(time.Millisecond * 1); err != nil {
log.Fatalln(err)
}
}
webserver.go
package main
import (
"fmt"
"net/http"
)
func helloWorld(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World")
}
func main() {
http.HandleFunc("/", helloWorld)
http.ListenAndServe(":8080", nil)
}
When watching folders recursively, there might be some folders where I don't want the watcher to touch.
These folders can be dependencies
(which were downloaded by some package manager), folders meant to be used as example
, folders that keep the app tests
(not the case in Go happens in mostly of other languages).
So, I think that we could have some way to ignore those paths, so we don't increase our slice of paths/files that's being watched without necessity.
For example, say that right now I'm using the watcher in a rather simple web app. In this app I have two folders that contain the libs used for both development
(like tasks and suchs - that's in node_modules) and libs that I really use in the app (that's in bower_components).
The number of folders in node_modules
is: 825. Inside each of these folders there's a bunch of other folders/files.
The number of folders in bower_components
is: 27. Inside each of these, there are also a bunch of other folders/files.
So, when I watch the root of my app (which contains both of these folders), right off the bat, I'm watching thousands of stuff I either don't care or won't change - which makes the watcher take longer to report the events going on in the files I really want to watch.
I know I could solve that by specifying exactly what I want to watch, like: w.Add("path_1")
, w.Add("path_2")
, w.Add("path_3"), etc. But sometimes inside those paths there might be something I'd also like to ignore.
Either refactor way Options
work, using a real struct so the user can have something like this when creating a new watcher:
opt := &Options{
NonRecursive: true,
Ignore: []string{"a", "b", "c", "d/f/g/h"},
SomethingElse: false,
}
w := watcher.New(opt)
Or use the same approach, but not at the moment to create a watcher, but when the user wants to Add a path to be watched:
w := watcher.New()
optPath1 := &Options{
NonRecursive: true,
Ignore: []string{"a"},
SomethingElse: false,
}
optPath2 := &Options{
NonRecursive: false,
Ignore: []string{"a", "b", "c", "d/f/g/h"},
}
optPath3 := &Options{
Ignore: []string{"xyz/123"},
}
w.Add("path_1", optPath1)
w.Add("path_2", optPath2)
w.Add("path_3", optPath3)
Something I noticed while looking through the code
https://github.com/radovskyb/watcher/blob/master/watcher.go#L351-L356
There's a lock around accessing the field .files
, but the map itself is being returned, and it's not a sync.Map
. Any access to this returned map will not be goroutine safe. The correct pattern here would be to copy the map under the lock, and return the copy, or use a sync.Map
for the .files
field and return that.
Is there a possibility to automatically execute cmd at the start of watcher? I am trying to use watcher with multiple microservices and it's painful to change every service for the first time just to get everything up.
Is there any way to watch all files in given folder? Say I'm in the root of my app and I want to watch everything, is there such thing as passing a single dot (.
)? Some libs in node-land do that, not sure what's the convention for go?
Hi @radovskyb. Thanks for making this project available to the public!
I've found one minor issue when importing github.com/radovskyb/watcher
as a Go module. Go can't find releases tagged in this repo when the tag name does not include the leading v
character. Namely: tags 1.0.3
, 1.0.4
and 1.0.5
are missing the leading v
character. Go believes that v1.0.2
is the latest release since that's the highest version within the subset where the the leading v
character is present.
This is what I'm seeing:
$ go mod init github.com/sevein/foobar
$ go get github.com/radovskyb/[email protected]
go: finding github.com/radovskyb/watcher v1.0.5
go get github.com/radovskyb/[email protected]: unknown revision v1.0.5
$ go get github.com/radovskyb/watcher
go: finding github.com/radovskyb/watcher v1.0.2
go: downloading github.com/radovskyb/watcher v1.0.2
Tagging 3818ec2 as v1.0.5
(in addition to the existing tags) may be enough to solve the problem.
When there is link to a directory, containing a numer of files, watch does not recognice changes in that directory. Utilizing the same application to watch a dirctory without a link alle changed files will be recognized.
Is this a bug, or are filesystem links not supported?
Is this project support add/remove watch items at run time? Can I find the sample code?
I have a website and back-end database to save the watch items, so my code will check the database table to add/remove watch items when data changed.
Thanks.
I find it useful to have a FINISH event which fires when a newly created file finish being written. The use cases include automatically trigger processing when a file finishes downloading or being fully generated (e.g, some media file cannot be processed on the fly).
The filesystem event notifications cannot solve this and polling is somehow required, which matches this watcher's polling nature. Should be a helpful feature?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.