// fs implements galaxy/storage.Storage with filesystem package fs /* Общие правила: 1. Все хранимые объекты сериализуются / десериализуются как JSON. 2. Структура хранения файлов: - storageRoot \ | +-- state.dat | +-- {GameID} \ | | | +-- {Turn}.dat (client.GameData) | +-- {Turn}.dat (client.GameData) | +-- ... | +-- {GameID} \ | +-- ... */ import ( "fmt" "galaxy/util" "path/filepath" ) const ( // Name of the file under the storage's root where [model.State] is stored. stateFileName = "state.dat" // Suffix of a Game's file inder the storage's root where [model.GameData] is stored. gameDataFileSuffix = ".dat" ) // StateFilePath returns client's state file path relative to the root, // file name and extension are pre-defined constant. func StateFilePath(root string) string { return filepath.Join(root, stateFileName) } // GameDataPath returns game's data file path relative to the root, // data file name is GameID string representation and extension is a pre-defined constant. func GameDataFilePath(root string, id fmt.Stringer) string { return filepath.Join(root, id.String()) + gameDataFileSuffix } type fsStorage struct { storageRoot string } // NewFS returns on-filesystem implementation of the "galaxy/storage.Storage" with root located at storageRoot. // storageRoot must me a directory and has write access to the current user. If initial checks failed, return nil and non-nil error. func NewStorage(storageRoot string) (*fsStorage, error) { if ok, err := util.PathExists(storageRoot, true); err != nil { return nil, fmt.Errorf("new storage: check path %q exists: %w", storageRoot, err) } else if !ok { return nil, fmt.Errorf("new storage: path %q does not exists", storageRoot) } if ok, err := util.Writable(storageRoot); err != nil { return nil, fmt.Errorf("new storage: check path %q writable: %w", storageRoot, err) } else if !ok { return nil, fmt.Errorf("new storage: path %q is not writable", storageRoot) } s := &fsStorage{ storageRoot: storageRoot, } return s, nil }