diff --git a/cop/collection.go b/cop/collection.go index 852dc1a..861772a 100644 --- a/cop/collection.go +++ b/cop/collection.go @@ -2,37 +2,88 @@ package cop import ( "encoding/json" - "errors" "fmt" - "io/fs" "log" "os" "path" - "path/filepath" "strings" - "github.com/go-git/go-git/v5" - "gitea.mmo.to/ppForge/ppforge/globals" "gitea.mmo.to/ppForge/ppforge/protocol" ) // GlobalCOP is the global variable holding the protocol collections, -var GlobalCOP *ProtocolCollectionList = &ProtocolCollectionList{[]COP{}} +var GlobalCOP *ProtocolCollectionList = &ProtocolCollectionList{[]COPer{}} + +// CollectionTypes contains the functions to create actual objects from JSON +var CollectionTypes map[string](func([]byte) (COPer, error)) + +// InitCollectionTypes initializes the functions for each actual object +func InitCollectionTypes() { + CollectionTypes = map[string]func([]byte) (COPer, error){} + //CollectionTypes["COP"] = COPFromJSON + CollectionTypes["FileCOP"] = FileCOPFromJSON + //CollectionTypes["FileCOPFromGitRemote"] = FileCOPGitRemoteFromJSON +} // ProtocolCollectionList is a list of all databases, with the most simple interface type ProtocolCollectionList struct { - PCs []COP + PCs []COPer +} + +// COPer interface defines the functions a CollectionOfProtocols should have at least. +type COPer interface { + Open(string) error + Close() error + GetProtocols() ([]ProtocolCollectionEntry, error) + AddOrUpdate(*protocol.Protocol) error + Sync() error + Get(string) (*protocol.Protocol, error) + ToJSON() (string, error) +} + +// Init initializes the databases, opens the default one and checks for others if available +func init() { + InitCollectionTypes() + Init() +} + +// Init the global COP +func Init() { + // initialize default db + fdb := FileCOP{COP{"FileCOP", map[string]ProtocolCollectionEntry{}, false}, globals.CollectionOfProtocolsDir} + err := fdb.Open(globals.CollectionOfProtocolsDir) + if err != nil { + log.Printf("Error opening global COP dir: %v", err) + return + } + err = fdb.Sync() + if err != nil { + log.Printf("Error on initial syncing of the global COP dir: %v", err) + return + } + GlobalCOP.PCs = append(GlobalCOP.PCs, &fdb) + log.Printf("Amount of databases available %d", len(GlobalCOP.PCs)) +} + +// GetCOP returns the global collection of Protocols +func GetCOP() *ProtocolCollectionList { + return GlobalCOP } // WriteCache updates the cache file of the available protocols func (pcl *ProtocolCollectionList) WriteCache() error { // [impl->dsn~protocol-collection-cache~0>>utest] - data, err := json.MarshalIndent(pcl, "", " ") - if err != nil { - return err + cops := []string{} + for _, pc := range pcl.PCs { + str, err := pc.ToJSON() + if err != nil { + log.Println(err) + } + cops = append(cops, str) } - err = os.WriteFile(path.Join(globals.CollectionOfProtocolsDir, globals.COPCacheFileName), data, 0644) + data := "{ \"PCs\": [" + strings.Join(cops, ",") + "]}" + err := os.WriteFile(path.Join(globals.CollectionOfProtocolsDir, globals.COPCacheFileName), []byte(data), 0644) return err } @@ -46,183 +97,20 @@ func (pcl *ProtocolCollectionList) ReadCache() error { return err } //fmt.Println(string(data)) - err = json.Unmarshal(data, pcl) - data, err = json.MarshalIndent(pcl, "", " ") + var raw map[string][]interface{} + err = json.Unmarshal(data, &raw) + PCs := raw + for _, rawcop := range PCs["PCs"] { + cop := rawcop.(map[string]interface{}) + coptype := cop["Type"].(string) + copjson, err := json.Marshal(rawcop) + fmt.Println(string(copjson)) + if err != nil { + log.Println(err) + } + coper, err := CollectionTypes[coptype](copjson) + pcl.PCs = append(pcl.PCs, coper) + } fmt.Println(string(data)) return err } - -// ProtocolCollectionEntry is a single entry in the file database, additionally to the meta data the path is required -type ProtocolCollectionEntry struct { - Path string - Protocol protocol.ProtocolMeta -} - -// Init initializes the databases, opens the default one and checks for others if available -func init() { - Init() -} - -// Init the global COP -func Init() { - // initialize default db - fdb := FileCOP{COP{map[string]ProtocolCollectionEntry{}, false}, globals.CollectionOfProtocolsDir} - err := fdb.Open(globals.CollectionOfProtocolsDir) - if err != nil { - log.Printf("Error opening global COP dir: %v", err) - return - } - err = fdb.Sync() - if err != nil { - log.Printf("Error on initial syncing of the global COP dir: %v", err) - return - } - GlobalCOP.PCs = append(GlobalCOP.PCs, fdb.COP) - log.Printf("Amount of databases available %d", len(GlobalCOP.PCs)) -} - -// GetCOP returns the global collection of Protocols -func GetCOP() *ProtocolCollectionList { - return GlobalCOP -} - -// COP represents a Collection of Protocols -type COP struct { - Protocols map[string]ProtocolCollectionEntry - closed bool -} - -// COPer interface defines the functions a CollectionOfProtocols should have at least. -type COPer interface { - Open(string) error - Close() error - GetProtocols() ([]ProtocolCollectionEntry, error) - Add(*protocol.Protocol) error - Sync() error - Update(*protocol.Protocol) error - Get(string) (*protocol.Protocol, error) -} - -// FileCOP implements -type FileCOP struct { - COP - Path string -} - -// FileCOPFromGitRemote implements retrieving the latest stuff from an external git remote additionally to the FileDatabase functionality -type FileCOPFromGitRemote struct { - FileCOP - RemoteGitRepository string - gitRepo git.Repository -} - -// NewFileCOP returns a new FileCOP instance from a given path -func NewFileCOP(path string) (*FileCOP, error) { - fCOP := &FileCOP{} - err := fCOP.Open(path) - if err != nil { - log.Printf("Error opening file COP at %s: %v", path, err) - return nil, err - } - err = fCOP.Sync() - return fCOP, err -} - -// Open the database -func (fd *FileCOP) Open(path string) error { - fileinfo, err := os.Stat(path) - if err == os.ErrNotExist { - return err - } - if fileinfo == nil || !fileinfo.IsDir() { - return fmt.Errorf("Path %s is not a directory or does not exist", path) - } - fd.Path = path - fd.closed = false - return nil -} - -// Close the database and free the data -func (fd *FileCOP) Close() error { - fd.closed = true - return nil -} - -// GetProtocols returns the map of protocols or an error if the first read failed -func (fd *FileCOP) GetProtocols() ([]ProtocolCollectionEntry, error) { - if fd.closed { - return nil, errors.New("COP closed, no protocols to deliver") - } - entries := []ProtocolCollectionEntry{} - if !fd.closed && len(fd.Protocols) == 0 { - fd.Protocols = map[string]ProtocolCollectionEntry{} - err := fd.Sync() - if err != nil { - return nil, err - } - } - if entries == nil || len(entries) == 0 { - entries = make([]ProtocolCollectionEntry, 0) - for _, v := range fd.Protocols { - entries = append(entries, v) - } - } - return entries, nil -} - -// Add puts a new protocol into the path of the file database -func (fd *FileCOP) Add(prot *protocol.Protocol) error { - if fd.closed { - return errors.New("Cannot add, FileCOP is closed") - } - p := path.Join(fd.Path, prot.Metadata.Name, ".protocoljson") - err := prot.Save(p) - if err == nil { - fd.Protocols[prot.Metadata.Name] = ProtocolCollectionEntry{p, prot.Metadata} - } - return err -} - -// Update updates an existing entry through saving the contents of the protocol to the file, or just adding it anew -func (fd *FileCOP) Update(prot *protocol.Protocol) error { - if fd.closed { - return errors.New("Cannot update, FileCOP is closed") - } - if v, ok := fd.Protocols[prot.Metadata.Name]; ok { - v.Protocol = prot.Metadata - return prot.Save(v.Path) - } - return fd.Add(prot) -} - -// Sync just rereads all files -func (fd *FileCOP) Sync() error { - if fd.closed { - return errors.New("Cannot sync, FileCOP is closed") - } - // recurse recursively through the path - if fd.Protocols == nil { - fd.Protocols = make(map[string]ProtocolCollectionEntry) - } - err := filepath.Walk(fd.Path, func(path string, info fs.FileInfo, err error) error { - if !info.IsDir() && strings.HasSuffix(info.Name(), ".protocoljson") { - prot, err := protocol.LoadNew(path) - if err == nil { - // add to map - // add to map - entry := ProtocolCollectionEntry{path, prot.Metadata} - - fd.Protocols[prot.Metadata.Name] = entry - } - return err - } - return nil - }) - return err -} - -// Get a protocol by name -func (fd *FileCOP) Get(prot string) (*protocol.Protocol, error) { - entry := fd.Protocols[prot] - return protocol.LoadNew(entry.Path) -} diff --git a/cop/collection_test.go b/cop/collection_test.go index a4d9c88..4c6df2f 100644 --- a/cop/collection_test.go +++ b/cop/collection_test.go @@ -12,6 +12,7 @@ import ( "gitea.mmo.to/ppForge/ppforge/cop" "gitea.mmo.to/ppForge/ppforge/globals" + "gitea.mmo.to/ppForge/ppforge/protocol" ) func getCurrentDir() string { @@ -94,14 +95,9 @@ func TestClosed(t *testing.T) { fmt.Println("Protocols did not deliver error") t.Fail() } - err = fcop.Add(nil) + err = fcop.AddOrUpdate(nil) if err == nil { - fmt.Println("Add did not deliver error") - t.Fail() - } - err = fcop.Update(nil) - if err == nil { - fmt.Println("Update did not deliver error") + fmt.Println("AddOrUpdate did not deliver error") t.Fail() } err = fcop.Sync() @@ -128,7 +124,7 @@ func TestGlobalCOP(t *testing.T) { fmt.Println("Error on loading new FileCOP", err) t.Fail() } - gcop.PCs = append(gcop.PCs, fcop.COP) + gcop.PCs = append(gcop.PCs, fcop) fmt.Printf("Length of all Protocol Collections: %d\n", len(gcop.PCs)) if len(gcop.PCs) != 2 { @@ -142,15 +138,15 @@ func TestCOPCache(t *testing.T) { defer td(t) gcop := cop.GlobalCOP // reset to zero because of other tests - gcop.PCs = []cop.COP{} + gcop.PCs = []cop.COPer{} cop.Init() c := gcop.PCs[0] - protos := c.Protocols + protos, err := c.GetProtocols() if len(protos) != 4 { t.Fail() } - err := gcop.WriteCache() + err = gcop.WriteCache() if err != nil { fmt.Println("Writing the cache file failed") t.Fail() @@ -162,17 +158,44 @@ func TestCOPCache(t *testing.T) { } // reset again and check if reading the cache works - gcop.PCs = []cop.COP{} + gcop.PCs = []cop.COPer{} err = gcop.ReadCache() if err != nil { - fmt.Println("Reading the cache file failed") + fmt.Println("Reading the cache file failed", err) t.Fail() } c = gcop.PCs[0] - protos = c.Protocols + protos, err = c.GetProtocols() if len(protos) != 4 { t.Fail() } - +} + +func TestAddOrUpdate(t *testing.T) { + td := setupSuite(t) + defer td(t) + gcop := cop.GlobalCOP + gcop.PCs = []cop.COPer{} + cop.Init() + + prot := protocol.NewProtocolStructure() + prot.Metadata.Name = "TestAddOrUpdate" + prot.Metadata.Description = "A protocol of the unit tests to test AddOrUpdate of a protocol collection" + prot.Metadata.Version = "1.0" + prot.Metadata.Revision = 0 + prot.Metadata.TCPIPLayer = 1 + + f := protocol.NewField( + "Testfield", + "A field for the unit tests of protocol collections...", + "", + 32, + nil, + false, + false, + ) + prot.AddField(0, f) + col := gcop.PCs[0] + col.AddOrUpdate(prot) } diff --git a/cop/cop.go b/cop/cop.go new file mode 100644 index 0000000..e57a267 --- /dev/null +++ b/cop/cop.go @@ -0,0 +1,16 @@ +package cop + +import "gitea.mmo.to/ppForge/ppforge/protocol" + +// COP represents a Collection of Protocols +type COP struct { + Type string + Protocols map[string]ProtocolCollectionEntry + closed bool +} + +// ProtocolCollectionEntry is a single entry in the file database, additionally to the meta data the path is required +type ProtocolCollectionEntry struct { + Path string + Protocol protocol.ProtocolMeta +} diff --git a/cop/filecop.go b/cop/filecop.go new file mode 100644 index 0000000..9f9ffb7 --- /dev/null +++ b/cop/filecop.go @@ -0,0 +1,137 @@ +package cop + +import ( + "encoding/json" + "errors" + "fmt" + "io/fs" + "log" + "os" + "path" + "path/filepath" + "strings" + + "gitea.mmo.to/ppForge/ppforge/protocol" +) + +// FileCOP implements +type FileCOP struct { + COP + Path string +} + +// FileCOPFromJSON creates a FileCOP +func FileCOPFromJSON(data []byte) (COPer, error) { + fcop := &FileCOP{} + err := json.Unmarshal(data, fcop) + return fcop, err +} + +// NewFileCOP returns a new FileCOP instance from a given path +func NewFileCOP(path string) (*FileCOP, error) { + fCOP := &FileCOP{} + err := fCOP.Open(path) + if err != nil { + log.Printf("Error opening file COP at %s: %v", path, err) + return nil, err + } + err = fCOP.Sync() + return fCOP, err +} + +// Open the database +func (fd *FileCOP) Open(path string) error { + fileinfo, err := os.Stat(path) + if err == os.ErrNotExist { + return err + } + if fileinfo == nil || !fileinfo.IsDir() { + return fmt.Errorf("Path %s is not a directory or does not exist", path) + } + fd.Path = path + fd.closed = false + return nil +} + +// Close the database and free the data +func (fd *FileCOP) Close() error { + fd.closed = true + return nil +} + +// GetProtocols returns the map of protocols or an error if the first read failed +func (fd *FileCOP) GetProtocols() ([]ProtocolCollectionEntry, error) { + if fd.closed { + return nil, errors.New("COP closed, no protocols to deliver") + } + entries := []ProtocolCollectionEntry{} + if !fd.closed && len(fd.Protocols) == 0 { + fd.Protocols = map[string]ProtocolCollectionEntry{} + err := fd.Sync() + if err != nil { + return nil, err + } + } + if entries == nil || len(entries) == 0 { + entries = make([]ProtocolCollectionEntry, 0) + for _, v := range fd.Protocols { + entries = append(entries, v) + } + } + return entries, nil +} + +// AddOrUpdate puts a new protocol into the path of the file database +func (fd *FileCOP) AddOrUpdate(prot *protocol.Protocol) error { + if fd.closed { + return errors.New("Cannot add or update, FileCOP is closed") + } + if v, ok := fd.Protocols[prot.Metadata.Name]; ok { + v.Protocol = prot.Metadata + return prot.Save(v.Path) + } + p := path.Join(fd.Path, prot.Metadata.Name, ".protocoljson") + err := prot.Save(p) + if err == nil { + fd.Protocols[prot.Metadata.Name] = ProtocolCollectionEntry{p, prot.Metadata} + } + return err +} + +// Sync just rereads all files +func (fd *FileCOP) Sync() error { + if fd.closed { + return errors.New("Cannot sync, FileCOP is closed") + } + // recurse recursively through the path + if fd.Protocols == nil { + fd.Protocols = make(map[string]ProtocolCollectionEntry) + } + err := filepath.Walk(fd.Path, func(path string, info fs.FileInfo, err error) error { + if !info.IsDir() && strings.HasSuffix(info.Name(), ".protocoljson") { + prot, err := protocol.LoadNew(path) + if err == nil { + // add to map + // add to map + entry := ProtocolCollectionEntry{path, prot.Metadata} + + fd.Protocols[prot.Metadata.Name] = entry + } + return err + } + return nil + }) + return err +} + +// Get a protocol by name +func (fd *FileCOP) Get(prot string) (*protocol.Protocol, error) { + entry := fd.Protocols[prot] + return protocol.LoadNew(entry.Path) +} + +// ToJSON marshals the FileCOP to JSON +func (fd *FileCOP) ToJSON() (string, error) { + by, err := json.MarshalIndent(fd, "", " ") + return string(by), err +} diff --git a/cop/filecopgitremote.go b/cop/filecopgitremote.go new file mode 100644 index 0000000..9d0c6e4 --- /dev/null +++ b/cop/filecopgitremote.go @@ -0,0 +1,10 @@ +package cop + +import "github.com/go-git/go-git/v5" + +// FileCOPGitRemote implements retrieving the latest stuff from an external git remote additionally to the FileDatabase functionality +type FileCOPGitRemote struct { + FileCOP + RemoteGitRepository string + gitRepo git.Repository +}