ledisdb/server/cmd_zset.go

810 lines
14 KiB
Go
Raw Normal View History

package server
2014-05-03 10:55:12 +04:00
2014-05-08 06:54:33 +04:00
import (
"errors"
2014-09-24 08:34:21 +04:00
"github.com/siddontang/go/hack"
2014-09-24 09:29:27 +04:00
"github.com/siddontang/go/num"
"github.com/siddontang/ledisdb/ledis"
2014-10-01 19:26:46 +04:00
"github.com/siddontang/ledisdb/store"
2014-05-08 06:54:33 +04:00
"math"
"strconv"
"strings"
)
//for simple implementation, we only support int64 score
var errScoreOverflow = errors.New("zset score overflow")
2014-08-25 10:18:23 +04:00
func zaddCommand(c *client) error {
args := c.args
2014-05-08 06:54:33 +04:00
if len(args) < 3 {
return ErrCmdParams
}
key := args[0]
2014-08-25 10:18:23 +04:00
if len(args[1:])&1 != 0 {
2014-05-08 06:54:33 +04:00
return ErrCmdParams
}
args = args[1:]
2014-05-16 04:56:32 +04:00
2014-08-25 10:18:23 +04:00
params := make([]ledis.ScorePair, len(args)>>1)
2014-05-16 04:56:32 +04:00
for i := 0; i < len(params); i++ {
score, err := ledis.StrInt64(args[2*i], nil)
2014-05-08 06:54:33 +04:00
if err != nil {
return ErrValue
2014-05-08 06:54:33 +04:00
}
2014-05-16 04:56:32 +04:00
params[i].Score = score
params[i].Member = args[2*i+1]
2014-05-08 06:54:33 +04:00
}
2014-08-25 10:18:23 +04:00
n, err := c.db.ZAdd(key, params...)
if err == nil {
c.resp.writeInteger(n)
2014-05-08 06:54:33 +04:00
}
2014-08-25 10:18:23 +04:00
return err
2014-05-08 06:54:33 +04:00
}
2014-08-25 10:18:23 +04:00
func zcardCommand(c *client) error {
args := c.args
2014-05-08 06:54:33 +04:00
if len(args) != 1 {
return ErrCmdParams
}
2014-08-25 10:18:23 +04:00
if n, err := c.db.ZCard(args[0]); err != nil {
2014-05-08 06:54:33 +04:00
return err
} else {
2014-08-25 10:18:23 +04:00
c.resp.writeInteger(n)
2014-05-08 06:54:33 +04:00
}
return nil
}
2014-08-25 10:18:23 +04:00
func zscoreCommand(c *client) error {
args := c.args
2014-05-08 06:54:33 +04:00
if len(args) != 2 {
return ErrCmdParams
}
2014-08-25 10:18:23 +04:00
if s, err := c.db.ZScore(args[0], args[1]); err != nil {
if err == ledis.ErrScoreMiss {
2014-08-25 10:18:23 +04:00
c.resp.writeBulk(nil)
} else {
return err
}
2014-05-08 06:54:33 +04:00
} else {
2014-09-24 09:29:27 +04:00
c.resp.writeBulk(num.FormatInt64ToSlice(s))
2014-05-08 06:54:33 +04:00
}
return nil
}
2014-08-25 10:18:23 +04:00
func zremCommand(c *client) error {
args := c.args
2014-05-08 06:54:33 +04:00
if len(args) < 2 {
return ErrCmdParams
}
2014-08-25 10:18:23 +04:00
n, err := c.db.ZRem(args[0], args[1:]...)
if err == nil {
c.resp.writeInteger(n)
2014-05-08 06:54:33 +04:00
}
2014-08-25 10:18:23 +04:00
return err
2014-05-08 06:54:33 +04:00
}
2014-08-25 10:18:23 +04:00
func zincrbyCommand(c *client) error {
args := c.args
2014-05-08 06:54:33 +04:00
if len(args) != 3 {
return ErrCmdParams
}
key := args[0]
delta, err := ledis.StrInt64(args[1], nil)
2014-05-08 06:54:33 +04:00
if err != nil {
return ErrValue
2014-05-08 06:54:33 +04:00
}
2014-08-25 10:18:23 +04:00
v, err := c.db.ZIncrBy(key, delta, args[2])
if err == nil {
2014-09-24 09:29:27 +04:00
c.resp.writeBulk(num.FormatInt64ToSlice(v))
2014-05-08 06:54:33 +04:00
}
2014-08-25 10:18:23 +04:00
return err
2014-05-08 06:54:33 +04:00
}
func zparseScoreRange(minBuf []byte, maxBuf []byte) (min int64, max int64, err error) {
2014-09-24 08:34:21 +04:00
if strings.ToLower(hack.String(minBuf)) == "-inf" {
2014-05-08 06:54:33 +04:00
min = math.MinInt64
} else {
if len(minBuf) == 0 {
err = ErrCmdParams
return
}
2014-07-23 13:10:06 +04:00
var lopen bool = false
if minBuf[0] == '(' {
lopen = true
minBuf = minBuf[1:]
}
min, err = ledis.StrInt64(minBuf, nil)
2014-05-08 06:54:33 +04:00
if err != nil {
err = ErrValue
2014-05-08 06:54:33 +04:00
return
}
if min <= ledis.MinScore || min >= ledis.MaxScore {
2014-05-08 06:54:33 +04:00
err = errScoreOverflow
return
}
if lopen {
min++
}
}
2014-09-24 08:34:21 +04:00
if strings.ToLower(hack.String(maxBuf)) == "+inf" {
2014-05-08 06:54:33 +04:00
max = math.MaxInt64
} else {
var ropen = false
if len(maxBuf) == 0 {
err = ErrCmdParams
return
}
2014-08-01 12:03:51 +04:00
if maxBuf[0] == '(' {
ropen = true
maxBuf = maxBuf[1:]
}
2014-05-08 06:54:33 +04:00
2014-07-23 13:10:06 +04:00
if maxBuf[0] == '(' {
ropen = true
maxBuf = maxBuf[1:]
}
max, err = ledis.StrInt64(maxBuf, nil)
2014-05-08 06:54:33 +04:00
if err != nil {
err = ErrValue
2014-05-08 06:54:33 +04:00
return
}
if max <= ledis.MinScore || max >= ledis.MaxScore {
2014-05-08 06:54:33 +04:00
err = errScoreOverflow
return
}
if ropen {
max--
}
}
return
}
2014-08-25 10:18:23 +04:00
func zcountCommand(c *client) error {
args := c.args
2014-05-08 06:54:33 +04:00
if len(args) != 3 {
return ErrCmdParams
}
min, max, err := zparseScoreRange(args[1], args[2])
if err != nil {
return ErrValue
2014-05-08 06:54:33 +04:00
}
if min > max {
2014-08-25 10:18:23 +04:00
c.resp.writeInteger(0)
2014-05-08 06:54:33 +04:00
return nil
}
2014-08-25 10:18:23 +04:00
if n, err := c.db.ZCount(args[0], min, max); err != nil {
2014-05-08 06:54:33 +04:00
return err
} else {
2014-08-25 10:18:23 +04:00
c.resp.writeInteger(n)
2014-05-08 06:54:33 +04:00
}
return nil
}
2014-08-25 10:18:23 +04:00
func zrankCommand(c *client) error {
args := c.args
2014-05-08 06:54:33 +04:00
if len(args) != 2 {
return ErrCmdParams
}
2014-08-25 10:18:23 +04:00
if n, err := c.db.ZRank(args[0], args[1]); err != nil {
2014-05-08 06:54:33 +04:00
return err
} else if n == -1 {
2014-08-25 10:18:23 +04:00
c.resp.writeBulk(nil)
2014-05-08 06:54:33 +04:00
} else {
2014-08-25 10:18:23 +04:00
c.resp.writeInteger(n)
2014-05-08 06:54:33 +04:00
}
return nil
}
2014-08-25 10:18:23 +04:00
func zrevrankCommand(c *client) error {
args := c.args
2014-05-08 06:54:33 +04:00
if len(args) != 2 {
return ErrCmdParams
}
2014-08-25 10:18:23 +04:00
if n, err := c.db.ZRevRank(args[0], args[1]); err != nil {
2014-05-08 06:54:33 +04:00
return err
} else if n == -1 {
2014-08-25 10:18:23 +04:00
c.resp.writeBulk(nil)
2014-05-08 06:54:33 +04:00
} else {
2014-08-25 10:18:23 +04:00
c.resp.writeInteger(n)
2014-05-08 06:54:33 +04:00
}
return nil
}
2014-08-25 10:18:23 +04:00
func zremrangebyrankCommand(c *client) error {
args := c.args
2014-05-08 06:54:33 +04:00
if len(args) != 3 {
return ErrCmdParams
}
key := args[0]
2014-08-25 10:18:23 +04:00
start, stop, err := zparseRange(c, args[1], args[2])
2014-05-08 06:54:33 +04:00
if err != nil {
return ErrValue
2014-05-08 06:54:33 +04:00
}
2014-08-25 10:18:23 +04:00
n, err := c.db.ZRemRangeByRank(key, start, stop)
if err == nil {
c.resp.writeInteger(n)
2014-05-08 06:54:33 +04:00
}
2014-08-25 10:18:23 +04:00
return err
2014-05-08 06:54:33 +04:00
}
2014-08-25 10:18:23 +04:00
func zremrangebyscoreCommand(c *client) error {
args := c.args
2014-05-08 06:54:33 +04:00
if len(args) != 3 {
return ErrCmdParams
}
key := args[0]
min, max, err := zparseScoreRange(args[1], args[2])
if err != nil {
return err
}
2014-08-25 10:18:23 +04:00
n, err := c.db.ZRemRangeByScore(key, min, max)
if err == nil {
c.resp.writeInteger(n)
2014-05-08 06:54:33 +04:00
}
2014-08-25 10:18:23 +04:00
return err
2014-05-08 06:54:33 +04:00
}
2014-08-25 10:18:23 +04:00
func zparseRange(c *client, a1 []byte, a2 []byte) (start int, stop int, err error) {
2014-09-24 08:34:21 +04:00
if start, err = strconv.Atoi(hack.String(a1)); err != nil {
2014-05-08 06:54:33 +04:00
return
}
2014-09-24 08:34:21 +04:00
if stop, err = strconv.Atoi(hack.String(a2)); err != nil {
2014-05-08 06:54:33 +04:00
return
}
return
}
2014-08-25 10:18:23 +04:00
func zrangeGeneric(c *client, reverse bool) error {
args := c.args
2014-05-08 06:54:33 +04:00
if len(args) < 3 {
return ErrCmdParams
}
key := args[0]
2014-08-25 10:18:23 +04:00
start, stop, err := zparseRange(c, args[1], args[2])
2014-05-08 06:54:33 +04:00
if err != nil {
return ErrValue
2014-05-08 06:54:33 +04:00
}
args = args[3:]
var withScores bool = false
if len(args) > 0 {
if len(args) != 1 {
return ErrCmdParams
}
2014-09-24 08:34:21 +04:00
if strings.ToLower(hack.String(args[0])) == "withscores" {
withScores = true
} else {
return ErrSyntax
}
2014-05-08 06:54:33 +04:00
}
2014-08-25 10:18:23 +04:00
if datas, err := c.db.ZRangeGeneric(key, start, stop, reverse); err != nil {
2014-05-08 06:54:33 +04:00
return err
} else {
2014-08-25 10:18:23 +04:00
c.resp.writeScorePairArray(datas, withScores)
2014-05-08 06:54:33 +04:00
}
return nil
}
2014-08-25 10:18:23 +04:00
func zrangeCommand(c *client) error {
return zrangeGeneric(c, false)
2014-05-08 06:54:33 +04:00
}
2014-08-25 10:18:23 +04:00
func zrevrangeCommand(c *client) error {
return zrangeGeneric(c, true)
2014-05-08 06:54:33 +04:00
}
2014-08-25 10:18:23 +04:00
func zrangebyscoreGeneric(c *client, reverse bool) error {
args := c.args
2014-05-08 06:54:33 +04:00
if len(args) < 3 {
return ErrCmdParams
}
key := args[0]
var minScore, maxScore []byte
if !reverse {
minScore, maxScore = args[1], args[2]
} else {
minScore, maxScore = args[2], args[1]
}
min, max, err := zparseScoreRange(minScore, maxScore)
2014-05-08 06:54:33 +04:00
if err != nil {
return err
}
args = args[3:]
var withScores bool = false
if len(args) > 0 {
2014-09-24 08:34:21 +04:00
if strings.ToLower(hack.String(args[0])) == "withscores" {
withScores = true
args = args[1:]
}
2014-05-08 06:54:33 +04:00
}
var offset int = 0
var count int = -1
2014-05-08 06:54:33 +04:00
if len(args) > 0 {
if len(args) != 3 {
return ErrCmdParams
}
2014-09-24 08:34:21 +04:00
if strings.ToLower(hack.String(args[0])) != "limit" {
return ErrSyntax
2014-05-08 06:54:33 +04:00
}
2014-09-24 08:34:21 +04:00
if offset, err = strconv.Atoi(hack.String(args[1])); err != nil {
return ErrValue
2014-05-08 06:54:33 +04:00
}
2014-09-24 08:34:21 +04:00
if count, err = strconv.Atoi(hack.String(args[2])); err != nil {
return ErrValue
2014-05-08 06:54:33 +04:00
}
}
if offset < 0 {
2014-06-22 06:39:23 +04:00
//for ledis, if offset < 0, a empty will return
2014-05-08 06:54:33 +04:00
//so here we directly return a empty array
2014-08-25 10:18:23 +04:00
c.resp.writeArray([]interface{}{})
2014-05-08 06:54:33 +04:00
return nil
}
2014-08-25 10:18:23 +04:00
if datas, err := c.db.ZRangeByScoreGeneric(key, min, max, offset, count, reverse); err != nil {
2014-05-08 06:54:33 +04:00
return err
} else {
2014-08-25 10:18:23 +04:00
c.resp.writeScorePairArray(datas, withScores)
2014-05-08 06:54:33 +04:00
}
return nil
}
2014-08-25 10:18:23 +04:00
func zrangebyscoreCommand(c *client) error {
return zrangebyscoreGeneric(c, false)
2014-05-08 06:54:33 +04:00
}
2014-08-25 10:18:23 +04:00
func zrevrangebyscoreCommand(c *client) error {
return zrangebyscoreGeneric(c, true)
2014-05-08 06:54:33 +04:00
}
2014-08-25 10:18:23 +04:00
func zclearCommand(c *client) error {
args := c.args
2014-05-12 11:08:59 +04:00
if len(args) != 1 {
return ErrCmdParams
}
2014-08-25 10:18:23 +04:00
n, err := c.db.ZClear(args[0])
if err == nil {
c.resp.writeInteger(n)
2014-05-12 11:08:59 +04:00
}
2014-08-25 10:18:23 +04:00
return err
2014-05-12 11:08:59 +04:00
}
2014-08-25 10:18:23 +04:00
func zmclearCommand(c *client) error {
args := c.args
if len(args) < 1 {
return ErrCmdParams
}
2014-08-25 10:18:23 +04:00
n, err := c.db.ZMclear(args...)
if err == nil {
c.resp.writeInteger(n)
}
2014-08-25 10:18:23 +04:00
return err
}
2014-08-25 10:18:23 +04:00
func zexpireCommand(c *client) error {
args := c.args
2014-06-30 07:23:14 +04:00
if len(args) != 2 {
2014-06-16 15:24:37 +04:00
return ErrCmdParams
}
duration, err := ledis.StrInt64(args[1], nil)
if err != nil {
return ErrValue
2014-06-16 15:24:37 +04:00
}
2014-08-25 10:18:23 +04:00
v, err := c.db.ZExpire(args[0], duration)
if err == nil {
c.resp.writeInteger(v)
2014-06-16 15:24:37 +04:00
}
2014-08-25 10:18:23 +04:00
return err
2014-06-16 15:24:37 +04:00
}
2014-08-25 10:18:23 +04:00
func zexpireAtCommand(c *client) error {
args := c.args
2014-06-30 07:23:14 +04:00
if len(args) != 2 {
2014-06-16 15:24:37 +04:00
return ErrCmdParams
}
when, err := ledis.StrInt64(args[1], nil)
if err != nil {
return ErrValue
2014-06-16 15:24:37 +04:00
}
2014-08-25 10:18:23 +04:00
v, err := c.db.ZExpireAt(args[0], when)
if err == nil {
c.resp.writeInteger(v)
2014-06-16 15:24:37 +04:00
}
2014-08-25 10:18:23 +04:00
return err
2014-06-16 15:24:37 +04:00
}
2014-08-25 10:18:23 +04:00
func zttlCommand(c *client) error {
args := c.args
2014-06-30 07:23:14 +04:00
if len(args) != 1 {
2014-06-16 15:24:37 +04:00
return ErrCmdParams
}
2014-08-25 10:18:23 +04:00
if v, err := c.db.ZTTL(args[0]); err != nil {
2014-06-16 15:24:37 +04:00
return err
} else {
2014-08-25 10:18:23 +04:00
c.resp.writeInteger(v)
2014-06-16 15:24:37 +04:00
}
return nil
}
2014-08-25 10:18:23 +04:00
func zpersistCommand(c *client) error {
args := c.args
2014-06-24 08:44:44 +04:00
if len(args) != 1 {
return ErrCmdParams
}
2014-08-25 10:18:23 +04:00
n, err := c.db.ZPersist(args[0])
if err == nil {
c.resp.writeInteger(n)
2014-06-24 08:44:44 +04:00
}
2014-08-25 10:18:23 +04:00
return err
2014-06-24 08:44:44 +04:00
}
func zparseZsetoptStore(args [][]byte) (destKey []byte, srcKeys [][]byte, weights []int64, aggregate byte, err error) {
destKey = args[0]
2014-09-24 08:34:21 +04:00
nKeys, err := strconv.Atoi(hack.String(args[1]))
if err != nil {
err = ErrValue
return
}
args = args[2:]
if len(args) < nKeys {
err = ErrSyntax
return
}
srcKeys = args[:nKeys]
args = args[nKeys:]
var weightsFlag = false
var aggregateFlag = false
for len(args) > 0 {
2014-09-24 08:34:21 +04:00
if strings.ToLower(hack.String(args[0])) == "weights" {
if weightsFlag {
err = ErrSyntax
return
}
args = args[1:]
if len(args) < nKeys {
err = ErrSyntax
return
}
weights = make([]int64, nKeys)
for i, arg := range args[:nKeys] {
if weights[i], err = ledis.StrInt64(arg, nil); err != nil {
err = ErrValue
return
}
}
args = args[nKeys:]
weightsFlag = true
2014-09-24 08:34:21 +04:00
} else if strings.ToLower(hack.String(args[0])) == "aggregate" {
if aggregateFlag {
err = ErrSyntax
return
}
if len(args) < 2 {
err = ErrSyntax
return
}
2014-09-24 08:34:21 +04:00
if strings.ToLower(hack.String(args[1])) == "sum" {
aggregate = ledis.AggregateSum
2014-09-24 08:34:21 +04:00
} else if strings.ToLower(hack.String(args[1])) == "min" {
aggregate = ledis.AggregateMin
2014-09-24 08:34:21 +04:00
} else if strings.ToLower(hack.String(args[1])) == "max" {
aggregate = ledis.AggregateMax
} else {
err = ErrSyntax
return
}
args = args[2:]
aggregateFlag = true
} else {
err = ErrSyntax
return
}
}
if !aggregateFlag {
aggregate = ledis.AggregateSum
}
return
}
2014-08-25 10:18:23 +04:00
func zunionstoreCommand(c *client) error {
args := c.args
if len(args) < 2 {
return ErrCmdParams
}
destKey, srcKeys, weights, aggregate, err := zparseZsetoptStore(args)
if err != nil {
return err
}
2014-08-25 10:18:23 +04:00
n, err := c.db.ZUnionStore(destKey, srcKeys, weights, aggregate)
if err == nil {
c.resp.writeInteger(n)
}
2014-08-25 10:18:23 +04:00
return err
}
2014-08-25 10:18:23 +04:00
func zinterstoreCommand(c *client) error {
args := c.args
if len(args) < 2 {
return ErrCmdParams
}
destKey, srcKeys, weights, aggregate, err := zparseZsetoptStore(args)
if err != nil {
return err
}
2014-08-25 10:18:23 +04:00
n, err := c.db.ZInterStore(destKey, srcKeys, weights, aggregate)
if err == nil {
c.resp.writeInteger(n)
}
2014-08-25 10:18:23 +04:00
return err
}
2014-09-09 05:53:26 +04:00
func zxscanCommand(c *client) error {
2014-10-20 18:36:16 +04:00
return xscanGeneric(c, c.db.ZScan)
}
2014-08-26 19:21:45 +04:00
2014-10-20 18:36:16 +04:00
func zxrevscanCommand(c *client) error {
return xscanGeneric(c, c.db.ZRevScan)
2014-08-26 19:21:45 +04:00
}
2014-10-01 19:26:46 +04:00
func zparseMemberRange(minBuf []byte, maxBuf []byte) (min []byte, max []byte, rangeType uint8, err error) {
rangeType = store.RangeClose
if strings.ToLower(hack.String(minBuf)) == "-" {
min = nil
} else {
if len(minBuf) == 0 {
err = ErrCmdParams
return
}
if minBuf[0] == '(' {
rangeType |= store.RangeLOpen
min = minBuf[1:]
} else if minBuf[0] == '[' {
min = minBuf[1:]
} else {
err = ErrCmdParams
return
}
}
if strings.ToLower(hack.String(maxBuf)) == "+" {
max = nil
} else {
if len(maxBuf) == 0 {
err = ErrCmdParams
return
}
if maxBuf[0] == '(' {
rangeType |= store.RangeROpen
max = maxBuf[1:]
} else if maxBuf[0] == '[' {
max = maxBuf[1:]
} else {
err = ErrCmdParams
return
}
}
return
}
func zrangebylexCommand(c *client) error {
args := c.args
if len(args) != 3 && len(args) != 6 {
return ErrCmdParams
}
min, max, rangeType, err := zparseMemberRange(args[1], args[2])
if err != nil {
return err
}
var offset int = 0
var count int = -1
if len(args) == 6 {
if strings.ToLower(hack.String(args[3])) != "limit" {
return ErrSyntax
}
if offset, err = strconv.Atoi(hack.String(args[4])); err != nil {
return ErrValue
}
if count, err = strconv.Atoi(hack.String(args[5])); err != nil {
return ErrValue
}
}
key := args[0]
if ay, err := c.db.ZRangeByLex(key, min, max, rangeType, offset, count); err != nil {
return err
} else {
c.resp.writeSliceArray(ay)
}
return nil
}
func zremrangebylexCommand(c *client) error {
args := c.args
if len(args) != 3 {
return ErrCmdParams
}
min, max, rangeType, err := zparseMemberRange(args[1], args[2])
if err != nil {
return err
}
key := args[0]
if n, err := c.db.ZRemRangeByLex(key, min, max, rangeType); err != nil {
return err
} else {
c.resp.writeInteger(n)
}
return nil
}
func zlexcountCommand(c *client) error {
args := c.args
if len(args) != 3 {
return ErrCmdParams
}
min, max, rangeType, err := zparseMemberRange(args[1], args[2])
if err != nil {
return err
}
key := args[0]
if n, err := c.db.ZLexCount(key, min, max, rangeType); err != nil {
return err
} else {
c.resp.writeInteger(n)
}
return nil
}
2014-05-03 10:55:12 +04:00
func init() {
register("zadd", zaddCommand)
register("zcard", zcardCommand)
register("zcount", zcountCommand)
register("zincrby", zincrbyCommand)
register("zrange", zrangeCommand)
register("zrangebyscore", zrangebyscoreCommand)
register("zrank", zrankCommand)
register("zrem", zremCommand)
register("zremrangebyrank", zremrangebyrankCommand)
register("zremrangebyscore", zremrangebyscoreCommand)
register("zrevrange", zrevrangeCommand)
register("zrevrank", zrevrankCommand)
register("zrevrangebyscore", zrevrangebyscoreCommand)
register("zscore", zscoreCommand)
2014-05-12 11:08:59 +04:00
register("zunionstore", zunionstoreCommand)
register("zinterstore", zinterstoreCommand)
2014-10-01 19:26:46 +04:00
register("zrangebylex", zrangebylexCommand)
register("zremrangebylex", zremrangebylexCommand)
register("zlexcount", zlexcountCommand)
2014-05-12 11:08:59 +04:00
//ledisdb special command
2014-06-16 15:24:37 +04:00
register("zclear", zclearCommand)
register("zmclear", zmclearCommand)
2014-06-16 15:24:37 +04:00
register("zexpire", zexpireCommand)
register("zexpireat", zexpireAtCommand)
register("zttl", zttlCommand)
2014-06-24 08:44:44 +04:00
register("zpersist", zpersistCommand)
2014-09-09 05:53:26 +04:00
register("zxscan", zxscanCommand)
2014-10-20 18:36:16 +04:00
register("zxrevscan", zxrevscanCommand)
register("xzscan", zxscanCommand)
register("xzrevscan", zxrevscanCommand)
2014-05-03 10:55:12 +04:00
}