Commit 9452ff16 authored by Ronny Bangsund's avatar Ronny Bangsund

First commit.

-Website with form to post a scrap of code
-Necessary folders are created on startup if they don't exist
-Index is saved with hash as name, containing the language and expiry timestamp
-Actual data is saved in "./data/<language>/<hash>" (TODO: make datapath configurable)
-Scrap struct has save, load, check for expiry and delete funcs
-Checking for expiry also deletes the two associated files if it has expired

TODO: Get a list of 10 recent posts and display them on the front page
TODO: Store username with cookies
TODO: Store username in index files
TODO: Timers to delete expired files during runtime, instead of relying on startup
parents
package main
import (
"log"
)
func info(f string, v ...interface{}) {
log.Printf(f, v...)
}
func fatal(f string, v ...interface{}) {
log.Fatalf(f, v...)
}
// Syncer project main.go
package main
import (
"code.google.com/p/gcfg"
"flag"
)
const PROGVERSION string = "0.1.0"
type Config struct {
Main struct {
Address string
Port int
Sitename string
Root string
Secure bool
}
}
var cfg Config
var cfgfile *string = flag.String("config", "scraps.conf", "Name of the configuration file to use.")
// Update settings
var interval int = 900
var server_id string = "Syncer"
func main() {
flag.Parse()
err := gcfg.ReadFileInto(&cfg, *cfgfile)
if err != nil {
fatal(err.Error())
}
info("Scraps starting up.")
janitorQuit := initJanitor()
defer close(janitorQuit)
loadScraps()
initWeb()
endWeb()
}
// Scrap structure and handling
//
// Each scrap has an index file named with a hash, containing two lines.
// These are the language and the expiry of the snippet.
// Inside the proper language directory is another file with the same hash name,
// containing the actual code snippet.
package main
import (
"bufio"
"code.google.com/p/go.crypto/sha3"
"fmt"
"io"
"io/ioutil"
"os"
"strconv"
"time"
)
var scraps = map[string]Scrap{}
type Scrap struct {
Hash string
Language string
User string
Content string
Expiry int64
}
func (s Scrap) hasExpired() bool {
if s.Expiry < time.Now().Unix() {
info("%s has expired.", s.Hash)
s.delete()
return true
}
return false
}
func (s Scrap) getHash() string {
hash := sha3.NewKeccak256()
io.WriteString(hash, s.User)
io.WriteString(hash, s.Content)
io.WriteString(hash, fmt.Sprintf("%d", s.Expiry))
return fmt.Sprintf("%x", hash.Sum(nil))
}
func (s Scrap) save() bool {
_, ok := languages[s.Language]
if !ok {
// Unsupported language (poster probably modified source)
return false
}
name := "./data/" + s.Hash
file, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
info("Error: %s.", err.Error())
return false
}
defer file.Close()
info("%s, %d", s.Language, s.Expiry)
lang := s.Language
file.WriteString(fmt.Sprintf("%s\n%d\n", lang, s.Expiry))
name = "./data/" + s.Language + "/" + s.Hash
file, err = os.OpenFile(name, os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
info("Error: %s.", err.Error())
return false
}
defer file.Close()
file.WriteString(s.Content)
return true
}
func (s Scrap) load() bool {
file, err := os.Open("./data/" + s.Hash)
if err != nil {
info("Error: %s.", err.Error())
return false
}
defer file.Close()
scanner := bufio.NewScanner(file)
scanner.Scan()
s.Language = scanner.Text()
scanner.Scan()
expiry, _ := strconv.ParseInt(scanner.Text(), 10, 64)
s.Expiry = expiry
if s.hasExpired() {
return false
}
return true
}
func (s Scrap) delete() {
os.Remove("./data/" + s.Hash)
os.Remove("./data/" + s.Language + "/" + s.Hash)
}
// Open all index files and load the associated scrap,
// and delete it if it doesn't exist. Set ut a cleanup
// timer for each as needed, and delete any which have
// already expired.
func loadScraps() {
files, err := ioutil.ReadDir("./data")
if err != nil {
fatal("Error: %s.", err.Error())
}
for _, fi := range files {
if !fi.IsDir() {
s := Scrap{Hash: fi.Name()}
if s.load() {
scraps[s.Hash] = s
info("Scrap %s loaded.", fi.Name())
}
}
}
}
[main]
; 0.0.0.0 to bind to all addresses
; 127.0.0.1 to keep it local, for example to share via nginx on a different port
; Any full IP address to bind to only that
address = 0.0.0.0
; The port must be above the privileged range if running as an ordinary user (recommended)
port = 1280
; This is shown on the index page
sitename = Scraps snippet sharing
; This is where sync folders are created
root = /Users/orb/Syncer
; Use SSL/TLS connections only
secure = true
package main
import (
"math/rand"
"os"
"time"
)
var languages = map[string]string{
"bash": "Bash",
"c": "C",
"csharp": "C#",
"go": "Go",
"ini": "INI config",
"java": "Java",
"lua": "Lua",
"objc": "Objective-C",
"php": "PHP",
"python": "Python",
"ruby": "Ruby",
"text": "Plain text",
}
func init() {
rand.Seed(time.Now().UnixNano())
// Set up directories for code snippets by language
for k, _ := range languages {
f := "./data/" + k
if !fexists(f) {
info("Creating '%s'.", f)
os.MkdirAll(f, 0700)
}
}
}
func fexists(path string) bool {
_, err := os.Stat(path)
return err == nil
}
func initJanitor() chan bool {
ticker := time.NewTicker(time.Second * 300)
quit := make(chan bool)
go func() {
for {
select {
case <-ticker.C:
info(" Tick! Tock!")
case <-quit:
ticker.Stop()
return
}
}
}()
return quit
}
<html>
<head>
<title>{{.Title}}</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<meta name="author" content="Neural Short-Circuit">
</head>
<body style="background-color:#A7DBFF;">
<div style="position:absolute; top:1%; left:2%; width:80%;">
<h1>{{.Sitename}}</h1>
<form name="scrap" style="position:relative; left:-9px; top:0;" method="post" action="/submit">
<select name="expiry" style="position:relative; left:10px; top:0;">
<option value="burn" >Burn after reading</option>
<option value="hour" >An hour</option>
<option value="day" >A day</option>
<option value="week" >A week</option>
</select>
<select name="language" style="position:relative; left:10px; top:0;">
{{with .}}
{{range $k,$v := .Folders}}
<option value="{{$k}}">{{$v}}</option>
{{end}}
{{end}}
</select>
<label for="expiry" style="left:2px;top:2px;position:relative;float:left;">Expiry:</label>
<textarea name="content" style="left:0;top:28px;width:100%;background-color:#FFFFFF;" rows="30"></textarea>
<input type="submit" value="Post scrap" />
<label for="username" style="left:20px; position:relative; top:2px;">Username:</label>
<input type="text" name="username" value="anonymous" style="position:relative; left:19px; top:1px; width:192px;" />
</form>
</div>
<div style="position:absolute;right:0;top:0;width:15%;height:100%;border-style:solid;border-left:2px solid black;border-top:0;border-right:0;border-bottom:0;padding-left:4px;background-color:white;">
<h2>Scraps</h2>
</div>
</body>
</html>
package main
import (
"strings"
)
func sane(s string) string {
return strings.TrimSpace(s)
}
func validLanguage(s string) bool {
if s == "" {
return false
}
return true
}
package main
import (
"fmt"
"github.com/hoisie/web"
"html/template"
"time"
)
type IndexPage struct {
Title string
Sitename string
Languages map[string]string
}
var indexTemplate, _ = template.ParseFiles("tpl/index.tpl")
var userTemplate, _ = template.ParseFiles("tpl/user.tpl")
var webserver *web.Server
func initWeb() {
info("Launching web service.")
web.Post("/submit", post_scrap)
web.Get("/(.*)", get_index)
web.Run(fmt.Sprintf("%s:%d", cfg.Main.Address, cfg.Main.Port))
}
func endWeb() {
info("Shutting down web service.")
web.Close()
}
func get_index(ctx *web.Context, p string) {
ctx.SetHeader("Content-type", "text/html", true)
title := cfg.Main.Sitename
name := cfg.Main.Sitename
if p != "" {
ctx.WriteString("Posted:")
}
indexTemplate.Execute(ctx.ResponseWriter, &IndexPage{Title: title, Sitename: name, Languages: languages})
}
func post_scrap(ctx *web.Context) {
// ctx.Redirect(303, "/")
p := ctx.Params
lang := sane(p["language"])
if !validLanguage(lang) {
info("Invalid language '%s' from %s.", lang, ctx.Request.RemoteAddr)
return
}
user := sane(p["username"])
content := p["content"]
expiry := sane(p["expiry"])
when := int64(0)
now := time.Now().Unix()
if expiry != "burn" {
switch expiry {
case "hour":
when = int64(time.Hour.Seconds())
case "day":
when = int64(time.Hour.Seconds()) * 24
case "week":
when = int64(time.Hour.Seconds()) * 24 * 7
}
}
s := Scrap{
Language: lang,
User: user,
Content: content,
Expiry: now + when,
}
s.Hash = s.getHash()
if s.save() {
info("Saved scrap %s.", s.Hash)
} else {
info("Error saving scrap %s.", s.Hash)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment