...

Source file src/codeberg.org/tslocum/sriracha/internal/server/server_export.go

Documentation: codeberg.org/tslocum/sriracha/internal/server

     1  package server
     2  
     3  import (
     4  	"archive/zip"
     5  	"database/sql"
     6  	"fmt"
     7  	"io"
     8  	"log"
     9  	"os"
    10  	"strings"
    11  	"time"
    12  
    13  	"codeberg.org/tslocum/sriracha/internal/database"
    14  	. "codeberg.org/tslocum/sriracha/model"
    15  	. "codeberg.org/tslocum/sriracha/util"
    16  	_ "modernc.org/sqlite"
    17  )
    18  
    19  func (s *Server) _exportBoardPosts(db *database.DB, b *Board, threads [][2]int) (*os.File, error) {
    20  	f, err := os.CreateTemp("", "*.db")
    21  	if err != nil {
    22  		return nil, fmt.Errorf("failed to create temporary file: %s", err)
    23  	}
    24  
    25  	export, err := sql.Open("sqlite", f.Name())
    26  	if err != nil {
    27  		log.Fatalf("failed to open temporary file %s: %s", f.Name(), err)
    28  	}
    29  	defer export.Close()
    30  
    31  	_, err = export.Exec(`
    32  CREATE TABLE post (
    33  	id           INTEGER PRIMARY KEY,
    34  	parent       INTEGER NOT NULL,
    35  	timestamp    INTEGER NOT NULL,
    36  	bumped       INTEGER NOT NULL,
    37  	name         TEXT NOT NULL,
    38  	tripcode     TEXT NOT NULL,
    39  	email        TEXT NOT NULL,
    40  	nameblock    TEXT NOT NULL,
    41  	subject      TEXT NOT NULL,
    42  	message      TEXT NOT NULL,
    43  	file         TEXT NOT NULL,
    44  	filemime     TEXT NOT NULL,
    45  	filehash     TEXT NOT NULL,
    46  	fileoriginal TEXT NOT NULL,
    47  	filesize     INTEGER NOT NULL,
    48  	filewidth    INTEGER NOT NULL,
    49  	fileheight   INTEGER NOT NULL,
    50  	thumb        TEXT NOT NULL,
    51  	thumbwidth   INTEGER NOT NULL,
    52  	thumbheight  INTEGER NOT NULL,
    53  	stickied     INTEGER NOT NULL,
    54  	locked       INTEGER NOT NULL
    55  );`)
    56  	if err != nil {
    57  		return nil, fmt.Errorf("failed to create post schema: %s", err)
    58  	}
    59  
    60  	var hash string
    61  	var stickied, locked int
    62  	for _, thread := range threads {
    63  		for _, p := range db.AllPostsInThread(thread[0], false) {
    64  			if p.IsEmbed() {
    65  				hash = p.FileHash
    66  			} else {
    67  				hash = ""
    68  			}
    69  			if p.Stickied {
    70  				stickied = 1
    71  			} else {
    72  				stickied = 0
    73  			}
    74  			if p.Locked {
    75  				locked = 1
    76  			} else {
    77  				locked = 0
    78  			}
    79  			_, err = export.Exec("INSERT INTO post VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
    80  				p.ID,
    81  				p.Parent,
    82  				p.Timestamp,
    83  				p.Bumped,
    84  				p.Name,
    85  				p.Tripcode,
    86  				p.Email,
    87  				p.NameBlock,
    88  				p.Subject,
    89  				p.Message,
    90  				p.File,
    91  				p.FileMIME,
    92  				hash,
    93  				p.FileOriginal,
    94  				p.FileSize,
    95  				p.FileWidth,
    96  				p.FileHeight,
    97  				p.Thumb,
    98  				p.ThumbWidth,
    99  				p.ThumbHeight,
   100  				stickied,
   101  				locked)
   102  			if err != nil {
   103  				return nil, fmt.Errorf("failed to export post: %s", err)
   104  			}
   105  		}
   106  	}
   107  	return f, nil
   108  }
   109  
   110  func (s *Server) exportPosts(db *database.DB, exportPath string) error {
   111  	boards := db.AllBoards()
   112  	if len(boards) == 0 {
   113  		return fmt.Errorf("no boards available to export")
   114  	}
   115  	var havePosts bool
   116  	for _, b := range boards {
   117  		if len(db.AllThreads(b, false)) > 0 {
   118  			havePosts = true
   119  			continue
   120  		}
   121  	}
   122  	if !havePosts {
   123  		return fmt.Errorf("no posts available to export")
   124  	}
   125  
   126  	_, err := os.Stat(exportPath)
   127  	if !os.IsNotExist(err) {
   128  		return fmt.Errorf("file %s already exists", exportPath)
   129  	}
   130  	zipFile, err := os.OpenFile(exportPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, NewFilePermission)
   131  	if err != nil {
   132  		return fmt.Errorf("failed to open zip file %s: %s", exportPath, err)
   133  	}
   134  	defer zipFile.Close()
   135  
   136  	zip := zip.NewWriter(zipFile)
   137  
   138  	date := time.Now().Format("20060102")
   139  	for _, b := range boards {
   140  		threads := db.AllThreads(b, false)
   141  		if len(threads) == 0 {
   142  			continue
   143  		}
   144  		fmt.Printf("Exporting %s...\n", b.Path())
   145  
   146  		fName := date
   147  		if b.Dir == "" {
   148  			fName += "_root"
   149  		} else {
   150  			fName += "_" + strings.ToLower(b.Dir)
   151  		}
   152  		if b.Description != "" {
   153  			fName += "_" + strings.ReplaceAll(strings.ToLower(b.Name), " ", "_")
   154  		}
   155  		fName += ".sriracha.db"
   156  
   157  		boardFile, err := s._exportBoardPosts(db, b, threads)
   158  		if err != nil {
   159  			return fmt.Errorf("failed to export board %s: %s", b.Path(), err)
   160  		}
   161  		zipBoardFile, err := zip.Create(fName)
   162  		if err != nil {
   163  			return fmt.Errorf("failed to create file in zip archive: %s", err)
   164  		}
   165  		_, err = io.Copy(zipBoardFile, boardFile)
   166  		if err != nil {
   167  			return fmt.Errorf("failed to write zip archive: %s", err)
   168  		}
   169  		boardFile.Close()
   170  	}
   171  
   172  	err = zip.Close()
   173  	if err != nil {
   174  		return fmt.Errorf("failed to write zip archive: %s", err)
   175  	}
   176  
   177  	fmt.Printf("Exported post data to %s\n", exportPath)
   178  	fmt.Printf("Warning: Attachment files are not included within the export. To import posts later, you will also need a copy of the src and thumb directories of each board.\n")
   179  	return nil
   180  }
   181  

View as plain text