2019-03-07 08:43:28 +05:00
// Fast Paste Bin - uberfast and easy-to-use pastebin.
//
// Copyright (c) 2018, Stanislav N. aka pztrn and Fast Paste Bin
// developers.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject
// to the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package flatfiles
import (
"database/sql"
"encoding/json"
"os"
"os/user"
"path/filepath"
"strconv"
"strings"
"sync"
2019-10-12 14:18:35 +05:00
"go.dev.pztrn.name/fastpastebin/internal/structs"
2019-03-07 08:43:28 +05:00
)
type FlatFiles struct {
2021-06-14 23:48:34 +05:00
writeMutex sync . Mutex
2019-03-07 08:43:28 +05:00
path string
2021-01-09 06:09:26 +05:00
pastesIndex [ ] Index
2019-03-07 08:43:28 +05:00
}
2021-01-09 06:09:26 +05:00
// DeletePaste deletes paste from disk and index.
func ( ff * FlatFiles ) DeletePaste ( pasteID int ) error {
// Delete from disk.
err := os . Remove ( filepath . Join ( ff . path , "pastes" , strconv . Itoa ( pasteID ) + ".json" ) )
if err != nil {
2022-08-19 21:52:49 +05:00
app . Log . Error ( ) . Err ( err ) . Msg ( "Failed to delete paste!" )
2021-01-09 06:09:26 +05:00
2022-08-14 15:49:05 +05:00
//nolint:wrapcheck
2021-01-09 06:09:26 +05:00
return err
}
// Delete from index.
ff . writeMutex . Lock ( )
defer ff . writeMutex . Unlock ( )
pasteIndex := - 1
for idx , paste := range ff . pastesIndex {
if paste . ID == pasteID {
pasteIndex = idx
break
}
}
if pasteIndex != - 1 {
ff . pastesIndex = append ( ff . pastesIndex [ : pasteIndex ] , ff . pastesIndex [ pasteIndex + 1 : ] ... )
}
return nil
}
2019-03-07 08:43:28 +05:00
func ( ff * FlatFiles ) GetDatabaseConnection ( ) * sql . DB {
return nil
}
func ( ff * FlatFiles ) GetPaste ( pasteID int ) ( * structs . Paste , error ) {
ff . writeMutex . Lock ( )
pastePath := filepath . Join ( ff . path , "pastes" , strconv . Itoa ( pasteID ) + ".json" )
2022-08-19 21:52:49 +05:00
app . Log . Debug ( ) . Str ( "path" , pastePath ) . Msg ( "Trying to load paste data" )
2019-12-22 01:17:18 +05:00
2022-08-14 15:49:05 +05:00
pasteInBytes , err := os . ReadFile ( pastePath )
2019-03-07 08:43:28 +05:00
if err != nil {
2022-08-19 21:52:49 +05:00
app . Log . Debug ( ) . Err ( err ) . Msg ( "Failed to read paste from storage" )
2021-06-14 23:48:34 +05:00
2022-08-14 15:49:05 +05:00
//nolint:wrapcheck
2019-03-07 08:43:28 +05:00
return nil , err
}
2019-12-22 01:17:18 +05:00
2022-08-19 21:52:49 +05:00
app . Log . Debug ( ) . Int ( "paste bytes" , len ( pasteInBytes ) ) . Msg ( "Loaded paste" )
2019-03-07 08:43:28 +05:00
ff . writeMutex . Unlock ( )
2022-08-14 15:49:05 +05:00
//nolint:exhaustruct
2019-03-07 08:43:28 +05:00
paste := & structs . Paste { }
2019-12-22 01:17:18 +05:00
2020-02-29 23:30:44 +05:00
err1 := json . Unmarshal ( pasteInBytes , paste )
if err1 != nil {
2022-08-19 21:52:49 +05:00
app . Log . Error ( ) . Err ( err1 ) . Msgf ( "Failed to parse paste" )
2021-06-14 23:48:34 +05:00
2022-08-14 15:49:05 +05:00
//nolint:wrapcheck
2020-02-29 23:30:44 +05:00
return nil , err1
2019-03-07 08:43:28 +05:00
}
return paste , nil
}
func ( ff * FlatFiles ) GetPagedPastes ( page int ) ( [ ] structs . Paste , error ) {
// Pagination.
2021-01-09 06:09:26 +05:00
startPagination := 0
2019-03-07 08:43:28 +05:00
if page > 1 {
2022-08-19 21:52:49 +05:00
startPagination = ( page - 1 ) * app . Config . Pastes . Pagination
2019-03-07 08:43:28 +05:00
}
// Iteration one - get only public pastes.
2021-01-09 06:09:26 +05:00
var publicPastes [ ] Index
2019-12-22 01:17:18 +05:00
2019-03-07 08:43:28 +05:00
for _ , paste := range ff . pastesIndex {
if ! paste . Private {
publicPastes = append ( publicPastes , paste )
}
}
// Iteration two - get paginated pastes.
2020-02-29 22:41:06 +05:00
pastesData := make ( [ ] structs . Paste , 0 )
2019-12-22 01:17:18 +05:00
2019-03-07 08:43:28 +05:00
for idx , paste := range publicPastes {
2022-08-19 21:52:49 +05:00
if len ( pastesData ) == app . Config . Pastes . Pagination {
2019-03-07 08:43:28 +05:00
break
}
if idx < startPagination {
2022-08-19 21:52:49 +05:00
app . Log . Debug ( ) . Int ( "paste index" , idx ) . Msg ( "Paste isn't in pagination query: too low index" )
2021-06-14 23:48:34 +05:00
2019-03-07 08:43:28 +05:00
continue
}
2022-08-19 21:52:49 +05:00
if ( idx - 1 >= startPagination && page > 1 && idx > startPagination + ( ( page - 1 ) * app . Config . Pastes . Pagination ) ) || ( idx - 1 >= startPagination && page == 1 && idx > startPagination + ( page * app . Config . Pastes . Pagination ) ) {
app . Log . Debug ( ) . Int ( "paste index" , idx ) . Msg ( "Paste isn't in pagination query: too high index" )
2021-06-14 23:48:34 +05:00
2019-03-07 08:43:28 +05:00
break
}
2019-12-22 01:17:18 +05:00
2022-08-19 21:52:49 +05:00
app . Log . Debug ( ) . Int ( "ID" , paste . ID ) . Int ( "index" , idx ) . Msg ( "Getting paste data" )
2019-03-07 08:43:28 +05:00
// Get paste data.
2022-08-14 15:49:05 +05:00
//nolint:exhaustruct
2019-03-07 08:43:28 +05:00
pasteData := & structs . Paste { }
2019-12-22 01:17:18 +05:00
2022-08-14 15:49:05 +05:00
pasteRawData , err := os . ReadFile ( filepath . Join ( ff . path , "pastes" , strconv . Itoa ( paste . ID ) + ".json" ) )
2019-03-07 08:43:28 +05:00
if err != nil {
2022-08-19 21:52:49 +05:00
app . Log . Error ( ) . Err ( err ) . Msg ( "Failed to read paste data" )
2021-06-14 23:48:34 +05:00
2019-03-07 08:43:28 +05:00
continue
}
2020-02-29 23:30:44 +05:00
err1 := json . Unmarshal ( pasteRawData , pasteData )
if err1 != nil {
2022-08-19 21:52:49 +05:00
app . Log . Error ( ) . Err ( err1 ) . Msg ( "Failed to parse paste data" )
2021-06-14 23:48:34 +05:00
2019-03-07 08:43:28 +05:00
continue
}
pastesData = append ( pastesData , ( * pasteData ) )
}
return pastesData , nil
}
func ( ff * FlatFiles ) GetPastesPages ( ) int {
// Get public pastes count.
2021-01-09 06:09:26 +05:00
var publicPastes [ ] Index
2019-03-07 08:43:28 +05:00
ff . writeMutex . Lock ( )
for _ , paste := range ff . pastesIndex {
if ! paste . Private {
publicPastes = append ( publicPastes , paste )
}
}
ff . writeMutex . Unlock ( )
// Calculate pages.
2022-08-19 21:52:49 +05:00
pages := len ( publicPastes ) / app . Config . Pastes . Pagination
2019-03-07 08:43:28 +05:00
// Check if we have any remainder. Add 1 to pages count if so.
2022-08-19 21:52:49 +05:00
if len ( publicPastes ) % app . Config . Pastes . Pagination > 0 {
2019-03-07 08:43:28 +05:00
pages ++
}
return pages
}
func ( ff * FlatFiles ) Initialize ( ) {
2022-08-19 21:52:49 +05:00
app . Log . Info ( ) . Msg ( "Initializing flatfiles storage..." )
2019-03-07 08:43:28 +05:00
2022-08-19 21:52:49 +05:00
path := app . Config . Database . Path
2019-03-07 08:43:28 +05:00
// Get proper paste file path.
2022-08-19 21:52:49 +05:00
if strings . Contains ( app . Config . Database . Path , "~" ) {
2019-03-07 08:43:28 +05:00
curUser , err := user . Current ( )
if err != nil {
2022-08-19 21:52:49 +05:00
app . Log . Error ( ) . Msg ( "Failed to get current user. Will replace '~' for '/' in storage path!" )
2019-12-22 01:17:18 +05:00
2019-03-07 08:43:28 +05:00
path = strings . Replace ( path , "~" , "/" , - 1 )
}
2019-12-22 01:17:18 +05:00
2019-03-07 08:43:28 +05:00
path = strings . Replace ( path , "~" , curUser . HomeDir , - 1 )
}
path , _ = filepath . Abs ( path )
ff . path = path
2019-12-22 01:17:18 +05:00
2022-08-19 21:52:49 +05:00
app . Log . Debug ( ) . Msgf ( "Storage path is now: %s" , ff . path )
2019-03-07 08:43:28 +05:00
2019-10-13 16:26:52 +05:00
// Create directory if necessary.
2019-03-07 08:43:28 +05:00
if _ , err := os . Stat ( ff . path ) ; err != nil {
2022-08-19 21:52:49 +05:00
app . Log . Debug ( ) . Str ( "directory" , ff . path ) . Msg ( "Directory does not exist, creating..." )
2019-10-13 16:26:52 +05:00
_ = os . MkdirAll ( ff . path , os . ModePerm )
2019-03-07 08:43:28 +05:00
} else {
2022-08-19 21:52:49 +05:00
app . Log . Debug ( ) . Str ( "directory" , ff . path ) . Msg ( "Directory already exists" )
2019-03-07 08:43:28 +05:00
}
// Create directory for pastes.
if _ , err := os . Stat ( filepath . Join ( ff . path , "pastes" ) ) ; err != nil {
2022-08-19 21:52:49 +05:00
app . Log . Debug ( ) . Str ( "directory" , ff . path ) . Msg ( "Directory does not exist, creating..." )
2019-10-13 16:26:52 +05:00
_ = os . MkdirAll ( filepath . Join ( ff . path , "pastes" ) , os . ModePerm )
2019-03-07 08:43:28 +05:00
} else {
2022-08-19 21:52:49 +05:00
app . Log . Debug ( ) . Str ( "directory" , ff . path ) . Msg ( "Directory already exists" )
2019-03-07 08:43:28 +05:00
}
// Load pastes index.
2021-01-09 06:09:26 +05:00
ff . pastesIndex = [ ] Index { }
2019-03-07 08:43:28 +05:00
if _ , err := os . Stat ( filepath . Join ( ff . path , "pastes" , "index.json" ) ) ; err != nil {
2022-08-19 21:52:49 +05:00
app . Log . Warn ( ) . Msg ( "Pastes index file does not exist, will create new one" )
2019-03-07 08:43:28 +05:00
} else {
2022-08-14 15:49:05 +05:00
indexData , err := os . ReadFile ( filepath . Join ( ff . path , "pastes" , "index.json" ) )
2019-03-07 08:43:28 +05:00
if err != nil {
2022-08-19 21:52:49 +05:00
app . Log . Fatal ( ) . Msg ( "Failed to read contents of index file!" )
2019-03-07 08:43:28 +05:00
}
2020-02-29 23:30:44 +05:00
err1 := json . Unmarshal ( indexData , & ff . pastesIndex )
if err1 != nil {
2022-08-19 21:52:49 +05:00
app . Log . Error ( ) . Err ( err1 ) . Msg ( "Failed to parse index file contents from JSON into internal structure. Will create new index file. All of your previous pastes will became unavailable." )
2019-03-07 08:43:28 +05:00
}
2022-08-19 21:52:49 +05:00
app . Log . Debug ( ) . Int ( "pastes count" , len ( ff . pastesIndex ) ) . Msg ( "Parsed pastes index" )
2019-03-07 08:43:28 +05:00
}
}
2021-11-20 22:19:58 +05:00
func ( ff * FlatFiles ) SavePaste ( paste * structs . Paste ) ( int64 , error ) {
2019-03-07 08:43:28 +05:00
ff . writeMutex . Lock ( )
// Write paste data on disk.
2022-08-14 15:49:05 +05:00
filesOnDisk , _ := os . ReadDir ( filepath . Join ( ff . path , "pastes" ) )
2019-03-07 08:43:28 +05:00
pasteID := len ( filesOnDisk ) + 1
2021-11-20 22:19:58 +05:00
paste . ID = pasteID
2019-12-22 01:17:18 +05:00
2022-08-19 21:52:49 +05:00
app . Log . Debug ( ) . Int ( "new paste ID" , pasteID ) . Msg ( "Writing paste to disk" )
2019-12-22 01:17:18 +05:00
2021-11-20 22:19:58 +05:00
data , err := json . Marshal ( paste )
2019-03-07 08:43:28 +05:00
if err != nil {
ff . writeMutex . Unlock ( )
2021-06-14 23:48:34 +05:00
2022-08-14 15:49:05 +05:00
//nolint:wrapcheck
2019-03-07 08:43:28 +05:00
return 0 , err
}
2019-12-22 01:17:18 +05:00
2022-08-14 15:49:05 +05:00
err = os . WriteFile ( filepath . Join ( ff . path , "pastes" , strconv . Itoa ( pasteID ) + ".json" ) , data , 0 o600 )
2019-03-07 08:43:28 +05:00
if err != nil {
ff . writeMutex . Unlock ( )
2021-06-14 23:48:34 +05:00
2022-08-14 15:49:05 +05:00
//nolint:wrapcheck
2019-03-07 08:43:28 +05:00
return 0 , err
}
2019-12-22 01:17:18 +05:00
2019-03-07 08:43:28 +05:00
// Add it to cache.
2022-08-14 15:49:05 +05:00
//nolint:exhaustruct
2021-01-09 06:09:26 +05:00
indexData := Index { }
2019-03-07 08:43:28 +05:00
indexData . ID = pasteID
2021-11-20 22:19:58 +05:00
indexData . Private = paste . Private
2019-03-07 08:43:28 +05:00
ff . pastesIndex = append ( ff . pastesIndex , indexData )
ff . writeMutex . Unlock ( )
2019-12-22 01:17:18 +05:00
2019-03-07 08:43:28 +05:00
return int64 ( pasteID ) , nil
}
func ( ff * FlatFiles ) Shutdown ( ) {
2022-08-19 21:52:49 +05:00
app . Log . Info ( ) . Msg ( "Saving indexes..." )
2019-12-22 01:17:18 +05:00
2019-03-07 08:43:28 +05:00
indexData , err := json . Marshal ( ff . pastesIndex )
if err != nil {
2022-08-19 21:52:49 +05:00
app . Log . Error ( ) . Err ( err ) . Msg ( "Failed to encode index data into JSON" )
2021-06-14 23:48:34 +05:00
2019-03-07 08:43:28 +05:00
return
}
2022-08-14 15:49:05 +05:00
err1 := os . WriteFile ( filepath . Join ( ff . path , "pastes" , "index.json" ) , indexData , 0 o600 )
2020-02-29 23:30:44 +05:00
if err1 != nil {
2022-08-19 21:52:49 +05:00
app . Log . Error ( ) . Err ( err1 ) . Msg ( "Failed to write index data to file. Pretty sure that you've lost your pastes." )
2021-06-14 23:48:34 +05:00
2019-03-07 08:43:28 +05:00
return
}
}