mirror of https://github.com/JustKato/FreePad.git
Implemented a views counter
+ Implemented views for the post struct + Views functions + Views storage file
This commit is contained in:
parent
23dd69e060
commit
53066025f0
|
@ -28,9 +28,23 @@ func TaskManager() {
|
||||||
cleanupInterval = 1
|
cleanupInterval = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("[Task::Cleanup]: Task registered")
|
// Run all handlers
|
||||||
for range time.Tick(time.Minute * 5) {
|
go cleanupHandler(cleanupInterval)
|
||||||
objects.CleanupPosts(cleanupInterval)
|
go savePostHandler()
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func savePostHandler() {
|
||||||
|
// Save the views cache
|
||||||
|
fmt.Println("[Task::Save]: File save registered")
|
||||||
|
for range time.NewTicker(time.Second).C {
|
||||||
|
objects.SavePostViewsCache()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanupHandler(cleanupInterval int) {
|
||||||
|
fmt.Println("[Task::Cleanup]: Cleanup task registered")
|
||||||
|
for range time.NewTicker(time.Minute * 5).C {
|
||||||
|
objects.CleanupPosts(cleanupInterval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,19 +1,154 @@
|
||||||
package objects
|
package objects
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/JustKato/FreePad/lib/helper"
|
"github.com/JustKato/FreePad/lib/helper"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Initialize the views cache
|
||||||
|
var ViewsCache map[string]uint32 = make(map[string]uint32)
|
||||||
|
|
||||||
|
// Mutex lock for the ViewsCache
|
||||||
|
var viewersLock sync.Mutex
|
||||||
|
|
||||||
type Post struct {
|
type Post struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
LastModified string `json:"last_modified"`
|
LastModified string `json:"last_modified"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the path to the storage directory
|
// Get the path to the storage directory
|
||||||
|
@ -47,9 +182,13 @@ func GetPost(fileName string) Post {
|
||||||
// Generate the file path
|
// Generate the file path
|
||||||
filePath := fmt.Sprintf("%s%s", storageDir, fileName)
|
filePath := fmt.Sprintf("%s%s", storageDir, fileName)
|
||||||
|
|
||||||
|
// Get the post views and add 1 to them
|
||||||
|
postViews := AddViewToPost(fileName)
|
||||||
|
|
||||||
p := Post{
|
p := Post{
|
||||||
Name: fileName,
|
Name: fileName,
|
||||||
Content: "",
|
Content: "",
|
||||||
|
Views: postViews,
|
||||||
LastModified: "Never Before",
|
LastModified: "Never Before",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,6 +233,8 @@ func WritePost(p Post) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// Actually close the file
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
// Write the contnets
|
// Write the contnets
|
||||||
_, err = f.WriteString(p.Content)
|
_, err = f.WriteString(p.Content)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -23,6 +24,13 @@ func HomeRoutes(router *gin.Engine) {
|
||||||
// Get the post we are looking for.
|
// Get the post we are looking for.
|
||||||
postName := c.Param("post")
|
postName := c.Param("post")
|
||||||
|
|
||||||
|
if postName == `views_storage.json` {
|
||||||
|
// Redirect the user to the homepage as this is a reserved keyword
|
||||||
|
c.Redirect(http.StatusPermanentRedirect, "/")
|
||||||
|
// Do not proceed further
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Get the maximum pad size, so that we may notify the client-side to match server-side
|
// Get the maximum pad size, so that we may notify the client-side to match server-side
|
||||||
maximumPadSize := helper.GetMaximumPadSize()
|
maximumPadSize := helper.GetMaximumPadSize()
|
||||||
|
|
||||||
|
@ -40,6 +48,7 @@ func HomeRoutes(router *gin.Engine) {
|
||||||
"post_content": post.Content,
|
"post_content": post.Content,
|
||||||
"maximumPadSize": maximumPadSize,
|
"maximumPadSize": maximumPadSize,
|
||||||
"last_modified": post.LastModified,
|
"last_modified": post.LastModified,
|
||||||
|
"views": post.Views,
|
||||||
"domain_base": helper.GetDomainBase(),
|
"domain_base": helper.GetDomainBase(),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -59,6 +68,7 @@ func HomeRoutes(router *gin.Engine) {
|
||||||
p := objects.Post{
|
p := objects.Post{
|
||||||
Name: postName,
|
Name: postName,
|
||||||
Content: postContent,
|
Content: postContent,
|
||||||
|
Views: 0, // This can just be ignored
|
||||||
LastModified: time.Now().Format("02/01/2006 03:04:05 PM"),
|
LastModified: time.Now().Format("02/01/2006 03:04:05 PM"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
9
main.go
9
main.go
|
@ -1,9 +1,11 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/JustKato/FreePad/lib/controllers"
|
"github.com/JustKato/FreePad/lib/controllers"
|
||||||
|
"github.com/JustKato/FreePad/lib/objects"
|
||||||
"github.com/JustKato/FreePad/lib/routes"
|
"github.com/JustKato/FreePad/lib/routes"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
|
@ -22,6 +24,13 @@ func main() {
|
||||||
// Run the TaskManager
|
// Run the TaskManager
|
||||||
go controllers.TaskManager()
|
go controllers.TaskManager()
|
||||||
|
|
||||||
|
// Load in the views data from storage
|
||||||
|
err := objects.LoadViewsCache()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to load views from cache")
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize the router
|
// Initialize the router
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@
|
||||||
</path>
|
</path>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<input type="text" class="form-control" readonly value="1">
|
<input type="text" class="form-control" readonly value="{{.views}}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue