2020-06-24 14:01:51 +00:00
package fs
import (
"encoding/json"
"errors"
2020-06-24 16:19:44 +00:00
"fmt"
2020-06-24 14:01:51 +00:00
"io/ioutil"
"log"
"net/http"
"path/filepath"
"strconv"
2020-06-25 20:31:58 +00:00
"strings"
2020-06-24 14:01:51 +00:00
"github.com/bouncepaw/mycorrhiza/cfg"
)
type Hypha struct {
Exists bool ` json:"-" `
FullName string ` json:"-" `
ViewCount int ` json:"views" `
Deleted bool ` json:"deleted" `
Revisions map [ string ] * Revision ` json:"revisions" `
actual * Revision ` json:"-" `
}
2020-06-25 17:18:50 +00:00
func ( h * Hypha ) TextPath ( ) string {
return h . actual . TextPath
}
2020-06-25 20:31:58 +00:00
func ( h * Hypha ) TagsJoined ( ) string {
if h . Exists {
return strings . Join ( h . actual . Tags , ", " )
}
return ""
}
func ( h * Hypha ) TextMime ( ) string {
if h . Exists {
return h . actual . TextMime
}
return "text/markdown"
}
func ( h * Hypha ) TextContent ( ) string {
if h . Exists {
contents , err := ioutil . ReadFile ( h . TextPath ( ) )
if err != nil {
log . Println ( "Could not read" , h . FullName )
return "Error: could not hypha text content file. It is recommended to cancel editing. Please contact the wiki admin. If you are the admin, see the logs."
}
return string ( contents )
}
return fmt . Sprintf ( cfg . DescribeHyphaHerePattern , h . FullName )
}
2020-06-24 14:01:51 +00:00
func ( s * Storage ) Open ( name string ) ( * Hypha , error ) {
h := & Hypha {
Exists : true ,
FullName : name ,
}
path , ok := s . paths [ name ]
// This hypha does not exist yet
if ! ok {
log . Println ( "Hypha" , name , "does not exist" )
h . Exists = false
h . Revisions = make ( map [ string ] * Revision )
} else {
metaJsonText , err := ioutil . ReadFile ( filepath . Join ( path , "meta.json" ) )
if err != nil {
log . Fatal ( err )
return nil , err
}
err = json . Unmarshal ( metaJsonText , & h )
if err != nil {
log . Fatal ( err )
return nil , err
}
// fill in rooted paths to content files and full names
for idStr , rev := range h . Revisions {
rev . FullName = filepath . Join ( h . parentName ( ) , rev . ShortName )
rev . Id , _ = strconv . Atoi ( idStr )
if rev . BinaryName != "" {
rev . BinaryPath = filepath . Join ( path , rev . BinaryName )
}
rev . TextPath = filepath . Join ( path , rev . TextName )
}
err = h . OnRevision ( "0" )
return h , err
}
return h , nil
}
func ( h * Hypha ) parentName ( ) string {
return filepath . Dir ( h . FullName )
}
func ( h * Hypha ) metaJsonPath ( ) string {
return filepath . Join ( cfg . WikiDir , h . FullName , "meta.json" )
}
// OnRevision tries to change to a revision specified by `id`.
func ( h * Hypha ) OnRevision ( id string ) error {
if len ( h . Revisions ) == 0 {
return errors . New ( "This hypha has no revisions" )
}
if id == "0" {
id = h . NewestId ( )
}
// Revision must be there, so no error checking
if rev , _ := h . Revisions [ id ] ; true {
h . actual = rev
}
return nil
}
// NewestId finds the largest id among all revisions.
func ( h * Hypha ) NewestId ( ) string {
var largest int
for k , _ := range h . Revisions {
id , _ := strconv . Atoi ( k )
if id > largest {
largest = id
}
}
return strconv . Itoa ( largest )
}
func ( h * Hypha ) PlainLog ( s string ) {
2020-06-25 17:18:50 +00:00
if h . Exists {
log . Println ( h . FullName , h . actual . Id , s )
} else {
log . Println ( "nonexistent" , h . FullName , s )
}
2020-06-24 14:01:51 +00:00
}
func ( h * Hypha ) mimeTypeForActionRaw ( ) string {
// If text mime type is text/html, it is not good as it will be rendered.
if h . actual . TextMime == "text/html" {
return "text/plain"
}
return h . actual . TextMime
}
2020-06-24 16:19:44 +00:00
// hasBinaryData returns true if the revision has any binary data associated.
// During initialisation, it is guaranteed that r.BinaryMime is set to "" if the revision has no binary data. (is it?)
func ( h * Hypha ) hasBinaryData ( ) bool {
return h . actual . BinaryMime != ""
}
2020-06-24 14:01:51 +00:00
// ActionRaw is used with `?action=raw`.
// It writes text content of the revision without any parsing or rendering.
func ( h * Hypha ) ActionRaw ( w http . ResponseWriter ) {
fileContents , err := ioutil . ReadFile ( h . actual . TextPath )
if err != nil {
log . Fatal ( err )
return
}
w . Header ( ) . Set ( "Content-Type" , h . mimeTypeForActionRaw ( ) )
w . WriteHeader ( http . StatusOK )
w . Write ( fileContents )
h . PlainLog ( "Serving raw text" )
}
2020-06-25 17:18:50 +00:00
// ActionBinary is used with `?action=binary`.
2020-06-24 14:01:51 +00:00
// It writes contents of binary content file.
2020-06-25 17:18:50 +00:00
func ( h * Hypha ) ActionBinary ( w http . ResponseWriter ) {
2020-06-24 14:01:51 +00:00
fileContents , err := ioutil . ReadFile ( h . actual . BinaryPath )
if err != nil {
log . Fatal ( err )
return
}
w . Header ( ) . Set ( "Content-Type" , h . actual . BinaryMime )
w . WriteHeader ( http . StatusOK )
w . Write ( fileContents )
h . PlainLog ( "Serving raw text" )
}
2020-06-24 16:19:44 +00:00
// ActionZen is used with `?action=zen`.
// It renders the hypha but without any layout or styles. Pure. Zen.
func ( h * Hypha ) ActionZen ( w http . ResponseWriter ) {
html , err := h . asHtml ( )
if err != nil {
log . Println ( err )
w . WriteHeader ( http . StatusInternalServerError )
return
}
w . Header ( ) . Set ( "Content-Type" , "text/html;charset=utf-8" )
w . WriteHeader ( http . StatusOK )
w . Write ( [ ] byte ( html ) )
h . PlainLog ( "Rendering zen" )
}
2020-06-25 17:18:50 +00:00
// ActionView is used with `?action=view` or no action at all.
// It renders the page, the layout and everything else.
func ( h * Hypha ) ActionView ( w http . ResponseWriter , renderExists , renderNotExists func ( string , string ) string ) {
var html string
var err error
if h . Exists {
html , err = h . asHtml ( )
if err != nil {
log . Println ( err )
w . WriteHeader ( http . StatusInternalServerError )
return
}
}
w . Header ( ) . Set ( "Content-Type" , "text/html;charset=utf-8" )
w . WriteHeader ( http . StatusOK )
if h . Exists {
w . Write ( [ ] byte ( renderExists ( h . FullName , html ) ) )
} else {
w . Write ( [ ] byte ( renderNotExists ( h . FullName , "" ) ) )
}
h . PlainLog ( "Rendering hypha view" )
}