update tools
parent
d414c98466
commit
402ee25ef1
@ -0,0 +1,23 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
.idea
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
{description}
|
||||
Copyright (C) {year} {fullname}
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
{signature of Ty Coon}, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
@ -0,0 +1,79 @@
|
||||
mysql_binlog_utils
|
||||
==================
|
||||
|
||||
Some utilities for mysql binlog
|
||||
|
||||
WARNING: these utilities are tested for mysql 5.5.33, but are not tested for mysql 5.6+
|
||||
|
||||
Dump binlog from pos
|
||||
----
|
||||
`DumpBinlogFromPos(srcFilePath string, startPos int, targetFilePath string)`
|
||||
|
||||
This function will dump binlog (at `srcFilePath`) from pos (`startPos`), and the output is at `targetFilePath`
|
||||
|
||||
If the `startPos` is 0 or 4, the whole binlog will be dump. Otherwise, the source binlog header (including `FORMAT_DESCRIPTION_EVENT` & `ROTATE_EVENT` & `PREVIOUS_GTIDS_LOG_EVENT`) will be dump as the target binlog header, and then the source data will be dump from `startPos`
|
||||
|
||||
This will make target binlog complete and available to replay.
|
||||
|
||||
Rotate relay log
|
||||
----
|
||||
`RotateRelayLog(relayLogPath string, endPos int)`
|
||||
|
||||
This function will add a rotate event to a relay log (at `relayLogPath`), after the position (`endPos`), and truncate the data after the position (`endPos`)
|
||||
|
||||
WARNING: after manually rotate relay log, DONOT forget to update `relay-log.index`
|
||||
|
||||
Fake master server
|
||||
----
|
||||
`NewFakeMasterServer(port int, unusedServerId int, characterSet int, keepAliveWhenFinish bool, baseDir string)`
|
||||
|
||||
WARNING: only support mysql 5.5.x
|
||||
|
||||
This fake master server is very helpful if you want to replay some binlog files to a mysql instance, and you're afraid of `mysqlbinlog` (http://bugs.mysql.com/bug.php?id=33048, for example)
|
||||
|
||||
Mysql replication is [more reliable way](http://www.xaprb.com/blog/2010/09/04/why-mysql-replication-is-better-than-mysqlbinlog-for-recovery/) to replay binlog, what we need is :
|
||||
|
||||
1. server := NewFakeMasterServer(...)
|
||||
2. server.Start()
|
||||
3. In target mysql instance, `change master` to the fake server, and `start slave`
|
||||
4. the server will be closed when done or error
|
||||
5. you can abort the server by `server.Abort()`
|
||||
|
||||
####Some other features:
|
||||
|
||||
1. `start slave until` is also supported
|
||||
2. large packet (>= `1<<24-1` bytes) is supported
|
||||
3. multiple binlog files are supported, fake server will act as a real replication master (rotate to the next when one is finished)
|
||||
|
||||
####Arguments:
|
||||
|
||||
Argument|_
|
||||
--- | ---
|
||||
`port` | the fake server port
|
||||
`unusedServerId` | the fake server id, should not be duplicate with any other mysql instance
|
||||
`characterSet` |the fake server character set id, should be the same with target mysql instance's. You can get the id by `SELECT id, collation_name FROM information_schema.collations ORDER BY id`
|
||||
`keepAliveWhenFinish` |when false, the fake server will quit when all binlogs are replayed. when true, the fake server will wait for more binlogs.
|
||||
`baseDir` | where the binlog files are located
|
||||
|
||||
|
||||
Get unexecuted binlog files by gtid
|
||||
----
|
||||
|
||||
`GetUnexecutedBinlogFilesByGtid(binlogDir string, binlogBaseName string, executedGtidDesc string) (ret []string, err error)`
|
||||
|
||||
This function will search binlog files in `binlogDir`, for the one contains a event whose gtid is not contained in `executedGtidDesc`
|
||||
|
||||
The scenario is in mysql replication, if mysql master is broken, mysql slave can call `GetUnexecutedBinlogFilesByGtid` to search in mysql master binlog dir, for the binlog files which need to be replay in slave
|
||||
|
||||
|
||||
Get unexecuted binlog pos by gtid
|
||||
---
|
||||
|
||||
`GetUnexecutedBinlogPosByGtid(binlogFilePath string, executedGtidDesc string) (pos uint, err error) `
|
||||
|
||||
This function will search first binlog event pos, which is not contained in `executedGtidDesc`, in binlog file (`binlogFilePath`)
|
||||
|
||||
Or return `EOF` error if has no unexecuted binlog event
|
||||
|
||||
----
|
||||
####Pull requests and issues are warmly welcome
|
@ -0,0 +1 @@
|
||||
binlog starttime stoptime startpos stoppos rows duration tables
|
@ -0,0 +1,39 @@
|
||||
package binlog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var binlogFileSuffixPattern = regexp.MustCompile("\\d\\d\\d\\d\\d\\d$")
|
||||
|
||||
func NextBinlogPath(binlogPath string) (string, error) {
|
||||
r := regexp.MustCompile("(.*)\\.(\\d\\d\\d\\d\\d\\d)$")
|
||||
if !r.MatchString(binlogPath) {
|
||||
return "", fmt.Errorf("path %v is not a binlog path", binlogPath)
|
||||
}
|
||||
matches := r.FindStringSubmatch(binlogPath)
|
||||
seq, _ := strconv.Atoi(matches[2])
|
||||
return fmt.Sprintf("%v.%06d", matches[1], seq+1), nil
|
||||
}
|
||||
|
||||
func BinlogIndexPath(binlogPath string) (string, error) {
|
||||
r := regexp.MustCompile("(.*)\\.(\\d\\d\\d\\d\\d\\d)$")
|
||||
if !r.MatchString(binlogPath) {
|
||||
return "", fmt.Errorf("path %v is not a binlog path", binlogPath)
|
||||
}
|
||||
matches := r.FindStringSubmatch(binlogPath)
|
||||
return fmt.Sprintf("%v.index", matches[1]), nil
|
||||
}
|
||||
|
||||
func NextBinlogName(binlogPath string) (string, error) {
|
||||
if path, err := NextBinlogPath(binlogPath); nil != err {
|
||||
return "", err
|
||||
} else {
|
||||
path = strings.Replace(path, "\\", "/", -1) //fix windows-style path
|
||||
return filepath.Base(path), nil
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
binlog starttime stoptime startpos stoppos inserts updates deletes database table
|
@ -0,0 +1,109 @@
|
||||
package binlog
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
func innerDumpBinlogFromPos(srcFilePath string, startPos uint, dumpEmptyBinlog bool, targetFilePath string) error {
|
||||
tracef("dump binlog from pos : srcFilePath=%v, startPos=%v, targetFilePath=%v", srcFilePath, startPos, targetFilePath)
|
||||
srcFile, err := os.Open(srcFilePath)
|
||||
if nil != err {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
stat, err := srcFile.Stat()
|
||||
if nil != err {
|
||||
return err
|
||||
}
|
||||
srcFileSize := uint(stat.Size())
|
||||
|
||||
if dumpEmptyBinlog {
|
||||
startPos = srcFileSize
|
||||
}
|
||||
|
||||
if startPos > srcFileSize {
|
||||
return fmt.Errorf("startPos (%v) >= binlog file size (%v)", startPos, srcFileSize)
|
||||
}
|
||||
|
||||
emptyFile := startPos == srcFileSize
|
||||
headerBs := make([]byte, 19)
|
||||
headerEndPos := uint(4)
|
||||
|
||||
for {
|
||||
if _, err := srcFile.Seek(int64(headerEndPos), 0); nil != err {
|
||||
if "EOF" == err.Error() {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(srcFile, headerBs); nil != err {
|
||||
if "EOF" == err.Error() {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
eventType := int(headerBs[4])
|
||||
length := binary.LittleEndian.Uint32(headerBs[9:13])
|
||||
if FORMAT_DESCRIPTION_EVENT != eventType && ROTATE_EVENT != eventType && PREVIOUS_GTIDS_LOG_EVENT != eventType {
|
||||
break
|
||||
}
|
||||
headerEndPos += uint(length)
|
||||
}
|
||||
|
||||
if headerEndPos >= srcFileSize {
|
||||
emptyFile = true
|
||||
} else if startPos < headerEndPos {
|
||||
return fmt.Errorf("dump binlog from startPos (%v) failed, pos < headerEndPos (%v) ", startPos, headerEndPos)
|
||||
}
|
||||
|
||||
if target, err := os.Create(targetFilePath); nil != err {
|
||||
return err
|
||||
} else {
|
||||
defer target.Close()
|
||||
if _, err := srcFile.Seek(0, 0); nil != err {
|
||||
os.Remove(targetFilePath)
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.CopyN(target, srcFile, int64(headerEndPos)); nil != err {
|
||||
os.Remove(targetFilePath)
|
||||
return err
|
||||
}
|
||||
|
||||
if !emptyFile {
|
||||
if _, err := srcFile.Seek(int64(startPos), 0); nil != err {
|
||||
os.Remove(targetFilePath)
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(target, srcFile); nil != err {
|
||||
os.Remove(targetFilePath)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func DumpBinlogFromPos(srcFilePath string, startPos uint, targetFilePath string) error {
|
||||
return innerDumpBinlogFromPos(srcFilePath, startPos, false, targetFilePath)
|
||||
}
|
||||
|
||||
func DumpUnexecutedBinlogByGtid(srcFilePath string, executedGtidDesc string, targetFilePath string, includeEventBeforeFirst bool) error {
|
||||
pos, err := GetUnexecutedBinlogPosByGtid(srcFilePath, executedGtidDesc, includeEventBeforeFirst)
|
||||
if nil != err {
|
||||
if "EOF" == err.Error() {
|
||||
return innerDumpBinlogFromPos(srcFilePath, 0, true, targetFilePath)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return innerDumpBinlogFromPos(srcFilePath, pos, false, targetFilePath)
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package binlog
|
||||
|
||||
const (
|
||||
UNKNOWN_EVENT = 0
|
||||
START_EVENT_V3 = 1
|
||||
QUERY_EVENT = 2
|
||||
STOP_EVENT = 3
|
||||
ROTATE_EVENT = 4
|
||||
INTVAR_EVENT = 5
|
||||
LOAD_EVENT = 6
|
||||
SLAVE_EVENT = 7
|
||||
CREATE_FILE_EVENT = 8
|
||||
APPEND_BLOCK_EVENT = 9
|
||||
EXEC_LOAD_EVENT = 10
|
||||
DELETE_FILE_EVENT = 11
|
||||
NEW_LOAD_EVENT = 12
|
||||
RAND_EVENT = 13
|
||||
USER_VAR_EVENT = 14
|
||||
FORMAT_DESCRIPTION_EVENT = 15
|
||||
XID_EVENT = 16
|
||||
BEGIN_LOAD_QUERY_EVENT = 17
|
||||
EXECUTE_LOAD_QUERY_EVENT = 18
|
||||
TABLE_MAP_EVENT = 19
|
||||
PRE_GA_WRITE_ROWS_EVENT = 20
|
||||
PRE_GA_UPDATE_ROWS_EVENT = 21
|
||||
PRE_GA_DELETE_ROWS_EVENT = 22
|
||||
WRITE_ROWS_EVENT_V1 = 23
|
||||
UPDATE_ROWS_EVENT_V1 = 24
|
||||
DELETE_ROWS_EVENT_V1 = 25
|
||||
INCIDENT_EVENT = 26
|
||||
HEARTBEAT_LOG_EVENT = 27
|
||||
IGNORABLE_LOG_EVENT = 28
|
||||
ROWS_QUERY_LOG_EVENT = 29
|
||||
WRITE_ROWS_EVENT = 30
|
||||
UPDATE_ROWS_EVENT = 31
|
||||
DELETE_ROWS_EVENT = 32
|
||||
GTID_LOG_EVENT = 33
|
||||
ANONYMOUS_GTID_LOG_EVENT = 34
|
||||
PREVIOUS_GTIDS_LOG_EVENT = 35
|
||||
)
|
||||
|
||||
const (
|
||||
LOG_EVENT_FIXED_HEADER_LEN = 19
|
||||
MAX_ALLOWED_PACKET = 1024 * 1024 * 1024
|
||||
)
|
||||
|
||||
type EventFixedHeader struct {
|
||||
Bytes []byte
|
||||
Timestamp int
|
||||
EventType int
|
||||
ServerId int
|
||||
EventLength uint
|
||||
NextPosition int
|
||||
Flags int
|
||||
}
|
||||
|
||||
type EventFixedData struct {
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
type EventVariableData struct {
|
||||
Bytes []byte
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package binlog
|
||||
|
||||
import (
|
||||
"b612.me/mysql/gtid"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetAllGtidOfBinlogDir(binlogDir, binlogBaseName string) (gtidDesc string, err error) {
|
||||
files, err := ioutil.ReadDir(binlogDir)
|
||||
if nil != err {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var binlogFiles []string
|
||||
for _, file := range files {
|
||||
if strings.HasPrefix(file.Name(), binlogBaseName+".") && binlogFileSuffixPattern.MatchString(file.Name()) {
|
||||
binlogFiles = append(binlogFiles, file.Name())
|
||||
}
|
||||
}
|
||||
|
||||
if 0 == len(binlogFiles) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
lastFile := filepath.Join(binlogDir, binlogFiles[len(binlogFiles)-1])
|
||||
lastPreviousGtid, err := GetPreviousGtids(lastFile)
|
||||
if nil != err {
|
||||
return "", err
|
||||
}
|
||||
lastBinlogGtid, err := GetGtidOfBinlog(lastFile)
|
||||
if nil != err {
|
||||
return "", err
|
||||
}
|
||||
sum, err := gtid.Add(lastPreviousGtid, lastBinlogGtid)
|
||||
if nil != err {
|
||||
return "", err
|
||||
}
|
||||
return sum, nil
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package binlog
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetFirstPreviousGtidOfBinlogDir(binlogDir, binlogBaseName string) (gtidDesc string, err error) {
|
||||
files, err := ioutil.ReadDir(binlogDir)
|
||||
if nil != err {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var binlogFiles []string
|
||||
for _, file := range files {
|
||||
if strings.HasPrefix(file.Name(), binlogBaseName+".") && binlogFileSuffixPattern.MatchString(file.Name()) {
|
||||
binlogFiles = append(binlogFiles, file.Name())
|
||||
}
|
||||
}
|
||||
|
||||
if 0 == len(binlogFiles) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
firstFile := filepath.Join(binlogDir, binlogFiles[0])
|
||||
ret, err := GetPreviousGtids(firstFile)
|
||||
return ret, err
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package binlog
|
||||
|
||||
import (
|
||||
"b612.me/mysql/gtid"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
func GetGtidOfBinlog(binlogPath string) (gtidDesc string, err error) {
|
||||
file, err := os.Open(binlogPath)
|
||||
if nil != err {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
p := uint32(4)
|
||||
headerBs := make([]byte, 19)
|
||||
gtidBs := make([]byte, 25)
|
||||
myGtid, err := gtid.Parse(gtidDesc)
|
||||
if err != nil {
|
||||
return gtidDesc, err
|
||||
}
|
||||
for {
|
||||
if _, err := file.Seek(int64(p), 0); nil != err {
|
||||
if "EOF" == err.Error() {
|
||||
break
|
||||
}
|
||||
return myGtid.String(), err
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(file, headerBs); nil != err {
|
||||
if "EOF" == err.Error() {
|
||||
break
|
||||
}
|
||||
return myGtid.String(), err
|
||||
}
|
||||
|
||||
length := binary.LittleEndian.Uint32(headerBs[9:13])
|
||||
eventType := int(headerBs[4])
|
||||
|
||||
if GTID_LOG_EVENT != eventType {
|
||||
p += length
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(file, gtidBs); nil != err {
|
||||
if "EOF" == err.Error() {
|
||||
break
|
||||
}
|
||||
return myGtid.String(), err
|
||||
}
|
||||
|
||||
uuid := bytesToUuid(gtidBs[1:17])
|
||||
number := bytesToUint64(gtidBs[17:])
|
||||
if err = myGtid.AddGtid(uuid, number); nil != err {
|
||||
return myGtid.String(), err
|
||||
}
|
||||
|
||||
p += length
|
||||
}
|
||||
return myGtid.String(), nil
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package binlog
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func GetPreviousGtids(binlogPath string) (gtidDesc string, err error) {
|
||||
file, err := os.Open(binlogPath)
|
||||
if nil != err {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
p := uint32(4)
|
||||
headerBs := make([]byte, 19)
|
||||
payloadBs := make([]byte, 1024)
|
||||
|
||||
for {
|
||||
if _, err := file.Seek(int64(p), 0); nil != err {
|
||||
if "EOF" == err.Error() {
|
||||
break
|
||||
}
|
||||
return gtidDesc, err
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(file, headerBs); nil != err {
|
||||
if "EOF" == err.Error() {
|
||||
break
|
||||
}
|
||||
return gtidDesc, err
|
||||
}
|
||||
|
||||
length := binary.LittleEndian.Uint32(headerBs[9:13])
|
||||
eventType := int(headerBs[4])
|
||||
|
||||
if PREVIOUS_GTIDS_LOG_EVENT != eventType {
|
||||
p += length
|
||||
continue
|
||||
}
|
||||
|
||||
payloadLength := length - 19
|
||||
|
||||
if payloadLength > uint32(len(payloadBs)) {
|
||||
payloadBs = make([]byte, payloadLength)
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(file, payloadBs[:payloadLength]); nil != err {
|
||||
if "EOF" == err.Error() {
|
||||
break
|
||||
}
|
||||
return gtidDesc, err
|
||||
}
|
||||
|
||||
ret := ""
|
||||
sidNumberCount := bytesToUint(payloadBs[0:8])
|
||||
pos := 8
|
||||
for i := uint(0); i < sidNumberCount; i++ {
|
||||
if "" != ret {
|
||||
ret = ret + ","
|
||||
}
|
||||
uuid := bytesToUuid(payloadBs[pos : pos+16])
|
||||
ret = ret + uuid
|
||||
internalCount := bytesToUint(payloadBs[pos+16 : pos+16+8])
|
||||
pos = pos + 16 + 8
|
||||
for i := uint(0); i < internalCount; i++ {
|
||||
from := bytesToUint64(payloadBs[pos : pos+8])
|
||||
to := bytesToUint64(payloadBs[pos+8:pos+16]) - 1
|
||||
pos = pos + 16
|
||||
ret = ret + ":" + strconv.FormatUint(from, 10) + "-" + strconv.FormatUint(to, 10)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
return gtidDesc, nil
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package binlog
|
||||
|
||||
import (
|
||||
"b612.me/mysql/gtid"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetUnexecutedBinlogFilesByGtid(binlogDir string, binlogBaseName string, executedGtidDesc string, includeEventBeforeFirst bool) (
|
||||
ret []string, err error) {
|
||||
files, err := ioutil.ReadDir(binlogDir)
|
||||
if nil != err {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var binlogFiles []string
|
||||
for _, file := range files {
|
||||
if strings.HasPrefix(file.Name(), binlogBaseName+".") && binlogFileSuffixPattern.MatchString(file.Name()) {
|
||||
binlogFiles = append(binlogFiles, file.Name())
|
||||
}
|
||||
}
|
||||
|
||||
if 0 == len(binlogFiles) {
|
||||
return make([]string, 0), nil
|
||||
}
|
||||
executedGtid, err := gtid.Parse(executedGtidDesc)
|
||||
if nil != err {
|
||||
return nil, err
|
||||
}
|
||||
for i := len(binlogFiles) - 1; i >= 0; i-- {
|
||||
binlogFile := binlogFiles[i]
|
||||
previousGtids, err := GetPreviousGtids(filepath.Join(binlogDir, binlogFile))
|
||||
if nil != err {
|
||||
return nil, err
|
||||
}
|
||||
contain, err := executedGtid.Contain(previousGtids)
|
||||
if nil != err {
|
||||
return nil, err
|
||||
}
|
||||
eql, err := executedGtid.Equal(previousGtids)
|
||||
if nil != err {
|
||||
return nil, err
|
||||
}
|
||||
if contain && !(includeEventBeforeFirst && eql && "" != executedGtidDesc) {
|
||||
for j := i; j < len(binlogFiles); j++ {
|
||||
ret = append(ret, binlogFiles[j])
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("Found unexecuted gtid comparing with previousGtids of even first binlog")
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
package binlog
|
||||
|
||||
import (
|
||||
"b612.me/mysql/gtid"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
func GetUnexecutedBinlogPosByGtidAndAllGtid(binlogPath string, executedGtidDesc string, includeEventBeforeFirst bool) (pos uint, allgtid string, err error) {
|
||||
file, err := os.Open(binlogPath)
|
||||
if nil != err {
|
||||
return 0, "", err
|
||||
}
|
||||
defer file.Close()
|
||||
myGtid, err := gtid.Parse(allgtid)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
p := uint32(4)
|
||||
finUnexec := false
|
||||
retPos := uint32(0)
|
||||
headerBs := make([]byte, 19)
|
||||
payloadBs := make([]byte, 1024)
|
||||
lastExecutedGtidPos := uint32(0)
|
||||
executedGtid, err := gtid.Parse(executedGtidDesc)
|
||||
if nil != err {
|
||||
return 0, "", err
|
||||
}
|
||||
for {
|
||||
if _, err := file.Seek(int64(p), 0); nil != err {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(file, headerBs); nil != err {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
length := binary.LittleEndian.Uint32(headerBs[9:13])
|
||||
eventType := int(headerBs[4])
|
||||
|
||||
if GTID_LOG_EVENT != eventType {
|
||||
p += length
|
||||
continue
|
||||
}
|
||||
|
||||
payloadLength := length - 19
|
||||
if payloadLength > uint32(len(payloadBs)) {
|
||||
payloadBs = make([]byte, payloadLength)
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(file, payloadBs[:payloadLength]); nil != err {
|
||||
return 0, "", err
|
||||
}
|
||||
|
||||
uuid := bytesToUuid(payloadBs[1:17])
|
||||
number := bytesToUint64(payloadBs[17:25])
|
||||
if !finUnexec {
|
||||
contain, err := executedGtid.ContainGtid(uuid, number)
|
||||
if nil != err {
|
||||
return 0, "", err
|
||||
}
|
||||
if contain {
|
||||
lastExecutedGtidPos = p
|
||||
p += length
|
||||
} else {
|
||||
retPos = p
|
||||
if includeEventBeforeFirst && 0 != lastExecutedGtidPos {
|
||||
retPos = lastExecutedGtidPos
|
||||
}
|
||||
finUnexec = true
|
||||
}
|
||||
} else {
|
||||
p += length
|
||||
}
|
||||
if err = myGtid.AddGtid(uuid, number); nil != err {
|
||||
return 0, "", err
|
||||
}
|
||||
}
|
||||
if !finUnexec {
|
||||
return 0, myGtid.String(), io.EOF
|
||||
}
|
||||
return uint(retPos), myGtid.String(), nil
|
||||
}
|
||||
|
||||
func GetUnexecutedBinlogPosByGtid(binlogPath string, executedGtidDesc string, includeEventBeforeFirst bool) (pos uint, err error) {
|
||||
file, err := os.Open(binlogPath)
|
||||
if nil != err {
|
||||
return 0, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
p := uint32(4)
|
||||
headerBs := make([]byte, 19)
|
||||
payloadBs := make([]byte, 1024)
|
||||
lastExecutedGtidPos := uint32(0)
|
||||
executedGtid, err := gtid.Parse(executedGtidDesc)
|
||||
if nil != err {
|
||||
return 0, err
|
||||
}
|
||||
for {
|
||||
if _, err := file.Seek(int64(p), 0); nil != err {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(file, headerBs); nil != err {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
length := binary.LittleEndian.Uint32(headerBs[9:13])
|
||||
eventType := int(headerBs[4])
|
||||
|
||||
if GTID_LOG_EVENT != eventType {
|
||||
p += length
|
||||
continue
|
||||
}
|
||||
|
||||
payloadLength := length - 19
|
||||
if payloadLength > uint32(len(payloadBs)) {
|
||||
payloadBs = make([]byte, payloadLength)
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(file, payloadBs[:payloadLength]); nil != err {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
uuid := bytesToUuid(payloadBs[1:17])
|
||||
number := bytesToUint64(payloadBs[17:25])
|
||||
contain, err := executedGtid.ContainGtid(uuid, number)
|
||||
if nil != err {
|
||||
return 0, err
|
||||
}
|
||||
if contain {
|
||||
lastExecutedGtidPos = p
|
||||
p += length
|
||||
} else {
|
||||
retPos := p
|
||||
if includeEventBeforeFirst && 0 != lastExecutedGtidPos {
|
||||
retPos = lastExecutedGtidPos
|
||||
}
|
||||
return uint(retPos), nil
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package binlog
|
||||
|
||||
type Logger interface {
|
||||
Tracef(fmt string, args ...interface{})
|
||||
}
|
||||
|
||||
var l Logger
|
||||
|
||||
func tracef(fmt string, args ...interface{}) {
|
||||
if nil != l {
|
||||
l.Tracef(fmt+"\n", args...)
|
||||
}
|
||||
}
|
||||
|
||||
func SetLogger(logger Logger) {
|
||||
l = logger
|
||||
}
|
@ -0,0 +1,589 @@
|
||||
package binlog
|
||||
|
||||
import (
|
||||
"b612.me/mysql/gtid"
|
||||
"b612.me/staros"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/starainrt/go-mysql/replication"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TxDetail struct {
|
||||
StartPos int
|
||||
EndPos int
|
||||
RowCount int
|
||||
Timestamp int64
|
||||
Time time.Time
|
||||
Sql string
|
||||
Db string
|
||||
Table string
|
||||
SqlType string
|
||||
Rows [][]interface{}
|
||||
}
|
||||
|
||||
type Transaction struct {
|
||||
GTID string
|
||||
Timestamp int64
|
||||
Time time.Time
|
||||
StartPos int
|
||||
EndPos int
|
||||
Size int
|
||||
RowsCount int
|
||||
sqlOrigin []string
|
||||
Txs []TxDetail
|
||||
}
|
||||
|
||||
func (t Transaction) GetSqlOrigin() []string {
|
||||
return t.sqlOrigin
|
||||
}
|
||||
|
||||
func ParseBinlogFile(path string, fx func(transaction Transaction)) error {
|
||||
return parseOneBinlog(path, fx)
|
||||
}
|
||||
func parseOneBinlog(path string, fx func(Transaction)) error {
|
||||
if !staros.Exists(path) {
|
||||
return os.ErrNotExist
|
||||
}
|
||||
f, err := os.Open(path)
|
||||
if f != nil {
|
||||
defer f.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileTypeBytes := int64(4)
|
||||
|
||||
b := make([]byte, fileTypeBytes)
|
||||
// 读取binlog头
|
||||
if _, err = f.Read(b); err != nil {
|
||||
return err
|
||||
} else if !bytes.Equal(b, replication.BinLogFileHeader) {
|
||||
//不是binlog格式
|
||||
return err
|
||||
}
|
||||
// must not seek to other position, otherwise the program may panic because formatevent, table map event is skipped
|
||||
if _, err = f.Seek(fileTypeBytes, os.SEEK_SET); err != nil {
|
||||
return err
|
||||
}
|
||||
return parseBinlogDetail(f, fx)
|
||||
}
|
||||
|
||||
func parseBinlogDetail(r io.Reader, f func(Transaction)) error {
|
||||
parse := replication.NewBinlogParser()
|
||||
parse.SetParseTime(false)
|
||||
parse.SetUseDecimal(false)
|
||||
// process: 0, continue: 1, break: 2, EOF: 3
|
||||
var (
|
||||
err error
|
||||
n int64
|
||||
db string = ""
|
||||
tb string = ""
|
||||
sql string = ""
|
||||
sqlType string = ""
|
||||
rowCnt uint32 = 0
|
||||
tbMapPos uint32 = 0
|
||||
rows [][]interface{}
|
||||
)
|
||||
var tx Transaction
|
||||
currentGtid := ""
|
||||
for {
|
||||
headBuf := make([]byte, replication.EventHeaderSize)
|
||||
if _, err = io.ReadFull(r, headBuf); err == io.EOF {
|
||||
idx := 0
|
||||
for k, v := range tx.Txs {
|
||||
if v.SqlType != "query" && len(tx.sqlOrigin) > idx {
|
||||
v.Sql = tx.sqlOrigin[idx]
|
||||
idx++
|
||||
}
|
||||
tx.RowsCount += v.RowCount
|
||||
tx.Txs[k] = v
|
||||
}
|
||||
tx.Size = tx.EndPos - tx.StartPos
|
||||
if f != nil {
|
||||
f(tx)
|
||||
}
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var h *replication.EventHeader
|
||||
h, err = parse.ParseHeader(headBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//fmt.Printf("parsing %s %d %s\n", *binlog, h.LogPos, GetDatetimeStr(int64(h.Timestamp), int64(0), DATETIME_FORMAT))
|
||||
|
||||
if h.EventSize <= uint32(replication.EventHeaderSize) {
|
||||
err = fmt.Errorf("invalid event header, event size is %d, too small", h.EventSize)
|
||||
return err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if n, err = io.CopyN(&buf, r, int64(h.EventSize)-int64(replication.EventHeaderSize)); err != nil {
|
||||
err = fmt.Errorf("get event body err %v, need %d - %d, but got %d", err, h.EventSize, replication.EventHeaderSize, n)
|
||||
return err
|
||||
}
|
||||
|
||||
//h.Dump(os.Stdout)
|
||||
|
||||
data := buf.Bytes()
|
||||
var rawData []byte
|
||||
rawData = append(rawData, headBuf...)
|
||||
rawData = append(rawData, data...)
|
||||
|
||||
eventLen := int(h.EventSize) - replication.EventHeaderSize
|
||||
|
||||
if len(data) != eventLen {
|
||||
err = fmt.Errorf("invalid data size %d in event %s, less event length %d", len(data), h.EventType, eventLen)
|
||||
return err
|
||||
}
|
||||
|
||||
var e replication.Event
|
||||
e, err = parse.ParseEvent(h, data, rawData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if h.EventType == replication.TABLE_MAP_EVENT {
|
||||
tbMapPos = h.LogPos - h.EventSize // avoid mysqlbing mask the row event as unknown table row event
|
||||
}
|
||||
|
||||
//e.Dump(os.Stdout)
|
||||
|
||||
//binEvent := &replication.BinlogEvent{RawData: rawData, Header: h, Event: e}
|
||||
binEvent := &replication.BinlogEvent{Header: h, Event: e} // we donnot need raw data
|
||||
|
||||
db, tb, sqlType, sql, rowCnt, rows = GetDbTbAndQueryAndRowCntFromBinevent(binEvent)
|
||||
startPos := 0
|
||||
if sqlType == "query" || sqlType == "gtid" {
|
||||
startPos = int(h.LogPos - h.EventSize)
|
||||
//fmt.Println(h.Timestamp, h.LogPos-h.EventSize, h.LogPos, db, tb, "sql="+sql, rowCnt, sqlType)
|
||||
// cfg.StatChan <- BinEventStats{Timestamp: h.Timestamp, Binlog: *binlog, StartPos: h.LogPos - h.EventSize, StopPos: h.LogPos,
|
||||
// Database: db, Table: tb, QuerySql: sql, RowCnt: rowCnt, QueryType: sqlType}
|
||||
} else {
|
||||
startPos = int(tbMapPos)
|
||||
//fmt.Println(h.Timestamp, t bMapPos, h.LogPos, db, tb, "sql="+sql, rowCnt, sqlType)
|
||||
// cfg.StatChan <- BinEventStats{Timestamp: h.Timestamp, Binlog: *binlog, StartPos: tbMapPos, StopPos: h.LogPos,
|
||||
// Database: db, Table: tb, QuerySql: sql, RowCnt: rowCnt, QueryType: sqlType}
|
||||
}
|
||||
switch sqlType {
|
||||
case "gtid":
|
||||
if currentGtid != "" {
|
||||
idx := 0
|
||||
for k, v := range tx.Txs {
|
||||
if v.SqlType != "query" && len(tx.sqlOrigin) > idx {
|
||||
v.Sql = tx.sqlOrigin[idx]
|
||||
idx++
|
||||
}
|
||||
tx.RowsCount += v.RowCount
|
||||
tx.Txs[k] = v
|
||||
}
|
||||
tx.Size = tx.EndPos - tx.StartPos
|
||||
if f != nil {
|
||||
f(tx)
|
||||
}
|
||||
}
|
||||
currentGtid = sql
|
||||
tx = Transaction{
|
||||
GTID: sql,
|
||||
StartPos: startPos,
|
||||
Timestamp: int64(h.Timestamp),
|
||||
Time: time.Unix(int64(h.Timestamp), 0),
|
||||
}
|
||||
case "":
|
||||
tx.EndPos = int(h.LogPos)
|
||||
continue
|
||||
case "rowsquery":
|
||||
tx.EndPos = int(h.LogPos)
|
||||
tx.sqlOrigin = append(tx.sqlOrigin, sql)
|
||||
default:
|
||||
tx.EndPos = int(h.LogPos)
|
||||
tx.Txs = append(tx.Txs, TxDetail{
|
||||
StartPos: startPos,
|
||||
EndPos: int(h.LogPos),
|
||||
Db: db,
|
||||
Table: tb,
|
||||
Sql: sql,
|
||||
SqlType: sqlType,
|
||||
Rows: rows,
|
||||
RowCount: int(rowCnt),
|
||||
Timestamp: int64(h.Timestamp),
|
||||
Time: time.Unix(int64(h.Timestamp), 0),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func GetDbTbAndQueryAndRowCntFromBinevent(ev *replication.BinlogEvent) (string, string, string, string, uint32, [][]interface{}) {
|
||||
var (
|
||||
db string = ""
|
||||
tb string = ""
|
||||
sql string = ""
|
||||
sqlType string = ""
|
||||
rowCnt uint32 = 0
|
||||
rows [][]interface{}
|
||||
)
|
||||
|
||||
switch ev.Header.EventType {
|
||||
case replication.ANONYMOUS_GTID_EVENT:
|
||||
//ge := ev.Event.(*replication.GTIDEvent)
|
||||
sql = "anonymous-gtid-event:1"
|
||||
sqlType = "gtid"
|
||||
case replication.WRITE_ROWS_EVENTv1,
|
||||
replication.WRITE_ROWS_EVENTv2:
|
||||
|
||||
wrEvent := ev.Event.(*replication.RowsEvent)
|
||||
db = string(wrEvent.Table.Schema)
|
||||
tb = string(wrEvent.Table.Table)
|
||||
sqlType = "insert"
|
||||
rowCnt = uint32(len(wrEvent.Rows))
|
||||
rows = wrEvent.Rows
|
||||
case replication.UPDATE_ROWS_EVENTv1,
|
||||
replication.UPDATE_ROWS_EVENTv2:
|
||||
wrEvent := ev.Event.(*replication.RowsEvent)
|
||||
db = string(wrEvent.Table.Schema)
|
||||
tb = string(wrEvent.Table.Table)
|
||||
sqlType = "update"
|
||||
rowCnt = uint32(len(wrEvent.Rows)) / 2
|
||||
rows = wrEvent.Rows
|
||||
case replication.DELETE_ROWS_EVENTv1,
|
||||
replication.DELETE_ROWS_EVENTv2:
|
||||
|
||||
//replication.XID_EVENT,
|
||||
//replication.TABLE_MAP_EVENT:
|
||||
|
||||
wrEvent := ev.Event.(*replication.RowsEvent)
|
||||
db = string(wrEvent.Table.Schema)
|
||||
tb = string(wrEvent.Table.Table)
|
||||
sqlType = "delete"
|
||||
rowCnt = uint32(len(wrEvent.Rows))
|
||||
rows = wrEvent.Rows
|
||||
case replication.ROWS_QUERY_EVENT:
|
||||
queryEvent := ev.Event.(*replication.RowsQueryEvent)
|
||||
sql = string(queryEvent.Query)
|
||||
sqlType = "rowsquery"
|
||||
case replication.QUERY_EVENT:
|
||||
queryEvent := ev.Event.(*replication.QueryEvent)
|
||||
db = string(queryEvent.Schema)
|
||||
sql = string(queryEvent.Query)
|
||||
sqlType = "query"
|
||||
|
||||
case replication.MARIADB_GTID_EVENT:
|
||||
// For global transaction ID, used to start a new transaction event group, instead of the old BEGIN query event, and also to mark stand-alone (ddl).
|
||||
//https://mariadb.com/kb/en/library/gtid_event/
|
||||
sql = "begin"
|
||||
sqlType = "query"
|
||||
|
||||
case replication.XID_EVENT:
|
||||
// XID_EVENT represents commit。rollback transaction not in binlog
|
||||
sql = "commit"
|
||||
sqlType = "query"
|
||||
case replication.GTID_EVENT:
|
||||
ge := ev.Event.(*replication.GTIDEvent)
|
||||
gid, err := gtid.Parse(fmt.Sprintf("%s:%d", bytesToUuid(ge.SID), ge.GNO))
|
||||
if err == nil {
|
||||
sql = gid.String()
|
||||
}
|
||||
sqlType = "gtid"
|
||||
}
|
||||
return db, tb, sqlType, sql, rowCnt, rows
|
||||
|
||||
}
|
||||
|
||||
type BinlogFilter struct {
|
||||
IncludeGtid string
|
||||
ExcludeGtid string
|
||||
StartPos int
|
||||
EndPos int
|
||||
StartDate time.Time
|
||||
EndDate time.Time
|
||||
BigThan int
|
||||
SmallThan int
|
||||
}
|
||||
|
||||
func parseBinlogWithFilter(r io.Reader, parse *replication.BinlogParser, filter BinlogFilter, fn func(Transaction)) error {
|
||||
var inGtid, exGtid *gtid.Gtid
|
||||
var err error
|
||||
if filter.IncludeGtid != "" {
|
||||
inGtid, err = gtid.Parse(filter.IncludeGtid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if filter.ExcludeGtid != "" {
|
||||
exGtid, err = gtid.Parse(filter.ExcludeGtid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// process: 0, continue: 1, break: 2, EOF: 3
|
||||
var (
|
||||
n int64
|
||||
db string = ""
|
||||
tb string = ""
|
||||
sql string = ""
|
||||
sqlType string = ""
|
||||
rowCnt uint32 = 0
|
||||
tbMapPos uint32 = 0
|
||||
skipTillNext bool = false
|
||||
rows [][]interface{}
|
||||
)
|
||||
var tx Transaction
|
||||
|
||||
currentGtid := ""
|
||||
callFn := func(tx Transaction) {
|
||||
if fn == nil {
|
||||
return
|
||||
}
|
||||
if !filter.StartDate.IsZero() && filter.StartDate.After(tx.Time) {
|
||||
return
|
||||
}
|
||||
if !filter.EndDate.IsZero() && filter.EndDate.Before(tx.Time) {
|
||||
return
|
||||
}
|
||||
if filter.StartPos != 0 && filter.StartPos > tx.StartPos {
|
||||
return
|
||||
}
|
||||
if filter.EndPos != 0 && filter.EndPos < tx.EndPos {
|
||||
return
|
||||
}
|
||||
if filter.BigThan != 0 && filter.BigThan > tx.Size {
|
||||
return
|
||||
}
|
||||
if filter.SmallThan != 0 && filter.SmallThan < tx.Size {
|
||||
return
|
||||
}
|
||||
fn(tx)
|
||||
}
|
||||
for {
|
||||
headBuf := make([]byte, replication.EventHeaderSize)
|
||||
if _, err = io.ReadFull(r, headBuf); err == io.EOF {
|
||||
idx := 0
|
||||
for k, v := range tx.Txs {
|
||||
if v.SqlType != "query" && len(tx.sqlOrigin) > idx {
|
||||
v.Sql = tx.sqlOrigin[idx]
|
||||
idx++
|
||||
}
|
||||
tx.RowsCount += v.RowCount
|
||||
tx.Txs[k] = v
|
||||
}
|
||||
tx.Size = tx.EndPos - tx.StartPos
|
||||
callFn(tx)
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
var h *replication.EventHeader
|
||||
h, err = parse.ParseHeader(headBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//fmt.Printf("parsing %s %d %s\n", *binlog, h.LogPos, GetDatetimeStr(int64(h.Timestamp), int64(0), DATETIME_FORMAT))
|
||||
if h.EventSize <= uint32(replication.EventHeaderSize) {
|
||||
err = fmt.Errorf("invalid event header, event size is %d, too small", h.EventSize)
|
||||
return err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if n, err = io.CopyN(&buf, r, int64(h.EventSize)-int64(replication.EventHeaderSize)); err != nil {
|
||||
err = fmt.Errorf("get event body err %v, need %d - %d, but got %d", err, h.EventSize, replication.EventHeaderSize, n)
|
||||
return err
|
||||
}
|
||||
if skipTillNext && h.EventType != replication.GTID_EVENT {
|
||||
continue
|
||||
}
|
||||
//h.Dump(os.Stdout)
|
||||
|
||||
data := buf.Bytes()
|
||||
var rawData []byte
|
||||
rawData = append(rawData, headBuf...)
|
||||
rawData = append(rawData, data...)
|
||||
|
||||
eventLen := int(h.EventSize) - replication.EventHeaderSize
|
||||
|
||||
if len(data) != eventLen {
|
||||
err = fmt.Errorf("invalid data size %d in event %s, less event length %d", len(data), h.EventType, eventLen)
|
||||
return err
|
||||
}
|
||||
|
||||
var e replication.Event
|
||||
e, err = parse.ParseEvent(h, data, rawData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if h.EventType == replication.TABLE_MAP_EVENT {
|
||||
tbMapPos = h.LogPos - h.EventSize // avoid mysqlbing mask the row event as unknown table row event
|
||||
}
|
||||
|
||||
//e.Dump(os.Stdout)
|
||||
|
||||
//binEvent := &replication.BinlogEvent{RawData: rawData, Header: h, Event: e}
|
||||
binEvent := &replication.BinlogEvent{Header: h, Event: e} // we donnot need raw data
|
||||
|
||||
db, tb, sqlType, sql, rowCnt, rows = GetDbTbAndQueryAndRowCntFromBinevent(binEvent)
|
||||
startPos := 0
|
||||
if sqlType == "query" || sqlType == "gtid" {
|
||||
startPos = int(h.LogPos - h.EventSize)
|
||||
//fmt.Println(h.Timestamp, h.LogPos-h.EventSize, h.LogPos, db, tb, "sql="+sql, rowCnt, sqlType)
|
||||
// cfg.StatChan <- BinEventStats{Timestamp: h.Timestamp, Binlog: *binlog, StartPos: h.LogPos - h.EventSize, StopPos: h.LogPos,
|
||||
// Database: db, Table: tb, QuerySql: sql, RowCnt: rowCnt, QueryType: sqlType}
|
||||
} else {
|
||||
startPos = int(tbMapPos)
|
||||
//fmt.Println(h.Timestamp, t bMapPos, h.LogPos, db, tb, "sql="+sql, rowCnt, sqlType)
|
||||
// cfg.StatChan <- BinEventStats{Timestamp: h.Timestamp, Binlog: *binlog, StartPos: tbMapPos, StopPos: h.LogPos,
|
||||
// Database: db, Table: tb, QuerySql: sql, RowCnt: rowCnt, QueryType: sqlType}
|
||||
}
|
||||
switch sqlType {
|
||||
case "gtid":
|
||||
if skipTillNext {
|
||||
skipTillNext = false
|
||||
}
|
||||
if currentGtid != "" {
|
||||
idx := 0
|
||||
for k, v := range tx.Txs {
|
||||
if v.SqlType != "query" && len(tx.sqlOrigin) > idx {
|
||||
v.Sql = tx.sqlOrigin[idx]
|
||||
idx++
|
||||
}
|
||||
tx.RowsCount += v.RowCount
|
||||
tx.Txs[k] = v
|
||||
}
|
||||
tx.Size = tx.EndPos - tx.StartPos
|
||||
callFn(tx)
|
||||
}
|
||||
currentGtid = sql
|
||||
if inGtid != nil {
|
||||
if c, _ := inGtid.Contain(sql); !c {
|
||||
currentGtid = ""
|
||||
skipTillNext = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
if exGtid != nil {
|
||||
if c, _ := exGtid.Contain(sql); c {
|
||||
currentGtid = ""
|
||||
skipTillNext = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
tx = Transaction{
|
||||
GTID: sql,
|
||||
StartPos: startPos,
|
||||
Timestamp: int64(h.Timestamp),
|
||||
Time: time.Unix(int64(h.Timestamp), 0),
|
||||
}
|
||||
case "":
|
||||
tx.EndPos = int(h.LogPos)
|
||||
continue
|
||||
case "rowsquery":
|
||||
tx.EndPos = int(h.LogPos)
|
||||
tx.sqlOrigin = append(tx.sqlOrigin, sql)
|
||||
default:
|
||||
tx.EndPos = int(h.LogPos)
|
||||
tx.Txs = append(tx.Txs, TxDetail{
|
||||
StartPos: startPos,
|
||||
EndPos: int(h.LogPos),
|
||||
Db: db,
|
||||
Table: tb,
|
||||
Sql: sql,
|
||||
SqlType: sqlType,
|
||||
Rows: rows,
|
||||
RowCount: int(rowCnt),
|
||||
Timestamp: int64(h.Timestamp),
|
||||
Time: time.Unix(int64(h.Timestamp), 0),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ParseBinlogWithFilter(path string, pos int64, filter BinlogFilter, fx func(Transaction)) error {
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
if !staros.Exists(path) {
|
||||
return os.ErrNotExist
|
||||
}
|
||||
f, err := os.Open(path)
|
||||
if f != nil {
|
||||
defer f.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parse := replication.NewBinlogParser()
|
||||
parse.SetParseTime(false)
|
||||
parse.SetUseDecimal(false)
|
||||
seekZore := func() error {
|
||||
fileTypeBytes := int64(4)
|
||||
b := make([]byte, fileTypeBytes)
|
||||
// 读取binlog头
|
||||
if _, err = f.Read(b); err != nil {
|
||||
return err
|
||||
} else if !bytes.Equal(b, replication.BinLogFileHeader) {
|
||||
//不是binlog格式
|
||||
return err
|
||||
}
|
||||
// must not seek to other position, otherwise the program may panic because formatevent, table map event is skipped
|
||||
if _, err = f.Seek(fileTypeBytes, os.SEEK_SET); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if pos != 0 {
|
||||
if err = seekZore(); err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
headBuf := make([]byte, replication.EventHeaderSize)
|
||||
if _, err = io.ReadFull(f, headBuf); err != nil {
|
||||
return err
|
||||
}
|
||||
var h *replication.EventHeader
|
||||
h, err = parse.ParseHeader(headBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if h.EventSize <= uint32(replication.EventHeaderSize) {
|
||||
err = fmt.Errorf("invalid event header, event size is %d, too small", h.EventSize)
|
||||
return err
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if n, err := io.CopyN(&buf, f, int64(h.EventSize)-int64(replication.EventHeaderSize)); err != nil {
|
||||
err = fmt.Errorf("get event body err %v, need %d - %d, but got %d", err, h.EventSize, replication.EventHeaderSize, n)
|
||||
return err
|
||||
}
|
||||
data := buf.Bytes()
|
||||
var rawData []byte
|
||||
rawData = append(rawData, headBuf...)
|
||||
rawData = append(rawData, data...)
|
||||
eventLen := int(h.EventSize) - replication.EventHeaderSize
|
||||
if len(data) != eventLen {
|
||||
err = fmt.Errorf("invalid data size %d in event %s, less event length %d", len(data), h.EventType, eventLen)
|
||||
return err
|
||||
}
|
||||
_, err = parse.ParseEvent(h, data, rawData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if h.EventType == replication.FORMAT_DESCRIPTION_EVENT || h.EventType == replication.GTID_EVENT {
|
||||
break
|
||||
}
|
||||
}
|
||||
if _, err = f.Seek(pos, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if pos == 0 {
|
||||
if err = seekZore(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return parseBinlogWithFilter(f, parse, filter, fx)
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package binlog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func intToBytes(num int, buf []byte) []byte {
|
||||
for i := 0; i < len(buf); i++ {
|
||||
buf[i] = byte(num & 0xff)
|
||||
num = num >> 8
|
||||
}
|
||||
return buf
|
||||
}
|
||||
func uintToBytes(num uint, buf []byte) []byte {
|
||||
for i := 0; i < len(buf); i++ {
|
||||
buf[i] = byte(num & 0xff)
|
||||
num = num >> 8
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
func stringNullToBytes(a string) []byte {
|
||||
ret := []byte(a)
|
||||
ret = append(ret, byte(0))
|
||||
return ret
|
||||
}
|
||||
|
||||
func bytesToUint(buf []byte) uint {
|
||||
var a uint
|
||||
var i uint
|
||||
for _, b := range buf {
|
||||
a += uint(b) << i
|
||||
i += 8
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func bytesToUint64(buf []byte) uint64 {
|
||||
var a uint64
|
||||
var i uint
|
||||
for _, b := range buf {
|
||||
a += uint64(b) << i
|
||||
i += 8
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
type tBytesToUuidCache struct {
|
||||
mutex sync.RWMutex
|
||||
bs []byte
|
||||
uuid string
|
||||
}
|
||||
|
||||
var bytesToUuidCache tBytesToUuidCache
|
||||
|
||||
func bytesToUuid(buf []byte) (ret string) {
|
||||
bytesToUuidCache.mutex.RLock()
|
||||
if 0 == bytes.Compare(buf, bytesToUuidCache.bs) {
|
||||
bytesToUuidCache.mutex.RUnlock()
|
||||
return bytesToUuidCache.uuid
|
||||
}
|
||||
bytesToUuidCache.mutex.RUnlock()
|
||||
uuid := strings.ToUpper(hex.EncodeToString(buf))
|
||||
bytesToUuidCache.mutex.Lock()
|
||||
bytesToUuidCache.bs = make([]byte, len(buf))
|
||||
copy(bytesToUuidCache.bs, buf)
|
||||
bytesToUuidCache.uuid = uuid
|
||||
bytesToUuidCache.mutex.Unlock()
|
||||
return uuid
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package binlog
|
||||
|
||||
func GenBinlogEventBytes(fh EventFixedHeader, fd EventFixedData, vd EventVariableData) ([]byte, error) {
|
||||
eventLength := LOG_EVENT_FIXED_HEADER_LEN + len(fd.Bytes) + len(vd.Bytes)
|
||||
fh.EventLength = uint(eventLength)
|
||||
|
||||
buf := make([]byte, eventLength)
|
||||
intToBytes(fh.Timestamp, buf[0:4])
|
||||
intToBytes(fh.EventType, buf[4:5])
|
||||
intToBytes(fh.ServerId, buf[5:9])
|
||||
uintToBytes(fh.EventLength, buf[9:13])
|
||||
intToBytes(fh.NextPosition, buf[13:17])
|
||||
intToBytes(fh.Flags, buf[17:19])
|
||||
copy(buf[19:19+len(fd.Bytes)-1], fd.Bytes)
|
||||
copy(buf[19+len(fd.Bytes):], vd.Bytes)
|
||||
return buf, nil
|
||||
}
|
@ -0,0 +1,569 @@
|
||||
package gtid
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Parse(gtidDesc string) (*Gtid, error) {
|
||||
return parse(gtidDesc)
|
||||
}
|
||||
func parse(gtidDesc string) (*Gtid, error) {
|
||||
var gtid Gtid
|
||||
gtid.hashMap = make(map[string]int)
|
||||
gtidDesc = strings.TrimSpace(gtidDesc)
|
||||
if "" == gtidDesc {
|
||||
return >id, nil
|
||||
}
|
||||
return >id, gtid.uniform(gtidDesc)
|
||||
}
|
||||
|
||||
func (g *Gtid) uniform(gtidDesc string) error {
|
||||
hashMap := make(map[string][]string)
|
||||
gtidlists := strings.Split(gtidDesc, ",")
|
||||
uuidLists := make([]string, 0, len(gtidlists))
|
||||
for _, v := range gtidlists {
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
numbers := strings.Split(v, ":")
|
||||
if len(numbers) < 2 {
|
||||
return fmt.Errorf("invalid format:%v", v)
|
||||
}
|
||||
uuid, err := g.isValidUUID(numbers[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
numbers = numbers[1:]
|
||||
existsNums, ok := hashMap[uuid]
|
||||
if ok {
|
||||
for _, v := range existsNums {
|
||||
numbers = append(numbers, v)
|
||||
}
|
||||
hashMap[uuid] = numbers
|
||||
continue
|
||||
}
|
||||
hashMap[uuid] = numbers
|
||||
uuidLists = append(uuidLists, uuid)
|
||||
}
|
||||
sort.Strings(uuidLists)
|
||||
g.uuidNumbers = make([]UUIDNumber, 0, len(uuidLists))
|
||||
for k, v := range uuidLists {
|
||||
number, err := g.uniformNumber(hashMap[v])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
number, err = g.uniformRange(number)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.mu.Lock()
|
||||
g.hashMap[v] = k
|
||||
g.mu.Unlock()
|
||||
g.uuidNumbers = append(g.uuidNumbers, UUIDNumber{
|
||||
uuid: v,
|
||||
intervals: number,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Gtid) reform() {
|
||||
sort.Sort(SortUUID(g.uuidNumbers))
|
||||
g.hashMap = make(map[string]int, len(g.uuidNumbers))
|
||||
for k, v := range g.uuidNumbers {
|
||||
g.uuidNumbers[k].intervals, _ = g.uniformRange(v.intervals)
|
||||
g.hashMap[v.uuid] = k
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (g *Gtid) uniformRange(rg []uuidRange) ([]uuidRange, error) {
|
||||
newRg := make([]uuidRange, 0, len(rg))
|
||||
sort.Sort(SortRange(rg))
|
||||
var last *uuidRange = nil
|
||||
for _, v := range rg {
|
||||
if last != nil && v.min <= last.max+1 {
|
||||
if last.max <= v.max {
|
||||
last.max = v.max
|
||||
}
|
||||
continue
|
||||
}
|
||||
newRg = append(newRg, v)
|
||||
last = &newRg[len(newRg)-1]
|
||||
}
|
||||
return newRg, nil
|
||||
}
|
||||
|
||||
func subRange(origin, rg []uuidRange) []uuidRange {
|
||||
newRg := make([]uuidRange, 0)
|
||||
sort.Sort(SortRange(rg))
|
||||
sort.Sort(SortRange(origin))
|
||||
for i := 0; i < len(origin); i++ {
|
||||
ori := origin[i]
|
||||
res := make([]uuidRange, 0)
|
||||
shouldAdd := true
|
||||
for _, v := range rg {
|
||||
if v.min <= ori.min && v.max >= ori.max {
|
||||
shouldAdd = false
|
||||
break
|
||||
}
|
||||
if v.max < ori.min {
|
||||
continue
|
||||
}
|
||||
if v.min > ori.max {
|
||||
break
|
||||
}
|
||||
|
||||
ur1 := uuidRange{
|
||||
min: ori.min,
|
||||
max: v.min - 1,
|
||||
}
|
||||
ur2 := uuidRange{
|
||||
min: v.max + 1,
|
||||
max: ori.max,
|
||||
}
|
||||
if ur1.max >= ur1.min {
|
||||
res = append(res, ur1)
|
||||
}
|
||||
if ur2.max >= ur2.min {
|
||||
res = append(res, ur2)
|
||||
}
|
||||
|
||||
}
|
||||
if len(res) == 0 && shouldAdd {
|
||||
res = append(res, ori)
|
||||
}
|
||||
newRg = append(newRg, res...)
|
||||
}
|
||||
return newRg
|
||||
}
|
||||
|
||||
func containRange(origin, rg []uuidRange) bool {
|
||||
sort.Sort(SortRange(rg))
|
||||
sort.Sort(SortRange(origin))
|
||||
for i := 0; i < len(rg); i++ {
|
||||
ft := rg[i]
|
||||
found := false
|
||||
for _, v := range origin {
|
||||
if v.min <= ft.min && v.max >= ft.max {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func overlapRange(origin, rg []uuidRange) bool {
|
||||
sort.Sort(SortRange(rg))
|
||||
sort.Sort(SortRange(origin))
|
||||
for i := 0; i < len(origin); i++ {
|
||||
ori := origin[i]
|
||||
for _, v := range rg {
|
||||
if v.min <= ori.min && v.max >= ori.max {
|
||||
return true
|
||||
}
|
||||
if v.max < ori.min {
|
||||
continue
|
||||
}
|
||||
if v.min > ori.max {
|
||||
break
|
||||
}
|
||||
|
||||
ur1 := uuidRange{
|
||||
min: ori.min,
|
||||
max: v.min - 1,
|
||||
}
|
||||
ur2 := uuidRange{
|
||||
min: v.max + 1,
|
||||
max: ori.max,
|
||||
}
|
||||
if ur1.max >= ur1.min {
|
||||
return true
|
||||
}
|
||||
if ur2.max >= ur2.min {
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (g *Gtid) uniformNumber(num []string) ([]uuidRange, error) {
|
||||
rg := make([]uuidRange, 0, len(num))
|
||||
for _, v := range num {
|
||||
ret := uuidRange{}
|
||||
if splitPos := strings.Index(v, "-"); -1 != splitPos {
|
||||
firstPart := string(v[0:splitPos])
|
||||
if i64, err := strconv.ParseUint(firstPart, 10, 64); nil == err {
|
||||
ret.min = i64
|
||||
} else {
|
||||
return rg, fmt.Errorf("invalid number %v", firstPart)
|
||||
}
|
||||
secondPart := string(v[splitPos+1:])
|
||||
if i64, err := strconv.ParseUint(secondPart, 10, 64); nil == err {
|
||||
ret.max = i64
|
||||
} else {
|
||||
return rg, fmt.Errorf("invalid number %v", secondPart)
|
||||
}
|
||||
} else {
|
||||
if i64, err := strconv.ParseUint(v, 10, 64); nil == err {
|
||||
ret.min = i64
|
||||
ret.max = i64
|
||||
} else {
|
||||
return rg, fmt.Errorf("invalid number %v", v)
|
||||
}
|
||||
}
|
||||
rg = append(rg, ret)
|
||||
}
|
||||
return rg, nil
|
||||
}
|
||||
|
||||
func (g *Gtid) isValidUUID(uuid string) (string, error) {
|
||||
uuid = strings.TrimSpace(strings.ToLower(strings.Replace(uuid, "-", "", -1)))
|
||||
if 32 != len(uuid) {
|
||||
return "", errors.New("invalid uuid" + uuid)
|
||||
}
|
||||
return uuid[0:8] + "-" + uuid[8:12] + "-" + uuid[12:16] + "-" + uuid[16:20] + "-" + uuid[20:], nil
|
||||
}
|
||||
|
||||
func (g *Gtid) String() (ret string) {
|
||||
|
||||
if g.changed {
|
||||
g.mu.Lock()
|
||||
g.reform()
|
||||
g.mu.Unlock()
|
||||
g.changed = false
|
||||
}
|
||||
for _, uuidNumber := range g.uuidNumbers {
|
||||
s := uuidNumber.uuid
|
||||
for _, interval := range uuidNumber.intervals {
|
||||
if interval.min == interval.max {
|
||||
s = s + ":" + strconv.FormatUint(interval.min, 10)
|
||||
} else {
|
||||
s = s + ":" + strconv.FormatUint(interval.min, 10) + "-" + strconv.FormatUint(interval.max, 10)
|
||||
}
|
||||
}
|
||||
if "" != ret {
|
||||
ret = ret + ","
|
||||
}
|
||||
ret = ret + s
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (g *Gtid) AddGtid(uuid string, number uint64) error {
|
||||
if strings.TrimSpace(uuid) == "" {
|
||||
return nil
|
||||
}
|
||||
uuid, err := g.isValidUUID(uuid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.mu.RLock()
|
||||
id, ok := g.hashMap[uuid]
|
||||
g.mu.RUnlock()
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
if ok {
|
||||
tmp := g.uuidNumbers[id].intervals
|
||||
if len(tmp) > 0 && tmp[len(tmp)-1].max+1 == number {
|
||||
g.uuidNumbers[id].intervals[len(tmp)-1].max = number
|
||||
} else {
|
||||
g.uuidNumbers[id].intervals = append(g.uuidNumbers[id].intervals, uuidRange{
|
||||
min: number,
|
||||
max: number,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
g.uuidNumbers = append(g.uuidNumbers, UUIDNumber{
|
||||
uuid: uuid,
|
||||
intervals: []uuidRange{uuidRange{
|
||||
min: number,
|
||||
max: number,
|
||||
}},
|
||||
})
|
||||
g.hashMap[uuid] = len(g.uuidNumbers) - 1
|
||||
}
|
||||
if len(g.uuidNumbers[id].intervals) > 5000 {
|
||||
g.reform()
|
||||
}
|
||||
g.changed = true
|
||||
return nil
|
||||
}
|
||||
func (g *Gtid) Add(gtidDesc string) error {
|
||||
return g.add(gtidDesc)
|
||||
}
|
||||
func (g *Gtid) add(gtidDesc string) error {
|
||||
tmpSlice := make([]UUIDNumber, 0)
|
||||
gtidlists := strings.Split(gtidDesc, ",")
|
||||
for _, v := range gtidlists {
|
||||
numbers := strings.Split(v, ":")
|
||||
if len(numbers) < 2 {
|
||||
return fmt.Errorf("invalid format:%v", v)
|
||||
}
|
||||
uuid, err := g.isValidUUID(numbers[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
num, err := g.uniformNumber(numbers[1:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmpSlice = append(tmpSlice, UUIDNumber{
|
||||
uuid: uuid,
|
||||
intervals: num,
|
||||
})
|
||||
}
|
||||
g.changed = true
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
for _, v := range tmpSlice {
|
||||
id, ok := g.hashMap[v.uuid]
|
||||
if ok {
|
||||
n := g.uuidNumbers[id].intervals
|
||||
n = append(n, v.intervals...)
|
||||
g.uuidNumbers[id].intervals = n
|
||||
} else {
|
||||
g.uuidNumbers = append(g.uuidNumbers, v)
|
||||
g.hashMap[v.uuid] = len(g.uuidNumbers) - 1
|
||||
}
|
||||
|
||||
if len(g.uuidNumbers[id].intervals) > 5000 {
|
||||
g.reform()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (g *Gtid) Sub(gtidDesc string) error {
|
||||
return g.sub(gtidDesc)
|
||||
}
|
||||
func (g *Gtid) sub(gtidDesc string) error {
|
||||
if strings.TrimSpace(gtidDesc) == "" {
|
||||
return nil
|
||||
}
|
||||
tmpSlice := make([]UUIDNumber, 0)
|
||||
gtidlists := strings.Split(gtidDesc, ",")
|
||||
for _, v := range gtidlists {
|
||||
numbers := strings.Split(v, ":")
|
||||
if len(numbers) < 2 {
|
||||
return fmt.Errorf("invalid format:%v", v)
|
||||
}
|
||||
uuid, err := g.isValidUUID(numbers[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
num, err := g.uniformNumber(numbers[1:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmpSlice = append(tmpSlice, UUIDNumber{
|
||||
uuid: uuid,
|
||||
intervals: num,
|
||||
})
|
||||
}
|
||||
g.changed = true
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
for _, v := range tmpSlice {
|
||||
id, ok := g.hashMap[v.uuid]
|
||||
if ok {
|
||||
n := subRange(g.uuidNumbers[id].intervals, v.intervals)
|
||||
n, _ = g.uniformRange(n)
|
||||
if len(n) == 0 || (len(n) == 1 && n[0].max == 0) {
|
||||
delete(g.hashMap, v.uuid)
|
||||
g.uuidNumbers = append(g.uuidNumbers[:id], g.uuidNumbers[id+1:]...)
|
||||
for k, v := range g.uuidNumbers {
|
||||
g.hashMap[v.uuid] = k
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
g.uuidNumbers[id].intervals = n
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
if len(g.uuidNumbers[id].intervals) > 5000 {
|
||||
g.reform()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Gtid) ContainGtid(uuid string, number uint64) (bool, error) {
|
||||
if strings.TrimSpace(uuid) == "" {
|
||||
return true, nil
|
||||
}
|
||||
uuid, err := g.isValidUUID(uuid)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
id, ok := g.hashMap[uuid]
|
||||
if ok {
|
||||
if g.changed {
|
||||
g.reform()
|
||||
}
|
||||
for _, v := range g.uuidNumbers[id].intervals {
|
||||
if v.min <= number && v.max >= number {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (g *Gtid) Contain(gtidDesc string) (bool, error) {
|
||||
return g.contain(gtidDesc)
|
||||
}
|
||||
|
||||
func (g *Gtid) contain(gtidDesc string) (bool, error) {
|
||||
if strings.TrimSpace(gtidDesc) == "" {
|
||||
return true, nil
|
||||
}
|
||||
tmpSlice := make([]UUIDNumber, 0)
|
||||
gtidlists := strings.Split(gtidDesc, ",")
|
||||
for _, v := range gtidlists {
|
||||
numbers := strings.Split(v, ":")
|
||||
if len(numbers) < 2 {
|
||||
return false, fmt.Errorf("invalid format:%v", v)
|
||||
}
|
||||
uuid, err := g.isValidUUID(numbers[0])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
num, err := g.uniformNumber(numbers[1:])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
tmpSlice = append(tmpSlice, UUIDNumber{
|
||||
uuid: uuid,
|
||||
intervals: num,
|
||||
})
|
||||
}
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
for _, v := range tmpSlice {
|
||||
id, ok := g.hashMap[v.uuid]
|
||||
if ok {
|
||||
if !containRange(g.uuidNumbers[id].intervals, v.intervals) {
|
||||
return false, nil
|
||||
}
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
func (g *Gtid) Overlap(gtidDesc string) (bool, error) {
|
||||
return g.overlap(gtidDesc)
|
||||
}
|
||||
func (g *Gtid) overlap(gtidDesc string) (bool, error) {
|
||||
if strings.TrimSpace(gtidDesc) == "" {
|
||||
if g.String() == "" {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
tmpSlice := make([]UUIDNumber, 0)
|
||||
gtidlists := strings.Split(gtidDesc, ",")
|
||||
for _, v := range gtidlists {
|
||||
numbers := strings.Split(v, ":")
|
||||
if len(numbers) < 2 {
|
||||
return false, fmt.Errorf("invalid format:%v", v)
|
||||
}
|
||||
uuid, err := g.isValidUUID(numbers[0])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
num, err := g.uniformNumber(numbers[1:])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
tmpSlice = append(tmpSlice, UUIDNumber{
|
||||
uuid: uuid,
|
||||
intervals: num,
|
||||
})
|
||||
}
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
for _, v := range tmpSlice {
|
||||
id, ok := g.hashMap[v.uuid]
|
||||
if ok {
|
||||
if overlapRange(g.uuidNumbers[id].intervals, v.intervals) {
|
||||
return true, nil
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (g *Gtid) Equal(gtidDesc string) (bool, error) {
|
||||
if g.String() == "" && strings.TrimSpace(gtidDesc) == "" {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
equ, err := parse(gtidDesc)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return g.String() == equ.String(), nil
|
||||
}
|
||||
|
||||
func (g *Gtid) Clone() *Gtid {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
var newGtid = &Gtid{}
|
||||
newGtid.uuidNumbers = make([]UUIDNumber, len(g.uuidNumbers))
|
||||
copy(newGtid.uuidNumbers, g.uuidNumbers)
|
||||
newGtid.hashMap = make(map[string]int, len(g.hashMap))
|
||||
for k, v := range g.hashMap {
|
||||
newGtid.hashMap[k] = v
|
||||
}
|
||||
if g.changed {
|
||||
newGtid.reform()
|
||||
}
|
||||
return newGtid
|
||||
}
|
||||
|
||||
func (g *Gtid) EventCount() uint64 {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
if g.changed {
|
||||
g.reform()
|
||||
}
|
||||
var ret uint64 = 0
|
||||
for _, uuidNumber := range g.uuidNumbers {
|
||||
for _, interval := range uuidNumber.intervals {
|
||||
ret += interval.max - interval.min + 1
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (g *Gtid) EventList() []string {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
if g.changed {
|
||||
g.reform()
|
||||
}
|
||||
eventList := []string{}
|
||||
for _, uuidNumber := range g.uuidNumbers {
|
||||
for _, interval := range uuidNumber.intervals {
|
||||
for i := interval.min; i <= interval.max; i++ {
|
||||
eventList = append(eventList, fmt.Sprintf("%s:%d", uuidNumber.uuid, i))
|
||||
}
|
||||
}
|
||||
}
|
||||
return eventList
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package gtid
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Add(gtidDesc0, gtidDesc1 string) (string, error) {
|
||||
gtid, err := Parse(gtidDesc0 + "," + gtidDesc1)
|
||||
if nil != err {
|
||||
return "", err
|
||||
}
|
||||
return gtid.String(), nil
|
||||
}
|
||||
|
||||
func Sub(gtidDesc0, gtidDesc1 string) (string, error) {
|
||||
if strings.TrimSpace(gtidDesc0) == "" {
|
||||
return "", nil
|
||||
}
|
||||
gtid, err := parse(gtidDesc0)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if strings.TrimSpace(gtidDesc1) == "" {
|
||||
return gtid.String(), nil
|
||||
}
|
||||
err = gtid.sub(gtidDesc1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return gtid.String(), nil
|
||||
}
|
||||
|
||||
func Contain(gtidDesc0, gtidDesc1 string) (bool, error) {
|
||||
if strings.TrimSpace(gtidDesc0) == "" {
|
||||
if strings.TrimSpace(gtidDesc1) == "" {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
gtid, err := parse(gtidDesc0)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return gtid.Contain(gtidDesc1)
|
||||
}
|
||||
|
||||
func Overlap(gtidDesc0, gtidDesc1 string) (bool, error) {
|
||||
if strings.TrimSpace(gtidDesc0) == "" {
|
||||
if strings.TrimSpace(gtidDesc1) == "" {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
gtid, err := parse(gtidDesc0)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return gtid.Overlap(gtidDesc1)
|
||||
}
|
||||
|
||||
func Equal(gtidDesc0, gtidDesc1 string) (bool, error) {
|
||||
if strings.TrimSpace(gtidDesc0) == "" {
|
||||
if strings.TrimSpace(gtidDesc1) == "" {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
gtid, err := parse(gtidDesc0)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return gtid.Equal(gtidDesc1)
|
||||
}
|
||||
|
||||
func EventCount(gtidDesc string) (uint64, error) {
|
||||
gtid, err := Parse(gtidDesc)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return gtid.EventCount(), nil
|
||||
}
|
||||
|
||||
func EventList(gtidDesc string) ([]string, error) {
|
||||
gtid, err := Parse(gtidDesc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return gtid.EventList(), nil
|
||||
}
|
||||
|
||||
func Uniform(gtidDesc string) (string, error) {
|
||||
gtid, err := Parse(gtidDesc)
|
||||
if err != nil {
|
||||
return gtidDesc, err
|
||||
}
|
||||
return gtid.String(), nil
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package gtid
|
||||
|
||||
import "sync"
|
||||
|
||||
type Gtid struct {
|
||||
uuidNumbers []UUIDNumber
|
||||
hashMap map[string]int
|
||||
changed bool
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
type UUIDNumber struct {
|
||||
uuid string
|
||||
intervals []uuidRange
|
||||
}
|
||||
|
||||
type uuidRange struct {
|
||||
min uint64
|
||||
max uint64
|
||||
}
|
||||
|
||||
type SortUUID []UUIDNumber
|
||||
|
||||
func (s SortUUID) Len() int { return len(s) }
|
||||
func (s SortUUID) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s SortUUID) Less(i, j int) bool { return s[i].uuid < s[j].uuid }
|
||||
|
||||
|
||||
type SortRange []uuidRange
|
||||
|
||||
func (s SortRange) Len() int { return len(s) }
|
||||
func (s SortRange) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s SortRange) Less(i, j int) bool { return s[i].min < s[j].min }
|
@ -0,0 +1,614 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"b612.me/starcrypto"
|
||||
"b612.me/stario"
|
||||
"b612.me/starnet"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ClientCommon struct {
|
||||
alive atomic.Value
|
||||
status Status
|
||||
byeFromServer bool
|
||||
conn net.Conn
|
||||
mu sync.Mutex
|
||||
msgID uint64
|
||||
queue *starnet.StarQueue
|
||||
stopFn context.CancelFunc
|
||||
stopCtx context.Context
|
||||
parallelNum int
|
||||
maxReadTimeout time.Duration
|
||||
maxWriteTimeout time.Duration
|
||||
keyExchangeFn func(c Client) error
|
||||
linkFns map[string]func(message *Message)
|
||||
defaultFns func(message *Message)
|
||||
msgEn func([]byte, []byte) []byte
|
||||
msgDe func([]byte, []byte) []byte
|
||||
noFinSyncMsgPool sync.Map
|
||||
handshakeRsaPubKey []byte
|
||||
SecretKey []byte
|
||||
noFinSyncMsgMaxKeepSeconds int
|
||||
lastHeartbeat int64
|
||||
heartbeatPeriod time.Duration
|
||||
wg stario.WaitGroup
|
||||
netType NetType
|
||||
showError bool
|
||||
skipKeyExchange bool
|
||||
useHeartBeat bool
|
||||
sequenceDe func([]byte) (interface{}, error)
|
||||
sequenceEn func(interface{}) ([]byte, error)
|
||||
debugMode bool
|
||||
}
|
||||
|
||||
func (c *ClientCommon) Connect(network string, addr string) error {
|
||||
if c.alive.Load().(bool) {
|
||||
return errors.New("client already run")
|
||||
}
|
||||
c.stopCtx, c.stopFn = context.WithCancel(context.Background())
|
||||
c.queue = starnet.NewQueueCtx(c.stopCtx, 4)
|
||||
conn, err := net.Dial(network, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.alive.Store(true)
|
||||
c.status.Alive = true
|
||||
c.conn = conn
|
||||
if c.useHeartBeat {
|
||||
go c.Heartbeat()
|
||||
}
|
||||
return c.clientPostInit()
|
||||
}
|
||||
|
||||
func (c *ClientCommon) DebugMode(dmg bool) {
|
||||
c.mu.Lock()
|
||||
c.debugMode = dmg
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func (c *ClientCommon) IsDebugMode() bool {
|
||||
return c.debugMode
|
||||
}
|
||||
|
||||
func (c *ClientCommon) ConnectTimeout(network string, addr string, timeout time.Duration) error {
|
||||
if c.alive.Load().(bool) {
|
||||
return errors.New("client already run")
|
||||
}
|
||||
c.stopCtx, c.stopFn = context.WithCancel(context.Background())
|
||||
c.queue = starnet.NewQueueCtx(c.stopCtx, 4)
|
||||
conn, err := net.DialTimeout(network, addr, timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.alive.Store(true)
|
||||
c.status.Alive = true
|
||||
c.conn = conn
|
||||
if c.useHeartBeat {
|
||||
go c.Heartbeat()
|
||||
}
|
||||
return c.clientPostInit()
|
||||
}
|
||||
|
||||
func (c *ClientCommon) monitorPool() {
|
||||
for {
|
||||
select {
|
||||
case <-c.stopCtx.Done():
|
||||
c.noFinSyncMsgPool.Range(func(k, v interface{}) bool {
|
||||
data := v.(WaitMsg)
|
||||
close(data.Reply)
|
||||
c.noFinSyncMsgPool.Delete(k)
|
||||
return true
|
||||
})
|
||||
return
|
||||
case <-time.After(time.Second * 30):
|
||||
}
|
||||
now := time.Now()
|
||||
if c.noFinSyncMsgMaxKeepSeconds > 0 {
|
||||
c.noFinSyncMsgPool.Range(func(k, v interface{}) bool {
|
||||
data := v.(WaitMsg)
|
||||
if data.Time.Add(time.Duration(c.noFinSyncMsgMaxKeepSeconds) * time.Second).Before(now) {
|
||||
close(data.Reply)
|
||||
c.noFinSyncMsgPool.Delete(k)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ClientCommon) SkipExchangeKey() bool {
|
||||
return c.skipKeyExchange
|
||||
}
|
||||
|
||||
func (c *ClientCommon) SetSkipExchangeKey(val bool) {
|
||||
c.skipKeyExchange = val
|
||||
}
|
||||
|
||||
func (c *ClientCommon) clientPostInit() error {
|
||||
go c.readMessage()
|
||||
go c.loadMessage()
|
||||
if !c.skipKeyExchange {
|
||||
err := c.keyExchangeFn(c)
|
||||
if err != nil {
|
||||
c.alive.Store(false)
|
||||
c.mu.Lock()
|
||||
c.status = Status{
|
||||
Alive: false,
|
||||
Reason: "key exchange failed",
|
||||
Err: err,
|
||||
}
|
||||
c.mu.Unlock()
|
||||
c.stopFn()
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func NewClient() Client {
|
||||
var client = ClientCommon{
|
||||
maxReadTimeout: 0,
|
||||
maxWriteTimeout: 0,
|
||||
sequenceEn: encode,
|
||||
sequenceDe: Decode,
|
||||
keyExchangeFn: aesRsaHello,
|
||||
SecretKey: defaultAesKey,
|
||||
handshakeRsaPubKey: defaultRsaPubKey,
|
||||
msgEn: defaultMsgEn,
|
||||
msgDe: defaultMsgDe,
|
||||
}
|
||||
client.alive.Store(false)
|
||||
//heartbeat should not controlable for user
|
||||
client.useHeartBeat = true
|
||||
client.heartbeatPeriod = time.Second * 20
|
||||
client.linkFns = make(map[string]func(*Message))
|
||||
client.defaultFns = func(message *Message) {
|
||||
return
|
||||
}
|
||||
client.wg = stario.NewWaitGroup(0)
|
||||
client.stopCtx, client.stopFn = context.WithCancel(context.Background())
|
||||
return &client
|
||||
}
|
||||
|
||||
func (c *ClientCommon) Heartbeat() {
|
||||
failedCount := 0
|
||||
for {
|
||||
select {
|
||||
case <-c.stopCtx.Done():
|
||||
return
|
||||
case <-time.After(c.heartbeatPeriod):
|
||||
}
|
||||
_, err := c.sendWait(TransferMsg{
|
||||
ID: 10000,
|
||||
Key: "heartbeat",
|
||||
Value: nil,
|
||||
Type: MSG_SYS_WAIT,
|
||||
}, time.Second*5)
|
||||
if err == nil {
|
||||
c.lastHeartbeat = time.Now().Unix()
|
||||
failedCount = 0
|
||||
}
|
||||
if c.debugMode {
|
||||
fmt.Println("failed to recv heartbeat,timeout!")
|
||||
}
|
||||
failedCount++
|
||||
if failedCount >= 3 {
|
||||
if c.debugMode {
|
||||
fmt.Println("heatbeat failed more than 3 times,stop client")
|
||||
}
|
||||
c.alive.Store(false)
|
||||
c.mu.Lock()
|
||||
c.status = Status{
|
||||
Alive: false,
|
||||
Reason: "heartbeat failed more than 3 times",
|
||||
Err: errors.New("heartbeat failed more than 3 times"),
|
||||
}
|
||||
c.mu.Unlock()
|
||||
c.stopFn()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ClientCommon) ShowError(std bool) {
|
||||
c.mu.Lock()
|
||||
c.showError = std
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func (c *ClientCommon) readMessage() {
|
||||
for {
|
||||
select {
|
||||
case <-c.stopCtx.Done():
|
||||
c.conn.Close()
|
||||
return
|
||||
default:
|
||||
}
|
||||
data := make([]byte, 8192)
|
||||
if c.maxReadTimeout.Seconds() != 0 {
|
||||
if err := c.conn.SetReadDeadline(time.Now().Add(c.maxReadTimeout)); err != nil {
|
||||
//TODO:ALERT
|
||||
}
|
||||
}
|
||||
readNum, err := c.conn.Read(data)
|
||||
if err == os.ErrDeadlineExceeded {
|
||||
if readNum != 0 {
|
||||
c.queue.ParseMessage(data[:readNum], "b612")
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
if c.showError || c.debugMode {
|
||||
fmt.Println("client read error", err)
|
||||
}
|
||||
c.alive.Store(false)
|
||||
c.mu.Lock()
|
||||
c.status = Status{
|
||||
Alive: false,
|
||||
Reason: "client read error",
|
||||
Err: err,
|
||||
}
|
||||
c.mu.Unlock()
|
||||
c.stopFn()
|
||||
continue
|
||||
}
|
||||
c.queue.ParseMessage(data[:readNum], "b612")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ClientCommon) sayGoodBye() error {
|
||||
_, err := c.sendWait(TransferMsg{
|
||||
ID: 10010,
|
||||
Key: "bye",
|
||||
Value: nil,
|
||||
Type: MSG_SYS_WAIT,
|
||||
}, time.Second*3)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ClientCommon) loadMessage() {
|
||||
for {
|
||||
select {
|
||||
case <-c.stopCtx.Done():
|
||||
//say goodbye
|
||||
if !c.byeFromServer {
|
||||
c.sayGoodBye()
|
||||
}
|
||||
c.conn.Close()
|
||||
return
|
||||
case data, ok := <-c.queue.RestoreChan():
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
c.wg.Add(1)
|
||||
go func(data starnet.MsgQueue) {
|
||||
defer c.wg.Done()
|
||||
//fmt.Println("c received:", float64(time.Now().UnixNano()-nowd)/1000000)
|
||||
now := time.Now()
|
||||
//transfer to Msg
|
||||
msg, err := c.sequenceDe(c.msgDe(c.SecretKey, data.Msg))
|
||||
if err != nil {
|
||||
if c.showError || c.debugMode {
|
||||
fmt.Println("client decode data error", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
message := Message{
|
||||
ServerConn: c,
|
||||
TransferMsg: msg.(TransferMsg),
|
||||
NetType: NET_CLIENT,
|
||||
}
|
||||
message.Time = now
|
||||
c.dispatchMsg(message)
|
||||
}(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ClientCommon) dispatchMsg(message Message) {
|
||||
switch message.TransferMsg.Type {
|
||||
case MSG_SYS_WAIT:
|
||||
fallthrough
|
||||
case MSG_SYS:
|
||||
c.sysMsg(message)
|
||||
return
|
||||
case MSG_KEY_CHANGE:
|
||||
fallthrough
|
||||
case MSG_SYS_REPLY:
|
||||
fallthrough
|
||||
case MSG_SYNC_REPLY:
|
||||
data, ok := c.noFinSyncMsgPool.Load(message.ID)
|
||||
if ok {
|
||||
wait := data.(WaitMsg)
|
||||
wait.Reply <- message
|
||||
c.noFinSyncMsgPool.Delete(message.ID)
|
||||
return
|
||||
}
|
||||
//return
|
||||
fallthrough
|
||||
default:
|
||||
}
|
||||
callFn := func(fn func(*Message)) {
|
||||
fn(&message)
|
||||
}
|
||||
fn, ok := c.linkFns[message.Key]
|
||||
if ok {
|
||||
callFn(fn)
|
||||
}
|
||||
if c.defaultFns != nil {
|
||||
callFn(c.defaultFns)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ClientCommon) sysMsg(message Message) {
|
||||
switch message.Key {
|
||||
case "bye":
|
||||
if message.TransferMsg.Type == MSG_SYS_WAIT {
|
||||
//fmt.Println("recv stop signal from server")
|
||||
c.byeFromServer = true
|
||||
message.Reply(nil)
|
||||
}
|
||||
c.alive.Store(false)
|
||||
c.mu.Lock()
|
||||
c.status = Status{
|
||||
Alive: false,
|
||||
Reason: "recv stop signal from server",
|
||||
Err: nil,
|
||||
}
|
||||
c.mu.Unlock()
|
||||
c.stopFn()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ClientCommon) SetDefaultLink(fn func(message *Message)) {
|
||||
c.defaultFns = fn
|
||||
}
|
||||
|
||||
func (c *ClientCommon) SetLink(key string, fn func(*Message)) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.linkFns[key] = fn
|
||||
}
|
||||
|
||||
func (c *ClientCommon) send(msg TransferMsg) (WaitMsg, error) {
|
||||
var wait WaitMsg
|
||||
if msg.Type != MSG_SYNC_REPLY && msg.Type != MSG_KEY_CHANGE && msg.Type != MSG_SYS_REPLY || msg.ID == 0 {
|
||||
msg.ID = atomic.AddUint64(&c.msgID, 1)
|
||||
}
|
||||
data, err := c.sequenceEn(msg)
|
||||
if err != nil {
|
||||
return WaitMsg{}, err
|
||||
}
|
||||
data = c.msgEn(c.SecretKey, data)
|
||||
data = c.queue.BuildMessage(data)
|
||||
if c.maxWriteTimeout.Seconds() != 0 {
|
||||
c.conn.SetWriteDeadline(time.Now().Add(c.maxWriteTimeout))
|
||||
}
|
||||
_, err = c.conn.Write(data)
|
||||
if err == nil && (msg.Type == MSG_SYNC_ASK || msg.Type == MSG_KEY_CHANGE || msg.Type == MSG_SYS_WAIT) {
|
||||
wait.Time = time.Now()
|
||||
wait.TransferMsg = msg
|
||||
wait.Reply = make(chan Message, 1)
|
||||
c.noFinSyncMsgPool.Store(msg.ID, wait)
|
||||
}
|
||||
return wait, err
|
||||
}
|
||||
|
||||
func (c *ClientCommon) Send(key string, value MsgVal) error {
|
||||
_, err := c.send(TransferMsg{
|
||||
Key: key,
|
||||
Value: value,
|
||||
Type: MSG_ASYNC,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ClientCommon) sendWait(msg TransferMsg, timeout time.Duration) (Message, error) {
|
||||
data, err := c.send(msg)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
if timeout.Seconds() == 0 {
|
||||
msg, ok := <-data.Reply
|
||||
if !ok {
|
||||
return msg, os.ErrInvalid
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
close(data.Reply)
|
||||
c.noFinSyncMsgPool.Delete(data.TransferMsg.ID)
|
||||
return Message{}, os.ErrDeadlineExceeded
|
||||
case <-c.stopCtx.Done():
|
||||
return Message{}, errors.New("service shutdown")
|
||||
case msg, ok := <-data.Reply:
|
||||
if !ok {
|
||||
return msg, os.ErrInvalid
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ClientCommon) sendCtx(msg TransferMsg, ctx context.Context) (Message, error) {
|
||||
data, err := c.send(msg)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
close(data.Reply)
|
||||
c.noFinSyncMsgPool.Delete(data.TransferMsg.ID)
|
||||
return Message{}, os.ErrDeadlineExceeded
|
||||
case <-c.stopCtx.Done():
|
||||
return Message{}, errors.New("service shutdown")
|
||||
case msg, ok := <-data.Reply:
|
||||
if !ok {
|
||||
return msg, os.ErrInvalid
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ClientCommon) SendObjCtx(ctx context.Context, key string, val interface{}) (Message, error) {
|
||||
data, err := c.sequenceEn(val)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
return c.sendCtx(TransferMsg{
|
||||
Key: key,
|
||||
Value: data,
|
||||
Type: MSG_SYNC_ASK,
|
||||
}, ctx)
|
||||
}
|
||||
|
||||
func (c *ClientCommon) SendObj(key string, val interface{}) error {
|
||||
data, err := encode(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = c.send(TransferMsg{
|
||||
Key: key,
|
||||
Value: data,
|
||||
Type: MSG_ASYNC,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ClientCommon) SendCtx(ctx context.Context, key string, value MsgVal) (Message, error) {
|
||||
return c.sendCtx(TransferMsg{
|
||||
Key: key,
|
||||
Value: value,
|
||||
Type: MSG_SYNC_ASK,
|
||||
}, ctx)
|
||||
}
|
||||
|
||||
func (c *ClientCommon) SendWait(key string, value MsgVal, timeout time.Duration) (Message, error) {
|
||||
return c.sendWait(TransferMsg{
|
||||
Key: key,
|
||||
Value: value,
|
||||
Type: MSG_SYNC_ASK,
|
||||
}, timeout)
|
||||
}
|
||||
|
||||
func (c *ClientCommon) SendWaitObj(key string, value interface{}, timeout time.Duration) (Message, error) {
|
||||
data, err := c.sequenceEn(value)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
return c.SendWait(key, data, timeout)
|
||||
}
|
||||
|
||||
func (c *ClientCommon) Reply(m Message, value MsgVal) error {
|
||||
return m.Reply(value)
|
||||
}
|
||||
|
||||
func (c *ClientCommon) ExchangeKey(newKey []byte) error {
|
||||
pubKey, err := starcrypto.DecodePublicKey(c.handshakeRsaPubKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newSendKey, err := starcrypto.RSAEncrypt(pubKey, newKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := c.sendWait(TransferMsg{
|
||||
ID: 19961127,
|
||||
Key: "sirius",
|
||||
Value: newSendKey,
|
||||
Type: MSG_KEY_CHANGE,
|
||||
}, time.Second*10)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if string(data.Value) != "success" {
|
||||
return errors.New("cannot exchange new aes-key")
|
||||
}
|
||||
c.SecretKey = newKey
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
return nil
|
||||
}
|
||||
|
||||
func aesRsaHello(c Client) error {
|
||||
newAesKey := []byte(fmt.Sprintf("%d%d%d%s", time.Now().UnixNano(), rand.Int63(), rand.Int63(), "b612.me"))
|
||||
newAesKey = []byte(starcrypto.Md5Str(newAesKey))
|
||||
return c.ExchangeKey(newAesKey)
|
||||
}
|
||||
|
||||
func (c *ClientCommon) GetMsgEn() func([]byte, []byte) []byte {
|
||||
return c.msgEn
|
||||
}
|
||||
func (c *ClientCommon) SetMsgEn(fn func([]byte, []byte) []byte) {
|
||||
c.msgEn = fn
|
||||
}
|
||||
func (c *ClientCommon) GetMsgDe() func([]byte, []byte) []byte {
|
||||
return c.msgDe
|
||||
}
|
||||
func (c *ClientCommon) SetMsgDe(fn func([]byte, []byte) []byte) {
|
||||
c.msgDe = fn
|
||||
}
|
||||
|
||||
func (c *ClientCommon) HeartbeatPeroid() time.Duration {
|
||||
return c.heartbeatPeriod
|
||||
}
|
||||
func (c *ClientCommon) SetHeartbeatPeroid(duration time.Duration) {
|
||||
c.heartbeatPeriod = duration
|
||||
}
|
||||
|
||||
func (c *ClientCommon) GetSecretKey() []byte {
|
||||
return c.SecretKey
|
||||
}
|
||||
func (c *ClientCommon) SetSecretKey(key []byte) {
|
||||
c.SecretKey = key
|
||||
}
|
||||
func (c *ClientCommon) RsaPubKey() []byte {
|
||||
return c.handshakeRsaPubKey
|
||||
}
|
||||
func (c *ClientCommon) SetRsaPubKey(key []byte) {
|
||||
c.handshakeRsaPubKey = key
|
||||
}
|
||||
func (c *ClientCommon) Stop() error {
|
||||
if !c.alive.Load().(bool) {
|
||||
return nil
|
||||
}
|
||||
c.alive.Store(false)
|
||||
c.mu.Lock()
|
||||
c.status = Status{
|
||||
Alive: false,
|
||||
Reason: "recv stop signal from user",
|
||||
Err: nil,
|
||||
}
|
||||
c.mu.Unlock()
|
||||
c.stopFn()
|
||||
return nil
|
||||
}
|
||||
func (c *ClientCommon) StopMonitorChan() <-chan struct{} {
|
||||
return c.stopCtx.Done()
|
||||
}
|
||||
|
||||
func (c *ClientCommon) Status() Status {
|
||||
return c.status
|
||||
}
|
||||
|
||||
func (c *ClientCommon) GetSequenceEn() func(interface{}) ([]byte, error) {
|
||||
return c.sequenceEn
|
||||
}
|
||||
func (c *ClientCommon) SetSequenceEn(fn func(interface{}) ([]byte, error)) {
|
||||
c.sequenceEn = fn
|
||||
}
|
||||
func (c *ClientCommon) GetSequenceDe() func([]byte) (interface{}, error) {
|
||||
return c.sequenceDe
|
||||
}
|
||||
func (c *ClientCommon) SetSequenceDe(fn func([]byte) (interface{}, error)) {
|
||||
c.sequenceDe = fn
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Client interface {
|
||||
SetDefaultLink(func(message *Message))
|
||||
SetLink(string, func(*Message))
|
||||
send(msg TransferMsg) (WaitMsg, error)
|
||||
sendWait(msg TransferMsg, timeout time.Duration) (Message, error)
|
||||
Send(key string, value MsgVal) error
|
||||
SendWait(key string, value MsgVal, timeout time.Duration) (Message, error)
|
||||
SendWaitObj(key string, value interface{}, timeout time.Duration) (Message, error)
|
||||
SendCtx(ctx context.Context, key string, value MsgVal) (Message, error)
|
||||
Reply(m Message, value MsgVal) error
|
||||
ExchangeKey(newKey []byte) error
|
||||
Connect(network string, addr string) error
|
||||
ConnectTimeout(network string, addr string, timeout time.Duration) error
|
||||
SkipExchangeKey() bool
|
||||
SetSkipExchangeKey(bool)
|
||||
|
||||
GetMsgEn() func([]byte, []byte) []byte
|
||||
SetMsgEn(func([]byte, []byte) []byte)
|
||||
GetMsgDe() func([]byte, []byte) []byte
|
||||
SetMsgDe(func([]byte, []byte) []byte)
|
||||
|
||||
Heartbeat()
|
||||
HeartbeatPeroid() time.Duration
|
||||
SetHeartbeatPeroid(duration time.Duration)
|
||||
|
||||
GetSecretKey() []byte
|
||||
SetSecretKey(key []byte)
|
||||
RsaPubKey() []byte
|
||||
SetRsaPubKey([]byte)
|
||||
|
||||
Stop() error
|
||||
StopMonitorChan() <-chan struct{}
|
||||
Status() Status
|
||||
ShowError(bool)
|
||||
DebugMode(bool)
|
||||
IsDebugMode() bool
|
||||
|
||||
GetSequenceEn() func(interface{}) ([]byte, error)
|
||||
SetSequenceEn(func(interface{}) ([]byte, error))
|
||||
GetSequenceDe() func([]byte) (interface{}, error)
|
||||
SetSequenceDe(func([]byte) (interface{}, error))
|
||||
SendObjCtx(ctx context.Context, key string, val interface{}) (Message, error)
|
||||
SendObj(key string, val interface{}) error
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"b612.me/starcrypto"
|
||||
)
|
||||
|
||||
var defaultRsaKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIJKAIBAAKCAgEAxmeMqr9yfJFKZn26oe/HvC7bZXNLC9Nk55AuTkb4XuIoqXDb
|
||||
AJD2Y/p167oJLKIqL3edcj7h+oTfn6s79vxT0ZCEf37ILU0G+scRzVwYHiLMwOUC
|
||||
bS2o4Xor3zqUi9f1piJBvoBNh8RKKtsmJW6VQZdiUGJHbgX4MdOdtf/6TvxZMwSX
|
||||
U+PRSCAjy04A31Zi7DEWUWJPyqmHeu++PxXU5lvoMdCGDqpcF2j2uO7oJJUww01M
|
||||
3F5FtTElMrK4/P9gD4kP7NiPhOfVPEfBsYT/DSSjvqNZJZuWnxu+cDxE7J/sBvdp
|
||||
eNRLhqzdmMYagZFuUmVrz8QmsD6jKHgydW+r7irllvb8WJPK/RIMif+4Rg7rDKFb
|
||||
j8+ZQ3HZ/gKELoRSyb3zL6RC2qlGLjC1tdeN7TNTinCv092y39T8jIARJ7tpfePh
|
||||
NBxsBdxfXbCAzHYZIHufI9Zlsc+felQwanlDhq+q8YLcnKHvNKYVyCf/upExpAiA
|
||||
rr88y/KbeKes0KorKkwMBnGUMTothWM25wHozcurixNvP4UMWX7LWD7vOZZuNDQN
|
||||
utZYeTwdsniI3mTO9vlPWEK8JTfxBU7x9SePUMJNDyjfDUJM8C2DOlyhGNPkgazO
|
||||
GdliH87tHkEy/7jJnGclgKmciiVPgwHfFx9GGoBHEfvmAoGGrk4qNbjm7JECAwEA
|
||||
AQKCAgBYzHe05ELFZfG6tYMWf08R9pbTbSqlfFOpIGrZNgJr1SUF0TDzq+3bCXpF
|
||||
qtn4VAw1en/JZkOV8Gp1+Bm6jWymWtwyg/fr7pG1I+vf0dwpgMHLg7P2UX1IjXmd
|
||||
S4a4oEuds69hJ+OLZFsdm0ATeM7ssGicOaBmqd1Pz7rCfnL1bxQtNVzVex1r/paG
|
||||
o77YNr3HoKCwhCPaPM4aQ7sOWSMUhwYBZabaYX0eLShf1O2pkexlPO+tobPpSLmx
|
||||
WzRYZ6QC0AGEq9hwT6KsfCFA5pmQtFllNY7suhpL1AsECLWAgoMNCyb1oW68NBpq
|
||||
CiBK5WBPGH2MW+pE74Pu1P0gen6kLGnApKQjprE1aGuR+xkZe3uEnXwSryU9TXki
|
||||
wINTEMsX8dkmofFqaJhUwSubrb+t7gvv9E9ZZe0X6UgKzAVVqvh4z1pP8VT+xHpu
|
||||
pW7SR8n9cFddaEPUijSb1rSpJrNzfJJ+G7yrB7Cw2kBgQ07vzD3z/3kA9cwFevLS
|
||||
mv3l3OQuB6y9c+AG3cX5WGAt/BVOLjimj9qJt+YglG0SwG31U0PUnnx6QVz/UtJm
|
||||
CbJQ2TpJd+mk0HyuMU+eycp7BWF3PMN+SE4QgKCKWnhsLeAd3gcvifsbLOYE1OPg
|
||||
wv1tqyJy0VsJiSn6Ub6Qq0kPLwCLlQTnLWk5mIhnRpHYufTSwQKCAQEA4gS4FKPU
|
||||
tAcQ82dEYW4OjGfhNWrjFpF+A8K5zufleQWcgzQ3fQho13zH0vZobukfkEVlVxla
|
||||
OIVk7ZgNA4mCSFrATjIx3RMqzrAUvTte0O4wkjYgCwVvTdS1W8nvRLKgugLygyoo
|
||||
r+MLW5IT3eNMK/2fZbftNlAkbc7NCo3c2tS6MXFgjx5JUuzChOY73Kp4p5KS38L5
|
||||
wRRiI8KTIKjBjMZ5q/l8VLKX89bKOCaWibmItoXY6QMbIjargb7YLp3X6uGEyGIu
|
||||
VhPbQ80/+OC2ZqIvDecp4PYnJNZFeqfjyfhJCNqDjBKYwIscBLMU/Wf9OY258OR4
|
||||
snQaerN1M0h9lQKCAQEA4LkZIRLLw+8bIVM+7VXxFwOAGy+MH35tvuNIToItAoUh
|
||||
zjL5LG34PjID8J0DPyP8VRVanak1EcxF0aTEkvnt2f2RAVsW89ytcn8Lybb12Ae8
|
||||
ia2ZWuIM+J40nuKOGPs3lJ9HqdPWmZYWsWKxFJmYBBnwD6CADYqhqambQn0HeaYl
|
||||
/WUD7blLYg+4Kk1mt9/hIw93jTWP/86O2H0ia+AhYPTqyvVXfIXKhat6NlOYksGf
|
||||
Hdv+aCC8Ukg6FyEgiNc/rFn0MWPnEX+cM1AwubviHIBhV8QWILLBTjupwsEBZVah
|
||||
60ftH+HRUCmEeOpI7jyzIlfEUNLoBHfswKMhMPtcDQKCAQEA0JFkQX+xn/PJW6PX
|
||||
AUWrXTvbIg0hw8i9DcFa76klJBnehWDhN5tUDE5Uo8PJOVgdTWgMjWSS0geezHX8
|
||||
xF/XfudoAIDnbMfsP9FTQhCQfaLf5XzW8vSv8pWwSiS9jJp+IUjo+8siwrR03aqe
|
||||
dKr0tr+ToS0qVG1+QGqO4gdpX/LgYxHp9ggPx9s94aAIa6hQMOrcaGqnSNqDedZr
|
||||
KL8x5LOewek3J32rJVP3Rfut/SfeFfjL4rKADoF+oPs4yUPVZSV4/+VCNyKZuyaj
|
||||
uwm6qFlPrLe9+J+OHbsxYG+fj9hzpRzoOZFLrppwX5HWc8XLcpnrlXVwP9VOPh5u
|
||||
r8VcRQKCAQAJFHGHfJLvH8Ig3pQ0UryjCWkrsAghXaJhjB1nzqqy514uTrDysp7N
|
||||
JIg0OKPg8TtI1MwMgsG6Ll7D0bx/k8mgfTZWr6+FuuznK2r2g4X7bJSZm4IOwgN0
|
||||
KDBIGy9SoxPj1Wu32O9a1U2lbS9qfao+wC2K9Bk4ctmFWW0Eiri6mZP/YQ1/lXUO
|
||||
SURPsUDtPQaDvCRAeGGRHG95H9U8NpoiqMKz4KXgSiecrwkJGOeZRml/c1wcKPZy
|
||||
/KgcNyJxZQEVnazYMgksE9Pj3uGZH5ZLQISuXyXlvFNDLfX2AIZl6dIxB371QtKK
|
||||
QqMvn4fC2IEEajdsbJkjVRUj03OL3xwhAoIBAAfMhDSvBbDkGTaXnNMjPPSbswqK
|
||||
qcSRhSG27mjs1dDNBKuFbz6TkIOp4nxjuS9Zp19fErXlAE9mF5yXSmuiAkZmWfhs
|
||||
HKpWIdjFJK1EqSfcINe2YuoyUIulz9oG7ObRHD4D8jSPjA8Ete+XsBHGyOtUl09u
|
||||
X4u9uClhqjK+r1Tno2vw5yF6ZxfQtdWuL4W0UL1S8E+VO7vjTjNOYvgjAIpAM/gW
|
||||
sqjA2Qw52UZqhhLXoTfRvtJilxlXXhIRJSsnUoGiYVCQ/upjqJCClEvJfIWdGY/U
|
||||
I2CbFrwJcNvOG1lUsSM55JUmbrSWVPfo7yq2k9GCuFxOy2n/SVlvlQUcNkA=
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var defaultRsaPubKey = []byte(`-----BEGIN PUBLIC KEY-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxmeMqr9yfJFKZn26oe/H
|
||||
vC7bZXNLC9Nk55AuTkb4XuIoqXDbAJD2Y/p167oJLKIqL3edcj7h+oTfn6s79vxT
|
||||
0ZCEf37ILU0G+scRzVwYHiLMwOUCbS2o4Xor3zqUi9f1piJBvoBNh8RKKtsmJW6V
|
||||
QZdiUGJHbgX4MdOdtf/6TvxZMwSXU+PRSCAjy04A31Zi7DEWUWJPyqmHeu++PxXU
|
||||
5lvoMdCGDqpcF2j2uO7oJJUww01M3F5FtTElMrK4/P9gD4kP7NiPhOfVPEfBsYT/
|
||||
DSSjvqNZJZuWnxu+cDxE7J/sBvdpeNRLhqzdmMYagZFuUmVrz8QmsD6jKHgydW+r
|
||||
7irllvb8WJPK/RIMif+4Rg7rDKFbj8+ZQ3HZ/gKELoRSyb3zL6RC2qlGLjC1tdeN
|
||||
7TNTinCv092y39T8jIARJ7tpfePhNBxsBdxfXbCAzHYZIHufI9Zlsc+felQwanlD
|
||||
hq+q8YLcnKHvNKYVyCf/upExpAiArr88y/KbeKes0KorKkwMBnGUMTothWM25wHo
|
||||
zcurixNvP4UMWX7LWD7vOZZuNDQNutZYeTwdsniI3mTO9vlPWEK8JTfxBU7x9SeP
|
||||
UMJNDyjfDUJM8C2DOlyhGNPkgazOGdliH87tHkEy/7jJnGclgKmciiVPgwHfFx9G
|
||||
GoBHEfvmAoGGrk4qNbjm7JECAwEAAQ==
|
||||
-----END PUBLIC KEY-----`)
|
||||
|
||||
var defaultAesKey = []byte{0x19, 0x96, 0x11, 0x27, 228, 187, 187, 231, 142, 137, 230, 179, 189, 229, 184, 133}
|
||||
|
||||
func defaultMsgEn(key []byte, d []byte) []byte {
|
||||
return starcrypto.AesEncryptCFB(d, key)
|
||||
}
|
||||
|
||||
func defaultMsgDe(key []byte, d []byte) []byte {
|
||||
return starcrypto.AesDecryptCFB(d, key)
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterName("b612.me/notify.Transfer", TransferMsg{})
|
||||
}
|
@ -0,0 +1,502 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"b612.me/starcrypto"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
MSG_SYS MessageType = iota
|
||||
MSG_SYS_WAIT
|
||||
MSG_SYS_REPLY
|
||||
MSG_KEY_CHANGE
|
||||
MSG_ASYNC
|
||||
MSG_SYNC_ASK
|
||||
MSG_SYNC_REPLY
|
||||
)
|
||||
|
||||
type MessageType uint8
|
||||
|
||||
type NetType uint8
|
||||
|
||||
const (
|
||||
NET_SERVER NetType = iota
|
||||
NET_CLIENT
|
||||
)
|
||||
|
||||
type MsgVal []byte
|
||||
type TransferMsg struct {
|
||||
ID uint64
|
||||
Key string
|
||||
Value MsgVal
|
||||
Type MessageType
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
NetType
|
||||
ClientConn *ClientConn
|
||||
ServerConn Client
|
||||
TransferMsg
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
type WaitMsg struct {
|
||||
TransferMsg
|
||||
Time time.Time
|
||||
Reply chan Message
|
||||
//Ctx context.Context
|
||||
}
|
||||
|
||||
func (m *Message) Reply(value MsgVal) (err error) {
|
||||
reply := TransferMsg{
|
||||
ID: m.ID,
|
||||
Key: m.Key,
|
||||
Value: value,
|
||||
Type: m.Type,
|
||||
}
|
||||
if reply.Type == MSG_SYNC_ASK {
|
||||
reply.Type = MSG_SYNC_REPLY
|
||||
}
|
||||
if reply.Type == MSG_SYS_WAIT {
|
||||
reply.Type = MSG_SYS_REPLY
|
||||
}
|
||||
if m.NetType == NET_SERVER {
|
||||
_, err = m.ClientConn.server.send(m.ClientConn, reply)
|
||||
}
|
||||
if m.NetType == NET_CLIENT {
|
||||
_, err = m.ServerConn.send(reply)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Message) ReplyObj(value interface{}) (err error) {
|
||||
data, err := encode(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return m.Reply(data)
|
||||
}
|
||||
|
||||
type ClientConn struct {
|
||||
alive atomic.Value
|
||||
status Status
|
||||
ClientID string
|
||||
ClientAddr net.Addr
|
||||
tuConn net.Conn
|
||||
server Server
|
||||
stopFn context.CancelFunc
|
||||
stopCtx context.Context
|
||||
maxReadTimeout time.Duration
|
||||
maxWriteTimeout time.Duration
|
||||
msgEn func([]byte, []byte) []byte
|
||||
msgDe func([]byte, []byte) []byte
|
||||
handshakeRsaKey []byte
|
||||
SecretKey []byte
|
||||
lastHeartBeat int64
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
Alive bool
|
||||
Reason string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (c *ClientConn) readTUMessage() {
|
||||
for {
|
||||
select {
|
||||
case <-c.stopCtx.Done():
|
||||
c.tuConn.Close()
|
||||
c.server.removeClient(c)
|
||||
return
|
||||
default:
|
||||
}
|
||||
if c.maxReadTimeout.Seconds() > 0 {
|
||||
c.tuConn.SetReadDeadline(time.Now().Add(c.maxReadTimeout))
|
||||
}
|
||||
data := make([]byte, 8192)
|
||||
num, err := c.tuConn.Read(data)
|
||||
if err == os.ErrDeadlineExceeded {
|
||||
if num != 0 {
|
||||
c.server.pushMessage(data[:num], c.ClientID)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
//conn is broke
|
||||
c.alive.Store(false)
|
||||
c.status = Status{
|
||||
Alive: false,
|
||||
Reason: "read error",
|
||||
Err: err,
|
||||
}
|
||||
c.stopFn()
|
||||
continue
|
||||
}
|
||||
c.server.pushMessage(data[:num], c.ClientID)
|
||||
//fmt.Println("finished:", float64(time.Now().UnixNano()-nowd)/1000000)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ClientConn) rsaDecode(message Message) {
|
||||
privKey, err := starcrypto.DecodePrivateKey(c.handshakeRsaKey, "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
message.Reply([]byte("failed"))
|
||||
return
|
||||
}
|
||||
data, err := starcrypto.RSADecrypt(privKey, message.Value)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
message.Reply([]byte("failed"))
|
||||
return
|
||||
}
|
||||
//fmt.Println("aes-key changed to", string(data))
|
||||
message.Reply([]byte("success"))
|
||||
c.SecretKey = data
|
||||
}
|
||||
|
||||
func (c *ClientConn) sayGoodByeForTU() error {
|
||||
_, err := c.server.sendWait(c, TransferMsg{
|
||||
ID: 10010,
|
||||
Key: "bye",
|
||||
Value: nil,
|
||||
Type: MSG_SYS_WAIT,
|
||||
}, time.Second*3)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ClientConn) GetSecretKey() []byte {
|
||||
return c.SecretKey
|
||||
}
|
||||
func (c *ClientConn) SetSecretKey(key []byte) {
|
||||
c.SecretKey = key
|
||||
}
|
||||
|
||||
func (c *ClientConn) GetMsgEn() func([]byte, []byte) []byte {
|
||||
return c.msgEn
|
||||
}
|
||||
func (c *ClientConn) SetMsgEn(fn func([]byte, []byte) []byte) {
|
||||
c.msgEn = fn
|
||||
}
|
||||
func (c *ClientConn) GetMsgDe() func([]byte, []byte) []byte {
|
||||
return c.msgDe
|
||||
}
|
||||
func (c *ClientConn) SetMsgDe(fn func([]byte, []byte) []byte) {
|
||||
c.msgDe = fn
|
||||
}
|
||||
|
||||
func (c *ClientConn) StopMonitorChan() <-chan struct{} {
|
||||
return c.stopCtx.Done()
|
||||
}
|
||||
|
||||
func (c *ClientConn) Status() Status {
|
||||
return c.status
|
||||
}
|
||||
|
||||
func (c *ClientConn) Server() Server {
|
||||
return c.server
|
||||
}
|
||||
|
||||
func (c *ClientConn) GetRemoteAddr() net.Addr {
|
||||
return c.ClientAddr
|
||||
}
|
||||
|
||||
func (m MsgVal) ToClearString() string {
|
||||
return string(m)
|
||||
}
|
||||
|
||||
func (m MsgVal) ToInterface() (interface{}, error) {
|
||||
return Decode(m)
|
||||
}
|
||||
|
||||
func (m MsgVal) MustToInterface() interface{} {
|
||||
inf, err := m.ToInterface()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return inf
|
||||
}
|
||||
|
||||
func (m MsgVal) ToString() (string, error) {
|
||||
inf, err := m.ToInterface()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if data, ok := inf.(string); !ok {
|
||||
return "", errors.New("source data not match target type")
|
||||
} else {
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m MsgVal) MustToString() string {
|
||||
inf, err := m.ToString()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return inf
|
||||
}
|
||||
|
||||
func (m MsgVal) ToInt32() (int32, error) {
|
||||
inf, err := m.ToInterface()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if data, ok := inf.(int32); !ok {
|
||||
return 0, errors.New("source data not match target type")
|
||||
} else {
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m MsgVal) MustToInt32() int32 {
|
||||
inf, err := m.ToInt32()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return inf
|
||||
}
|
||||
|
||||
func (m MsgVal) ToInt() (int, error) {
|
||||
inf, err := m.ToInterface()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if data, ok := inf.(int); !ok {
|
||||
return 0, errors.New("source data not match target type")
|
||||
} else {
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m MsgVal) MustToInt() int {
|
||||
inf, err := m.ToInt()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return inf
|
||||
}
|
||||
|
||||
func (m MsgVal) ToUint64() (uint64, error) {
|
||||
inf, err := m.ToInterface()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if data, ok := inf.(uint64); !ok {
|
||||
return 0, errors.New("source data not match target type")
|
||||
} else {
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m MsgVal) MustToUint64() uint64 {
|
||||
inf, err := m.ToUint64()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return inf
|
||||
}
|
||||
|
||||
func (m MsgVal) ToUint32() (uint32, error) {
|
||||
inf, err := m.ToInterface()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if data, ok := inf.(uint32); !ok {
|
||||
return 0, errors.New("source data not match target type")
|
||||
} else {
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m MsgVal) MustToUint32() uint32 {
|
||||
inf, err := m.ToUint32()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return inf
|
||||
}
|
||||
|
||||
func (m MsgVal) ToUint() (uint, error) {
|
||||
inf, err := m.ToInterface()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if data, ok := inf.(uint); !ok {
|
||||
return 0, errors.New("source data not match target type")
|
||||
} else {
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m MsgVal) MustToUint() uint {
|
||||
inf, err := m.ToUint()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return inf
|
||||
}
|
||||
|
||||
func (m MsgVal) ToBool() (bool, error) {
|
||||
inf, err := m.ToInterface()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if data, ok := inf.(bool); !ok {
|
||||
return false, errors.New("source data not match target type")
|
||||
} else {
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m MsgVal) MustToBool() bool {
|
||||
inf, err := m.ToBool()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return inf
|
||||
}
|
||||
|
||||
func (m MsgVal) ToFloat64() (float64, error) {
|
||||
inf, err := m.ToInterface()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if data, ok := inf.(float64); !ok {
|
||||
return 0, errors.New("source data not match target type")
|
||||
} else {
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m MsgVal) MustToFloat64() float64 {
|
||||
inf, err := m.ToFloat64()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return inf
|
||||
}
|
||||
func (m MsgVal) ToFloat32() (float32, error) {
|
||||
inf, err := m.ToInterface()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if data, ok := inf.(float32); !ok {
|
||||
return 0, errors.New("source data not match target type")
|
||||
} else {
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m MsgVal) MustToFloat32() float32 {
|
||||
inf, err := m.ToFloat32()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return inf
|
||||
}
|
||||
|
||||
func (m MsgVal) ToSliceString() ([]string, error) {
|
||||
inf, err := m.ToInterface()
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
if data, ok := inf.([]string); !ok {
|
||||
return []string{}, errors.New("source data not match target type")
|
||||
} else {
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m MsgVal) MustToSliceString() []string {
|
||||
inf, err := m.ToSliceString()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return inf
|
||||
}
|
||||
|
||||
func (m MsgVal) ToSliceInt64() ([]int64, error) {
|
||||
inf, err := m.ToInterface()
|
||||
if err != nil {
|
||||
return []int64{}, err
|
||||
}
|
||||
if data, ok := inf.([]int64); !ok {
|
||||
return []int64{}, errors.New("source data not match target type")
|
||||
} else {
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m MsgVal) MustToSliceInt64() []int64 {
|
||||
inf, err := m.ToSliceInt64()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return inf
|
||||
}
|
||||
|
||||
func (m MsgVal) ToSliceFloat64() ([]float64, error) {
|
||||
inf, err := m.ToInterface()
|
||||
if err != nil {
|
||||
return []float64{}, err
|
||||
}
|
||||
if data, ok := inf.([]float64); !ok {
|
||||
return []float64{}, errors.New("source data not match target type")
|
||||
} else {
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m MsgVal) MustToSliceFloat64() []float64 {
|
||||
inf, err := m.ToSliceFloat64()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return inf
|
||||
}
|
||||
|
||||
func ToMsgVal(val interface{}) (MsgVal, error) {
|
||||
return Encode(val)
|
||||
}
|
||||
|
||||
func MustToMsgVal(val interface{}) MsgVal {
|
||||
d, err := ToMsgVal(val)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func (m MsgVal) Orm(stu interface{}) error {
|
||||
inf, err := m.ToInterface()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t := reflect.TypeOf(stu)
|
||||
if t.Kind() != reflect.Ptr {
|
||||
return errors.New("interface not writable(pointer wanted)")
|
||||
}
|
||||
if !reflect.ValueOf(stu).Elem().CanSet() {
|
||||
return errors.New("interface not writable")
|
||||
}
|
||||
it := reflect.TypeOf(inf)
|
||||
if t.Elem().Kind() != it.Kind() {
|
||||
return fmt.Errorf("interface{} kind is %v,not %v", t.Elem().Kind(), it.Kind())
|
||||
}
|
||||
if t.Elem().Name() != it.Name() {
|
||||
return fmt.Errorf("interface{} name is %v,not %v", t.Elem().Name(), it.Name())
|
||||
}
|
||||
if t.Elem().String() != it.String() {
|
||||
return fmt.Errorf("interface{} string is %v,not %v", t.Elem().String(), it.String())
|
||||
}
|
||||
reflect.ValueOf(stu).Elem().Set(reflect.ValueOf(inf))
|
||||
return nil
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
)
|
||||
|
||||
func Register(data interface{}) {
|
||||
gob.Register(data)
|
||||
}
|
||||
|
||||
func RegisterName(name string, data interface{}) {
|
||||
gob.RegisterName(name, data)
|
||||
}
|
||||
|
||||
func RegisterAll(data []interface{}) {
|
||||
for _, v := range data {
|
||||
gob.Register(v)
|
||||
}
|
||||
}
|
||||
|
||||
func RegisterNames(data map[string]interface{}) {
|
||||
for k, v := range data {
|
||||
gob.RegisterName(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
func encode(src interface{}) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
enc := gob.NewEncoder(&buf)
|
||||
err := enc.Encode(&src)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
func Encode(src interface{}) ([]byte, error) {
|
||||
return encode(src)
|
||||
}
|
||||
|
||||
func Decode(src []byte) (interface{}, error) {
|
||||
dec := gob.NewDecoder(bytes.NewReader(src))
|
||||
var dst interface{}
|
||||
err := dec.Decode(&dst)
|
||||
return dst, err
|
||||
}
|
@ -0,0 +1,693 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"b612.me/stario"
|
||||
"b612.me/starnet"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ServerCommon struct {
|
||||
msgID uint64
|
||||
alive atomic.Value
|
||||
status Status
|
||||
listener net.Listener
|
||||
udpListener *net.UDPConn
|
||||
queue *starnet.StarQueue
|
||||
stopFn context.CancelFunc
|
||||
stopCtx context.Context
|
||||
maxReadTimeout time.Duration
|
||||
maxWriteTimeout time.Duration
|
||||
parallelNum int
|
||||
wg stario.WaitGroup
|
||||
clientPool map[string]*ClientConn
|
||||
mu sync.RWMutex
|
||||
handshakeRsaKey []byte
|
||||
SecretKey []byte
|
||||
defaultMsgEn func([]byte, []byte) []byte
|
||||
defaultMsgDe func([]byte, []byte) []byte
|
||||
linkFns map[string]func(message *Message)
|
||||
defaultFns func(message *Message)
|
||||
noFinSyncMsgPool sync.Map
|
||||
noFinSyncMsgMaxKeepSeconds int64
|
||||
maxHeartbeatLostSeconds int64
|
||||
sequenceDe func([]byte) (interface{}, error)
|
||||
sequenceEn func(interface{}) ([]byte, error)
|
||||
showError bool
|
||||
debugMode bool
|
||||
}
|
||||
|
||||
func NewServer() Server {
|
||||
var server ServerCommon
|
||||
server.wg = stario.NewWaitGroup(0)
|
||||
server.parallelNum = 0
|
||||
server.noFinSyncMsgMaxKeepSeconds = 0
|
||||
server.maxHeartbeatLostSeconds = 300
|
||||
server.stopCtx, server.stopFn = context.WithCancel(context.Background())
|
||||
server.SecretKey = defaultAesKey
|
||||
server.handshakeRsaKey = defaultRsaKey
|
||||
server.clientPool = make(map[string]*ClientConn)
|
||||
server.defaultMsgEn = defaultMsgEn
|
||||
server.defaultMsgDe = defaultMsgDe
|
||||
server.sequenceEn = encode
|
||||
server.sequenceDe = Decode
|
||||
server.alive.Store(false)
|
||||
server.linkFns = make(map[string]func(*Message))
|
||||
server.defaultFns = func(message *Message) {
|
||||
return
|
||||
}
|
||||
return &server
|
||||
}
|
||||
|
||||
func (s *ServerCommon) DebugMode(dmg bool) {
|
||||
s.mu.Lock()
|
||||
s.debugMode = dmg
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *ServerCommon) IsDebugMode() bool {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.debugMode
|
||||
}
|
||||
|
||||
func (s *ServerCommon) ShowError(std bool) {
|
||||
s.mu.Lock()
|
||||
s.showError = std
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *ServerCommon) Stop() error {
|
||||
if !s.alive.Load().(bool) {
|
||||
return nil
|
||||
}
|
||||
s.alive.Store(false)
|
||||
s.mu.Lock()
|
||||
s.status = Status{
|
||||
Alive: false,
|
||||
Reason: "recv stop signal from user",
|
||||
Err: nil,
|
||||
}
|
||||
s.mu.Unlock()
|
||||
s.stopFn()
|
||||
return nil
|
||||
}
|
||||
func (s *ServerCommon) Listen(network string, addr string) error {
|
||||
if s.alive.Load().(bool) {
|
||||
return errors.New("server already run")
|
||||
}
|
||||
s.stopCtx, s.stopFn = context.WithCancel(context.Background())
|
||||
s.queue = starnet.NewQueueCtx(s.stopCtx, 128)
|
||||
if strings.Contains(strings.ToLower(network), "udp") {
|
||||
return s.ListenUDP(network, addr)
|
||||
}
|
||||
return s.ListenTU(network, addr)
|
||||
}
|
||||
|
||||
func (s *ServerCommon) ListenTU(network string, addr string) error {
|
||||
listener, err := net.Listen(network, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.alive.Store(true)
|
||||
s.status.Alive = true
|
||||
s.listener = listener
|
||||
go s.accept()
|
||||
go s.monitorPool()
|
||||
go s.loadMessage()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServerCommon) monitorPool() {
|
||||
for {
|
||||
select {
|
||||
case <-s.stopCtx.Done():
|
||||
s.noFinSyncMsgPool.Range(func(k, v interface{}) bool {
|
||||
data := v.(WaitMsg)
|
||||
close(data.Reply)
|
||||
s.noFinSyncMsgPool.Delete(k)
|
||||
return true
|
||||
})
|
||||
return
|
||||
case <-time.After(time.Second * 30):
|
||||
}
|
||||
now := time.Now()
|
||||
if s.noFinSyncMsgMaxKeepSeconds > 0 {
|
||||
s.noFinSyncMsgPool.Range(func(k, v interface{}) bool {
|
||||
data := v.(WaitMsg)
|
||||
if data.Time.Add(time.Duration(s.noFinSyncMsgMaxKeepSeconds) * time.Second).Before(now) {
|
||||
close(data.Reply)
|
||||
s.noFinSyncMsgPool.Delete(k)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
if s.maxHeartbeatLostSeconds != 0 {
|
||||
for _, v := range s.clientPool {
|
||||
if now.Unix()-v.lastHeartBeat > s.maxHeartbeatLostSeconds {
|
||||
v.stopFn()
|
||||
s.removeClient(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ServerCommon) SetDefaultCommEncode(fn func([]byte, []byte) []byte) {
|
||||
s.defaultMsgEn = fn
|
||||
}
|
||||
|
||||
func (s *ServerCommon) SetDefaultCommDecode(fn func([]byte, []byte) []byte) {
|
||||
s.defaultMsgDe = fn
|
||||
}
|
||||
|
||||
func (s *ServerCommon) SetDefaultLink(fn func(message *Message)) {
|
||||
s.defaultFns = fn
|
||||
}
|
||||
|
||||
func (s *ServerCommon) SetLink(key string, fn func(*Message)) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.linkFns[key] = fn
|
||||
}
|
||||
|
||||
func (s *ServerCommon) pushMessage(data []byte, source string) {
|
||||
s.queue.ParseMessage(data, source)
|
||||
}
|
||||
|
||||
func (s *ServerCommon) removeClient(client *ClientConn) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
delete(s.clientPool, client.ClientID)
|
||||
}
|
||||
|
||||
func (s *ServerCommon) accept() {
|
||||
if s.udpListener != nil {
|
||||
s.acceptUDP()
|
||||
}
|
||||
s.acceptTU()
|
||||
}
|
||||
func (s *ServerCommon) acceptTU() {
|
||||
for {
|
||||
select {
|
||||
case <-s.stopCtx.Done():
|
||||
if s.debugMode {
|
||||
fmt.Println("accept goroutine recv exit signal,exit")
|
||||
}
|
||||
return
|
||||
default:
|
||||
}
|
||||
conn, err := s.listener.Accept()
|
||||
if err != nil {
|
||||
if s.showError || s.debugMode {
|
||||
fmt.Println("error accept:", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if s.debugMode {
|
||||
fmt.Println("accept new connection from", conn.RemoteAddr())
|
||||
}
|
||||
var id string
|
||||
for {
|
||||
id = fmt.Sprintf("%s%d%d", conn.RemoteAddr().String(), time.Now().UnixNano(), rand.Int63())
|
||||
s.mu.RLock()
|
||||
if _, ok := s.clientPool[id]; ok {
|
||||
s.mu.RUnlock()
|
||||
continue
|
||||
}
|
||||
s.mu.RUnlock()
|
||||
break
|
||||
}
|
||||
client := ClientConn{
|
||||
ClientID: id,
|
||||
ClientAddr: conn.RemoteAddr(),
|
||||
tuConn: conn,
|
||||
server: s,
|
||||
maxReadTimeout: s.maxReadTimeout,
|
||||
maxWriteTimeout: s.maxWriteTimeout,
|
||||
SecretKey: s.SecretKey,
|
||||
handshakeRsaKey: s.handshakeRsaKey,
|
||||
msgEn: s.defaultMsgEn,
|
||||
msgDe: s.defaultMsgDe,
|
||||
lastHeartBeat: time.Now().Unix(),
|
||||
}
|
||||
client.alive.Store(true)
|
||||
client.status = Status{
|
||||
Alive: true,
|
||||
Reason: "",
|
||||
Err: nil,
|
||||
}
|
||||
client.stopCtx, client.stopFn = context.WithCancel(context.Background())
|
||||
s.mu.Lock()
|
||||
s.clientPool[id] = &client
|
||||
s.mu.Unlock()
|
||||
go client.readTUMessage()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ServerCommon) loadMessage() {
|
||||
for {
|
||||
select {
|
||||
case <-s.stopCtx.Done():
|
||||
var wg sync.WaitGroup
|
||||
s.mu.RLock()
|
||||
for _, v := range s.clientPool {
|
||||
wg.Add(1)
|
||||
go func(v *ClientConn) {
|
||||
defer wg.Done()
|
||||
v.sayGoodByeForTU()
|
||||
v.alive.Store(false)
|
||||
v.status = Status{
|
||||
Alive: false,
|
||||
Reason: "recv stop signal from server",
|
||||
Err: nil,
|
||||
}
|
||||
v.stopFn()
|
||||
s.removeClient(v)
|
||||
}(v)
|
||||
}
|
||||
s.mu.RUnlock()
|
||||
select {
|
||||
case <-time.After(time.Second * 8):
|
||||
case <-stario.WaitUntilFinished(func() error {
|
||||
wg.Wait()
|
||||
return nil
|
||||
}):
|
||||
}
|
||||
if s.listener != nil {
|
||||
s.listener.Close()
|
||||
}
|
||||
s.wg.Wait()
|
||||
return
|
||||
case data, ok := <-s.queue.RestoreChan():
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
s.wg.Add(1)
|
||||
go func(data starnet.MsgQueue) {
|
||||
s.mu.RLock()
|
||||
cc, ok := s.clientPool[data.Conn.(string)]
|
||||
s.mu.RUnlock()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
//fmt.Println("received:", float64(time.Now().UnixNano()-nowd)/1000000)
|
||||
msg, err := s.sequenceDe(cc.msgDe(cc.SecretKey, data.Msg))
|
||||
if err != nil {
|
||||
if s.showError || s.debugMode {
|
||||
fmt.Println("server decode data error", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
//fmt.Println("decoded:", float64(time.Now().UnixNano()-nowd)/1000000)
|
||||
message := Message{
|
||||
NetType: NET_SERVER,
|
||||
ClientConn: cc,
|
||||
TransferMsg: msg.(TransferMsg),
|
||||
}
|
||||
message.Time = time.Now()
|
||||
|
||||
//fmt.Println("dispatch:", float64(time.Now().UnixNano()-nowd)/1000000)
|
||||
s.dispatchMsg(message)
|
||||
}(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
func (s *ServerCommon) sysMsg(message Message) {
|
||||
switch message.Key {
|
||||
case "bye":
|
||||
//fmt.Println("recv stop signal from client", message.ClientConn.ClientID)
|
||||
if message.TransferMsg.Type == MSG_SYS_WAIT {
|
||||
message.Reply(nil)
|
||||
}
|
||||
message.ClientConn.alive.Store(false)
|
||||
message.ClientConn.status = Status{
|
||||
Alive: false,
|
||||
Reason: "recv stop signal from client",
|
||||
Err: nil,
|
||||
}
|
||||
message.ClientConn.stopFn()
|
||||
case "heartbeat":
|
||||
message.ClientConn.lastHeartBeat = time.Now().Unix()
|
||||
message.Reply(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ServerCommon) dispatchMsg(message Message) {
|
||||
defer s.wg.Done()
|
||||
switch message.TransferMsg.Type {
|
||||
case MSG_SYS_WAIT:
|
||||
fallthrough
|
||||
case MSG_SYS:
|
||||
s.sysMsg(message)
|
||||
return
|
||||
case MSG_KEY_CHANGE:
|
||||
message.ClientConn.rsaDecode(message)
|
||||
return
|
||||
case MSG_SYS_REPLY:
|
||||
fallthrough
|
||||
case MSG_SYNC_REPLY:
|
||||
data, ok := s.noFinSyncMsgPool.Load(message.TransferMsg.ID)
|
||||
if ok {
|
||||
wait := data.(WaitMsg)
|
||||
wait.Reply <- message
|
||||
s.noFinSyncMsgPool.Delete(message.TransferMsg.ID)
|
||||
return
|
||||
}
|
||||
//just throw
|
||||
//return
|
||||
fallthrough
|
||||
default:
|
||||
}
|
||||
callFn := func(fn func(*Message)) {
|
||||
fn(&message)
|
||||
}
|
||||
fn, ok := s.linkFns[message.TransferMsg.Key]
|
||||
if ok {
|
||||
callFn(fn)
|
||||
}
|
||||
if s.defaultFns != nil {
|
||||
callFn(s.defaultFns)
|
||||
}
|
||||
}
|
||||
func (s *ServerCommon) send(c *ClientConn, msg TransferMsg) (WaitMsg, error) {
|
||||
if s.udpListener != nil {
|
||||
return s.sendUDP(c, msg)
|
||||
}
|
||||
return s.sendTU(c, msg)
|
||||
}
|
||||
|
||||
func (s *ServerCommon) sendTU(c *ClientConn, msg TransferMsg) (WaitMsg, error) {
|
||||
var wait WaitMsg
|
||||
if msg.Type != MSG_SYNC_REPLY && msg.Type != MSG_KEY_CHANGE && msg.Type != MSG_SYS_REPLY || msg.ID == 0 {
|
||||
msg.ID = atomic.AddUint64(&s.msgID, 1)
|
||||
}
|
||||
data, err := s.sequenceEn(msg)
|
||||
if err != nil {
|
||||
return WaitMsg{}, err
|
||||
}
|
||||
data = c.msgEn(c.SecretKey, data)
|
||||
data = s.queue.BuildMessage(data)
|
||||
if c.maxWriteTimeout.Seconds() != 0 {
|
||||
if err := c.tuConn.SetWriteDeadline(time.Now().Add(c.maxWriteTimeout)); err != nil {
|
||||
return WaitMsg{}, err
|
||||
}
|
||||
}
|
||||
_, err = c.tuConn.Write(data)
|
||||
//fmt.Println("resend:", float64(time.Now().UnixNano()-nowd)/1000000)
|
||||
if err == nil && (msg.Type == MSG_SYNC_ASK || msg.Type == MSG_SYS_WAIT) {
|
||||
wait.Time = time.Now()
|
||||
wait.TransferMsg = msg
|
||||
wait.Reply = make(chan Message, 1)
|
||||
s.noFinSyncMsgPool.Store(msg.ID, wait)
|
||||
}
|
||||
return wait, err
|
||||
}
|
||||
|
||||
func (s *ServerCommon) Send(c *ClientConn, key string, value MsgVal) error {
|
||||
_, err := s.send(c, TransferMsg{
|
||||
Key: key,
|
||||
Value: value,
|
||||
Type: MSG_ASYNC,
|
||||
})
|
||||
return err
|
||||
}
|
||||
func (s *ServerCommon) sendWait(c *ClientConn, msg TransferMsg, timeout time.Duration) (Message, error) {
|
||||
data, err := s.send(c, msg)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
if timeout.Seconds() == 0 {
|
||||
msg, ok := <-data.Reply
|
||||
if !ok {
|
||||
return msg, os.ErrInvalid
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
close(data.Reply)
|
||||
s.noFinSyncMsgPool.Delete(data.TransferMsg.ID)
|
||||
return Message{}, os.ErrDeadlineExceeded
|
||||
case <-s.stopCtx.Done():
|
||||
return Message{}, errors.New("service shutdown")
|
||||
case msg, ok := <-data.Reply:
|
||||
if !ok {
|
||||
return msg, os.ErrInvalid
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ServerCommon) SendCtx(ctx context.Context, c *ClientConn, key string, value MsgVal) (Message, error) {
|
||||
return s.sendCtx(c, TransferMsg{
|
||||
Key: key,
|
||||
Value: value,
|
||||
Type: MSG_SYNC_ASK,
|
||||
}, ctx)
|
||||
}
|
||||
|
||||
func (s *ServerCommon) sendCtx(c *ClientConn, msg TransferMsg, ctx context.Context) (Message, error) {
|
||||
data, err := s.send(c, msg)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
close(data.Reply)
|
||||
s.noFinSyncMsgPool.Delete(data.TransferMsg.ID)
|
||||
return Message{}, os.ErrClosed
|
||||
case <-s.stopCtx.Done():
|
||||
return Message{}, errors.New("service shutdown")
|
||||
case msg, ok := <-data.Reply:
|
||||
if !ok {
|
||||
return msg, os.ErrInvalid
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
}
|
||||
func (s *ServerCommon) SendWait(c *ClientConn, key string, value MsgVal, timeout time.Duration) (Message, error) {
|
||||
return s.sendWait(c, TransferMsg{
|
||||
Key: key,
|
||||
Value: value,
|
||||
Type: MSG_SYNC_ASK,
|
||||
}, timeout)
|
||||
}
|
||||
|
||||
func (s *ServerCommon) SendWaitObj(c *ClientConn, key string, value interface{}, timeout time.Duration) (Message, error) {
|
||||
data, err := s.sequenceEn(value)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
return s.SendWait(c, key, data, timeout)
|
||||
}
|
||||
|
||||
func (s *ServerCommon) SendObjCtx(ctx context.Context, c *ClientConn, key string, val interface{}) (Message, error) {
|
||||
data, err := s.sequenceEn(val)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
return s.sendCtx(c, TransferMsg{
|
||||
Key: key,
|
||||
Value: data,
|
||||
Type: MSG_SYNC_ASK,
|
||||
}, ctx)
|
||||
}
|
||||
|
||||
func (s *ServerCommon) SendObj(c *ClientConn, key string, val interface{}) error {
|
||||
data, err := encode(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.send(c, TransferMsg{
|
||||
Key: key,
|
||||
Value: data,
|
||||
Type: MSG_ASYNC,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ServerCommon) Reply(m Message, value MsgVal) error {
|
||||
return m.Reply(value)
|
||||
}
|
||||
|
||||
//for udp below
|
||||
|
||||
func (s *ServerCommon) ListenUDP(network string, addr string) error {
|
||||
udpAddr, err := net.ResolveUDPAddr(network, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
listener, err := net.ListenUDP(network, udpAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.alive.Store(true)
|
||||
s.status.Alive = true
|
||||
s.udpListener = listener
|
||||
go s.accept()
|
||||
go s.monitorPool()
|
||||
go s.loadMessage()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServerCommon) acceptUDP() {
|
||||
for {
|
||||
select {
|
||||
case <-s.stopCtx.Done():
|
||||
if s.debugMode {
|
||||
fmt.Println("accept goroutine recv exit signal,exit")
|
||||
}
|
||||
return
|
||||
default:
|
||||
}
|
||||
if s.maxReadTimeout.Seconds() > 0 {
|
||||
s.udpListener.SetReadDeadline(time.Now().Add(s.maxReadTimeout))
|
||||
}
|
||||
data := make([]byte, 4096)
|
||||
num, addr, err := s.udpListener.ReadFromUDP(data)
|
||||
id := addr.String()
|
||||
if s.debugMode {
|
||||
fmt.Println("accept new udp message from", id)
|
||||
}
|
||||
//fmt.Println("s recv udp:", float64(time.Now().UnixNano()-nowd)/1000000)
|
||||
s.mu.RLock()
|
||||
if _, ok := s.clientPool[id]; !ok {
|
||||
s.mu.RUnlock()
|
||||
client := ClientConn{
|
||||
ClientID: id,
|
||||
ClientAddr: addr,
|
||||
server: s,
|
||||
maxReadTimeout: s.maxReadTimeout,
|
||||
maxWriteTimeout: s.maxWriteTimeout,
|
||||
SecretKey: s.SecretKey,
|
||||
handshakeRsaKey: s.handshakeRsaKey,
|
||||
msgEn: s.defaultMsgEn,
|
||||
msgDe: s.defaultMsgDe,
|
||||
lastHeartBeat: time.Now().Unix(),
|
||||
}
|
||||
client.stopCtx, client.stopFn = context.WithCancel(context.Background())
|
||||
s.mu.Lock()
|
||||
s.clientPool[id] = &client
|
||||
s.mu.Unlock()
|
||||
} else {
|
||||
s.mu.RUnlock()
|
||||
}
|
||||
if err == os.ErrDeadlineExceeded {
|
||||
if num != 0 {
|
||||
s.pushMessage(data[:num], id)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
s.pushMessage(data[:num], id)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ServerCommon) sendUDP(c *ClientConn, msg TransferMsg) (WaitMsg, error) {
|
||||
var wait WaitMsg
|
||||
if msg.Type != MSG_SYNC_REPLY && msg.Type != MSG_KEY_CHANGE && msg.Type != MSG_SYS_REPLY || msg.ID == 0 {
|
||||
msg.ID = uint64(time.Now().UnixNano()) + rand.Uint64() + rand.Uint64()
|
||||
}
|
||||
data, err := s.sequenceEn(msg)
|
||||
if err != nil {
|
||||
return WaitMsg{}, err
|
||||
}
|
||||
data = c.msgEn(c.SecretKey, data)
|
||||
data = s.queue.BuildMessage(data)
|
||||
if c.maxWriteTimeout.Seconds() != 0 {
|
||||
s.udpListener.SetWriteDeadline(time.Now().Add(c.maxWriteTimeout))
|
||||
}
|
||||
_, err = s.udpListener.WriteTo(data, c.ClientAddr)
|
||||
if err == nil && (msg.Type == MSG_SYNC_ASK || msg.Type == MSG_SYS_WAIT) {
|
||||
wait.Time = time.Now()
|
||||
wait.TransferMsg = msg
|
||||
wait.Reply = make(chan Message, 1)
|
||||
s.noFinSyncMsgPool.Store(msg.ID, wait)
|
||||
}
|
||||
return wait, err
|
||||
}
|
||||
|
||||
func (s *ServerCommon) StopMonitorChan() <-chan struct{} {
|
||||
return s.stopCtx.Done()
|
||||
}
|
||||
|
||||
func (s *ServerCommon) Status() Status {
|
||||
return s.status
|
||||
}
|
||||
|
||||
func (s *ServerCommon) GetSecretKey() []byte {
|
||||
return s.SecretKey
|
||||
}
|
||||
func (s *ServerCommon) SetSecretKey(key []byte) {
|
||||
s.SecretKey = key
|
||||
}
|
||||
func (s *ServerCommon) RsaPrivKey() []byte {
|
||||
return s.handshakeRsaKey
|
||||
}
|
||||
func (s *ServerCommon) SetRsaPrivKey(key []byte) {
|
||||
s.handshakeRsaKey = key
|
||||
}
|
||||
|
||||
func (s *ServerCommon) GetClient(id string) *ClientConn {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
c, ok := s.clientPool[id]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return c
|
||||
}
|
||||
func (s *ServerCommon) GetClientLists() []*ClientConn {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
var list = make([]*ClientConn, 0, len(s.clientPool))
|
||||
for _, v := range s.clientPool {
|
||||
list = append(list, v)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func (s *ServerCommon) GetClientAddrs() []net.Addr {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
var list = make([]net.Addr, 0, len(s.clientPool))
|
||||
for _, v := range s.clientPool {
|
||||
list = append(list, v.ClientAddr)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func (s *ServerCommon) GetSequenceEn() func(interface{}) ([]byte, error) {
|
||||
return s.sequenceEn
|
||||
}
|
||||
func (s *ServerCommon) SetSequenceEn(fn func(interface{}) ([]byte, error)) {
|
||||
s.sequenceEn = fn
|
||||
}
|
||||
func (s *ServerCommon) GetSequenceDe() func([]byte) (interface{}, error) {
|
||||
return s.sequenceDe
|
||||
}
|
||||
func (s *ServerCommon) SetSequenceDe(fn func([]byte) (interface{}, error)) {
|
||||
s.sequenceDe = fn
|
||||
}
|
||||
|
||||
func (s *ServerCommon) HeartbeatTimeoutSec() int64 {
|
||||
return s.maxHeartbeatLostSeconds
|
||||
}
|
||||
|
||||
func (s *ServerCommon) SetHeartbeatTimeoutSec(sec int64) {
|
||||
s.maxHeartbeatLostSeconds = sec
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Server interface {
|
||||
SetDefaultCommEncode(func([]byte, []byte) []byte)
|
||||
SetDefaultCommDecode(func([]byte, []byte) []byte)
|
||||
SetDefaultLink(func(message *Message))
|
||||
SetLink(string, func(*Message))
|
||||
send(c *ClientConn, msg TransferMsg) (WaitMsg, error)
|
||||
sendWait(c *ClientConn, msg TransferMsg, timeout time.Duration) (Message, error)
|
||||
SendObjCtx(ctx context.Context, c *ClientConn, key string, val interface{}) (Message, error)
|
||||
SendObj(c *ClientConn, key string, val interface{}) error
|
||||
Send(c *ClientConn, key string, value MsgVal) error
|
||||
SendWait(c *ClientConn, key string, value MsgVal, timeout time.Duration) (Message, error)
|
||||
SendWaitObj(c *ClientConn, key string, value interface{}, timeout time.Duration) (Message, error)
|
||||
SendCtx(ctx context.Context, c *ClientConn, key string, value MsgVal) (Message, error)
|
||||
Reply(m Message, value MsgVal) error
|
||||
pushMessage([]byte, string)
|
||||
removeClient(client *ClientConn)
|
||||
Listen(network string, addr string) error
|
||||
Stop() error
|
||||
StopMonitorChan() <-chan struct{}
|
||||
Status() Status
|
||||
|
||||
GetSecretKey() []byte
|
||||
SetSecretKey(key []byte)
|
||||
RsaPrivKey() []byte
|
||||
SetRsaPrivKey([]byte)
|
||||
|
||||
GetClient(id string) *ClientConn
|
||||
GetClientLists() []*ClientConn
|
||||
GetClientAddrs() []net.Addr
|
||||
|
||||
GetSequenceEn() func(interface{}) ([]byte, error)
|
||||
SetSequenceEn(func(interface{}) ([]byte, error))
|
||||
GetSequenceDe() func([]byte) (interface{}, error)
|
||||
SetSequenceDe(func([]byte) (interface{}, error))
|
||||
ShowError(bool)
|
||||
DebugMode(bool)
|
||||
IsDebugMode() bool
|
||||
|
||||
HeartbeatTimeoutSec() int64
|
||||
SetHeartbeatTimeoutSec(int64)
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
package starcrypto
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
// CheckCRC32A calculates CRC32A (ITU I.363.5 algorithm, popularized by BZIP2) checksum.
|
||||
// This function will produce the same results as following PHP code:
|
||||
// hexdec(hash('crc32', $data))
|
||||
func CheckCRC32A(data []byte) uint32 {
|
||||
b := digest(data)
|
||||
|
||||
return binary.BigEndian.Uint32(b)
|
||||
}
|
||||
|
||||
func Crc32A(data []byte) []byte {
|
||||
return digest(data)
|
||||
}
|
||||
|
||||
// Crc32AStr is a convenience function that outputs CRC32A (ITU I.363.5 algorithm, popularized by BZIP2) checksum as a hex string.
|
||||
// This function will produce the same results as following PHP code:
|
||||
// hash('crc32', $data)
|
||||
func Crc32AStr(data []byte) string {
|
||||
b := digest(data)
|
||||
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
// digest performs checksum calculation for each byte of provided data and returns digest in form of byte array.
|
||||
func digest(data []byte) []byte {
|
||||
var crc uint32
|
||||
var digest = make([]byte, 4)
|
||||
|
||||
crc = ^crc
|
||||
for i := 0; i < len(data); i++ {
|
||||
crc = (crc << 8) ^ table[(crc>>24)^(uint32(data[i])&0xff)]
|
||||
}
|
||||
crc = ^crc
|
||||
|
||||
digest[3] = byte((crc >> 24) & 0xff)
|
||||
digest[2] = byte((crc >> 16) & 0xff)
|
||||
digest[1] = byte((crc >> 8) & 0xff)
|
||||
digest[0] = byte(crc & 0xff)
|
||||
|
||||
return digest
|
||||
}
|
||||
|
||||
// table is the pre-generated 0x04C11DB7 polynominal used for CRC32A.
|
||||
var table = [256]uint32{
|
||||
0x0,
|
||||
0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
|
||||
0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6,
|
||||
0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
|
||||
0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac,
|
||||
0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f,
|
||||
0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a,
|
||||
0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
|
||||
0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58,
|
||||
0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033,
|
||||
0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe,
|
||||
0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
|
||||
0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4,
|
||||
0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
|
||||
0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5,
|
||||
0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
|
||||
0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07,
|
||||
0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c,
|
||||
0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1,
|
||||
0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
|
||||
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b,
|
||||
0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698,
|
||||
0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d,
|
||||
0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
|
||||
0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f,
|
||||
0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
|
||||
0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80,
|
||||
0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
|
||||
0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a,
|
||||
0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629,
|
||||
0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c,
|
||||
0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
|
||||
0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e,
|
||||
0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65,
|
||||
0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8,
|
||||
0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
|
||||
0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2,
|
||||
0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
|
||||
0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74,
|
||||
0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
|
||||
0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21,
|
||||
0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a,
|
||||
0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087,
|
||||
0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
|
||||
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d,
|
||||
0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce,
|
||||
0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb,
|
||||
0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
|
||||
0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09,
|
||||
0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
|
||||
0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf,
|
||||
0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4,
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
package stario
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type StarBuffer struct {
|
||||
io.Reader
|
||||
io.Writer
|
||||
io.Closer
|
||||
datas []byte
|
||||
pStart uint64
|
||||
pEnd uint64
|
||||
cap uint64
|
||||
isClose atomic.Value
|
||||
isEnd atomic.Value
|
||||
rmu sync.Mutex
|
||||
wmu sync.Mutex
|
||||
}
|
||||
|
||||
func NewStarBuffer(cap uint64) *StarBuffer {
|
||||
rtnBuffer := new(StarBuffer)
|
||||
rtnBuffer.cap = cap
|
||||
rtnBuffer.datas = make([]byte, cap)
|
||||
rtnBuffer.isClose.Store(false)
|
||||
rtnBuffer.isEnd.Store(false)
|
||||
return rtnBuffer
|
||||
}
|
||||
|
||||
func (star *StarBuffer) Free() uint64 {
|
||||
return star.cap - star.Len()
|
||||
}
|
||||
|
||||
func (star *StarBuffer) Cap() uint64 {
|
||||
return star.cap
|
||||
}
|
||||
|
||||
func (star *StarBuffer) Len() uint64 {
|
||||
if star.pEnd >= star.pStart {
|
||||
return star.pEnd - star.pStart
|
||||
}
|
||||
return star.pEnd - star.pStart + star.cap
|
||||
}
|
||||
|
||||
func (star *StarBuffer) getByte() (byte, error) {
|
||||
if star.isClose.Load().(bool) || (star.Len() == 0 && star.isEnd.Load().(bool)) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if star.Len() == 0 {
|
||||
return 0, os.ErrNotExist
|
||||
}
|
||||
nowPtr := star.pStart
|
||||
nextPtr := star.pStart + 1
|
||||
if nextPtr >= star.cap {
|
||||
nextPtr = 0
|
||||
}
|
||||
data := star.datas[nowPtr]
|
||||
ok := atomic.CompareAndSwapUint64(&star.pStart, nowPtr, nextPtr)
|
||||
if !ok {
|
||||
return 0, os.ErrInvalid
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (star *StarBuffer) putByte(data byte) error {
|
||||
if star.isClose.Load().(bool) {
|
||||
return io.EOF
|
||||
}
|
||||
nowPtr := star.pEnd
|
||||
kariEnd := nowPtr + 1
|
||||
if kariEnd == star.cap {
|
||||
kariEnd = 0
|
||||
}
|
||||
if kariEnd == atomic.LoadUint64(&star.pStart) {
|
||||
for {
|
||||
time.Sleep(time.Microsecond)
|
||||
runtime.Gosched()
|
||||
if kariEnd != atomic.LoadUint64(&star.pStart) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
star.datas[nowPtr] = data
|
||||
if ok := atomic.CompareAndSwapUint64(&star.pEnd, nowPtr, kariEnd); !ok {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (star *StarBuffer) Close() error {
|
||||
star.isClose.Store(true)
|
||||
return nil
|
||||
}
|
||||
func (star *StarBuffer) Read(buf []byte) (int, error) {
|
||||
if star.isClose.Load().(bool) || (star.Len() == 0 && star.isEnd.Load().(bool)) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if buf == nil {
|
||||
return 0, errors.New("buffer is nil")
|
||||
}
|
||||
star.rmu.Lock()
|
||||
defer star.rmu.Unlock()
|
||||
var sum int = 0
|
||||
for i := 0; i < len(buf); i++ {
|
||||
data, err := star.getByte()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return sum, err
|
||||
}
|
||||
if err == os.ErrNotExist {
|
||||
i--
|
||||
continue
|
||||
}
|
||||
return sum, nil
|
||||
}
|
||||
buf[i] = data
|
||||
sum++
|
||||
}
|
||||
return sum, nil
|
||||
}
|
||||
|
||||
func (star *StarBuffer) Write(bts []byte) (int, error) {
|
||||
if bts == nil && !star.isEnd.Load().(bool) {
|
||||
star.isEnd.Store(true)
|
||||
return 0, nil
|
||||
}
|
||||
if bts == nil || star.isClose.Load().(bool) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
star.wmu.Lock()
|
||||
defer star.wmu.Unlock()
|
||||
var sum = 0
|
||||
for i := 0; i < len(bts); i++ {
|
||||
err := star.putByte(bts[i])
|
||||
if err != nil {
|
||||
fmt.Println("Write bts err:", err)
|
||||
return sum, err
|
||||
}
|
||||
sum++
|
||||
}
|
||||
return sum, nil
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package stario
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ERR_TIMEOUT = errors.New("TIME OUT")
|
||||
|
||||
func WaitUntilTimeout(tm time.Duration, fn func(chan struct{}) error) error {
|
||||
var err error
|
||||
finished := make(chan struct{})
|
||||
imout := make(chan struct{})
|
||||
go func() {
|
||||
err = fn(imout)
|
||||
finished <- struct{}{}
|
||||
}()
|
||||
select {
|
||||
case <-finished:
|
||||
return err
|
||||
case <-time.After(tm):
|
||||
close(imout)
|
||||
return ERR_TIMEOUT
|
||||
}
|
||||
}
|
||||
|
||||
func WaitUntilFinished(fn func() error) <-chan error {
|
||||
finished := make(chan error)
|
||||
go func() {
|
||||
err := fn()
|
||||
finished <- err
|
||||
}()
|
||||
return finished
|
||||
}
|
||||
|
||||
func WaitUntilTimeoutFinished(tm time.Duration, fn func(chan struct{}) error) <-chan error {
|
||||
var err error
|
||||
finished := make(chan struct{})
|
||||
result := make(chan error)
|
||||
imout := make(chan struct{})
|
||||
go func() {
|
||||
err = fn(imout)
|
||||
finished <- struct{}{}
|
||||
}()
|
||||
go func() {
|
||||
select {
|
||||
case <-finished:
|
||||
result <- err
|
||||
case <-time.After(tm):
|
||||
close(imout)
|
||||
result <- ERR_TIMEOUT
|
||||
}
|
||||
}()
|
||||
return result
|
||||
}
|
@ -0,0 +1,439 @@
|
||||
package stario
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type InputMsg struct {
|
||||
msg string
|
||||
err error
|
||||
skipSliceSigErr bool
|
||||
}
|
||||
|
||||
func Passwd(hint string, defaultVal string) InputMsg {
|
||||
return passwd(hint, defaultVal, "", false)
|
||||
}
|
||||
|
||||
func PasswdWithMask(hint string, defaultVal string, mask string) InputMsg {
|
||||
return passwd(hint, defaultVal, mask, false)
|
||||
}
|
||||
|
||||
func PasswdResponseSignal(hint string, defaultVal string) InputMsg {
|
||||
return passwd(hint, defaultVal, "", true)
|
||||
}
|
||||
|
||||
func PasswdResponseSignalWithMask(hint string, defaultVal string, mask string) InputMsg {
|
||||
return passwd(hint, defaultVal, mask, true)
|
||||
}
|
||||
|
||||
func MessageBoxRaw(hint string, defaultVal string) InputMsg {
|
||||
return messageBox(hint, defaultVal)
|
||||
}
|
||||
|
||||
func messageBox(hint string, defaultVal string) InputMsg {
|
||||
var ioBuf []rune
|
||||
if hint != "" {
|
||||
fmt.Print(hint)
|
||||
}
|
||||
if strings.Index(hint, "\n") >= 0 {
|
||||
hint = strings.TrimSpace(hint[strings.LastIndex(hint, "\n"):])
|
||||
}
|
||||
fd := int(os.Stdin.Fd())
|
||||
state, err := terminal.MakeRaw(fd)
|
||||
if err != nil {
|
||||
return InputMsg{msg: "", err: err}
|
||||
}
|
||||
defer fmt.Println()
|
||||
defer terminal.Restore(fd, state)
|
||||
inputReader := bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
b, _, err := inputReader.ReadRune()
|
||||
if err != nil {
|
||||
return InputMsg{msg: "", err: err}
|
||||
}
|
||||
if b == 0x0d {
|
||||
strValue := strings.TrimSpace(string(ioBuf))
|
||||
if len(strValue) == 0 {
|
||||
strValue = defaultVal
|
||||
}
|
||||
return InputMsg{msg: strValue, err: err}
|
||||
}
|
||||
if b == 0x08 || b == 0x7F {
|
||||
if len(ioBuf) > 0 {
|
||||
ioBuf = ioBuf[:len(ioBuf)-1]
|
||||
}
|
||||
fmt.Print("\r")
|
||||
for i := 0; i < len(ioBuf)+2+len(hint); i++ {
|
||||
fmt.Print(" ")
|
||||
}
|
||||
} else {
|
||||
ioBuf = append(ioBuf, b)
|
||||
}
|
||||
fmt.Print("\r")
|
||||
if hint != "" {
|
||||
fmt.Print(hint)
|
||||
}
|
||||
fmt.Print(string(ioBuf))
|
||||
}
|
||||
}
|
||||
|
||||
func isSiganl(s rune) bool {
|
||||
switch s {
|
||||
case 0x03, 0x1a, 0x1c:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func passwd(hint string, defaultVal string, mask string, handleSignal bool) InputMsg {
|
||||
var ioBuf []rune
|
||||
if hint != "" {
|
||||
fmt.Print(hint)
|
||||
}
|
||||
if strings.Index(hint, "\n") >= 0 {
|
||||
hint = strings.TrimSpace(hint[strings.LastIndex(hint, "\n"):])
|
||||
}
|
||||
fd := int(os.Stdin.Fd())
|
||||
state, err := terminal.MakeRaw(fd)
|
||||
if err != nil {
|
||||
return InputMsg{msg: "", err: err}
|
||||
}
|
||||
defer fmt.Println()
|
||||
defer terminal.Restore(fd, state)
|
||||
inputReader := bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
b, _, err := inputReader.ReadRune()
|
||||
if err != nil {
|
||||
return InputMsg{msg: "", err: err}
|
||||
}
|
||||
if handleSignal && isSiganl(b) {
|
||||
if runtime.GOOS != "windows" {
|
||||
terminal.Restore(fd, state)
|
||||
}
|
||||
if err := signal(b); err != nil {
|
||||
return InputMsg{
|
||||
msg: "",
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
if b == 0x0d {
|
||||
strValue := strings.TrimSpace(string(ioBuf))
|
||||
if len(strValue) == 0 {
|
||||
strValue = defaultVal
|
||||
}
|
||||
return InputMsg{msg: strValue, err: err}
|
||||
}
|
||||
if b == 0x08 || b == 0x7F {
|
||||
if len(ioBuf) > 0 {
|
||||
ioBuf = ioBuf[:len(ioBuf)-1]
|
||||
}
|
||||
fmt.Print("\r")
|
||||
for i := 0; i < len(ioBuf)+2+len(hint); i++ {
|
||||
fmt.Print(" ")
|
||||
}
|
||||
} else {
|
||||
ioBuf = append(ioBuf, b)
|
||||
}
|
||||
fmt.Print("\r")
|
||||
if hint != "" {
|
||||
fmt.Print(hint)
|
||||
}
|
||||
for i := 0; i < len(ioBuf); i++ {
|
||||
fmt.Print(mask)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func MessageBox(hint string, defaultVal string) InputMsg {
|
||||
if hint != "" {
|
||||
fmt.Print(hint)
|
||||
}
|
||||
inputReader := bufio.NewReader(os.Stdin)
|
||||
str, err := inputReader.ReadString('\n')
|
||||
if err != nil {
|
||||
return InputMsg{msg: str, err: err}
|
||||
}
|
||||
str = strings.TrimSpace(str)
|
||||
if len(str) == 0 {
|
||||
str = defaultVal
|
||||
}
|
||||
return InputMsg{msg: str, err: err}
|
||||
}
|
||||
|
||||
func (im InputMsg) IgnoreSliceParseError(i bool) InputMsg {
|
||||
im.skipSliceSigErr = i
|
||||
return im
|
||||
}
|
||||
|
||||
func (im InputMsg) String() (string, error) {
|
||||
if im.err != nil {
|
||||
return "", im.err
|
||||
}
|
||||
return im.msg, nil
|
||||
}
|
||||
|
||||
func (im InputMsg) MustString() string {
|
||||
res, _ := im.String()
|
||||
return res
|
||||
}
|
||||
|
||||
func (im InputMsg) SliceString(sep string) ([]string, error) {
|
||||
if im.err != nil {
|
||||
return nil, im.err
|
||||
}
|
||||
return strings.Split(im.msg, sep), nil
|
||||
}
|
||||
|
||||
func (im InputMsg) MustSliceString(sep string) []string {
|
||||
res, _ := im.SliceString(sep)
|
||||
return res
|
||||
}
|
||||
|
||||
func (im InputMsg) sliceFn(sep string, fn func(string) (interface{}, error)) ([]interface{}, error) {
|
||||
var res []interface{}
|
||||
data, err := im.SliceString(sep)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
for _, v := range data {
|
||||
code, err := fn(v)
|
||||
if err != nil && !im.skipSliceSigErr {
|
||||
return nil, err
|
||||
} else if err == nil {
|
||||
res = append(res, code)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (im InputMsg) Int() (int, error) {
|
||||
if im.err != nil {
|
||||
return 0, im.err
|
||||
}
|
||||
return strconv.Atoi(im.msg)
|
||||
}
|
||||
|
||||
func (im InputMsg) SliceInt(sep string) ([]int, error) {
|
||||
data, err := im.sliceFn(sep, func(v string) (interface{}, error) {
|
||||
return strconv.Atoi(v)
|
||||
})
|
||||
var res []int
|
||||
for _, v := range data {
|
||||
res = append(res, v.(int))
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (im InputMsg) MustSliceInt(sep string) []int {
|
||||
res, _ := im.SliceInt(sep)
|
||||
return res
|
||||
}
|
||||
|
||||
func (im InputMsg) MustInt() int {
|
||||
res, _ := im.Int()
|
||||
return res
|
||||
}
|
||||
|
||||
func (im InputMsg) Int64() (int64, error) {
|
||||
if im.err != nil {
|
||||
return 0, im.err
|
||||
}
|
||||
return strconv.ParseInt(im.msg, 10, 64)
|
||||
}
|
||||
|
||||
func (im InputMsg) MustInt64() int64 {
|
||||
res, _ := im.Int64()
|
||||
return res
|
||||
}
|
||||
|
||||
func (im InputMsg) SliceInt64(sep string) ([]int64, error) {
|
||||
data, err := im.sliceFn(sep, func(v string) (interface{}, error) {
|
||||
return strconv.ParseInt(v, 10, 64)
|
||||
})
|
||||
var res []int64
|
||||
for _, v := range data {
|
||||
res = append(res, v.(int64))
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (im InputMsg) MustSliceInt64(sep string) []int64 {
|
||||
res, _ := im.SliceInt64(sep)
|
||||
return res
|
||||
}
|
||||
|
||||
func (im InputMsg) Uint64() (uint64, error) {
|
||||
if im.err != nil {
|
||||
return 0, im.err
|
||||
}
|
||||
return strconv.ParseUint(im.msg, 10, 64)
|
||||
}
|
||||
|
||||
func (im InputMsg) MustUint64() uint64 {
|
||||
res, _ := im.Uint64()
|
||||
return res
|
||||
}
|
||||
func (im InputMsg) SliceUint64(sep string) ([]uint64, error) {
|
||||
data, err := im.sliceFn(sep, func(v string) (interface{}, error) {
|
||||
return strconv.ParseUint(v, 10, 64)
|
||||
})
|
||||
var res []uint64
|
||||
for _, v := range data {
|
||||
res = append(res, v.(uint64))
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (im InputMsg) MustSliceUint64(sep string) []uint64 {
|
||||
res, _ := im.SliceUint64(sep)
|
||||
return res
|
||||
}
|
||||
|
||||
func (im InputMsg) Bool() (bool, error) {
|
||||
if im.err != nil {
|
||||
return false, im.err
|
||||
}
|
||||
return strconv.ParseBool(im.msg)
|
||||
}
|
||||
|
||||
func (im InputMsg) MustBool() bool {
|
||||
res, _ := im.Bool()
|
||||
return res
|
||||
}
|
||||
|
||||
func (im InputMsg) SliceBool(sep string) ([]bool, error) {
|
||||
data, err := im.sliceFn(sep, func(v string) (interface{}, error) {
|
||||
return strconv.ParseBool(v)
|
||||
})
|
||||
var res []bool
|
||||
for _, v := range data {
|
||||
res = append(res, v.(bool))
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (im InputMsg) MustSliceBool(sep string) []bool {
|
||||
res, _ := im.SliceBool(sep)
|
||||
return res
|
||||
}
|
||||
|
||||
func (im InputMsg) Float64() (float64, error) {
|
||||
if im.err != nil {
|
||||
return 0, im.err
|
||||
}
|
||||
return strconv.ParseFloat(im.msg, 64)
|
||||
}
|
||||
|
||||
func (im InputMsg) MustFloat64() float64 {
|
||||
res, _ := im.Float64()
|
||||
return res
|
||||
}
|
||||
|
||||
func (im InputMsg) SliceFloat64(sep string) ([]float64, error) {
|
||||
data, err := im.sliceFn(sep, func(v string) (interface{}, error) {
|
||||
return strconv.ParseFloat(v, 64)
|
||||
})
|
||||
var res []float64
|
||||
for _, v := range data {
|
||||
res = append(res, v.(float64))
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (im InputMsg) MustSliceFloat64(sep string) []float64 {
|
||||
res, _ := im.SliceFloat64(sep)
|
||||
return res
|
||||
}
|
||||
|
||||
func (im InputMsg) Float32() (float32, error) {
|
||||
if im.err != nil {
|
||||
return 0, im.err
|
||||
}
|
||||
res, err := strconv.ParseFloat(im.msg, 32)
|
||||
return float32(res), err
|
||||
}
|
||||
|
||||
func (im InputMsg) MustFloat32() float32 {
|
||||
res, _ := im.Float32()
|
||||
return res
|
||||
}
|
||||
|
||||
func (im InputMsg) SliceFloat32(sep string) ([]float32, error) {
|
||||
data, err := im.sliceFn(sep, func(v string) (interface{}, error) {
|
||||
return strconv.ParseFloat(v, 32)
|
||||
})
|
||||
var res []float32
|
||||
for _, v := range data {
|
||||
res = append(res, v.(float32))
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (im InputMsg) MustSliceFloat32(sep string) []float32 {
|
||||
res, _ := im.SliceFloat32(sep)
|
||||
return res
|
||||
}
|
||||
|
||||
func YesNo(hint string, defaults bool) bool {
|
||||
for {
|
||||
res := strings.ToUpper(MessageBox(hint, "").MustString())
|
||||
if res == "" {
|
||||
return defaults
|
||||
}
|
||||
res = res[0:1]
|
||||
if res == "Y" {
|
||||
return true
|
||||
} else if res == "N" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StopUntil(hint string, trigger string, repeat bool) error {
|
||||
pressLen := len([]rune(trigger))
|
||||
if trigger == "" {
|
||||
pressLen = 1
|
||||
}
|
||||
fd := int(os.Stdin.Fd())
|
||||
if hint != "" {
|
||||
fmt.Print(hint)
|
||||
}
|
||||
state, err := terminal.MakeRaw(fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer terminal.Restore(fd, state)
|
||||
inputReader := bufio.NewReader(os.Stdin)
|
||||
//ioBuf := make([]byte, pressLen)
|
||||
i := 0
|
||||
for {
|
||||
b, _, err := inputReader.ReadRune()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if trigger == "" {
|
||||
break
|
||||
}
|
||||
if b == []rune(trigger)[i] {
|
||||
i++
|
||||
if i == pressLen {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
i = 0
|
||||
if hint != "" && repeat {
|
||||
fmt.Print("\r\n")
|
||||
fmt.Print(hint)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package stario
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
func signal(sigtype rune) error {
|
||||
//todo: use win32api call signal
|
||||
switch sigtype {
|
||||
case 0x03:
|
||||
return errors.New("SIGNAL SIGINT RECIVED")
|
||||
case 0x1a:
|
||||
return errors.New("SIGNAL SIGSTOP RECIVED")
|
||||
case 0x1c:
|
||||
return errors.New("SIGNAL SIGQUIT RECIVED")
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package stario
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func signal(sigtype rune) error {
|
||||
switch sigtype {
|
||||
case 0x03:
|
||||
syscall.Kill(os.Getpid(), syscall.SIGINT)
|
||||
return errors.New("SIGNAL SIGINT RECIVED")
|
||||
case 0x1a:
|
||||
syscall.Kill(os.Getpid(), syscall.SIGSTOP)
|
||||
return errors.New("SIGNAL SIGSTOP RECIVED")
|
||||
case 0x1c:
|
||||
syscall.Kill(os.Getpid(), syscall.SIGQUIT)
|
||||
return errors.New("SIGNAL SIGQUIT RECIVED")
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package stario
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type WaitGroup struct {
|
||||
wg *sync.WaitGroup
|
||||
maxCount uint32
|
||||
allCount uint32
|
||||
}
|
||||
|
||||
func NewWaitGroup(maxCount int) WaitGroup {
|
||||
return WaitGroup{wg: &sync.WaitGroup{}, maxCount: uint32(maxCount)}
|
||||
}
|
||||
|
||||
func (swg *WaitGroup) Add(delta int) {
|
||||
var Udelta uint32
|
||||
if delta < 0 {
|
||||
Udelta = uint32(-delta - 1)
|
||||
} else {
|
||||
Udelta = uint32(delta)
|
||||
}
|
||||
for {
|
||||
allC := atomic.LoadUint32(&swg.allCount)
|
||||
if atomic.LoadUint32(&swg.maxCount) == 0 || atomic.LoadUint32(&swg.maxCount) >= allC+uint32(delta) {
|
||||
if delta < 0 {
|
||||
atomic.AddUint32(&swg.allCount, ^uint32(Udelta))
|
||||
} else {
|
||||
atomic.AddUint32(&swg.allCount, uint32(Udelta))
|
||||
}
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Microsecond)
|
||||
}
|
||||
swg.wg.Add(delta)
|
||||
}
|
||||
|
||||
func (swg *WaitGroup) Done() {
|
||||
swg.Add(-1)
|
||||
}
|
||||
|
||||
func (swg *WaitGroup) Wait() {
|
||||
swg.wg.Wait()
|
||||
}
|
||||
|
||||
func (swg *WaitGroup) GetMaxWaitNum() int {
|
||||
return int(atomic.LoadUint32(&swg.maxCount))
|
||||
}
|
||||
|
||||
func (swg *WaitGroup) SetMaxWaitNum(num int) {
|
||||
atomic.AddUint32(&swg.maxCount, uint32(num))
|
||||
}
|
@ -0,0 +1,284 @@
|
||||
package starlog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"b612.me/staros"
|
||||
|
||||
"b612.me/starmap"
|
||||
)
|
||||
|
||||
var archMap starmap.StarMapKV
|
||||
|
||||
func init() {
|
||||
archMap = starmap.NewStarMap()
|
||||
}
|
||||
|
||||
type Archive interface {
|
||||
ShouldArchiveNow(string, os.FileInfo) bool
|
||||
NextLogFilePath(string, os.FileInfo) string
|
||||
Interval() int64
|
||||
HookBeforArchive() func(string, os.FileInfo) error
|
||||
HookAfterArchive() func(string, string, os.FileInfo) error
|
||||
}
|
||||
|
||||
type logfileinfo struct {
|
||||
fullpath string
|
||||
pointer *os.File
|
||||
}
|
||||
|
||||
func SetLogFile(path string, logger *StarLogger, appendMode bool) error {
|
||||
var fileMode int
|
||||
if appendMode {
|
||||
fileMode = os.O_APPEND | os.O_CREATE | os.O_WRONLY
|
||||
} else {
|
||||
fileMode = os.O_CREATE | os.O_WRONLY | os.O_TRUNC
|
||||
}
|
||||
fullpath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !appendMode && staros.Exists(fullpath) {
|
||||
os.Remove(fullpath)
|
||||
}
|
||||
fp, err := os.OpenFile(fullpath, fileMode, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if archMap.MustGet(logger.logcore.id) != nil {
|
||||
logger.SetSwitching(true)
|
||||
err := archMap.MustGet(logger.logcore.id).(logfileinfo).pointer.Close()
|
||||
if err != nil {
|
||||
logger.logcore.output = nil
|
||||
logger.SetSwitching(false)
|
||||
return err
|
||||
}
|
||||
err = archMap.Delete(logger.logcore.id)
|
||||
if err != nil {
|
||||
logger.logcore.output = nil
|
||||
logger.SetSwitching(false)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = archMap.Store(logger.logcore.id, logfileinfo{
|
||||
fullpath: fullpath,
|
||||
pointer: fp,
|
||||
})
|
||||
if err != nil {
|
||||
fp.Close()
|
||||
logger.logcore.output = nil
|
||||
logger.SetSwitching(false)
|
||||
return err
|
||||
}
|
||||
logger.SetSwitching(true)
|
||||
logger.logcore.output = fp
|
||||
logger.SetSwitching(false)
|
||||
return nil
|
||||
}
|
||||
|
||||
func CloseWithSwitching(logger *StarLogger) error {
|
||||
if archMap.MustGet(logger.logcore.id) != nil {
|
||||
logger.SetSwitching(true)
|
||||
err := archMap.MustGet(logger.logcore.id).(logfileinfo).pointer.Close()
|
||||
if err != nil {
|
||||
logger.logcore.output = nil
|
||||
return err
|
||||
}
|
||||
err = archMap.Delete(logger.logcore.id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Close(logger *StarLogger) error {
|
||||
defer logger.SetSwitching(false)
|
||||
return CloseWithSwitching(logger)
|
||||
}
|
||||
|
||||
func GetLogFileInfo(logger *StarLogger) (os.FileInfo, error) {
|
||||
if archMap.MustGet(logger.logcore.id) != nil {
|
||||
return archMap.MustGet(logger.logcore.id).(logfileinfo).pointer.Stat()
|
||||
}
|
||||
return nil, errors.New("logger don't have a register logfile")
|
||||
}
|
||||
|
||||
func StartArchive(logger *StarLogger, arch Archive) error {
|
||||
if archMap.MustGet("arch"+logger.logcore.id) != nil {
|
||||
return errors.New("already running")
|
||||
}
|
||||
stopChan := make(chan int)
|
||||
archMap.Store("arch"+logger.logcore.id, stopChan)
|
||||
go func(stopChan chan int, arch Archive, logger *StarLogger) {
|
||||
for {
|
||||
select {
|
||||
case <-stopChan:
|
||||
return
|
||||
case <-time.After(time.Second * time.Duration(arch.Interval())):
|
||||
}
|
||||
fileinfo, err := GetLogFileInfo(logger)
|
||||
if err != nil {
|
||||
logger.Errorf("cannot get log file info,reason is %v\n", err)
|
||||
continue
|
||||
}
|
||||
if archMap.MustGet(logger.logcore.id) == nil {
|
||||
logger.Errorf("cannot get log core info from the map:no such keys\n")
|
||||
continue
|
||||
}
|
||||
fullpath := archMap.MustGet(logger.logcore.id).(logfileinfo).fullpath
|
||||
if !arch.ShouldArchiveNow(fullpath, fileinfo) {
|
||||
continue
|
||||
}
|
||||
newLogPath := arch.NextLogFilePath(fullpath, fileinfo)
|
||||
if arch.HookBeforArchive() != nil {
|
||||
if err := arch.HookBeforArchive()(fullpath, fileinfo); err != nil {
|
||||
logger.Errorf("error occur while executing hook before archive,detail is %v\n", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if err := SetLogFile(newLogPath, logger, false); err != nil {
|
||||
logger.Errorf("error occur while executing coverting new log file,detail is %v\n", err)
|
||||
continue
|
||||
} else {
|
||||
logger.Debugln("Set Log Success")
|
||||
}
|
||||
fileinfo, err = GetLogFileInfo(logger)
|
||||
if err != nil {
|
||||
logger.Errorf("cannot get new log core info from the map:no such keys\n")
|
||||
continue
|
||||
}
|
||||
if arch.HookAfterArchive() != nil {
|
||||
if err := arch.HookAfterArchive()(fullpath, newLogPath, fileinfo); err != nil {
|
||||
logger.Errorf("error occur while executing hook after archive,detail is %v\n", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}(stopChan, arch, logger)
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsArchiveRun(logger *StarLogger) bool {
|
||||
if archMap.MustGet("arch"+logger.logcore.id) == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func StopArchive(logger *StarLogger) {
|
||||
if archMap.MustGet("arch"+logger.logcore.id) == nil {
|
||||
return
|
||||
}
|
||||
archMap.MustGet("arch" + logger.logcore.id).(chan int) <- 1
|
||||
}
|
||||
|
||||
type ArchiveByDate struct {
|
||||
interval int64
|
||||
checkInterval int64
|
||||
newFileNameStyle string
|
||||
hookBefor func(string, os.FileInfo) error
|
||||
hookAfter func(string, string, os.FileInfo) error
|
||||
}
|
||||
|
||||
func (abd *ArchiveByDate) ShouldArchiveNow(fullpath string, info os.FileInfo) bool {
|
||||
if time.Now().Unix()-staros.GetFileCreationTime(info).Unix() > abd.interval {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (abd *ArchiveByDate) NextLogFilePath(oldpath string, info os.FileInfo) string {
|
||||
dir := filepath.Dir(oldpath)
|
||||
newName := time.Now().Format(abd.newFileNameStyle)
|
||||
return filepath.Join(dir, newName)
|
||||
}
|
||||
|
||||
func (abd *ArchiveByDate) Interval() int64 {
|
||||
return abd.checkInterval
|
||||
}
|
||||
|
||||
func (abd *ArchiveByDate) HookBeforArchive() func(string, os.FileInfo) error {
|
||||
|
||||
return abd.hookBefor
|
||||
}
|
||||
|
||||
func (abd *ArchiveByDate) HookAfterArchive() func(string, string, os.FileInfo) error {
|
||||
return abd.hookAfter
|
||||
}
|
||||
|
||||
func (abd *ArchiveByDate) SetHookBeforArchive(f func(string, os.FileInfo) error) {
|
||||
|
||||
abd.hookBefor = f
|
||||
}
|
||||
|
||||
func (abd *ArchiveByDate) SetHookAfterArchive(f func(string, string, os.FileInfo) error) {
|
||||
abd.hookAfter = f
|
||||
}
|
||||
|
||||
func NewArchiveByDate(archInterval int64, checkInterval int64, fileStyle string, hookbefor func(string, os.FileInfo) error, hookafter func(string, string, os.FileInfo) error) *ArchiveByDate {
|
||||
return &ArchiveByDate{
|
||||
interval: archInterval,
|
||||
checkInterval: checkInterval,
|
||||
newFileNameStyle: fileStyle,
|
||||
hookBefor: hookbefor,
|
||||
hookAfter: hookafter,
|
||||
}
|
||||
}
|
||||
|
||||
type ArchiveBySize struct {
|
||||
size int64
|
||||
checkInterval int64
|
||||
newFileNameStyle string
|
||||
hookBefor func(string, os.FileInfo) error
|
||||
hookAfter func(string, string, os.FileInfo) error
|
||||
}
|
||||
|
||||
func (abd *ArchiveBySize) ShouldArchiveNow(fullpath string, info os.FileInfo) bool {
|
||||
if info.Size() > abd.size {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (abd *ArchiveBySize) NextLogFilePath(oldpath string, info os.FileInfo) string {
|
||||
dir := filepath.Dir(oldpath)
|
||||
newName := time.Now().Format(abd.newFileNameStyle)
|
||||
return filepath.Join(dir, newName)
|
||||
}
|
||||
|
||||
func (abd *ArchiveBySize) Interval() int64 {
|
||||
return abd.checkInterval
|
||||
}
|
||||
|
||||
func (abd *ArchiveBySize) HookBeforArchive() func(string, os.FileInfo) error {
|
||||
|
||||
return abd.hookBefor
|
||||
}
|
||||
|
||||
func (abd *ArchiveBySize) HookAfterArchive() func(string, string, os.FileInfo) error {
|
||||
return abd.hookAfter
|
||||
}
|
||||
|
||||
func (abd *ArchiveBySize) SetHookBeforArchive(f func(string, os.FileInfo) error) {
|
||||
|
||||
abd.hookBefor = f
|
||||
}
|
||||
|
||||
func (abd *ArchiveBySize) SetHookAfterArchive(f func(string, string, os.FileInfo) error) {
|
||||
abd.hookAfter = f
|
||||
}
|
||||
|
||||
func NewArchiveBySize(size int64, checkInterval int64, fileStyle string, hookbefor func(string, os.FileInfo) error, hookafter func(string, string, os.FileInfo) error) *ArchiveBySize {
|
||||
return &ArchiveBySize{
|
||||
size: size,
|
||||
checkInterval: checkInterval,
|
||||
newFileNameStyle: fileStyle,
|
||||
hookBefor: hookbefor,
|
||||
hookAfter: hookafter,
|
||||
}
|
||||
}
|
@ -0,0 +1,603 @@
|
||||
package starlog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"b612.me/starlog/colorable"
|
||||
"b612.me/starlog/isatty"
|
||||
)
|
||||
|
||||
var (
|
||||
// NoColor defines if the output is colorized or not. It's dynamically set to
|
||||
// false or true based on the stdout's file descriptor referring to a terminal
|
||||
// or not. This is a global option and affects all colors. For more control
|
||||
// over each color block use the methods DisableColor() individually.
|
||||
NoColor = os.Getenv("TERM") == "dumb" ||
|
||||
(!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()))
|
||||
|
||||
// Output defines the standard output of the print functions. By default
|
||||
// os.Stdout is used.
|
||||
Output = colorable.NewColorableStdout()
|
||||
|
||||
// Error defines a color supporting writer for os.Stderr.
|
||||
Errors = colorable.NewColorableStderr()
|
||||
|
||||
// colorsCache is used to reduce the count of created Color objects and
|
||||
// allows to reuse already created objects with required Attr.
|
||||
colorsCache = make(map[Attr]*Color)
|
||||
colorsCacheMu sync.Mutex // protects colorsCache
|
||||
)
|
||||
|
||||
// Color defines a custom color object which is defined by SGR parameters.
|
||||
type Color struct {
|
||||
params []Attr
|
||||
noColor *bool
|
||||
}
|
||||
|
||||
// Attr defines a single SGR Code
|
||||
type Attr int
|
||||
|
||||
const escape = "\x1b"
|
||||
|
||||
// Base attributes
|
||||
const (
|
||||
Reset Attr = iota
|
||||
Bold
|
||||
Faint
|
||||
Italic
|
||||
Underline
|
||||
BlinkSlow
|
||||
BlinkRapid
|
||||
ReverseVideo
|
||||
Concealed
|
||||
CrossedOut
|
||||
)
|
||||
|
||||
// Foreground text colors
|
||||
const (
|
||||
FgBlack Attr = iota + 30
|
||||
FgRed
|
||||
FgGreen
|
||||
FgYellow
|
||||
FgBlue
|
||||
FgMagenta
|
||||
FgCyan
|
||||
FgWhite
|
||||
)
|
||||
|
||||
// Foreground Hi-Intensity text colors
|
||||
const (
|
||||
FgHiBlack Attr = iota + 90
|
||||
FgHiRed
|
||||
FgHiGreen
|
||||
FgHiYellow
|
||||
FgHiBlue
|
||||
FgHiMagenta
|
||||
FgHiCyan
|
||||
FgHiWhite
|
||||
)
|
||||
|
||||
// Background text colors
|
||||
const (
|
||||
BgBlack Attr = iota + 40
|
||||
BgRed
|
||||
BgGreen
|
||||
BgYellow
|
||||
BgBlue
|
||||
BgMagenta
|
||||
BgCyan
|
||||
BgWhite
|
||||
)
|
||||
|
||||
// Background Hi-Intensity text colors
|
||||
const (
|
||||
BgHiBlack Attr = iota + 100
|
||||
BgHiRed
|
||||
BgHiGreen
|
||||
BgHiYellow
|
||||
BgHiBlue
|
||||
BgHiMagenta
|
||||
BgHiCyan
|
||||
BgHiWhite
|
||||
)
|
||||
|
||||
// New returns a newly created color object.
|
||||
func NewColor(value ...Attr) *Color {
|
||||
c := &Color{params: make([]Attr, 0)}
|
||||
c.Add(value...)
|
||||
return c
|
||||
}
|
||||
|
||||
// Set sets the given parameters immediately. It will change the color of
|
||||
// output with the given SGR parameters until color.Unset() is called.
|
||||
func Set(p ...Attr) *Color {
|
||||
c := NewColor(p...)
|
||||
c.Set()
|
||||
return c
|
||||
}
|
||||
|
||||
// Unset resets all escape attributes and clears the output. Usually should
|
||||
// be called after Set().
|
||||
func Unset() {
|
||||
if NoColor {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(Output, "%s[%dm", escape, Reset)
|
||||
}
|
||||
|
||||
// Set sets the SGR sequence.
|
||||
func (c *Color) Set() *Color {
|
||||
if c.isNoColorSet() {
|
||||
return c
|
||||
}
|
||||
|
||||
fmt.Fprintf(Output, c.format())
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Color) unset() {
|
||||
if c.isNoColorSet() {
|
||||
return
|
||||
}
|
||||
|
||||
Unset()
|
||||
}
|
||||
|
||||
func (c *Color) setWriter(w io.Writer) *Color {
|
||||
if c.isNoColorSet() {
|
||||
return c
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, c.format())
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Color) unsetWriter(w io.Writer) {
|
||||
if c.isNoColorSet() {
|
||||
return
|
||||
}
|
||||
|
||||
if NoColor {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s[%dm", escape, Reset)
|
||||
}
|
||||
|
||||
// Add is used to chain SGR parameters. Use as many as parameters to combine
|
||||
// and create custom color objects. Example: Add(color.FgRed, color.Underline).
|
||||
func (c *Color) Add(value ...Attr) *Color {
|
||||
c.params = append(c.params, value...)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Color) prepend(value Attr) {
|
||||
c.params = append(c.params, 0)
|
||||
copy(c.params[1:], c.params[0:])
|
||||
c.params[0] = value
|
||||
}
|
||||
|
||||
// Fprint formats using the default formats for its operands and writes to w.
|
||||
// Spaces are added between operands when neither is a string.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
||||
// type *os.File.
|
||||
func (c *Color) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
c.setWriter(w)
|
||||
defer c.unsetWriter(w)
|
||||
|
||||
return fmt.Fprint(w, a...)
|
||||
}
|
||||
|
||||
// Print formats using the default formats for its operands and writes to
|
||||
// standard output. Spaces are added between operands when neither is a
|
||||
// string. It returns the number of bytes written and any write error
|
||||
// encountered. This is the standard fmt.Print() method wrapped with the given
|
||||
// color.
|
||||
func (c *Color) Print(a ...interface{}) (n int, err error) {
|
||||
c.Set()
|
||||
defer c.unset()
|
||||
|
||||
return fmt.Fprint(Output, a...)
|
||||
}
|
||||
|
||||
// Fprintf formats according to a format specifier and writes to w.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
||||
// type *os.File.
|
||||
func (c *Color) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||
c.setWriter(w)
|
||||
defer c.unsetWriter(w)
|
||||
|
||||
return fmt.Fprintf(w, format, a...)
|
||||
}
|
||||
|
||||
// Printf formats according to a format specifier and writes to standard output.
|
||||
// It returns the number of bytes written and any write error encountered.
|
||||
// This is the standard fmt.Printf() method wrapped with the given color.
|
||||
func (c *Color) Printf(format string, a ...interface{}) (n int, err error) {
|
||||
c.Set()
|
||||
defer c.unset()
|
||||
|
||||
return fmt.Fprintf(Output, format, a...)
|
||||
}
|
||||
|
||||
// Fprintln formats using the default formats for its operands and writes to w.
|
||||
// Spaces are always added between operands and a newline is appended.
|
||||
// On Windows, users should wrap w with colorable.NewColorable() if w is of
|
||||
// type *os.File.
|
||||
func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
c.setWriter(w)
|
||||
defer c.unsetWriter(w)
|
||||
|
||||
return fmt.Fprintln(w, a...)
|
||||
}
|
||||
|
||||
// Println formats using the default formats for its operands and writes to
|
||||
// standard output. Spaces are always added between operands and a newline is
|
||||
// appended. It returns the number of bytes written and any write error
|
||||
// encountered. This is the standard fmt.Print() method wrapped with the given
|
||||
// color.
|
||||
func (c *Color) Println(a ...interface{}) (n int, err error) {
|
||||
c.Set()
|
||||
defer c.unset()
|
||||
|
||||
return fmt.Fprintln(Output, a...)
|
||||
}
|
||||
|
||||
// Sprint is just like Print, but returns a string instead of printing it.
|
||||
func (c *Color) Sprint(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprint(a...))
|
||||
}
|
||||
|
||||
// Sprintln is just like Println, but returns a string instead of printing it.
|
||||
func (c *Color) Sprintln(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintln(a...))
|
||||
}
|
||||
|
||||
// Sprintf is just like Printf, but returns a string instead of printing it.
|
||||
func (c *Color) Sprintf(format string, a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// FprintFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Fprint().
|
||||
func (c *Color) FprintFunc() func(w io.Writer, a ...interface{}) {
|
||||
return func(w io.Writer, a ...interface{}) {
|
||||
c.Fprint(w, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Print().
|
||||
func (c *Color) PrintFunc() func(a ...interface{}) {
|
||||
return func(a ...interface{}) {
|
||||
c.Print(a...)
|
||||
}
|
||||
}
|
||||
|
||||
// FprintfFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Fprintf().
|
||||
func (c *Color) FprintfFunc() func(w io.Writer, format string, a ...interface{}) {
|
||||
return func(w io.Writer, format string, a ...interface{}) {
|
||||
c.Fprintf(w, format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintfFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Printf().
|
||||
func (c *Color) PrintfFunc() func(format string, a ...interface{}) {
|
||||
return func(format string, a ...interface{}) {
|
||||
c.Printf(format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// FprintlnFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Fprintln().
|
||||
func (c *Color) FprintlnFunc() func(w io.Writer, a ...interface{}) {
|
||||
return func(w io.Writer, a ...interface{}) {
|
||||
c.Fprintln(w, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintlnFunc returns a new function that prints the passed arguments as
|
||||
// colorized with color.Println().
|
||||
func (c *Color) PrintlnFunc() func(a ...interface{}) {
|
||||
return func(a ...interface{}) {
|
||||
c.Println(a...)
|
||||
}
|
||||
}
|
||||
|
||||
// SprintFunc returns a new function that returns colorized strings for the
|
||||
// given arguments with fmt.Sprint(). Useful to put into or mix into other
|
||||
// string. Windows users should use this in conjunction with color.Output, example:
|
||||
//
|
||||
// put := New(FgYellow).SprintFunc()
|
||||
// fmt.Fprintf(color.Output, "This is a %s", put("warning"))
|
||||
func (c *Color) SprintFunc() func(a ...interface{}) string {
|
||||
return func(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprint(a...))
|
||||
}
|
||||
}
|
||||
|
||||
// SprintfFunc returns a new function that returns colorized strings for the
|
||||
// given arguments with fmt.Sprintf(). Useful to put into or mix into other
|
||||
// string. Windows users should use this in conjunction with color.Output.
|
||||
func (c *Color) SprintfFunc() func(format string, a ...interface{}) string {
|
||||
return func(format string, a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintf(format, a...))
|
||||
}
|
||||
}
|
||||
|
||||
// SprintlnFunc returns a new function that returns colorized strings for the
|
||||
// given arguments with fmt.Sprintln(). Useful to put into or mix into other
|
||||
// string. Windows users should use this in conjunction with color.Output.
|
||||
func (c *Color) SprintlnFunc() func(a ...interface{}) string {
|
||||
return func(a ...interface{}) string {
|
||||
return c.wrap(fmt.Sprintln(a...))
|
||||
}
|
||||
}
|
||||
|
||||
// sequence returns a formatted SGR sequence to be plugged into a "\x1b[...m"
|
||||
// an example output might be: "1;36" -> bold cyan
|
||||
func (c *Color) sequence() string {
|
||||
format := make([]string, len(c.params))
|
||||
for i, v := range c.params {
|
||||
format[i] = strconv.Itoa(int(v))
|
||||
}
|
||||
|
||||
return strings.Join(format, ";")
|
||||
}
|
||||
|
||||
// wrap wraps the s string with the colors attributes. The string is ready to
|
||||
// be printed.
|
||||
func (c *Color) wrap(s string) string {
|
||||
if c.isNoColorSet() {
|
||||
return s
|
||||
}
|
||||
|
||||
return c.format() + s + c.unformat()
|
||||
}
|
||||
|
||||
func (c *Color) format() string {
|
||||
return fmt.Sprintf("%s[%sm", escape, c.sequence())
|
||||
}
|
||||
|
||||
func (c *Color) unformat() string {
|
||||
return fmt.Sprintf("%s[%dm", escape, Reset)
|
||||
}
|
||||
|
||||
// DisableColor disables the color output. Useful to not change any existing
|
||||
// code and still being able to output. Can be used for flags like
|
||||
// "--no-color". To enable back use EnableColor() method.
|
||||
func (c *Color) DisableColor() {
|
||||
c.noColor = boolPtr(true)
|
||||
}
|
||||
|
||||
// EnableColor enables the color output. Use it in conjunction with
|
||||
// DisableColor(). Otherwise this method has no side effects.
|
||||
func (c *Color) EnableColor() {
|
||||
c.noColor = boolPtr(false)
|
||||
}
|
||||
|
||||
func (c *Color) isNoColorSet() bool {
|
||||
// check first if we have user setted action
|
||||
if c.noColor != nil {
|
||||
return *c.noColor
|
||||
}
|
||||
|
||||
// if not return the global option, which is disabled by default
|
||||
return NoColor
|
||||
}
|
||||
|
||||
// Equals returns a boolean value indicating whether two colors are equal.
|
||||
func (c *Color) Equals(c2 *Color) bool {
|
||||
if len(c.params) != len(c2.params) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, attr := range c.params {
|
||||
if !c2.attrExists(attr) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Color) attrExists(a Attr) bool {
|
||||
for _, attr := range c.params {
|
||||
if attr == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func boolPtr(v bool) *bool {
|
||||
return &v
|
||||
}
|
||||
|
||||
func getCachedColor(p Attr) *Color {
|
||||
colorsCacheMu.Lock()
|
||||
defer colorsCacheMu.Unlock()
|
||||
|
||||
c, ok := colorsCache[p]
|
||||
if !ok {
|
||||
c = NewColor(p)
|
||||
colorsCache[p] = c
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func colorPrint(format string, p Attr, a ...interface{}) {
|
||||
c := getCachedColor(p)
|
||||
|
||||
if !strings.HasSuffix(format, "\n") {
|
||||
format += "\n"
|
||||
}
|
||||
|
||||
if len(a) == 0 {
|
||||
c.Print(format)
|
||||
} else {
|
||||
c.Printf(format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
func colorString(format string, p Attr, a ...interface{}) string {
|
||||
c := getCachedColor(p)
|
||||
|
||||
if len(a) == 0 {
|
||||
return c.SprintFunc()(format)
|
||||
}
|
||||
|
||||
return c.SprintfFunc()(format, a...)
|
||||
}
|
||||
|
||||
// Black is a convenient helper function to print with black foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Black(format string, a ...interface{}) { colorPrint(format, FgBlack, a...) }
|
||||
|
||||
// Red is a convenient helper function to print with red foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Red(format string, a ...interface{}) { colorPrint(format, FgRed, a...) }
|
||||
|
||||
// Green is a convenient helper function to print with green foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Green(format string, a ...interface{}) { colorPrint(format, FgGreen, a...) }
|
||||
|
||||
// Yellow is a convenient helper function to print with yellow foreground.
|
||||
// A newline is appended to format by default.
|
||||
func Yellow(format string, a ...interface{}) { colorPrint(format, FgYellow, a...) }
|
||||
|
||||
// Blue is a convenient helper function to print with blue foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Blue(format string, a ...interface{}) { colorPrint(format, FgBlue, a...) }
|
||||
|
||||
// Magenta is a convenient helper function to print with magenta foreground.
|
||||
// A newline is appended to format by default.
|
||||
func Magenta(format string, a ...interface{}) { colorPrint(format, FgMagenta, a...) }
|
||||
|
||||
// Cyan is a convenient helper function to print with cyan foreground. A
|
||||
// newline is appended to format by default.
|
||||
func Cyan(format string, a ...interface{}) { colorPrint(format, FgCyan, a...) }
|
||||
|
||||
// White is a convenient helper function to print with white foreground. A
|
||||
// newline is appended to format by default.
|
||||
func White(format string, a ...interface{}) { colorPrint(format, FgWhite, a...) }
|
||||
|
||||
// BlackString is a convenient helper function to return a string with black
|
||||
// foreground.
|
||||
func BlackString(format string, a ...interface{}) string { return colorString(format, FgBlack, a...) }
|
||||
|
||||
// RedString is a convenient helper function to return a string with red
|
||||
// foreground.
|
||||
func RedString(format string, a ...interface{}) string { return colorString(format, FgRed, a...) }
|
||||
|
||||
// GreenString is a convenient helper function to return a string with green
|
||||
// foreground.
|
||||
func GreenString(format string, a ...interface{}) string { return colorString(format, FgGreen, a...) }
|
||||
|
||||
// YellowString is a convenient helper function to return a string with yellow
|
||||
// foreground.
|
||||
func YellowString(format string, a ...interface{}) string { return colorString(format, FgYellow, a...) }
|
||||
|
||||
// BlueString is a convenient helper function to return a string with blue
|
||||
// foreground.
|
||||
func BlueString(format string, a ...interface{}) string { return colorString(format, FgBlue, a...) }
|
||||
|
||||
// MagentaString is a convenient helper function to return a string with magenta
|
||||
// foreground.
|
||||
func MagentaString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgMagenta, a...)
|
||||
}
|
||||
|
||||
// CyanString is a convenient helper function to return a string with cyan
|
||||
// foreground.
|
||||
func CyanString(format string, a ...interface{}) string { return colorString(format, FgCyan, a...) }
|
||||
|
||||
// WhiteString is a convenient helper function to return a string with white
|
||||
// foreground.
|
||||
func WhiteString(format string, a ...interface{}) string { return colorString(format, FgWhite, a...) }
|
||||
|
||||
// HiBlack is a convenient helper function to print with hi-intensity black foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiBlack(format string, a ...interface{}) { colorPrint(format, FgHiBlack, a...) }
|
||||
|
||||
// HiRed is a convenient helper function to print with hi-intensity red foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiRed(format string, a ...interface{}) { colorPrint(format, FgHiRed, a...) }
|
||||
|
||||
// HiGreen is a convenient helper function to print with hi-intensity green foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiGreen(format string, a ...interface{}) { colorPrint(format, FgHiGreen, a...) }
|
||||
|
||||
// HiYellow is a convenient helper function to print with hi-intensity yellow foreground.
|
||||
// A newline is appended to format by default.
|
||||
func HiYellow(format string, a ...interface{}) { colorPrint(format, FgHiYellow, a...) }
|
||||
|
||||
// HiBlue is a convenient helper function to print with hi-intensity blue foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiBlue(format string, a ...interface{}) { colorPrint(format, FgHiBlue, a...) }
|
||||
|
||||
// HiMagenta is a convenient helper function to print with hi-intensity magenta foreground.
|
||||
// A newline is appended to format by default.
|
||||
func HiMagenta(format string, a ...interface{}) { colorPrint(format, FgHiMagenta, a...) }
|
||||
|
||||
// HiCyan is a convenient helper function to print with hi-intensity cyan foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiCyan(format string, a ...interface{}) { colorPrint(format, FgHiCyan, a...) }
|
||||
|
||||
// HiWhite is a convenient helper function to print with hi-intensity white foreground. A
|
||||
// newline is appended to format by default.
|
||||
func HiWhite(format string, a ...interface{}) { colorPrint(format, FgHiWhite, a...) }
|
||||
|
||||
// HiBlackString is a convenient helper function to return a string with hi-intensity black
|
||||
// foreground.
|
||||
func HiBlackString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiBlack, a...)
|
||||
}
|
||||
|
||||
// HiRedString is a convenient helper function to return a string with hi-intensity red
|
||||
// foreground.
|
||||
func HiRedString(format string, a ...interface{}) string { return colorString(format, FgHiRed, a...) }
|
||||
|
||||
// HiGreenString is a convenient helper function to return a string with hi-intensity green
|
||||
// foreground.
|
||||
func HiGreenString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiGreen, a...)
|
||||
}
|
||||
|
||||
// HiYellowString is a convenient helper function to return a string with hi-intensity yellow
|
||||
// foreground.
|
||||
func HiYellowString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiYellow, a...)
|
||||
}
|
||||
|
||||
// HiBlueString is a convenient helper function to return a string with hi-intensity blue
|
||||
// foreground.
|
||||
func HiBlueString(format string, a ...interface{}) string { return colorString(format, FgHiBlue, a...) }
|
||||
|
||||
// HiMagentaString is a convenient helper function to return a string with hi-intensity magenta
|
||||
// foreground.
|
||||
func HiMagentaString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiMagenta, a...)
|
||||
}
|
||||
|
||||
// HiCyanString is a convenient helper function to return a string with hi-intensity cyan
|
||||
// foreground.
|
||||
func HiCyanString(format string, a ...interface{}) string { return colorString(format, FgHiCyan, a...) }
|
||||
|
||||
// HiWhiteString is a convenient helper function to return a string with hi-intensity white
|
||||
// foreground.
|
||||
func HiWhiteString(format string, a ...interface{}) string {
|
||||
return colorString(format, FgHiWhite, a...)
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Yasuhiro Matsumoto
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -0,0 +1,48 @@
|
||||
# go-colorable
|
||||
|
||||
[![Build Status](https://travis-ci.org/mattn/go-colorable.svg?branch=master)](https://travis-ci.org/mattn/go-colorable)
|
||||
[![Codecov](https://codecov.io/gh/mattn/go-colorable/branch/master/graph/badge.svg)](https://codecov.io/gh/mattn/go-colorable)
|
||||
[![GoDoc](https://godoc.org/github.com/mattn/go-colorable?status.svg)](http://godoc.org/github.com/mattn/go-colorable)
|
||||
[![Go Report Card](https://goreportcard.com/badge/mattn/go-colorable)](https://goreportcard.com/report/mattn/go-colorable)
|
||||
|
||||
Colorable writer for windows.
|
||||
|
||||
For example, most of logger packages doesn't show colors on windows. (I know we can do it with ansicon. But I don't want.)
|
||||
This package is possible to handle escape sequence for ansi color on windows.
|
||||
|
||||
## Too Bad!
|
||||
|
||||
![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/bad.png)
|
||||
|
||||
|
||||
## So Good!
|
||||
|
||||
![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/good.png)
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true})
|
||||
logrus.SetOutput(colorable.NewColorableStdout())
|
||||
|
||||
logrus.Info("succeeded")
|
||||
logrus.Warn("not correct")
|
||||
logrus.Error("something error")
|
||||
logrus.Fatal("panic")
|
||||
```
|
||||
|
||||
You can compile above code on non-windows OSs.
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
$ go get github.com/mattn/go-colorable
|
||||
```
|
||||
|
||||
# License
|
||||
|
||||
MIT
|
||||
|
||||
# Author
|
||||
|
||||
Yasuhiro Matsumoto (a.k.a mattn)
|
@ -0,0 +1,37 @@
|
||||
// +build appengine
|
||||
|
||||
package colorable
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
_ "b612.me/starlog/isatty"
|
||||
)
|
||||
|
||||
// NewColorable returns new instance of Writer which handles escape sequence.
|
||||
func NewColorable(file *os.File) io.Writer {
|
||||
if file == nil {
|
||||
panic("nil passed instead of *os.File to NewColorable()")
|
||||
}
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
// NewColorableStdout returns new instance of Writer which handles escape sequence for stdout.
|
||||
func NewColorableStdout() io.Writer {
|
||||
return os.Stdout
|
||||
}
|
||||
|
||||
// NewColorableStderr returns new instance of Writer which handles escape sequence for stderr.
|
||||
func NewColorableStderr() io.Writer {
|
||||
return os.Stderr
|
||||
}
|
||||
|
||||
// EnableColorsStdout enable colors if possible.
|
||||
func EnableColorsStdout(enabled *bool) func() {
|
||||
if enabled != nil {
|
||||
*enabled = true
|
||||
}
|
||||
return func() {}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
// +build !windows
|
||||
// +build !appengine
|
||||
|
||||
package colorable
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
_ "b612.me/starlog/isatty"
|
||||
)
|
||||
|
||||
// NewColorable returns new instance of Writer which handles escape sequence.
|
||||
func NewColorable(file *os.File) io.Writer {
|
||||
if file == nil {
|
||||
panic("nil passed instead of *os.File to NewColorable()")
|
||||
}
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
// NewColorableStdout returns new instance of Writer which handles escape sequence for stdout.
|
||||
func NewColorableStdout() io.Writer {
|
||||
return os.Stdout
|
||||
}
|
||||
|
||||
// NewColorableStderr returns new instance of Writer which handles escape sequence for stderr.
|
||||
func NewColorableStderr() io.Writer {
|
||||
return os.Stderr
|
||||
}
|
||||
|
||||
// EnableColorsStdout enable colors if possible.
|
||||
func EnableColorsStdout(enabled *bool) func() {
|
||||
if enabled != nil {
|
||||
*enabled = true
|
||||
}
|
||||
return func() {}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
echo "" > coverage.txt
|
||||
|
||||
for d in $(go list ./... | grep -v vendor); do
|
||||
go test -race -coverprofile=profile.out -covermode=atomic "$d"
|
||||
if [ -f profile.out ]; then
|
||||
cat profile.out >> coverage.txt
|
||||
rm profile.out
|
||||
fi
|
||||
done
|
@ -0,0 +1,55 @@
|
||||
package colorable
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
// NonColorable holds writer but removes escape sequence.
|
||||
type NonColorable struct {
|
||||
out io.Writer
|
||||
}
|
||||
|
||||
// NewNonColorable returns new instance of Writer which removes escape sequence from Writer.
|
||||
func NewNonColorable(w io.Writer) io.Writer {
|
||||
return &NonColorable{out: w}
|
||||
}
|
||||
|
||||
// Write writes data on console
|
||||
func (w *NonColorable) Write(data []byte) (n int, err error) {
|
||||
er := bytes.NewReader(data)
|
||||
var bw [1]byte
|
||||
loop:
|
||||
for {
|
||||
c1, err := er.ReadByte()
|
||||
if err != nil {
|
||||
break loop
|
||||
}
|
||||
if c1 != 0x1b {
|
||||
bw[0] = c1
|
||||
w.out.Write(bw[:])
|
||||
continue
|
||||
}
|
||||
c2, err := er.ReadByte()
|
||||
if err != nil {
|
||||
break loop
|
||||
}
|
||||
if c2 != 0x5b {
|
||||
continue
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
for {
|
||||
c, err := er.ReadByte()
|
||||
if err != nil {
|
||||
break loop
|
||||
}
|
||||
if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' {
|
||||
break
|
||||
}
|
||||
buf.Write([]byte(string(c)))
|
||||
}
|
||||
}
|
||||
|
||||
return len(data), nil
|
||||
}
|
@ -0,0 +1,325 @@
|
||||
package starlog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func generateCoreLogStr(skip int, logstr string) string {
|
||||
var line int = 0
|
||||
var funcname, fileName string
|
||||
now := time.Now()
|
||||
|
||||
pc, fName, codeln, ok := runtime.Caller(skip)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
line = codeln
|
||||
funcname = runtime.FuncForPC(pc).Name()
|
||||
funcname = filepath.Ext(funcname)
|
||||
funcname = strings.TrimPrefix(funcname, ".")
|
||||
fileName = filepath.Base(fName)
|
||||
|
||||
y, m, d := now.Date()
|
||||
h, i, s := now.Clock()
|
||||
micro := now.Nanosecond() / 1e3
|
||||
logStr := fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d.%06d", y, m, d, h, i, s, micro)
|
||||
logStr += " " + fileName + ":" + strconv.Itoa(line)
|
||||
logStr += " <" + funcname + ">"
|
||||
logStr += " " + logstr
|
||||
return logStr
|
||||
}
|
||||
|
||||
func (logger *starlog) build(thread string, isStd bool, isShow bool, handler func(data LogData), level int, logDetail string) {
|
||||
logger.mu.Lock()
|
||||
defer logger.mu.Unlock()
|
||||
var skip, line int = 3, 0
|
||||
var funcname, fileName string
|
||||
now := time.Now()
|
||||
if isStd {
|
||||
skip++
|
||||
}
|
||||
if logger.showDeatilFile || logger.showFuncName {
|
||||
pc, fName, codeln, ok := runtime.Caller(skip)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
line = codeln
|
||||
funcname = runtime.FuncForPC(pc).Name()
|
||||
funcname = filepath.Ext(funcname)
|
||||
funcname = strings.TrimPrefix(funcname, ".")
|
||||
fileName = filepath.Base(fName)
|
||||
}
|
||||
|
||||
y, m, d := now.Date()
|
||||
h, i, s := now.Clock()
|
||||
micro := now.Nanosecond() / 1e3
|
||||
logStr := fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d.%06d", y, m, d, h, i, s, micro)
|
||||
var cenStr string
|
||||
if logger.showDeatilFile {
|
||||
cenStr += " " + fileName + ":" + strconv.Itoa(line)
|
||||
}
|
||||
if logger.showFuncName {
|
||||
cenStr += " <" + funcname + ">"
|
||||
}
|
||||
if logger.showThread {
|
||||
cenStr += " |" + thread + "|"
|
||||
}
|
||||
if logger.showLevel {
|
||||
cenStr += " " + `[` + levels[level] + `]`
|
||||
}
|
||||
if !logger.showColor || !logger.onlyColorLevel {
|
||||
logStr += cenStr + " " + logDetail
|
||||
} else {
|
||||
logStr += logger.colorMe[level].Sprint(cenStr) + " " + logDetail
|
||||
}
|
||||
if isShow {
|
||||
if !logger.showColor {
|
||||
if level >= logger.errOutputLevel {
|
||||
fmt.Fprint(os.Stderr, logStr)
|
||||
} else {
|
||||
fmt.Print(logStr)
|
||||
}
|
||||
} else if !logger.onlyColorLevel {
|
||||
if level < logger.errOutputLevel {
|
||||
logger.colorMe[level].Fprint(stdScreen, logStr)
|
||||
} else {
|
||||
logger.colorMe[level].Fprint(errScreen, logStr)
|
||||
}
|
||||
} else {
|
||||
if level < logger.errOutputLevel {
|
||||
fmt.Fprint(stdScreen, logStr)
|
||||
} else {
|
||||
fmt.Fprint(errScreen, logStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
if handler != nil {
|
||||
stacks.Push(logTransfer{
|
||||
handlerFunc: handler,
|
||||
LogData: LogData{
|
||||
Log: logStr,
|
||||
Colors: logger.colorList[level],
|
||||
Name: logger.name,
|
||||
},
|
||||
})
|
||||
}
|
||||
if !logger.stopWriter {
|
||||
logger.write(logStr)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *starlog) write(logStr string) {
|
||||
if logger.output == nil || logger.stopWriter {
|
||||
return
|
||||
}
|
||||
var count int = 0
|
||||
for logger.switching {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
count++
|
||||
if count > 50 {
|
||||
return
|
||||
}
|
||||
}
|
||||
if logger.output == nil {
|
||||
return
|
||||
}
|
||||
logger.output.Write([]byte(logStr))
|
||||
}
|
||||
|
||||
func (logger *starlog) print(str ...interface{}) string {
|
||||
return fmt.Sprint(str...)
|
||||
}
|
||||
|
||||
func (logger *starlog) printf(format string, str ...interface{}) string {
|
||||
return fmt.Sprintf(format, str...)
|
||||
}
|
||||
|
||||
func (logger *starlog) println(str ...interface{}) string {
|
||||
return fmt.Sprintln(str...)
|
||||
}
|
||||
|
||||
func (logger *starlog) Debug(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
||||
strs := fmt.Sprint(str...)
|
||||
logger.build(thread, isStd, logger.showStd, handler, LvDebug, strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Debugf(thread string, isStd bool, handler func(LogData), format string, str ...interface{}) {
|
||||
strs := fmt.Sprintf(format, str...)
|
||||
logger.build(thread, isStd, logger.showStd, handler, LvDebug, strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Debugln(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
||||
strs := fmt.Sprintln(str...)
|
||||
logger.build(thread, isStd, logger.showStd, handler, LvDebug, strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Info(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
||||
strs := fmt.Sprint(str...)
|
||||
logger.build(thread, isStd, logger.showStd, handler, LvInfo, strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Infof(thread string, isStd bool, handler func(LogData), format string, str ...interface{}) {
|
||||
strs := fmt.Sprintf(format, str...)
|
||||
logger.build(thread, isStd, logger.showStd, handler, LvInfo, strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Infoln(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
||||
strs := fmt.Sprintln(str...)
|
||||
logger.build(thread, isStd, logger.showStd, handler, LvInfo, strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Notice(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
||||
strs := fmt.Sprint(str...)
|
||||
logger.build(thread, isStd, logger.showStd, handler, LvNotice, strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Noticef(thread string, isStd bool, handler func(LogData), format string, str ...interface{}) {
|
||||
strs := fmt.Sprintf(format, str...)
|
||||
logger.build(thread, isStd, logger.showStd, handler, LvNotice, strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Noticeln(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
||||
strs := fmt.Sprintln(str...)
|
||||
logger.build(thread, isStd, logger.showStd, handler, LvNotice, strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Warning(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
||||
strs := fmt.Sprint(str...)
|
||||
logger.build(thread, isStd, logger.showStd, handler, LvWarning, strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Warningf(thread string, isStd bool, handler func(LogData), format string, str ...interface{}) {
|
||||
strs := fmt.Sprintf(format, str...)
|
||||
logger.build(thread, isStd, logger.showStd, handler, LvWarning, strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Warningln(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
||||
strs := fmt.Sprintln(str...)
|
||||
logger.build(thread, isStd, logger.showStd, handler, LvWarning, strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Error(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
||||
strs := fmt.Sprint(str...)
|
||||
logger.build(thread, isStd, logger.showStd, handler, LvError, strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Errorf(thread string, isStd bool, handler func(LogData), format string, str ...interface{}) {
|
||||
strs := fmt.Sprintf(format, str...)
|
||||
logger.build(thread, isStd, logger.showStd, handler, LvError, strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Errorln(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
||||
strs := fmt.Sprintln(str...)
|
||||
logger.build(thread, isStd, logger.showStd, handler, LvError, strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Critical(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
||||
strs := fmt.Sprint(str...)
|
||||
logger.build(thread, isStd, logger.showStd, handler, LvCritical, strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Criticalf(thread string, isStd bool, handler func(LogData), format string, str ...interface{}) {
|
||||
strs := fmt.Sprintf(format, str...)
|
||||
logger.build(thread, isStd, logger.showStd, handler, LvCritical, strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Criticalln(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
||||
strs := fmt.Sprintln(str...)
|
||||
logger.build(thread, isStd, logger.showStd, handler, LvCritical, strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Fatal(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
||||
strs := fmt.Sprint(str...)
|
||||
logger.build(thread, isStd, logger.showStd, handler, LvFatal, strs)
|
||||
os.Exit(9)
|
||||
}
|
||||
|
||||
func (logger *starlog) Fatalf(thread string, isStd bool, handler func(LogData), format string, str ...interface{}) {
|
||||
strs := fmt.Sprintf(format, str...)
|
||||
logger.build(thread, isStd, logger.showStd, handler, LvFatal, strs)
|
||||
os.Exit(9)
|
||||
}
|
||||
|
||||
func (logger *starlog) Fatalln(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
||||
strs := fmt.Sprintln(str...)
|
||||
logger.build(thread, isStd, logger.showStd, handler, LvFatal, strs)
|
||||
os.Exit(9)
|
||||
}
|
||||
|
||||
func (logger *starlog) Panic(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
||||
strs := fmt.Sprint(str...)
|
||||
logger.build(thread, isStd, logger.showStd, handler, LvPanic, strs)
|
||||
panic(str)
|
||||
}
|
||||
|
||||
func (logger *starlog) Panicf(thread string, isStd bool, handler func(LogData), format string, str ...interface{}) {
|
||||
strs := fmt.Sprintf(format, str...)
|
||||
logger.build(thread, isStd, logger.showStd, handler, LvPanic, strs)
|
||||
panic(fmt.Sprintf(format, str...))
|
||||
}
|
||||
|
||||
func (logger *starlog) Panicln(thread string, isStd bool, handler func(LogData), str ...interface{}) {
|
||||
strs := fmt.Sprintln(str...)
|
||||
logger.build(thread, isStd, logger.showStd, handler, LvPanic, strs)
|
||||
panic(fmt.Sprintln(str...))
|
||||
}
|
||||
|
||||
func (logger *starlog) Print(thread string, isStd bool, isShow bool, handler func(LogData), str ...interface{}) {
|
||||
strs := fmt.Sprint(str...)
|
||||
if isShow {
|
||||
fmt.Print(strs)
|
||||
}
|
||||
logger.write(strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Printf(thread string, isStd bool, isShow bool, handler func(LogData), format string, str ...interface{}) {
|
||||
strs := fmt.Sprintf(format, str...)
|
||||
if isShow {
|
||||
fmt.Print(strs)
|
||||
}
|
||||
logger.write(strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Println(thread string, isStd bool, isShow bool, handler func(LogData), str ...interface{}) {
|
||||
strs := fmt.Sprintln(str...)
|
||||
if isShow {
|
||||
fmt.Print(strs)
|
||||
}
|
||||
logger.write(strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Log(thread string, isStd bool, isShow bool, level int, handler func(LogData), str ...interface{}) {
|
||||
strs := fmt.Sprint(str...)
|
||||
logger.build(thread, isStd, isShow, handler, level, strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Logf(thread string, isStd bool, isShow bool, level int, handler func(LogData), format string, str ...interface{}) {
|
||||
strs := fmt.Sprintf(format, str...)
|
||||
logger.build(thread, isStd, isShow, handler, level, strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Logln(thread string, isStd bool, isShow bool, level int, handler func(LogData), str ...interface{}) {
|
||||
strs := fmt.Sprintln(str...)
|
||||
logger.build(thread, isStd, isShow, handler, level, strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Write(str ...interface{}) {
|
||||
strs := fmt.Sprint(str...)
|
||||
logger.Write(strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Writef(format string, str ...interface{}) {
|
||||
strs := fmt.Sprintf(format, str...)
|
||||
logger.Write(strs)
|
||||
}
|
||||
|
||||
func (logger *starlog) Writeln(str ...interface{}) {
|
||||
strs := fmt.Sprintln(str...)
|
||||
logger.Write(strs)
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
Copyright (c) Yasuhiro MATSUMOTO <mattn.jp@gmail.com>
|
||||
|
||||
MIT License (Expat)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@ -0,0 +1,50 @@
|
||||
# go-isatty
|
||||
|
||||
[![Godoc Reference](https://godoc.org/github.com/mattn/go-isatty?status.svg)](http://godoc.org/github.com/mattn/go-isatty)
|
||||
[![Codecov](https://codecov.io/gh/mattn/go-isatty/branch/master/graph/badge.svg)](https://codecov.io/gh/mattn/go-isatty)
|
||||
[![Coverage Status](https://coveralls.io/repos/github/mattn/go-isatty/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-isatty?branch=master)
|
||||
[![Go Report Card](https://goreportcard.com/badge/mattn/go-isatty)](https://goreportcard.com/report/mattn/go-isatty)
|
||||
|
||||
isatty for golang
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mattn/go-isatty"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
fmt.Println("Is Terminal")
|
||||
} else if isatty.IsCygwinTerminal(os.Stdout.Fd()) {
|
||||
fmt.Println("Is Cygwin/MSYS2 Terminal")
|
||||
} else {
|
||||
fmt.Println("Is Not Terminal")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
$ go get github.com/mattn/go-isatty
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Author
|
||||
|
||||
Yasuhiro Matsumoto (a.k.a mattn)
|
||||
|
||||
## Thanks
|
||||
|
||||
* k-takata: base idea for IsCygwinTerminal
|
||||
|
||||
https://github.com/k-takata/go-iscygpty
|
@ -0,0 +1,2 @@
|
||||
// Package isatty implements interface to isatty
|
||||
package isatty
|
@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
echo "" > coverage.txt
|
||||
|
||||
for d in $(go list ./... | grep -v vendor); do
|
||||
go test -race -coverprofile=profile.out -covermode=atomic "$d"
|
||||
if [ -f profile.out ]; then
|
||||
cat profile.out >> coverage.txt
|
||||
rm profile.out
|
||||
fi
|
||||
done
|
@ -0,0 +1,18 @@
|
||||
// +build darwin freebsd openbsd netbsd dragonfly
|
||||
// +build !appengine
|
||||
|
||||
package isatty
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
// IsTerminal return true if the file descriptor is terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
_, err := unix.IoctlGetTermios(int(fd), unix.TIOCGETA)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
// +build appengine js nacl wasm
|
||||
|
||||
package isatty
|
||||
|
||||
// IsTerminal returns true if the file descriptor is terminal which
|
||||
// is always false on js and appengine classic which is a sandboxed PaaS.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
// +build plan9
|
||||
|
||||
package isatty
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
path, err := syscall.Fd2path(int(fd))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return path == "/dev/cons" || path == "/mnt/term/dev/cons"
|
||||
}
|
||||
|
||||
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
// +build solaris
|
||||
// +build !appengine
|
||||
|
||||
package isatty
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
// see: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
var termio unix.Termio
|
||||
err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
// +build linux aix
|
||||
// +build !appengine
|
||||
|
||||
package isatty
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
// IsTerminal return true if the file descriptor is terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
_, err := unix.IoctlGetTermios(int(fd), unix.TCGETS)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
// +build windows
|
||||
// +build !appengine
|
||||
|
||||
package isatty
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
objectNameInfo uintptr = 1
|
||||
fileNameInfo = 2
|
||||
fileTypePipe = 3
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
ntdll = syscall.NewLazyDLL("ntdll.dll")
|
||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||
procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx")
|
||||
procGetFileType = kernel32.NewProc("GetFileType")
|
||||
procNtQueryObject = ntdll.NewProc("NtQueryObject")
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Check if GetFileInformationByHandleEx is available.
|
||||
if procGetFileInformationByHandleEx.Find() != nil {
|
||||
procGetFileInformationByHandleEx = nil
|
||||
}
|
||||
}
|
||||
|
||||
// IsTerminal return true if the file descriptor is terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
var st uint32
|
||||
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0)
|
||||
return r != 0 && e == 0
|
||||
}
|
||||
|
||||
// Check pipe name is used for cygwin/msys2 pty.
|
||||
// Cygwin/MSYS2 PTY has a name like:
|
||||
// \{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master
|
||||
func isCygwinPipeName(name string) bool {
|
||||
token := strings.Split(name, "-")
|
||||
if len(token) < 5 {
|
||||
return false
|
||||
}
|
||||
|
||||
if token[0] != `\msys` &&
|
||||
token[0] != `\cygwin` &&
|
||||
token[0] != `\Device\NamedPipe\msys` &&
|
||||
token[0] != `\Device\NamedPipe\cygwin` {
|
||||
return false
|
||||
}
|
||||
|
||||
if token[1] == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(token[2], "pty") {
|
||||
return false
|
||||
}
|
||||
|
||||
if token[3] != `from` && token[3] != `to` {
|
||||
return false
|
||||
}
|
||||
|
||||
if token[4] != "master" {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// getFileNameByHandle use the undocomented ntdll NtQueryObject to get file full name from file handler
|
||||
// since GetFileInformationByHandleEx is not avilable under windows Vista and still some old fashion
|
||||
// guys are using Windows XP, this is a workaround for those guys, it will also work on system from
|
||||
// Windows vista to 10
|
||||
// see https://stackoverflow.com/a/18792477 for details
|
||||
func getFileNameByHandle(fd uintptr) (string, error) {
|
||||
if procNtQueryObject == nil {
|
||||
return "", errors.New("ntdll.dll: NtQueryObject not supported")
|
||||
}
|
||||
|
||||
var buf [4 + syscall.MAX_PATH]uint16
|
||||
var result int
|
||||
r, _, e := syscall.Syscall6(procNtQueryObject.Addr(), 5,
|
||||
fd, objectNameInfo, uintptr(unsafe.Pointer(&buf)), uintptr(2*len(buf)), uintptr(unsafe.Pointer(&result)), 0)
|
||||
if r != 0 {
|
||||
return "", e
|
||||
}
|
||||
return string(utf16.Decode(buf[4 : 4+buf[0]/2])), nil
|
||||
}
|
||||
|
||||
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
|
||||
// terminal.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
if procGetFileInformationByHandleEx == nil {
|
||||
name, err := getFileNameByHandle(fd)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return isCygwinPipeName(name)
|
||||
}
|
||||
|
||||
// Cygwin/msys's pty is a pipe.
|
||||
ft, _, e := syscall.Syscall(procGetFileType.Addr(), 1, fd, 0, 0)
|
||||
if ft != fileTypePipe || e != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var buf [2 + syscall.MAX_PATH]uint16
|
||||
r, _, e := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(),
|
||||
4, fd, fileNameInfo, uintptr(unsafe.Pointer(&buf)),
|
||||
uintptr(len(buf)*2), 0, 0)
|
||||
if r == 0 || e != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
l := *(*uint32)(unsafe.Pointer(&buf))
|
||||
return isCygwinPipeName(string(utf16.Decode(buf[2 : 2+l/2])))
|
||||
}
|
@ -0,0 +1,322 @@
|
||||
package starlog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var Std *StarLogger
|
||||
var stdmu sync.Mutex
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
stackStopChan = make(chan int)
|
||||
Std = NewStarlog(nil)
|
||||
}
|
||||
|
||||
func SetShowColor(val bool) {
|
||||
Std.SetShowColor(val)
|
||||
}
|
||||
|
||||
func GetShowColor() bool {
|
||||
return Std.GetShowColor()
|
||||
}
|
||||
|
||||
func SetLevelColor(level int, color []Attr) {
|
||||
Std.SetLevelColor(level, color)
|
||||
}
|
||||
|
||||
func GetLevelColor(level int) []Attr {
|
||||
return Std.GetLevelColor(level)
|
||||
}
|
||||
|
||||
func Debug(str ...interface{}) {
|
||||
stdmu.Lock()
|
||||
defer stdmu.Unlock()
|
||||
Std.isStd = true
|
||||
Std.Debug(str...)
|
||||
Std.isStd = false
|
||||
}
|
||||
|
||||
func Debugf(format string, str ...interface{}) {
|
||||
stdmu.Lock()
|
||||
defer stdmu.Unlock()
|
||||
Std.isStd = true
|
||||
Std.Debugf(format, str...)
|
||||
Std.isStd = false
|
||||
}
|
||||
|
||||
func Debugln(str ...interface{}) {
|
||||
stdmu.Lock()
|
||||
defer stdmu.Unlock()
|
||||
Std.isStd = true
|
||||
Std.Debugln(str...)
|
||||
Std.isStd = false
|
||||
}
|
||||
|
||||
func Info(str ...interface{}) {
|
||||
stdmu.Lock()
|
||||
defer stdmu.Unlock()
|
||||
Std.isStd = true
|
||||
Std.Info(str...)
|
||||
Std.isStd = false
|
||||
}
|
||||
|
||||
func Infof(format string, str ...interface{}) {
|
||||
stdmu.Lock()
|
||||
defer stdmu.Unlock()
|
||||
Std.isStd = true
|
||||
Std.Infof(format, str...)
|
||||
Std.isStd = false
|
||||
}
|
||||
|
||||
func Infoln(str ...interface{}) {
|
||||
stdmu.Lock()
|
||||
defer stdmu.Unlock()
|
||||
Std.isStd = true
|
||||
Std.Infoln(str...)
|
||||
Std.isStd = false
|
||||
}
|
||||
|
||||
func Notice(str ...interface{}) {
|
||||
stdmu.Lock()
|
||||
defer stdmu.Unlock()
|
||||
Std.isStd = true
|
||||
Std.Notice(str...)
|
||||
Std.isStd = false
|
||||
}
|
||||
|
||||
func Noticef(format string, str ...interface{}) {
|
||||
stdmu.Lock()
|
||||
defer stdmu.Unlock()
|
||||
Std.isStd = true
|
||||
Std.Noticef(format, str...)
|
||||
Std.isStd = false
|
||||
}
|
||||
|
||||
func Noticeln(str ...interface{}) {
|
||||
stdmu.Lock()
|
||||
defer stdmu.Unlock()
|
||||
Std.isStd = true
|
||||
Std.Noticeln(str...)
|
||||
Std.isStd = false
|
||||
}
|
||||
|
||||
func Warning(str ...interface{}) {
|
||||
stdmu.Lock()
|
||||
defer stdmu.Unlock()
|
||||
Std.isStd = true
|
||||
Std.Warning(str...)
|
||||
Std.isStd = false
|
||||
}
|
||||
|
||||
func Warningf(format string, str ...interface{}) {
|
||||
stdmu.Lock()
|
||||
defer stdmu.Unlock()
|
||||
Std.isStd = true
|
||||
Std.Warningf(format, str...)
|
||||
Std.isStd = false
|
||||
}
|
||||
|
||||
func Warningln(str ...interface{}) {
|
||||
stdmu.Lock()
|
||||
defer stdmu.Unlock()
|
||||
Std.isStd = true
|
||||
Std.Warningln(str...)
|
||||
Std.isStd = false
|
||||
}
|
||||
|
||||
func Error(str ...interface{}) {
|
||||
stdmu.Lock()
|
||||
defer stdmu.Unlock()
|
||||
Std.isStd = true
|
||||
Std.Error(str...)
|
||||
Std.isStd = false
|
||||
}
|
||||
|
||||
func Errorf(format string, str ...interface{}) {
|
||||
stdmu.Lock()
|
||||
defer stdmu.Unlock()
|
||||
Std.isStd = true
|
||||
Std.Errorf(format, str...)
|
||||
Std.isStd = false
|
||||
}
|
||||
|
||||
func Errorln(str ...interface{}) {
|
||||
stdmu.Lock()
|
||||
defer stdmu.Unlock()
|
||||
Std.isStd = true
|
||||
Std.Errorln(str...)
|
||||
Std.isStd = false
|
||||
}
|
||||
|
||||
func Critical(str ...interface{}) {
|
||||
stdmu.Lock()
|
||||
defer stdmu.Unlock()
|
||||
Std.isStd = true
|
||||
Std.Critical(str...)
|
||||
Std.isStd = false
|
||||
}
|
||||
|
||||
func Criticalf(format string, str ...interface{}) {
|
||||
stdmu.Lock()
|
||||
defer stdmu.Unlock()
|
||||
Std.isStd = true
|
||||
Std.Criticalf(format, str...)
|
||||
Std.isStd = false
|
||||
}
|
||||
|
||||
func Criticalln(str ...interface{}) {
|
||||
stdmu.Lock()
|
||||
defer stdmu.Unlock()
|
||||
Std.isStd = true
|
||||
Std.Criticalln(str...)
|
||||
Std.isStd = false
|
||||
}
|
||||
|
||||
func Fatal(str ...interface{}) {
|
||||
stdmu.Lock()
|
||||
defer stdmu.Unlock()
|
||||
Std.isStd = true
|
||||
Std.Fatal(str...)
|
||||
Std.isStd = false
|
||||
}
|
||||
|
||||
func Fatalf(format string, str ...interface{}) {
|
||||
stdmu.Lock()
|
||||
defer stdmu.Unlock()
|
||||
Std.isStd = true
|
||||
Std.Fatalf(format, str...)
|
||||
Std.isStd = false
|
||||
}
|
||||
|
||||
func Panicln(str ...interface{}) {
|
||||
stdmu.Lock()
|
||||
defer stdmu.Unlock()
|
||||
Std.isStd = true
|
||||
Std.Fatalln(str...)
|
||||
Std.isStd = false
|
||||
}
|
||||
|
||||
func Print(str ...interface{}) {
|
||||
Std.Print(str...)
|
||||
}
|
||||
|
||||
func Printf(format string, str ...interface{}) {
|
||||
Std.Printf(format, str...)
|
||||
}
|
||||
|
||||
func Println(str ...interface{}) {
|
||||
Std.Println(str...)
|
||||
}
|
||||
|
||||
func Log(isShow bool, level int, str ...interface{}) {
|
||||
stdmu.Lock()
|
||||
defer stdmu.Unlock()
|
||||
Std.isStd = true
|
||||
Std.Log(isShow, level, str...)
|
||||
Std.isStd = false
|
||||
}
|
||||
|
||||
func Logf(isShow bool, level int, format string, str ...interface{}) {
|
||||
stdmu.Lock()
|
||||
defer stdmu.Unlock()
|
||||
Std.isStd = true
|
||||
Std.Logf(isShow, level, format, str...)
|
||||
Std.isStd = false
|
||||
}
|
||||
|
||||
func Logln(isShow bool, level int, str ...interface{}) {
|
||||
stdmu.Lock()
|
||||
defer stdmu.Unlock()
|
||||
Std.isStd = true
|
||||
Std.Logln(isShow, level, str...)
|
||||
Std.isStd = false
|
||||
}
|
||||
|
||||
func StdPrint(attr []Attr, str ...interface{}) {
|
||||
strs := fmt.Sprint(str...)
|
||||
NewColor(attr...).Fprint(stdScreen, strs)
|
||||
}
|
||||
|
||||
func StdPrintf(attr []Attr, format string, str ...interface{}) {
|
||||
strs := fmt.Sprintf(format, str...)
|
||||
NewColor(attr...).Fprint(stdScreen, strs)
|
||||
}
|
||||
|
||||
func StdPrintln(attr []Attr, str ...interface{}) {
|
||||
strs := fmt.Sprintln(str...)
|
||||
NewColor(attr...).Fprint(stdScreen, strs)
|
||||
}
|
||||
|
||||
func SetWriter(wr io.Writer) {
|
||||
Std.SetWriter(wr)
|
||||
}
|
||||
func GetWriter() io.Writer {
|
||||
return Std.GetWriter()
|
||||
}
|
||||
|
||||
func SetHandler(f func(LogData)) {
|
||||
Std.SetHandler(f)
|
||||
}
|
||||
func GetHandler() func(LogData) {
|
||||
return Std.GetHandler()
|
||||
}
|
||||
func SetSwitching(sw bool) {
|
||||
Std.SetSwitching(sw)
|
||||
}
|
||||
|
||||
func SetShowOriginFile(val bool) {
|
||||
Std.SetShowOriginFile(val)
|
||||
}
|
||||
|
||||
func GetShowOriginFile() bool {
|
||||
return Std.GetShowOriginFile()
|
||||
}
|
||||
|
||||
func SetShowFuncName(val bool) {
|
||||
Std.logcore.showFuncName = val
|
||||
}
|
||||
|
||||
func GetShowFuncName() bool {
|
||||
return Std.logcore.showFuncName
|
||||
}
|
||||
|
||||
func SetShowLevel(val bool) {
|
||||
Std.SetShowLevel(val)
|
||||
}
|
||||
|
||||
func GetShowLevel() bool {
|
||||
return Std.GetShowLevel()
|
||||
}
|
||||
|
||||
func SetShowFlag(val bool) {
|
||||
Std.SetShowFlag(val)
|
||||
}
|
||||
|
||||
func GetShowFlag() bool {
|
||||
return Std.GetShowFlag()
|
||||
}
|
||||
|
||||
func SetShowStd(val bool) {
|
||||
Std.SetShowStd(val)
|
||||
}
|
||||
|
||||
func GetShowStd() bool {
|
||||
return Std.GetShowStd()
|
||||
}
|
||||
|
||||
func StopWrite() {
|
||||
Std.StopWrite()
|
||||
}
|
||||
|
||||
func EnbaleWrite() {
|
||||
Std.EnbaleWrite()
|
||||
}
|
||||
|
||||
func IsWriteStoed() bool {
|
||||
return Std.IsWriteStoed()
|
||||
}
|
@ -0,0 +1,221 @@
|
||||
package starlog
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
func (logger *StarLogger) SetShowColor(val bool) {
|
||||
logger.logcore.showColor = val
|
||||
}
|
||||
|
||||
func (logger *StarLogger) GetShowColor() bool {
|
||||
return logger.logcore.showColor
|
||||
}
|
||||
|
||||
func (logger *StarLogger) SetLevelColor(level int, color []Attr) {
|
||||
logger.logcore.colorList[level] = color
|
||||
logger.logcore.colorMe[level] = NewColor(color...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) GetLevelColor(level int) []Attr {
|
||||
return logger.logcore.colorList[level]
|
||||
}
|
||||
|
||||
func (logger *StarLogger) SetWriter(wr io.Writer) {
|
||||
logger.logcore.output = wr
|
||||
}
|
||||
func (logger *StarLogger) GetWriter() io.Writer {
|
||||
return logger.logcore.output
|
||||
}
|
||||
|
||||
func (logger *StarLogger) SetHandler(f func(LogData)) {
|
||||
if f != nil {
|
||||
StartStacks()
|
||||
}
|
||||
logger.handlerFunc = f
|
||||
}
|
||||
func (logger *StarLogger) GetHandler() func(LogData) {
|
||||
return logger.handlerFunc
|
||||
}
|
||||
func (logger *StarLogger) SetSwitching(sw bool) {
|
||||
logger.logcore.switching = sw
|
||||
}
|
||||
|
||||
func (logger *StarLogger) SetOnlyColorLevel(ocl bool) {
|
||||
logger.logcore.onlyColorLevel = ocl
|
||||
}
|
||||
func (logger *StarLogger) GetOnlyColorLevel() bool {
|
||||
return logger.logcore.onlyColorLevel
|
||||
}
|
||||
|
||||
func (logger *StarLogger) SetShowOriginFile(val bool) {
|
||||
logger.logcore.showDeatilFile = val
|
||||
}
|
||||
|
||||
func (logger *StarLogger) GetShowOriginFile() bool {
|
||||
return logger.logcore.showDeatilFile
|
||||
}
|
||||
|
||||
func (logger *StarLogger) SetShowFuncName(val bool) {
|
||||
logger.logcore.showFuncName = val
|
||||
}
|
||||
|
||||
func (logger *StarLogger) GetShowFuncName() bool {
|
||||
return logger.logcore.showFuncName
|
||||
}
|
||||
|
||||
func (logger *StarLogger) SetShowLevel(val bool) {
|
||||
logger.logcore.showLevel = val
|
||||
}
|
||||
|
||||
func (logger *StarLogger) GetShowLevel() bool {
|
||||
return logger.logcore.showLevel
|
||||
}
|
||||
|
||||
func (logger *StarLogger) SetShowFlag(val bool) {
|
||||
logger.logcore.showThread = val
|
||||
}
|
||||
|
||||
func (logger *StarLogger) GetShowFlag() bool {
|
||||
return logger.logcore.showThread
|
||||
}
|
||||
|
||||
func (logger *StarLogger) SetShowStd(val bool) {
|
||||
logger.logcore.showStd = val
|
||||
}
|
||||
|
||||
func (logger *StarLogger) GetShowStd() bool {
|
||||
return logger.logcore.showStd
|
||||
}
|
||||
|
||||
func (logger *StarLogger) StopWrite() {
|
||||
logger.logcore.stopWriter = true
|
||||
}
|
||||
|
||||
func (logger *StarLogger) EnbaleWrite() {
|
||||
logger.logcore.stopWriter = false
|
||||
}
|
||||
|
||||
func (logger *StarLogger) IsWriteStoed() bool {
|
||||
return logger.logcore.stopWriter
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Debug(str ...interface{}) {
|
||||
logger.logcore.Debug(logger.thread, logger.isStd, logger.handlerFunc, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Debugf(format string, str ...interface{}) {
|
||||
logger.logcore.Debugf(logger.thread, logger.isStd, logger.handlerFunc, format, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Debugln(str ...interface{}) {
|
||||
logger.logcore.Debugln(logger.thread, logger.isStd, logger.handlerFunc, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Info(str ...interface{}) {
|
||||
logger.logcore.Info(logger.thread, logger.isStd, logger.handlerFunc, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Infof(format string, str ...interface{}) {
|
||||
logger.logcore.Infof(logger.thread, logger.isStd, logger.handlerFunc, format, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Infoln(str ...interface{}) {
|
||||
logger.logcore.Infoln(logger.thread, logger.isStd, logger.handlerFunc, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Notice(str ...interface{}) {
|
||||
logger.logcore.Notice(logger.thread, logger.isStd, logger.handlerFunc, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Noticef(format string, str ...interface{}) {
|
||||
logger.logcore.Noticef(logger.thread, logger.isStd, logger.handlerFunc, format, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Noticeln(str ...interface{}) {
|
||||
logger.logcore.Noticeln(logger.thread, logger.isStd, logger.handlerFunc, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Warning(str ...interface{}) {
|
||||
logger.logcore.Warning(logger.thread, logger.isStd, logger.handlerFunc, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Warningf(format string, str ...interface{}) {
|
||||
logger.logcore.Warningf(logger.thread, logger.isStd, logger.handlerFunc, format, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Warningln(str ...interface{}) {
|
||||
logger.logcore.Warningln(logger.thread, logger.isStd, logger.handlerFunc, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Error(str ...interface{}) {
|
||||
logger.logcore.Error(logger.thread, logger.isStd, logger.handlerFunc, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Errorf(format string, str ...interface{}) {
|
||||
logger.logcore.Errorf(logger.thread, logger.isStd, logger.handlerFunc, format, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Errorln(str ...interface{}) {
|
||||
logger.logcore.Errorln(logger.thread, logger.isStd, logger.handlerFunc, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Critical(str ...interface{}) {
|
||||
logger.logcore.Critical(logger.thread, logger.isStd, logger.handlerFunc, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Criticalf(format string, str ...interface{}) {
|
||||
logger.logcore.Criticalf(logger.thread, logger.isStd, logger.handlerFunc, format, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Criticalln(str ...interface{}) {
|
||||
logger.logcore.Criticalln(logger.thread, logger.isStd, logger.handlerFunc, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Fatal(str ...interface{}) {
|
||||
logger.logcore.Fatal(logger.thread, logger.isStd, logger.handlerFunc, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Fatalf(format string, str ...interface{}) {
|
||||
logger.logcore.Fatalf(logger.thread, logger.isStd, logger.handlerFunc, format, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Fatalln(str ...interface{}) {
|
||||
logger.logcore.Fatalln(logger.thread, logger.isStd, logger.handlerFunc, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Print(str ...interface{}) {
|
||||
logger.logcore.Print(logger.thread, logger.isStd, logger.GetShowStd(), logger.handlerFunc, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Printf(format string, str ...interface{}) {
|
||||
logger.logcore.Printf(logger.thread, logger.isStd, logger.GetShowStd(), logger.handlerFunc, format, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Println(str ...interface{}) {
|
||||
logger.logcore.Println(logger.thread, logger.isStd, logger.GetShowStd(), logger.handlerFunc, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Log(showLog bool, level int, str ...interface{}) {
|
||||
logger.logcore.Log(logger.thread, logger.isStd, showLog, level, logger.handlerFunc, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Logf(showLog bool, level int, format string, str ...interface{}) {
|
||||
logger.logcore.Logf(logger.thread, logger.isStd, showLog, level, logger.handlerFunc, format, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Logln(showLog bool, level int, str ...interface{}) {
|
||||
logger.logcore.Logln(logger.thread, logger.isStd, showLog, level, logger.handlerFunc, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Write(str ...interface{}) {
|
||||
logger.logcore.Write(str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Writef(format string, str ...interface{}) {
|
||||
logger.logcore.Writef(format, str...)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) Writeln(str ...interface{}) {
|
||||
logger.logcore.Writeln(str...)
|
||||
}
|
@ -0,0 +1,222 @@
|
||||
package starlog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"b612.me/starlog/colorable"
|
||||
"b612.me/starmap"
|
||||
)
|
||||
|
||||
const (
|
||||
LvDebug = iota
|
||||
LvInfo
|
||||
LvNotice
|
||||
LvWarning
|
||||
LvError
|
||||
LvCritical
|
||||
LvPanic
|
||||
LvFatal
|
||||
)
|
||||
|
||||
var (
|
||||
levels = map[int]string{
|
||||
LvDebug: "DEBUG",
|
||||
LvInfo: "INFO",
|
||||
LvNotice: "NOTICE",
|
||||
LvWarning: "WARNING",
|
||||
LvError: "ERROR",
|
||||
LvCritical: "CRITICAL",
|
||||
LvPanic: "PANIC",
|
||||
LvFatal: "FATAL",
|
||||
}
|
||||
stacks *starmap.StarChanStack
|
||||
stackStarted bool = false
|
||||
stackStopChan chan int
|
||||
stackMu sync.Mutex
|
||||
stdScreen io.Writer = colorable.NewColorableStdout()
|
||||
errScreen io.Writer = colorable.NewColorableStderr()
|
||||
)
|
||||
|
||||
type starlog struct {
|
||||
mu *sync.Mutex
|
||||
output io.Writer
|
||||
errOutputLevel int
|
||||
showFuncName bool
|
||||
showThread bool
|
||||
showLevel bool
|
||||
showDeatilFile bool
|
||||
showColor bool
|
||||
switching bool
|
||||
showStd bool
|
||||
onlyColorLevel bool
|
||||
stopWriter bool
|
||||
id string
|
||||
name string
|
||||
colorList map[int][]Attr
|
||||
colorMe map[int]*Color
|
||||
}
|
||||
|
||||
type StarLogger struct {
|
||||
thread string
|
||||
handlerFunc func(LogData)
|
||||
logcore *starlog
|
||||
isStd bool
|
||||
}
|
||||
|
||||
type logTransfer struct {
|
||||
handlerFunc func(LogData)
|
||||
LogData
|
||||
}
|
||||
|
||||
type LogData struct {
|
||||
Name string
|
||||
Log string
|
||||
Colors []Attr
|
||||
}
|
||||
|
||||
func newLogCore(out io.Writer) *starlog {
|
||||
return &starlog{
|
||||
mu: &sync.Mutex{},
|
||||
output: out,
|
||||
errOutputLevel: LvError,
|
||||
showFuncName: true,
|
||||
showThread: true,
|
||||
showLevel: true,
|
||||
showStd: true,
|
||||
showDeatilFile: true,
|
||||
switching: false,
|
||||
stopWriter: false,
|
||||
showColor: true,
|
||||
id: generateId(),
|
||||
colorList: map[int][]Attr{
|
||||
LvDebug: []Attr{FgWhite},
|
||||
LvInfo: []Attr{FgGreen},
|
||||
LvNotice: []Attr{FgCyan},
|
||||
LvWarning: []Attr{FgYellow},
|
||||
LvError: []Attr{FgMagenta},
|
||||
LvCritical: []Attr{FgRed, Bold},
|
||||
LvPanic: []Attr{FgRed, Bold},
|
||||
LvFatal: []Attr{FgRed},
|
||||
},
|
||||
colorMe: map[int]*Color{
|
||||
LvDebug: NewColor([]Attr{FgWhite}...),
|
||||
LvInfo: NewColor([]Attr{FgGreen}...),
|
||||
LvNotice: NewColor([]Attr{FgCyan}...),
|
||||
LvWarning: NewColor([]Attr{FgYellow}...),
|
||||
LvError: NewColor([]Attr{FgMagenta}...),
|
||||
LvCritical: NewColor([]Attr{FgRed, Bold}...),
|
||||
LvPanic: NewColor([]Attr{FgRed, Bold}...),
|
||||
LvFatal: NewColor([]Attr{FgRed}...),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewStarlog(out io.Writer) *StarLogger {
|
||||
return &StarLogger{
|
||||
handlerFunc: nil,
|
||||
thread: "MAN",
|
||||
logcore: newLogCore(out),
|
||||
isStd: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *StarLogger) StdErrLevel() int {
|
||||
logger.logcore.mu.Lock()
|
||||
defer logger.logcore.mu.Unlock()
|
||||
return logger.logcore.errOutputLevel
|
||||
}
|
||||
|
||||
func (logger *StarLogger) SetStdErrLevel(level int) {
|
||||
logger.logcore.mu.Lock()
|
||||
defer logger.logcore.mu.Unlock()
|
||||
logger.logcore.errOutputLevel = level
|
||||
}
|
||||
|
||||
func (logger *StarLogger) NewFlag() *StarLogger {
|
||||
return &StarLogger{
|
||||
thread: getRandomFlag(false),
|
||||
handlerFunc: logger.handlerFunc,
|
||||
logcore: logger.logcore,
|
||||
isStd: false,
|
||||
}
|
||||
}
|
||||
func (logger *StarLogger) SetNewRandomFlag() {
|
||||
logger.thread = getRandomFlag(false)
|
||||
}
|
||||
|
||||
func (logger *StarLogger) SetName(name string) {
|
||||
logger.logcore.mu.Lock()
|
||||
defer logger.logcore.mu.Unlock()
|
||||
logger.logcore.name = name
|
||||
}
|
||||
|
||||
func (logger *StarLogger) GetName() string {
|
||||
return logger.logcore.name
|
||||
}
|
||||
|
||||
func getRandomFlag(isMain bool) string {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
if isMain {
|
||||
return "MAN"
|
||||
}
|
||||
flag := "MAN"
|
||||
for flag == "MAN" {
|
||||
flag = string([]byte{uint8(rand.Intn(26) + 65), uint8(rand.Intn(26) + 65), uint8(rand.Intn(26) + 65)})
|
||||
}
|
||||
return flag
|
||||
}
|
||||
|
||||
func generateId() string {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
return fmt.Sprintf("%dstar%db612%d", time.Now().UnixNano(), rand.Intn(1000000), rand.Intn(1000000))
|
||||
}
|
||||
|
||||
func StartStacks() {
|
||||
stackMu.Lock()
|
||||
if stackStarted {
|
||||
stackMu.Unlock()
|
||||
return
|
||||
}
|
||||
unlock := make(chan struct{})
|
||||
go func() {
|
||||
stackStarted = true
|
||||
stacks = starmap.NewStarChanStack(1024)
|
||||
stackMu.Unlock()
|
||||
unlock <- struct{}{}
|
||||
defer func() {
|
||||
stackStarted = false
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case <-stackStopChan:
|
||||
return
|
||||
default:
|
||||
}
|
||||
poped, err := stacks.Pop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
val := poped.(logTransfer)
|
||||
if val.handlerFunc != nil {
|
||||
val.handlerFunc(val.LogData)
|
||||
}
|
||||
}
|
||||
}()
|
||||
<-unlock
|
||||
}
|
||||
|
||||
func StopStacks() {
|
||||
if !stackStarted {
|
||||
return
|
||||
}
|
||||
stackStopChan <- 1
|
||||
}
|
||||
|
||||
func Stop() {
|
||||
stacks.Close()
|
||||
StopStacks()
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package starmap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
)
|
||||
|
||||
func (stack *StarStackMem) Count() int {
|
||||
stack.kvPushmu.Lock()
|
||||
defer stack.kvPushmu.Unlock()
|
||||
return len(stack.kvStack)
|
||||
}
|
||||
|
||||
func (stack *StarStackMem) Push(val interface{}) error {
|
||||
stack.kvPushmu.Lock()
|
||||
defer stack.kvPushmu.Unlock()
|
||||
stack.kvStack = append(stack.kvStack, val)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (stack *StarStackMem) Pop() (interface{}, error) {
|
||||
stack.kvPushmu.Lock()
|
||||
defer stack.kvPushmu.Unlock()
|
||||
if len(stack.kvStack) == 0 {
|
||||
return nil, errors.New("Empty Stacks")
|
||||
}
|
||||
val := stack.kvStack[0]
|
||||
stack.kvStack = stack.kvStack[1:]
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (stack *StarStackMem) MustPop() interface{} {
|
||||
val, _ := stack.Pop()
|
||||
return val
|
||||
}
|
||||
|
||||
func Get(key interface{}) (interface{}, error) {
|
||||
return globalMap.Get(key)
|
||||
}
|
||||
|
||||
func (m *StarMapKV) Get(key interface{}) (interface{}, error) {
|
||||
var err error
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
data, ok := m.kvMap[key]
|
||||
if !ok {
|
||||
err = os.ErrNotExist
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (m *StarMapKV) MustGet(key interface{}) interface{} {
|
||||
result, _ := m.Get(key)
|
||||
return result
|
||||
}
|
||||
func MustGet(key interface{}) interface{} {
|
||||
return globalMap.MustGet(key)
|
||||
}
|
||||
|
||||
func Store(key interface{}, value interface{}) error {
|
||||
return globalMap.Store(key, value)
|
||||
}
|
||||
func (m *StarMapKV) Store(key interface{}, value interface{}) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.kvMap[key] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func Exists(key interface{}) bool {
|
||||
return globalMap.Exists(key)
|
||||
}
|
||||
|
||||
func (m *StarMapKV) Exists(key interface{}) bool {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
_, ok := m.kvMap[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
func Delete(key interface{}) error {
|
||||
return globalMap.Delete(key)
|
||||
}
|
||||
func (m *StarMapKV) Delete(key interface{}) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
delete(m.kvMap, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
func Range(run func(k interface{}, v interface{}) bool) error {
|
||||
return globalMap.Range(run)
|
||||
}
|
||||
|
||||
func (m *StarMapKV) Range(run func(k interface{}, v interface{}) bool) error {
|
||||
for k, v := range m.kvMap {
|
||||
if !run(k, v) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package starmap
|
||||
|
||||
import (
|
||||
"b612.me/notify"
|
||||
)
|
||||
|
||||
func (kv *RemoteKv) clientOnline() error {
|
||||
return kv.reconnect()
|
||||
}
|
||||
|
||||
func (kv *RemoteKv) MustGet(key string) interface{} {
|
||||
result, _ := kv.Get(key)
|
||||
return result
|
||||
}
|
||||
|
||||
func (kv *RemoteKv) Get(key interface{}) (interface{}, error) {
|
||||
if err := kv.clientOnline(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyCode, err := notify.ToMsgVal(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := kv.client.SendWait("get", keyCode, kv.readTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rp, err := data.Value.ToInterface()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reply := rp.(kvMsg)
|
||||
return reply.Value, reply.Err
|
||||
}
|
||||
|
||||
func (kv *RemoteKv) Store(key interface{}, value interface{}) error {
|
||||
if err := kv.clientOnline(); err != nil {
|
||||
return err
|
||||
}
|
||||
encodeData, err := notify.ToMsgVal(kvMsg{
|
||||
Key: key,
|
||||
Value: value,
|
||||
Err: nil,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := kv.client.SendWait("store", encodeData, kv.readTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rp, err := data.Value.ToInterface()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return rp.(kvMsg).Err
|
||||
}
|
||||
|
||||
func (kv *RemoteKv) Delete(key interface{}) error {
|
||||
if err := kv.clientOnline(); err != nil {
|
||||
return err
|
||||
}
|
||||
keyCode, err := notify.ToMsgVal(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := kv.client.SendWait("delete", keyCode, kv.readTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rp, err := data.Value.ToInterface()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return rp.(kvMsg).Err
|
||||
}
|
||||
|
||||
func (kv *RemoteKv) Exists(key interface{}) bool {
|
||||
if err := kv.clientOnline(); err != nil {
|
||||
return false
|
||||
}
|
||||
keyCode, err := notify.ToMsgVal(key)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
data, err := kv.client.SendWait("exists", keyCode, kv.readTimeout)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
rp, err := data.Value.ToInterface()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
reply := rp.(kvMsg)
|
||||
return reply.Value.(bool)
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package starmap
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"b612.me/notify"
|
||||
)
|
||||
|
||||
func init() {
|
||||
notify.RegisterName("b612.me/starmap/kvmsg", kvMsg{})
|
||||
notify.RegisterName("b612.me/starmap/error", starMapErr{})
|
||||
}
|
||||
|
||||
type starMapErr struct {
|
||||
Err string
|
||||
}
|
||||
|
||||
func (s starMapErr) Error() string {
|
||||
return s.Err
|
||||
}
|
||||
func newStarMapErr(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return starMapErr{Err: err.Error()}
|
||||
}
|
||||
|
||||
type kvMsg struct {
|
||||
Key interface{}
|
||||
Value interface{}
|
||||
Err error
|
||||
}
|
||||
|
||||
type RemoteKv struct {
|
||||
server notify.Server
|
||||
client notify.Client
|
||||
kvmap StarMapKV
|
||||
addr string
|
||||
network string
|
||||
readTimeout time.Duration
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func NewServer(network, addr string) (*RemoteKv, error) {
|
||||
var err error
|
||||
kv := RemoteKv{
|
||||
server: notify.NewServer(),
|
||||
kvmap: NewStarMap(),
|
||||
addr: addr,
|
||||
network: network,
|
||||
}
|
||||
err = kv.server.Listen(network, addr)
|
||||
if err == nil {
|
||||
kv.bind()
|
||||
}
|
||||
return &kv, err
|
||||
}
|
||||
|
||||
func NewClient(network, addr string, dialTimeout time.Duration) (*RemoteKv, error) {
|
||||
var err error
|
||||
kv := RemoteKv{
|
||||
client: notify.NewClient(),
|
||||
kvmap: NewStarMap(),
|
||||
addr: addr,
|
||||
network: network,
|
||||
timeout: dialTimeout,
|
||||
readTimeout: time.Second * 5,
|
||||
}
|
||||
err = kv.client.ConnectTimeout(network, addr, dialTimeout)
|
||||
return &kv, err
|
||||
}
|
||||
|
||||
func (kv *RemoteKv) Register(data interface{}) {
|
||||
gob.Register(data)
|
||||
}
|
||||
|
||||
func (kv *RemoteKv) RegisterAll(data []interface{}) {
|
||||
for _, v := range data {
|
||||
gob.Register(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (kv *RemoteKv) bind() {
|
||||
//for server
|
||||
kv.server.SetDefaultLink(kv.dispatch)
|
||||
//for client
|
||||
}
|
||||
|
||||
func (kv *RemoteKv) reconnect() error {
|
||||
if kv.server != nil {
|
||||
return nil
|
||||
}
|
||||
if kv.client != nil {
|
||||
if kv.client.Status().Alive {
|
||||
return nil
|
||||
}
|
||||
return kv.client.ConnectTimeout(kv.network, kv.addr, kv.timeout)
|
||||
}
|
||||
return os.ErrInvalid
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package starmap
|
||||
|
||||
import (
|
||||
"b612.me/notify"
|
||||
)
|
||||
|
||||
func (r *RemoteKv) dispatch(msg *notify.Message) {
|
||||
switch msg.Key {
|
||||
case "get":
|
||||
data, err := r.kvmap.Get(msg.Value.MustToInterface())
|
||||
msg.ReplyObj(kvMsg{
|
||||
Key: msg.Value.MustToInterface(),
|
||||
Value: data,
|
||||
Err: newStarMapErr(err),
|
||||
})
|
||||
case "delete":
|
||||
err := r.kvmap.Delete(msg.Value.MustToInterface())
|
||||
msg.ReplyObj(kvMsg{
|
||||
Key: msg.Value.MustToInterface(),
|
||||
Value: nil,
|
||||
Err: newStarMapErr(err),
|
||||
})
|
||||
case "exists":
|
||||
ext := r.kvmap.Exists(msg.Value.MustToInterface())
|
||||
msg.ReplyObj(kvMsg{
|
||||
Key: msg.Value.MustToInterface(),
|
||||
Value: ext,
|
||||
Err: newStarMapErr(nil),
|
||||
})
|
||||
case "store":
|
||||
ext := msg.Value.MustToInterface().(kvMsg)
|
||||
err := r.kvmap.Store(ext.Key, ext.Value)
|
||||
msg.ReplyObj(kvMsg{
|
||||
Key: msg.Value.MustToInterface(),
|
||||
Value: nil,
|
||||
Err: newStarMapErr(err),
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,282 @@
|
||||
package starmap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type StarStack struct {
|
||||
datas []interface{}
|
||||
pStart uint64
|
||||
pEnd uint64
|
||||
cap uint64
|
||||
isClose atomic.Value
|
||||
rmu sync.Mutex
|
||||
wmu sync.Mutex
|
||||
}
|
||||
|
||||
func NewStarStack(cap uint64) *StarStack {
|
||||
rtnBuffer := new(StarStack)
|
||||
rtnBuffer.cap = cap
|
||||
rtnBuffer.datas = make([]interface{}, cap)
|
||||
rtnBuffer.isClose.Store(false)
|
||||
return rtnBuffer
|
||||
}
|
||||
|
||||
func (star *StarStack) init() {
|
||||
star.cap = 1024
|
||||
star.datas = make([]interface{}, star.cap)
|
||||
star.isClose.Store(false)
|
||||
}
|
||||
|
||||
func (star *StarStack) Free() uint64 {
|
||||
return star.cap - star.Len()
|
||||
}
|
||||
|
||||
func (star *StarStack) Cap() uint64 {
|
||||
return star.cap
|
||||
}
|
||||
|
||||
func (star *StarStack) Len() uint64 {
|
||||
if star.pEnd >= star.pStart {
|
||||
return star.pEnd - star.pStart
|
||||
}
|
||||
return star.pEnd - star.pStart + star.cap
|
||||
}
|
||||
|
||||
func (star *StarStack) PopNoWait() (interface{}, error) {
|
||||
if star.isClose.Load() == nil {
|
||||
star.init()
|
||||
}
|
||||
if star.isClose.Load().(bool) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if star.Len() == 0 {
|
||||
return 0, os.ErrNotExist
|
||||
}
|
||||
nowPtr := star.pStart
|
||||
nextPtr := star.pStart + 1
|
||||
if nextPtr >= star.cap {
|
||||
nextPtr = 0
|
||||
}
|
||||
data := star.datas[nowPtr]
|
||||
ok := atomic.CompareAndSwapUint64(&star.pStart, nowPtr, nextPtr)
|
||||
if !ok {
|
||||
return 0, os.ErrInvalid
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (star *StarStack) MustPop() interface{} {
|
||||
if star.isClose.Load() == nil {
|
||||
star.init()
|
||||
}
|
||||
data, err := star.Pop()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (star *StarStack) Pop() (interface{}, error) {
|
||||
if star.isClose.Load() == nil {
|
||||
star.init()
|
||||
}
|
||||
for {
|
||||
if star.isClose.Load().(bool) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if star.Len() == 0 {
|
||||
return 0, os.ErrNotExist
|
||||
}
|
||||
nowPtr := star.pStart
|
||||
nextPtr := star.pStart + 1
|
||||
if nextPtr >= star.cap {
|
||||
nextPtr = 0
|
||||
}
|
||||
data := star.datas[nowPtr]
|
||||
ok := atomic.CompareAndSwapUint64(&star.pStart, nowPtr, nextPtr)
|
||||
if !ok {
|
||||
time.Sleep(time.Microsecond)
|
||||
runtime.Gosched()
|
||||
continue
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (star *StarStack) Push(data interface{}) error {
|
||||
if star.isClose.Load() == nil {
|
||||
star.init()
|
||||
}
|
||||
if star.isClose.Load().(bool) {
|
||||
return io.EOF
|
||||
}
|
||||
nowPtr := star.pEnd
|
||||
kariEnd := nowPtr + 1
|
||||
if kariEnd == star.cap {
|
||||
kariEnd = 0
|
||||
}
|
||||
if kariEnd == atomic.LoadUint64(&star.pStart) {
|
||||
for {
|
||||
time.Sleep(time.Microsecond)
|
||||
runtime.Gosched()
|
||||
if kariEnd != atomic.LoadUint64(&star.pStart) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
star.datas[nowPtr] = data
|
||||
if ok := atomic.CompareAndSwapUint64(&star.pEnd, nowPtr, kariEnd); !ok {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (star *StarStack) Close() error {
|
||||
if star.isClose.Load() == nil {
|
||||
star.init()
|
||||
}
|
||||
star.isClose.Store(true)
|
||||
return nil
|
||||
}
|
||||
func (star *StarStack) Read(buf []interface{}) (int, error) {
|
||||
if star.isClose.Load() == nil {
|
||||
star.init()
|
||||
}
|
||||
if star.isClose.Load().(bool) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if buf == nil {
|
||||
return 0, errors.New("buffer is nil")
|
||||
}
|
||||
star.rmu.Lock()
|
||||
defer star.rmu.Unlock()
|
||||
var sum int = 0
|
||||
for i := 0; i < len(buf); i++ {
|
||||
data, err := star.PopNoWait()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return sum, err
|
||||
}
|
||||
if err == os.ErrNotExist {
|
||||
i--
|
||||
continue
|
||||
}
|
||||
return sum, nil
|
||||
}
|
||||
buf[i] = data
|
||||
sum++
|
||||
}
|
||||
return sum, nil
|
||||
}
|
||||
|
||||
func (star *StarStack) Write(bts []byte) (int, error) {
|
||||
if star.isClose.Load() == nil {
|
||||
star.init()
|
||||
}
|
||||
if bts == nil || star.isClose.Load().(bool) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
star.wmu.Lock()
|
||||
defer star.wmu.Unlock()
|
||||
var sum = 0
|
||||
for i := 0; i < len(bts); i++ {
|
||||
err := star.Push(bts[i])
|
||||
if err != nil {
|
||||
fmt.Println("Write bts err:", err)
|
||||
return sum, err
|
||||
}
|
||||
sum++
|
||||
}
|
||||
return sum, nil
|
||||
}
|
||||
|
||||
type StarChanStack struct {
|
||||
data chan interface{}
|
||||
cap uint64
|
||||
current uint64
|
||||
isClose atomic.Value
|
||||
}
|
||||
|
||||
func NewStarChanStack(cap uint64) *StarChanStack {
|
||||
rtnBuffer := new(StarChanStack)
|
||||
rtnBuffer.cap = cap
|
||||
rtnBuffer.isClose.Store(false)
|
||||
rtnBuffer.data = make(chan interface{}, cap)
|
||||
return rtnBuffer
|
||||
}
|
||||
|
||||
func (star *StarChanStack) init() {
|
||||
star.cap = 1024
|
||||
star.data = make(chan interface{}, star.cap)
|
||||
star.isClose.Store(false)
|
||||
}
|
||||
|
||||
func (star *StarChanStack) Free() uint64 {
|
||||
return star.cap - star.current
|
||||
}
|
||||
|
||||
func (star *StarChanStack) Cap() uint64 {
|
||||
return star.cap
|
||||
}
|
||||
|
||||
func (star *StarChanStack) Len() uint64 {
|
||||
return star.current
|
||||
}
|
||||
|
||||
func (star *StarChanStack) Pop() (interface{}, error) {
|
||||
if star.isClose.Load() == nil {
|
||||
star.init()
|
||||
}
|
||||
if star.isClose.Load().(bool) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
data, ok := <-star.data
|
||||
if !ok {
|
||||
star.isClose.Store(true)
|
||||
return 0, errors.New("channel read error")
|
||||
}
|
||||
for {
|
||||
current := atomic.LoadUint64(&star.current)
|
||||
if atomic.CompareAndSwapUint64(&star.current, current, current-1) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (star *StarChanStack) Push(data interface{}) error {
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
if star.isClose.Load() == nil {
|
||||
star.init()
|
||||
}
|
||||
if star.isClose.Load().(bool) {
|
||||
return io.EOF
|
||||
}
|
||||
star.data <- data
|
||||
for {
|
||||
current := atomic.LoadUint64(&star.current)
|
||||
if atomic.CompareAndSwapUint64(&star.current, current, current+1) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (star *StarChanStack) Close() error {
|
||||
if star.isClose.Load() == nil {
|
||||
star.init()
|
||||
}
|
||||
star.isClose.Store(true)
|
||||
close(star.data)
|
||||
return nil
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package starmap
|
||||
|
||||
import "sync"
|
||||
|
||||
var globalMap StarMapKV
|
||||
|
||||
type StarMapKV struct {
|
||||
kvMap map[interface{}]interface{}
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
type StarStackMem struct {
|
||||
kvPushmu sync.RWMutex
|
||||
kvStack []interface{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
globalMap = NewStarMap()
|
||||
}
|
||||
|
||||
func NewStarMap() StarMapKV {
|
||||
var mp StarMapKV
|
||||
mp.kvMap = make(map[interface{}]interface{})
|
||||
return mp
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package starmap
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type WaitGroup struct {
|
||||
wg *sync.WaitGroup
|
||||
maxCount uint32
|
||||
allCount uint32
|
||||
}
|
||||
|
||||
func NewWaitGroup(maxCount int) WaitGroup {
|
||||
return WaitGroup{wg: &sync.WaitGroup{}, maxCount: uint32(maxCount)}
|
||||
}
|
||||
|
||||
func (swg *WaitGroup) Add(delta int) {
|
||||
var Udelta uint32
|
||||
if delta < 0 {
|
||||
Udelta = uint32(-delta - 1)
|
||||
} else {
|
||||
Udelta = uint32(delta)
|
||||
}
|
||||
for {
|
||||
allC := atomic.LoadUint32(&swg.allCount)
|
||||
if atomic.LoadUint32(&swg.maxCount) == 0 || atomic.LoadUint32(&swg.maxCount) >= allC+uint32(delta) {
|
||||
if delta < 0 {
|
||||
atomic.AddUint32(&swg.allCount, ^uint32(Udelta))
|
||||
} else {
|
||||
atomic.AddUint32(&swg.allCount, uint32(Udelta))
|
||||
}
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Microsecond)
|
||||
}
|
||||
swg.wg.Add(delta)
|
||||
}
|
||||
|
||||
func (swg *WaitGroup) Done() {
|
||||
swg.Add(-1)
|
||||
}
|
||||
|
||||
func (swg *WaitGroup) Wait() {
|
||||
swg.wg.Wait()
|
||||
}
|
||||
|
||||
func (swg *WaitGroup) GetMaxWaitNum() int {
|
||||
return int(atomic.LoadUint32(&swg.maxCount))
|
||||
}
|
||||
|
||||
func (swg *WaitGroup) SetMaxWaitNum(num int) {
|
||||
atomic.AddUint32(&swg.maxCount, uint32(num))
|
||||
}
|
@ -0,0 +1,463 @@
|
||||
package starnet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"b612.me/stario"
|
||||
)
|
||||
|
||||
const (
|
||||
HEADER_FORM_URLENCODE = `application/x-www-form-urlencoded`
|
||||
HEADER_FORM_DATA = `multipart/form-data`
|
||||
HEADER_JSON = `application/json`
|
||||
HEADER_PLAIN = `text/plain`
|
||||
)
|
||||
|
||||
type RequestFile struct {
|
||||
UploadFile string
|
||||
UploadForm map[string]string
|
||||
UploadName string
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
Url string
|
||||
RespURL string
|
||||
Method string
|
||||
RecvData []byte
|
||||
RecvContentLength int64
|
||||
RecvIo io.Writer
|
||||
RespHeader http.Header
|
||||
RespCookies []*http.Cookie
|
||||
RespHttpCode int
|
||||
Location *url.URL
|
||||
CircleBuffer *stario.StarBuffer
|
||||
respReader io.ReadCloser
|
||||
respOrigin *http.Response
|
||||
reqOrigin *http.Request
|
||||
RequestOpts
|
||||
}
|
||||
|
||||
type RequestOpts struct {
|
||||
RequestFile
|
||||
PostBuffer io.Reader
|
||||
Process func(float64)
|
||||
Proxy string
|
||||
Timeout time.Duration
|
||||
DialTimeout time.Duration
|
||||
ReqHeader http.Header
|
||||
ReqCookies []*http.Cookie
|
||||
WriteRecvData bool
|
||||
SkipTLSVerify bool
|
||||
CustomTransport *http.Transport
|
||||
Queries map[string]string
|
||||
DisableRedirect bool
|
||||
TlsConfig *tls.Config
|
||||
}
|
||||
|
||||
type RequestOpt func(opt *RequestOpts)
|
||||
|
||||
func WithDialTimeout(timeout time.Duration) RequestOpt {
|
||||
return func(opt *RequestOpts) {
|
||||
opt.DialTimeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
func WithTimeout(timeout time.Duration) RequestOpt {
|
||||
return func(opt *RequestOpts) {
|
||||
opt.Timeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
func WithHeader(key, val string) RequestOpt {
|
||||
return func(opt *RequestOpts) {
|
||||
opt.ReqHeader.Set(key, val)
|
||||
}
|
||||
}
|
||||
|
||||
func WithTlsConfig(tlscfg *tls.Config) RequestOpt {
|
||||
return func(opt *RequestOpts) {
|
||||
opt.TlsConfig = tlscfg
|
||||
}
|
||||
}
|
||||
|
||||
func WithHeaderMap(header map[string]string) RequestOpt {
|
||||
return func(opt *RequestOpts) {
|
||||
for key, val := range header {
|
||||
opt.ReqHeader.Set(key, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithHeaderAdd(key, val string) RequestOpt {
|
||||
return func(opt *RequestOpts) {
|
||||
opt.ReqHeader.Add(key, val)
|
||||
}
|
||||
}
|
||||
|
||||
func WithReader(r io.Reader) RequestOpt {
|
||||
return func(opt *RequestOpts) {
|
||||
opt.PostBuffer = r
|
||||
}
|
||||
}
|
||||
|
||||
func WithFetchRespBody(fetch bool) RequestOpt {
|
||||
return func(opt *RequestOpts) {
|
||||
opt.WriteRecvData = fetch
|
||||
}
|
||||
}
|
||||
|
||||
func WithCookies(ck []*http.Cookie) RequestOpt {
|
||||
return func(opt *RequestOpts) {
|
||||
opt.ReqCookies = ck
|
||||
}
|
||||
}
|
||||
|
||||
func WithCookie(key, val, path string) RequestOpt {
|
||||
return func(opt *RequestOpts) {
|
||||
opt.ReqCookies = append(opt.ReqCookies, &http.Cookie{Name: key, Value: val, Path: path})
|
||||
}
|
||||
}
|
||||
|
||||
func WithCookieMap(header map[string]string, path string) RequestOpt {
|
||||
return func(opt *RequestOpts) {
|
||||
for key, val := range header {
|
||||
opt.ReqCookies = append(opt.ReqCookies, &http.Cookie{Name: key, Value: val, Path: path})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithQueries(queries map[string]string) RequestOpt {
|
||||
return func(opt *RequestOpts) {
|
||||
opt.Queries = queries
|
||||
}
|
||||
}
|
||||
|
||||
func WithProxy(proxy string) RequestOpt {
|
||||
return func(opt *RequestOpts) {
|
||||
opt.Proxy = proxy
|
||||
}
|
||||
}
|
||||
|
||||
func WithProcess(fn func(float64)) RequestOpt {
|
||||
return func(opt *RequestOpts) {
|
||||
opt.Process = fn
|
||||
}
|
||||
}
|
||||
|
||||
func WithContentType(ct string) RequestOpt {
|
||||
return func(opt *RequestOpts) {
|
||||
opt.ReqHeader.Set("Content-Type", ct)
|
||||
}
|
||||
}
|
||||
|
||||
func WithUserAgent(ua string) RequestOpt {
|
||||
return func(opt *RequestOpts) {
|
||||
opt.ReqHeader.Set("User-Agent", ua)
|
||||
}
|
||||
}
|
||||
|
||||
func WithCustomTransport(hs *http.Transport) RequestOpt {
|
||||
return func(opt *RequestOpts) {
|
||||
opt.CustomTransport = hs
|
||||
}
|
||||
}
|
||||
|
||||
func WithSkipTLSVerify(skip bool) RequestOpt {
|
||||
return func(opt *RequestOpts) {
|
||||
opt.SkipTLSVerify = skip
|
||||
}
|
||||
}
|
||||
|
||||
func WithDisableRedirect(disable bool) RequestOpt {
|
||||
return func(opt *RequestOpts) {
|
||||
opt.DisableRedirect = disable
|
||||
}
|
||||
}
|
||||
|
||||
func NewRequests(url string, rawdata []byte, method string, opts ...RequestOpt) Request {
|
||||
req := Request{
|
||||
RequestOpts: RequestOpts{
|
||||
Timeout: 30 * time.Second,
|
||||
DialTimeout: 15 * time.Second,
|
||||
WriteRecvData: true,
|
||||
},
|
||||
Url: url,
|
||||
Method: method,
|
||||
}
|
||||
if rawdata != nil {
|
||||
req.PostBuffer = bytes.NewBuffer(rawdata)
|
||||
}
|
||||
req.ReqHeader = make(http.Header)
|
||||
if strings.ToUpper(method) == "POST" {
|
||||
req.ReqHeader.Set("Content-Type", HEADER_FORM_URLENCODE)
|
||||
}
|
||||
req.ReqHeader.Set("User-Agent", "B612 / 1.1.0")
|
||||
for _, v := range opts {
|
||||
v(&req.RequestOpts)
|
||||
}
|
||||
if req.CustomTransport == nil {
|
||||
req.CustomTransport = &http.Transport{}
|
||||
}
|
||||
if req.SkipTLSVerify {
|
||||
if req.CustomTransport.TLSClientConfig == nil {
|
||||
req.CustomTransport.TLSClientConfig = &tls.Config{}
|
||||
}
|
||||
req.CustomTransport.TLSClientConfig.InsecureSkipVerify = true
|
||||
}
|
||||
if req.TlsConfig != nil {
|
||||
req.CustomTransport.TLSClientConfig = req.TlsConfig
|
||||
}
|
||||
req.CustomTransport.DialContext = func(ctx context.Context, netw, addr string) (net.Conn, error) {
|
||||
c, err := net.DialTimeout(netw, addr, req.DialTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.Timeout != 0 {
|
||||
c.SetDeadline(time.Now().Add(req.Timeout))
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
return req
|
||||
}
|
||||
|
||||
func (curl *Request) ResetReqHeader() {
|
||||
curl.ReqHeader = make(http.Header)
|
||||
}
|
||||
|
||||
func (curl *Request) ResetReqCookies() {
|
||||
curl.ReqCookies = []*http.Cookie{}
|
||||
}
|
||||
|
||||
func (curl *Request) AddSimpleCookie(key, value string) {
|
||||
curl.ReqCookies = append(curl.ReqCookies, &http.Cookie{Name: key, Value: value, Path: "/"})
|
||||
}
|
||||
func (curl *Request) AddCookie(key, value, path string) {
|
||||
curl.ReqCookies = append(curl.ReqCookies, &http.Cookie{Name: key, Value: value, Path: path})
|
||||
}
|
||||
|
||||
func randomBoundary() string {
|
||||
var buf [30]byte
|
||||
_, err := io.ReadFull(rand.Reader, buf[:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return fmt.Sprintf("%x", buf[:])
|
||||
}
|
||||
|
||||
func Curl(curl Request) (resps Request, err error) {
|
||||
var fpsrc *os.File
|
||||
if curl.RequestFile.UploadFile != "" {
|
||||
fpsrc, err = os.Open(curl.UploadFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer fpsrc.Close()
|
||||
boundary := randomBoundary()
|
||||
boundarybytes := []byte("\r\n--" + boundary + "\r\n")
|
||||
endbytes := []byte("\r\n--" + boundary + "--\r\n")
|
||||
fpstat, _ := fpsrc.Stat()
|
||||
filebig := float64(fpstat.Size())
|
||||
sum, n := 0, 0
|
||||
fpdst := stario.NewStarBuffer(1048576)
|
||||
if curl.UploadForm != nil {
|
||||
for k, v := range curl.UploadForm {
|
||||
header := fmt.Sprintf("Content-Disposition: form-data; name=\"%s\";\r\nContent-Type: x-www-form-urlencoded \r\n\r\n", k)
|
||||
fpdst.Write(boundarybytes)
|
||||
fpdst.Write([]byte(header))
|
||||
fpdst.Write([]byte(v))
|
||||
}
|
||||
}
|
||||
header := fmt.Sprintf("Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\nContent-Type: application/octet-stream\r\n\r\n", curl.UploadName, fpstat.Name())
|
||||
fpdst.Write(boundarybytes)
|
||||
fpdst.Write([]byte(header))
|
||||
go func() {
|
||||
for {
|
||||
bufs := make([]byte, 393213)
|
||||
n, err = fpsrc.Read(bufs)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
if n != 0 {
|
||||
fpdst.Write(bufs[0:n])
|
||||
if curl.Process != nil {
|
||||
go curl.Process(float64(sum+n) / filebig * 100)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
sum += n
|
||||
if curl.Process != nil {
|
||||
go curl.Process(float64(sum+n) / filebig * 100)
|
||||
}
|
||||
fpdst.Write(bufs[0:n])
|
||||
}
|
||||
fpdst.Write(endbytes)
|
||||
fpdst.Write(nil)
|
||||
}()
|
||||
curl.CircleBuffer = fpdst
|
||||
curl.ReqHeader.Set("Content-Type", "multipart/form-data;boundary="+boundary)
|
||||
}
|
||||
req, resp, err := netcurl(curl)
|
||||
if err != nil {
|
||||
return Request{}, err
|
||||
}
|
||||
if resp.Request != nil && resp.Request.URL != nil {
|
||||
curl.RespURL = resp.Request.URL.String()
|
||||
}
|
||||
curl.reqOrigin = req
|
||||
curl.respOrigin = resp
|
||||
curl.Location, _ = resp.Location()
|
||||
curl.RespHttpCode = resp.StatusCode
|
||||
curl.RespHeader = resp.Header
|
||||
curl.RespCookies = resp.Cookies()
|
||||
curl.RecvContentLength = resp.ContentLength
|
||||
readFunc := func(reader io.ReadCloser, writer io.Writer) error {
|
||||
lengthall := resp.ContentLength
|
||||
defer reader.Close()
|
||||
var lengthsum int
|
||||
buf := make([]byte, 65535)
|
||||
for {
|
||||
n, err := reader.Read(buf)
|
||||
if n != 0 {
|
||||
_, err := writer.Write(buf[:n])
|
||||
lengthsum += n
|
||||
if curl.Process != nil {
|
||||
go curl.Process(float64(lengthsum) / float64(lengthall) * 100.00)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
} else if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if curl.WriteRecvData {
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
err = readFunc(resp.Body, buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
curl.RecvData = buf.Bytes()
|
||||
} else {
|
||||
curl.respReader = resp.Body
|
||||
}
|
||||
if curl.RecvIo != nil {
|
||||
if curl.WriteRecvData {
|
||||
_, err = curl.RecvIo.Write(curl.RecvData)
|
||||
} else {
|
||||
err = readFunc(resp.Body, curl.RecvIo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return curl, err
|
||||
}
|
||||
|
||||
// RespBodyReader Only works when WriteRecvData set to false
|
||||
func (curl *Request) RespBodyReader() io.ReadCloser {
|
||||
return curl.respReader
|
||||
}
|
||||
|
||||
func netcurl(curl Request) (*http.Request, *http.Response, error) {
|
||||
var req *http.Request
|
||||
var err error
|
||||
if curl.Method == "" {
|
||||
return nil, nil, errors.New("Error Method Not Entered")
|
||||
}
|
||||
if curl.PostBuffer != nil {
|
||||
req, err = http.NewRequest(curl.Method, curl.Url, curl.PostBuffer)
|
||||
} else if curl.CircleBuffer != nil && curl.CircleBuffer.Len() > 0 {
|
||||
req, err = http.NewRequest(curl.Method, curl.Url, curl.CircleBuffer)
|
||||
} else {
|
||||
req, err = http.NewRequest(curl.Method, curl.Url, nil)
|
||||
}
|
||||
if curl.Queries != nil {
|
||||
sid := req.URL.Query()
|
||||
for k, v := range curl.Queries {
|
||||
sid.Add(k, v)
|
||||
}
|
||||
req.URL.RawQuery = sid.Encode()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
req.Header = curl.ReqHeader
|
||||
if len(curl.ReqCookies) != 0 {
|
||||
for _, v := range curl.ReqCookies {
|
||||
req.AddCookie(v)
|
||||
}
|
||||
}
|
||||
if curl.Proxy != "" {
|
||||
purl, err := url.Parse(curl.Proxy)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
curl.CustomTransport.Proxy = http.ProxyURL(purl)
|
||||
}
|
||||
client := &http.Client{
|
||||
Transport: curl.CustomTransport,
|
||||
}
|
||||
if curl.DisableRedirect {
|
||||
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
|
||||
return req, resp, err
|
||||
}
|
||||
|
||||
func UrlEncodeRaw(str string) string {
|
||||
strs := strings.Replace(url.QueryEscape(str), "+", "%20", -1)
|
||||
return strs
|
||||
}
|
||||
|
||||
func UrlEncode(str string) string {
|
||||
return url.QueryEscape(str)
|
||||
}
|
||||
|
||||
func UrlDecode(str string) (string, error) {
|
||||
return url.QueryUnescape(str)
|
||||
}
|
||||
|
||||
func BuildQuery(queryData map[string]string) string {
|
||||
query := url.Values{}
|
||||
for k, v := range queryData {
|
||||
query.Add(k, v)
|
||||
}
|
||||
return query.Encode()
|
||||
}
|
||||
|
||||
func BuildPostForm(queryMap map[string]string) []byte {
|
||||
query := url.Values{}
|
||||
for k, v := range queryMap {
|
||||
query.Add(k, v)
|
||||
}
|
||||
return []byte(query.Encode())
|
||||
}
|
||||
|
||||
func (r Request) Resopnse() *http.Response {
|
||||
return r.respOrigin
|
||||
}
|
||||
|
||||
func (r Request) Request() *http.Request {
|
||||
return r.reqOrigin
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
package starnet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ICMP struct {
|
||||
Type uint8
|
||||
Code uint8
|
||||
CheckSum uint16
|
||||
Identifier uint16
|
||||
SequenceNum uint16
|
||||
}
|
||||
|
||||
func getICMP(seq uint16) ICMP {
|
||||
icmp := ICMP{
|
||||
Type: 8,
|
||||
Code: 0,
|
||||
CheckSum: 0,
|
||||
Identifier: 0,
|
||||
SequenceNum: seq,
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
binary.Write(&buffer, binary.BigEndian, icmp)
|
||||
icmp.CheckSum = checkSum(buffer.Bytes())
|
||||
buffer.Reset()
|
||||
|
||||
return icmp
|
||||
}
|
||||
|
||||
func sendICMPRequest(icmp ICMP, destAddr *net.IPAddr, timeout time.Duration) (PingResult, error) {
|
||||
var res PingResult
|
||||
res.RemoteIP = destAddr.String()
|
||||
conn, err := net.DialIP("ip:icmp", nil, destAddr)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
defer conn.Close()
|
||||
var buffer bytes.Buffer
|
||||
binary.Write(&buffer, binary.BigEndian, icmp)
|
||||
|
||||
if _, err := conn.Write(buffer.Bytes()); err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
tStart := time.Now()
|
||||
|
||||
conn.SetReadDeadline((time.Now().Add(timeout)))
|
||||
|
||||
recv := make([]byte, 1024)
|
||||
res.RecvCount, err = conn.Read(recv)
|
||||
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
tEnd := time.Now()
|
||||
res.Duration = tEnd.Sub(tStart)
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
func checkSum(data []byte) uint16 {
|
||||
var (
|
||||
sum uint32
|
||||
length int = len(data)
|
||||
index int
|
||||
)
|
||||
for length > 1 {
|
||||
sum += uint32(data[index])<<8 + uint32(data[index+1])
|
||||
index += 2
|
||||
length -= 2
|
||||
}
|
||||
if length > 0 {
|
||||
sum += uint32(data[index])
|
||||
}
|
||||
sum += (sum >> 16)
|
||||
|
||||
return uint16(^sum)
|
||||
}
|
||||
|
||||
type PingResult struct {
|
||||
Duration time.Duration
|
||||
RecvCount int
|
||||
RemoteIP string
|
||||
}
|
||||
|
||||
func Ping(ip string, seq int, timeout time.Duration) (PingResult, error) {
|
||||
var res PingResult
|
||||
ipAddr, err := net.ResolveIPAddr("ip", ip)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
icmp := getICMP(uint16(seq))
|
||||
return sendICMPRequest(icmp, ipAddr, timeout)
|
||||
}
|
||||
|
||||
func IsIpPingable(ip string, timeout time.Duration, retryLimit int) bool {
|
||||
for i := 0; i < retryLimit; i++ {
|
||||
_, err := Ping(ip, 29, timeout)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
.idea
|
||||
.vscode
|
@ -0,0 +1,28 @@
|
||||
// +build darwin
|
||||
|
||||
package staros
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultFreq - frequency, in Hz, middle A
|
||||
DefaultFreq = 0.0
|
||||
// DefaultDuration - duration in milliseconds
|
||||
DefaultDuration = 0
|
||||
)
|
||||
|
||||
// Beep beeps the PC speaker (https://en.wikipedia.org/wiki/PC_speaker).
|
||||
func Beep(freq float64, duration int) error {
|
||||
osa, err := exec.LookPath("osascript")
|
||||
if err != nil {
|
||||
// Output the only beep we can
|
||||
_, err = os.Stdout.Write([]byte{7})
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := exec.Command(osa, "-e", `beep`)
|
||||
return cmd.Run()
|
||||
}
|
@ -0,0 +1,138 @@
|
||||
// +build linux
|
||||
|
||||
package staros
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Constants
|
||||
const (
|
||||
// This number represents the fixed frequency of the original PC XT's timer chip, which is approximately 1.193 MHz. This number
|
||||
// is divided with the desired frequency to obtain a counter value, that is subsequently fed into the timer chip, tied to the PC speaker.
|
||||
clockTickRate = 1193180
|
||||
|
||||
// linux/kd.h, start sound generation (0 for off)
|
||||
kiocsound = 0x4B2F
|
||||
|
||||
// linux/input-event-codes.h
|
||||
evSnd = 0x12 // Event type
|
||||
sndTone = 0x02 // Sound
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultFreq - frequency, in Hz, middle A
|
||||
DefaultFreq = 440.0
|
||||
// DefaultDuration - duration in milliseconds
|
||||
DefaultDuration = 200
|
||||
)
|
||||
|
||||
// inputEvent represents linux/input.h event structure.
|
||||
type inputEvent struct {
|
||||
Time syscall.Timeval // time in seconds since epoch at which event occurred
|
||||
Type uint16 // event type
|
||||
Code uint16 // event code related to the event type
|
||||
Value int32 // event value related to the event type
|
||||
}
|
||||
|
||||
// ioctl system call manipulates the underlying device parameters of special files.
|
||||
func ioctl(fd, name, data uintptr) error {
|
||||
_, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, name, data)
|
||||
if e != 0 {
|
||||
return e
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Beep beeps the PC speaker (https://en.wikipedia.org/wiki/PC_speaker).
|
||||
//
|
||||
// On Linux it needs permission to access `/dev/tty0` or `/dev/input/by-path/platform-pcspkr-event-spkr` files for writing,
|
||||
// and `pcspkr` module must be loaded. User must be in correct groups, usually `input` and/or `tty`.
|
||||
//
|
||||
// If it can not open device files, it will fallback to sending Bell character (https://en.wikipedia.org/wiki/Bell_character).
|
||||
// For bell character in X11 terminals you can enable bell with `xset b on`. For console check `setterm` and `--blength` or `--bfreq` options.
|
||||
//
|
||||
// On macOS this just sends bell character. Enable `Audible bell` in Terminal --> Preferences --> Settings --> Advanced.
|
||||
//
|
||||
// On Windows it uses Beep function via syscall.
|
||||
//
|
||||
// On Web it plays hard coded beep sound.
|
||||
func Beep(freq float64, duration int) error {
|
||||
if freq == 0 {
|
||||
freq = DefaultFreq
|
||||
} else if freq > 20000 {
|
||||
freq = 20000
|
||||
} else if freq < 0 {
|
||||
freq = DefaultFreq
|
||||
}
|
||||
|
||||
if duration == 0 {
|
||||
duration = DefaultDuration
|
||||
}
|
||||
|
||||
period := int(float64(clockTickRate) / freq)
|
||||
|
||||
var evdev bool
|
||||
|
||||
f, err := os.OpenFile("/dev/tty0", os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
e := err
|
||||
f, err = os.OpenFile("/dev/input/by-path/platform-pcspkr-event-spkr", os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
e = errors.New("beeep: " + e.Error() + "; " + err.Error())
|
||||
|
||||
// Output the only beep we can
|
||||
_, err = os.Stdout.Write([]byte{7})
|
||||
if err != nil {
|
||||
return errors.New(e.Error() + "; " + err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
evdev = true
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
if evdev { // Use Linux evdev API
|
||||
ev := inputEvent{}
|
||||
ev.Type = evSnd
|
||||
ev.Code = sndTone
|
||||
ev.Value = int32(freq)
|
||||
|
||||
d := *(*[unsafe.Sizeof(ev)]byte)(unsafe.Pointer(&ev))
|
||||
|
||||
// Start beep
|
||||
f.Write(d[:])
|
||||
|
||||
time.Sleep(time.Duration(duration) * time.Millisecond)
|
||||
|
||||
ev.Value = 0
|
||||
d = *(*[unsafe.Sizeof(ev)]byte)(unsafe.Pointer(&ev))
|
||||
|
||||
// Stop beep
|
||||
f.Write(d[:])
|
||||
} else { // Use ioctl
|
||||
// Start beep
|
||||
err = ioctl(f.Fd(), kiocsound, uintptr(period))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
time.Sleep(time.Duration(duration) * time.Millisecond)
|
||||
|
||||
// Stop beep
|
||||
err = ioctl(f.Fd(), kiocsound, uintptr(0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
// +build windows
|
||||
|
||||
package staros
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultFreq - frequency, in Hz, middle A
|
||||
DefaultFreq = 587.0
|
||||
// DefaultDuration - duration in milliseconds
|
||||
DefaultDuration = 500
|
||||
)
|
||||
|
||||
// Beep beeps the PC speaker (https://en.wikipedia.org/wiki/PC_speaker).
|
||||
func Beep(freq float64, duration int) error {
|
||||
if freq == 0 {
|
||||
freq = DefaultFreq
|
||||
} else if freq > 32767 {
|
||||
freq = 32767
|
||||
} else if freq < 37 {
|
||||
freq = DefaultFreq
|
||||
}
|
||||
|
||||
if duration == 0 {
|
||||
duration = DefaultDuration
|
||||
}
|
||||
|
||||
kernel32, _ := syscall.LoadLibrary("kernel32.dll")
|
||||
beep32, _ := syscall.GetProcAddress(kernel32, "Beep")
|
||||
|
||||
defer syscall.FreeLibrary(kernel32)
|
||||
|
||||
_, _, e := syscall.Syscall(uintptr(beep32), uintptr(2), uintptr(int(freq)), uintptr(duration), 0)
|
||||
if e != 0 {
|
||||
return e
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package staros
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
)
|
||||
|
||||
var ERR_ALREADY_LOCKED = errors.New("ALREADY LOCKED")
|
||||
var ERR_TIMEOUT = errors.New("TIME OUT")
|
||||
|
||||
func NewFileLock(filepath string) FileLock {
|
||||
return FileLock{
|
||||
filepath: filepath,
|
||||
}
|
||||
}
|
||||
|
||||
// 检测文件/文件夹是否存在
|
||||
func Exists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsFile 返回给定文件地址是否是一个文件,
|
||||
//True为是一个文件,False为不是文件或路径无效
|
||||
func IsFile(fpath string) bool {
|
||||
s, err := os.Stat(fpath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return !s.IsDir()
|
||||
}
|
||||
|
||||
// IsFolder 返回给定文件地址是否是一个文件夹,
|
||||
//True为是一个文件夹,False为不是文件夹或路径无效
|
||||
func IsFolder(fpath string) bool {
|
||||
s, err := os.Stat(fpath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return s.IsDir()
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
//+build darwin
|
||||
|
||||
package staros
|
||||
|
||||
import (
|
||||
"b612.me/stario"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type FileLock struct {
|
||||
fd int
|
||||
filepath string
|
||||
}
|
||||
|
||||
func (f *FileLock) openFileForLock() error {
|
||||
fd, err := syscall.Open(f.filepath, syscall.O_CREAT|syscall.O_RDONLY, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.filepath = f.filepath
|
||||
f.fd = fd
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FileLock) Lock(Exclusive bool) error {
|
||||
var lockType int
|
||||
if Exclusive {
|
||||
lockType = syscall.LOCK_EX
|
||||
} else {
|
||||
lockType = syscall.LOCK_SH
|
||||
}
|
||||
if err := f.openFileForLock(); err != nil {
|
||||
return err
|
||||
}
|
||||
return syscall.Flock(f.fd, lockType)
|
||||
}
|
||||
|
||||
func (f *FileLock) LockNoBlocking(Exclusive bool) error {
|
||||
var lockType int
|
||||
if Exclusive {
|
||||
lockType = syscall.LOCK_EX
|
||||
} else {
|
||||
lockType = syscall.LOCK_SH
|
||||
}
|
||||
if err := f.openFileForLock(); err != nil {
|
||||
return err
|
||||
}
|
||||
err := syscall.Flock(f.fd, lockType|syscall.LOCK_NB)
|
||||
if err != nil {
|
||||
syscall.Close(f.fd)
|
||||
if err == syscall.EWOULDBLOCK {
|
||||
return ERR_ALREADY_LOCKED
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *FileLock) Unlock() error {
|
||||
err := syscall.Flock(f.fd, syscall.LOCK_UN)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return syscall.Close(f.fd)
|
||||
}
|
||||
|
||||
func (f *FileLock) LockWithTimeout(tm time.Duration, Exclusive bool) error {
|
||||
return stario.WaitUntilTimeout(tm, func(tmout chan struct{}) error {
|
||||
err := f.Lock(Exclusive)
|
||||
select {
|
||||
case <-tmout:
|
||||
f.Unlock()
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func timespecToTime(ts syscall.Timespec) time.Time {
|
||||
return time.Unix(int64(ts.Sec), int64(ts.Nsec))
|
||||
}
|
||||
|
||||
func GetFileCreationTime(fileinfo os.FileInfo) time.Time {
|
||||
return timespecToTime(fileinfo.Sys().(*syscall.Stat_t).Ctimespec)
|
||||
}
|
||||
|
||||
func GetFileAccessTime(fileinfo os.FileInfo) time.Time {
|
||||
return timespecToTime(fileinfo.Sys().(*syscall.Stat_t).Atimespec)
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
//+build linux
|
||||
|
||||
package staros
|
||||
|
||||
import (
|
||||
"b612.me/stario"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type FileLock struct {
|
||||
fd int
|
||||
filepath string
|
||||
}
|
||||
|
||||
func timespecToTime(ts syscall.Timespec) time.Time {
|
||||
return time.Unix(int64(ts.Sec), int64(ts.Nsec))
|
||||
}
|
||||
|
||||
func GetFileCreationTime(fileinfo os.FileInfo) time.Time {
|
||||
return timespecToTime(fileinfo.Sys().(*syscall.Stat_t).Ctim)
|
||||
}
|
||||
|
||||
func GetFileAccessTime(fileinfo os.FileInfo) time.Time {
|
||||
return timespecToTime(fileinfo.Sys().(*syscall.Stat_t).Atim)
|
||||
}
|
||||
|
||||
func (f *FileLock) openFileForLock() error {
|
||||
fd, err := syscall.Open(f.filepath, syscall.O_CREAT|syscall.O_RDONLY, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.filepath = f.filepath
|
||||
f.fd = fd
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FileLock) Lock(Exclusive bool) error {
|
||||
var lockType int
|
||||
if Exclusive {
|
||||
lockType = syscall.LOCK_EX
|
||||
} else {
|
||||
lockType = syscall.LOCK_SH
|
||||
}
|
||||
if err := f.openFileForLock(); err != nil {
|
||||
return err
|
||||
}
|
||||
return syscall.Flock(f.fd, lockType)
|
||||
}
|
||||
|
||||
func (f *FileLock) LockNoBlocking(Exclusive bool) error {
|
||||
var lockType int
|
||||
if Exclusive {
|
||||
lockType = syscall.LOCK_EX
|
||||
} else {
|
||||
lockType = syscall.LOCK_SH
|
||||
}
|
||||
if err := f.openFileForLock(); err != nil {
|
||||
return err
|
||||
}
|
||||
err := syscall.Flock(f.fd, lockType|syscall.LOCK_NB)
|
||||
if err != nil {
|
||||
syscall.Close(f.fd)
|
||||
if err == syscall.EWOULDBLOCK {
|
||||
return ERR_ALREADY_LOCKED
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *FileLock) Unlock() error {
|
||||
err := syscall.Flock(f.fd, syscall.LOCK_UN)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return syscall.Close(f.fd)
|
||||
}
|
||||
|
||||
func (f *FileLock) LockWithTimeout(tm time.Duration, Exclusive bool) error {
|
||||
return stario.WaitUntilTimeout(tm, func(tmout chan struct{}) error {
|
||||
err := f.Lock(Exclusive)
|
||||
select {
|
||||
case <-tmout:
|
||||
f.Unlock()
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
// +build windows
|
||||
|
||||
package staros
|
||||
|
||||
import (
|
||||
"b612.me/win32api"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type FileLock struct {
|
||||
filepath string
|
||||
handle win32api.HANDLE
|
||||
}
|
||||
|
||||
func GetFileCreationTime(fileinfo os.FileInfo) time.Time {
|
||||
d := fileinfo.Sys().(*syscall.Win32FileAttributeData)
|
||||
return time.Unix(0, d.CreationTime.Nanoseconds())
|
||||
}
|
||||
|
||||
func GetFileAccessTime(fileinfo os.FileInfo) time.Time {
|
||||
d := fileinfo.Sys().(*syscall.Win32FileAttributeData)
|
||||
return time.Unix(0, d.LastAccessTime.Nanoseconds())
|
||||
}
|
||||
|
||||
func SetFileTimes(file *os.File, info os.FileInfo) {
|
||||
|
||||
}
|
||||
|
||||
func SetFileTimesbyTime(file *os.File) {
|
||||
|
||||
}
|
||||
|
||||
func (f *FileLock) openFileForLock() error {
|
||||
name, err := syscall.UTF16PtrFromString(f.filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
handle, err := syscall.CreateFile(
|
||||
name,
|
||||
syscall.GENERIC_READ,
|
||||
syscall.FILE_SHARE_READ,
|
||||
nil,
|
||||
syscall.OPEN_ALWAYS,
|
||||
syscall.FILE_FLAG_OVERLAPPED|0x00000080,
|
||||
0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.handle = win32api.HANDLE(handle)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FileLock) lockForTimeout(timeout time.Duration, lockType win32api.DWORD) error {
|
||||
var err error
|
||||
if err = f.openFileForLock(); err != nil {
|
||||
return err
|
||||
}
|
||||
event, err := win32api.CreateEventW(nil, true, false, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
myEvent := &syscall.Overlapped{HEvent: syscall.Handle(event)}
|
||||
defer syscall.CloseHandle(myEvent.HEvent)
|
||||
_, err = win32api.LockFileEx(f.handle, lockType, 0, 1, 0, myEvent)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if err != syscall.ERROR_IO_PENDING {
|
||||
return err
|
||||
}
|
||||
millis := uint32(syscall.INFINITE)
|
||||
if timeout >= 0 {
|
||||
millis = uint32(timeout.Nanoseconds() / 1000000)
|
||||
}
|
||||
s, err := syscall.WaitForSingleObject(myEvent.HEvent, millis)
|
||||
switch s {
|
||||
case syscall.WAIT_OBJECT_0:
|
||||
// success!
|
||||
return nil
|
||||
case syscall.WAIT_TIMEOUT:
|
||||
f.Unlock()
|
||||
return ERR_TIMEOUT
|
||||
default:
|
||||
f.Unlock()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FileLock) Lock(Exclusive bool) error {
|
||||
var lockType win32api.DWORD
|
||||
if Exclusive {
|
||||
lockType = win32api.LOCKFILE_EXCLUSIVE_LOCK
|
||||
} else {
|
||||
lockType = 0
|
||||
}
|
||||
return f.lockForTimeout(0, lockType)
|
||||
}
|
||||
|
||||
func (f *FileLock) LockWithTimeout(tm time.Duration, Exclusive bool) error {
|
||||
var lockType win32api.DWORD
|
||||
if Exclusive {
|
||||
lockType = win32api.LOCKFILE_EXCLUSIVE_LOCK
|
||||
} else {
|
||||
lockType = 0
|
||||
}
|
||||
return f.lockForTimeout(tm, lockType)
|
||||
}
|
||||
|
||||
func (f *FileLock) LockNoBlocking(Exclusive bool) error {
|
||||
var lockType win32api.DWORD
|
||||
if Exclusive {
|
||||
lockType = win32api.LOCKFILE_EXCLUSIVE_LOCK
|
||||
} else {
|
||||
lockType = 0
|
||||
}
|
||||
return f.lockForTimeout(0, lockType|win32api.LOCKFILE_FAIL_IMMEDIATELY)
|
||||
}
|
||||
|
||||
func (f *FileLock) Unlock() error {
|
||||
return syscall.Close(syscall.Handle(f.handle))
|
||||
}
|
@ -0,0 +1,305 @@
|
||||
package staros
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Calc(math string) (float64, error) {
|
||||
math = strings.Replace(math, " ", "", -1)
|
||||
math = strings.ToLower(math)
|
||||
if err := check(math); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
result,err:=calc(math)
|
||||
if err!=nil {
|
||||
return 0,err
|
||||
}
|
||||
return floatRound(result,15),nil
|
||||
}
|
||||
|
||||
func floatRound(f float64, n int) float64 {
|
||||
format := "%." + strconv.Itoa(n) + "f"
|
||||
res, _ := strconv.ParseFloat(fmt.Sprintf(format, f), 64)
|
||||
return res
|
||||
}
|
||||
|
||||
func check(math string) error {
|
||||
math = strings.Replace(math, " ", "", -1)
|
||||
math = strings.ToLower(math)
|
||||
var bracketSum int
|
||||
var signReady bool
|
||||
for k, v := range math {
|
||||
if string([]rune{v}) == "(" {
|
||||
bracketSum++
|
||||
}
|
||||
if string([]rune{v}) == ")" {
|
||||
bracketSum--
|
||||
}
|
||||
if bracketSum < 0 {
|
||||
return fmt.Errorf("err at position %d.Reason is right bracket position not correct,except (", k)
|
||||
}
|
||||
if containSign(string([]rune{v})) {
|
||||
if signReady {
|
||||
if string([]rune{v}) != "+" && string([]rune{v}) != "-" {
|
||||
return fmt.Errorf("err at position %d.Reason is sign %s not correct", k, string([]rune{v}))
|
||||
}
|
||||
} else {
|
||||
signReady = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
signReady = false
|
||||
}
|
||||
if bracketSum != 0 {
|
||||
return fmt.Errorf("Error:right bracket is not equal as left bracket")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func calc(math string) (float64, error) {
|
||||
var bracketLeft int
|
||||
var bracketRight int
|
||||
var DupStart int = -1
|
||||
for pos, str := range math {
|
||||
if string(str) == "(" {
|
||||
bracketLeft = pos
|
||||
}
|
||||
if string(str) == ")" {
|
||||
bracketRight = pos
|
||||
break
|
||||
}
|
||||
}
|
||||
if bracketRight == 0 && bracketLeft != 0 || (bracketLeft > bracketRight) {
|
||||
return 0, fmt.Errorf("Error:bracket not correct at %d ,except )", bracketLeft)
|
||||
}
|
||||
if bracketRight == 0 && bracketLeft == 0 {
|
||||
return calcLong(math)
|
||||
}
|
||||
line := math[bracketLeft+1 : bracketRight]
|
||||
num, err := calcLong(line)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for i := bracketLeft - 1; i >= 0; i-- {
|
||||
if !containSign(math[i : i+1]) {
|
||||
DupStart = i
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if DupStart != -1 {
|
||||
sign := math[DupStart:bracketLeft]
|
||||
num, err := calcDuaFloat(sign, num)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
math = math[:DupStart] + fmt.Sprintf("%.15f", num) + math[bracketRight+1:]
|
||||
DupStart = -1
|
||||
} else {
|
||||
math = math[:bracketLeft] + fmt.Sprintf("%.15f", num) + math[bracketRight+1:]
|
||||
}
|
||||
return calc(math)
|
||||
}
|
||||
|
||||
func calcLong(str string) (float64, error) {
|
||||
var sigReady bool = false
|
||||
var sigApply bool = false
|
||||
var numPool []float64
|
||||
var operPool []string
|
||||
var numStr string
|
||||
var oper string
|
||||
if str[0:1] == "+" || str[0:1] == "-" {
|
||||
sigReady = true
|
||||
}
|
||||
for _, stp := range str {
|
||||
if sigReady && containSign(string(stp)) {
|
||||
sigReady = false
|
||||
sigApply = true
|
||||
oper = string(stp)
|
||||
continue
|
||||
}
|
||||
if !containSign(string(stp)) {
|
||||
sigReady = false
|
||||
numStr = string(append([]rune(numStr), stp))
|
||||
continue
|
||||
}
|
||||
if !sigReady {
|
||||
sigReady = true
|
||||
}
|
||||
if sigApply {
|
||||
num, err := calcDua(oper, numStr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sigApply = false
|
||||
numPool = append(numPool, num)
|
||||
} else {
|
||||
num, err := parseNumbic(numStr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
numPool = append(numPool, num)
|
||||
}
|
||||
numStr = ""
|
||||
operPool = append(operPool, string(stp))
|
||||
}
|
||||
if sigApply {
|
||||
num, err := calcDua(oper, numStr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
numPool = append(numPool, num)
|
||||
} else {
|
||||
num, err := parseNumbic(numStr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
numPool = append(numPool, num)
|
||||
}
|
||||
return calcPool(numPool, operPool)
|
||||
}
|
||||
|
||||
func calcPool(numPool []float64, operPool []string) (float64, error) {
|
||||
if len(numPool) == 1 && len(operPool) == 0 {
|
||||
return numPool[0], nil
|
||||
}
|
||||
if len(numPool) < len(operPool) {
|
||||
return 0, errors.New(("Operate Signal Is too much"))
|
||||
}
|
||||
calcFunc := func(k int, v string) (float64, error) {
|
||||
num, err := calcSigFloat(numPool[k], v, numPool[k+1])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
tmp := append(numPool[:k], num)
|
||||
numPool = append(tmp, numPool[k+2:]...)
|
||||
operPool = append(operPool[:k], operPool[k+1:]...)
|
||||
return calcPool(numPool, operPool)
|
||||
}
|
||||
for k, v := range operPool {
|
||||
if v == "^" {
|
||||
return calcFunc(k, v)
|
||||
}
|
||||
}
|
||||
for k, v := range operPool {
|
||||
if v == "*" || v == "/" {
|
||||
return calcFunc(k, v)
|
||||
}
|
||||
}
|
||||
for k, v := range operPool {
|
||||
return calcFunc(k, v)
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func calcSigFloat(floatA float64, b string, floatC float64) (float64, error) {
|
||||
switch b {
|
||||
case "+":
|
||||
return floatRound(floatA + floatC,15), nil
|
||||
case "-":
|
||||
return floatRound(floatA - floatC,15), nil
|
||||
case "*":
|
||||
return floatRound(floatA * floatC,15), nil
|
||||
case "/":
|
||||
if floatC == 0 {
|
||||
return 0, errors.New("Divisor cannot be 0")
|
||||
}
|
||||
return floatRound(floatA / floatC,15), nil
|
||||
case "^":
|
||||
return math.Pow(floatA, floatC), nil
|
||||
}
|
||||
return 0, fmt.Errorf("unexpect method:%s", b)
|
||||
}
|
||||
|
||||
func calcSig(a, b, c string) (float64, error) {
|
||||
floatA, err := parseNumbic(a)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
floatC, err := parseNumbic(c)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return calcSigFloat(floatA, b, floatC)
|
||||
}
|
||||
|
||||
func calcDuaFloat(a string, floatB float64) (float64, error) {
|
||||
switch a {
|
||||
case "sin":
|
||||
return math.Sin(floatB), nil
|
||||
case "cos":
|
||||
return math.Cos(floatB), nil
|
||||
case "tan":
|
||||
return math.Tan(floatB), nil
|
||||
case "abs":
|
||||
return math.Abs(floatB), nil
|
||||
case "arcsin":
|
||||
return math.Asin(floatB), nil
|
||||
case "arccos":
|
||||
return math.Acos(floatB), nil
|
||||
case "arctan":
|
||||
return math.Atan(floatB), nil
|
||||
case "sqrt":
|
||||
return math.Sqrt(floatB), nil
|
||||
case "loge":
|
||||
return math.Log(floatB), nil
|
||||
case "log10":
|
||||
return math.Log10(floatB), nil
|
||||
case "log2":
|
||||
return math.Log2(floatB), nil
|
||||
case "floor":
|
||||
return math.Floor(floatB), nil
|
||||
case "ceil":
|
||||
return math.Ceil(floatB), nil
|
||||
case "round":
|
||||
return math.Round(floatB), nil
|
||||
case "trunc":
|
||||
return math.Trunc(floatB), nil
|
||||
case "+":
|
||||
return 0 + floatB, nil
|
||||
case "-":
|
||||
return 0 - floatB, nil
|
||||
}
|
||||
return 0, fmt.Errorf("unexpect method:%s", a)
|
||||
}
|
||||
func calcDua(a, b string) (float64, error) {
|
||||
floatB, err := parseNumbic(b)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return calcDuaFloat(a, floatB)
|
||||
}
|
||||
|
||||
func parseNumbic(str string) (float64, error) {
|
||||
switch str {
|
||||
case "pi":
|
||||
return float64(math.Pi), nil
|
||||
case "e":
|
||||
return float64(math.E), nil
|
||||
default:
|
||||
return strconv.ParseFloat(str, 64)
|
||||
}
|
||||
}
|
||||
|
||||
func containSign(str string) bool {
|
||||
var sign []string = []string{"+", "-", "*", "/", "^"}
|
||||
for _, v := range sign {
|
||||
if str == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func contain(pool []string, str string) bool {
|
||||
for _, v := range pool {
|
||||
if v == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
//+build darwin
|
||||
|
||||
package staros
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"golang.org/x/sys/unix"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Memory 系统内存信息
|
||||
func Memory() (MemStatus,error) {
|
||||
return darwinMemory()
|
||||
}
|
||||
|
||||
type swapUsage struct {
|
||||
Total uint64
|
||||
Avail uint64
|
||||
Used uint64
|
||||
Pagesize int32
|
||||
Encrypted bool
|
||||
}
|
||||
|
||||
func darwinMemory() (MemStatus, error) {
|
||||
var err error
|
||||
var res MemStatus
|
||||
vm_stat, err := exec.LookPath("vm_stat")
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
out, err := exec.Command(vm_stat).CombinedOutput()
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
totalString, err := unix.Sysctl("hw.memsize")
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// unix.sysctl() helpfully assumes the result is a null-terminated string and
|
||||
// removes the last byte of the result if it's 0 :/
|
||||
totalString += "\x00"
|
||||
|
||||
res.All = uint64(binary.LittleEndian.Uint64([]byte(totalString)))
|
||||
|
||||
lines := strings.Split(string(out), "\n")
|
||||
pagesize := uint64(unix.Getpagesize())
|
||||
for _, line := range lines {
|
||||
fields := strings.Split(line, ":")
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
key := strings.TrimSpace(fields[0])
|
||||
value := strings.Trim(fields[1], " .")
|
||||
switch key {
|
||||
case "Pages free":
|
||||
free, e := strconv.ParseUint(value, 10, 64)
|
||||
if e != nil {
|
||||
err = e
|
||||
}
|
||||
res.Free = free * pagesize
|
||||
case "Pages inactive":
|
||||
inactive, e := strconv.ParseUint(value, 10, 64)
|
||||
if e != nil {
|
||||
err = e
|
||||
}
|
||||
res.Available = inactive * pagesize
|
||||
case "Pages active":
|
||||
active, e := strconv.ParseUint(value, 10, 64)
|
||||
if e != nil {
|
||||
err = e
|
||||
}
|
||||
_ = active * pagesize
|
||||
case "Pages wired down":
|
||||
wired, e := strconv.ParseUint(value, 10, 64)
|
||||
if e != nil {
|
||||
err = e
|
||||
}
|
||||
_ = wired * pagesize
|
||||
}
|
||||
}
|
||||
res.Available += res.Free
|
||||
res.Used = res.All - res.Available
|
||||
//swap
|
||||
value, err := unix.SysctlRaw("vm.swapusage")
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
if len(value) != 32 {
|
||||
return res, fmt.Errorf("unexpected output of sysctl vm.swapusage: %v (len: %d)", value, len(value))
|
||||
}
|
||||
swap := (*swapUsage)(unsafe.Pointer(&value[0]))
|
||||
res.SwapAll = swap.Total
|
||||
res.SwapUsed = swap.Used
|
||||
res.SwapFree = swap.Avail
|
||||
return res, err
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
//+build linux
|
||||
|
||||
package staros
|
||||
|
||||
import "syscall"
|
||||
|
||||
// Memory 系统内存信息
|
||||
func Memory() (MemStatus, error) {
|
||||
var mem MemStatus
|
||||
ram := new(syscall.Sysinfo_t)
|
||||
if err := syscall.Sysinfo(ram); err != nil {
|
||||
return mem, err
|
||||
}
|
||||
mem.All = uint64(ram.Totalram)
|
||||
mem.BuffCache = uint64(ram.Bufferram)
|
||||
mem.Free = uint64(ram.Freeram)
|
||||
mem.Shared = uint64(ram.Sharedram)
|
||||
mem.Available = uint64(ram.Freeram + ram.Sharedram + ram.Bufferram)
|
||||
mem.SwapAll = uint64(ram.Totalswap)
|
||||
mem.SwapFree = uint64(ram.Freeswap)
|
||||
mem.SwapUsed = uint64(mem.SwapAll - mem.SwapFree)
|
||||
mem.Used = uint64(mem.All - mem.Free)
|
||||
return mem, nil
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
// +build windows
|
||||
|
||||
package staros
|
||||
|
||||
import "b612.me/win32api"
|
||||
|
||||
// Memory 系统内存信息
|
||||
func Memory() (MemStatus, error) {
|
||||
var mem MemStatus
|
||||
ram := new(win32api.MEMORYSTATUSEX)
|
||||
_, err := win32api.GlobalMemoryStatusEx(ram)
|
||||
if err != nil {
|
||||
return mem, err
|
||||
}
|
||||
mem.All = uint64(ram.UllTotalPhys)
|
||||
mem.Free = uint64(ram.UllAvailPhys)
|
||||
mem.Available = uint64(ram.UllAvailPhys)
|
||||
mem.Used = uint64(mem.All - mem.Free)
|
||||
mem.SwapAll = uint64(ram.UllTotalPageFile)
|
||||
mem.SwapFree = uint64(ram.UllAvailPageFile)
|
||||
mem.SwapUsed = mem.SwapAll - mem.SwapFree
|
||||
mem.VirtualAll = uint64(mem.VirtualAll)
|
||||
mem.VirtualAvail = uint64(mem.VirtualAvail)
|
||||
mem.VirtualUsed = mem.VirtualAll - mem.VirtualUsed
|
||||
return mem, nil
|
||||
}
|
@ -0,0 +1,326 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package staros
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NetUsage() ([]NetAdapter, error) {
|
||||
data, err := ioutil.ReadFile("/proc/net/dev")
|
||||
if err != nil {
|
||||
return []NetAdapter{}, err
|
||||
}
|
||||
sps := strings.Split(strings.TrimSpace(string(data)), "\n")
|
||||
if len(sps) < 3 {
|
||||
return []NetAdapter{}, errors.New("No Adaptor")
|
||||
}
|
||||
var res []NetAdapter
|
||||
netLists := sps[2:]
|
||||
for _, v := range netLists {
|
||||
v = strings.ReplaceAll(v, " ", " ")
|
||||
for strings.Contains(v, " ") {
|
||||
v = strings.ReplaceAll(v, " ", " ")
|
||||
}
|
||||
v = strings.TrimSpace(v)
|
||||
card := strings.Split(v, " ")
|
||||
name := strings.ReplaceAll(card[0], ":", "")
|
||||
recvBytes, _ := strconv.Atoi(card[1])
|
||||
sendBytes, _ := strconv.Atoi(card[9])
|
||||
res = append(res, NetAdapter{name, uint64(recvBytes), uint64(sendBytes)})
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func NetUsageByname(name string) (NetAdapter, error) {
|
||||
ada, err := NetUsage()
|
||||
if err != nil {
|
||||
return NetAdapter{}, err
|
||||
}
|
||||
for _, v := range ada {
|
||||
if v.Name == name {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return NetAdapter{}, errors.New("Not Found")
|
||||
}
|
||||
|
||||
func NetSpeeds(duration time.Duration) ([]NetSpeed, error) {
|
||||
list1, err := NetUsage()
|
||||
if err != nil {
|
||||
return []NetSpeed{}, err
|
||||
}
|
||||
time.Sleep(duration)
|
||||
list2, err := NetUsage()
|
||||
if err != nil {
|
||||
return []NetSpeed{}, err
|
||||
}
|
||||
if len(list1) > len(list2) {
|
||||
return []NetSpeed{}, errors.New("NetWork Adaptor Num Not ok")
|
||||
}
|
||||
var res []NetSpeed
|
||||
for k, v := range list1 {
|
||||
recv := float64(list2[k].RecvBytes-v.RecvBytes) / duration.Seconds()
|
||||
send := float64(list2[k].SendBytes-v.SendBytes) / duration.Seconds()
|
||||
res = append(res, NetSpeed{
|
||||
Name: v.Name,
|
||||
RecvSpeeds: recv,
|
||||
SendSpeeds: send,
|
||||
RecvBytes: list2[k].RecvBytes,
|
||||
SendBytes: list2[k].SendBytes,
|
||||
})
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func NetSpeedsByName(duration time.Duration, name string) (NetSpeed, error) {
|
||||
ada, err := NetSpeeds(duration)
|
||||
if err != nil {
|
||||
return NetSpeed{}, err
|
||||
}
|
||||
for _, v := range ada {
|
||||
if v.Name == name {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return NetSpeed{}, errors.New("Not Found")
|
||||
}
|
||||
|
||||
// NetConnections return all TCP/UDP/UNIX DOMAIN SOCKET Connections
|
||||
// if your uid != 0 ,and analysePid==true ,you should have CAP_SYS_PRTACE and CAP_DAC_OVERRIDE/CAP_DAC_READ_SEARCH Caps
|
||||
func NetConnections(analysePid bool, types string) ([]NetConn, error) {
|
||||
var result []NetConn
|
||||
var inodeMap map[string]int64
|
||||
var err error
|
||||
var fileList []string
|
||||
if types == "" || strings.Contains(strings.ToLower(types), "all") {
|
||||
fileList = []string{
|
||||
"/proc/net/tcp",
|
||||
"/proc/net/tcp6",
|
||||
"/proc/net/udp",
|
||||
"/proc/net/udp6",
|
||||
"/proc/net/unix",
|
||||
}
|
||||
}
|
||||
if strings.Contains(strings.ToLower(types), "tcp") {
|
||||
fileList = append(fileList, "/proc/net/tcp", "/proc/net/tcp6")
|
||||
}
|
||||
if strings.Contains(strings.ToLower(types), "udp") {
|
||||
fileList = append(fileList, "/proc/net/udp", "/proc/net/udp6")
|
||||
}
|
||||
if strings.Contains(strings.ToLower(types), "unix") {
|
||||
fileList = append(fileList, "/proc/net/unix")
|
||||
}
|
||||
if analysePid {
|
||||
inodeMap, err = GetInodeMap()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
for _, file := range fileList {
|
||||
data, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
tmpRes, err := analyseNetFiles(data, inodeMap, file[strings.LastIndex(file, "/")+1:])
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result = append(result, tmpRes...)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func GetInodeMap() (map[string]int64, error) {
|
||||
res := make(map[string]int64)
|
||||
paths, err := ioutil.ReadDir("/proc")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range paths {
|
||||
if v.IsDir() && Exists("/proc/"+v.Name()+"/fd") {
|
||||
fds, err := ioutil.ReadDir("/proc/" + v.Name() + "/fd")
|
||||
if err != nil && Exists("/proc/"+v.Name()+"/fd") {
|
||||
return nil, err
|
||||
}
|
||||
for _, fd := range fds {
|
||||
socket, err := os.Readlink("/proc/" + v.Name() + "/fd/" + fd.Name())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(socket, "socket") {
|
||||
continue
|
||||
}
|
||||
start := strings.Index(socket, "[")
|
||||
if start < 0 {
|
||||
continue
|
||||
}
|
||||
pid, err := strconv.ParseInt(v.Name(), 10, 64)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
res[socket[start+1:len(socket)-1]] = pid
|
||||
}
|
||||
}
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
func analyseNetFiles(data []byte, inodeMap map[string]int64, typed string) ([]NetConn, error) {
|
||||
if typed == "unix" {
|
||||
return analyseUnixFiles(data, inodeMap, typed)
|
||||
}
|
||||
var result []NetConn
|
||||
strdata := strings.TrimSpace(string(data))
|
||||
strdata = remainOne(strdata, " ", " ")
|
||||
csvData := strings.Split(strdata, "\n")
|
||||
pidMap := make(map[int64]*Process)
|
||||
for line, lineData := range csvData {
|
||||
if line == 0 {
|
||||
continue
|
||||
}
|
||||
v := strings.Split(strings.TrimSpace(lineData), " ")
|
||||
var res NetConn
|
||||
ip, port, err := parseHexIpPort(v[1])
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
res.LocalAddr = ip
|
||||
res.LocalPort = port
|
||||
ip, port, err = parseHexIpPort(v[2])
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
res.RemoteAddr = ip
|
||||
res.RemotePort = port
|
||||
//connection state
|
||||
if strings.Contains(typed, "tcp") {
|
||||
state, err := strconv.ParseInt(strings.TrimSpace(v[3]), 16, 64)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
res.Status = TCP_STATE[state]
|
||||
}
|
||||
txrx_queue := strings.Split(strings.TrimSpace(v[4]), ":")
|
||||
if len(txrx_queue) != 2 {
|
||||
return result, errors.New("not a valid net file")
|
||||
}
|
||||
tx_queue, err := strconv.ParseInt(txrx_queue[0], 16, 64)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
res.TX_Queue = tx_queue
|
||||
rx_queue, err := strconv.ParseInt(txrx_queue[1], 16, 64)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
res.RX_Queue = rx_queue
|
||||
timer := strings.Split(strings.TrimSpace(v[5]), ":")
|
||||
if len(timer) != 2 {
|
||||
return result, errors.New("not a valid net file")
|
||||
}
|
||||
switch timer[0] {
|
||||
case "00":
|
||||
res.TimerActive = "NO_TIMER"
|
||||
case "01":
|
||||
//重传定时器
|
||||
res.TimerActive = "RETRANSMIT"
|
||||
case "02":
|
||||
//连接定时器、FIN_WAIT_2定时器或TCP保活定时器
|
||||
res.TimerActive = "KEEPALIVE"
|
||||
case "03":
|
||||
//TIME_WAIT定时器
|
||||
res.TimerActive = "TIME_WAIT"
|
||||
case "04":
|
||||
//持续定时器
|
||||
res.TimerActive = "ZERO_WINDOW_PROBE"
|
||||
default:
|
||||
res.TimerActive = "UNKNOWN"
|
||||
}
|
||||
timerJif, err := strconv.ParseInt(timer[1], 16, 64)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
res.TimerJiffies = timerJif
|
||||
timerCnt, err := strconv.ParseInt(strings.TrimSpace(v[6]), 16, 64)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
res.RtoTimer = timerCnt
|
||||
res.Uid, err = strconv.ParseInt(v[7], 10, 64)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
res.Inode = v[9]
|
||||
if inodeMap != nil && len(inodeMap) > 0 {
|
||||
var ok bool
|
||||
res.Pid, ok = inodeMap[res.Inode]
|
||||
if !ok {
|
||||
res.Pid = -1
|
||||
} else {
|
||||
_, ok := pidMap[res.Pid]
|
||||
if !ok {
|
||||
tmp, err := FindProcessByPid(res.Pid)
|
||||
if err != nil {
|
||||
pidMap[res.Pid] = nil
|
||||
} else {
|
||||
pidMap[res.Pid] = &tmp
|
||||
}
|
||||
}
|
||||
res.Process = pidMap[res.Pid]
|
||||
}
|
||||
}
|
||||
res.Typed = typed
|
||||
result = append(result, res)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func analyseUnixFiles(data []byte, inodeMap map[string]int64, typed string) ([]NetConn, error) {
|
||||
var result []NetConn
|
||||
strdata := strings.TrimSpace(string(data))
|
||||
strdata = remainOne(strdata, " ", " ")
|
||||
csvData := strings.Split(strdata, "\n")
|
||||
pidMap := make(map[int64]*Process)
|
||||
for line, lineData := range csvData {
|
||||
if line == 0 {
|
||||
continue
|
||||
}
|
||||
v := strings.Split(strings.TrimSpace(lineData), " ")
|
||||
var res NetConn
|
||||
res.Inode = v[6]
|
||||
if len(v) == 8 {
|
||||
res.Socket = v[7]
|
||||
}
|
||||
if inodeMap != nil && len(inodeMap) > 0 {
|
||||
var ok bool
|
||||
res.Pid, ok = inodeMap[res.Inode]
|
||||
if !ok {
|
||||
res.Pid = -1
|
||||
} else {
|
||||
_, ok := pidMap[res.Pid]
|
||||
if !ok || pidMap[res.Pid] == nil {
|
||||
tmp, err := FindProcessByPid(res.Pid)
|
||||
if err != nil {
|
||||
pidMap[res.Pid] = nil
|
||||
} else {
|
||||
pidMap[res.Pid] = &tmp
|
||||
}
|
||||
}
|
||||
if pidMap[res.Pid] != nil {
|
||||
res.Uid = int64(pidMap[res.Pid].RUID)
|
||||
res.Process = pidMap[res.Pid]
|
||||
}
|
||||
}
|
||||
}
|
||||
res.Typed = typed
|
||||
result = append(result, res)
|
||||
}
|
||||
return result, nil
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
// +build windows
|
||||
|
||||
package staros
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func NetUsage() ([]NetAdapter, error) {
|
||||
var res []NetAdapter
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func NetUsageByname(name string) (NetAdapter, error) {
|
||||
return NetAdapter{}, nil
|
||||
}
|
||||
|
||||
func NetSpeeds(duration time.Duration) ([]NetSpeed, error) {
|
||||
var res []NetSpeed
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func NetSpeedsByName(duration time.Duration, name string) (NetSpeed, error) {
|
||||
|
||||
return NetSpeed{}, nil
|
||||
}
|
||||
|
||||
// NetConnections return all TCP/UDP/UNIX DOMAIN SOCKET Connections
|
||||
// if your uid != 0 ,and analysePid==true ,you should have CAP_SYS_PRTACE and CAP_DAC_OVERRIDE/CAP_DAC_READ_SEARCH Caps
|
||||
func NetConnections(analysePid bool) ([]NetConn, error) {
|
||||
var result []NetConn
|
||||
return result, nil
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package staros
|
||||
|
||||
import (
|
||||
"os/user"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// GetUidGid
|
||||
func GetUidGid(uname string) (uint32, uint32, string, error) {
|
||||
usr, err := user.Lookup(uname)
|
||||
if err != nil {
|
||||
return 0, 0, "", err
|
||||
}
|
||||
uidInt, _ := strconv.Atoi(usr.Uid)
|
||||
gidInt, _ := strconv.Atoi(usr.Gid)
|
||||
return uint32(uidInt), uint32(gidInt), usr.HomeDir, nil
|
||||
}
|
||||
|
||||
// GetUid
|
||||
func GetUid(uname string) (uint32, error) {
|
||||
usr, err := user.Lookup(uname)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
uidInt, _ := strconv.Atoi(usr.Uid)
|
||||
return uint32(uidInt), nil
|
||||
}
|
||||
|
||||
// GetGid
|
||||
func GetGid(uname string) (uint32, error) {
|
||||
usr, err := user.LookupGroup(uname)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
gidInt, _ := strconv.Atoi(usr.Gid)
|
||||
return uint32(gidInt), nil
|
||||
}
|
||||
|
||||
// GetGidByName
|
||||
func GetGidByName(uname string) (uint32, error) {
|
||||
usr, err := user.Lookup(uname)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
uidInt, _ := strconv.Atoi(usr.Gid)
|
||||
return uint32(uidInt), nil
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
// +build linux darwin unix
|
||||
|
||||
package staros
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os/user"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
var clockTicks = 100 // default value
|
||||
|
||||
// StartTime 开机时间
|
||||
func StartTime() time.Time {
|
||||
tmp, _ := readAsString("/proc/stat")
|
||||
data := splitBy(ReplaceByte9(tmp), " ")
|
||||
btime, _ := strconv.ParseInt(strings.TrimSpace(data["btime"]), 10, 64)
|
||||
return time.Unix(btime, 0)
|
||||
}
|
||||
|
||||
// IsRoot 当前是否是管理员用户
|
||||
func IsRoot() bool {
|
||||
uid, _ := user.Current()
|
||||
if uid.Uid == "0" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func Whoami() (uid, gid int, uname, gname, home string, err error) {
|
||||
var me *user.User
|
||||
var gup *user.Group
|
||||
me, err = user.Current()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
uid, _ = strconv.Atoi(me.Uid)
|
||||
gid, _ = strconv.Atoi(me.Uid)
|
||||
home = me.HomeDir
|
||||
uname = me.Username
|
||||
gup, err = user.LookupGroupId(me.Gid)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
gname = gup.Name
|
||||
return
|
||||
}
|
||||
|
||||
func getCPUSample() (idle, total uint64) {
|
||||
contents, err := ioutil.ReadFile("/proc/stat")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
lines := strings.Split(string(contents), "\n")
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
if fields[0] == "cpu" {
|
||||
numFields := len(fields)
|
||||
for i := 1; i < numFields; i++ {
|
||||
val, err := strconv.ParseUint(fields[i], 10, 64)
|
||||
if err != nil {
|
||||
fmt.Println("Error: ", i, fields[i], err)
|
||||
}
|
||||
total += val // tally up all the numbers to get total ticks
|
||||
if i == 4 || i == 5 { // idle is the 5th field in the cpu line
|
||||
idle += val
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func splitProcStat(content []byte) []string {
|
||||
nameStart := bytes.IndexByte(content, '(')
|
||||
nameEnd := bytes.LastIndexByte(content, ')')
|
||||
restFields := strings.Fields(string(content[nameEnd+2:])) // +2 skip ') '
|
||||
name := content[nameStart+1 : nameEnd]
|
||||
pid := strings.TrimSpace(string(content[:nameStart]))
|
||||
fields := make([]string, 3, len(restFields)+3)
|
||||
fields[1] = string(pid)
|
||||
fields[2] = string(name)
|
||||
fields = append(fields, restFields...)
|
||||
return fields
|
||||
}
|
||||
|
||||
func getCPUSampleByPid(pid int) float64 {
|
||||
contents, err := ioutil.ReadFile("/proc/" + strconv.Itoa(pid) + "/stat")
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
fields := splitProcStat(contents)
|
||||
utime, err := strconv.ParseFloat(fields[14], 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
stime, err := strconv.ParseFloat(fields[15], 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
// There is no such thing as iotime in stat file. As an approximation, we
|
||||
// will use delayacct_blkio_ticks (aggregated block I/O delays, as per Linux
|
||||
// docs). Note: I am assuming at least Linux 2.6.18
|
||||
var iotime float64
|
||||
if len(fields) > 42 {
|
||||
iotime, err = strconv.ParseFloat(fields[42], 64)
|
||||
if err != nil {
|
||||
iotime = 0 // Ancient linux version, most likely
|
||||
}
|
||||
} else {
|
||||
iotime = 0 // e.g. SmartOS containers
|
||||
}
|
||||
return utime/float64(clockTicks) + stime/float64(clockTicks) + iotime/float64(clockTicks)
|
||||
}
|
||||
func CpuUsageByPid(pid int, sleep time.Duration) float64 {
|
||||
total1 := getCPUSampleByPid(pid)
|
||||
time.Sleep(sleep)
|
||||
total2 := getCPUSampleByPid(pid)
|
||||
return (total2 - total1) / sleep.Seconds() * 100
|
||||
}
|
||||
|
||||
// CpuUsage 获取CPU使用量
|
||||
func CpuUsage(sleep time.Duration) float64 {
|
||||
idle0, total0 := getCPUSample()
|
||||
time.Sleep(sleep)
|
||||
idle1, total1 := getCPUSample()
|
||||
idleTicks := float64(idle1 - idle0)
|
||||
totalTicks := float64(total1 - total0)
|
||||
cpuUsage := 100 * (totalTicks - idleTicks) / totalTicks
|
||||
return cpuUsage
|
||||
//fmt.Printf("CPU usage is %f%% [busy: %f, total: %f]\n", cpuUsage, totalTicks-idleTicks, totalTicks)
|
||||
}
|
||||
|
||||
func DiskUsage(path string) (disk DiskStatus) {
|
||||
fs := syscall.Statfs_t{}
|
||||
err := syscall.Statfs(path, &fs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
disk.All = fs.Blocks * uint64(fs.Bsize)
|
||||
disk.Free = fs.Bfree * uint64(fs.Bsize)
|
||||
disk.Available = fs.Bavail * uint64(fs.Bsize)
|
||||
disk.Used = disk.All - disk.Free
|
||||
return
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
// +build windows
|
||||
|
||||
package staros
|
||||
|
||||
import (
|
||||
"log"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"b612.me/wincmd"
|
||||
|
||||
"b612.me/win32api"
|
||||
)
|
||||
|
||||
// StartTime 开机时间
|
||||
func StartTime() time.Time {
|
||||
data, _ := win32api.GetTickCount()
|
||||
date := float64(time.Now().Unix())
|
||||
unix := date - float64(data)/1000
|
||||
max := (unix - float64(int64(unix))) * 1000000000
|
||||
return time.Unix(int64(unix), int64(max))
|
||||
}
|
||||
|
||||
// IsRoot 当前是否是管理员用户
|
||||
func IsRoot() bool {
|
||||
return wincmd.Isas()
|
||||
}
|
||||
|
||||
|
||||
func DiskUsage(path string) (disk DiskStatus) {
|
||||
kernel32, err := syscall.LoadLibrary("Kernel32.dll")
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
defer syscall.FreeLibrary(kernel32)
|
||||
GetDiskFreeSpaceEx, err := syscall.GetProcAddress(syscall.Handle(kernel32), "GetDiskFreeSpaceExW")
|
||||
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
lpFreeBytesAvailable := int64(0)
|
||||
lpTotalNumberOfBytes := int64(0)
|
||||
lpTotalNumberOfFreeBytes := int64(0)
|
||||
syscall.Syscall6(uintptr(GetDiskFreeSpaceEx), 4,
|
||||
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("C:"))),
|
||||
uintptr(unsafe.Pointer(&lpFreeBytesAvailable)),
|
||||
uintptr(unsafe.Pointer(&lpTotalNumberOfBytes)),
|
||||
uintptr(unsafe.Pointer(&lpTotalNumberOfFreeBytes)), 0, 0)
|
||||
disk.Free = uint64(lpTotalNumberOfFreeBytes)
|
||||
disk.Used = uint64(lpTotalNumberOfBytes - lpTotalNumberOfFreeBytes)
|
||||
disk.All = uint64(lpTotalNumberOfBytes)
|
||||
disk.Available = uint64(lpFreeBytesAvailable)
|
||||
return
|
||||
}
|
||||
|
||||
func CpuUsage(sleep time.Duration) float64 {
|
||||
return 0
|
||||
}
|
@ -0,0 +1,347 @@
|
||||
package staros
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
//StarCmd Is Here
|
||||
|
||||
type StarCmd struct {
|
||||
CMD *exec.Cmd
|
||||
outfile io.ReadCloser
|
||||
infile io.WriteCloser
|
||||
errfile io.ReadCloser
|
||||
running int32
|
||||
//Store AlL of the Standed Outputs
|
||||
stdout []byte
|
||||
//Store All of the Standed Errors
|
||||
errout []byte
|
||||
runerr error
|
||||
exitcode int
|
||||
stdoutBuf *bytes.Buffer
|
||||
stderrBuf *bytes.Buffer
|
||||
stdoutpoint int
|
||||
stderrpoint int
|
||||
lock sync.Mutex
|
||||
prewrite []string
|
||||
prewritetime time.Duration
|
||||
stopctxfunc context.CancelFunc
|
||||
stopctx context.Context
|
||||
}
|
||||
|
||||
func Command(command string, args ...string) (*StarCmd, error) {
|
||||
var err error
|
||||
shell := new(StarCmd)
|
||||
shell.running = 0
|
||||
shell.prewritetime = time.Millisecond * 200
|
||||
shell.stdoutBuf = bytes.NewBuffer(make([]byte, 0))
|
||||
shell.stderrBuf = bytes.NewBuffer(make([]byte, 0))
|
||||
shell.stopctx, shell.stopctxfunc = context.WithCancel(context.Background())
|
||||
cmd := exec.Command(command, args...)
|
||||
shell.CMD = cmd
|
||||
shell.infile, err = shell.CMD.StdinPipe()
|
||||
if err != nil {
|
||||
return shell, err
|
||||
}
|
||||
shell.errfile, err = shell.CMD.StderrPipe()
|
||||
if err != nil {
|
||||
return shell, err
|
||||
}
|
||||
shell.outfile, err = shell.CMD.StdoutPipe()
|
||||
if err != nil {
|
||||
return shell, err
|
||||
}
|
||||
shell.runerr = nil
|
||||
shell.exitcode = -999
|
||||
return shell, nil
|
||||
}
|
||||
func CommandContext(ctx context.Context, command string, args ...string) (*StarCmd, error) {
|
||||
var err error
|
||||
shell := new(StarCmd)
|
||||
shell.running = 0
|
||||
shell.stdoutBuf = bytes.NewBuffer(make([]byte, 0))
|
||||
shell.stderrBuf = bytes.NewBuffer(make([]byte, 0))
|
||||
shell.prewritetime = time.Millisecond * 200
|
||||
shell.stopctx, shell.stopctxfunc = context.WithCancel(context.Background())
|
||||
cmd := exec.CommandContext(ctx, command, args...)
|
||||
shell.CMD = cmd
|
||||
shell.infile, err = shell.CMD.StdinPipe()
|
||||
if err != nil {
|
||||
return shell, err
|
||||
}
|
||||
shell.errfile, err = shell.CMD.StderrPipe()
|
||||
if err != nil {
|
||||
return shell, err
|
||||
}
|
||||
shell.outfile, err = shell.CMD.StdoutPipe()
|
||||
if err != nil {
|
||||
return shell, err
|
||||
}
|
||||
shell.runerr = nil
|
||||
shell.exitcode = -999
|
||||
return shell, nil
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) queryStdout(ctx context.Context) {
|
||||
for starcli.IsRunning() && starcli.CMD != nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
out := make([]byte, 65535)
|
||||
n, err := starcli.outfile.Read(out)
|
||||
if n != 0 {
|
||||
starcli.lock.Lock()
|
||||
starcli.stdoutBuf.Write(out[:n])
|
||||
starcli.lock.Unlock()
|
||||
for _, v := range out[:n] {
|
||||
starcli.stdout = append(starcli.stdout, v)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else {
|
||||
if !strings.Contains(err.Error(), "file already closed") {
|
||||
starcli.runerr = err
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) queryStderr(ctx context.Context) {
|
||||
for starcli.IsRunning() && starcli.CMD != nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
out := make([]byte, 65535)
|
||||
n, err := starcli.errfile.Read(out)
|
||||
if n != 0 {
|
||||
starcli.lock.Lock()
|
||||
starcli.stderrBuf.Write(out[:n])
|
||||
starcli.lock.Unlock()
|
||||
for _, v := range out[:n] {
|
||||
starcli.errout = append(starcli.errout, v)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else {
|
||||
if !strings.Contains(err.Error(), "file already closed") {
|
||||
starcli.runerr = err
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func (starcli *StarCmd) NowLineOutput() (string, error) {
|
||||
starcli.lock.Lock()
|
||||
buf, _ := starcli.stdoutBuf.ReadBytes('\n')
|
||||
buferr, _ := starcli.stderrBuf.ReadBytes(byte('\n'))
|
||||
starcli.lock.Unlock()
|
||||
if len(buferr) != 0 {
|
||||
return string(buf), errors.New(string(buferr))
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) NowLineStdOut() string {
|
||||
starcli.lock.Lock()
|
||||
defer starcli.lock.Unlock()
|
||||
buf, _ := starcli.stdoutBuf.ReadBytes('\n')
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) NowLineStdErr() error {
|
||||
starcli.lock.Lock()
|
||||
defer starcli.lock.Unlock()
|
||||
buferr, _ := starcli.stderrBuf.ReadBytes(byte('\n'))
|
||||
if len(buferr) != 0 {
|
||||
return errors.New(string(buferr))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) NowAllOutput() (string, error) {
|
||||
var outstr string
|
||||
starcli.lock.Lock()
|
||||
buf := make([]byte, starcli.stdoutBuf.Len())
|
||||
n, _ := starcli.stdoutBuf.Read(buf)
|
||||
starcli.lock.Unlock()
|
||||
if n != 0 {
|
||||
outstr = string(buf[:n])
|
||||
}
|
||||
if starcli.runerr != nil {
|
||||
return outstr, starcli.runerr
|
||||
}
|
||||
starcli.lock.Lock()
|
||||
buf = make([]byte, starcli.stderrBuf.Len())
|
||||
n, _ = starcli.stderrBuf.Read(buf)
|
||||
starcli.lock.Unlock()
|
||||
if n != 0 {
|
||||
return outstr, errors.New(string(buf[:n]))
|
||||
}
|
||||
return outstr, nil
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) NowStdOut() string {
|
||||
var outstr string
|
||||
starcli.lock.Lock()
|
||||
buf := make([]byte, starcli.stdoutBuf.Len())
|
||||
n, _ := starcli.stdoutBuf.Read(buf)
|
||||
starcli.lock.Unlock()
|
||||
if n != 0 {
|
||||
outstr = string(buf[:n])
|
||||
}
|
||||
return outstr
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) NowStdErr() error {
|
||||
starcli.lock.Lock()
|
||||
buf := make([]byte, starcli.stderrBuf.Len())
|
||||
n, _ := starcli.stderrBuf.Read(buf)
|
||||
starcli.lock.Unlock()
|
||||
if n != 0 {
|
||||
return errors.New(string(buf[:n]))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) AllOutPut() (string, error) {
|
||||
err := starcli.runerr
|
||||
if err == nil && len(starcli.errout) != 0 {
|
||||
err = errors.New(string(starcli.errout))
|
||||
}
|
||||
return string(starcli.stdout), err
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) AllStdOut() string {
|
||||
return string(starcli.stdout)
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) AllStdErr() error {
|
||||
err := starcli.runerr
|
||||
if err == nil && len(starcli.errout) != 0 {
|
||||
err = errors.New(string(starcli.errout))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) setRunning(alive bool) {
|
||||
if alive {
|
||||
val := atomic.LoadInt32(&starcli.running)
|
||||
if val == 0 {
|
||||
atomic.AddInt32(&starcli.running, 1)
|
||||
} else {
|
||||
atomic.AddInt32(&starcli.running, 1-val)
|
||||
}
|
||||
return
|
||||
}
|
||||
val := atomic.LoadInt32(&starcli.running)
|
||||
if val == 1 {
|
||||
atomic.AddInt32(&starcli.running, -1)
|
||||
} else {
|
||||
atomic.AddInt32(&starcli.running, -val)
|
||||
}
|
||||
}
|
||||
func (starcli *StarCmd) Start() error {
|
||||
if err := starcli.CMD.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
starcli.setRunning(true)
|
||||
go func() {
|
||||
err := starcli.CMD.Wait()
|
||||
if err != nil {
|
||||
starcli.runerr = err
|
||||
}
|
||||
starcli.stopctxfunc()
|
||||
starcli.setRunning(false)
|
||||
if starcli.CMD.ProcessState != nil {
|
||||
starcli.exitcode = starcli.CMD.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
|
||||
}
|
||||
}()
|
||||
go starcli.queryStdout(starcli.stopctx)
|
||||
go starcli.queryStderr(starcli.stopctx)
|
||||
go func(ctx context.Context) {
|
||||
if len(starcli.prewrite) != 0 {
|
||||
for _, v := range starcli.prewrite {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
break
|
||||
}
|
||||
starcli.WriteCmd(v)
|
||||
time.Sleep(starcli.prewritetime)
|
||||
}
|
||||
}
|
||||
}(starcli.stopctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) IsRunning() bool {
|
||||
return 0 != atomic.LoadInt32(&starcli.running)
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) Stoped() <-chan struct{} {
|
||||
return starcli.stopctx.Done()
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) Exec(cmd string, wait int) (string, error) {
|
||||
starcli.infile.Write([]byte(cmd + "\n"))
|
||||
time.Sleep(time.Millisecond * time.Duration(wait))
|
||||
return starcli.NowAllOutput()
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) WriteCmd(cmdstr string) {
|
||||
starcli.infile.Write([]byte(cmdstr + "\n"))
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) PreWrite(cmd ...string) {
|
||||
for _, v := range cmd {
|
||||
starcli.prewrite = append(starcli.prewrite, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) PreWriteInterval(dt time.Duration) {
|
||||
starcli.prewritetime = dt
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) ExitCode() int {
|
||||
return starcli.exitcode
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) Kill() error {
|
||||
err := starcli.CMD.Process.Kill()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
starcli.setRunning(false)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) GetPid() int {
|
||||
return starcli.CMD.Process.Pid
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) Signal(sig os.Signal) error {
|
||||
return starcli.CMD.Process.Signal(sig)
|
||||
}
|
@ -0,0 +1,348 @@
|
||||
// +build linux darwin
|
||||
|
||||
package staros
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
//FindProcessByName 通过进程名来查询应用信息
|
||||
func FindProcessByName(name string) (datas []Process, err error) {
|
||||
return FindProcess(func(in Process) bool {
|
||||
if name == in.Name {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
// FindProcess 通过进程信息来查询应用信息
|
||||
func FindProcess(compare func(Process) bool) (datas []Process, err error) {
|
||||
var name, main string
|
||||
var mainb []byte
|
||||
var netErr error
|
||||
var netInfo []NetConn
|
||||
paths, err := ioutil.ReadDir("/proc")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
netInfo, netErr = NetConnections(false, "")
|
||||
appendNetInfo := func(p *Process) {
|
||||
if netErr != nil {
|
||||
p.netErr = netErr
|
||||
return
|
||||
}
|
||||
fds, err := ioutil.ReadDir("/proc/" + strconv.Itoa(int(p.Pid)) + "/fd")
|
||||
if err != nil && Exists("/proc/"+strconv.Itoa(int(p.Pid))+"/fd") {
|
||||
p.netErr = err
|
||||
return
|
||||
}
|
||||
for _, fd := range fds {
|
||||
socket, err := os.Readlink("/proc/" + strconv.Itoa(int(p.Pid)) + "/fd/" + fd.Name())
|
||||
if err != nil {
|
||||
p.netErr = err
|
||||
return
|
||||
}
|
||||
start := strings.Index(socket, "[")
|
||||
if start < 0 {
|
||||
continue
|
||||
}
|
||||
sid := socket[start+1 : len(socket)-1]
|
||||
for _, v := range netInfo {
|
||||
if v.Inode == sid {
|
||||
v.Pid = p.Pid
|
||||
v.Process = p
|
||||
p.netConn = append(p.netConn, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, v := range paths {
|
||||
if v.IsDir() && Exists("/proc/"+v.Name()+"/comm") {
|
||||
name, err = readAsString("/proc/" + v.Name() + "/comm")
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var tmp Process
|
||||
tmp.LocalPath, err = os.Readlink("/proc/" + v.Name() + "/exe")
|
||||
tmp.Path = tmp.LocalPath
|
||||
tmp.LocalPath = filepath.Dir(tmp.LocalPath)
|
||||
tmp.ExecPath, err = os.Readlink("/proc/" + v.Name() + "/cwd")
|
||||
tmp.Name = strings.TrimSpace(name)
|
||||
main, err = readAsString("/proc/" + v.Name() + "/status")
|
||||
if err != nil {
|
||||
tmp.Err = err
|
||||
if compare(tmp) {
|
||||
appendNetInfo(&tmp)
|
||||
datas = append(datas, tmp)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
data := splitBy(main, ":")
|
||||
tmp.Pid, _ = strconv.ParseInt(data["Pid"], 10, 64)
|
||||
tmp.PPid, _ = strconv.ParseInt(data["PPid"], 10, 64)
|
||||
tmp.TPid, _ = strconv.ParseInt(data["TracerPid"], 10, 64)
|
||||
uids := splitBySpace(data["Uid"])
|
||||
gids := splitBySpace(data["Gid"])
|
||||
tmp.RUID, _ = strconv.Atoi(uids[0])
|
||||
tmp.EUID, _ = strconv.Atoi(uids[1])
|
||||
tmp.RGID, _ = strconv.Atoi(gids[0])
|
||||
tmp.EGID, _ = strconv.Atoi(gids[1])
|
||||
tmp.VmPeak, _ = strconv.ParseInt(splitBySpace(data["VmPeak"])[0], 10, 64)
|
||||
tmp.VmSize, _ = strconv.ParseInt(splitBySpace(data["VmSize"])[0], 10, 64)
|
||||
tmp.VmHWM, _ = strconv.ParseInt(splitBySpace(data["VmHWM"])[0], 10, 64)
|
||||
tmp.VmRSS, _ = strconv.ParseInt(splitBySpace(data["VmRSS"])[0], 10, 64)
|
||||
tmp.VmLck, _ = strconv.ParseInt(splitBySpace(data["VmLck"])[0], 10, 64)
|
||||
tmp.VmData, _ = strconv.ParseInt(splitBySpace(data["VmData"])[0], 10, 64)
|
||||
tmp.VmLck *= 1024
|
||||
tmp.VmData *= 1024
|
||||
tmp.VmPeak *= 1024
|
||||
tmp.VmSize *= 1024
|
||||
tmp.VmHWM *= 1024
|
||||
tmp.VmRSS *= 1024
|
||||
}
|
||||
mainb, err = ioutil.ReadFile("/proc/" + v.Name() + "/cmdline")
|
||||
if err != nil {
|
||||
tmp.Err = err
|
||||
if compare(tmp) {
|
||||
appendNetInfo(&tmp)
|
||||
datas = append(datas, tmp)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
args := bytes.Split(mainb, []byte{0})
|
||||
for _, v := range args {
|
||||
tmp.Args = append(tmp.Args, string(v))
|
||||
}
|
||||
}
|
||||
mainb, err = ioutil.ReadFile("/proc/" + v.Name() + "/environ")
|
||||
if err != nil {
|
||||
tmp.Err = err
|
||||
if compare(tmp) {
|
||||
appendNetInfo(&tmp)
|
||||
datas = append(datas, tmp)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
args := bytes.Split(mainb, []byte{0})
|
||||
for _, v := range args {
|
||||
tmp.Env = append(tmp.Env, string(v))
|
||||
}
|
||||
}
|
||||
|
||||
main, err = readAsString("/proc/" + v.Name() + "/stat")
|
||||
if err != nil {
|
||||
tmp.Err = err
|
||||
if compare(tmp) {
|
||||
appendNetInfo(&tmp)
|
||||
datas = append(datas, tmp)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
times := splitBySpace(main)
|
||||
uptime, _ := strconv.ParseInt(strings.TrimSpace(times[21]), 10, 64)
|
||||
tmp.Uptime = time.Unix(StartTime().Unix()+uptime/100, int64((float64(uptime)/100-float64(uptime/100))*1000000000))
|
||||
}
|
||||
if compare(tmp) {
|
||||
appendNetInfo(&tmp)
|
||||
datas = append(datas, tmp)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FindProcessByPid 通过Pid来查询应用信息
|
||||
func FindProcessByPid(pid int64) (datas Process, err error) {
|
||||
var name, main string
|
||||
var mainb []byte
|
||||
if !Exists("/proc/" + fmt.Sprint(pid) + "/comm") {
|
||||
err = errors.New("Not Found")
|
||||
return
|
||||
}
|
||||
netInfo, netErr := NetConnections(false, "")
|
||||
appendNetInfo := func(p *Process) {
|
||||
if netErr != nil {
|
||||
p.netErr = netErr
|
||||
return
|
||||
}
|
||||
fds, err := ioutil.ReadDir("/proc/" + strconv.Itoa(int(p.Pid)) + "/fd")
|
||||
if err != nil && Exists("/proc/"+strconv.Itoa(int(p.Pid))+"/fd") {
|
||||
p.netErr = err
|
||||
return
|
||||
}
|
||||
for _, fd := range fds {
|
||||
socket, err := os.Readlink("/proc/" + strconv.Itoa(int(p.Pid)) + "/fd/" + fd.Name())
|
||||
if err != nil {
|
||||
p.netErr = err
|
||||
return
|
||||
}
|
||||
start := strings.Index(socket, "[")
|
||||
if start < 0 {
|
||||
continue
|
||||
}
|
||||
sid := socket[start+1 : len(socket)-1]
|
||||
for _, v := range netInfo {
|
||||
if v.Inode == sid {
|
||||
v.Pid = p.Pid
|
||||
v.Process = p
|
||||
p.netConn = append(p.netConn, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
name, err = readAsString("/proc/" + fmt.Sprint(pid) + "/comm")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
main, err = readAsString("/proc/" + fmt.Sprint(pid) + "/status")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data := splitBy(main, ":")
|
||||
datas.Name = strings.TrimSpace(name)
|
||||
datas.Pid, _ = strconv.ParseInt(data["Pid"], 10, 64)
|
||||
datas.PPid, _ = strconv.ParseInt(data["PPid"], 10, 64)
|
||||
datas.TPid, _ = strconv.ParseInt(data["TracerPid"], 10, 64)
|
||||
uids := splitBySpace(data["Uid"])
|
||||
gids := splitBySpace(data["Gid"])
|
||||
datas.RUID, _ = strconv.Atoi(uids[0])
|
||||
datas.EUID, _ = strconv.Atoi(uids[1])
|
||||
datas.RGID, _ = strconv.Atoi(gids[0])
|
||||
datas.EGID, _ = strconv.Atoi(gids[1])
|
||||
datas.VmPeak, _ = strconv.ParseInt(splitBySpace(data["VmPeak"])[0], 10, 64)
|
||||
datas.VmSize, _ = strconv.ParseInt(splitBySpace(data["VmSize"])[0], 10, 64)
|
||||
datas.VmHWM, _ = strconv.ParseInt(splitBySpace(data["VmHWM"])[0], 10, 64)
|
||||
datas.VmRSS, _ = strconv.ParseInt(splitBySpace(data["VmRSS"])[0], 10, 64)
|
||||
datas.VmLck, _ = strconv.ParseInt(splitBySpace(data["VmLck"])[0], 10, 64)
|
||||
datas.VmData, _ = strconv.ParseInt(splitBySpace(data["VmData"])[0], 10, 64)
|
||||
datas.VmLck *= 1024
|
||||
datas.VmData *= 1024
|
||||
datas.VmPeak *= 1024
|
||||
datas.VmSize *= 1024
|
||||
datas.VmHWM *= 1024
|
||||
datas.VmRSS *= 1024
|
||||
appendNetInfo(&datas)
|
||||
mainb, err = ioutil.ReadFile("/proc/" + fmt.Sprint(pid) + "/cmdline")
|
||||
if err != nil {
|
||||
datas.Err = err
|
||||
err = nil
|
||||
} else {
|
||||
args := bytes.Split(mainb, []byte{0})
|
||||
for _, v := range args {
|
||||
datas.Args = append(datas.Args, string(v))
|
||||
}
|
||||
}
|
||||
|
||||
mainb, err = ioutil.ReadFile("/proc/" + fmt.Sprint(pid) + "/environ")
|
||||
if err != nil {
|
||||
datas.Err = err
|
||||
err = nil
|
||||
} else {
|
||||
args := bytes.Split(mainb, []byte{0})
|
||||
for _, v := range args {
|
||||
datas.Env = append(datas.Env, string(v))
|
||||
}
|
||||
}
|
||||
|
||||
datas.LocalPath, err = os.Readlink("/proc/" + fmt.Sprint(pid) + "/exe")
|
||||
datas.Path = datas.LocalPath
|
||||
datas.LocalPath = filepath.Dir(datas.LocalPath)
|
||||
datas.ExecPath, err = os.Readlink("/proc/" + fmt.Sprint(pid) + "/cwd")
|
||||
main, err = readAsString("/proc/" + fmt.Sprint(pid) + "/stat")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
times := splitBySpace(main)
|
||||
uptime, _ := strconv.ParseInt(strings.TrimSpace(times[21]), 10, 64)
|
||||
datas.Uptime = time.Unix(StartTime().Unix()+uptime/100, int64((float64(uptime)/100-float64(uptime/100))*1000000000))
|
||||
return
|
||||
}
|
||||
|
||||
func Daemon(path string, args ...string) (int, error) {
|
||||
cmd := exec.Command(path, args...)
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setsid: true,
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
pid := cmd.Process.Pid
|
||||
err := cmd.Process.Release()
|
||||
return pid, err
|
||||
}
|
||||
|
||||
func DaemonWithUser(uid, gid uint32, groups []uint32, path string, args ...string) (int, error) {
|
||||
cmd := exec.Command(path, args...)
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Credential: &syscall.Credential{
|
||||
Uid: uid,
|
||||
Gid: gid,
|
||||
Groups: groups,
|
||||
},
|
||||
Setsid: true,
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
pid := cmd.Process.Pid
|
||||
err := cmd.Process.Release()
|
||||
return pid, err
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) SetRunUser(uid, gid uint32, groups []uint32) {
|
||||
starcli.CMD.SysProcAttr = &syscall.SysProcAttr{
|
||||
Credential: &syscall.Credential{
|
||||
Uid: uid,
|
||||
Gid: gid,
|
||||
Groups: groups,
|
||||
},
|
||||
Setsid: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) Release() error {
|
||||
if starcli.CMD.SysProcAttr == nil {
|
||||
starcli.CMD.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setsid: true,
|
||||
}
|
||||
} else {
|
||||
if !starcli.CMD.SysProcAttr.Setsid {
|
||||
starcli.CMD.SysProcAttr.Setsid = true
|
||||
}
|
||||
}
|
||||
if !starcli.IsRunning() {
|
||||
if err := starcli.CMD.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
return starcli.CMD.Process.Release()
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) SetKeepCaps() error {
|
||||
_, _, err := syscall.RawSyscall(157 /*SYS PRCTL */, 0x8 /*PR SET KEEPCAPS*/, 1, 0)
|
||||
if 0 != err {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetKeepCaps() error {
|
||||
_, _, err := syscall.RawSyscall(157 /*SYS PRCTL */, 0x8 /*PR SET KEEPCAPS*/, 1, 0)
|
||||
if 0 != err {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
// +build windows
|
||||
|
||||
package staros
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
|
||||
"b612.me/wincmd"
|
||||
)
|
||||
|
||||
// FindProcessByName 通过进程名来查询应用信息
|
||||
func FindProcessByName(pname string) (data []Process, err error) {
|
||||
var lists []map[string]string
|
||||
lists, err = wincmd.GetRunningProcess()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, v := range lists {
|
||||
if v["name"] == pname {
|
||||
var tmp Process
|
||||
tmp.Name = pname
|
||||
tmp.Pid, _ = strconv.ParseInt(v["pid"], 10, 64)
|
||||
tmp.PPid, _ = strconv.ParseInt(v["ppid"], 10, 64)
|
||||
data = append(data, tmp)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// FindProcessByPid 通过pid来查询应用信息
|
||||
func FindProcessByPid(pid int64) (data Process, err error) {
|
||||
var lists []map[string]string
|
||||
lists, err = wincmd.GetRunningProcess()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, v := range lists {
|
||||
if v["pid"] == fmt.Sprint(pid) {
|
||||
data.Name = v["name"]
|
||||
data.Pid = pid
|
||||
data.PPid, _ = strconv.ParseInt(v["ppid"], 10, 64)
|
||||
return
|
||||
}
|
||||
}
|
||||
err = errors.New("Not Found")
|
||||
return
|
||||
}
|
||||
|
||||
func Daemon(path string, args ...string) (int, error) {
|
||||
cmd := exec.Command(path, args...)
|
||||
if err := cmd.Start(); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
pid := cmd.Process.Pid
|
||||
cmd.Process.Release()
|
||||
return pid, nil
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) SetRunUser(uid, gid uint32, groups []uint32) {
|
||||
|
||||
}
|
||||
|
||||
func (starcli *StarCmd) Release() error {
|
||||
if err := starcli.CMD.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
starcli.CMD.Process.Release()
|
||||
return nil
|
||||
}
|
@ -0,0 +1 @@
|
||||
package staros
|
@ -0,0 +1,159 @@
|
||||
package staros
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func splitBy(data, sep string) map[string]string {
|
||||
res := make(map[string]string)
|
||||
lists := strings.Split(data, "\n")
|
||||
for _, v := range lists {
|
||||
list := strings.SplitN(v, sep, 2)
|
||||
if len(list) != 2 {
|
||||
continue
|
||||
}
|
||||
res[strings.TrimSpace(list[0])] = strings.TrimSpace(list[1])
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// 横向替换ASCII<9>
|
||||
func ReplaceByte9(data string) string {
|
||||
return string(bytes.ReplaceAll([]byte(data), []byte{9}, []byte(" ")))
|
||||
}
|
||||
|
||||
func splitBySpace(data string) []string {
|
||||
data = string(bytes.ReplaceAll([]byte(data), []byte{9}, []byte(" ")))
|
||||
nomorespace := func(data string) string {
|
||||
return strings.ReplaceAll(data, " ", " ")
|
||||
}
|
||||
for strings.Index(data, " ") >= 0 {
|
||||
data = nomorespace(data)
|
||||
}
|
||||
return strings.Split(data, " ")
|
||||
}
|
||||
|
||||
func readAsString(path string) (string, error) {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func remainOne(data, old, new string) string {
|
||||
data = strings.TrimSpace(data)
|
||||
if !strings.Contains(data, old) {
|
||||
return data
|
||||
}
|
||||
data = strings.ReplaceAll(data, old, new)
|
||||
return remainOne(data, old, new)
|
||||
}
|
||||
|
||||
func parseHexIpPort(str string) (string, int, error) {
|
||||
str = strings.TrimSpace(str)
|
||||
if len(str) != 13 && len(str) != 37 {
|
||||
return "", 0, errors.New("Not a valid ip:port addr:" + str)
|
||||
}
|
||||
ipPort := strings.Split(str, ":")
|
||||
if len(ipPort) != 2 {
|
||||
return "", 0, errors.New("Not a valid ip:port addr:" + str)
|
||||
}
|
||||
if len(ipPort[0]) == 8 {
|
||||
ip, err := parseHexIPv4(ipPort[0])
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
port, err := parseHexPort(ipPort[1])
|
||||
return ip, port, err
|
||||
}
|
||||
|
||||
if len(ipPort[0]) == 32 {
|
||||
ip, err := parseHexIPv6(ipPort[0])
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
port, err := parseHexPort(ipPort[1])
|
||||
return ip, port, err
|
||||
}
|
||||
return "", 0, errors.New("Invalid ip address:" + str)
|
||||
}
|
||||
|
||||
func parseHexPort(str string) (int, error) {
|
||||
tmpUint32, err := strconv.ParseUint(str, 16, 32)
|
||||
return int(tmpUint32), err
|
||||
}
|
||||
|
||||
func parseHexIPv4(str string) (string, error) {
|
||||
var result string
|
||||
if len(str) != 8 {
|
||||
return "", errors.New("Not a vaild ipv4:" + str)
|
||||
}
|
||||
tmpUint64, err := strconv.ParseUint(str, 16, 32)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
numicIp := uint32(tmpUint64)
|
||||
for i := 0; i < 4; i++ {
|
||||
result += strconv.FormatUint(uint64(uint8(numicIp>>(8*uint8(i)))), 10) + "."
|
||||
}
|
||||
return result[0 : len(result)-1], nil
|
||||
}
|
||||
|
||||
func parseHexIPv6(str string) (string, error) {
|
||||
var result string
|
||||
if len(str) != 32 {
|
||||
return "", errors.New("Not a vaild ipv6:" + str)
|
||||
}
|
||||
for i := 0; i < 4; i++ {
|
||||
part := str[i*8 : (i+1)*8]
|
||||
tmpUint64, err := strconv.ParseUint(part, 16, 32)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tmpUint32 := uint32(tmpUint64)
|
||||
//07C2022A
|
||||
for i := 0; i < 4; i++ {
|
||||
tmp := strconv.FormatUint(uint64(uint8(tmpUint32>>uint8(8*i))), 16)
|
||||
if len(tmp) == 1 {
|
||||
tmp = "0" + tmp
|
||||
}
|
||||
result += tmp
|
||||
if (i+1)%2 == 0 {
|
||||
result += ":"
|
||||
}
|
||||
}
|
||||
}
|
||||
ipv6 := result[0 : len(result)-1]
|
||||
ipv6List := strings.Split(ipv6, ":")
|
||||
prepareZero := false
|
||||
alreadyZero := false
|
||||
for k, v := range ipv6List {
|
||||
if v == "0000" && !alreadyZero {
|
||||
ipv6List[k] = ""
|
||||
prepareZero = true
|
||||
continue
|
||||
}
|
||||
if v != "0000" && prepareZero {
|
||||
alreadyZero = true
|
||||
}
|
||||
var nonZero = 0
|
||||
for i := 0; i < 4; i++ {
|
||||
sig := v[i : i+1]
|
||||
if sig != "0" {
|
||||
nonZero = i
|
||||
break
|
||||
}
|
||||
}
|
||||
ipv6List[k] = v[nonZero:4]
|
||||
}
|
||||
ipv6 = strings.TrimSuffix(remainOne(strings.Join(ipv6List, ":"), ":::", "::"), "::")
|
||||
if ipv6 == "" {
|
||||
ipv6 = "::0"
|
||||
}
|
||||
return ipv6, nil
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
package staros
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
KB = 1024
|
||||
MB = KB << 10
|
||||
GB = MB << 10
|
||||
TB = GB << 10
|
||||
PB = TB << 10
|
||||
)
|
||||
const (
|
||||
TCP_UNKNOWN = iota
|
||||
TCP_ESTABLISHED
|
||||
TCP_SYN_SENT
|
||||
TCP_SYN_RECV
|
||||
TCP_FIN_WAIT1
|
||||
TCP_FIN_WAIT2
|
||||
TCP_TIME_WAIT
|
||||
TCP_CLOSE
|
||||
TCP_CLOSE_WAIT
|
||||
TCP_LAST_ACL
|
||||
TCP_LISTEN
|
||||
TCP_CLOSING
|
||||
)
|
||||
|
||||
var TCP_STATE = []string{"TCP_UNKNOWN", "TCP_ESTABLISHED", "TCP_SYN_SENT", "TCP_SYN_RECV", "TCP_FIN_WAIT1", "TCP_FIN_WAIT2", "TCP_TIME_WAIT", "TCP_CLOSE", "TCP_CLOSE_WAIT", "TCP_LAST_ACL", "TCP_LISTEN", "TCP_CLOSING"}
|
||||
|
||||
type NetAdapter struct {
|
||||
Name string
|
||||
RecvBytes uint64
|
||||
SendBytes uint64
|
||||
}
|
||||
|
||||
type NetSpeed struct {
|
||||
Name string
|
||||
RecvSpeeds float64
|
||||
SendSpeeds float64
|
||||
RecvBytes uint64
|
||||
SendBytes uint64
|
||||
}
|
||||
|
||||
// Process 定义一个进程的信息
|
||||
type Process struct {
|
||||
PPid int64
|
||||
Pid int64
|
||||
Name string
|
||||
ExecPath string
|
||||
LocalPath string
|
||||
Path string
|
||||
Args []string
|
||||
Env []string
|
||||
RUID int
|
||||
EUID int
|
||||
RGID int
|
||||
EGID int
|
||||
TPid int64
|
||||
Uptime time.Time
|
||||
VmPeak int64
|
||||
VmSize int64
|
||||
VmLck int64
|
||||
VmHWM int64
|
||||
VmRSS int64
|
||||
VmData int64
|
||||
netConn []NetConn
|
||||
netErr error
|
||||
Err error
|
||||
}
|
||||
|
||||
func (p Process) GetNetConns() ([]NetConn, error) {
|
||||
return p.netConn, p.netErr
|
||||
}
|
||||
|
||||
type MemStatus struct {
|
||||
All uint64
|
||||
Used uint64
|
||||
Free uint64
|
||||
Shared uint64
|
||||
BuffCache uint64
|
||||
Available uint64
|
||||
SwapAll uint64
|
||||
SwapUsed uint64
|
||||
SwapFree uint64
|
||||
VirtualAll uint64
|
||||
VirtualUsed uint64
|
||||
VirtualAvail uint64
|
||||
AvailExtended uint64
|
||||
}
|
||||
|
||||
type DiskStatus struct {
|
||||
All uint64
|
||||
Used uint64
|
||||
Free uint64
|
||||
Available uint64
|
||||
}
|
||||
|
||||
type NetConn struct {
|
||||
LocalAddr string
|
||||
LocalPort int
|
||||
Typed string
|
||||
RemoteAddr string
|
||||
RemotePort int
|
||||
Socket string
|
||||
Inode string
|
||||
Status string
|
||||
TX_Queue int64
|
||||
RX_Queue int64
|
||||
TimerActive string
|
||||
TimerJiffies int64
|
||||
RtoTimer int64
|
||||
Pid int64
|
||||
Uid int64
|
||||
Process *Process
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package win32api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func DuplicateTokenEx(hExistingToken HANDLE, dwDesiredAccess DWORD,
|
||||
lpTokenAttributes uintptr, ImpersonationLevel int,
|
||||
TokenType TOKEN_TYPE, phNewToken *TOKEN) error {
|
||||
|
||||
advapi32, err := syscall.LoadLibrary("advapi32.dll")
|
||||
if err != nil {
|
||||
return errors.New("Can't Load Advapi32 API")
|
||||
}
|
||||
defer syscall.FreeLibrary(advapi32)
|
||||
Dup, err := syscall.GetProcAddress(syscall.Handle(advapi32), "DuplicateTokenEx")
|
||||
if err != nil {
|
||||
return errors.New("Can't Load WTSQueryUserToken API")
|
||||
}
|
||||
r, _, errno := syscall.Syscall6(uintptr(Dup), 6, uintptr(hExistingToken), uintptr(dwDesiredAccess), lpTokenAttributes, uintptr(ImpersonationLevel),
|
||||
uintptr(TokenType), uintptr(unsafe.Pointer(phNewToken)))
|
||||
if r == 0 {
|
||||
return error(errno)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateProcessAsUser(hToken TOKEN, lpApplicationName, lpCommandLine string,
|
||||
lpProcessAttributes, lpThreadAttributes, bInheritHandles uintptr,
|
||||
dwCreationFlags uint16, lpEnvironment HANDLE, lpCurrentDirectory string,
|
||||
lpStartupInfo *StartupInfo, lpProcessInformation *ProcessInformation) error {
|
||||
var (
|
||||
commandLine uintptr = 0
|
||||
workingDir uintptr = 0
|
||||
)
|
||||
advapi32, err := syscall.LoadLibrary("advapi32.dll")
|
||||
if err != nil {
|
||||
return errors.New("Can't Load Advapi32 API")
|
||||
}
|
||||
defer syscall.FreeLibrary(advapi32)
|
||||
CPAU, err := syscall.GetProcAddress(syscall.Handle(advapi32), "CreateProcessAsUserW")
|
||||
if err != nil {
|
||||
return errors.New("Can't Load CreateProcessAsUserW API")
|
||||
}
|
||||
if len(lpCommandLine) > 0 {
|
||||
commandLine = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(lpCommandLine)))
|
||||
}
|
||||
if len(lpCurrentDirectory) > 0 {
|
||||
workingDir = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(lpCurrentDirectory)))
|
||||
}
|
||||
r, _, errno := syscall.Syscall12(uintptr(CPAU), 11, uintptr(hToken), uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(lpApplicationName))),
|
||||
commandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, uintptr(dwCreationFlags), uintptr(lpEnvironment),
|
||||
workingDir, uintptr(unsafe.Pointer(lpStartupInfo)), uintptr(unsafe.Pointer(lpProcessInformation)), 0)
|
||||
if r == 0 {
|
||||
return error(errno)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func GetTokenInformation(TokenHandle HANDLE, TokenInformationClass, TokenInformation,
|
||||
TokenInformationLength uintptr, ReturnLength *uintptr) error {
|
||||
advapi32, err := syscall.LoadLibrary("advapi32.dll")
|
||||
if err != nil {
|
||||
return errors.New("Can't Load Advapi32 API")
|
||||
}
|
||||
defer syscall.FreeLibrary(advapi32)
|
||||
GTI, err := syscall.GetProcAddress(syscall.Handle(advapi32), "GetTokenInformation")
|
||||
if err != nil {
|
||||
return errors.New("Can't Load GetTokenInformation API")
|
||||
}
|
||||
if r, _, errno := syscall.Syscall6(uintptr(GTI), 5, uintptr(TokenHandle), TokenInformationClass,
|
||||
TokenInformation, TokenInformationLength, uintptr(unsafe.Pointer(ReturnLength)), 0); r == 0 {
|
||||
return error(errno)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package win32api
|
||||
|
||||
type TOKEN_LINKED_TOKEN struct {
|
||||
LinkedToken TOKEN
|
||||
}
|
@ -0,0 +1,284 @@
|
||||
package win32api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func WTSGetActiveConsoleSessionId() (DWORD, error) {
|
||||
kernel32, err := syscall.LoadLibrary("kernel32.dll")
|
||||
if err != nil {
|
||||
return 0, errors.New("Can't Load Kernel32 API")
|
||||
}
|
||||
defer syscall.FreeLibrary(kernel32)
|
||||
WTGet, err := syscall.GetProcAddress(syscall.Handle(kernel32), "WTSGetActiveConsoleSessionId")
|
||||
if err != nil {
|
||||
return 0, errors.New("Can't Load WTSGetActiveConsoleSessionId API")
|
||||
}
|
||||
res, _, _ := syscall.Syscall(uintptr(WTGet), 0, 0, 0, 0)
|
||||
return DWORD(res), nil
|
||||
}
|
||||
|
||||
func CloseHandle(hObject HANDLE) error {
|
||||
kernel32, err := syscall.LoadLibrary("kernel32.dll")
|
||||
if err != nil {
|
||||
return errors.New("Can't Load Kernel32 API")
|
||||
}
|
||||
defer syscall.FreeLibrary(kernel32)
|
||||
CH, err := syscall.GetProcAddress(syscall.Handle(kernel32), "CloseHandle")
|
||||
if err != nil {
|
||||
return errors.New("Can't Load CloseHandle API")
|
||||
}
|
||||
if r, _, errno := syscall.Syscall(uintptr(CH), 1, uintptr(hObject), 0, 0); r == 0 {
|
||||
return error(errno)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateToolhelp32Snapshot(dwFlags, th32ProcessID DWORD) (HANDLE, error) {
|
||||
kernel32, err := syscall.LoadLibrary("kernel32.dll")
|
||||
if err != nil {
|
||||
return 0, errors.New("Can't Load Kernel32 API")
|
||||
}
|
||||
defer syscall.FreeLibrary(kernel32)
|
||||
CTS, err := syscall.GetProcAddress(syscall.Handle(kernel32), "CreateToolhelp32Snapshot")
|
||||
if err != nil {
|
||||
return 0, errors.New("Can't Load CreateToolhelp32Snapshot API")
|
||||
}
|
||||
r, _, errno := syscall.Syscall(uintptr(CTS), 2, uintptr(dwFlags), uintptr(th32ProcessID), 0)
|
||||
if int(r) == -1 {
|
||||
return 0, error(errno)
|
||||
}
|
||||
return HANDLE(r), nil
|
||||
}
|
||||
|
||||
func Process32Next(hSnapshot HANDLE, lppe *PROCESSENTRY32) error {
|
||||
kernel32, err := syscall.LoadLibrary("kernel32.dll")
|
||||
if err != nil {
|
||||
return errors.New("Can't Load Kernel32 API")
|
||||
}
|
||||
defer syscall.FreeLibrary(kernel32)
|
||||
PN, err := syscall.GetProcAddress(syscall.Handle(kernel32), "Process32Next")
|
||||
if err != nil {
|
||||
return errors.New("Can't Load Process32Next API")
|
||||
}
|
||||
r, _, errno := syscall.Syscall(uintptr(PN), 2, uintptr(hSnapshot), uintptr(unsafe.Pointer(lppe)), 0)
|
||||
if r == 0 {
|
||||
if errno != 0 {
|
||||
return error(errno)
|
||||
}
|
||||
return syscall.EINVAL
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetProcessId(Process HANDLE) uint32 {
|
||||
kernel32, err := syscall.LoadLibrary("kernel32.dll")
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
defer syscall.FreeLibrary(kernel32)
|
||||
GPI, err := syscall.GetProcAddress(syscall.Handle(kernel32), "GetProcessId")
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
r, _, _ := syscall.Syscall(uintptr(GPI), 1, uintptr(Process), 0, 0)
|
||||
return uint32(r)
|
||||
}
|
||||
|
||||
func GetTickCount() (uint32, error) {
|
||||
kernel32, err := syscall.LoadLibrary("kernel32.dll")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer syscall.FreeLibrary(kernel32)
|
||||
GTC, err := syscall.GetProcAddress(syscall.Handle(kernel32), "GetTickCount")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
r, _, _ := syscall.Syscall(uintptr(GTC), 0, 0, 0, 0)
|
||||
return uint32(r), nil
|
||||
}
|
||||
|
||||
func GlobalMemoryStatusEx(data *MEMORYSTATUSEX) (bool, error) {
|
||||
(*data).DwLength = DWORD(unsafe.Sizeof(*data))
|
||||
kernel32, err := syscall.LoadLibrary("kernel32.dll")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer syscall.FreeLibrary(kernel32)
|
||||
GMS, err := syscall.GetProcAddress(syscall.Handle(kernel32), "GlobalMemoryStatusEx")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
r, _, errno := syscall.Syscall(uintptr(GMS), 1, uintptr(unsafe.Pointer(data)), 0, 0)
|
||||
if r == 0 {
|
||||
if errno != 0 {
|
||||
return false, error(errno)
|
||||
}
|
||||
return false, syscall.EINVAL
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func LockFileEx(hFile HANDLE, dwFlags DWORD, dwReserved DWORD, nNumberOfBytesToLockLow DWORD,
|
||||
nNumberOfBytesToLockHigh DWORD, lpOverlapped *syscall.Overlapped) (bool, error) {
|
||||
kernel32, err := syscall.LoadLibrary("kernel32.dll")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer syscall.FreeLibrary(kernel32)
|
||||
Lck, err := syscall.GetProcAddress(syscall.Handle(kernel32), "LockFileEx")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
r, _, errno := syscall.Syscall6(uintptr(Lck), 6, uintptr(hFile), uintptr(dwFlags), uintptr(dwReserved),
|
||||
uintptr(nNumberOfBytesToLockLow), uintptr(nNumberOfBytesToLockHigh), uintptr(unsafe.Pointer(lpOverlapped)))
|
||||
if r == 0 {
|
||||
if errno != 0 {
|
||||
return false, error(errno)
|
||||
}
|
||||
return false, syscall.EINVAL
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func OpenFileById(hVolumeHint HANDLE, lpFileId *FILE_ID_DESCRIPTOR, dwDesiredAccess DWORD, dwShareMode DWORD,
|
||||
lpSecurityAttributes *syscall.SecurityAttributes, dwFlagsAndAttributes DWORD) (HANDLE, error) {
|
||||
kernel32, err := syscall.LoadLibrary("kernel32.dll")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer syscall.FreeLibrary(kernel32)
|
||||
ofb, err := syscall.GetProcAddress(syscall.Handle(kernel32), "OpenFileById")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
r, _, errno := syscall.Syscall6(ofb, 6, uintptr(hVolumeHint),
|
||||
uintptr(unsafe.Pointer(lpFileId)), uintptr(dwDesiredAccess), uintptr(dwShareMode),
|
||||
uintptr(unsafe.Pointer(lpSecurityAttributes)), uintptr(dwFlagsAndAttributes))
|
||||
if r == syscall.INVALID_FILE_ATTRIBUTES {
|
||||
if errno != 0 {
|
||||
return HANDLE(r), error(errno)
|
||||
}
|
||||
return HANDLE(r), syscall.EINVAL
|
||||
}
|
||||
return HANDLE(r), nil
|
||||
}
|
||||
|
||||
func CreateEventW(lpEventAttributes *syscall.SecurityAttributes, bManualReset bool,
|
||||
bInitialState bool, lpName LPCWSTR) (HANDLE, error) {
|
||||
var intBManualReset, intBInitialState int
|
||||
if bManualReset {
|
||||
intBManualReset = 1
|
||||
}
|
||||
if bInitialState {
|
||||
intBInitialState = 1
|
||||
}
|
||||
kernel32, err := syscall.LoadLibrary("kernel32.dll")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer syscall.FreeLibrary(kernel32)
|
||||
Lck, err := syscall.GetProcAddress(syscall.Handle(kernel32), "CreateEventW")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
r, _, errno := syscall.Syscall6(uintptr(Lck), 4, uintptr(unsafe.Pointer(lpEventAttributes)),
|
||||
uintptr(intBManualReset), uintptr(intBInitialState), uintptr(unsafe.Pointer(lpName)), 0, 0)
|
||||
if HANDLE(r) == 0 {
|
||||
if errno != 0 {
|
||||
return HANDLE(r), error(errno)
|
||||
}
|
||||
return HANDLE(r), syscall.EINVAL
|
||||
}
|
||||
return HANDLE(r), nil
|
||||
}
|
||||
|
||||
func GetLogicalDriveStringsW(nBufferLength DWORD, lpBuffer LPWSTR) error {
|
||||
kernel32, err := syscall.LoadLibrary("kernel32.dll")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.FreeLibrary(kernel32)
|
||||
glds, err := syscall.GetProcAddress(syscall.Handle(kernel32), "GetLogicalDriveStringsW")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _, errno := syscall.Syscall(uintptr(glds), 2, uintptr(nBufferLength), uintptr(unsafe.Pointer(lpBuffer)), 0)
|
||||
if errno != 0 {
|
||||
return error(errno)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetVolumeInformationW(lpRootPathName LPCWSTR, lpVolumeNameBuffer LPWSTR, nVolumeNameSize DWORD,
|
||||
lpVolumeSerialNumber LPDWORD, lpMaximumComponentLength LPDWORD, lpFileSystemFlags LPDWORD,
|
||||
lpFileSystemNameBuffer LPWSTR, nFileSystemNameSize DWORD) error {
|
||||
kernel32, err := syscall.LoadLibrary("kernel32.dll")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.FreeLibrary(kernel32)
|
||||
glds, err := syscall.GetProcAddress(syscall.Handle(kernel32), "GetVolumeInformationW")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _, errno := syscall.Syscall9(uintptr(glds), 8, uintptr(unsafe.Pointer(lpRootPathName)),
|
||||
uintptr(unsafe.Pointer(lpVolumeNameBuffer)), uintptr(nVolumeNameSize), uintptr(unsafe.Pointer(lpVolumeSerialNumber)),
|
||||
uintptr(unsafe.Pointer(lpMaximumComponentLength)), uintptr(unsafe.Pointer(lpFileSystemFlags)),
|
||||
uintptr(unsafe.Pointer(lpFileSystemNameBuffer)), uintptr(nFileSystemNameSize), 0)
|
||||
if errno != 0 {
|
||||
return error(errno)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeviceIoControl(hDevice HANDLE, dwIoControlCode DWORD, lpInBuffer LPVOID, nInBufferSize DWORD, lpOutBuffer LPVOID,
|
||||
nOutBufferSize DWORD, lpBytesReturned LPDWORD, lpOverlapped *syscall.Overlapped) (bool, error) {
|
||||
kernel32, err := syscall.LoadLibrary("kernel32.dll")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer syscall.FreeLibrary(kernel32)
|
||||
dic, err := syscall.GetProcAddress(syscall.Handle(kernel32), "DeviceIoControl")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
r, _, errno := syscall.Syscall9(uintptr(dic), 8, uintptr(hDevice), uintptr(dwIoControlCode),
|
||||
uintptr(unsafe.Pointer(lpInBuffer)), uintptr(nInBufferSize), uintptr(unsafe.Pointer(lpOutBuffer)), uintptr(nOutBufferSize),
|
||||
uintptr(unsafe.Pointer(lpBytesReturned)), uintptr(unsafe.Pointer(lpOverlapped)), 0)
|
||||
if r == 0 {
|
||||
if errno != 0 {
|
||||
return false, error(errno)
|
||||
} else {
|
||||
return false, syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func DeviceIoControlPtr(hDevice HANDLE, dwIoControlCode DWORD, lpInBuffer uintptr, nInBufferSize DWORD, lpOutBuffer uintptr,
|
||||
nOutBufferSize DWORD, lpBytesReturned LPDWORD, lpOverlapped *syscall.Overlapped) (bool, error) {
|
||||
kernel32, err := syscall.LoadLibrary("kernel32.dll")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer syscall.FreeLibrary(kernel32)
|
||||
dic, err := syscall.GetProcAddress(syscall.Handle(kernel32), "DeviceIoControl")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
r, _, errno := syscall.Syscall9(uintptr(dic), 8, uintptr(hDevice), uintptr(dwIoControlCode),
|
||||
lpInBuffer, uintptr(nInBufferSize), lpOutBuffer, uintptr(nOutBufferSize),
|
||||
uintptr(unsafe.Pointer(lpBytesReturned)), uintptr(unsafe.Pointer(lpOverlapped)), 0)
|
||||
if r == 0 {
|
||||
if errno != 0 {
|
||||
return false, error(errno)
|
||||
} else {
|
||||
return false, syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
package win32api
|
||||
|
||||
import "syscall"
|
||||
|
||||
type Ulong int32
|
||||
type Ulong_ptr uintptr
|
||||
|
||||
const (
|
||||
LOCKFILE_EXCLUSIVE_LOCK DWORD = 0x00000002
|
||||
LOCKFILE_FAIL_IMMEDIATELY DWORD = 0x00000001
|
||||
)
|
||||
|
||||
type PROCESSENTRY32 struct {
|
||||
DwSize Ulong
|
||||
CntUsage Ulong
|
||||
Th32ProcessID Ulong
|
||||
Th32DefaultHeapID Ulong_ptr
|
||||
Th32ModuleID Ulong
|
||||
CntThreads Ulong
|
||||
Th32ParentProcessID Ulong
|
||||
PcPriClassBase Ulong
|
||||
DwFlags Ulong
|
||||
SzExeFile [260]byte
|
||||
}
|
||||
|
||||
type MEMORYSTATUSEX struct {
|
||||
DwLength DWORD
|
||||
DwMemoryLoad DWORD
|
||||
UllTotalPhys DWORDLONG
|
||||
UllAvailPhys DWORDLONG
|
||||
UllTotalPageFile DWORDLONG
|
||||
UllAvailPageFile DWORDLONG
|
||||
UllTotalVirtual DWORDLONG
|
||||
UllAvailVirtual DWORDLONG
|
||||
UllAvailExtendedVirtual DWORDLONG
|
||||
}
|
||||
|
||||
type USN_JOURNAL_DATA struct {
|
||||
UsnJournalID DWORDLONG
|
||||
FirstUsn USN
|
||||
NextUsn USN
|
||||
LowestValidUsn USN
|
||||
MaxUsn USN
|
||||
MaximumSize DWORDLONG
|
||||
AllocationDelta DWORDLONG
|
||||
}
|
||||
|
||||
type READ_USN_JOURNAL_DATA struct {
|
||||
StartUsn USN
|
||||
ReasonMask DWORD
|
||||
ReturnOnlyOnClose DWORD
|
||||
Timeout DWORDLONG
|
||||
BytesToWaitFor DWORDLONG
|
||||
UsnJournalID DWORDLONG
|
||||
}
|
||||
|
||||
type USN_RECORD struct {
|
||||
RecordLength DWORD
|
||||
MajorVersion WORD
|
||||
MinorVersion WORD
|
||||
FileReferenceNumber DWORDLONG
|
||||
ParentFileReferenceNumber DWORDLONG
|
||||
Usn USN
|
||||
TimeStamp LARGE_INTEGER
|
||||
Reason DWORD
|
||||
SourceInfo DWORD
|
||||
SecurityId DWORD
|
||||
FileAttributes DWORD
|
||||
FileNameLength WORD
|
||||
FileNameOffset WORD
|
||||
FileName [1]WCHAR
|
||||
}
|
||||
|
||||
type MFT_ENUM_DATA struct {
|
||||
StartFileReferenceNumber DWORDLONG
|
||||
LowUsn USN
|
||||
HighUsn USN
|
||||
}
|
||||
|
||||
const (
|
||||
FSCTL_ENUM_USN_DATA = 0x900B3
|
||||
FSCTL_QUERY_USN_JOURNAL = 0x900F4
|
||||
FSCTL_READ_USN_JOURNAL = 0x900BB
|
||||
O_RDONLY = syscall.O_RDONLY
|
||||
O_RDWR = syscall.O_RDWR
|
||||
O_CREAT = syscall.O_CREAT
|
||||
O_WRONLY = syscall.O_WRONLY
|
||||
GENERIC_READ = syscall.GENERIC_READ
|
||||
GENERIC_WRITE = syscall.GENERIC_WRITE
|
||||
FILE_APPEND_DATA = syscall.FILE_APPEND_DATA
|
||||
FILE_SHARE_READ = syscall.FILE_SHARE_READ
|
||||
FILE_SHARE_WRITE = syscall.FILE_SHARE_WRITE
|
||||
ERROR_FILE_NOT_FOUND = syscall.ERROR_FILE_NOT_FOUND
|
||||
O_APPEND = syscall.O_APPEND
|
||||
O_CLOEXEC = syscall.O_CLOEXEC
|
||||
O_EXCL = syscall.O_EXCL
|
||||
O_TRUNC = syscall.O_TRUNC
|
||||
CREATE_ALWAYS = syscall.CREATE_ALWAYS
|
||||
CREATE_NEW = syscall.CREATE_NEW
|
||||
OPEN_ALWAYS = syscall.OPEN_ALWAYS
|
||||
TRUNCATE_EXISTING = syscall.TRUNCATE_EXISTING
|
||||
OPEN_EXISTING = syscall.OPEN_EXISTING
|
||||
FILE_ATTRIBUTE_NORMAL = syscall.FILE_ATTRIBUTE_NORMAL
|
||||
FILE_FLAG_BACKUP_SEMANTICS = syscall.FILE_FLAG_BACKUP_SEMANTICS
|
||||
FILE_ATTRIBUTE_DIRECTORY = syscall.FILE_ATTRIBUTE_DIRECTORY
|
||||
MAX_LONG_PATH = syscall.MAX_LONG_PATH
|
||||
)
|
||||
|
||||
type FILE_ID_DESCRIPTOR struct {
|
||||
DwSize DWORD
|
||||
Type DWORD
|
||||
FileId DWORDLONG
|
||||
ObjectId DWORDLONG
|
||||
ExtendedFileId DWORDLONG
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
package win32api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func ShellExecute(hwnd HWND, lpOperation, lpFile, lpParameters, lpDirectory string, nShowCmd int) error {
|
||||
shell32, err := syscall.LoadLibrary("shell32.dll")
|
||||
var op, param, directory uintptr
|
||||
if err != nil {
|
||||
return errors.New("Can't Load Shell32 API")
|
||||
}
|
||||
defer syscall.FreeLibrary(shell32)
|
||||
ShellExecute, err := syscall.GetProcAddress(syscall.Handle(shell32), "ShellExecuteW")
|
||||
if err != nil {
|
||||
return errors.New("Can't Load ShellExecute API")
|
||||
}
|
||||
if len(lpOperation) != 0 {
|
||||
op = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpOperation)))
|
||||
}
|
||||
if len(lpParameters) != 0 {
|
||||
param = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpParameters)))
|
||||
}
|
||||
if len(lpDirectory) != 0 {
|
||||
directory = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpDirectory)))
|
||||
}
|
||||
r, _, _ := syscall.Syscall6(uintptr(ShellExecute), 6,
|
||||
uintptr(hwnd),
|
||||
op,
|
||||
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpFile))),
|
||||
param,
|
||||
directory,
|
||||
uintptr(nShowCmd))
|
||||
if r < 32 {
|
||||
return errors.New("ERROR:" + strconv.Itoa(int(r)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/**
|
||||
func ShellExecuteEX2(hwnd HWND, lpVerb, lpFile, lpParameters, lpDirectory string, nShow int) error {
|
||||
var newcmd SHELLEXECUTEINFOW
|
||||
newcmd.cbSize = DWORD(unsafe.Sizeof(newcmd))
|
||||
newcmd.hwnd = hwnd
|
||||
newcmd.lpVerb = lpVerb
|
||||
newcmd.lpFile = lpFile
|
||||
newcmd.lpParameters = lpParameters
|
||||
newcmd.lpDirectory = lpDirectory
|
||||
newcmd.nShow = nShow
|
||||
shell32, err := syscall.LoadLibrary("shell32.dll")
|
||||
if err != nil {
|
||||
return errors.New("Can't Load Shell32 API")
|
||||
}
|
||||
defer syscall.FreeLibrary(shell32)
|
||||
ShellExecuteEx, err := syscall.GetProcAddress(syscall.Handle(shell32), "ShellExecuteExW")
|
||||
if err != nil {
|
||||
return errors.New("Can't Load ShellExecuteEx API")
|
||||
}
|
||||
r, _, _ := syscall.Syscall6(ShellExecuteEx, 1, uintptr(unsafe.Pointer(&newcmd)), 0, 0, 0, 0, 0)
|
||||
fmt.Println(strconv.Itoa(int(r)))
|
||||
fmt.Println(strconv.Itoa(int(newcmd.hInstApp)))
|
||||
if r != 0 {
|
||||
return errors.New("Error Recured")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
*/
|
||||
|
||||
func ShellExecuteEx(muzika *SHELLEXECUTEINFOW) error {
|
||||
shell32, err := syscall.LoadLibrary("shell32.dll")
|
||||
|
||||
if err != nil {
|
||||
return errors.New("Can't Load Shell32 API")
|
||||
}
|
||||
defer syscall.FreeLibrary(shell32)
|
||||
ShellExecuteEx, err := syscall.GetProcAddress(syscall.Handle(shell32), "ShellExecuteExW")
|
||||
if err != nil {
|
||||
return errors.New("Can't Load ShellExecuteEx API")
|
||||
}
|
||||
r, _, errno := syscall.Syscall6(ShellExecuteEx, 1, uintptr(unsafe.Pointer(muzika)), 0, 0, 0, 0, 0)
|
||||
if r == 0 {
|
||||
return error(errno)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package win32api
|
||||
|
||||
type SHELLEXECUTEINFOW struct {
|
||||
CbSize DWORD
|
||||
FMask ULONG
|
||||
Hwnd HWND
|
||||
LpVerb uintptr
|
||||
LpFile uintptr
|
||||
LpParameters uintptr
|
||||
LpDirectory uintptr
|
||||
NShow int
|
||||
HInstApp HINSTANCE
|
||||
LpIDList LPVOID
|
||||
LpClass uintptr
|
||||
HkeyClass HKEY
|
||||
DwHotKey DWORD
|
||||
UnionorHMonitor HANDLE
|
||||
HProcess HANDLE
|
||||
}
|
||||
|
||||
type UNION struct {
|
||||
HIcon HANDLE
|
||||
HMonitor HANDLE
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue