mirror of
https://github.com/wisplite/parchment.git
synced 2026-06-27 13:47:08 -05:00
222 lines
5.9 KiB
Go
222 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
type fileData struct {
|
|
name string
|
|
content []byte
|
|
}
|
|
|
|
type cpkg struct {
|
|
packageName string
|
|
files []fileData
|
|
}
|
|
|
|
func NewCpkg(packageName string) *cpkg {
|
|
return &cpkg{
|
|
packageName: packageName,
|
|
files: make([]fileData, 0),
|
|
}
|
|
}
|
|
|
|
func (c *cpkg) AddFile(fsys fs.FS, path string) error {
|
|
file, err := fs.ReadFile(fsys, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.files = append(c.files, fileData{name: path, content: file})
|
|
return nil
|
|
}
|
|
|
|
// cpkg file header format:
|
|
// 1 byte: filename length
|
|
// n bytes: filename
|
|
// 4 bytes: file size (uint32)
|
|
func (c *cpkg) createCpkgFileHeader(file fileData) []byte {
|
|
headerBytes := make([]byte, 0)
|
|
headerBytes = append(headerBytes, byte(len(file.name)))
|
|
headerBytes = append(headerBytes, []byte(file.name)...)
|
|
|
|
// file size (uint32 - 4 bytes)
|
|
fileSizeBytes := make([]byte, 4)
|
|
binary.LittleEndian.PutUint32(fileSizeBytes, uint32(len(file.content)))
|
|
headerBytes = append(headerBytes, fileSizeBytes...)
|
|
|
|
return headerBytes
|
|
}
|
|
|
|
// cpkg header format:
|
|
// 4 bytes: magic number "Cpkg"
|
|
// 2 bytes: version
|
|
// 4 bytes: file count
|
|
func (c *cpkg) createCpkgHeader() []byte {
|
|
headerBytes := make([]byte, 0)
|
|
headerBytes = append(headerBytes, []byte("Cpkg")...)
|
|
|
|
// version (uint16 - 2 bytes)
|
|
versionBytes := make([]byte, 2)
|
|
binary.LittleEndian.PutUint16(versionBytes, 1)
|
|
headerBytes = append(headerBytes, versionBytes...)
|
|
|
|
// file count (uint32 - 4 bytes)
|
|
fileCountBytes := make([]byte, 4)
|
|
binary.LittleEndian.PutUint32(fileCountBytes, uint32(len(c.files)))
|
|
headerBytes = append(headerBytes, fileCountBytes...)
|
|
|
|
return headerBytes
|
|
}
|
|
|
|
func (c *cpkg) Create() (bool, error) {
|
|
file, err := os.Create(c.packageName + ".cpkg")
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
defer file.Close()
|
|
|
|
// Write main header
|
|
header := c.createCpkgHeader()
|
|
n, err := file.Write(header)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to write header: %w", err)
|
|
}
|
|
if n != len(header) {
|
|
return false, fmt.Errorf("incomplete header write: wrote %d of %d bytes", n, len(header))
|
|
}
|
|
|
|
// Write each file
|
|
for i, fileData := range c.files {
|
|
// Write file header
|
|
fileHeader := c.createCpkgFileHeader(fileData)
|
|
n, err := file.Write(fileHeader)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to write header for file %d (%s): %w", i, fileData.name, err)
|
|
}
|
|
if n != len(fileHeader) {
|
|
return false, fmt.Errorf("incomplete header write for file %d (%s): wrote %d of %d bytes", i, fileData.name, n, len(fileHeader))
|
|
}
|
|
|
|
// Write file content
|
|
n, err = file.Write(fileData.content)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to write content for file %d (%s): %w", i, fileData.name, err)
|
|
}
|
|
if n != len(fileData.content) {
|
|
return false, fmt.Errorf("incomplete content write for file %d (%s): wrote %d of %d bytes", i, fileData.name, n, len(fileData.content))
|
|
}
|
|
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (c *cpkg) Unpack(fsys fs.FS) error {
|
|
file, err := os.Open(c.packageName + ".cpkg")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
// Read main header (10 bytes)
|
|
header := make([]byte, 10)
|
|
_, err = file.Read(header)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read header: %w", err)
|
|
}
|
|
|
|
// Verify magic number
|
|
magicNumber := string(header[:4])
|
|
if magicNumber != "Cpkg" {
|
|
return fmt.Errorf("invalid cpkg file: wrong magic number")
|
|
}
|
|
|
|
// Verify version
|
|
version := binary.LittleEndian.Uint16(header[4:6])
|
|
if version != 1 {
|
|
return fmt.Errorf("unsupported cpkg file version: %d", version)
|
|
}
|
|
|
|
// Read file count
|
|
fileCount := binary.LittleEndian.Uint32(header[6:10])
|
|
|
|
// Clear existing files array
|
|
c.files = make([]fileData, 0, fileCount)
|
|
|
|
// Read each file sequentially
|
|
for i := 0; i < int(fileCount); i++ {
|
|
// Read filename length (1 byte)
|
|
fileNameLengthBuf := make([]byte, 1)
|
|
n, err := file.Read(fileNameLengthBuf)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read filename length for file %d: %w", i, err)
|
|
}
|
|
if n != 1 {
|
|
return fmt.Errorf("incomplete read of filename length for file %d: got %d bytes, expected 1", i, n)
|
|
}
|
|
fileNameLength := fileNameLengthBuf[0]
|
|
|
|
// Read filename
|
|
fileNameBuf := make([]byte, fileNameLength)
|
|
n, err = file.Read(fileNameBuf)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read filename for file %d: %w", i, err)
|
|
}
|
|
if n != int(fileNameLength) {
|
|
return fmt.Errorf("incomplete read of filename for file %d: got %d bytes, expected %d", i, n, fileNameLength)
|
|
}
|
|
fileName := string(fileNameBuf)
|
|
|
|
// Read file size (4 bytes)
|
|
fileSizeBuf := make([]byte, 4)
|
|
n, err = file.Read(fileSizeBuf)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read file size for file %d: %w", i, err)
|
|
}
|
|
if n != 4 {
|
|
return fmt.Errorf("incomplete read of file size for file %d: got %d bytes, expected 4", i, n)
|
|
}
|
|
fileSize := binary.LittleEndian.Uint32(fileSizeBuf)
|
|
|
|
// Read file content
|
|
fileContent := make([]byte, fileSize)
|
|
n, err = file.Read(fileContent)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read content for file %d (%s): %w", i, fileName, err)
|
|
}
|
|
if n != int(fileSize) {
|
|
return fmt.Errorf("incomplete read of content for file %d (%s): got %d bytes, expected %d", i, fileName, n, fileSize)
|
|
}
|
|
|
|
c.files = append(c.files, fileData{name: fileName, content: fileContent})
|
|
}
|
|
|
|
// Write files to filesystem in subdirectory with the package name
|
|
err = os.MkdirAll(c.packageName, 0755)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create output directory: %w", err)
|
|
}
|
|
|
|
for _, fileData := range c.files {
|
|
outputPath := filepath.Join(c.packageName, fileData.name)
|
|
|
|
// Create parent directories if they don't exist
|
|
parentDir := filepath.Dir(outputPath)
|
|
err = os.MkdirAll(parentDir, 0755)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create directory for file %s: %w", fileData.name, err)
|
|
}
|
|
|
|
err = os.WriteFile(outputPath, fileData.content, 0644)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to write file %s: %w", fileData.name, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|