2022-05-20 00:46:14 +03:00
|
|
|
package objects
|
|
|
|
|
|
|
|
import (
|
2022-06-01 13:26:54 +03:00
|
|
|
"encoding/json"
|
2022-05-21 16:17:25 +03:00
|
|
|
"errors"
|
2022-05-20 00:46:14 +03:00
|
|
|
"fmt"
|
|
|
|
"os"
|
2022-06-01 13:26:54 +03:00
|
|
|
"path"
|
2022-05-20 01:59:53 +03:00
|
|
|
"path/filepath"
|
2022-06-01 13:26:54 +03:00
|
|
|
"sync"
|
2022-05-20 01:59:53 +03:00
|
|
|
"time"
|
2022-05-21 16:17:25 +03:00
|
|
|
|
|
|
|
"github.com/JustKato/FreePad/lib/helper"
|
2022-05-20 00:46:14 +03:00
|
|
|
)
|
|
|
|
|
2022-06-01 13:26:54 +03:00
|
|
|
// Initialize the views cache
|
|
|
|
var ViewsCache map[string]uint32 = make(map[string]uint32)
|
|
|
|
|
|
|
|
// Mutex lock for the ViewsCache
|
|
|
|
var viewersLock sync.Mutex
|
|
|
|
|
2022-05-20 00:46:14 +03:00
|
|
|
type Post struct {
|
2022-05-20 01:40:21 +03:00
|
|
|
Name string `json:"name"`
|
|
|
|
LastModified string `json:"last_modified"`
|
|
|
|
Content string `json:"content"`
|
2022-06-01 13:26:54 +03:00
|
|
|
Views uint32 `json:"views"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the path to the views JSON
|
|
|
|
func getViewsFilePath() (string, error) {
|
|
|
|
// Get the path to the storage then append the const name for the storage file
|
|
|
|
filePath := path.Join(getStorageDirectory(), "views_storage.json")
|
|
|
|
|
|
|
|
// Check if the file exists
|
|
|
|
if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) {
|
|
|
|
// Create the file
|
|
|
|
err := os.WriteFile(filePath, []byte(""), 0777)
|
|
|
|
if err != nil {
|
|
|
|
return ``, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the file path
|
|
|
|
return filePath, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load the views cache from file
|
|
|
|
func LoadViewsCache() error {
|
|
|
|
// Get the views file path
|
|
|
|
viewsFilePath, err := getViewsFilePath()
|
|
|
|
if err != nil {
|
|
|
|
// This is now a problem for the upstairs
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read the contents of the file as a map
|
|
|
|
f, err := os.ReadFile(viewsFilePath)
|
|
|
|
if err != nil {
|
|
|
|
// This is now a problem for the upstairs
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the contents are valid
|
|
|
|
if len(f) <= 0 && len(string(f)) <= 0 {
|
|
|
|
// The file is completely empty!
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var parsedData map[string]uint32
|
|
|
|
|
|
|
|
// Parse the data
|
|
|
|
err = json.Unmarshal(f, &parsedData)
|
|
|
|
if err != nil {
|
|
|
|
// This is now a problem for the function caller :D
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
storageDir := getStorageDirectory()
|
|
|
|
// Loop through all of the mapped files
|
|
|
|
for fileName := range parsedData {
|
|
|
|
// Grab the path to the file
|
|
|
|
// TODO: Create a generic function that checks if a post exists by name to use here adn in the GetPost method.
|
|
|
|
filePath := path.Join(storageDir, fileName)
|
|
|
|
// Check if the file exists
|
|
|
|
if _, err := os.Stat(filePath); !os.IsNotExist(err) {
|
|
|
|
// Looks like the file does not exist anymore, remove it from the map
|
|
|
|
delete(parsedData, fileName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the current cache
|
|
|
|
ViewsCache = parsedData
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func AddViewToPost(postName string) uint32 {
|
|
|
|
// Lock the viewers mapping
|
|
|
|
viewersLock.Lock()
|
|
|
|
|
|
|
|
// Check if the map has any value set to this elem
|
|
|
|
if _, ok := ViewsCache[postName]; !ok {
|
|
|
|
// Set the map value
|
|
|
|
ViewsCache[postName] = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add to the counter
|
|
|
|
ViewsCache[postName]++
|
|
|
|
|
|
|
|
// Unlock
|
|
|
|
viewersLock.Unlock()
|
|
|
|
|
|
|
|
// Return the value
|
|
|
|
return ViewsCache[postName]
|
|
|
|
}
|
|
|
|
|
|
|
|
func SavePostViewsCache() error {
|
|
|
|
|
|
|
|
data, err := json.Marshal(ViewsCache)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
viewsFilePath, err := getViewsFilePath()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
f, err := os.OpenFile(viewsFilePath, os.O_WRONLY|os.O_CREATE, 0777)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// Actually close the file
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
// Delete all past content
|
|
|
|
err = f.Truncate(0)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset pointer
|
|
|
|
_, err = f.Seek(0, 0)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write the json into storage
|
|
|
|
_, err = f.Write(data)
|
|
|
|
|
|
|
|
return err
|
2022-05-20 00:46:14 +03:00
|
|
|
}
|
|
|
|
|
2022-06-01 11:32:12 +03:00
|
|
|
// Get the path to the storage directory
|
2022-05-20 00:46:14 +03:00
|
|
|
func getStorageDirectory() string {
|
|
|
|
|
|
|
|
baseStoragePath, exists := os.LookupEnv("PAD_STORAGE_PATH")
|
|
|
|
if !exists {
|
|
|
|
baseStoragePath = "/tmp/"
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the base storage path exists
|
|
|
|
if _, err := os.Stat(baseStoragePath); os.IsNotExist(err) {
|
|
|
|
// Looks like the base storage path was NOT set, create the dir
|
|
|
|
err = os.Mkdir(baseStoragePath, 0777)
|
|
|
|
// Check for errors
|
|
|
|
if err != nil {
|
|
|
|
// No way this sends an error unless it goes horribly wrong.
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the base storage path
|
|
|
|
return baseStoragePath
|
|
|
|
}
|
|
|
|
|
2022-06-01 11:32:12 +03:00
|
|
|
// Get a post from the file system
|
2022-05-20 00:46:14 +03:00
|
|
|
func GetPost(fileName string) Post {
|
|
|
|
// Get the base storage directory and make sure it exists
|
|
|
|
storageDir := getStorageDirectory()
|
|
|
|
|
|
|
|
// Generate the file path
|
|
|
|
filePath := fmt.Sprintf("%s%s", storageDir, fileName)
|
|
|
|
|
2022-06-01 13:26:54 +03:00
|
|
|
// Get the post views and add 1 to them
|
|
|
|
postViews := AddViewToPost(fileName)
|
|
|
|
|
2022-05-20 00:46:14 +03:00
|
|
|
p := Post{
|
2022-05-20 01:40:21 +03:00
|
|
|
Name: fileName,
|
|
|
|
Content: "",
|
2022-06-01 13:26:54 +03:00
|
|
|
Views: postViews,
|
2022-05-20 01:40:21 +03:00
|
|
|
LastModified: "Never Before",
|
2022-05-20 00:46:14 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the file exits
|
|
|
|
if _, err := os.Stat(filePath); !os.IsNotExist(err) {
|
|
|
|
// File does exist, read it and set the content
|
|
|
|
data, err := os.ReadFile(filePath)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("Error:", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the content of the file and put it in the response
|
|
|
|
p.Content = string(data)
|
2022-05-20 01:40:21 +03:00
|
|
|
|
|
|
|
// Get last modified date
|
|
|
|
fileData, err := os.Stat(filePath)
|
|
|
|
if err == nil {
|
|
|
|
p.LastModified = fileData.ModTime().Format("02/01/2006 03:04:05 PM")
|
|
|
|
} else {
|
|
|
|
fmt.Println(err)
|
|
|
|
}
|
2022-05-20 00:46:14 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
2022-06-01 11:32:12 +03:00
|
|
|
// Write a post to the file system
|
2022-05-20 00:46:14 +03:00
|
|
|
func WritePost(p Post) error {
|
|
|
|
|
2022-05-21 16:17:25 +03:00
|
|
|
maximumPadSize := helper.GetMaximumPadSize()
|
|
|
|
if len(p.Content) > maximumPadSize {
|
|
|
|
return errors.New("The pad is too big, please limit to the maximum of " + fmt.Sprint(maximumPadSize) + " characters")
|
|
|
|
}
|
|
|
|
|
2022-05-20 01:40:21 +03:00
|
|
|
// Get the base storage directory and make sure it exists
|
|
|
|
storageDir := getStorageDirectory()
|
|
|
|
|
|
|
|
// Generate the file path
|
|
|
|
filePath := fmt.Sprintf("%s%s", storageDir, p.Name)
|
|
|
|
|
|
|
|
f, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-06-01 13:26:54 +03:00
|
|
|
// Actually close the file
|
|
|
|
defer f.Close()
|
2022-05-20 01:40:21 +03:00
|
|
|
|
|
|
|
// Write the contnets
|
|
|
|
_, err = f.WriteString(p.Content)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := f.Close(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-05-20 00:46:14 +03:00
|
|
|
return nil
|
|
|
|
}
|
2022-05-20 01:59:53 +03:00
|
|
|
|
2022-06-01 11:32:12 +03:00
|
|
|
// Cleanup all of the older posts based on the environment settings
|
2022-05-20 01:59:53 +03:00
|
|
|
func CleanupPosts(age int) {
|
|
|
|
// Initialize the files buffer
|
|
|
|
var files []string
|
|
|
|
|
|
|
|
// Get the base path
|
|
|
|
root := getStorageDirectory()
|
|
|
|
|
|
|
|
// Scan the directory
|
|
|
|
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
|
|
|
|
|
|
// Check that the file is not a dir
|
|
|
|
if !info.IsDir() {
|
|
|
|
files = append(files, path)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
// Check if we had any errors
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("[Task::Cleanup]: Error", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// The timestamp where the file should be deleted
|
|
|
|
tooOldTime := time.Now()
|
|
|
|
|
|
|
|
// Go through all files and process them
|
|
|
|
for _, filePath := range files {
|
|
|
|
|
|
|
|
// Get last modified date
|
|
|
|
fileData, err := os.Stat(filePath)
|
|
|
|
if err == nil {
|
|
|
|
fileAge := fileData.ModTime()
|
|
|
|
|
|
|
|
// Check if the file is too old
|
|
|
|
if fileAge.Add(time.Duration(age)*time.Minute).Unix() < tooOldTime.Unix() {
|
|
|
|
fmt.Println("[Task::Cleanup]: Removing File", filePath)
|
|
|
|
|
|
|
|
// Remove the file
|
|
|
|
err = os.Remove(filePath)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("[Task::Cleanup]: Failed to remove file", filePath)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
fmt.Println("[Task::Cleanup]: Error", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|