Files

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
}