-
Notifications
You must be signed in to change notification settings - Fork 139
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add web ui to view recent snapshots and install updates #16
- Loading branch information
Showing
45 changed files
with
1,783 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
build | ||
.DS_Store | ||
db | ||
db | ||
cmd/hkcam/fs.go |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package api | ||
|
||
import ( | ||
"github.com/brutella/hkcam/app" | ||
"github.com/go-chi/chi" | ||
|
||
"net/http" | ||
) | ||
|
||
const ( | ||
ErrorInvalidPayload = 1 | ||
ErrorInvalidRequest = 2 | ||
ErrorUnknown = 3 | ||
) | ||
|
||
type Api struct { | ||
App *app.App | ||
} | ||
|
||
func (a *Api) Router() http.Handler { | ||
r := chi.NewRouter() | ||
r.Get("/system/heartbeat", a.SystemHeartbeat) | ||
r.Get("/system/info", a.SystemInfo) | ||
r.Post("/system/restart", a.SystemRestart) | ||
r.Get("/snapshots/recent", a.RecentSnapshot) | ||
r.Get("/snapshots/new", a.NewSnapshot) | ||
|
||
return r | ||
} | ||
|
||
// RestartApp restarts the app. | ||
func (a *Api) RestartApp() { | ||
a.App.Restart() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package apiutil | ||
|
||
import ( | ||
"github.com/gorilla/schema" | ||
|
||
"net/http" | ||
"strconv" | ||
) | ||
|
||
// ParseInt64 converts a string to an 8-byte integer | ||
func ParseInt64(s string) (int64, error) { | ||
return strconv.ParseInt(s, 10, 64) | ||
} | ||
|
||
var decoder = schema.NewDecoder() | ||
|
||
func DecodeForm(w http.ResponseWriter, r *http.Request, v interface{}) error { | ||
if err := r.ParseForm(); err != nil { | ||
return err | ||
} | ||
|
||
return decoder.Decode(v, r.Form) | ||
} | ||
|
||
func DecodeURLQuery(w http.ResponseWriter, r *http.Request, v interface{}) error { | ||
if err := r.ParseForm(); err != nil { | ||
return err | ||
} | ||
|
||
return decoder.Decode(v, r.URL.Query()) | ||
} | ||
|
||
func ToBool(s string) bool { | ||
switch s { | ||
case "on": | ||
return true | ||
case "off": | ||
return false | ||
default: | ||
v, _ := strconv.ParseBool(s) | ||
return v | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package apiutil | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"io" | ||
"net/http" | ||
) | ||
|
||
func JSONEncode(v interface{}) (*bytes.Buffer, error) { | ||
buf := &bytes.Buffer{} | ||
enc := json.NewEncoder(buf) | ||
enc.SetEscapeHTML(true) | ||
err := enc.Encode(v) | ||
|
||
return buf, err | ||
} | ||
|
||
func JSONDecode(r io.Reader, v interface{}) error { | ||
return json.NewDecoder(r).Decode(v) | ||
} | ||
|
||
func WriteJSON(w http.ResponseWriter, r *http.Request, v interface{}) error { | ||
buf, err := JSONEncode(v) | ||
if err != nil { | ||
http.Error(w, err.Error(), http.StatusInternalServerError) | ||
return err | ||
} | ||
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8") | ||
_, err = w.Write(buf.Bytes()) | ||
return err | ||
} | ||
|
||
func ReadJSON(rc io.Reader, v interface{}) error { | ||
if err := JSONDecode(rc, v); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package api | ||
|
||
type ErrResponse struct { | ||
Error Error `json:"error"` | ||
} | ||
|
||
type Error struct { | ||
Message string `json:"message"` | ||
Code int `json:"code"` | ||
} | ||
|
||
func NewErrResponse(err error, code int) *ErrResponse { | ||
return &ErrResponse{ | ||
Error{ | ||
Message: err.Error(), | ||
Code: code, | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package api | ||
|
||
import ( | ||
"github.com/brutella/hkcam/api/apiutil" | ||
"net/http" | ||
) | ||
|
||
// WriteJSON responds to request r by encoding and sending v as json. | ||
// If v is an instance of of an ErrResponse, the response status code is 400 (Bad Request). | ||
func WriteJSON(w http.ResponseWriter, r *http.Request, v interface{}) error { | ||
switch v.(type) { | ||
case *ErrResponse, ErrResponse: | ||
w.WriteHeader(http.StatusBadRequest) | ||
default: | ||
w.WriteHeader(http.StatusOK) | ||
} | ||
|
||
return apiutil.WriteJSON(w, r, v) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package api | ||
|
||
import ( | ||
"github.com/brutella/hkcam/api/apiutil" | ||
|
||
"bytes" | ||
"fmt" | ||
"image/jpeg" | ||
"net/http" | ||
"time" | ||
) | ||
|
||
// SnapshotRequest is a request restart the system. | ||
type SnapshotRequest struct { | ||
Width uint `schema:"width"` | ||
Height uint `schema:"height"` | ||
} | ||
|
||
// SnapshotResponse is a response to a SnapshotRequest. | ||
type SnapshotResponse struct { | ||
Data *SnapshotResponseData `json:"data"` | ||
} | ||
|
||
// SnapshotResponseData is the response data of a SnapshotRequest. | ||
type SnapshotResponseData struct { | ||
Date *time.Time `json:"date,omitempty"` | ||
Bytes []byte `json:"bytes"` | ||
} | ||
|
||
// RecentSnapshot responds with the recent snapshot. | ||
func (a *Api) RecentSnapshot(w http.ResponseWriter, r *http.Request) { | ||
req := SnapshotRequest{ | ||
Width: 1920, | ||
Height: 1080, | ||
} | ||
var resp interface{} | ||
|
||
if err := apiutil.DecodeURLQuery(w, r, &req); err != nil { | ||
resp = NewErrResponse(fmt.Errorf("invalid payload"), ErrorInvalidPayload) | ||
} else if req.Width == 0 || req.Height == 0 { | ||
resp = NewErrResponse(fmt.Errorf("invalid payload"), ErrorInvalidPayload) | ||
} else if snapshot := a.App.FFMPEG.RecentSnapshot(req.Width, req.Height); snapshot != nil { | ||
buf := new(bytes.Buffer) | ||
if err := jpeg.Encode(buf, snapshot.Image, nil); err != nil { | ||
resp = NewErrResponse(fmt.Errorf("encode: %v", err), ErrorUnknown) | ||
} | ||
resp = SnapshotResponse{ | ||
Data: &SnapshotResponseData{ | ||
Bytes: buf.Bytes(), | ||
Date: &snapshot.Date, | ||
}, | ||
} | ||
} else { | ||
resp = SnapshotResponse{} | ||
} | ||
|
||
if err := WriteJSON(w, r, resp); err != nil { | ||
fmt.Println("responding failed", err) | ||
} | ||
} | ||
|
||
// NewSnapshot create a new snapshot. | ||
func (a *Api) NewSnapshot(w http.ResponseWriter, r *http.Request) { | ||
req := SnapshotRequest{ | ||
Width: 1920, | ||
Height: 1080, | ||
} | ||
var resp interface{} | ||
|
||
if err := apiutil.DecodeURLQuery(w, r, &req); err != nil { | ||
resp = NewErrResponse(fmt.Errorf("invalid payload"), ErrorInvalidPayload) | ||
} else if req.Width == 0 || req.Height == 0 { | ||
resp = NewErrResponse(fmt.Errorf("invalid payload"), ErrorInvalidPayload) | ||
} else if snapshot, err := a.App.FFMPEG.Snapshot(req.Width, req.Height); err != nil { | ||
resp = NewErrResponse(fmt.Errorf("snapshot: %v", err), ErrorUnknown) | ||
} else { | ||
buf := new(bytes.Buffer) | ||
if err := jpeg.Encode(buf, snapshot.Image, nil); err != nil { | ||
resp = NewErrResponse(fmt.Errorf("encode: %v", err), ErrorUnknown) | ||
} else { | ||
resp = SnapshotResponse{ | ||
Data: &SnapshotResponseData{ | ||
Bytes: buf.Bytes(), | ||
}, | ||
} | ||
} | ||
} | ||
|
||
if err := WriteJSON(w, r, resp); err != nil { | ||
fmt.Println("responding failed", err) | ||
} | ||
} |
Oops, something went wrong.