Personal warehouse management system
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

275 lines
5.8KB

  1. package main
  2. import (
  3. "errors"
  4. "html/template"
  5. "io"
  6. "log"
  7. "math/rand"
  8. "net/http"
  9. "os"
  10. "path/filepath"
  11. "time"
  12. "janouch.name/sklad/imgutil"
  13. "janouch.name/sklad/label"
  14. "janouch.name/sklad/ql"
  15. )
  16. var templates = map[string]*template.Template{}
  17. func executeTemplate(name string, w io.Writer, data interface{}) {
  18. if err := templates[name].Execute(w, data); err != nil {
  19. panic(err)
  20. }
  21. }
  22. func wrap(inner func(http.ResponseWriter, *http.Request)) func(
  23. http.ResponseWriter, *http.Request) {
  24. return func(w http.ResponseWriter, r *http.Request) {
  25. if err := r.ParseForm(); err != nil {
  26. http.Error(w, err.Error(), http.StatusInternalServerError)
  27. return
  28. }
  29. if r.Method == http.MethodGet {
  30. w.Header().Set("Cache-Control", "no-store")
  31. }
  32. inner(w, r)
  33. }
  34. }
  35. func handleLogin(w http.ResponseWriter, r *http.Request) {
  36. redirect := r.FormValue("redirect")
  37. if redirect == "" {
  38. redirect = "/"
  39. }
  40. session := sessionGet(w, r)
  41. if session.LoggedIn {
  42. http.Redirect(w, r, redirect, http.StatusSeeOther)
  43. return
  44. }
  45. params := struct {
  46. IncorrectPassword bool
  47. }{}
  48. switch r.Method {
  49. case http.MethodGet:
  50. // We're just going to render the template.
  51. case http.MethodPost:
  52. if r.FormValue("password") == db.Password {
  53. session.LoggedIn = true
  54. http.Redirect(w, r, redirect, http.StatusSeeOther)
  55. return
  56. }
  57. params.IncorrectPassword = true
  58. default:
  59. w.WriteHeader(http.StatusMethodNotAllowed)
  60. return
  61. }
  62. executeTemplate("login.tmpl", w, &params)
  63. }
  64. func handleLogout(w http.ResponseWriter, r *http.Request) {
  65. if r.Method != http.MethodPost {
  66. w.WriteHeader(http.StatusMethodNotAllowed)
  67. return
  68. }
  69. session := r.Context().Value(sessionContextKey{}).(*Session)
  70. session.LoggedIn = false
  71. http.Redirect(w, r, "/", http.StatusSeeOther)
  72. }
  73. func handleContainer(w http.ResponseWriter, r *http.Request) {
  74. if r.Method == http.MethodPost {
  75. // TODO
  76. }
  77. if r.Method != http.MethodGet {
  78. w.WriteHeader(http.StatusMethodNotAllowed)
  79. return
  80. }
  81. allSeries := map[string]string{}
  82. for _, s := range indexSeries {
  83. allSeries[s.Prefix] = s.Description
  84. }
  85. var container *Container
  86. children := []*Container{}
  87. if id := ContainerId(r.FormValue("id")); id == "" {
  88. children = indexChildren[""]
  89. } else if c, ok := indexContainer[id]; ok {
  90. children = c.Children()
  91. container = c
  92. }
  93. params := struct {
  94. Container *Container
  95. Children []*Container
  96. AllSeries map[string]string
  97. }{
  98. Container: container,
  99. Children: children,
  100. AllSeries: allSeries,
  101. }
  102. executeTemplate("container.tmpl", w, &params)
  103. }
  104. func handleSeries(w http.ResponseWriter, r *http.Request) {
  105. if r.Method == http.MethodPost {
  106. // TODO
  107. }
  108. if r.Method != http.MethodGet {
  109. w.WriteHeader(http.StatusMethodNotAllowed)
  110. return
  111. }
  112. allSeries := map[string]string{}
  113. for _, s := range indexSeries {
  114. allSeries[s.Prefix] = s.Description
  115. }
  116. prefix := r.FormValue("prefix")
  117. description := ""
  118. if prefix == "" {
  119. } else if series, ok := indexSeries[prefix]; ok {
  120. description = series.Description
  121. }
  122. params := struct {
  123. Prefix string
  124. Description string
  125. AllSeries map[string]string
  126. }{
  127. Prefix: prefix,
  128. Description: description,
  129. AllSeries: allSeries,
  130. }
  131. executeTemplate("series.tmpl", w, &params)
  132. }
  133. func handleSearch(w http.ResponseWriter, r *http.Request) {
  134. if r.Method != http.MethodGet {
  135. w.WriteHeader(http.StatusMethodNotAllowed)
  136. return
  137. }
  138. query := r.FormValue("q")
  139. params := struct {
  140. Query string
  141. Series []*Series
  142. Containers []*Container
  143. }{
  144. Query: query,
  145. Series: dbSearchSeries(query),
  146. Containers: dbSearchContainers(query),
  147. }
  148. executeTemplate("search.tmpl", w, &params)
  149. }
  150. func printLabel(id string) error {
  151. printer, err := ql.Open()
  152. if err != nil {
  153. return err
  154. }
  155. if printer == nil {
  156. return errors.New("no suitable printer found")
  157. }
  158. defer printer.Close()
  159. /*
  160. printer.StatusNotify = func(status *ql.Status) {
  161. log.Printf("\x1b[1mreceived status\x1b[m\n%+v\n%s",
  162. status[:], status)
  163. }
  164. */
  165. if err := printer.Initialize(); err != nil {
  166. return err
  167. }
  168. if err := printer.UpdateStatus(); err != nil {
  169. return err
  170. }
  171. mediaInfo := ql.GetMediaInfo(
  172. printer.LastStatus.MediaWidthMM(),
  173. printer.LastStatus.MediaLengthMM(),
  174. )
  175. if mediaInfo == nil {
  176. return errors.New("unknown media")
  177. }
  178. return printer.Print(&imgutil.LeftRotate{Image: label.GenLabelForHeight(
  179. labelFont, id, mediaInfo.PrintAreaPins, db.BDFScale)})
  180. }
  181. func handleLabel(w http.ResponseWriter, r *http.Request) {
  182. if r.Method != http.MethodPost {
  183. w.WriteHeader(http.StatusMethodNotAllowed)
  184. return
  185. }
  186. params := struct {
  187. Id string
  188. UnknownId bool
  189. Error error
  190. }{
  191. Id: r.FormValue("id"),
  192. }
  193. if c := indexContainer[ContainerId(params.Id)]; c == nil {
  194. params.UnknownId = true
  195. } else {
  196. params.Error = printLabel(params.Id)
  197. }
  198. executeTemplate("label.tmpl", w, &params)
  199. }
  200. func main() {
  201. // Randomize the RNG for session string generation.
  202. rand.Seed(time.Now().UnixNano())
  203. if len(os.Args) != 3 {
  204. log.Fatalf("Usage: %s ADDRESS DATABASE-FILE\n", os.Args[0])
  205. }
  206. var address string
  207. address, dbPath = os.Args[1], os.Args[2]
  208. // Load database.
  209. if err := loadDatabase(); err != nil {
  210. log.Fatalln(err)
  211. }
  212. // Load HTML templates from the current working directory.
  213. m, err := filepath.Glob("*.tmpl")
  214. if err != nil {
  215. log.Fatalln(err)
  216. }
  217. for _, name := range m {
  218. templates[name] = template.Must(template.ParseFiles("base.tmpl", name))
  219. }
  220. // TODO: Eventually we will need to load a font file for label printing.
  221. // - The path might be part of configuration, or implicit by filename.
  222. http.HandleFunc("/login", wrap(handleLogin))
  223. http.HandleFunc("/logout", sessionWrap(wrap(handleLogout)))
  224. http.HandleFunc("/", sessionWrap(wrap(handleContainer)))
  225. http.HandleFunc("/series", sessionWrap(wrap(handleSeries)))
  226. http.HandleFunc("/search", sessionWrap(wrap(handleSearch)))
  227. http.HandleFunc("/label", sessionWrap(wrap(handleLabel)))
  228. log.Fatalln(http.ListenAndServe(address, nil))
  229. }