diff --git a/base.go b/base.go index 609e48d4..d74ec2a0 100644 --- a/base.go +++ b/base.go @@ -38,7 +38,9 @@ func (f *Frame) Start(m *Message, arg ...string) bool { return true } func (f *Frame) Close(m *Message, arg ...string) bool { - m.target.wg.Wait() + m.TryCatch(m, true, func(m *Message) { + m.target.wg.Wait() + }) list := map[*Context]*Message{m.target: m} m.Travel(func(p *Context, s *Context) { if msg, ok := list[p]; ok && msg != nil { diff --git a/base/nfs/nfs.go b/base/nfs/nfs.go index 97a528a7..3e73c983 100644 --- a/base/nfs/nfs.go +++ b/base/nfs/nfs.go @@ -18,7 +18,9 @@ import ( func dir(m *ice.Message, root string, name string, level int, deep bool, dir_type string, dir_reg *regexp.Regexp, fields []string, format string) { - if fs, e := ioutil.ReadDir(path.Join(root, name)); m.Assert(e) { + if fs, e := ioutil.ReadDir(path.Join(root, name)); e != nil { + m.Log(ice.LOG_WARN, "%s", e) + } else { for _, f := range fs { if f.Name() == "." || f.Name() == ".." { continue @@ -73,7 +75,7 @@ func dir(m *ice.Message, root string, name string, level int, deep bool, dir_typ m.Push("tree", strings.Repeat("| ", level-1)+"|-"+f.Name()) } case "size": - m.Push("size", f.Size()) + m.Push("size", kit.FmtSize(f.Size())) case "line": if f.IsDir() { if d, e := ioutil.ReadDir(p); m.Assert(e) { diff --git a/base/web/template.go b/base/web/template.go index e892063c..7c81c0c7 100644 --- a/base/web/template.go +++ b/base/web/template.go @@ -5,6 +5,13 @@ import ( ) var share_template = kit.Dict( + "story.prefix", ` + + + + +`, "story.suffix", ``, + "download", `%s`, "share", `%s`, "shy/story", `{{.}}`, diff --git a/base/web/web.go b/base/web/web.go index 4ab57eb2..bc628aaa 100644 --- a/base/web/web.go +++ b/base/web/web.go @@ -4,6 +4,7 @@ import ( "github.com/gorilla/websocket" "github.com/shylinux/icebergs" "github.com/shylinux/toolkits" + "github.com/skip2/go-qrcode" "bytes" "encoding/json" @@ -41,8 +42,9 @@ func Cookie(msg *ice.Message, sessid string) string { return sessid } func IsLocalIP(ip string) bool { - return ip == "::1" || ip == "127.0.0.1" + return ip == "::1" || strings.HasPrefix(ip, "127.") } + func (web *Frame) Login(msg *ice.Message, w http.ResponseWriter, r *http.Request) bool { if msg.Options(ice.WEB_SESS) { // 会话认证 @@ -50,6 +52,9 @@ func (web *Frame) Login(msg *ice.Message, w http.ResponseWriter, r *http.Request msg.Info("role: %s user: %s", msg.Option(ice.MSG_USERROLE, sub.Append("userrole")), msg.Option(ice.MSG_USERNAME, sub.Append("username"))) } + if strings.HasPrefix(msg.Option(ice.MSG_USERURL), "/space/") { + return true + } if (!msg.Options(ice.MSG_SESSID) || !msg.Options(ice.MSG_USERNAME)) && IsLocalIP(msg.Option(ice.MSG_USERIP)) { // 自动认证 @@ -169,6 +174,7 @@ func (web *Frame) HandleCmd(m *ice.Message, key string, cmd *ice.Command) { msg.Option(ice.MSG_USERUA, r.Header.Get("User-Agent")) msg.Option(ice.MSG_USERIP, r.Header.Get(ice.MSG_USERIP)) msg.Option(ice.MSG_USERURL, r.URL.Path) + msg.Option(ice.MSG_USERNAME, "") msg.Option(ice.WEB_SESS, "") msg.R, msg.W = r, w @@ -208,6 +214,10 @@ func (web *Frame) HandleCmd(m *ice.Message, key string, cmd *ice.Command) { msg.Optionv(k, v) } + if msg.Optionv("cmds") == nil { + msg.Optionv("cmds", strings.Split(msg.Option(ice.MSG_USERURL), "/")[2:]) + } + // 执行命令 if web.Login(msg, w, r) && msg.Target().Run(msg, cmd, msg.Option(ice.MSG_USERURL), kit.Simple(msg.Optionv("cmds"))...) != nil { // 输出响应 @@ -218,6 +228,11 @@ func (web *Frame) HandleCmd(m *ice.Message, key string, cmd *ice.Command) { w.Header().Set("Content-Disposition", fmt.Sprintf("filename=%s", kit.Select(msg.Append("name"), msg.Append("story")))) w.Header().Set("Content-Type", kit.Select("text/html", msg.Append("type"))) http.ServeFile(w, r, msg.Append("file")) + case "qrcode": + if qr, e := qrcode.New(msg.Append("qrcode"), qrcode.Medium); m.Assert(e) { + w.Header().Set("Content-Type", "image/png") + m.Assert(qr.Write(256, w)) + } case "result": w.Header().Set("Content-Type", kit.Select("text/html", msg.Append("type"))) fmt.Fprint(w, msg.Result()) @@ -347,13 +362,18 @@ var Index = &ice.Context{Name: "web", Help: "网页模块", ice.WEB_SPACE: {Name: "space", Help: "空间站", Value: kit.Data(kit.MDB_SHORT, "name", "redial.a", 3000, "redial.b", 1000, "redial.c", 10, "buffer.r", 4096, "buffer.w", 4096, + "timeout.c", "30s", )}, ice.WEB_DREAM: {Name: "dream", Help: "梦想家", Value: kit.Data("path", "usr/local/work", "cmd", []interface{}{ice.CLI_SYSTEM, "ice.sh", "start", ice.WEB_SPACE, "connect"}, )}, ice.WEB_FAVOR: {Name: "favor", Help: "收藏夹", Value: kit.Data(kit.MDB_SHORT, kit.MDB_NAME)}, ice.WEB_CACHE: {Name: "cache", Help: "缓存池", Value: kit.Data(kit.MDB_SHORT, "text", "path", "var/file", "store", "var/data", "limit", "30", "least", "10")}, - ice.WEB_STORY: {Name: "story", Help: "故事会", Value: kit.Dict(kit.MDB_META, kit.Dict(kit.MDB_SHORT, "data"), "head", kit.Data(kit.MDB_SHORT, "story"))}, + ice.WEB_STORY: {Name: "story", Help: "故事会", Value: kit.Dict( + kit.MDB_META, kit.Dict(kit.MDB_SHORT, "data"), + "head", kit.Data(kit.MDB_SHORT, "story"), + "mime", kit.Dict("md", "txt"), + )}, ice.WEB_SHARE: {Name: "share", Help: "共享链", Value: kit.Data("template", share_template)}, ice.WEB_ROUTE: {Name: "route", Help: "路由", Value: kit.Data()}, ice.WEB_PROXY: {Name: "proxy", Help: "代理", Value: kit.Data()}, @@ -377,6 +397,14 @@ var Index = &ice.Context{Name: "web", Help: "网页模块", ice.ICE_EXIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { m.Done() m.Done() + p := m.Conf(ice.WEB_CACHE, "meta.store") + m.Richs(ice.WEB_CACHE, nil, "*", func(key string, value map[string]interface{}) { + if f, _, e := kit.Create(path.Join(p, key[:2], key)); e == nil { + defer f.Close() + f.WriteString(kit.Formats(value)) + } + }) + // m.Conf(ice.WEB_CACHE, "hash", kit.Dict()) m.Cmd(ice.CTX_CONFIG, "save", "web.json", ice.WEB_SPIDE, ice.WEB_FAVOR, ice.WEB_CACHE, ice.WEB_STORY, ice.WEB_SHARE) }}, @@ -540,8 +568,14 @@ var Index = &ice.Context{Name: "web", Help: "网页模块", // 验证结果 if m.Cost("%s %s: %s", res.Status, res.Header.Get("Content-Length"), res.Header.Get("Content-Type")); m.Warn(e != nil, "%s", e) { + if cache != "" { + m.Set("result") + } return } else if m.Warn(res.StatusCode != http.StatusOK, "%s", res.Status) { + if cache != "" { + m.Set("result") + } return } @@ -572,10 +606,18 @@ var Index = &ice.Context{Name: "web", Help: "网页模块", m.Conf(ice.CLI_RUNTIME, "node.type", ice.WEB_SERVER) // 启动服务 - m.Target().Start(m, kit.Select("self", arg, 0)) - m.Richs(ice.WEB_SPIDE, nil, "dev", func(key string, value map[string]interface{}) { - m.Cmd(ice.WEB_SPACE, "connect", "dev") - }) + switch kit.Select("self", arg, 0) { + case "dev": + m.Event(ice.SYSTEM_INIT) + fallthrough + case "self": + m.Target().Start(m, "self") + fallthrough + default: + m.Richs(ice.WEB_SPIDE, nil, "dev", func(key string, value map[string]interface{}) { + m.Cmd(ice.WEB_SPACE, "connect", "dev") + }) + } }}, ice.WEB_SPACE: {Name: "space", Help: "空间站", Meta: kit.Dict("exports", []string{"pod", "name"}), List: kit.List( kit.MDB_INPUT, "text", "name", "pod", @@ -635,7 +677,7 @@ var Index = &ice.Context{Name: "web", Help: "网页模块", // 连接成功 m.Rich(ice.WEB_SPACE, nil, kit.Dict(kit.MDB_TYPE, ice.WEB_MASTER, kit.MDB_NAME, dev)) - m.Info("%d conn %s success %s", i, dev, u) + m.Log(ice.LOG_CMDS, "%d conn %s success %s", i, dev, u) if i = 0; web.HandleWSS(m, true, s, dev) { break } @@ -682,9 +724,14 @@ var Index = &ice.Context{Name: "web", Help: "网页模块", // 下发命令 m.Target().Server().(*Frame).send[id] = m socket.WriteMessage(MSG_MAPS, []byte(m.Format("meta"))) + t := time.AfterFunc(kit.Duration(m.Conf(ice.WEB_SPACE, "meta.timeout.c")), func() { + m.Log(ice.LOG_WARN, "timeout") + m.Back(nil) + }) m.Call(true, func(msg *ice.Message) *ice.Message { // 返回结果 m.Copy(msg).Log("cost", "%s: %s %v", m.Format("cost"), arg[0], arg[1:]) + t.Stop() return nil }) } @@ -714,7 +761,7 @@ var Index = &ice.Context{Name: "web", Help: "网页模块", if len(arg) > 0 { // 规范命名 - if !strings.Contains(arg[0], "-") { + if !strings.Contains(arg[0], "-") || !strings.HasPrefix(arg[0], "20") { arg[0] = m.Time("20060102-") + arg[0] } @@ -748,6 +795,7 @@ var Index = &ice.Context{Name: "web", Help: "网页模块", m.Sort("name") m.Sort("status") }}, + ice.WEB_FAVOR: {Name: "favor", Help: "收藏夹", Meta: kit.Dict("remote", "you", "exports", []string{"hot", "favor"}, "detail", []string{"执行", "编辑", "收录", "下载"}), List: kit.List( kit.MDB_INPUT, "text", "name", "hot", "action", "auto", kit.MDB_INPUT, "text", "name", "id", "action", "auto", @@ -766,6 +814,7 @@ var Index = &ice.Context{Name: "web", Help: "网页模块", }) }) arg = []string{m.Option("hot")} + case "收录": m.Richs(ice.WEB_FAVOR, nil, m.Option("hot"), func(key string, value map[string]interface{}) { m.Grows(ice.WEB_FAVOR, kit.Keys(kit.MDB_HASH, key), "id", id, func(index int, value map[string]interface{}) { @@ -773,8 +822,13 @@ var Index = &ice.Context{Name: "web", Help: "网页模块", }) }) arg = []string{m.Option("hot")} + + case "执行": + m.Event(ice.FAVOR_START, m.Option("you"), kit.Select(m.Option("hot"), arg[3], arg[2] == "favor")) + arg = arg[:0] } } + if len(arg) > 0 { switch arg[0] { case "import": @@ -813,22 +867,15 @@ var Index = &ice.Context{Name: "web", Help: "网页模块", favor := "" if m.Richs(ice.WEB_FAVOR, nil, arg[0], func(key string, value map[string]interface{}) { favor = key - }) == nil { + }) == nil && len(arg) > 1 { favor = m.Rich(ice.WEB_FAVOR, nil, kit.Data(kit.MDB_NAME, arg[0])) - m.Info("create favor: %s name: %s", favor, arg[0]) + m.Log(ice.LOG_CREATE, "favor: %s name: %s", favor, arg[0]) } - extras := []string{} - if len(arg) == 3 && arg[1] == "extra" { - extras, arg = strings.Split(arg[2], " "), arg[:1] - } if len(arg) == 1 { // 收藏列表 m.Grows(ice.WEB_FAVOR, kit.Keys(kit.MDB_HASH, favor), "", "", func(index int, value map[string]interface{}) { m.Push(kit.Format(index), value, []string{kit.MDB_TIME, kit.MDB_ID, kit.MDB_TYPE, kit.MDB_NAME, kit.MDB_TEXT}) - for _, k := range extras { - m.Push(k, kit.Select("", kit.Value(value, "extra."+k))) - } }) return } @@ -887,7 +934,7 @@ var Index = &ice.Context{Name: "web", Help: "网页模块", case "upload", "download": // 打开文件 if m.R != nil { - if f, h, e := m.R.FormFile(kit.Select("upload", arg, 1)); m.Assert(e) { + if f, h, e := m.R.FormFile(kit.Select("upload", arg, 1)); e == nil { defer f.Close() // 创建文件 @@ -983,9 +1030,154 @@ var Index = &ice.Context{Name: "web", Help: "网页模块", // head list data time text file switch arg[0] { - case "catch": + case ice.STORY_PULL: + // 起止节点 + prev, begin, end := "", arg[2], "" + m.Richs(ice.WEB_STORY, "head", arg[2], func(key string, val map[string]interface{}) { + prev, end = kit.Format(val["list"]), kit.Format(kit.Value(val, kit.Keys("remote", arg[1], "pull", "list"))) + }) + + pull := end + var first map[string]interface{} + for begin != end { + if m.Cmd(ice.WEB_SPIDE, arg[1], "/story/pull", "begin", begin, "end", end).Table(func(index int, value map[string]string, head []string) { + if m.Richs(ice.WEB_CACHE, nil, value["data"], nil) == nil { + // 导入缓存 + m.Log(ice.LOG_IMPORT, "%v: %v", value["data"], value["save"]) + if node := kit.UnMarshal(value["save"]); kit.Format(kit.Value(node, "file")) != "" { + m.Cmd(ice.WEB_SPIDE, arg[1], "cache", "GET", "/story/download/"+kit.Format(value["data"])) + } else { + m.Conf(ice.WEB_CACHE, kit.Keys("hash", value["data"]), node) + } + } + + node := kit.UnMarshal(value["node"]).(map[string]interface{}) + if m.Richs(ice.WEB_STORY, nil, value["list"], nil) == nil { + // 导入节点 + m.Log(ice.LOG_IMPORT, "%v: %v", value["list"], value["node"]) + m.Conf(ice.WEB_STORY, kit.Keys("hash", value["list"]), node) + } + + if first == nil { + if m.Richs(ice.WEB_STORY, "head", node["story"], nil) == nil { + // 自动创建 + h := m.Rich(ice.WEB_STORY, "head", kit.Dict( + "scene", node["scene"], "story", node["story"], + "count", node["count"], "list", value["list"], + )) + m.Log(ice.LOG_CREATE, "%v: %v", h, node["story"]) + } + + pull, first = kit.Format(value["list"]), node + m.Richs(ice.WEB_STORY, "head", node["story"], func(key string, val map[string]interface{}) { + prev = kit.Format(val["list"]) + if kit.Int(node["count"]) > kit.Int(kit.Value(val, kit.Keys("remote", arg[1], "pull", "count"))) { + // 更新分支 + m.Log(ice.LOG_IMPORT, "%v: %v", pull, arg[1]) + kit.Value(val, kit.Keys("remote", arg[1], "pull"), kit.Dict( + "time", node["time"], "list", pull, "count", node["count"], + )) + } + }) + } + + if prev == kit.Format(node["prev"]) || prev == kit.Format(node["pull"]) { + // 快速合并 + m.Log(ice.LOG_IMPORT, "%v: %v", pull, arg[2]) + m.Richs(ice.WEB_STORY, "head", node["story"], func(key string, val map[string]interface{}) { + val["count"] = first["count"] + val["time"] = first["time"] + val["list"] = pull + }) + prev = pull + } + + begin = kit.Format(node["prev"]) + }).Append("list") == "" { + break + } + } + if !m.Warn(prev != pull, "unmerge: %s", pull) { + m.Echo(pull) + } + + case ice.STORY_PUSH: + // 更新分支 + m.Cmdx(ice.WEB_STORY, "pull", arg[1:]) + + // 查询索引 + push, list := "", m.Cmd(ice.WEB_STORY, "index", arg[2]).Append("list") + m.Richs(ice.WEB_STORY, "head", arg[2], func(key string, val map[string]interface{}) { + push = kit.Format(kit.Value(val, kit.Keys("remote", arg[1], "push", "list"))) + }) + + // 查询节点 + nodes := []string{} + for list != "" && list != push { + m.Richs(ice.WEB_STORY, nil, list, func(key string, value map[string]interface{}) { + nodes, list = append(nodes, list), kit.Format(value["prev"]) + }) + } + + for _, v := range kit.Revert(nodes) { + m.Richs(ice.WEB_STORY, nil, v, func(list string, node map[string]interface{}) { + m.Richs(ice.WEB_CACHE, nil, node["data"], func(data string, save map[string]interface{}) { + // 推送节点 + m.Log(ice.LOG_EXPORT, "%s: %s", v, kit.Format(node)) + m.Cmd(ice.WEB_SPIDE, arg[1], "/story/push", + "list", v, "node", kit.Format(node), + "data", node["data"], "save", kit.Format(save), + ) + + if kit.Format(save["file"]) != "" { + // 推送缓存 + m.Cmd(ice.WEB_SPIDE, arg[1], "/story/upload", + "part", "upload", "@"+kit.Format(save["file"]), + ) + } + }) + }) + } + + // 更新分支 + m.Cmd(ice.WEB_STORY, "pull", arg[1:]) + + case ice.STORY_WATCH: + msg := m.Cmd(ice.WEB_STORY, "index", arg[1]) + name := kit.Select(arg[1], arg, 2) + os.Rename(name, kit.Keys(name, "bak")) + if msg.Append("file") != "" { + os.Link(msg.Append("file"), name) + m.Log(ice.LOG_EXPORT, "%s: %s", msg.Append("file"), name) + } else { + if f, p, e := kit.Create(name); m.Assert(e) { + defer f.Close() + f.WriteString(msg.Append("text")) + m.Log(ice.LOG_EXPORT, "%s: %s", msg.Append("text"), p) + } + } + + case ice.STORY_CATCH: + if last := m.Richs(ice.WEB_STORY, "head", arg[2], nil); last != nil { + if t, e := time.ParseInLocation(ice.ICE_TIME, kit.Format(last["time"]), time.Local); e == nil { + if s, e := os.Stat(arg[2]); e == nil && s.ModTime().Before(t) { + m.Info("%s last: %s", arg[2], kit.Format(t)) + m.Echo("%s", last["list"]) + break + } + } + } fallthrough - case "add", "upload": + case "add", ice.STORY_UPLOAD: + pull := "" + if arg[0] == ice.STORY_CATCH { + pull, arg = kit.Select("", arg, 3), arg[:3] + m.Richs(ice.WEB_STORY, "head", pull, func(key string, value map[string]interface{}) { + // 合并分支 + pull = kit.Format(kit.Value(value, kit.Keys("remote", pull, "pull", "list"))) + }) + } + // 保存数据 if m.Richs(ice.WEB_CACHE, nil, kit.Select("", arg, 3), nil) == nil { m.Cmdy(ice.WEB_CACHE, arg) @@ -993,33 +1185,40 @@ var Index = &ice.Context{Name: "web", Help: "网页模块", } // 查询索引 - head, prev, count := "", "", 0 - m.Richs(ice.WEB_STORY, "head", arg[2], func(key string, value map[string]interface{}) { - head, prev, count = key, kit.Format(value["list"]), kit.Int(value["count"]) + head, prev, value, count := "", "", map[string]interface{}{}, 0 + m.Richs(ice.WEB_STORY, "head", arg[2], func(key string, val map[string]interface{}) { + head, prev, value, count = key, kit.Format(val["list"]), val, kit.Int(val["count"]) m.Log("info", "head: %v prev: %v count: %v", head, prev, count) }) + if last := m.Richs(ice.WEB_STORY, nil, prev, nil); prev != "" && last != nil && last["data"] == arg[3] { + // 重复提交 + break + } + // 添加节点 list := m.Rich(ice.WEB_STORY, nil, kit.Dict( - "count", count+1, "scene", arg[1], "story", arg[2], "data", arg[3], "prev", prev, + "scene", arg[1], "story", arg[2], "count", count+1, "data", arg[3], "prev", prev, "pull", pull, )) m.Log(ice.LOG_CREATE, "story: %s %s: %s", list, arg[1], arg[2]) m.Push("list", list) + m.Push("pull", pull) - // 添加索引 - m.Rich(ice.WEB_STORY, "head", kit.Dict( - "count", count+1, "scene", arg[1], "story", arg[2], "list", list, - )) - + if head == "" { + // 添加索引 + m.Rich(ice.WEB_STORY, "head", kit.Dict("scene", arg[1], "story", arg[2], "count", count+1, "list", list)) + } else { + // 更新索引 + value["count"] = count + 1 + value["time"] = m.Time() + value["list"] = list + } m.Echo(list) - case "download": + case ice.STORY_DOWNLOAD: // 下载文件 - if m.Cmdy(ice.WEB_STORY, "index", arg[1]); m.Append("file") != "" { - m.Push("_output", "file") - } else { - m.Push("_output", "result") - } + m.Cmdy(ice.WEB_STORY, "index", arg[1]) + m.Push("_output", kit.Select("file", "result", m.Append("file") == "")) case "commit": // 查询索引 @@ -1064,12 +1263,18 @@ var Index = &ice.Context{Name: "web", Help: "网页模块", for i := 0; i < 10 && list != ""; i++ { m.Confm(ice.WEB_STORY, kit.Keys("hash", list), func(value map[string]interface{}) { // 直连节点 - m.Confm(ice.WEB_CACHE, kit.Keys("hash", value["data"]), func(val map[string]interface{}) { - m.Push(list, value, []string{"key", "time", "count", "scene", "story"}) + val := m.Confm(ice.WEB_CACHE, kit.Keys("hash", value["data"])) + if val == nil { + data := kit.Format(value["data"]) + if f, e := os.Open(path.Join(m.Conf(ice.WEB_CACHE, "meta.store"), data[:2], data)); e != nil || + json.NewDecoder(f).Decode(&val) != nil { + return + } + } + m.Push(list, value, []string{"key", "time", "count", "scene", "story"}) - m.Push("drama", val["text"]) - m.Push("data", value["data"]) - }) + m.Push("drama", val["text"]) + m.Push("data", value["data"]) // 复合节点 kit.Fetch(value["list"], func(key string, val string) { @@ -1102,13 +1307,24 @@ var Index = &ice.Context{Name: "web", Help: "网页模块", } // 查询数据 - if node := m.Confm(ice.WEB_CACHE, kit.Keys("hash", arg[1])); node != nil { - m.Push("data", arg[1]) - m.Push(arg[1], node, []string{"text", "time", "size", "type", "name", "file"}) - m.Echo("%s", node["text"]) + node := m.Confm(ice.WEB_CACHE, kit.Keys("hash", arg[1])) + if node == nil { + if f, e := os.Open(path.Join(m.Conf(ice.WEB_CACHE, "meta.store"), arg[1][:2], arg[1])); e != nil || + json.NewDecoder(f).Decode(&node) != nil { + return + } } + m.Push("data", arg[1]) + m.Push(arg[1], node, []string{"text", "time", "size", "type", "name", "file"}) + m.Echo("%s", node["text"]) default: if len(arg) == 1 { + if _, e := os.Stat(arg[0]); e == nil { + if scene := m.Conf(ice.WEB_STORY, kit.Keys("mime", strings.TrimPrefix(path.Ext(arg[0]), "."))); scene != "" { + m.Cmd(ice.WEB_STORY, ice.STORY_CATCH, scene, arg[0]) + } + } + m.Cmd(ice.WEB_STORY, "history", arg).Table(func(index int, value map[string]string, head []string) { m.Push("time", value["time"]) m.Push("list", value["key"]) @@ -1147,24 +1363,28 @@ var Index = &ice.Context{Name: "web", Help: "网页模块", switch arg[0] { case "add": + arg = arg[1:] + fallthrough + default: // 创建共享 extra := kit.Dict() - for i := 4; i < len(arg)-1; i += 2 { + for i := 3; i < len(arg)-1; i += 2 { kit.Value(extra, arg[i], arg[i+1]) } h := m.Rich(ice.WEB_SHARE, nil, kit.Dict( - kit.MDB_TYPE, arg[1], kit.MDB_NAME, arg[2], kit.MDB_TEXT, arg[3], + kit.MDB_TYPE, arg[0], kit.MDB_NAME, arg[1], kit.MDB_TEXT, arg[2], "extra", extra, )) m.Grow(ice.WEB_SHARE, nil, kit.Dict( - kit.MDB_TYPE, arg[1], kit.MDB_NAME, arg[2], kit.MDB_TEXT, arg[3], + kit.MDB_TYPE, arg[0], kit.MDB_NAME, arg[1], kit.MDB_TEXT, arg[2], "share", h, )) m.Log(ice.LOG_CREATE, "share: %s extra: %s", h, kit.Format(extra)) m.Echo(h) } }}, + ice.WEB_ROUTE: {Name: "route", Help: "路由", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { }}, ice.WEB_PROXY: {Name: "proxy", Help: "代理", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { @@ -1174,36 +1394,168 @@ var Index = &ice.Context{Name: "web", Help: "网页模块", ice.WEB_LABEL: {Name: "label", Help: "标签", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { }}, - "/proxy/": {Name: "/proxy/", Help: "代理商", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { - }}, "/share/": {Name: "/share/", Help: "共享链", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { - key := kit.Select("", strings.Split(cmd, "/"), 2) - m.Confm(ice.WEB_SHARE, kit.Keys("hash", key), func(value map[string]interface{}) { - m.Info("share %s %v", key, kit.Format(value)) - switch value["type"] { - case ice.TYPE_STORY: - if m.Cmdy(ice.WEB_STORY, "index", kit.Value(value, "extra.data")).Append("text") == "" { - m.Cmdy(ice.WEB_SPACE, kit.Value(value, "extra.pod"), ice.WEB_STORY, "index", kit.Value(value, "extra.data")) - } - m.Push("_output", kit.Select("file", "result", m.Append("file") == "")) + switch arg[0] { + case "login": + Cookie(m, m.Cmdx(ice.AAA_USER, "login", m.Option("username"), m.Option("password"))) - default: - if m.Cmdy(ice.WEB_STORY, "index", value["data"]); m.Append("file") != "" { - m.Push("_output", "file") - } else { + default: + m.Richs(ice.WEB_SHARE, nil, arg[0], func(key string, value map[string]interface{}) { + m.Info("share %s %v", arg, kit.Format(value)) + switch value["type"] { + case ice.TYPE_STORY: + if m.Cmdy(ice.WEB_STORY, "index", kit.Value(value, "text")).Append("text") == "" { + m.Cmdy(ice.WEB_SPACE, kit.Value(value, "extra.pod"), ice.WEB_STORY, "index", kit.Value(value, "text")) + } + + p := path.Join("tmp/file", m.Append("data")) + if _, e := os.Stat(p); e == nil { + m.Append("_output", "file") + m.Append("file", p) + break + } + + m.Set("result") + m.Render(m.Conf(ice.WEB_SHARE, "meta.template.story.prefix")) + m.Cmdy("web.wiki._text", m.Append("file")) + m.Render(m.Conf(ice.WEB_SHARE, "meta.template.story.suffix")) m.Push("_output", "result") + + if f, p, e := kit.Create(p); e == nil { + defer f.Close() + if n, e := f.WriteString(m.Result()); e == nil { + m.Log(ice.LOG_EXPORT, "%d: %s", n, p) + } + } + + case "qrcode": + m.Push("_output", "qrcode") + m.Push("qrcode", value["text"]) + + default: + if m.Cmdy(ice.WEB_STORY, "index", value["data"]); m.Append("file") != "" { + m.Push("_output", "file") + } else { + m.Push("_output", "result") + } + } + + }) + } + + }}, + "/story/": {Name: "/story/", Help: "故事会", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { + switch arg[0] { + case ice.STORY_PULL: + // 下载节点 + list := m.Cmd(ice.WEB_STORY, "index", m.Option("begin")).Append("list") + for i := 0; i < 10 && list != "" && list != m.Option("end"); i++ { + if m.Richs(ice.WEB_STORY, nil, list, func(key string, value map[string]interface{}) { + // 节点信息 + m.Push("list", key) + m.Push("node", kit.Format(value)) + m.Push("data", value["data"]) + m.Push("save", kit.Format(m.Richs(ice.WEB_CACHE, nil, value["data"], nil))) + list = kit.Format(value["prev"]) + }) == nil { + break } } - }) + case ice.STORY_PUSH: + // 上传节点 + if m.Richs(ice.WEB_CACHE, nil, m.Option("data"), nil) == nil { + // 导入缓存 + m.Log(ice.LOG_IMPORT, "%v: %v", m.Option("data"), m.Option("save")) + node := kit.UnMarshal(m.Option("save")) + m.Conf(ice.WEB_CACHE, kit.Keys("hash", m.Option("data")), node) + } + + node := kit.UnMarshal(m.Option("node")).(map[string]interface{}) + if m.Richs(ice.WEB_STORY, nil, m.Option("list"), nil) == nil { + // 导入节点 + m.Log(ice.LOG_IMPORT, "%v: %v", m.Option("list"), m.Option("node")) + m.Conf(ice.WEB_STORY, kit.Keys("hash", m.Option("list")), node) + } + + if head := m.Richs(ice.WEB_STORY, "head", node["story"], nil); head == nil { + // 自动创建 + h := m.Rich(ice.WEB_STORY, "head", kit.Dict( + "scene", node["scene"], "story", node["story"], + "count", node["count"], "list", m.Option("list"), + )) + m.Log(ice.LOG_CREATE, "%v: %v", h, node["story"]) + } else if head["list"] == kit.Format(node["prev"]) || head["list"] == kit.Format(node["pull"]) { + // 快速合并 + head["list"] = m.Option("list") + head["count"] = node["count"] + head["time"] = node["time"] + } else { + // 推送失败 + } + + case ice.STORY_UPLOAD: + // 上传数据 + m.Cmdy(ice.WEB_CACHE, "upload") + + case ice.STORY_DOWNLOAD: + // 下载数据 + m.Cmdy(ice.WEB_STORY, "index", arg[1]) + m.Push("_output", kit.Select("file", "result", m.Append("file") == "")) + } }}, "/space/": {Name: "/space/", Help: "空间站", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { list := strings.Split(cmd, "/") switch list[2] { + case "login": + m.Option(ice.MSG_SESSID, Cookie(m, m.Cmdx(ice.AAA_USER, "login", m.Option("username"), m.Option("password")))) + return case "share": m.Cmdy(ice.WEB_SHARE, list[3:]) return + case "pull": + node := m.Cmd(ice.WEB_STORY, "index", list[3]).Append("list") + for i := 0; i < 10 && node != "" && node != list[4]; i++ { + m.Confm(ice.WEB_STORY, kit.Keys("hash", node), func(value map[string]interface{}) { + m.Push("list", node) + m.Push("node", kit.Format(value)) + m.Push("data", value["data"]) + m.Push("save", kit.Format(m.Confm(ice.WEB_CACHE, kit.Keys("hash", value["data"])))) + node = kit.Format(value["prev"]) + }) + } + return + case "push": + if m.Confm(ice.WEB_CACHE, kit.Keys("hash", m.Option("data"))) == nil { + // 导入缓存 + m.Log(ice.LOG_IMPORT, "%v: %v", m.Option("data"), m.Option("save")) + node := kit.UnMarshal(m.Option("save")) + m.Conf(ice.WEB_CACHE, kit.Keys("hash", m.Option("data")), node) + } + + node := kit.UnMarshal(m.Option("node")).(map[string]interface{}) + if m.Confm(ice.WEB_STORY, kit.Keys("hash", m.Option("list"))) == nil { + // 导入节点 + m.Log(ice.LOG_IMPORT, "%v: %v", m.Option("list"), m.Option("node")) + m.Conf(ice.WEB_STORY, kit.Keys("hash", m.Option("list")), node) + } + + if head := m.Richs(ice.WEB_STORY, "head", node["story"], nil); head == nil { + // 自动创建 + h := m.Rich(ice.WEB_STORY, "head", kit.Dict( + "count", node["count"], "scene", node["scene"], "story", node["story"], "list", m.Option("list"), + )) + m.Log(ice.LOG_CREATE, "%v: %v", h, node["story"]) + } else if head["list"] == kit.Format(node["prev"]) || head["list"] == kit.Format(node["pull"]) { + // 更新索引 + head["list"] = m.Option("list") + head["count"] = node["count"] + head["time"] = node["time"] + } else { + // 推送失败 + } + return case "upload": m.Cmdy(ice.WEB_CACHE, "upload") return @@ -1214,6 +1566,8 @@ var Index = &ice.Context{Name: "web", Help: "网页模块", } if s, e := websocket.Upgrade(m.W, m.R, nil, m.Confi(ice.WEB_SPACE, "meta.buffer.r"), m.Confi(ice.WEB_SPACE, "meta.buffer.w")); m.Assert(e) { + m.Option("name", strings.Replace(m.Option("name"), ".", "_", -1)) + // 共享空间 share := m.Option("share") if m.Richs(ice.WEB_SHARE, nil, share, nil) == nil { diff --git a/build.sh b/build.sh index 0784d8d6..e34ed997 100755 --- a/build.sh +++ b/build.sh @@ -20,6 +20,8 @@ END [ -f Makefile ] || cat >> Makefile <boot.log && echo -e "\n\nrestarting..." || break + trap HUP hup && while true; do + date && ice.bin \$@ 2>\$ctx_log && echo -e "\n\nrestarting..." || break done } +serve() { + prepare && shutdown && start \$@ +} restart() { [ -e \$ctx_pid ] && kill -2 \`cat \$ctx_pid\` || echo } @@ -58,7 +64,7 @@ shutdown() { [ -e \$ctx_pid ] && kill -3 \`cat \$ctx_pid\` || echo } -cmd=\$1 && [ -n "\$cmd" ] && shift || cmd=start +cmd=\$1 && [ -n "\$cmd" ] && shift || cmd=serve \$cmd \$* END chmod u+x ice.sh @@ -66,7 +72,7 @@ END build() { miss=./ && [ "$1" != "" ] && miss=$1 && shift && mkdir $miss - cd $miss && prepare && go build -o ice.bin main.go && chmod u+x ice.bin && ./ice.sh start + cd $miss && prepare && go build -o ice.bin main.go && chmod u+x ice.bin && ./ice.sh start serve dev } cmd=build && [ "$1" != "" ] && cmd=$1 && shift diff --git a/conf.go b/conf.go index 8cb0b66b..3de590d1 100644 --- a/conf.go +++ b/conf.go @@ -71,6 +71,7 @@ const ( // WEB const ( // LOG LOG_ENABLE = "enable" LOG_IMPORT = "import" + LOG_MODIFY = "modify" LOG_CREATE = "create" LOG_INSERT = "insert" LOG_EXPORT = "export" @@ -97,6 +98,8 @@ const ( // GDB GDB_TIMER = "timer" GDB_EVENT = "event" + SYSTEM_INIT = "system.init" + SERVE_START = "serve.start" SERVE_CLOSE = "serve.close" SPACE_START = "space.start" @@ -105,6 +108,7 @@ const ( // GDB DREAM_CLOSE = "dream.close" USER_CREATE = "user.create" + MISS_CREATE = "miss.create" ) const ( // MDB MDB_REDIS = "redis" @@ -137,6 +141,7 @@ const ( // TYPE TYPE_RIVER = "river" TYPE_STORM = "storm" + TYPE_DRIVE = "drive" TYPE_STORY = "story" TYPE_SHELL = "shell" TYPE_VIMRC = "vimrc" @@ -148,6 +153,20 @@ const ( // FAVOR FAVOR_CHAT = "chat.init" FAVOR_TMUX = "tmux.init" FAVOR_RIVER = "river.init" + FAVOR_START = "favor.start" +) +const ( // STORY + STORY_CATCH = "catch" + STORY_WATCH = "watch" + STORY_STATUS = "status" + STORY_COMMIT = "commit" + STORY_BRANCH = "branch" + STORY_HISTORY = "history" + + STORY_PULL = "pull" + STORY_PUSH = "push" + STORY_UPLOAD = "upload" + STORY_DOWNLOAD = "download" ) var Alias = map[string]string{ diff --git a/core/chat/chat.go b/core/chat/chat.go index 165287df..3e1c34a4 100644 --- a/core/chat/chat.go +++ b/core/chat/chat.go @@ -7,7 +7,7 @@ import ( "github.com/shylinux/toolkits" ) -var Index = &ice.Context{Name: "chat", Help: "聊天模块", +var Index = &ice.Context{Name: "chat", Help: "聊天中心", Caches: map[string]*ice.Cache{}, Configs: map[string]*ice.Config{ ice.CHAT_RIVER: {Name: "river", Help: "群组", Value: kit.Data()}, @@ -16,10 +16,18 @@ var Index = &ice.Context{Name: "chat", Help: "聊天模块", ice.ICE_INIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { m.Cmd(ice.CTX_CONFIG, "load", "chat.json") - if m.Conf(ice.CLI_RUNTIME, "boot.count") == "1" { + m.Watch(ice.SYSTEM_INIT, "web.chat.init") + m.Watch(ice.USER_CREATE, "web.chat./ocean", "spawn", "") + }}, + ice.ICE_EXIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { + m.Cmd(ice.CTX_CONFIG, "save", "chat.json", ice.CHAT_RIVER) + }}, + + "init": {Name: "init", Help: "初始化", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { + if len(m.Confm(ice.CHAT_RIVER, "hash")) == 0 { // 系统群组 - m.Option(ice.MSG_USERNAME, m.Conf(ice.CLI_RUNTIME, "boot.username")) m.Option(ice.MSG_USERROLE, ice.ROLE_ROOT) + m.Option(ice.MSG_USERNAME, m.Conf(ice.CLI_RUNTIME, "boot.username")) river := m.Cmdx("web.chat./ocean", "spawn", "meet", m.Conf(ice.CLI_RUNTIME, "boot.username")) river = m.Cmdx("web.chat./steam", river, "spawn", "miss", "", "", "spide", "", @@ -41,13 +49,7 @@ var Index = &ice.Context{Name: "chat", Help: "聊天模块", m.Cmd(ice.AAA_ROLE, "black", ice.ROLE_VOID, "enable", "dream.停止") } - // 用户群组 - m.Watch(ice.USER_CREATE, "web.chat./ocean", "spawn", "") }}, - ice.ICE_EXIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { - m.Cmd(ice.CTX_CONFIG, "save", "chat.json", ice.CHAT_RIVER) - }}, - ice.WEB_LOGIN: {Name: "login", Help: "登录", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { if len(arg) > 0 { switch arg[0] { @@ -172,6 +174,9 @@ var Index = &ice.Context{Name: "chat", Help: "聊天模块", )) m.Log("insert", "storm: %s %d: %v", arg[1], id, arg[i:i+4]) } + case "share": + m.Cmdy(ice.WEB_SHARE, "add", arg[3:]) + case "rename": // 重命名应用 old := m.Conf(ice.CHAT_RIVER, kit.Keys(prefix, kit.MDB_HASH, arg[1], kit.MDB_META, kit.MDB_NAME)) diff --git a/core/code/code.go b/core/code/code.go index a0ff9158..090d0bd6 100644 --- a/core/code/code.go +++ b/core/code/code.go @@ -33,11 +33,22 @@ var Index = &ice.Context{Name: "code", Help: "编程模块", "compile": {Name: "compile", Help: "编译", Value: kit.Data("path", "usr/publish")}, "publish": {Name: "publish", Help: "发布", Value: kit.Data("path", "usr/publish")}, - "upgrade": {Name: "upgrade", Help: "升级", Value: kit.Data("path", "usr/publish")}, + "upgrade": {Name: "upgrade", Help: "升级", Value: kit.Dict( + kit.MDB_HASH, kit.Dict( + "system", kit.Dict( + kit.MDB_LIST, kit.List( + kit.MDB_INPUT, "bin", "file", "ice.sh", "path", "ice.sh", + kit.MDB_INPUT, "bin", "file", "ice.bin", "path", "ice.bin", + ), + ), + ), + )}, }, Commands: map[string]*ice.Command{ ice.ICE_INIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { m.Cmd(ice.CTX_CONFIG, "load", "code.json") + m.Watch(ice.SYSTEM_INIT, "compile", "linux") + m.Watch(ice.SYSTEM_INIT, "publish", "ice.sh") }}, ice.ICE_EXIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { m.Cmd(ice.CTX_CONFIG, "save", "code.json", "web.code.login") @@ -46,7 +57,7 @@ var Index = &ice.Context{Name: "code", Help: "编程模块", "compile": {Name: "compile", Help: "编译", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { if len(arg) == 0 { // 目录列表 - m.Cmdy("nfs.dir", "", m.Conf("publish", "meta.path")) + m.Cmdy("nfs.dir", "", m.Conf("publish", "meta.path"), "time size path") return } @@ -69,7 +80,7 @@ var Index = &ice.Context{Name: "code", Help: "编程模块", "publish": {Name: "publish", Help: "发布", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { if len(arg) == 0 { // 目录列表 - m.Cmdy("nfs.dir", "", m.Conf("publish", "meta.path")) + m.Cmdy("nfs.dir", "", m.Conf("publish", "meta.path"), "time size path") return } @@ -92,12 +103,28 @@ var Index = &ice.Context{Name: "code", Help: "编程模块", m.Log(ice.LOG_EXPORT, "%s: %s", arg[0], target) }}, "upgrade": {Name: "upgrade", Help: "升级", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { - os.Rename("ice.sh", "ice.sh.bak") - os.Link(m.Cmd(ice.WEB_STORY, "index", m.Cmdx(ice.WEB_SPIDE, "dev", "cache", "/publish/ice.sh")).Append("file"), "ice.sh") + exit := true + m.Grows("upgrade", "hash.system", "", "", func(index int, value map[string]interface{}) { + if value["file"] == "ice.bin" { + value["file"] = kit.Keys("ice", m.Conf(ice.CLI_RUNTIME, "host.GOOS"), m.Conf(ice.CLI_RUNTIME, "host.GOARCH")) + } - os.Rename("ice.bin", "ice.bin.bak") - os.Link(m.Cmd(ice.WEB_STORY, "index", m.Cmdx(ice.WEB_SPIDE, "dev", "cache", kit.Keys("/publish/ice", - m.Conf(ice.CLI_RUNTIME, "host.GOOS"), m.Conf(ice.CLI_RUNTIME, "host.GOARCH")))).Append("file"), "ice.bin") + h := m.Cmdx(ice.WEB_SPIDE, "dev", "cache", "GET", "/publish/"+kit.Format(value["file"])) + if h == "" { + exit = false + return + } + m.Cmd(ice.WEB_STORY, "add", "bin", value["path"], h) + + os.Rename(kit.Format(value["path"]), kit.Keys(value["path"], "bak")) + os.Link(m.Cmd(ice.WEB_STORY, "index", h).Append("file"), kit.Format(value["path"])) + os.Chmod(kit.Format(value["path"]), 777) + m.Log(ice.LOG_EXPORT, "%s: %s", h, value["path"]) + }) + + if exit { + m.Cmd("exit") + } }}, "login": {Name: "login", Help: "登录", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { @@ -162,7 +189,9 @@ var Index = &ice.Context{Name: "code", Help: "编程模块", m.Cmdy("login", "exit") case "upload": // 缓存文件 - msg := m.Cmd(ice.WEB_CACHE, "upload") + you := m.Option("you") + m.Option("you", "") + msg := m.Cmd(ice.WEB_STORY, "upload") m.Echo("data: %s\n", msg.Append("data")) m.Echo("time: %s\n", msg.Append("time")) m.Echo("type: %s\n", msg.Append("type")) @@ -171,6 +200,7 @@ var Index = &ice.Context{Name: "code", Help: "编程模块", m.Push("_output", "result") // 下发文件 + m.Option("you", you) m.Cmd(ice.WEB_SPACE, msg.Option("you"), ice.WEB_SPACE, "download", msg.Append("type"), msg.Append("name"), "self", msg.Append("data")) case "download": diff --git a/core/team/team.go b/core/team/team.go index 06daf503..50134ea1 100644 --- a/core/team/team.go +++ b/core/team/team.go @@ -12,10 +12,20 @@ import ( var Index = &ice.Context{Name: "team", Help: "团队模块", Caches: map[string]*ice.Cache{}, Configs: map[string]*ice.Config{ - ice.APP_MISS: {Name: "miss", Help: "任务", Value: kit.Data()}, + ice.APP_MISS: {Name: "miss", Help: "任务", Value: kit.Data( + "mis", []interface{}{"已取消", "准备中", "开发中", "测试中", "发布中", "已完成"}, "fsm", kit.Dict( + "准备中", kit.Dict("next", "开发中"), + "开发中", kit.Dict("next", "测试中", "prev", "准备中"), + "测试中", kit.Dict("next", "发布中", "prev", "开发中"), + "发布中", kit.Dict("next", "已完成", "prev", "测试中"), + "已完成", kit.Dict(), + "已取消", kit.Dict(), + ), + )}, }, Commands: map[string]*ice.Command{ ice.ICE_INIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { + m.Watch(ice.MISS_CREATE, ice.APP_MISS) m.Cmd(ice.CTX_CONFIG, "load", "team.json") }}, ice.ICE_EXIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { @@ -27,8 +37,11 @@ var Index = &ice.Context{Name: "team", Help: "团队模块", switch arg[1] { case "modify": // 修改任务 - m.Grows(ice.APP_MISS, nil, "id", arg[0], func(index int, value map[string]interface{}) { - value[arg[2]] = arg[3] + m.Richs(ice.WEB_FAVOR, nil, m.Option("hot"), func(key string, value map[string]interface{}) { + m.Grows(ice.WEB_FAVOR, kit.Keys("hash", key), "id", arg[0], func(index int, value map[string]interface{}) { + m.Log(ice.LOG_MODIFY, "%s: %s->%s", arg[2], arg[4], arg[3]) + kit.Value(value, arg[2], arg[3]) + }) }) arg = arg[:0] } @@ -36,22 +49,19 @@ var Index = &ice.Context{Name: "team", Help: "团队模块", if len(arg) == 0 { // 任务列表 - m.Grows(ice.APP_MISS, nil, "", "", func(index int, value map[string]interface{}) { - m.Push(kit.Format(index), value, []string{"begin_time", "close_time", "status", "id", "type", "name", "text"}) + m.Richs(ice.WEB_FAVOR, nil, m.Option("hot"), func(key string, value map[string]interface{}) { + m.Grows(ice.WEB_FAVOR, kit.Keys("hash", key), "", "", func(index int, value map[string]interface{}) { + m.Push(kit.Format(index), value, []string{"extra.begin_time", "extra.close_time", "extra.status", "id", "type", "name", "text"}) + }) }) return } // 添加任务 - h := m.Grow(ice.APP_MISS, nil, kit.Dict( - kit.MDB_NAME, arg[0], - kit.MDB_TYPE, kit.Select("开发", arg, 1), - kit.MDB_TEXT, kit.Select("功能开发", arg, 2), - "status", kit.Select("准备中", arg, 3), + m.Cmdy(ice.WEB_FAVOR, kit.Select("miss", m.Option("hot")), ice.TYPE_DRIVE, arg[0], arg[1], "begin_time", m.Time(), "close_time", m.Time(), - )) - m.Info("miss: %d", h) - m.Echo("%d", h) + "status", kit.Select("准备中", arg, 3), + ) }}, "date": {Name: "date", Help: "日历", Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) { show := map[int]string{0: "周日", 1: "周一", 2: "周二", 3: "周三", 4: "周四", 5: "周五", 6: "周六"} @@ -107,49 +117,45 @@ var Index = &ice.Context{Name: "team", Help: "团队模块", "detail", []string{"回退", "前进", "取消", "完成"}, ), Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) { if len(arg) > 0 { - m.Grows(ice.APP_MISS, nil, "id", arg[0], func(index int, value map[string]interface{}) { - switch value["status"] { - case "准备中": - switch arg[1] { - case "开始": - value["status"] = "进行中" - value["begin_time"] = m.Time() - value["close_time"] = m.Time("30m") + m.Richs(ice.WEB_FAVOR, nil, m.Option("hot"), func(key string, value map[string]interface{}) { + m.Grows(ice.WEB_FAVOR, kit.Keys("hash", key), "id", arg[0], func(index int, value map[string]interface{}) { + switch value = value["extra"].(map[string]interface{}); arg[1] { + case "前进": + if value["status"] == "准备中" { + value["begin_time"] = m.Time() + value["close_time"] = m.Time("30m") + } + if next := m.Conf(ice.APP_MISS, kit.Keys("meta.fsm", value["status"], "next")); next != "" { + value["status"] = next + } + + case "回退": + if prev := m.Conf(ice.APP_MISS, kit.Keys("meta.fsm", value["status"], "prev")); prev != "" { + value["status"] = prev + } + case "取消": value["status"] = "已取消" value["close_time"] = m.Time() + case "完成": value["status"] = "已完成" value["close_time"] = m.Time() } - case "进行中": - switch arg[1] { - case "准备": - value["status"] = "准备中" - value["begin_time"] = m.Time() - value["close_time"] = m.Time() - case "取消": - value["status"] = "已取消" - value["close_time"] = m.Time() - case "完成": - value["status"] = "已完成" - value["close_time"] = m.Time() - } - } + }) }) } - m.Push("准备中", "") - m.Push("开发中", "") - m.Push("测试中", "") - m.Push("发布中", "") - m.Push("已取消", "") - m.Push("已完成", "") - m.Grows(ice.APP_MISS, nil, "", "", func(index int, value map[string]interface{}) { - m.Push(kit.Format(value["status"]), - kit.Format(`%v`, - kit.Format("%s-%s\n%s", value["begin_time"], value["close_time"], value["text"]), - value["id"], value["name"])) + m.Confm(ice.APP_MISS, "meta.mis", func(index int, value string) { + m.Push(value, "") + }) + m.Richs(ice.WEB_FAVOR, nil, m.Option("hot"), func(key string, value map[string]interface{}) { + m.Grows(ice.WEB_FAVOR, kit.Keys("hash", key), "", "", func(index int, value map[string]interface{}) { + m.Push(kit.Format(kit.Value(value, "extra.status")), + kit.Format(`%v`, + kit.Format("%s-%s\n%s", kit.Value(value, "extra.begin_time"), kit.Value(value, "extra.close_time"), value["text"]), + value["id"], value["name"])) + }) }) }}, }, diff --git a/core/wiki/wiki.go b/core/wiki/wiki.go index 2e1639da..5fb4a9c3 100644 --- a/core/wiki/wiki.go +++ b/core/wiki/wiki.go @@ -143,7 +143,7 @@ var Index = &ice.Context{Name: "wiki", Help: "文档模块", // 生成文章 buffer := bytes.NewBuffer([]byte{}) f := m.Target().Server().(*web.Frame) - tmpl := f.HandleCGI(m, m.Confm("note", ice.Meta("alias")), path.Join(m.Conf("note", ice.Meta("path")), arg[0])) + tmpl := f.HandleCGI(m, m.Confm("note", ice.Meta("alias")), arg[0]) m.Assert(tmpl.ExecuteTemplate(buffer, m.Option("filename", path.Base(arg[0])), m)) // 缓存文章 @@ -158,8 +158,8 @@ var Index = &ice.Context{Name: "wiki", Help: "文档模块", m.Echo(string(markdown.ToHTML(buffer.Bytes(), nil, nil))) }}, "_tree": {Name: "_tree path", Help: "文库", Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) { - m.Cmdy("nfs.dir", m.Conf("note", ice.Meta("path")), - kit.Select("", arg, 0), m.Conf("note", ice.Meta("head"))) + m.Cmdy("nfs.dir", m.Conf("note", "meta.path"), + kit.Select("", arg, 0), m.Conf("note", "meta.head")) }}, "note": {Name: "note file", Help: "笔记", Meta: map[string]interface{}{ "remote": "you", "display": "inner", @@ -180,6 +180,9 @@ var Index = &ice.Context{Name: "wiki", Help: "文档模块", } return } + if len(arg) > 0 && strings.HasSuffix(arg[0], ".md") { + arg[0] = path.Join(m.Conf("note", "meta.path"), arg[0]) + } m.Cmdy(kit.Select("_tree", "_text", len(arg) > 0 && strings.HasSuffix(arg[0], ".md")), arg) }}, "index": {Name: "index hash", Help: "索引", Hand: func(m *ice.Message, c *ice.Context, key string, arg ...string) { diff --git a/go.mod b/go.mod index 3f7008d2..2d136764 100644 --- a/go.mod +++ b/go.mod @@ -6,4 +6,5 @@ require ( github.com/gomarkdown/markdown v0.0.0-20191207194928-fbea82c4bb03 github.com/gorilla/websocket v1.4.1 github.com/shylinux/toolkits v0.1.0 + github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086 ) diff --git a/go.sum b/go.sum index 5deecf7a..3abaecc0 100644 --- a/go.sum +++ b/go.sum @@ -6,4 +6,6 @@ github.com/shylinux/toolkits v0.0.0-20191225132906-3c11db083b5b h1:BXDEMcpHmwuwo github.com/shylinux/toolkits v0.0.0-20191225132906-3c11db083b5b/go.mod h1:Y68Ot6xOmo1bun67YvqC3chDGeU2gDxtsUnvVDGJm4g= github.com/shylinux/toolkits v0.1.0 h1:7ghnVEjuwLf7zBsyeR37ahm2gaOKIyjSw9F9Pp9oTBU= github.com/shylinux/toolkits v0.1.0/go.mod h1:Y68Ot6xOmo1bun67YvqC3chDGeU2gDxtsUnvVDGJm4g= +github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086 h1:RYiqpb2ii2Z6J4x0wxK46kvPBbFuZcdhS+CIztmYgZs= +github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086/go.mod h1:PLPIyL7ikehBD1OAjmKKiOEhbvWyHGaNDjquXMcYABo= golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ= diff --git a/misc/git/git.go b/misc/git/git.go index da842a47..407792c0 100644 --- a/misc/git/git.go +++ b/misc/git/git.go @@ -21,7 +21,7 @@ var Index = &ice.Context{Name: "git", Help: "代码管理", "name", "volcanos", "path", "usr/volcanos", "branch", "master", "remote", "https://github.com/shylinux/volcanos", )) - m.Watch(ice.SERVE_START, "cli.git.check", "volcanos") + m.Watch(ice.SYSTEM_INIT, "cli.git.check", "volcanos") }}, ice.ICE_EXIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { }}, diff --git a/misc/tmux/tmux.go b/misc/tmux/tmux.go index 61d541c2..10880af6 100644 --- a/misc/tmux/tmux.go +++ b/misc/tmux/tmux.go @@ -4,6 +4,7 @@ import ( "github.com/shylinux/icebergs" "github.com/shylinux/icebergs/base/cli" "github.com/shylinux/toolkits" + "os" "path" "strings" "time" @@ -52,21 +53,31 @@ var Index = &ice.Context{Name: "tmux", Help: "终端模块", }, Commands: map[string]*ice.Command{ ice.ICE_INIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { + m.Watch(ice.SYSTEM_INIT, "cli.tmux.init") m.Watch(ice.DREAM_START, "cli.tmux.auto") - return - m.Watch(ice.SERVE_START, "cli.tmux.auto") - for _, p := range []string{"auto.sh", "auto.vim"} { - if m.Richs(ice.WEB_STORY, "head", p, nil) == nil { - m.Cmd(ice.WEB_STORY, "add", ice.TYPE_SHELL, p, m.Cmdx(ice.WEB_SPIDE, "shy", "cache", "GET", "/publish/"+p)) - } - } - if m.Richs(ice.WEB_FAVOR, nil, ice.FAVOR_TMUX, nil) == nil { - m.Cmd(ice.WEB_FAVOR, ice.FAVOR_TMUX, ice.TYPE_SHELL, "下载脚本", `curl -s "$ctx_dev/code/zsh?cmd=download&arg=auto.sh" > auto.sh`) - m.Cmd(ice.WEB_FAVOR, ice.FAVOR_TMUX, ice.TYPE_SHELL, "加载脚本", `source auto.sh`) - } + m.Watch(ice.FAVOR_START, "cli.tmux.auto") }}, ice.ICE_EXIT: {Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { }}, + "init": {Name: "init", Help: "初始化", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { + for _, v := range []string{"auto.sh", "auto.vim"} { + p := path.Join(m.Conf("web.code.publish", "meta.path"), v) + if _, e := os.Stat(p); e != nil && os.IsNotExist(e) { + if h := m.Cmdx(ice.WEB_SPIDE, "shy", "cache", "GET", "/publish/"+v); h != "" { + os.Link(m.Cmd(ice.WEB_STORY, "index", h).Append("file"), p) + m.Log(ice.LOG_EXPORT, "%s: %s", h, p) + } + } + } + + if m.Richs(ice.WEB_FAVOR, nil, "tmux.auto", nil) == nil { + m.Cmd(ice.WEB_FAVOR, "tmux.auto", ice.TYPE_SHELL, "下载脚本", `curl -s "$ctx_dev/publish/auto.sh" -o auto.sh`) + m.Cmd(ice.WEB_FAVOR, "tmux.auto", ice.TYPE_SHELL, "加载脚本", `source auto.sh`) + } + if m.Richs(ice.WEB_FAVOR, nil, "tmux.init", nil) == nil { + m.Cmd(ice.WEB_FAVOR, "tmux.init", ice.TYPE_SHELL, "一键启动", `curl -s "$ctx_dev/publish/ice.sh" |sh`) + } + }}, "buffer": {Name: "buffer", Help: "终端", List: kit.List( @@ -224,6 +235,13 @@ var Index = &ice.Context{Name: "tmux", Help: "终端模块", }}, "auto": {Name: "auto", Help: "终端", Hand: func(m *ice.Message, c *ice.Context, cmd string, arg ...string) { prefix := []string{"cli.system", "tmux"} + if arg[0] == "" { + m.Cmd("cli.tmux.session").Table(func(index int, value map[string]string, head []string) { + if value["tag"] == "1" { + arg[0] = value["session"] + } + }) + } m.Option("cmd_env", "TMUX", "") m.Option("cmd_dir", path.Join(m.Conf(ice.WEB_DREAM, "meta.path"), arg[0])) @@ -233,12 +251,12 @@ var Index = &ice.Context{Name: "tmux", Help: "终端模块", m.Cmd(prefix, "new-session", "-ds", arg[0]) } + m.Cmdy(prefix, "send-keys", "-t", arg[0], "export ctx_dev=", kit.Select(m.Conf(ice.CLI_RUNTIME, "conf.ctx_dev"), m.Conf(ice.CLI_RUNTIME, "host.ctx_self")), "Enter") m.Richs(ice.WEB_SPACE, nil, arg[0], func(key string, value map[string]interface{}) { - m.Cmdy(prefix, "send-keys", "-t", arg[0], "export ctx_dev=", kit.Select(m.Conf(ice.CLI_RUNTIME, "host.ctx_dev"), m.Conf(ice.CLI_RUNTIME, "host.ctx_self")), "Enter") m.Cmdy(prefix, "send-keys", "-t", arg[0], "export ctx_share=", value["share"], "Enter") }) - m.Cmd(ice.WEB_FAVOR, kit.Select("tmux.init", arg, 1)).Table(func(index int, value map[string]string, head []string) { + m.Cmd(ice.WEB_FAVOR, kit.Select("tmux.auto", arg, 1)).Table(func(index int, value map[string]string, head []string) { switch value["type"] { case "shell": m.Cmdy(prefix, "send-keys", "-t", arg[0], value["text"], "Enter") diff --git a/miss/go.mod b/miss/go.mod new file mode 100644 index 00000000..f0b362e2 --- /dev/null +++ b/miss/go.mod @@ -0,0 +1,10 @@ +module miss + +go 1.13 + +require github.com/shylinux/icebergs v0.1.0 // indirect + +replace ( + github.com/shylinux/icebergs => ../ + github.com/shylinux/toolkits => ../../toolkits +) diff --git a/miss/miss.md b/miss/miss.md index 4d172f0c..80832b52 100644 --- a/miss/miss.md +++ b/miss/miss.md @@ -2,10 +2,54 @@ icebergs是一个后端框架,通过模块化、集群化实现资源的无限的扩展与自由的组合。 +{{chain "icebergs" ` +icebergs + type.go + msg.Detail + msg.Option + msg.Append + msg.Result + msg.Travel + msg.Search + msg.Conf + msg.Cmd + msg.Cap + base.go bg blue + Begin + _init + Start bg red + code + wiki + chat + ocean + river + action + storm + steam + team + mall + _exit + Close + conf.go + init + host + boot + node + user + work + auth + data + file +` "" "" 16}} + 一键创建项目 ``` mkdir miss; cd miss && curl -s https://shylinux.com/publish/build.sh | sh ``` +一键启动项目 +``` +mkdir miss; cd miss && curl -s https://shylinux.com/publish/ice.sh | sh +``` ## 命令模块 base/cli @@ -14,10 +58,22 @@ cli模块用于与系统进行交互。 - 系统信息 ice.CLI_RUNTIME - 系统命令 ice.CLI_SYSTEM +## 网络模块 base/tcp + +tcp模块用于管理网络的读写 + +## 文件模块 base/nfs + +nfs模块用于管理文件的读写。 + ## 终端模块 base/ssh ssh模块用于与终端交互。 +## 数据模块 base/mdb + +mdb模块用于管理数据的读写。 + ## 日志模块 base/log log模块负责输出日志。 diff --git a/type.go b/type.go index fa2c4bb8..c75b5502 100644 --- a/type.go +++ b/type.go @@ -190,6 +190,12 @@ func (m *Message) Format(key interface{}) string { return kit.FmtTime(kit.Int64(time.Now().Sub(m.time))) case "meta": return kit.Format(m.meta) + case "append": + if len(m.meta["append"]) == 0 { + return fmt.Sprintf("%dx%d %s", 0, len(m.meta["append"]), kit.Format(m.meta["append"])) + } else { + return fmt.Sprintf("%dx%d %s", len(m.meta[m.meta["append"][0]]), len(m.meta["append"]), kit.Format(m.meta["append"])) + } case "time": return m.Time() case "ship": @@ -279,6 +285,8 @@ func (m *Message) Spawn(arg ...interface{}) *Message { source: m.target, target: m.target, + R: m.R, + W: m.W, } if len(arg) > 0 { @@ -331,6 +339,9 @@ func (m *Message) Set(key string, arg ...string) *Message { return m.Add(key, arg...) } func (m *Message) Copy(msg *Message) *Message { + if msg == nil { + return m + } for _, k := range msg.meta[MSG_APPEND] { if kit.IndexOf(m.meta[MSG_APPEND], k) == -1 { m.meta[MSG_APPEND] = append(m.meta[MSG_APPEND], k)