[
  {
    "path": ".gitignore",
    "content": "*.iml\n.idea/\n"
  },
  {
    "path": "README.md",
    "content": "# Go语言实战流媒体视频网站\n\n学习笔记链接：https://alanhou.org/golang-video-streaming/\n\n## 第1章 课程介绍\n\n介绍这门课程大纲，技术堆栈以及环境\n \n* 1-1 prestudy \n* 1-2 课程介绍及知识要点预习\n\n## 第2章 一个例子了解golang工具链\n\n通过一个简单的webservice具体从golang的工具链，到test，全面介绍golang在工程项目里需要掌握的知识点。\n\n* 2-1 一个例子了解golang常用工具链\n* 2-2 golang项目中test的写法\n* 2-3 golang项目中benchmark的写法\n* 2-4 章节总结\n\n## 第3章 流媒体网站架构以及API模块的实现\n\n本章通过实战演练，从网站的整体架构设计，到服务划分，数据库设计，到api模块的实现，全面讲述golang对webservice的实现以及代码分层架构的思想，同时辅以test cases的全程编写与指导，全面了解工程化golang项目的实现。\n\n* 3-1 流媒体网站整体介绍与架构梳理\n* 3-2 api设计与架构 试看\n* 3-3 api实现之详细设计(上)\n* 3-4 api实现之详细设计(中)\n* 3-5 api实现之详细设计(下)\n* 3-6 api之http handler层\n* 3-7 api之数据库层设计\n* 3-8 api之数据库层实现_数据库连接\n* 3-9 api之数据库层实现_实现User\n* 3-10 api之数据库层实现_编写User Test Case\n* 3-11 api之数据库层实现_User部分代码优化\n* 3-12 api之数据库层实现_实现和验证Video\n* 3-13 api之数据库层实现_实现Comments\n* 3-14 api之数据库层实现_Comments Test Case\n* 3-15 api之session处理与实现(上)\n* 3-16 api之session处理与实现(下)\n* 3-17 api之http middleware的实现与handler收尾(上)\n* 3-18 api之http middleware的实现与handler收尾(下)\n\n## 第4章 stream模块\n\n通过stream server的实现过程，着重讲述通过golang实现流式播放，上传文件，以及利用channel实现流控等实用知识点，进一步加深对golang的掌握。\n\n* 4-1 stream server\n* 4-2 streaming的架构搭建\n* 4-3 token bucket\n* 4-4 流控模块的实现 试看\n* 4-5 在http middleware中嵌入流控\n* 4-6 streamHandler实现\n* 4-7 验证streamHandler\n* 4-8 uploadHandler实现\n* 4-9 验证uploadHandler\n## 第5章 scheduler模块\n\n通过对生产者消费者模型在scheduler中的实现，全面了解golang是如何处理并发场景，以及如何在并发场景下通过channel实现消息同步。\n\n* 5-1 scheduler介绍\n* 5-2 代码架构搭建\n* 5-3 runner的生产消费者模型实现\n* 5-4 runner的使用与测试\n* 5-5 task示例的实现\n* 5-6 timer的实现\n* 5-7 api实现以及scheduler完成\n\n## 第6章 前端服务和模版引擎渲染\n\n讲述如何使用golang的模版引擎来渲染html文件，如何通过原生proxy和api两种模式实现后端服务接口透传并避免跨域访问，以及整个前台在实现业务上的js逻辑代码。\n\n* 6-1 大前端和golang模版引擎介绍\n* 6-2 前端代码架构搭建\n* 6-3 静态页面渲染*\n* 6-4 build脚本和homeHandler\n* 6-5 userHomeHandler\n* 6-6 api透传模块实现\n* 6-7 proxy转发的实现\n* 6-8 UI部分的预览\n* 6-9 API service补全与讲解\n* 6-10 UI之html讲解\n* 6-11 js部分实现\n\n## 第7章 网站上云\n\n通过对网站部分架构的改造和代码重构，使之更符合cloud native架构，辅以阿里云计算存储网络等服务，最终实现网站上云，打通网站上线最后一公里。\n\n* 7-1 云原生讲解\n* 7-2 云存储改造之OSS方案分析\n* 7-3 云存储改造之OSS适配\n* 7-4 公共配置实现\n* 7-5 用vendor处理公共配置包\n* 7-6 SLB讲解与配置\n* 7-7 SLB之添加session容错\n* 7-8 ECS云主机和安全组配置\n* 7-9 scheduler的改造\n* 7-10 部署脚本以及db初始化\n* 7-11 部署演示以及完成效果展示\n* 7-12 课程总结（回顾，延伸和优化）\n\ncredit：慕课网"
  },
  {
    "path": "video_server/api/auth.go",
    "content": "package main\n\nimport (\n\t\"golang-streaming/video_server/api/defs\"\n\t\"golang-streaming/video_server/api/session\"\n\t\"net/http\"\n)\n\nvar HEADER_FIELD_SESSION = \"X-Session-Id\"\nvar HEADER_FIELD_UNAME = \"X-User-Name\"\n\nfunc ValidateUserSession(r *http.Request) bool  {\n\tsid := r.Header.Get(HEADER_FIELD_SESSION)\n\tif len(sid) == 0 {\n\t\treturn false\n\t}\n\n\tuname, ok := session.IsSessionExpired(sid)\n\tif ok {\n\t\treturn false\n\t}\n\n\tr.Header.Add(HEADER_FIELD_UNAME, uname)\n\treturn true\n}\n\n\nfunc ValidateUser(w http.ResponseWriter, r *http.Request) bool {\n\tuname := r.Header.Get(HEADER_FIELD_UNAME)\n\tif len(uname) == 0 {\n\t\tsendErrorResponse(w, defs.ErrorNotAuthUser)\n\t\treturn false\n\t}\n\n\treturn true\n}"
  },
  {
    "path": "video_server/api/dbops/api.go",
    "content": "package dbops\n\nimport (\n\t\"golang-streaming/video_server/api/defs\"\n\t\"golang-streaming/video_server/api/utils\"\n\t\"database/sql\"\n\t_ \"github.com/go-sql-driver/mysql\"\n\t\"log\"\n\t\"time\"\n)\n\nfunc AddUserCredential(loginName string, pwd string) error {\n\t stmtIns, err := dbConn.Prepare(\"INSERT INTO users (login_name, pwd) VALUES (?, ?)\")\n\t if err != nil {\n\t \treturn err\n\t }\n\n\t _, err = stmtIns.Exec(loginName, pwd)\n\t if err != nil {\n\t \treturn err\n\t }\n\n\t stmtIns.Close()\n\t return nil\n}\n\nfunc GetUserCredential(loginName string) (string, error)  {\n\tstmtOut, err := dbConn.Prepare(\"SELECT pwd FROM users WHERE login_name = ?\")\n\tif err != nil {\n\t\tlog.Printf(\"%s\", err)\n\t\treturn \"\", err\n\t}\n\n\tvar pwd string\n\terr = stmtOut.QueryRow(loginName).Scan(&pwd)\n\tif err != nil && err != sql.ErrNoRows {\n\t\treturn \"\", err\n\t}\n\n\tdefer stmtOut.Close()\n\n\treturn pwd, nil\n}\n\nfunc DeleteUser(loginName string, pwd string) error {\n\tstmtDel, err := dbConn.Prepare(\"DELETE FROM users WHERE login_name = ? AND pwd = ?\")\n\tif err != nil {\n\t\tlog.Printf(\"DeleteUser error: %s\", err)\n\t\treturn err\n\t}\n\n\t_, err = stmtDel.Exec(loginName, pwd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer stmtDel.Close()\n\treturn nil\n}\n\nfunc GetUser(loginName string) (*defs.User, error) {\n\tstmtOut, err:=dbConn.Prepare(\"SELECT id, pwd FROM users WHERE login_name=?\")\n\tif err!=nil {\n\t\tlog.Printf(\"%s\", err)\n\t\treturn nil, err\n\t}\n\tvar id int\n\tvar pwd string\n\terr=stmtOut.QueryRow(loginName).Scan(&id, &pwd)\n\tif err!= nil && err!=sql.ErrNoRows {\n\t\treturn nil, err\n\t}\n\tif err==sql.ErrNoRows {\n\t\treturn nil, nil\n\t}\n\tres:=&defs.User{Id: id, LoginName: loginName, Pwd: pwd}\n\tdefer stmtOut.Close()\n\treturn res, nil\n}\n\nfunc AddNewVideo(aid int, name string) (*defs.VideoInfo, error) {\n\t// create uuid\n\tvid, err := utils.NewUUID()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tt := time.Now()\n\tctime := t.Format(\"Jan 02 2006, 15:04:05\") // M D y, HH:MM:SS\n\tstmtIns, err := dbConn.Prepare(`INSERT INTO video_info \n\t(id, author_id, name, display_ctime) VALUES(?, ?, ?, ?)`)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, err = stmtIns.Exec(vid, aid, name, ctime)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tres := &defs.VideoInfo{Id: vid, AuthorId: aid, Name: name, DisplayCtime:ctime}\n\n\tdefer stmtIns.Close()\n\treturn res, nil\n}\n\nfunc GetVideoInfo(vid string) (*defs.VideoInfo, error) {\n\t// create uuid\n\tstmtOut, err := dbConn.Prepare(\"SELECT  author_id, name, display_ctime FROM video_info WHERE id=?\")\n\n\tvar aid int\n\tvar dct string\n\tvar name string\n\n\terr = stmtOut.QueryRow(vid).Scan(&aid, &name, &dct)\n\tif err != nil && err != sql.ErrNoRows {\n\t\treturn nil, err\n\t}\n\n\tif err == sql.ErrNoRows {\n\t\treturn nil, nil\n\t}\n\n\tdefer stmtOut.Close()\n\n\tres := &defs.VideoInfo{Id: vid, AuthorId: aid, Name: name, DisplayCtime: dct}\n\n\treturn res, nil\n}\n\nfunc DeleteVideoInfo(vid string) error {\n\tstmtDel, err := dbConn.Prepare(\"DELETE FROM video_info WHERE id = ?\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = stmtDel.Exec(vid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer stmtDel.Close()\n\treturn nil\n}\n\nfunc AddNewComments(vid string, aid int, content string) error {\n\tid, err := utils.NewUUID()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tstmtIns, err := dbConn.Prepare(\"INSERT INTO comments (id, video_id, author_id, content) VALUES (?, ?, ?, ?)\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = stmtIns.Exec(id, vid, aid, content)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer stmtIns.Close()\n\treturn nil\n}\n\nfunc ListComments(vid string, from, to int) ([]*defs.Comment, error) {\n\tstmtOut, err := dbConn.Prepare(`SELECT comments.id, users.login_name, comments.content FROM comments\n\t\tINNER JOIN users ON comments.author_id = users.id\n\t\tWHERE comments.video_id = ? AND comments.time > FROM_UNIXTIME(?) AND comments.time <= FROM_UNIXTIME(?)\n\t\tORDER BY comments.time DESC`)\n\n\tvar res []*defs.Comment\n\n\trows, err := stmtOut.Query(vid, from, to)\n\tif err != nil {\n\t\treturn res, err\n\t}\n\n\tfor rows.Next() {\n\t\tvar id, name, content string\n\t\tif err := rows.Scan(&id, &name, &content); err != nil {\n\t\t\treturn res, err\n\t\t}\n\n\t\tc := &defs.Comment{Id: id, VideoId: vid, Author: name, Content: content}\n\t\tres = append(res, c)\n\t}\n\n\tdefer stmtOut.Close()\n\n\treturn res, nil\n}\n\nfunc ListVideoInfo(uname string, from, to int) ([]*defs.VideoInfo, error) {\n\tstmtOut, err:=dbConn.Prepare(`SELECT video_info.id, video_info.author_id, video_info.name, video_info.display_ctime FROM video_info\n\t\tINNER JOIN users ON video_info.author_id = users.id\n\t\tWHERE users.login_name=? AND video_info.create_time > FROM_UNIXTIME(?) AND video_info.create_time<=FROM_UNIXTIME(?)\n\t\tOREDER BY video_info.create_time DESC`)\n\tvar res []*defs.VideoInfo\n\tif err!=nil{\n\t\treturn res, err\n\t}\n\trows, err:=stmtOut.Query(uname, from, to)\n\tif err!=nil {\n\t\tlog.Printf(\"%s\", err)\n\t\treturn res, err\n\t}\n\n\tfor rows.Next() {\n\t\tvar id, name, ctime string\n\t\tvar aid int\n\t\tif err:=rows.Scan(&id, &aid, &name, &ctime); err!=nil{\n\t\t\treturn res, err\n\t\t}\n\t\tvi:=&defs.VideoInfo{Id:id, AuthorId:aid, Name:name, DisplayCtime: ctime}\n\t\tres = append(res, vi)\n\t}\n\tdefer stmtOut.Close()\n\treturn res, nil\n}"
  },
  {
    "path": "video_server/api/dbops/api_test.go",
    "content": "package dbops\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n)\n\n//init(dblogin, truncate tables) -> run tests -> clear data(truncate tables)\n\nvar tempvid string\n\nfunc clearTables()  {\n\tdbConn.Exec(\"truncate users\")\n\tdbConn.Exec(\"truncate video_info\")\n\tdbConn.Exec(\"truncate comments\")\n\tdbConn.Exec(\"truncate sessions\")\n}\n\nfunc TestMain(m *testing.M)  {\n\tclearTables()\n\tm.Run()\n\tclearTables()\n}\n\nfunc TestUserWorkFlow(t *testing.T)  {\n\tt.Run(\"Add\", testAddUser)\n\tt.Run(\"Get\", testGetUser)\n\tt.Run(\"Del\", testDeleteUser)\n\tt.Run(\"Reget\", testRegetUser)\n\n}\n\nfunc testAddUser(t *testing.T) {\n\terr := AddUserCredential(\"alan\", \"123\")\n\tif err != nil {\n\t\tt.Errorf(\"Error of AddUser: %v\", err)\n\t}\n}\n\nfunc testGetUser(t *testing.T) {\n\tpwd, err := GetUserCredential(\"alan\")\n\tif pwd != \"123\" || err != nil {\n\t\tt.Errorf(\"Error of GetUser\")\n\t}\n}\n\nfunc testDeleteUser(t *testing.T) {\n\terr := DeleteUser(\"alan\", \"123\")\n\tif err != nil {\n\t\tt.Errorf(\"Error of DeleteUser: %v\", err)\n\t}\n}\n\nfunc testRegetUser(t *testing.T) {\n\tpwd, err := GetUserCredential(\"alan\")\n\tif err != nil {\n\t\tt.Errorf(\"Error of RegetUser: %v\", err)\n\t}\n\n\tif pwd != \"\" {\n\t\tt.Errorf(\"Deleting user test failed\")\n\t}\n}\n\nfunc TestVideoWorkFlow(t *testing.T) {\n\tclearTables()\n\tt.Run(\"PrepareUser\", testAddUser)\n\tt.Run(\"AddVideo\", testAddVideoInfo)\n\tt.Run(\"GetVideo\", testGetVideoInfo)\n\tt.Run(\"DelVideo\", testDeleteVideoInfo)\n\tt.Run(\"RegetVideo\", testRegetVideoInfo)\n}\n\nfunc testAddVideoInfo(t *testing.T)  {\n\tvi, err := AddNewVideo(1, \"my-video\")\n\tif err != nil {\n\t\tt.Errorf(\"Error of AddVideoInfo: %v\", err)\n\t}\n\ttempvid = vi.Id\n}\n\nfunc testGetVideoInfo(t *testing.T)  {\n\t_, err := GetVideoInfo(tempvid)\n\tif err != nil {\n\t\tt.Errorf(\"Error of GetVideoInfo: %v\", err)\n\t}\n}\n\nfunc testDeleteVideoInfo(t *testing.T)  {\n\terr := DeleteVideoInfo(tempvid)\n\tif err != nil {\n\t\tt.Errorf(\"Error of DeleteVideoInfo: %v\", err)\n\t}\n}\n\nfunc testRegetVideoInfo(t *testing.T)  {\n\tvi, err := GetVideoInfo(tempvid)\n\tif err != nil || vi != nil {\n\t\tt.Errorf(\"Error of RegetVideoInfo: %v\", err)\n\t}\n}\n\nfunc TestComments(t *testing.T) {\n\tclearTables()\n\tt.Run(\"AddUser\", testAddUser)\n\tt.Run(\"AddComments\", testAddComments)\n\tt.Run(\"ListComments\", testListComments)\n}\n\nfunc testAddComments(t *testing.T) {\n\tvid := \"12345\"\n\taid := 1\n\tcontent := \"I like this video\"\n\n\terr := AddNewComments(vid, aid, content)\n\n\tif err != nil {\n\t\tt.Errorf(\"Error of AddComments: %v\", err)\n\t}\n}\n\nfunc testListComments(t *testing.T) {\n\tvid := \"12345\"\n\tfrom := 1560960000\n\tto, _  := strconv.Atoi(strconv.FormatInt(time.Now().UnixNano()/1000000000, 10))\n\n\tres, err := ListComments(vid, from ,to)\n\tif err != nil {\n\t\tt.Errorf(\"Error of ListComments: %v\", err)\n\t}\n\n\tfor i, ele := range res {\n\t\tfmt.Printf(\"comment: %d, %v \\n\", i, ele)\n\t}\n}\n"
  },
  {
    "path": "video_server/api/dbops/conn.go",
    "content": "package dbops\n\nimport (\n\t\"database/sql\"\n\t_ \"github.com/go-sql-driver/mysql\"\n)\n\nvar (\n\tdbConn *sql.DB\n\terr error\n)\n\nfunc init()  {\n\tdbConn, err = sql.Open(\"mysql\", \"root:@tcp(127.0.0.1:3306)/video_server?charset=utf8\")\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n}\n"
  },
  {
    "path": "video_server/api/dbops/internal.go",
    "content": "package dbops\n\nimport (\n\t\"golang-streaming/video_server/api/defs\"\n\t\"database/sql\"\n\t\"log\"\n\t\"strconv\"\n\t\"sync\"\n)\n\nfunc InsertSession(sid string, ttl int64, uname string) error {\n\tttlstr := strconv.FormatInt(ttl, 10)\n\tstmtIns, err := dbConn.Prepare(\"INSERT INTO sessions (session_id, TTL, login_name) VALUE (?, ?, ?)\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = stmtIns.Exec(sid, ttlstr, uname)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer stmtIns.Close()\n\treturn nil\n}\n\nfunc RetrieveSession(sid string) (*defs.SimpleSession, error) {\n\tss := &defs.SimpleSession{}\n\tstmtOut, err := dbConn.Prepare(\"SELECT TTL, login_name from sessions WHERE session_id = ?\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar ttl string\n\tvar uname string\n\tstmtOut.QueryRow(sid).Scan(&ttl, &uname)\n\tif err != nil && err != sql.ErrNoRows {\n\t\treturn nil, err\n\t}\n\n\tif res, err := strconv.ParseInt(ttl, 10, 64); err == nil {\n\t\tss.TTL = res\n\t\tss.Username = uname\n\t} else {\n\t\treturn nil, err\n\t}\n\n\tdefer stmtOut.Close()\n\treturn ss, nil\n}\n\nfunc RetrieveAllSessions() (*sync.Map, error) {\n\tm := &sync.Map{}\n\tstmtOut, err := dbConn.Prepare(\"SELECT * FROM sessions\")\n\tif err != nil {\n\t\tlog.Printf(\"%s\", err)\n\t\treturn nil, err\n\t}\n\n\trows, err := stmtOut.Query()\n\tif err != nil {\n\t\tlog.Printf(\"%s\", err)\n\t\treturn nil, err\n\t}\n\n\tfor rows.Next() {\n\t\tvar id string\n\t\tvar ttlstr string\n\t\tvar login_name string\n\t\tif err := rows.Scan(&id, &ttlstr, &login_name); err != nil {\n\t\t\tlog.Printf(\"retrieve sessions error: %s\", err)\n\t\t\tbreak\n\t\t}\n\n\t\tif ttl, err1 := strconv.ParseInt(ttlstr, 10, 64); err1 == nil {\n\t\t\tss := &defs.SimpleSession{Username: login_name, TTL: ttl}\n\t\t\tm.Store(id, ss)\n\t\t\tlog.Printf(\"session id: 5s, ttl: %d\", id, ss.TTL)\n\t\t}\n\t}\n\n\treturn m, nil\n}\n\nfunc DeleteSession(sid string) error {\n\tstmtOut, err := dbConn.Prepare(\"DELETE FROM sessions WHERE session_id = ?\")\n\tif err != nil {\n\t\tlog.Printf(\"%s\", err)\n\t\treturn err\n\t}\n\n\tif _, err := stmtOut.Query(sid); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}"
  },
  {
    "path": "video_server/api/defs/apidef.go",
    "content": "package defs\n\n//requests\ntype UserCredential struct {\n\tUsername string `json:\"username\"`\n\tPwd string `json:\"pwd\"`\n}\n\ntype NewComment struct {\n\tAuthorId int `json:\"author_id\"`\n\tContent string `json:\"content\"`\n}\n\ntype NewVideo struct {\n\tAuthorId int `json:\"author_id\"`\n\tName string `json:\"name\"`\n}\n\n// response\ntype SignedUp struct {\n\tSuccess bool `json:\"success\"`\n\tSessionId string `json:\"session_id\"`\n}\n\ntype UserSession struct {\n\tUsername string `json:\"user_name\"`\n\tSessionId string `json:\"session_id\"`\n}\n\ntype UserInfo struct {\n\tId int `json:\"id\"\"`\n}\n\ntype SignedIn struct {\n\tSuccess bool `json:\"success\"`\n\tSessionId string `json:\"session_id\"`\n}\n\ntype VideosInfo struct {\n\tVideos []*VideoInfo `json:\"videos\"`\n}\n\ntype Comments struct {\n\tComments []*Comment `json:\"comments\"`\n}\n\n// Data model\n\ntype User struct {\n\tId int\n\tLoginName string\n\tPwd string\n}\n\ntype VideoInfo struct {\n\tId string `json:\"id\"`\n\tAuthorId int `json:\"author_id\"`\n\tName string `json:\"name\"`\n\tDisplayCtime string `json:\"display_ctime\"`\n}\n\ntype Comment struct {\n\tId string `json:\"id\"`\n\tVideoId string `json:\"video_id\"`\n\tAuthor string `json:\"author\"`\n\tContent string `json:\"content\"`\n}\n\ntype SimpleSession struct {\n\tUsername string // login name\n\tTTL int64\n}\n"
  },
  {
    "path": "video_server/api/defs/errs.go",
    "content": "package defs\n\ntype Err struct {\n\tError string `json:\"error\"`\n\tErrorCode string `json:\"error_code\"`\n}\n\ntype ErrResponse struct {\n\tHttpSC int\n\tError Err\n}\n\nvar (\n\tErrorRequestBodyParseFailed = ErrResponse{HttpSC: 400, Error: Err{Error: \"Request body is not correct\", ErrorCode: \"001\"}}\n\tErrorNotAuthUser = ErrResponse{HttpSC: 401, Error: Err{Error: \"User authentication failed\", ErrorCode: \"002\"}}\n\tErrorDBError = ErrResponse{HttpSC: 500, Error: Err{Error: \"DB ops failed\", ErrorCode: \"003\"}}\n\tErrorInternalFaults = ErrResponse{HttpSC: 500, Error: Err{Error: \"Internal service error\", ErrorCode: \"004\"}}\n)"
  },
  {
    "path": "video_server/api/handlers.go",
    "content": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"github.com/julienschmidt/httprouter\"\n\t\"golang-streaming/video_server/api/dbops\"\n\t\"golang-streaming/video_server/api/defs\"\n\t\"golang-streaming/video_server/api/session\"\n\t\"golang-streaming/video_server/api/utils\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net/http\"\n)\n\nfunc CreateUser(w http.ResponseWriter, r *http.Request, p httprouter.Params) {\n\tres, _ := ioutil.ReadAll(r.Body)\n\tubody := &defs.UserCredential{}\n\n\tif err := json.Unmarshal(res, ubody); err != nil {\n\t\tsendErrorResponse(w, defs.ErrorRequestBodyParseFailed)\n\t\treturn\n\t}\n\n\tif err := dbops.AddUserCredential(ubody.Username, ubody.Pwd); err != nil {\n\t\tsendErrorResponse(w, defs.ErrorDBError)\n\t\treturn\n\t}\n\n\tid := session.GenerateNewSessionId(ubody.Username)\n\tsu := &defs.SignedUp{Success: true, SessionId: id}\n\n\tif resp, err := json.Marshal(su); err != nil {\n\t\tsendErrorResponse(w, defs.ErrorInternalFaults)\n\t\treturn\n\t} else {\n\t\tsendNormalResponse(w, string(resp), 201)\n\t}\n}\n\nfunc Login(w http.ResponseWriter, r *http.Request, p httprouter.Params) {\n\tres, _ := ioutil.ReadAll(r.Body)\n\tlog.Printf(\"%s\", res)\n\tubody := &defs.UserCredential{}\n\tif err := json.Unmarshal(res, ubody); err != nil {\n\t\tlog.Printf(\"%s\", err)\n\t\t// io.WriteString(w, \"wrong\")\n\t\tsendErrorResponse(w, defs.ErrorRequestBodyParseFailed)\n\t\treturn\n\t}\n\n\t// Validate the request body\n\tuname := p.ByName(\"username\")\n\tlog.Printf(\"Login url name: %s\", uname)\n\tlog.Printf(\"Login body name: %s\", ubody.Username)\n\tif uname != ubody.Username {\n\t\tsendErrorResponse(w, defs.ErrorNotAuthUser)\n\t\treturn\n\t}\n\n\tlog.Printf(\"%s\", ubody.Username)\n\tpwd, err := dbops.GetUserCredential(ubody.Username)\n\tlog.Printf(\"Login pwd: %s\", pwd)\n\tif err != nil || len(pwd) == 0 || pwd != ubody.Pwd {\n\t\tsendErrorResponse(w, defs.ErrorNotAuthUser)\n\t\treturn\n\t}\n\n\tid := session.GenerateNewSessionId(ubody.Username)\n\tsi := &defs.SignedIn{Success: true, SessionId: id}\n\tif resp, err := json.Marshal(si); err != nil {\n\t\tsendErrorResponse(w, defs.ErrorInternalFaults)\n\t} else {\n\t\tsendNormalResponse(w, string(resp), 200)\n\t}\n\n\t//io.WriteString(w, \"signed in\")\n}\n\nfunc GetUserInfo(w http.ResponseWriter, r *http.Request, p httprouter.Params)  {\n\tif !ValidateUser(w, r) {\n\t\tlog.Printf(\"Unauthorized user \\n\")\n\t}\n\n\tuname := p.ByName(\"username\")\n\tu, err := dbops.GetUser(uname)\n\tif err != nil {\n\t\tlog.Printf(\"Erorr in GetUserinfo: %s\", err)\n\t\tsendErrorResponse(w, defs.ErrorDBError)\n\t\treturn\n\t}\n\n\tui := &defs.UserInfo{Id: u.Id}\n\tif resp, err := json.Marshal(ui); err != nil {\n\t\tsendErrorResponse(w, defs.ErrorInternalFaults)\n\t} else {\n\t\tsendNormalResponse(w, string(resp), 200)\n\t}\n\n}\n\nfunc AddNewVideo(w http.ResponseWriter, r *http.Request, p httprouter.Params)  {\n\tif !ValidateUser(w, r) {\n\t\tlog.Printf(\"Unauthorized user \\n\")\n\t\treturn\n\t}\n\n\tres, _ := ioutil.ReadAll(r.Body)\n\tnvbody := &defs.NewVideo{}\n\tif err := json.Unmarshal(res, nvbody); err != nil {\n\t\tlog.Printf(\"%s\", err)\n\t\tsendErrorResponse(w, defs.ErrorRequestBodyParseFailed)\n\t\treturn\n\t}\n\n\tvi, err := dbops.AddNewVideo(nvbody.AuthorId, nvbody.Name)\n\tlog.Printf(\"Author id : %d, name: %s \\n\", nvbody.AuthorId, nvbody.Name)\n\tif err != nil {\n\t\tlog.Printf(\"Error in AddNewVideo: 5s\", err)\n\t\tsendErrorResponse(w, defs.ErrorDBError)\n\t\treturn\n\t}\n\n\tif resp, err := json.Marshal(vi); err != nil {\n\t\tsendErrorResponse(w, defs.ErrorInternalFaults)\n\t} else {\n\t\tsendNormalResponse(w, string(resp), 201)\n\t}\n}\n\nfunc ListAllVideos(w http.ResponseWriter, r *http.Request, p httprouter.Params) {\n\tif !ValidateUser(w ,r) {\n\t\treturn\n\t}\n\n\tuname := p.ByName(\"username\")\n\tvs, err := dbops.ListVideoInfo(uname, 0, utils.GetCurrentTimestampSec())\n\tif err != nil {\n\t\tlog.Printf(\"Error in ListAllVideos: %s\", err)\n\t\tsendErrorResponse(w, defs.ErrorDBError)\n\t\treturn\n\t}\n\n\tvsi := &defs.VideosInfo{Videos: vs}\n\tif resp, err := json.Marshal(vsi); err != nil {\n\t\tsendErrorResponse(w, defs.ErrorInternalFaults)\n\t} else {\n\t\tsendNormalResponse(w, string(resp), 200)\n\t}\n}\n\nfunc DeleteVideo(w http.ResponseWriter, r *http.Request, p httprouter.Params) {\n\tif !ValidateUser(w ,r) {\n\t\treturn\n\t}\n\n\tvid := p.ByName(\"vid-id\")\n\terr := dbops.DeleteVideoInfo(vid)\n\tif err != nil {\n\t\tlog.Printf(\"Error in DeleteVideo: %s\", err)\n\t\tsendErrorResponse(w, defs.ErrorDBError)\n\t\treturn\n\t}\n\n\tsendNormalResponse(w, \"\", 204)\n}\n\nfunc PostComment(w http.ResponseWriter, r *http.Request, p httprouter.Params) {\n\tif ValidateUser(w, r) {\n\t\treturn\n\t}\n\n\treqBody, _ := ioutil.ReadAll(r.Body)\n\n\tcbody := &defs.NewComment{}\n\tif err := json.Unmarshal(reqBody, cbody); err != nil {\n\t\tlog.Printf(\"%s\", err)\n\t\tsendErrorResponse(w, defs.ErrorRequestBodyParseFailed)\n\t\treturn\n\t}\n\n\tvid := p.ByName(\"vid-id\")\n\tif err := dbops.AddNewComments(vid, cbody.AuthorId, cbody.Content); err != nil {\n\t\tlog.Printf(\"Error in PostComment: %s\", err)\n\t\tsendErrorResponse(w, defs.ErrorDBError)\n\t} else {\n\t\tsendNormalResponse(w, \"ok\", 201)\n\t}\n}\n\nfunc ShowComments(w http.ResponseWriter, r *http.Request, p httprouter.Params) {\n\tif !ValidateUser(w, r) {\n\t\treturn\n\t}\n\n\tvid := p.ByName(\"vid-id\")\n\tcm, err := dbops.ListComments(vid, 0, utils.GetCurrentTimestampSec())\n\tif err != nil {\n\t\tlog.Printf(\"Error in ShowComments: %s\", err)\n\t\tsendErrorResponse(w, defs.ErrorDBError)\n\t\treturn\n\t}\n\n\tcms := &defs.Comments{Comments: cm}\n\tif resp, err := json.Marshal(cms); err != nil {\n\t\tsendErrorResponse(w, defs.ErrorInternalFaults)\n\t} else {\n\t\tsendNormalResponse(w, string(resp), 200)\n\t}\n}"
  },
  {
    "path": "video_server/api/main.go",
    "content": "package main\n\nimport (\n\t\"github.com/julienschmidt/httprouter\"\n\t\"golang-streaming/video_server/api/session\"\n\t\"net/http\"\n)\n\ntype middleWareHandler struct {\n\tr *httprouter.Router\n}\n\nfunc NewMiddleWareHandler(r *httprouter.Router) http.Handler {\n\tm := middleWareHandler{}\n\tm.r = r\n\treturn m\n}\n\nfunc (m middleWareHandler) ServeHTTP(w http.ResponseWriter, r * http.Request) {\n\tValidateUserSession(r)\n\n\tm.r.ServeHTTP(w, r)\n}\n\nfunc RegisterHandlers() *httprouter.Router {\n\trouter := httprouter.New()\n\n\trouter.POST(\"/user\", CreateUser)\n\n\trouter.POST(\"/user/:username\", Login)\n\n\trouter.GET(\"/user/:username\", GetUserInfo)\n\n\trouter.POST(\"/user/:username/videos\", AddNewVideo)\n\n\trouter.GET(\"/user/:username/videos\", ListAllVideos)\n\n\trouter.DELETE(\"/user/:username/videos/:vid-id\", DeleteVideo)\n\n\trouter.POST(\"/videos/:vid-id/comments\", PostComment)\n\n\trouter.GET(\"/videos/:vid-id/comments\", ShowComments)\n\n\treturn router\n}\n\nfunc  Prepare()  {\n\tsession.LoadSessionsFromDB()\n}\n\nfunc main() {\n\tPrepare()\n\tr := RegisterHandlers()\n\tmh := NewMiddleWareHandler(r)\n\thttp.ListenAndServe(\":8000\", mh)\n}\n\n"
  },
  {
    "path": "video_server/api/response.go",
    "content": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"golang-streaming/video_server/api/defs\"\n\t\"io\"\n\t\"net/http\"\n)\n\nfunc sendErrorResponse(w http.ResponseWriter, errResp defs.ErrResponse) {\n\tw.WriteHeader(errResp.HttpSC)\n\n\tresStr, _ := json.Marshal(&errResp.Error)\n\tio.WriteString(w, string(resStr))\n}\n\nfunc sendNormalResponse(w http.ResponseWriter, resp string, sc int) {\n\tw.WriteHeader(sc)\n\tio.WriteString(w, resp)\n}"
  },
  {
    "path": "video_server/api/session/ops.go",
    "content": "package session\n\nimport (\n\t\"golang-streaming/video_server/api/dbops\"\n\t\"golang-streaming/video_server/api/defs\"\n\t\"golang-streaming/video_server/api/utils\"\n\t\"sync\"\n\t\"time\"\n)\n\nvar sessionMap *sync.Map\n\nfunc init()  {\n\tsessionMap = &sync.Map{}\n}\n\nfunc noInMilli() int64  {\n\treturn time.Now().UnixNano()/1000000\n}\n\nfunc deleteExpiredSession(sid string) {\n\tsessionMap.Delete(sid)\n\tdbops.DeleteSession(sid)\n}\n\nfunc LoadSessionsFromDB() {\n\tr, err := dbops.RetrieveAllSessions()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tr.Range(func(k, v interface{}) bool {\n\t\tss := v.(*defs.SimpleSession)\n\t\tsessionMap.Store(k, ss)\n\t\treturn true\n\t})\n}\n\nfunc GenerateNewSessionId(un string) string {\n\tid, _ := utils.NewUUID()\n\tct := noInMilli()\n\tttl := ct + 30 * 60 * 1000 // Server side session valid time: 30 min\n\n\tss := &defs.SimpleSession{Username: un, TTL: ttl}\n\tsessionMap.Store(id, ss)\n\tdbops.InsertSession(id, ttl, un)\n\n\treturn id\n}\n\nfunc IsSessionExpired(sid string) (string, bool) {\n\tss, ok := sessionMap.Load(sid)\n\tif ok {\n\t\tct := noInMilli()\n\t\tif ss.(*defs.SimpleSession).TTL < ct {\n\t\t\tdeleteExpiredSession(sid)\n\t\t\treturn \"\", true\n\t\t}\n\n\t\treturn ss.(*defs.SimpleSession).Username, false\n\t}\n\n\treturn \"\", true\n}"
  },
  {
    "path": "video_server/api/utils/uuid.go",
    "content": "package utils\n\nimport (\n\t\"crypto/rand\"\n\t\"fmt\"\n\t\"io\"\n\t\"strconv\"\n\t\"time\"\n)\n\nfunc NewUUID() (string, error) {\n\tuuid := make([]byte, 16)\n\tn, err := io.ReadFull(rand.Reader, uuid)\n\tif n != len(uuid) || err != nil {\n\t\treturn \"\", err\n\t}\n\t// variant bits; see section 4.1.1\n\tuuid[8] = uuid[8]&^0xc0 | 0x80\n\t// version 4 (pseudo-random); see section 4.1.3\n\tuuid[6] = uuid[6]&^0xf0 | 0x40\n\treturn fmt.Sprintf(\"%x-%x-%x-%x-%x\", uuid[0:4], uuid[4:6],uuid[6:8], uuid[8:10], uuid[10:]), nil\n}\n\nfunc GetCurrentTimestampSec() int {\n\tts, _ := strconv.Atoi(strconv.FormatInt(time.Now().UnixNano()/1000000000, 10))\n\treturn ts\n}"
  },
  {
    "path": "video_server/build.sh",
    "content": "#! /bin/bash\n\n# Build web UI\ncd ~/go/src/golang-streaming/video_server/web/\ngo install\ncp ~/go/bin/web ~/go/bin/video_server_web_ui/web\ncp -R ~/go/src/golang-streaming/video_server/templates ~/go/bin/video_server_web_ui/web"
  },
  {
    "path": "video_server/scheduler/dbops/api.go",
    "content": "package dbops\n\nimport \"log\"\n\n//1. user -> api service -> delete video\n//2. api service -> scheduler -> write video deletion record\n//3. timer\n//4. timer -> runner -> read wvdr -> exec -> delete video form folder\n\nfunc AddVideoDeletionRecord(vid string) error {\n\tstmtIns, err := dbConn.Prepare(\"INSERT INTO video_del_rec (video_id) VALUES (?)\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = stmtIns.Exec(vid)\n\tif err != nil {\n\t\tlog.Printf(\"AddVideoDeletionRecord error: %v\", err)\n\t\treturn err\n\t}\n\n\tdefer stmtIns.Close()\n\treturn nil\n}"
  },
  {
    "path": "video_server/scheduler/dbops/conn.go",
    "content": "package dbops\n\nimport (\n\"database/sql\"\n_ \"github.com/go-sql-driver/mysql\"\n)\n\nvar (\n\tdbConn *sql.DB\n\terr error\n)\n\nfunc init()  {\n\tdbConn, err = sql.Open(\"mysql\", \"root:@tcp(127.0.0.1:3306)/video_server?charset=utf8\")\n\tif err != nil {\n\t\tpanic(err.Error())\n\t}\n}\n\n"
  },
  {
    "path": "video_server/scheduler/dbops/internal.go",
    "content": "package dbops\n\nimport \"log\"\n\nfunc ReadVideoDeletionRecord(count int) ([]string, error) {\n\tstmtOut, err := dbConn.Prepare(\"SELECT video_id FROM video_del_rec LIMIT ?\")\n\n\tvar ids []string\n\n\tif err != nil {\n\t\treturn ids, err\n\t}\n\n\trows, err := stmtOut.Query(count)\n\tif err != nil {\n\t\tlog.Printf(\"Query VideoDeletionRecord error: %v\", err)\n\t\treturn ids, err\n\t}\n\n\tfor rows.Next() {\n\t\tvar id string\n\t\tif err := rows.Scan(&id); err != nil {\n\t\t\treturn ids, err\n\t\t}\n\n\t\tids = append(ids, id)\n\t}\n\n\tdefer stmtOut.Close()\n\treturn ids, nil\n}\n\nfunc DelVideoDeletionRecord(vid string) error {\n\tstmtDel, err := dbConn.Prepare(\"DELETE FROM video_del_rec WHERE video_id = ?\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = stmtDel.Exec(vid)\n\tif err != nil {\n\t\tlog.Printf(\"Deleting VideoDeletionRecord: %v\", err)\n\t\treturn  err\n\t}\n\n\tdefer stmtDel.Close()\n\treturn nil\n}"
  },
  {
    "path": "video_server/scheduler/handlers.go",
    "content": "package main\n\nimport (\n\t\"github.com/julienschmidt/httprouter\"\n\t\"golang-streaming/video_server/scheduler/dbops\"\n\t\"net/http\"\n)\n\nfunc vidDelRecHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {\n\tvid := p.ByName(\"vid-id\")\n\n\tif len(vid) == 0 {\n\t\tsendResponse(w, 400, \"Video id should not be empty\")\n\t\treturn\n\t}\n\n\terr := dbops.AddVideoDeletionRecord(vid)\n\tif err != nil {\n\t\tsendResponse(w, 500, \"Internal server error\")\n\t\treturn\n\t}\n\n\tsendResponse(w, 200, \"\")\n\treturn\n}"
  },
  {
    "path": "video_server/scheduler/main.go",
    "content": "package main\n\nimport (\n\t\"github.com/julienschmidt/httprouter\"\n\t\"golang-streaming/video_server/scheduler/taskrunner\"\n\t\"net/http\"\n)\n\nfunc RegisterHandlers() *httprouter.Router {\n\trouter := httprouter.New()\n\n\trouter.GET(\"/video-delete-record/:vid-id\", vidDelRecHandler)\n\n\treturn router\n}\n\nfunc main() {\n\tgo taskrunner.Start()\n\tr := RegisterHandlers()\n\thttp.ListenAndServe(\":9001\", r)\n}\n"
  },
  {
    "path": "video_server/scheduler/response.go",
    "content": "package main\n\nimport (\n\t\"io\"\n\t\"net/http\"\n)\n\nfunc sendResponse(w http.ResponseWriter, sc int, resp string) {\n\tw.WriteHeader(sc)\n\tio.WriteString(w, resp)\n}\n"
  },
  {
    "path": "video_server/scheduler/taskrunner/defs.go",
    "content": "package taskrunner\n\nconst (\n\tREADY_TO_DISPATCH = \"d\"\n\tREADY_TO_EXCUTE = \"e\"\n\tCLOSE = \"c\"\n\n\tVIDEO_PATH = \"./videos/\"\n)\n\ntype controlChan chan string\n\ntype dataChan chan interface{}\n\ntype fn func(dc dataChan) error"
  },
  {
    "path": "video_server/scheduler/taskrunner/runner.go",
    "content": "package taskrunner\n\ntype Runner struct {\n\tController controlChan\n\tError controlChan\n\tData dataChan\n\tdataSize int\n\tlongLived bool\n\tDispatcher fn\n\tExecutor fn\n}\n\nfunc NewRunner(size int, longlived bool, d fn, e fn) *Runner {\n\treturn &Runner{\n\t\tController: make(chan string, 1),\n\t\tError: make(chan  string, 1),\n\t\tData: make(chan interface{}, size),\n\t\tlongLived: longlived,\n\t\tdataSize: size,\n\t\tDispatcher: d,\n\t\tExecutor: e,\n\t}\n}\n\nfunc (r *Runner) startDispatch()  {\n\tdefer func() {\n\t\tif !r.longLived {\n\t\t\tclose(r.Controller)\n\t\t\tclose(r.Data)\n\t\t\tclose(r.Error)\n\t\t}\n\t}()\n\n\tfor {\n\t\tselect {\n\t\tcase c := <- r.Controller:\n\t\t\tif c == READY_TO_DISPATCH {\n\t\t\t\terr := r.Dispatcher(r.Data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tr.Error <- CLOSE\n\t\t\t\t} else {\n\t\t\t\t\tr.Controller <- READY_TO_EXCUTE\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif c == READY_TO_EXCUTE {\n\t\t\t\terr := r.Executor(r.Data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tr.Error <- CLOSE\n\t\t\t\t} else {\n\t\t\t\t\tr.Controller <- READY_TO_DISPATCH\n\t\t\t\t}\n\t\t\t}\n\t\tcase e := <- r.Error:\n\t\t\tif e == CLOSE {\n\t\t\t\treturn\n\t\t\t}\n\t\tdefault:\n\n\t\t}\n\t}\n}\n\nfunc (r *Runner) StartAll()  {\n\tr.Controller <- READY_TO_DISPATCH\n\tr.startDispatch()\n}"
  },
  {
    "path": "video_server/scheduler/taskrunner/runner_test.go",
    "content": "package taskrunner\n\nimport (\n\t\"github.com/pkg/errors\"\n\t\"log\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestRuner(t *testing.T)  {\n\td := func(dc dataChan) error {\n\t\tfor i := 0; i < 30; i++ {\n\t\t\tdc <- i\n\t\t\tlog.Printf(\"Dispatcher sent: %v\", i)\n\t\t}\n\n\t\treturn nil\n\t}\n\n\te := func(dc dataChan) error {\n\t\tforloop:\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase d := <- dc:\n\t\t\t\t\tlog.Printf(\"Executor received: %v\", d)\n\t\t\t\tdefault:\n\t\t\t\t\tbreak forloop\n\t\t\t\t}\n\t\t\t}\n\n\t\treturn errors.New(\"Executor\")\n\t}\n\n\trunner := NewRunner(30, false, d, e)\n\tgo runner.StartAll()\n\ttime.Sleep(3 * time.Second)\n}"
  },
  {
    "path": "video_server/scheduler/taskrunner/tasks.go",
    "content": "package taskrunner\n\nimport (\n\t\"errors\"\n\t\"golang-streaming/video_server/scheduler/dbops\"\n\t\"log\"\n\t\"os\"\n\t\"sync\"\n)\n\nfunc VideoClearDispatcher(dc dataChan) error {\n\tres, err := dbops.ReadVideoDeletionRecord(3) // 简单起见读3条\n\tif err != nil {\n\t\tlog.Printf(\"Video clear dispatcher error: %v\", err)\n\t\treturn err\n\t}\n\n\tif len(res) == 0 {\n\t\treturn errors.New(\"All tasks finished\")\n\t}\n\n\tfor _, id := range res {\n\t\tdc <- id\n\t}\n\n\treturn nil\n}\n\nfunc deleteVideo(vid string) error {\n\terr := os.Remove(VIDEO_PATH + vid)\n\n\tif err != nil && !os.IsNotExist(err) {\n\t\tlog.Printf(\"Deleting video error: %v\", err)\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc VideoClearExecutor(dc dataChan) error {\n\terrMap := &sync.Map{}\n\tvar err error\n\n\tforloop:\n\t\tfor {\n\t\t\tselect {\n\t\t\t\tcase vid := <- dc:\n\t\t\t\t\tgo func(id interface{}) {\n\t\t\t\t\t\tif err := deleteVideo(id.(string)); err != nil {\n\t\t\t\t\t\t\terrMap.Store(id, err)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif err := dbops.DelVideoDeletionRecord(id.(string)); err != nil {\n\t\t\t\t\t\t\terrMap.Store(id, err)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t}(vid)\n\t\t\tdefault:\n\t\t\t\tbreak forloop\n\t\t\t}\n\t\t}\n\n\terrMap.Range(func(k, v interface{}) bool {\n\t\terr = v.(error)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\n\treturn err\n}"
  },
  {
    "path": "video_server/scheduler/taskrunner/trmain.go",
    "content": "package taskrunner\n\nimport \"time\"\n\ntype Worker struct {\n\tticker *time.Ticker\n\trunner *Runner\n}\n\nfunc NewWorker(interval time.Duration, r *Runner) *Worker {\n\treturn &Worker {\n\t\tticker : time.NewTicker(interval * time.Second),\n\t\trunner: r,\n\t}\n}\n\nfunc (w *Worker) startWorker() {\n\tfor {\n\t\tselect {\n\t\tcase <- w.ticker.C:\n\t\t\tgo w.runner.StartAll()\n\n\t\t}\n\t}\n}\n\nfunc Start() {\n\t// Start video file cleaning\n\tr := NewRunner(3,true, VideoClearDispatcher, VideoClearExecutor)\n\tw := NewWorker(3, r)\n\tgo w.startWorker()\n}"
  },
  {
    "path": "video_server/streamserver/defs.go",
    "content": "package main\n\nconst (\n\tVIDEO_DIR = \"./videos/\"\n\tMAX_UPLOAD_SIZE = 50 * 1024 * 1024 // 50MB\n)"
  },
  {
    "path": "video_server/streamserver/handlers.go",
    "content": "package main\n\nimport (\n\t\"github.com/julienschmidt/httprouter\"\n\t\"html/template\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"time\"\n)\n\nfunc testPageHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {\n\tt, _ := template.ParseFiles(\"./videos/upload.html\")\n\n\tt.Execute(w, nil)\n}\n\nfunc streamHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {\n\tvid := p.ByName(\"vid-id\")\n\tvl := VIDEO_DIR + vid\n\n\tvideo, err := os.Open(vl)\n\tif err != nil {\n\t\tlog.Printf(\"Error when try to open file: %v\", err)\n\t\tsendErrorResponse(w, http.StatusInternalServerError, \"Internal Error\")\n\t\treturn\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"video/mp4\")\n\thttp.ServeContent(w, r, \"\", time.Now(), video)\n\n\tdefer video.Close()\n}\n\nfunc uploadHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params)  {\n\tr.Body = http.MaxBytesReader(w, r.Body, MAX_UPLOAD_SIZE)\n\tif err := r.ParseMultipartForm(MAX_UPLOAD_SIZE); err != nil {\n\t\tsendErrorResponse(w, http.StatusBadRequest, \"File is too big\")\n\t\treturn\n\t}\n\n\tfile, _, err := r.FormFile(\"file\")\n\tif err != nil {\n\t\tsendErrorResponse(w, http.StatusInternalServerError, \"Internal Error\")\n\t\treturn\n\t}\n\n\tdata, err := ioutil.ReadAll(file)\n\tif err != nil {\n\t\tlog.Printf(\"Read file error: %v\", err)\n\t\tsendErrorResponse(w, http.StatusInternalServerError, \"Internal Error\")\n\t}\n\n\tfn := p.ByName(\"vid-id\")\n\terr = ioutil.WriteFile(VIDEO_DIR + fn, data, 0666 )\n\tif err != nil {\n\t\tlog.Printf(\"Write file error: %v\", err)\n\t\tsendErrorResponse(w, http.StatusInternalServerError, \"Internal Error\")\n\t\treturn\n\t}\n\n\tw.WriteHeader(http.StatusCreated)\n\tio.WriteString(w, \"Upload successfully\")\n}\n"
  },
  {
    "path": "video_server/streamserver/limiter.go",
    "content": "package main\n\nimport \"log\"\n\ntype ConnLimiter struct {\n\tconcurrentConn int\n\tbucket chan int\n}\n\nfunc NewConnLimiter(cc int) *ConnLimiter {\n\treturn &ConnLimiter{\n\t\tconcurrentConn: cc,\n\t\tbucket: make(chan int, cc),\n\t}\n}\n\nfunc (cl *ConnLimiter) GetConn() bool {\n\tif len(cl.bucket) >= cl.concurrentConn {\n\t\tlog.Printf(\"Reached the rate limitation.\")\n\t\treturn false\n\t}\n\n\tcl.bucket <- 1\n\treturn true\n}\n\nfunc (cl *ConnLimiter) ReleaseConn()  {\n\tc := <- cl.bucket\n\tlog.Printf(\"New connection coming: %d\", c)\n}"
  },
  {
    "path": "video_server/streamserver/main.go",
    "content": "package main\n\nimport (\n\t\"github.com/julienschmidt/httprouter\"\n\t\"net/http\"\n)\n\ntype middleWareHandler struct {\n\tr *httprouter.Router\n\tl *ConnLimiter\n}\n\nfunc NewMiddleWareHandler(r *httprouter.Router, cc int) http.Handler {\n\tm := middleWareHandler{}\n\tm.r = r\n\tm.l = NewConnLimiter(cc)\n\treturn m\n}\n\nfunc (m middleWareHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\tif !m.l.GetConn() {\n\t\tsendErrorResponse(w, http.StatusTooManyRequests, \"Too many requests\")\n\t\treturn\n\t}\n\n\tm.r.ServeHTTP(w, r)\n\tdefer m.l.ReleaseConn()\n}\n\nfunc RegisterHandlers() *httprouter.Router {\n\trouter := httprouter.New()\n\n\trouter.GET(\"/videos/:vid-id\", streamHandler)\n\n\trouter.POST(\"/upload/:vid-id\", uploadHandler)\n\n\trouter.GET(\"/testpage\", testPageHandler)\n\n\treturn router\n}\nfunc main()  {\n\tr := RegisterHandlers()\n\tmh := NewMiddleWareHandler(r, 100) // 流控值暂置为2便于测试\n\thttp.ListenAndServe(\":9000\", mh)\n}\n"
  },
  {
    "path": "video_server/streamserver/response.go",
    "content": "package main\n\nimport (\n\t\"io\"\n\t\"net/http\"\n)\n\nfunc sendErrorResponse(w http.ResponseWriter, sc int, errMsg string)  {\n\tw.WriteHeader(sc)\n\tio.WriteString(w, errMsg)\n}"
  },
  {
    "path": "video_server/streamserver/upload.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Test Upload a File</title>\n</head>\n<body>\n    <form enctype=\"multipart/form-data\" action=\"http://127.0.0.1:9000/upload/ddd\" method=\"post\">\n        {{/* 1. File input */}}\n        <input type=\"file\" name=\"file\" />\n\n        {{/* 2. Submit button */}}\n        <input type=\"submit\" value=\"upload file\" />\n    </form>\n</body>\n</html>"
  },
  {
    "path": "video_server/templates/home.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <style>\n        body {\n            margin: 0;\n            font-family: Arial, Helvetica, sans-serif;\n        }\n\n        .topnav {\n            overflow: hidden;\n            background-color: #333;\n        }\n\n        .topnav a {\n            float: left;\n            color: #f2f2f2;\n            text-align: center;\n            padding: 14px 16px;\n            text-decoration: none;\n            font-size: 17px;\n        }\n\n        .topnav a:hover {\n            background-color: #ddd;\n        }\n\n        .topnav a.active {\n            background-color: #4CAF50;\n            color: white;\n        }\n\n        .topic-back {\n            text-align: center;\n            background-color: #66ffff;\n        }\n        #main-back {\n            background-color: #66ffff;\n        }\n        #title{\n            font-size: 60px;\n        }\n        * {\n            box-sizing: border-box;\n        }\n        /* Add padding to containers */\n        .container{\n            padding: 16px;\n            background-color: white;\n        }\n\n        /* Full-width input fields */\n        input[type=text], input[type=password] {\n            width: 100%;\n            padding: 15px;\n            margin: 5px 0 22px 0;\n            display: inline-block;\n            border: none;\n            background: #f1f1f1;\n        }\n\n        input[type=text]:focus, input[type=password]:focus {\n            background-color: #ddd;\n            outline: none;\n        }\n\n        /* Overwrite default styles of hr */\n        hr {\n            border: 1px solid #f1f1f1;\n            margin-bottom: 25px;\n        }\n\n        /* Set a style for the submit button */\n        .registerbtn {\n            background-color: #4CAF50;\n            color: white;\n            padding: 16px 20px;\n            margin: 8px 0;\n            border: none;\n            cursor: pointer;\n            width: 100%;\n            opacity: 0.9;\n            font-size: 20px;\n        }\n\n        .registerbtn:hover {\n            opacity: 1;\n        }\n\n        /* Add a blue text color to links */\n    </style>\n    <script\n        src=\"https://code.jquery.com/jquery-3.3.1.min.js\"\n        integrity=\"sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=\"\n        crossorigin=\"anonymous\">\n    </script>\n    <script type=\"text/javascript\" src=\"statics/scripts/home.js\"></script>\n    <title>Title</title>\n</head>\n<body id=\"main-back\">\n\n    <div class=\"topnav\">\n        <a class=\"active\" href=\"#home\">Home</a>\n        <a href=\"#news\">{{.Name}}</a>\n        <a href=\"#about\">About</a>\n        <a href=\"#\" id=\"upload\" data-rel=\"popup\">Upload</a>\n        <a id=\"logout\" href=\"/\">Logout</a>\n    </div>\n\n    <div class=\"topic-back\" style=\"padding-left: 16px\">\n        <h2 id=\"title\">Welcome to MOVIEDASH</h2>\n    </div>\n\n    <form method=\"post\" id=\"regsubmit\" action=\"/userhome\" style=\"\n    width: 500px; margin: 0 auto;\">\n        <div class=\"container\">\n            <h1>Register</h1>\n            <hr>\n\n            <label for=\"email\"><b>User name</b></label>\n            <input id=\"username\" type=\"text\" placeholder=\"Enter user name\" name=\"username\" required>\n\n            <label for=\"psw\"><b>Password</b></label>\n            <input id=\"pwd\" type=\"password\" placeholder=\"Enter Password\" name=\"pwd\" required>\n\n            <hr>\n            <p>By creating an account you agree to our <a href=\"#\">Terms & Privacy</a>.</p>\n            <button id=\"regbtn\" type=\"submit\" class=\"registerbtn\">Register</button>\n        </div>\n        <div class=\"container signin\">\n        <p>Already have an account? <a id=\"signinhref\" href=\"#\">Sign in</a>.</p>\n        </div>\n    </form>\n\n    <form method=\"post\" id=\"signinsubmit\" action=\"/userhome\" style=\"\n    width: 500px; margin: 0 auto; display: none\">\n        <div class=\"container\">\n            <h1>Sign In</h1>\n            <hr>\n\n            <label for=\"email\"><b>User name</b></label>\n            <input id=\"susername\" type=\"text\" placeholder=\"Enter user name\" name=\"username\" required>\n\n            <label for=\"psw\"><b>Password</b></label>\n            <input id=\"spwd\" type=\"password\" placeholder=\"Enter Password\" name=\"pwd\" required>\n\n            <hr>\n            <p>By creating an account you agree to our <a href=\"#\">Terms & Privacy</a>.</p>\n            <button id=\"signinbtn\" type=\"submit\" class=\"registerbtn\">Sign In</button>\n        </div>\n\n        <div class=\"container signin\">\n            <p>Don't have an account? <a id=\"registerhref\" href=\"#\">Register</a>.</p>\n        </div>\n    </form>\n</body>\n</html>"
  },
  {
    "path": "video_server/templates/scripts/home.js",
    "content": "$(document).ready(function() {\n\n    DEFAULT_COOKIE_EXPIRE_TIME = 30;\n\n    uname = '';\n    session = '';\n    uid = 0;\n    currentVideo = null;\n    listedVideos = null;\n\n    session = getCookie('session');\n    uname = getCookie('username');\n\n    initPage(function() {\n        if (listedVideos !== null) {\n            currentVideo = listedVideos[0];\n            selectVideo(listedVideos[0]['id']);\n        }\n\n        $(\".video-item\").click(function() {\n            var self = this.id\n            listedVideos.forEach(function(item, index) {\n                if (item['id'] === self) {\n                    currentVideo = item;\n                    return\n                }\n            });\n\n            selectVideo(self);\n        });\n\n        $(\".del-video-button\").click(function() {\n            var id = this.id.substring(4);\n            deleteVideo(id, function(res, err) {\n                if (err !== null) {\n                    //window.alert(\"encounter an error when try to delete video: \" + id);\n                    popupErrorMsg(\"encounter an error when try to delete video: \" + id);\n                    return;\n                }\n\n                popupNotificationMsg(\"Successfully deleted video: \" + id)\n                location.reload();\n            });\n        });\n\n        $(\"#submit-comment\").on('click', function() {\n            var content = $(\"#comments-input\").val();\n            postComment(currentVideo['id'], content, function(res, err) {\n                if (err !== null) {\n                    popupErrorMsg(\"encounter and error when try to post a comment: \" + content);\n                    return;\n                }\n\n                if (res === \"ok\") {\n                    popupNotificationMsg(\"New comment posted\")\n                    $(\"#comments-input\").val(\"\");\n\n                    refreshComments(currentVideo['id']);\n                }\n            });\n        });\n    });\n\n    // home page event registry\n    $(\"#regbtn\").on('click', function(e) {\n        $(\"#regbtn\").text('Loading...')\n        e.preventDefault()\n        registerUser(function(res, err) {\n            if (err != null) {\n                $('#regbtn').text(\"Register\")\n                popupErrorMsg('encounter an error, pls check your username or pwd');\n                return;\n            }\n\n            var obj = JSON.parse(res);\n            setCookie(\"session\", obj[\"session_id\"], DEFAULT_COOKIE_EXPIRE_TIME);\n            setCookie(\"username\", uname, DEFAULT_COOKIE_EXPIRE_TIME);\n            $(\"#regsubmit\").submit();\n        });\n    });\n\n    $(\"#signinbtn\").on('click', function(e) {\n\n        $(\"#signinbtn\").text('Loading...')\n        e.preventDefault();\n        signinUser(function(res, err) {\n            if (err != null) {\n                $('#signinbtn').text(\"Sign In\");\n                //window.alert('encounter an error, pls check your username or pwd')\n                popupErrorMsg('encounter an error, pls check your username or pwd');\n                return;\n            }\n\n            var obj = JSON.parse(res);\n            setCookie(\"session\", obj[\"session_id\"], DEFAULT_COOKIE_EXPIRE_TIME);\n            setCookie(\"username\", uname, DEFAULT_COOKIE_EXPIRE_TIME);\n            $(\"#signinsubmit\").submit();\n        });\n    });\n\n    $(\"#signinhref\").on('click', function() {\n        $(\"#regsubmit\").hide();\n        $(\"#signinsubmit\").show();\n    });\n\n    $(\"#registerhref\").on('click', function() {\n        $(\"#regsubmit\").show();\n        $(\"#signinsubmit\").hide();\n    });\n\n    // userhome event register\n    $(\"#upload\").on('click', function() {\n        $(\"#uploadvideomodal\").show();\n\n    });\n\n\n    $(\"#uploadform\").on('submit', function(e) {\n        e.preventDefault()\n        var vname = $('#vname').val();\n\n        createVideo(vname, function(res, err) {\n            if (err != null ) {\n                //window.alert('encounter an error when try to create video');\n                popupErrorMsg('encounter an error when try to create video');\n                return;\n            }\n\n            var obj = JSON.parse(res);\n            var formData = new FormData();\n            formData.append('file', $('#inputFile')[0].files[0]);\n\n            $.ajax({\n                url : 'http://' + window.location.hostname + ':8080/upload/' + obj['id'],\n                type : 'POST',\n                data : formData,\n                //headers: {'Access-Control-Allow-Origin': 'http://127.0.0.1:9000'},\n                crossDomain: true,\n                processData: false,  // tell jQuery not to process the data\n                contentType: false,  // tell jQuery not to set contentType\n                success : function(data) {\n                    console.log(data);\n                    $('#uploadvideomodal').hide();\n                    location.reload();\n                    //window.alert(\"hoa\");\n                },\n                complete: function(xhr, textStatus) {\n                    if (xhr.status === 204) {\n                        window.alert(\"finish\")\n                        return;\n                    }\n                    if (xhr.status === 400) {\n                        $(\"#uploadvideomodal\").hide();\n                        popupErrorMsg('file is too big');\n                        return;\n                    }\n                }\n\n            });\n        });\n    });\n\n    $(\".close\").on('click', function() {\n        $(\"#uploadvideomodal\").hide();\n    });\n\n    $(\"#logout\").on('click', function() {\n        setCookie(\"session\", \"\", -1)\n        setCookie(\"username\", \"\", -1)\n    });\n\n\n    $(\".video-item\").click(function () {\n        var url = 'http://' + window.location.hostname + ':9000/videos/'+ this.id\n        var video = $(\"#curr-video\");\n        video[0].attr('src', url);\n        video.load();\n    });\n});\n\nfunction initPage(callback) {\n    getUserId(function(res, err) {\n        if (err != null) {\n            window.alert(\"Encountered error when loading user id\");\n            return;\n        }\n\n        var obj = JSON.parse(res);\n        uid = obj['id'];\n        //window.alert(obj['id']);\n        listAllVideos(function(res, err) {\n            if (err != null) {\n                //window.alert('encounter an error, pls check your username or pwd');\n                popupErrorMsg('encounter an error, pls check your username or pwd');\n                return;\n            }\n            var obj = JSON.parse(res);\n            listedVideos = obj['videos'];\n            obj['videos'].forEach(function(item, index) {\n                var ele = htmlVideoListElement(item['id'], item['name'], item['display_ctime']);\n                $(\"#items\").append(ele);\n            });\n            callback();\n        });\n    });\n}\n\nfunction setCookie(cname, cvalue, exmin) {\n    var d = new Date();\n    d.setTime(d.getTime() + (exmin * 60 * 1000));\n    var expires = \"expires=\"+d.toUTCString();\n    document.cookie = cname + \"=\" + cvalue + \";\" + expires + \";path=/\";\n}\n\nfunction getCookie(cname) {\n    var name = cname + \"=\";\n    var ca = document.cookie.split(';');\n    for(var i = 0; i < ca.length; i++) {\n        var c = ca[i];\n        while (c.charAt(0) == ' ') {\n            c = c.substring(1);\n        }\n        if (c.indexOf(name) == 0) {\n            return c.substring(name.length, c.length);\n        }\n    }\n    return \"\";\n}\n\n// DOM operations\nfunction selectVideo(vid) {\n    var url = 'http://' + window.location.hostname + ':8080/videos/'+ vid\n    var video = $(\"#curr-video\");\n    $(\"#curr-video:first-child\").attr('src', url);\n    $(\"#curr-video-name\").text(currentVideo['name']);\n    $(\"#curr-video-ctime\").text('Uploaded at: ' + currentVideo['display_ctime']);\n    //currentVideoId = vid;\n    refreshComments(vid);\n}\n\nfunction refreshComments(vid) {\n    listAllComments(vid, function (res, err) {\n        if (err !== null) {\n            //window.alert(\"encounter an error when loading comments\");\n            popupErrorMsg('encounter an error when loading comments');\n            return\n        }\n\n        var obj = JSON.parse(res);\n        $(\"#comments-history\").empty();\n        if (obj['comments'] === null) {\n            $(\"#comments-total\").text('0 Comments');\n        } else {\n            $(\"#comments-total\").text(obj['comments'].length + ' Comments');\n        }\n        obj['comments'].forEach(function(item, index) {\n            var ele = htmlCommentListElement(item['id'], item['author'], item['content']);\n            $(\"#comments-history\").append(ele);\n        });\n\n    });\n}\n\nfunction popupNotificationMsg(msg) {\n    var x = document.getElementById(\"snackbar\");\n    $(\"#snackbar\").text(msg);\n    x.className = \"show\";\n    setTimeout(function(){ x.className = x.className.replace(\"show\", \"\"); }, 2000);\n}\n\nfunction popupErrorMsg(msg) {\n    var x = document.getElementById(\"errorbar\");\n    $(\"#errorbar\").text(msg);\n    x.className = \"show\";\n    setTimeout(function(){ x.className = x.className.replace(\"show\", \"\"); }, 2000);\n}\n\nfunction htmlCommentListElement(cid, author, content) {\n    var ele = $('<div/>', {\n        id: cid\n    });\n\n    ele.append(\n        $('<div/>', {\n            class: 'comment-author',\n            text: author + ' says:'\n        })\n    );\n    ele.append(\n        $('<div/>', {\n            class: 'comment',\n            text: content\n        })\n    );\n\n    ele.append('<hr style=\"height: 1px; border:none; color:#EDE3E1;background-color:#EDE3E1\">');\n\n    return ele;\n}\n\nfunction htmlVideoListElement(vid, name, ctime) {\n    var ele = $('<a/>', {\n        href: '#'\n    });\n    ele.append(\n        $('<video/>', {\n            width:'320',\n            height:'240',\n            poster:'/statics/img/preloader.jpg',\n            controls: true\n            //href: '#'\n        })\n    );\n    ele.append(\n        $('<div/>', {\n            text: name\n        })\n    );\n    ele.append(\n        $('<div/>', {\n            text: ctime\n        })\n    );\n\n\n    var res = $('<div/>', {\n        id: vid,\n        class: 'video-item'\n    }).append(ele);\n\n    res.append(\n        $('<button/>', {\n            id: 'del-' + vid,\n            type: 'button',\n            class: 'del-video-button',\n            text: 'Delete'\n        })\n    );\n\n    res.append(\n        $('<hr>', {\n            size: '2'\n        }).css('border-color', 'grey')\n    );\n\n    return res;\n}\n\n// Async ajax methods\n\n// User operations\nfunction registerUser(callback) {\n    var username = $(\"#username\").val();\n    var pwd = $(\"#pwd\").val();\n    var apiUrl = window.location.hostname + ':8080/api';\n\n    if (username == '' || pwd == '') {\n        callback(null, err);\n    }\n\n    var reqBody = {\n        'user_name': username,\n        'pwd': pwd\n    }\n\n    var dat = {\n        'url': 'http://'+ window.location.hostname + ':8000/user',\n        'method': 'POST',\n        'req_body': JSON.stringify(reqBody)\n    };\n\n\n\n\n    $.ajax({\n        url  : 'http://' + window.location.hostname + ':8080/api',\n        type : 'post',\n        data : JSON.stringify(dat),\n        statusCode: {\n            500: function() {\n                callback(null, \"internal error\");\n            }\n        },\n        complete: function(xhr, textStatus) {\n            if (xhr.status >= 400) {\n                callback(null, \"Error of Signin\");\n                return;\n            }\n        }\n    }).done(function(data, statusText, xhr){\n        if (xhr.status >= 400) {\n            callback(null, \"Error of register\");\n            return;\n        }\n\n        uname = username;\n        callback(data, null);\n    });\n}\n\nfunction signinUser(callback) {\n    var username = $(\"#susername\").val();\n    var pwd = $(\"#spwd\").val();\n    var apiUrl = window.location.hostname + ':8080/api';\n\n    if (username == '' || pwd == '') {\n        callback(null, err);\n    }\n\n    var reqBody = {\n        'user_name': username,\n        'pwd': pwd\n    }\n\n    var dat = {\n        'url': 'http://'+ window.location.hostname + ':8000/user/' + username,\n        'method': 'POST',\n        'req_body': JSON.stringify(reqBody)\n    };\n\n    $.ajax({\n        url  : 'http://' + window.location.hostname + ':8080/api',\n        type : 'post',\n        data : JSON.stringify(dat),\n        statusCode: {\n            500: function() {\n                callback(null, \"Internal error\");\n            }\n        },\n        complete: function(xhr, textStatus) {\n            if (xhr.status >= 400) {\n                callback(null, \"Error of Signin\");\n                return;\n            }\n        }\n    }).done(function(data, statusText, xhr){\n        if (xhr.status >= 400) {\n            callback(null, \"Error of Signin\");\n            return;\n        }\n        uname = username;\n\n        callback(data, null);\n    });\n}\n\nfunction getUserId(callback) {\n    var dat = {\n        'url': 'http://' + window.location.hostname + ':8000/user/' + uname,\n        'method': 'GET'\n    };\n\n    $.ajax({\n        url: 'http://' + window.location.hostname + ':8080/api',\n        type: 'post',\n        data: JSON.stringify(dat),\n        headers: {'X-Session-Id': session},\n        statusCode: {\n            500: function() {\n                callback(null, \"Internal Error\");\n            }\n        },\n        complete: function(xhr, textStatus) {\n            if (xhr.status >= 400) {\n                callback(null, \"Error of getUserId\");\n                return;\n            }\n        }\n    }).done(function (data, statusText, xhr) {\n        callback(data, null);\n    });\n}\n\n// Video operations\nfunction createVideo(vname, callback) {\n    var reqBody = {\n        'author_id': uid,\n        'name': vname\n    };\n\n    var dat = {\n        'url': 'http://' + window.location.hostname + ':8000/user/' + uname + '/videos',\n        'method': 'POST',\n        'req_body': JSON.stringify(reqBody)\n    };\n\n    $.ajax({\n        url  : 'http://' + window.location.hostname + ':8080/api',\n        type : 'post',\n        data : JSON.stringify(dat),\n        headers: {'X-Session-Id': session},\n        statusCode: {\n            500: function() {\n                callback(null, \"Internal error\");\n            }\n        },\n        complete: function(xhr, textStatus) {\n            if (xhr.status >= 400) {\n                callback(null, \"Error of Signin\");\n                return;\n            }\n        }\n    }).done(function(data, statusText, xhr){\n        if (xhr.status >= 400) {\n            callback(null, \"Error of Signin\");\n            return;\n        }\n        callback(data, null);\n    });\n}\n\nfunction listAllVideos(callback) {\n    var dat = {\n        'url': 'http://' + window.location.hostname + ':8000/user/' + uname + '/videos',\n        'method': 'GET',\n        'req_body': ''\n    };\n\n    $.ajax({\n        url  : 'http://' + window.location.hostname + ':8080/api',\n        type : 'post',\n        data : JSON.stringify(dat),\n        headers: {'X-Session-Id': session},\n        statusCode: {\n            500: function() {\n                callback(null, \"Internal error\");\n            }\n        },\n        complete: function(xhr, textStatus) {\n            if (xhr.status >= 400) {\n                callback(null, \"Error of Signin\");\n                return;\n            }\n        }\n    }).done(function(data, statusText, xhr){\n        if (xhr.status >= 400) {\n            callback(null, \"Error of Signin\");\n            return;\n        }\n        callback(data, null);\n    });\n}\n\nfunction deleteVideo(vid, callback) {\n    var dat = {\n        'url': 'http://' + window.location.hostname + ':8000/user/' + uname + '/videos/' + vid,\n        'method': 'DELETE',\n        'req_body': ''\n    };\n\n    $.ajax({\n        url  : 'http://' + window.location.hostname + ':8080/api',\n        type : 'post',\n        data : JSON.stringify(dat),\n        headers: {'X-Session-Id': session},\n        statusCode: {\n            500: function() {\n                callback(null, \"Internal error\");\n            }\n        },\n        complete: function(xhr, textStatus) {\n            if (xhr.status >= 400) {\n                callback(null, \"Error of Signin\");\n                return;\n            }\n        }\n    }).done(function(data, statusText, xhr){\n        if (xhr.status >= 400) {\n            callback(null, \"Error of Signin\");\n            return;\n        }\n        callback(data, null);\n    });\n}\n\n// Comments operations\nfunction postComment(vid, content, callback) {\n    var reqBody = {\n        'author_id': uid,\n        'content': content\n    }\n\n\n    var dat = {\n        'url': 'http://' + window.location.hostname + ':8000/videos/' + vid + '/comments',\n        'method': 'POST',\n        'req_body': JSON.stringify(reqBody)\n    };\n\n    $.ajax({\n        url  : 'http://' + window.location.hostname + ':8080/api',\n        type : 'post',\n        data : JSON.stringify(dat),\n        headers: {'X-Session-Id': session},\n        statusCode: {\n            500: function() {\n                callback(null, \"Internal error\");\n            }\n        },\n        complete: function(xhr, textStatus) {\n            if (xhr.status >= 400) {\n                callback(null, \"Error of Signin\");\n                return;\n            }\n        }\n    }).done(function(data, statusText, xhr){\n        if (xhr.status >= 400) {\n            callback(null, \"Error of Signin\");\n            return;\n        }\n        callback(data, null);\n    });\n}\n\nfunction listAllComments(vid, callback) {\n    var dat = {\n        'url': 'http://' + window.location.hostname + ':8000/videos/' + vid + '/comments',\n        'method': 'GET',\n        'req_body': ''\n    };\n\n    $.ajax({\n        url  : 'http://' + window.location.hostname + ':8080/api',\n        type : 'post',\n        data : JSON.stringify(dat),\n        headers: {'X-Session-Id': session},\n        statusCode: {\n            500: function() {\n                callback(null, \"Internal error\");\n            }\n        },\n        complete: function(xhr, textStatus) {\n            if (xhr.status >= 400) {\n                callback(null, \"Error of Signin\");\n                return;\n            }\n        }\n    }).done(function(data, statusText, xhr){\n        if (xhr.status >= 400) {\n            callback(null, \"Error of Signin\");\n            return;\n        }\n        callback(data, null);\n    });\n}"
  },
  {
    "path": "video_server/templates/userhome.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <style>\n        body {\n            margin: 0;\n            font-family: Arial, Helvetica, sans-serif;\n        }\n        .topnav {\n            overflow: hidden;\n            background-color: #333;\n        }\n        .topnav a {\n            float: left;\n            color: #f2f2f2;\n            text-align: center;\n            padding: 14px 16px;\n            text-decoration: none;\n            font-size: 17px;\n        }\n        .topnav a:hover {\n            background-color: #ddd;\n            color: black;\n        }\n        .topnav a.active {\n            background-color: #4CAF50;\n            color: white;\n        }\n        .topic-back {\n            text-align: center;\n            background-color: #F1F1F1;\n        }\n        #main-back {\n            background-color: #F1F1F1;\n        }\n        #title {\n            font-size: 60px;\n        }\n        * {\n            box-sizing: border-box;\n        }\n        /* Add padding to containers */\n        .container {\n            padding: 16px;\n            background-color: white;\n        }\n        /* Full-width input fields */\n        input[type=text], input[type=password] {\n            width: 100%;\n            padding: 15px;\n            margin: 5px 0 22px 0;\n            display: inline-block;\n            border: none;\n            background: #f1f1f1;\n        }\n        input[type=text]:focus, input[type=password]:focus {\n            background-color: #ddd;\n            outline: none;\n        }\n        /* Overwrite default styles of hr */\n        hr {\n            border: 1px solid #f1f1f1;\n            margin-bottom: 25px;\n        }\n        /* Set a style for the submit button */\n        .registerbtn {\n            background-color: #4CAF50;\n            color: white;\n            padding: 16px 20px;\n            margin: 8px 0;\n            border: none;\n            cursor: pointer;\n            width: 100%;\n            opacity: 0.9;\n            font-size: 20px;\n        }\n        .registerbtn:hover {\n            opacity: 1;\n        }\n        /* Add a blue text color to links */\n        a {\n            color: dodgerblue;\n        }\n        /* Set a grey background color and center the text of the \"sign in\" section */\n        .signin {\n            background-color: #f1f1f1;\n            text-align: center;\n        }\n        .modal {\n            display: none; /* Hidden by default */\n            position: fixed; /* Stay in place */\n            z-index: 1; /* Sit on top */\n            padding-top: 100px; /* Location of the box */\n            left: 0;\n            top: 0;\n            width: 100%; /* Full width */\n            height: 100%; /* Full height */\n            overflow: auto; /* Enable scroll if needed */\n            background-color: rgb(0,0,0); /* Fallback color */\n            background-color: rgba(0,0,0,0.4); /* Black w/ opacity */\n        }\n        .modal-content {\n            position: relative;\n            text-align: center;\n            background-color: #fefefe;\n            margin: auto;\n            padding: 0;\n            border: 1px solid #888;\n            width: 40%;\n            box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);\n            -webkit-animation-name: animatetop;\n            -webkit-animation-duration: 0.4s;\n            animation-name: animatetop;\n            animation-duration: 0.4s\n        }\n        .modal-header {\n            padding: 2px 16px;\n            background-color: #5cb85c;\n            color: white;\n        }\n        .modal-body {padding: 2px 16px;}\n        .modal-footer {\n            padding: 2px 16px;\n            background-color: #5cb85c;\n            color: white;\n        }\n        .video-thumbnail-list {\n            padding-left: 50px;\n        }\n\n        .whole {\n            padding-left: 100px;\n        }\n\n        #comments {\n            padding-top: 20px;\n        }\n\n        .comment-author {\n            font-size: 12px;\n            color: grey;\n        }\n\n        .comment-ctime {\n            font-size: 12px;\n            color: gray;\n        }\n        .comment {\n            font-size: 15px;\n            color: black;\n        }\n        #comments-total {\n            font-size: 18px;\n            color: black;\n            line-height: 3;\n        }\n        #curr-video-name {\n            font-size: 18px;\n            padding-top: 10px;\n            line-height: 20px;\n        }\n        #curr-video-ctime {\n            font-size: 10px;\n            color: gray;\n        }\n\n        #snackbar {\n            visibility: hidden;\n            min-width: 250px;\n            margin-left: -125px;\n            background-color: #333;\n            color: #fff;\n            text-align: center;\n            border-radius: 2px;\n            padding: 16px;\n            position: fixed;\n            z-index: 1;\n            left: 50%;\n            bottom: 30px;\n            font-size: 17px;\n        }\n\n        #snackbar.show{\n            visibility: visible;\n            -webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s;\n            animation: fadein 0.5s, fadeout 0.5s 2.5s;\n        }\n\n        #errorbar {\n            visibility: hidden;\n            min-width: 250px;\n            margin-left: -125px;\n            background-color: red;\n            color: #fff;\n            text-align: center;\n            border-radius: 2px;\n            padding: 10px;\n            position: fixed;\n            z-index: 1;\n            left: 50%;\n            bottom: 30px;\n            font-size: 17px;\n        }\n\n        #errorbar.show {\n            visibility: visible;\n            -webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s;\n            animation: fadein 0.5s, fadeout 0.5s 2.5s;\n        }\n    </style>\n    <script\n            src=\"https://code.jquery.com/jquery-3.3.1.min.js\"\n            integrity=\"sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=\"\n            crossorigin=\"anonymous\">\n    </script>\n    <script type=\"text/javascript\" src=\"statics/scripts/home.js\"></script>\n    <title>Title</title>\n</head>\n<body id=\"main-back\">\n\n<div class=\"topnav\">\n    <a class=\"active\" href=\"#home\">Home</a>\n    <a href=\"#news\">{{.Name}}</a>\n    <a href=\"#about\">About</a>\n    <a href=\"#\" id=\"upload\" data-rel=\"popup\">Upload</a>\n    <a id=\"logout\" href=\"/\">Logout</a>\n</div>\n\n<div class=\"topic-back\" style=\"padding-left: 16px\">\n    <h2 id=\"title\">Welcome to MOVIEDASH</h2>\n</div>\n\n<div class=\"whole\">\n    <div id=\"play-box\">\n        <video id=\"curr-video\" width=\"640\" height=\"400\" controls preload=\"auto\" border=\"5\"></video>\n        <div id=\"curr-video-name\"></div>\n        <div id=\"curr-video-ctime\"></div>\n        <div id=\"snackbar\">Comment submission succeeded</div>\n        <div id=\"errorbar\">Error happened</div>\n        <div id=\"comments\">\n            <textarea id=\"comments-input\" name=\"mesage\" rows=\"5\" cols=\"90\"\\\n            placeholder=\"Input your comment\"></textarea>\n            <input id=\"submit-comment\" type=\"submit\">\n            <hr size=\"1\" style=\"border-color: #EDE3E1;\">\n            <div id=\"comments-total\">0 Comments</div>\n            <div id=\"comments-history\"></div>\n        </div>\n    </div>\n\n    <div id=\"items\" class=\"video-thumbnail-list\"></div>\n</div>\n<div id=\"uploadvideomodal\" class=\"modal\">\n\n    <!--Modal content-->\n    <div class=\"modal-content\">\n        <div class=\"modal-header\">\n            <span class=\"close\">&times;</span>\n            <h2>Model Header</h2>\n        </div>\n        <div class=\"modal-body\">\n            <form id=\"uploadform\" action=\"http://127.0.0.1:9000/upload/alan\"\n            method=\"post\" enctype=\"multipart/form-data\" class=\"uploadform\">\n                <input class=\"uploadform__input\" type=\"file\" name=\"file\"\n                id=\"inputFile\" accept=\"video/*\">\n                <input type=\"text\" id=\"vname\" name=\"video name\">\n                <input type=\"submit\" id=\"upload-submit\" name=\"submit\" value=\"Submit\">\n            </form>\n        </div>\n        <div class=\"modal-footer\">\n            <h3>Modal Footer</h3>\n        </div>\n    </div>\n</div>\n</body>\n</html>"
  },
  {
    "path": "video_server/web/client.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net/http\"\n)\n\nvar httpClient *http.Client\n\nfunc init()  {\n\thttpClient = &http.Client{}\n}\n\nfunc request(b *ApiBody, w http.ResponseWriter, r *http.Request) {\n\tvar resp *http.Response\n\tvar err error\n\n\tswitch  b.Method {\n\tcase http.MethodGet:\n\t\treq, _ := http.NewRequest(\"GET\", b.Url, nil)\n\t\treq.Header = r.Header\n\t\tresp, err = httpClient.Do(req)\n\t\tif err != nil {\n\t\t\tlog.Print(err)\n\t\t\treturn\n\t\t}\n\t\tnormalResponse(w, resp)\n\tcase http.MethodPost:\n\t\treq, _ := http.NewRequest(\"POST\", b.Url, bytes.NewBuffer([]byte(b.ReqBody)))\n\t\treq.Header = r.Header\n\t\tresp, err = httpClient.Do(req)\n\t\tif err != nil {\n\t\t\tlog.Print(err)\n\t\t\treturn\n\t\t}\n\t\tnormalResponse(w, resp)\n\tcase http.MethodDelete:\n\t\treq, _ := http.NewRequest(\"Delete\", b.Url, nil)\n\t\treq.Header = r.Header\n\t\tresp, err = httpClient.Do(req)\n\t\tif err != nil {\n\t\t\tlog.Print(err)\n\t\t\treturn\n\t\t}\n\t\tnormalResponse(w, resp)\n\tdefault:\n\t\tw.WriteHeader(http.StatusBadRequest)\n\t\tio.WriteString(w, \"Bad api request\")\n\t}\n}\n\nfunc normalResponse(w http.ResponseWriter, r *http.Response)  {\n\tres, err := ioutil.ReadAll(r.Body)\n\tif err != nil {\n\t\tre, _ := json.Marshal(ErorrInternalFaults)\n\t\tw.WriteHeader(500)\n\t\tio.WriteString(w, string(re))\n\t\treturn\n\t}\n\n\tw.WriteHeader(r.StatusCode)\n\tio.WriteString(w, string(res))\n}"
  },
  {
    "path": "video_server/web/defs.go",
    "content": "package main\n\ntype ApiBody struct {\n\tUrl string `json:\"url\"`\n\tMethod string `json:\"method\"`\n\tReqBody string `json:\"req_body\"`\n}\n\ntype Err struct {\n\tError string `json:\"error\"`\n\tErrorCode string `json:\"error_code\"`\n}\n\nvar (\n\tErrorRequestNotRecognized  = Err{Error: \"api not recognize, bad request\", ErrorCode: \"001\"}\n\tErrorRequestBodyParseFailed = Err{Error: \"request body is not correct\", ErrorCode: \"002\"}\n\tErorrInternalFaults = Err{Error: \"internal service error\", ErrorCode: \"003\"}\n)"
  },
  {
    "path": "video_server/web/handlers.go",
    "content": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"github.com/julienschmidt/httprouter\"\n\t\"html/template\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"net/url\"\n)\n\n\ntype HomePage struct {\n\tName string\n}\n\ntype UserPage struct {\n\tName string\n}\n\nfunc homeHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {\n\tcname, err := r.Cookie(\"username\")\n\tsid, err2 := r.Cookie(\"session\")\n\n\tif err != nil || err2 != nil {\n\t\tp := &HomePage{Name: \"Alan\"}\n\t\tt, e := template.ParseFiles(\"./templates/home.html\")\n\t\tif e != nil {\n\t\t\tlog.Printf(\"Parsing template home.html error: %s\", e)\n\t\t\treturn\n\t\t}\n\n\t\tt.Execute(w, p)\n\t\treturn\n\t}\n\n\tif len(cname.Value) != 0 && len(sid.Value) != 0 {\n\t\thttp.Redirect(w, r, \"/userhome\", http.StatusFound)\n\t\treturn\n\t}\n}\n\n\nfunc userHomeHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {\n\tcname, err1 := r.Cookie(\"username\")\n\t_, err2 := r.Cookie(\"session\")\n\n\tif err1 != nil || err2 != nil {\n\t\thttp.Redirect(w, r, \"/\", http.StatusFound)\n\t\treturn\n\t}\n\n\tfname := r.FormValue(\"username\")\n\n\tvar p *UserPage\n\tif len(cname.Value) !=0 {\n\t\tp = &UserPage{Name: cname.Value}\n\t} else if len(fname) != 0 {\n\t\tp = &UserPage{Name: fname}\n\t}\n\n\tt, e := template.ParseFiles(\"./templates/userhome.html\")\n\tif e != nil {\n\t\tlog.Printf(\"Parsing userhome.html error: %s\", e)\n\t\treturn\n\t}\n\n\tt.Execute(w, p)\n}\n\nfunc apiHandler(w http.ResponseWriter,  r *http.Request, ps httprouter.Params) {\n\tif r.Method != http.MethodPost {\n\t\tre, _ := json.Marshal(ErrorRequestNotRecognized)\n\t\tio.WriteString(w, string(re))\n\t\treturn\n\t}\n\n\tres, _ := ioutil.ReadAll(r.Body)\n\tapibody := &ApiBody{}\n\tif err := json.Unmarshal(res, apibody); err != nil {\n\t\tre, _ := json.Marshal(ErrorRequestBodyParseFailed)\n\t\tio.WriteString(w, string(re))\n\t\treturn\n\t}\n\n\trequest(apibody, w, r)\n\tdefer r.Body.Close()\n}\n\nfunc proxyHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {\n\tu, _ := url.Parse(\"http://127.0.0.1:9000/\")\n\tproxy := httputil.NewSingleHostReverseProxy(u)\n\tproxy.ServeHTTP(w, r)\n}"
  },
  {
    "path": "video_server/web/main.go",
    "content": "package main\n\nimport (\n\t\"github.com/julienschmidt/httprouter\"\n\t\"net/http\"\n)\n\nfunc RegisterHandler() *httprouter.Router  {\n\trouter := httprouter.New()\n\n\trouter.GET(\"/\", homeHandler)\n\n\trouter.POST(\"/\", homeHandler)\n\n\trouter.GET(\"/userhome\", userHomeHandler)\n\n\trouter.POST(\"/userhome\", userHomeHandler)\n\n\trouter.POST(\"/api\", apiHandler)\n\n\trouter.POST(\"/upload/:vid-id\", proxyHandler)\n\n\t//router.ServeFiles(\"/statics/*filepath\", http.Dir(\"./template\"))\n\trouter.ServeFiles(\"/statics/*filepath\", http.Dir(\"./templates\"))\n\n\treturn router\n}\n\nfunc main() {\n\tr := RegisterHandler()\n\thttp.ListenAndServe(\":8080\", r)\n}\n"
  },
  {
    "path": "webserver/main.go",
    "content": "package main\n\nimport(\n\t\"io\"\n\t\"net/http\"\n)\n\nfunc Print1to20() int{\n\tres := 0\n\tfor i := 1; i<= 20; i++{\n\t\tres += i\n\t}\n\treturn res\n}\n\nfunc firstPage(w http.ResponseWriter, r *http.Request){\n\tio.WriteString(w, \"<h1>Hello, this is my first page!</h1>\")\n}\n\nfunc main(){\n\thttp.HandleFunc(\"/\", firstPage)\n\thttp.ListenAndServe(\":8000\", nil)\n}"
  },
  {
    "path": "webserver/main_test.go",
    "content": "package main\n\nimport(\n\t\"testing\"\n\t\"fmt\"\n)\n\nfunc TestPrint(t *testing.T){\n\t// t.SkipNow()\n\tres := Print1to20()\n\tfmt.Println(\"hey\")\n\tif res != 210{\n\t\tt.Errorf(\"Wrong result of Print1to20\")\n\t}\n}\n\nfunc TestPrint2(t *testing.T){\n\tres := Print1to20()\n\tres++\n\tif res !=211 {\n\t\tt.Errorf(\"Test Print2 failed\")\n\t}\n}\n\nfunc TestAll(t *testing.T){\n\tt.Run(\"TestPrint\", TestPrint)\n\tt.Run(\"TestPrint2\", TestPrint2)\n}\n\nfunc TestMain(m *testing.M){\n\tfmt.Println(\"Test begins...\")\n\tm.Run() \n}\n\n// func aaa(n int) int{\n// \tfor n > 0{\n// \t\tn--\n// \t}\n// \treturn n\n// }\n\n// go test -bench=.\nfunc BenchmarkAll(b * testing.B){\n\tfor n := 0; n < b.N; n++ {\n\t\tPrint1to20()\n\t\t// aaa(n)\n\t}\n}"
  }
]