/* * Wiki - A wiki with editor * Copyright (c) 2021 Peter Stuifzand * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package main import ( "encoding/json" "fmt" "log" "os" "path/filepath" "sync" ) const BlocksDirectory = "_blocks" type FileBlockRepository struct { dirname string rwLock sync.RWMutex } type Block struct { Text string Children []string Parent string } func NewBlockRepo(dirname string) (*FileBlockRepository, error) { err := os.MkdirAll(filepath.Join(dirname, BlocksDirectory), 0777) if err != nil { return nil, err } return &FileBlockRepository{ dirname: dirname, }, nil } func (blockRepo *FileBlockRepository) GetBlock(blockID string) (Block, error) { blockRepo.rwLock.RLock() defer blockRepo.rwLock.RUnlock() return loadBlock(blockRepo.dirname, blockID) } func (blockRepo *FileBlockRepository) SaveBlock(blockID string, block Block) error { blockRepo.rwLock.Lock() defer blockRepo.rwLock.Unlock() return saveBlock(blockRepo.dirname, blockID, block) } func (blockRepo *FileBlockRepository) GetBlocks(rootBlockID string) (BlockResponse, error) { resp := BlockResponse{ rootBlockID, "", nil, nil, nil, } resp.Texts = make(map[string]string) resp.Children = make(map[string][]string) queue := []string{rootBlockID} block, err := blockRepo.GetBlock(rootBlockID) if err != nil { return BlockResponse{}, err } // NOTE: what does this do? if rootBlockID[0] != '_' && block.Children == nil { return BlockResponse{}, fmt.Errorf("not a block and has no children: %w", BlockNotFound) } prevID := rootBlockID parentID := block.Parent for parentID != "root" { parent, err := blockRepo.GetBlock(parentID) if err != nil { return BlockResponse{}, fmt.Errorf("while loading current parent block (%s->%s): %w", prevID, parentID, err) } resp.Texts[parentID] = parent.Text resp.Children[parentID] = parent.Children resp.ParentID = parentID resp.Parents = append(resp.Parents, parentID) prevID = parentID parentID = parent.Parent } if parentID == "root" { resp.ParentID = "root" } for { if len(queue) == 0 { break } current := queue[0] queue = queue[1:] block, err := blockRepo.GetBlock(current) if err != nil { return BlockResponse{}, err } resp.Texts[current] = block.Text resp.Children[current] = block.Children queue = append(queue, block.Children...) } return resp, nil } func loadBlock(dirname, blockID string) (Block, error) { f, err := os.Open(filepath.Join(dirname, BlocksDirectory, blockID)) if err != nil { return Block{}, fmt.Errorf("%q: %w", blockID, BlockNotFound) } defer f.Close() var block Block err = json.NewDecoder(f).Decode(&block) if err != nil { return Block{}, fmt.Errorf("%q: %v", blockID, err) } return block, nil } func saveBlock(dirname, blockID string, block Block) error { log.Printf("Writing to %q\n", blockID) f, err := os.OpenFile(filepath.Join(dirname, BlocksDirectory, blockID), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) if err != nil { return fmt.Errorf("while saving block %s: %w", blockID, err) } defer f.Close() enc := json.NewEncoder(f) enc.SetIndent("", " ") err = enc.Encode(&block) if err != nil { return fmt.Errorf("while encoding block %s: %w", blockID, err) } return nil }