forked from TrueCloudLab/frostfs-node
[#1377] oid, cid: Upgrade SDK package
Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
parent
f65898a354
commit
f15e6e888f
118 changed files with 1455 additions and 886 deletions
|
@ -420,12 +420,13 @@ func (x DeleteObjectRes) TombstoneAddress() *addressSDK.Address {
|
|||
func DeleteObject(prm DeleteObjectPrm) (*DeleteObjectRes, error) {
|
||||
var delPrm client.PrmObjectDelete
|
||||
|
||||
if id := prm.objAddr.ContainerID(); id != nil {
|
||||
delPrm.FromContainer(*id)
|
||||
cnr, ok := prm.objAddr.ContainerID()
|
||||
if ok {
|
||||
delPrm.FromContainer(cnr)
|
||||
}
|
||||
|
||||
if id := prm.objAddr.ObjectID(); id != nil {
|
||||
delPrm.ByID(*id)
|
||||
if id, ok := prm.objAddr.ObjectID(); ok {
|
||||
delPrm.ByID(id)
|
||||
}
|
||||
|
||||
if prm.sessionToken != nil {
|
||||
|
@ -450,8 +451,8 @@ func DeleteObject(prm DeleteObjectPrm) (*DeleteObjectRes, error) {
|
|||
}
|
||||
|
||||
var addr addressSDK.Address
|
||||
addr.SetObjectID(&id)
|
||||
addr.SetContainerID(prm.objAddr.ContainerID())
|
||||
addr.SetObjectID(id)
|
||||
addr.SetContainerID(cnr)
|
||||
|
||||
return &DeleteObjectRes{
|
||||
addrTombstone: &addr,
|
||||
|
@ -492,12 +493,12 @@ func (x GetObjectRes) Header() *object.Object {
|
|||
func GetObject(prm GetObjectPrm) (*GetObjectRes, error) {
|
||||
var getPrm client.PrmObjectGet
|
||||
|
||||
if id := prm.objAddr.ContainerID(); id != nil {
|
||||
getPrm.FromContainer(*id)
|
||||
if id, ok := prm.objAddr.ContainerID(); ok {
|
||||
getPrm.FromContainer(id)
|
||||
}
|
||||
|
||||
if id := prm.objAddr.ObjectID(); id != nil {
|
||||
getPrm.ByID(*id)
|
||||
if id, ok := prm.objAddr.ObjectID(); ok {
|
||||
getPrm.ByID(id)
|
||||
}
|
||||
|
||||
if prm.sessionToken != nil {
|
||||
|
@ -574,12 +575,12 @@ func (x HeadObjectRes) Header() *object.Object {
|
|||
func HeadObject(prm HeadObjectPrm) (*HeadObjectRes, error) {
|
||||
var cliPrm client.PrmObjectHead
|
||||
|
||||
if id := prm.objAddr.ContainerID(); id != nil {
|
||||
cliPrm.FromContainer(*id)
|
||||
if id, ok := prm.objAddr.ContainerID(); ok {
|
||||
cliPrm.FromContainer(id)
|
||||
}
|
||||
|
||||
if id := prm.objAddr.ObjectID(); id != nil {
|
||||
cliPrm.ByID(*id)
|
||||
if id, ok := prm.objAddr.ObjectID(); ok {
|
||||
cliPrm.ByID(id)
|
||||
}
|
||||
|
||||
if prm.sessionToken != nil {
|
||||
|
@ -739,12 +740,12 @@ func (x HashPayloadRangesRes) HashList() [][]byte {
|
|||
func HashPayloadRanges(prm HashPayloadRangesPrm) (*HashPayloadRangesRes, error) {
|
||||
var cliPrm client.PrmObjectHash
|
||||
|
||||
if id := prm.objAddr.ContainerID(); id != nil {
|
||||
cliPrm.FromContainer(*id)
|
||||
if id, ok := prm.objAddr.ContainerID(); ok {
|
||||
cliPrm.FromContainer(id)
|
||||
}
|
||||
|
||||
if id := prm.objAddr.ObjectID(); id != nil {
|
||||
cliPrm.ByID(*id)
|
||||
if id, ok := prm.objAddr.ObjectID(); ok {
|
||||
cliPrm.ByID(id)
|
||||
}
|
||||
|
||||
if prm.local {
|
||||
|
@ -813,12 +814,12 @@ type PayloadRangeRes struct{}
|
|||
func PayloadRange(prm PayloadRangePrm) (*PayloadRangeRes, error) {
|
||||
var cliPrm client.PrmObjectRange
|
||||
|
||||
if id := prm.objAddr.ContainerID(); id != nil {
|
||||
cliPrm.FromContainer(*id)
|
||||
if id, ok := prm.objAddr.ContainerID(); ok {
|
||||
cliPrm.FromContainer(id)
|
||||
}
|
||||
|
||||
if id := prm.objAddr.ObjectID(); id != nil {
|
||||
cliPrm.ByID(*id)
|
||||
if id, ok := prm.objAddr.ObjectID(); ok {
|
||||
cliPrm.ByID(id)
|
||||
}
|
||||
|
||||
if prm.sessionToken != nil {
|
||||
|
|
|
@ -65,10 +65,10 @@ func createEACL(cmd *cobra.Command, _ []string) {
|
|||
outArg, _ := cmd.Flags().GetString("out")
|
||||
cidArg, _ := cmd.Flags().GetString("cid")
|
||||
|
||||
var containerID *cid.ID
|
||||
var containerID cid.ID
|
||||
if cidArg != "" {
|
||||
containerID = cid.New()
|
||||
if err := containerID.Parse(cidArg); err != nil {
|
||||
var containerID cid.ID
|
||||
if err := containerID.DecodeString(cidArg); err != nil {
|
||||
cmd.PrintErrf("invalid container ID: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
|
|
@ -280,7 +280,7 @@ var listContainerObjectsCmd = &cobra.Command{
|
|||
var prm internalclient.SearchObjectsPrm
|
||||
|
||||
sessionObjectCtxAddress := addressSDK.NewAddress()
|
||||
sessionObjectCtxAddress.SetContainerID(id)
|
||||
sessionObjectCtxAddress.SetContainerID(*id)
|
||||
prepareSessionPrm(cmd, sessionObjectCtxAddress, &prm)
|
||||
prepareObjectPrm(cmd, &prm)
|
||||
prm.SetContainerID(id)
|
||||
|
@ -413,7 +413,7 @@ Container ID in EACL table will be substituted with ID from the CLI.`,
|
|||
tok, err := getSessionToken(sessionTokenPath)
|
||||
exitOnErr(cmd, err)
|
||||
|
||||
eaclTable.SetCID(id)
|
||||
eaclTable.SetCID(*id)
|
||||
eaclTable.SetSessionToken(tok)
|
||||
|
||||
var (
|
||||
|
@ -712,14 +712,14 @@ func parseContainerID(idStr string) (*cid.ID, error) {
|
|||
return nil, errors.New("container ID is not set")
|
||||
}
|
||||
|
||||
id := cid.New()
|
||||
var id cid.ID
|
||||
|
||||
err := id.Parse(idStr)
|
||||
err := id.DecodeString(idStr)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't decode container ID value")
|
||||
}
|
||||
|
||||
return id, nil
|
||||
return &id, nil
|
||||
}
|
||||
|
||||
func prettyPrintContainer(cmd *cobra.Command, cnr *container.Container, jsonEncoding bool) {
|
||||
|
|
|
@ -20,7 +20,7 @@ var cmdObjectLock = &cobra.Command{
|
|||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var cnr cid.ID
|
||||
|
||||
err := cnr.Parse(args[0])
|
||||
err := cnr.DecodeString(args[0])
|
||||
exitOnErr(cmd, errf("Incorrect container arg: %v", err))
|
||||
|
||||
argsList := args[1:]
|
||||
|
@ -28,7 +28,7 @@ var cmdObjectLock = &cobra.Command{
|
|||
lockList := make([]oid.ID, len(argsList))
|
||||
|
||||
for i := range argsList {
|
||||
err = lockList[i].Parse(argsList[i])
|
||||
err = lockList[i].DecodeString(argsList[i])
|
||||
exitOnErr(cmd, errf(fmt.Sprintf("Incorrect object arg #%d: %%v", i+1), err))
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ var cmdObjectLock = &cobra.Command{
|
|||
lock.WriteMembers(lockList)
|
||||
|
||||
obj := object.New()
|
||||
obj.SetContainerID(&cnr)
|
||||
obj.SetContainerID(cnr)
|
||||
obj.SetOwnerID(idOwner)
|
||||
obj.SetType(object.TypeLock)
|
||||
obj.SetPayload(lock.Marshal())
|
||||
|
|
|
@ -423,7 +423,7 @@ func putObject(cmd *cobra.Command, _ []string) {
|
|||
|
||||
ownerID, err := getOwnerID(key)
|
||||
exitOnErr(cmd, err)
|
||||
cid, err := getCID(cmd)
|
||||
cnr, err := getCID(cmd)
|
||||
exitOnErr(cmd, err)
|
||||
|
||||
filename := cmd.Flag("file").Value.String()
|
||||
|
@ -457,7 +457,7 @@ func putObject(cmd *cobra.Command, _ []string) {
|
|||
}
|
||||
|
||||
obj := object.New()
|
||||
obj.SetContainerID(cid)
|
||||
obj.SetContainerID(*cnr)
|
||||
obj.SetOwnerID(ownerID)
|
||||
obj.SetAttributes(attrs...)
|
||||
|
||||
|
@ -471,7 +471,7 @@ func putObject(cmd *cobra.Command, _ []string) {
|
|||
var prm internalclient.PutObjectPrm
|
||||
|
||||
sessionObjectCtxAddress := addressSDK.NewAddress()
|
||||
sessionObjectCtxAddress.SetContainerID(cid)
|
||||
sessionObjectCtxAddress.SetContainerID(*cnr)
|
||||
prepareSessionPrmWithOwner(cmd, sessionObjectCtxAddress, key, ownerID, &prm)
|
||||
prepareObjectPrm(cmd, &prm)
|
||||
prm.SetHeader(obj)
|
||||
|
@ -501,7 +501,7 @@ func putObject(cmd *cobra.Command, _ []string) {
|
|||
p.Finish()
|
||||
}
|
||||
cmd.Printf("[%s] Object successfully stored\n", filename)
|
||||
cmd.Printf(" ID: %s\n CID: %s\n", res.ID(), cid)
|
||||
cmd.Printf(" ID: %s\n CID: %s\n", res.ID(), cnr)
|
||||
}
|
||||
|
||||
func deleteObject(cmd *cobra.Command, _ []string) {
|
||||
|
@ -520,7 +520,25 @@ func deleteObject(cmd *cobra.Command, _ []string) {
|
|||
tombstoneAddr := res.TombstoneAddress()
|
||||
|
||||
cmd.Println("Object removed successfully.")
|
||||
cmd.Printf(" ID: %s\n CID: %s\n", tombstoneAddr.ObjectID(), tombstoneAddr.ContainerID())
|
||||
|
||||
const strEmpty = "<empty>"
|
||||
var strID, strCnr string
|
||||
|
||||
id, ok := tombstoneAddr.ObjectID()
|
||||
if ok {
|
||||
strID = id.String()
|
||||
} else {
|
||||
strID = strEmpty
|
||||
}
|
||||
|
||||
cnr, ok := tombstoneAddr.ContainerID()
|
||||
if ok {
|
||||
strCnr = cnr.String()
|
||||
} else {
|
||||
strCnr = strEmpty
|
||||
}
|
||||
|
||||
cmd.Printf(" ID: %s\n CID: %s\n", strID, strCnr)
|
||||
}
|
||||
|
||||
func getObject(cmd *cobra.Command, _ []string) {
|
||||
|
@ -616,7 +634,7 @@ func getObjectHeader(cmd *cobra.Command, _ []string) {
|
|||
}
|
||||
|
||||
func searchObject(cmd *cobra.Command, _ []string) {
|
||||
cid, err := getCID(cmd)
|
||||
cnr, err := getCID(cmd)
|
||||
exitOnErr(cmd, err)
|
||||
|
||||
sf, err := parseSearchFilters(cmd)
|
||||
|
@ -625,10 +643,10 @@ func searchObject(cmd *cobra.Command, _ []string) {
|
|||
var prm internalclient.SearchObjectsPrm
|
||||
|
||||
sessionObjectCtxAddress := addressSDK.NewAddress()
|
||||
sessionObjectCtxAddress.SetContainerID(cid)
|
||||
sessionObjectCtxAddress.SetContainerID(*cnr)
|
||||
prepareSessionPrm(cmd, sessionObjectCtxAddress, &prm)
|
||||
prepareObjectPrm(cmd, &prm)
|
||||
prm.SetContainerID(cid)
|
||||
prm.SetContainerID(cnr)
|
||||
prm.SetFilters(sf)
|
||||
|
||||
res, err := internalclient.SearchObjects(prm)
|
||||
|
@ -783,8 +801,8 @@ func parseSearchFilters(cmd *cobra.Command) (object.SearchFilters, error) {
|
|||
|
||||
oid, _ := cmd.Flags().GetString(searchOIDFlag)
|
||||
if oid != "" {
|
||||
id := oidSDK.NewID()
|
||||
if err := id.Parse(oid); err != nil {
|
||||
var id oidSDK.ID
|
||||
if err := id.DecodeString(oid); err != nil {
|
||||
return nil, fmt.Errorf("could not parse object ID: %w", err)
|
||||
}
|
||||
|
||||
|
@ -869,29 +887,29 @@ func parseObjectNotifications(cmd *cobra.Command) (*object.NotificationInfo, err
|
|||
}
|
||||
|
||||
func getCID(cmd *cobra.Command) (*cid.ID, error) {
|
||||
id := cid.New()
|
||||
var id cid.ID
|
||||
|
||||
err := id.Parse(cmd.Flag("cid").Value.String())
|
||||
err := id.DecodeString(cmd.Flag("cid").Value.String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse container ID: %w", err)
|
||||
}
|
||||
|
||||
return id, nil
|
||||
return &id, nil
|
||||
}
|
||||
|
||||
func getOID(cmd *cobra.Command) (*oidSDK.ID, error) {
|
||||
oid := oidSDK.NewID()
|
||||
var oid oidSDK.ID
|
||||
|
||||
err := oid.Parse(cmd.Flag("oid").Value.String())
|
||||
err := oid.DecodeString(cmd.Flag("oid").Value.String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse object ID: %w", err)
|
||||
}
|
||||
|
||||
return oid, nil
|
||||
return &oid, nil
|
||||
}
|
||||
|
||||
func getObjectAddress(cmd *cobra.Command) (*addressSDK.Address, error) {
|
||||
cid, err := getCID(cmd)
|
||||
cnr, err := getCID(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -901,8 +919,8 @@ func getObjectAddress(cmd *cobra.Command) (*addressSDK.Address, error) {
|
|||
}
|
||||
|
||||
objAddr := addressSDK.NewAddress()
|
||||
objAddr.SetContainerID(cid)
|
||||
objAddr.SetObjectID(oid)
|
||||
objAddr.SetContainerID(*cnr)
|
||||
objAddr.SetObjectID(*oid)
|
||||
return objAddr, nil
|
||||
}
|
||||
|
||||
|
@ -977,9 +995,35 @@ func printChecksum(cmd *cobra.Command, name string, recv func() (checksum.Checks
|
|||
cmd.Printf("%s: %s\n", name, strVal)
|
||||
}
|
||||
|
||||
func printObjectID(cmd *cobra.Command, recv func() (oidSDK.ID, bool)) {
|
||||
var strID string
|
||||
|
||||
id, ok := recv()
|
||||
if ok {
|
||||
strID = id.String()
|
||||
} else {
|
||||
strID = "<empty>"
|
||||
}
|
||||
|
||||
cmd.Printf("ID: %s\n", strID)
|
||||
}
|
||||
|
||||
func printContainerID(cmd *cobra.Command, recv func() (cid.ID, bool)) {
|
||||
var strID string
|
||||
|
||||
id, ok := recv()
|
||||
if ok {
|
||||
strID = id.String()
|
||||
} else {
|
||||
strID = "<empty>"
|
||||
}
|
||||
|
||||
cmd.Printf("CID: %s\n", strID)
|
||||
}
|
||||
|
||||
func printHeader(cmd *cobra.Command, obj *object.Object) error {
|
||||
cmd.Printf("ID: %s\n", obj.ID())
|
||||
cmd.Printf("CID: %s\n", obj.ContainerID())
|
||||
printObjectID(cmd, obj.ID)
|
||||
printContainerID(cmd, obj.ContainerID)
|
||||
cmd.Printf("Owner: %s\n", obj.OwnerID())
|
||||
cmd.Printf("CreatedAt: %d\n", obj.CreationEpoch())
|
||||
cmd.Printf("Size: %d\n", obj.PayloadSize())
|
||||
|
@ -1007,11 +1051,11 @@ func printSplitHeader(cmd *cobra.Command, obj *object.Object) error {
|
|||
cmd.Printf("Split ID: %s\n", splitID)
|
||||
}
|
||||
|
||||
if oid := obj.ParentID(); oid != nil {
|
||||
if oid, ok := obj.ParentID(); ok {
|
||||
cmd.Printf("Split ParentID: %s\n", oid)
|
||||
}
|
||||
|
||||
if prev := obj.PreviousID(); prev != nil {
|
||||
if prev, ok := obj.PreviousID(); ok {
|
||||
cmd.Printf("Split PreviousID: %s\n", prev)
|
||||
}
|
||||
|
||||
|
@ -1165,10 +1209,10 @@ func marshalSplitInfo(cmd *cobra.Command, info *object.SplitInfo) ([]byte, error
|
|||
if splitID := info.SplitID(); splitID != nil {
|
||||
b.WriteString("Split ID: " + splitID.String() + "\n")
|
||||
}
|
||||
if link := info.Link(); link != nil {
|
||||
if link, ok := info.Link(); ok {
|
||||
b.WriteString("Linking object: " + link.String() + "\n")
|
||||
}
|
||||
if last := info.LastPart(); last != nil {
|
||||
if last, ok := info.LastPart(); ok {
|
||||
b.WriteString("Last object: " + last.String() + "\n")
|
||||
}
|
||||
return b.Bytes(), nil
|
||||
|
|
|
@ -167,13 +167,13 @@ func putSG(cmd *cobra.Command, _ []string) {
|
|||
ownerID, err := getOwnerID(key)
|
||||
exitOnErr(cmd, err)
|
||||
|
||||
cid, err := getCID(cmd)
|
||||
cnr, err := getCID(cmd)
|
||||
exitOnErr(cmd, err)
|
||||
|
||||
members := make([]oidSDK.ID, len(sgMembers))
|
||||
|
||||
for i := range sgMembers {
|
||||
err = members[i].Parse(sgMembers[i])
|
||||
err = members[i].DecodeString(sgMembers[i])
|
||||
exitOnErr(cmd, errf("could not parse object ID: %w", err))
|
||||
}
|
||||
|
||||
|
@ -183,7 +183,7 @@ func putSG(cmd *cobra.Command, _ []string) {
|
|||
)
|
||||
|
||||
sessionObjectCtxAddress := addressSDK.NewAddress()
|
||||
sessionObjectCtxAddress.SetContainerID(cid)
|
||||
sessionObjectCtxAddress.SetContainerID(*cnr)
|
||||
prepareSessionPrmWithOwner(cmd, sessionObjectCtxAddress, key, ownerID, &putPrm)
|
||||
prepareObjectPrm(cmd, &headPrm, &putPrm)
|
||||
|
||||
|
@ -194,14 +194,14 @@ func putSG(cmd *cobra.Command, _ []string) {
|
|||
key: key,
|
||||
ownerID: ownerID,
|
||||
prm: headPrm,
|
||||
}, cid, members)
|
||||
}, cnr, members)
|
||||
exitOnErr(cmd, errf("could not collect storage group members: %w", err))
|
||||
|
||||
sgContent, err := sg.Marshal()
|
||||
exitOnErr(cmd, errf("could not marshal storage group: %w", err))
|
||||
|
||||
obj := object.New()
|
||||
obj.SetContainerID(cid)
|
||||
obj.SetContainerID(*cnr)
|
||||
obj.SetOwnerID(ownerID)
|
||||
obj.SetType(object.TypeStorageGroup)
|
||||
|
||||
|
@ -212,29 +212,29 @@ func putSG(cmd *cobra.Command, _ []string) {
|
|||
exitOnErr(cmd, errf("rpc error: %w", err))
|
||||
|
||||
cmd.Println("Storage group successfully stored")
|
||||
cmd.Printf(" ID: %s\n CID: %s\n", res.ID(), cid)
|
||||
cmd.Printf(" ID: %s\n CID: %s\n", res.ID(), cnr)
|
||||
}
|
||||
|
||||
func getSGID() (*oidSDK.ID, error) {
|
||||
oid := oidSDK.NewID()
|
||||
err := oid.Parse(sgID)
|
||||
var oid oidSDK.ID
|
||||
err := oid.DecodeString(sgID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse storage group ID: %w", err)
|
||||
}
|
||||
|
||||
return oid, nil
|
||||
return &oid, nil
|
||||
}
|
||||
|
||||
func getSG(cmd *cobra.Command, _ []string) {
|
||||
cid, err := getCID(cmd)
|
||||
cnr, err := getCID(cmd)
|
||||
exitOnErr(cmd, err)
|
||||
|
||||
id, err := getSGID()
|
||||
exitOnErr(cmd, err)
|
||||
|
||||
addr := addressSDK.NewAddress()
|
||||
addr.SetContainerID(cid)
|
||||
addr.SetObjectID(id)
|
||||
addr.SetContainerID(*cnr)
|
||||
addr.SetObjectID(*id)
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
|
@ -267,16 +267,16 @@ func getSG(cmd *cobra.Command, _ []string) {
|
|||
}
|
||||
|
||||
func listSG(cmd *cobra.Command, _ []string) {
|
||||
cid, err := getCID(cmd)
|
||||
cnr, err := getCID(cmd)
|
||||
exitOnErr(cmd, err)
|
||||
|
||||
var prm internalclient.SearchObjectsPrm
|
||||
|
||||
sessionObjectCtxAddress := addressSDK.NewAddress()
|
||||
sessionObjectCtxAddress.SetContainerID(cid)
|
||||
sessionObjectCtxAddress.SetContainerID(*cnr)
|
||||
prepareSessionPrm(cmd, sessionObjectCtxAddress, &prm)
|
||||
prepareObjectPrm(cmd, &prm)
|
||||
prm.SetContainerID(cid)
|
||||
prm.SetContainerID(cnr)
|
||||
prm.SetFilters(storagegroup.SearchQuery())
|
||||
|
||||
res, err := internalclient.SearchObjects(prm)
|
||||
|
@ -292,15 +292,15 @@ func listSG(cmd *cobra.Command, _ []string) {
|
|||
}
|
||||
|
||||
func delSG(cmd *cobra.Command, _ []string) {
|
||||
cid, err := getCID(cmd)
|
||||
cnr, err := getCID(cmd)
|
||||
exitOnErr(cmd, err)
|
||||
|
||||
id, err := getSGID()
|
||||
exitOnErr(cmd, err)
|
||||
|
||||
addr := addressSDK.NewAddress()
|
||||
addr.SetContainerID(cid)
|
||||
addr.SetObjectID(id)
|
||||
addr.SetContainerID(*cnr)
|
||||
addr.SetObjectID(*id)
|
||||
|
||||
var prm internalclient.DeleteObjectPrm
|
||||
|
||||
|
@ -313,6 +313,15 @@ func delSG(cmd *cobra.Command, _ []string) {
|
|||
|
||||
tombstone := res.TombstoneAddress()
|
||||
|
||||
var strID string
|
||||
|
||||
idTomb, ok := tombstone.ObjectID()
|
||||
if ok {
|
||||
strID = idTomb.String()
|
||||
} else {
|
||||
strID = "<empty>"
|
||||
}
|
||||
|
||||
cmd.Println("Storage group removed successfully.")
|
||||
cmd.Printf(" Tombstone: %s\n", tombstone.ObjectID())
|
||||
cmd.Printf(" Tombstone: %s\n", strID)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue