2020-08-05 15:08:59 +00:00
package main
import (
"errors"
"fmt"
"io/ioutil"
"log"
2020-10-22 16:42:48 +00:00
"mime/multipart"
2020-08-05 15:08:59 +00:00
"os"
"path/filepath"
2021-01-22 17:25:40 +00:00
"regexp"
2020-08-05 15:08:59 +00:00
2020-09-29 15:04:22 +00:00
"github.com/bouncepaw/mycorrhiza/history"
2021-01-02 21:25:04 +00:00
"github.com/bouncepaw/mycorrhiza/hyphae"
2020-10-30 13:25:48 +00:00
"github.com/bouncepaw/mycorrhiza/markup"
2021-01-28 19:07:21 +00:00
"github.com/bouncepaw/mycorrhiza/mimetype"
2020-11-18 13:07:53 +00:00
"github.com/bouncepaw/mycorrhiza/user"
2020-10-03 16:56:56 +00:00
"github.com/bouncepaw/mycorrhiza/util"
2020-08-05 15:08:59 +00:00
)
func init ( ) {
2020-10-30 13:25:48 +00:00
markup . HyphaExists = func ( hyphaName string ) bool {
2020-08-05 15:08:59 +00:00
_ , hyphaExists := HyphaStorage [ hyphaName ]
return hyphaExists
}
2020-10-30 13:25:48 +00:00
markup . HyphaAccess = func ( hyphaName string ) ( rawText , binaryBlock string , err error ) {
2020-08-05 15:08:59 +00:00
if hyphaData , ok := HyphaStorage [ hyphaName ] ; ok {
rawText , err = FetchTextPart ( hyphaData )
2021-02-04 15:47:09 +00:00
if hyphaData . BinaryPath != "" {
2020-08-05 15:08:59 +00:00
binaryBlock = binaryHtmlBlock ( hyphaName , hyphaData )
}
} else {
err = errors . New ( "Hypha " + hyphaName + " does not exist" )
}
return
}
2020-11-26 18:41:26 +00:00
markup . HyphaIterate = IterateHyphaNamesWith
2020-12-17 12:59:59 +00:00
markup . HyphaImageForOG = func ( hyphaName string ) string {
2021-02-04 15:47:09 +00:00
if hd , isOld := GetHyphaData ( hyphaName ) ; isOld && hd . BinaryPath != "" {
2020-12-17 12:59:59 +00:00
return util . URL + "/binary/" + hyphaName
}
return util . URL + "/favicon.ico"
}
2020-08-05 15:08:59 +00:00
}
2020-11-18 13:07:53 +00:00
// GetHyphaData finds a hypha addressed by `hyphaName` and returns its `hyphaData`. `hyphaData` is set to a zero value if this hypha does not exist. `isOld` is false if this hypha does not exist.
func GetHyphaData ( hyphaName string ) ( hyphaData * HyphaData , isOld bool ) {
hyphaData , isOld = HyphaStorage [ hyphaName ]
if hyphaData == nil {
hyphaData = & HyphaData { }
}
return
}
2020-08-05 15:08:59 +00:00
// HyphaData represents a hypha's meta information: binary and text parts rooted paths and content types.
2021-02-04 15:47:09 +00:00
type HyphaData hyphae . Hypha
2020-08-05 15:08:59 +00:00
2020-10-22 16:42:48 +00:00
// uploadHelp is a helper function for UploadText and UploadBinary
2020-11-18 13:07:53 +00:00
func uploadHelp ( hop * history . HistoryOp , hyphaName , ext string , data [ ] byte , u * user . User ) * history . HistoryOp {
2020-10-22 16:42:48 +00:00
var (
2020-11-18 13:07:53 +00:00
hyphaData , isOld = GetHyphaData ( hyphaName )
fullPath = filepath . Join ( WikiDir , hyphaName + ext )
2021-02-04 15:47:09 +00:00
originalFullPath = & hyphaData . TextPath
2020-10-22 16:42:48 +00:00
)
2020-11-18 13:07:53 +00:00
if hop . Type == history . TypeEditBinary {
2021-02-04 15:47:09 +00:00
originalFullPath = & hyphaData . BinaryPath
2020-11-18 13:07:53 +00:00
}
2020-10-22 16:42:48 +00:00
if err := os . MkdirAll ( filepath . Dir ( fullPath ) , 0777 ) ; err != nil {
return hop . WithError ( err )
}
if err := ioutil . WriteFile ( fullPath , data , 0644 ) ; err != nil {
return hop . WithError ( err )
}
2020-11-18 13:07:53 +00:00
2020-10-25 13:50:14 +00:00
if isOld && * originalFullPath != fullPath && * originalFullPath != "" {
if err := history . Rename ( * originalFullPath , fullPath ) ; err != nil {
2020-10-22 16:42:48 +00:00
return hop . WithError ( err )
}
2020-10-25 13:50:14 +00:00
log . Println ( "Move" , * originalFullPath , "to" , fullPath )
2020-10-22 16:42:48 +00:00
}
2020-11-18 13:07:53 +00:00
// New hyphae must be added to the hypha storage
2020-10-22 16:42:48 +00:00
if ! isOld {
2020-11-18 13:07:53 +00:00
HyphaStorage [ hyphaName ] = hyphaData
2021-01-02 21:25:04 +00:00
hyphae . IncrementCount ( )
2020-10-22 16:42:48 +00:00
}
2020-10-25 13:50:14 +00:00
* originalFullPath = fullPath
2021-01-22 17:25:40 +00:00
if isOld && hop . Type == history . TypeEditText && ! history . FileChanged ( fullPath ) {
2021-01-21 14:21:34 +00:00
return hop . Abort ( )
}
2020-10-22 16:42:48 +00:00
return hop . WithFiles ( fullPath ) .
2020-11-18 13:07:53 +00:00
WithUser ( u ) .
2020-10-22 16:42:48 +00:00
Apply ( )
}
2020-11-18 13:07:53 +00:00
// UploadText loads a new text part from `textData` for hypha `hyphaName`.
func UploadText ( hyphaName , textData string , u * user . User ) * history . HistoryOp {
return uploadHelp (
history .
Operation ( history . TypeEditText ) .
WithMsg ( fmt . Sprintf ( "Edit ‘ %s’ " , hyphaName ) ) ,
hyphaName , ".myco" , [ ] byte ( textData ) , u )
2020-10-22 16:42:48 +00:00
}
// UploadBinary loads a new binary part from `file` for hypha `hyphaName` with `hd`. The contents have the specified `mime` type. It must be marked if the hypha `isOld`.
2020-11-18 13:07:53 +00:00
func UploadBinary ( hyphaName , mime string , file multipart . File , u * user . User ) * history . HistoryOp {
2020-10-22 16:42:48 +00:00
var (
hop = history . Operation ( history . TypeEditBinary ) . WithMsg ( fmt . Sprintf ( "Upload binary part for ‘ %s’ with type ‘ %s’ " , hyphaName , mime ) )
data , err = ioutil . ReadAll ( file )
)
if err != nil {
2020-11-01 19:09:41 +00:00
return hop . WithError ( err ) . Apply ( )
2020-10-22 16:42:48 +00:00
}
2021-01-28 19:07:21 +00:00
return uploadHelp ( hop , hyphaName , mimetype . ToExtension ( mime ) , data , u )
2020-10-22 16:42:48 +00:00
}
2020-09-29 15:04:22 +00:00
// DeleteHypha deletes hypha and makes a history record about that.
2020-11-18 13:07:53 +00:00
func ( hd * HyphaData ) DeleteHypha ( hyphaName string , u * user . User ) * history . HistoryOp {
2020-10-03 14:19:49 +00:00
hop := history . Operation ( history . TypeDeleteHypha ) .
2021-02-04 15:47:09 +00:00
WithFilesRemoved ( hd . TextPath , hd . BinaryPath ) .
2020-09-29 15:04:22 +00:00
WithMsg ( fmt . Sprintf ( "Delete ‘ %s’ " , hyphaName ) ) .
2020-11-18 13:07:53 +00:00
WithUser ( u ) .
2020-09-29 15:04:22 +00:00
Apply ( )
2020-10-03 14:19:49 +00:00
if len ( hop . Errs ) == 0 {
delete ( HyphaStorage , hyphaName )
2021-01-02 21:25:04 +00:00
hyphae . DecrementCount ( )
2020-10-03 14:19:49 +00:00
}
return hop
2020-09-29 15:04:22 +00:00
}
2021-01-19 18:08:59 +00:00
// UnattachHypha unattaches hypha and makes a history record about that.
func ( hd * HyphaData ) UnattachHypha ( hyphaName string , u * user . User ) * history . HistoryOp {
hop := history . Operation ( history . TypeUnattachHypha ) .
2021-02-04 15:47:09 +00:00
WithFilesRemoved ( hd . BinaryPath ) .
2021-01-19 18:08:59 +00:00
WithMsg ( fmt . Sprintf ( "Unattach ‘ %s’ " , hyphaName ) ) .
WithUser ( u ) .
Apply ( )
if len ( hop . Errs ) == 0 {
hd , ok := HyphaStorage [ hyphaName ]
if ok {
2021-02-04 15:47:09 +00:00
if hd . BinaryPath != "" {
hd . BinaryPath = ""
2021-01-19 18:08:59 +00:00
}
// If nothing is left of the hypha
2021-02-04 15:47:09 +00:00
if hd . TextPath == "" {
2021-01-19 18:08:59 +00:00
delete ( HyphaStorage , hyphaName )
hyphae . DecrementCount ( )
}
}
}
return hop
}
2020-10-03 16:56:56 +00:00
func findHyphaeToRename ( hyphaName string , recursive bool ) [ ] string {
hyphae := [ ] string { hyphaName }
if recursive {
hyphae = append ( hyphae , util . FindSubhyphae ( hyphaName , IterateHyphaNamesWith ) ... )
}
return hyphae
}
2020-10-21 18:37:39 +00:00
func renamingPairs ( hyphaNames [ ] string , replaceName func ( string ) string ) ( map [ string ] string , error ) {
2020-10-03 16:56:56 +00:00
renameMap := make ( map [ string ] string )
for _ , hn := range hyphaNames {
if hd , ok := HyphaStorage [ hn ] ; ok {
2020-10-21 18:37:39 +00:00
if _ , nameUsed := HyphaStorage [ replaceName ( hn ) ] ; nameUsed {
return nil , errors . New ( "Hypha " + replaceName ( hn ) + " already exists" )
}
2021-02-04 15:47:09 +00:00
if hd . TextPath != "" {
renameMap [ hd . TextPath ] = replaceName ( hd . TextPath )
2020-10-03 16:56:56 +00:00
}
2021-02-04 15:47:09 +00:00
if hd . BinaryPath != "" {
renameMap [ hd . BinaryPath ] = replaceName ( hd . BinaryPath )
2020-10-03 16:56:56 +00:00
}
}
}
2020-10-21 18:37:39 +00:00
return renameMap , nil
2020-10-03 16:56:56 +00:00
}
// word Data is plural here
func relocateHyphaData ( hyphaNames [ ] string , replaceName func ( string ) string ) {
for _ , hyphaName := range hyphaNames {
if hd , ok := HyphaStorage [ hyphaName ] ; ok {
2021-02-04 15:47:09 +00:00
hd . TextPath = replaceName ( hd . TextPath )
hd . BinaryPath = replaceName ( hd . BinaryPath )
2020-10-03 16:56:56 +00:00
HyphaStorage [ replaceName ( hyphaName ) ] = hd
delete ( HyphaStorage , hyphaName )
}
}
}
// RenameHypha renames hypha from old name `hyphaName` to `newName` and makes a history record about that. If `recursive` is `true`, its subhyphae will be renamed the same way.
2020-11-18 13:07:53 +00:00
func RenameHypha ( hyphaName , newName string , recursive bool , u * user . User ) * history . HistoryOp {
2020-10-02 15:31:59 +00:00
var (
2021-01-22 17:25:40 +00:00
re = regexp . MustCompile ( ` (?i) ` + hyphaName )
2020-10-03 16:56:56 +00:00
replaceName = func ( str string ) string {
2021-01-22 17:25:40 +00:00
return re . ReplaceAllString ( CanonicalName ( str ) , newName )
2020-10-03 16:56:56 +00:00
}
2020-10-21 18:37:39 +00:00
hyphaNames = findHyphaeToRename ( hyphaName , recursive )
renameMap , err = renamingPairs ( hyphaNames , replaceName )
renameMsg = "Rename ‘ %s’ to ‘ %s’ "
hop = history . Operation ( history . TypeRenameHypha )
2020-10-02 15:31:59 +00:00
)
2020-10-21 18:37:39 +00:00
if err != nil {
hop . Errs = append ( hop . Errs , err )
return hop
}
2020-10-03 16:56:56 +00:00
if recursive {
renameMsg += " recursively"
}
hop . WithFilesRenamed ( renameMap ) .
WithMsg ( fmt . Sprintf ( renameMsg , hyphaName , newName ) ) .
2020-11-18 13:07:53 +00:00
WithUser ( u ) .
2020-10-03 16:56:56 +00:00
Apply ( )
2020-10-02 15:31:59 +00:00
if len ( hop . Errs ) == 0 {
2020-10-03 16:56:56 +00:00
relocateHyphaData ( hyphaNames , replaceName )
2020-10-02 15:31:59 +00:00
}
return hop
}
2020-08-05 15:08:59 +00:00
// binaryHtmlBlock creates an html block for binary part of the hypha.
2020-10-22 17:12:12 +00:00
func binaryHtmlBlock ( hyphaName string , hd * HyphaData ) string {
2021-02-04 15:47:09 +00:00
switch filepath . Ext ( hd . BinaryPath ) {
2020-10-22 17:12:12 +00:00
case ".jpg" , ".gif" , ".png" , ".webp" , ".svg" , ".ico" :
2020-08-05 15:08:59 +00:00
return fmt . Sprintf ( `
< div class = "binary-container binary-container_with-img" >
2020-11-26 18:41:26 +00:00
< a href = "/binary/%[1]s" > < img src = "/binary/%[1]s" / > < / a >
2020-08-05 15:08:59 +00:00
< / div > ` , hyphaName )
2020-10-22 17:12:12 +00:00
case ".ogg" , ".webm" , ".mp4" :
2020-08-05 15:08:59 +00:00
return fmt . Sprintf ( `
< div class = "binary-container binary-container_with-video" >
< video >
< source src = "/binary/%[1]s" / >
< p > Your browser does not support video . See video ' s < a href = "/binary/%[1]s" > direct url < / a > < / p >
< / video >
` , hyphaName )
2020-10-22 17:12:12 +00:00
case ".mp3" :
2020-08-05 15:08:59 +00:00
return fmt . Sprintf ( `
< div class = "binary-container binary-container_with-audio" >
< audio >
< source src = "/binary/%[1]s" / >
< p > Your browser does not support audio . See audio ' s < a href = "/binary/%[1]s" > direct url < / a > < / p >
< / audio >
` , hyphaName )
default :
return fmt . Sprintf ( `
< div class = "binary-container binary-container_with-nothing" >
2021-01-02 21:25:04 +00:00
< p > This hypha ' s media cannot be rendered . < a href = "/binary/%s" > Download it < / a > < / p >
2020-08-05 15:08:59 +00:00
< / div >
` , hyphaName )
}
}
2021-01-28 19:07:21 +00:00
// FetchTextPart tries to read text file in the `d`. If there is no file, empty string is returned.
func FetchTextPart ( d * HyphaData ) ( string , error ) {
2021-02-04 15:47:09 +00:00
if d . TextPath == "" {
2021-01-28 19:07:21 +00:00
return "" , nil
}
2021-02-04 15:47:09 +00:00
_ , err := os . Stat ( d . TextPath )
2021-01-28 19:07:21 +00:00
if os . IsNotExist ( err ) {
return "" , nil
} else if err != nil {
return "" , err
}
2021-02-04 15:47:09 +00:00
text , err := ioutil . ReadFile ( d . TextPath )
2021-01-28 19:07:21 +00:00
if err != nil {
return "" , err
}
return string ( text ) , nil
}
func setHeaderLinks ( ) {
if userLinksHypha , ok := GetHyphaData ( util . HeaderLinksHypha ) ; ! ok {
util . SetDefaultHeaderLinks ( )
} else {
2021-02-04 15:47:09 +00:00
contents , err := ioutil . ReadFile ( userLinksHypha . TextPath )
2021-01-28 19:07:21 +00:00
if err != nil || len ( contents ) == 0 {
util . SetDefaultHeaderLinks ( )
} else {
text := string ( contents )
util . ParseHeaderLinks ( text , markup . Rocketlink )
}
}
}
2021-02-08 14:59:00 +00:00
func HyphaToTemporaryWorkaround ( h * hyphae . Hypha ) * HyphaData {
return & HyphaData {
Name : h . Name ,
TextPath : h . TextPath ,
BinaryPath : h . BinaryPath ,
2020-08-05 15:08:59 +00:00
}
2021-02-08 14:59:00 +00:00
}
2020-08-05 15:08:59 +00:00
2021-02-08 14:59:00 +00:00
// MergeIn merges in content file paths from a different hypha object. Prints warnings sometimes.
func ( h * HyphaData ) MergeIn ( oh * hyphae . Hypha ) {
if h . TextPath == "" && oh . TextPath != "" {
h . TextPath = oh . TextPath
}
if oh . BinaryPath != "" {
if h . BinaryPath != "" {
log . Println ( "There is a file collision for binary part of a hypha:" , h . BinaryPath , "and" , oh . BinaryPath , "-- going on with the latter" )
2020-08-05 15:08:59 +00:00
}
2021-02-08 14:59:00 +00:00
h . BinaryPath = oh . BinaryPath
}
}
2020-08-05 15:08:59 +00:00
2021-02-08 14:59:00 +00:00
// Index finds all hypha files in the full `path` and saves them to HyphaStorage. This function is recursive.
func Index ( path string ) {
ch := make ( chan * hyphae . Hypha , 5 )
2020-10-22 16:42:48 +00:00
2021-02-08 14:59:00 +00:00
go func ( ) {
hyphae . Index ( path , 0 , ch )
close ( ch )
} ( )
for h := range ch {
if oldHypha , ok := HyphaStorage [ h . Name ] ; ok {
oldHypha . MergeIn ( h )
} else {
HyphaStorage [ h . Name ] = HyphaToTemporaryWorkaround ( h )
hyphae . IncrementCount ( )
}
2020-08-05 15:08:59 +00:00
}
2021-02-08 14:59:00 +00:00
2020-08-05 15:08:59 +00:00
}