github.com/cyverse/go-irodsclient@v0.13.2/irods/fs/collection.go (about)

     1  package fs
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  	"strconv"
     7  	"time"
     8  
     9  	"github.com/cyverse/go-irodsclient/irods/common"
    10  	"github.com/cyverse/go-irodsclient/irods/connection"
    11  	"github.com/cyverse/go-irodsclient/irods/message"
    12  	"github.com/cyverse/go-irodsclient/irods/types"
    13  	"github.com/cyverse/go-irodsclient/irods/util"
    14  	"golang.org/x/xerrors"
    15  )
    16  
    17  /*
    18  Table "public.r_coll_main"
    19  Column      |          Type           | Collation | Nullable |        Default
    20  ------------------+-------------------------+-----------+----------+------------------------
    21  coll_id          | bigint                  |           | not null |
    22  parent_coll_name | character varying(2700) |           | not null |
    23  coll_name        | character varying(2700) |           | not null |
    24  coll_owner_name  | character varying(250)  |           | not null |
    25  coll_owner_zone  | character varying(250)  |           | not null |
    26  coll_map_id      | bigint                  |           |          | 0
    27  coll_inheritance | character varying(1000) |           |          |
    28  coll_type        | character varying(250)  |           |          | '0'::character varying
    29  coll_info1       | character varying(2700) |           |          | '0'::character varying
    30  coll_info2       | character varying(2700) |           |          | '0'::character varying
    31  coll_expiry_ts   | character varying(32)   |           |          |
    32  r_comment        | character varying(1000) |           |          |
    33  create_ts        | character varying(32)   |           |          |
    34  modify_ts        | character varying(32)   |           |          |
    35  Indexes:
    36  "idx_coll_main2" UNIQUE, btree (parent_coll_name, coll_name)
    37  "idx_coll_main3" UNIQUE, btree (coll_name)
    38  "idx_coll_main1" btree (coll_id)
    39  "idx_coll_main_parent_coll_name" btree (parent_coll_name)
    40  */
    41  
    42  // GetCollection returns a collection for the path
    43  func GetCollection(conn *connection.IRODSConnection, path string) (*types.IRODSCollection, error) {
    44  	if conn == nil || !conn.IsConnected() {
    45  		return nil, xerrors.Errorf("connection is nil or disconnected")
    46  	}
    47  
    48  	metrics := conn.GetMetrics()
    49  	if metrics != nil {
    50  		metrics.IncreaseCounterForStat(1)
    51  	}
    52  
    53  	// lock the connection
    54  	conn.Lock()
    55  	defer conn.Unlock()
    56  
    57  	query := message.NewIRODSMessageQueryRequest(common.MaxQueryRows, 0, 0, 0)
    58  	query.AddSelect(common.ICAT_COLUMN_COLL_ID, 1)
    59  	query.AddSelect(common.ICAT_COLUMN_COLL_NAME, 1)
    60  	query.AddSelect(common.ICAT_COLUMN_COLL_OWNER_NAME, 1)
    61  	query.AddSelect(common.ICAT_COLUMN_COLL_CREATE_TIME, 1)
    62  	query.AddSelect(common.ICAT_COLUMN_COLL_MODIFY_TIME, 1)
    63  
    64  	condVal := fmt.Sprintf("= '%s'", path)
    65  	query.AddCondition(common.ICAT_COLUMN_COLL_NAME, condVal)
    66  
    67  	queryResult := message.IRODSMessageQueryResponse{}
    68  	err := conn.Request(query, &queryResult, nil)
    69  	if err != nil {
    70  		return nil, xerrors.Errorf("failed to receive collection query result message: %w", err)
    71  	}
    72  
    73  	err = queryResult.CheckError()
    74  	if err != nil {
    75  		if types.GetIRODSErrorCode(err) == common.CAT_NO_ROWS_FOUND {
    76  			return nil, xerrors.Errorf("failed to find the collection for path %s: %w", path, types.NewFileNotFoundError(path))
    77  		}
    78  		return nil, xerrors.Errorf("received collection query error: %w", err)
    79  	}
    80  
    81  	if queryResult.RowCount != 1 {
    82  		// file not found
    83  		return nil, xerrors.Errorf("failed to find the collection for path %s: %w", path, types.NewFileNotFoundError(path))
    84  	}
    85  
    86  	if queryResult.AttributeCount > len(queryResult.SQLResult) {
    87  		return nil, xerrors.Errorf("failed to receive collection attributes - requires %d, but received %d attributes", queryResult.AttributeCount, len(queryResult.SQLResult))
    88  	}
    89  
    90  	var collectionID int64 = -1
    91  	collectionPath := ""
    92  	collectionOwner := ""
    93  	createTime := time.Time{}
    94  	modifyTime := time.Time{}
    95  	for idx := 0; idx < queryResult.AttributeCount; idx++ {
    96  		sqlResult := queryResult.SQLResult[idx]
    97  		if len(sqlResult.Values) != queryResult.RowCount {
    98  			return nil, xerrors.Errorf("failed to receive collection rows - requires %d, but received %d attributes", queryResult.RowCount, len(sqlResult.Values))
    99  		}
   100  
   101  		value := sqlResult.Values[0]
   102  
   103  		switch sqlResult.AttributeIndex {
   104  		case int(common.ICAT_COLUMN_COLL_ID):
   105  			cID, err := strconv.ParseInt(value, 10, 64)
   106  			if err != nil {
   107  				return nil, xerrors.Errorf("failed to parse collection id '%s': %w", value, err)
   108  			}
   109  			collectionID = cID
   110  		case int(common.ICAT_COLUMN_COLL_NAME):
   111  			collectionPath = value
   112  		case int(common.ICAT_COLUMN_COLL_OWNER_NAME):
   113  			collectionOwner = value
   114  		case int(common.ICAT_COLUMN_COLL_CREATE_TIME):
   115  			cT, err := util.GetIRODSDateTime(value)
   116  			if err != nil {
   117  				return nil, xerrors.Errorf("failed to parse create time '%s': %w", value, err)
   118  			}
   119  			createTime = cT
   120  		case int(common.ICAT_COLUMN_COLL_MODIFY_TIME):
   121  			mT, err := util.GetIRODSDateTime(value)
   122  			if err != nil {
   123  				return nil, xerrors.Errorf("failed to parse modify time '%s': %w", value, err)
   124  			}
   125  			modifyTime = mT
   126  		default:
   127  			// ignore
   128  		}
   129  	}
   130  
   131  	if collectionID == -1 {
   132  		return nil, xerrors.Errorf("failed to find the collection for path %s: %w", path, types.NewFileNotFoundError(path))
   133  	}
   134  
   135  	return &types.IRODSCollection{
   136  		ID:         collectionID,
   137  		Path:       collectionPath,
   138  		Name:       util.GetIRODSPathFileName(collectionPath),
   139  		Owner:      collectionOwner,
   140  		CreateTime: createTime,
   141  		ModifyTime: modifyTime,
   142  	}, nil
   143  }
   144  
   145  // ListCollectionMeta returns a colleciton metadata for the path
   146  func ListCollectionMeta(conn *connection.IRODSConnection, path string) ([]*types.IRODSMeta, error) {
   147  	if conn == nil || !conn.IsConnected() {
   148  		return nil, xerrors.Errorf("connection is nil or disconnected")
   149  	}
   150  
   151  	metrics := conn.GetMetrics()
   152  	if metrics != nil {
   153  		metrics.IncreaseCounterForMetadataList(1)
   154  	}
   155  
   156  	// lock the connection
   157  	conn.Lock()
   158  	defer conn.Unlock()
   159  
   160  	metas := []*types.IRODSMeta{}
   161  
   162  	continueQuery := true
   163  	continueIndex := 0
   164  	for continueQuery {
   165  		query := message.NewIRODSMessageQueryRequest(common.MaxQueryRows, continueIndex, 0, 0)
   166  		query.AddSelect(common.ICAT_COLUMN_META_COLL_ATTR_ID, 1)
   167  		query.AddSelect(common.ICAT_COLUMN_META_COLL_ATTR_NAME, 1)
   168  		query.AddSelect(common.ICAT_COLUMN_META_COLL_ATTR_VALUE, 1)
   169  		query.AddSelect(common.ICAT_COLUMN_META_COLL_ATTR_UNITS, 1)
   170  
   171  		condVal := fmt.Sprintf("= '%s'", path)
   172  		query.AddCondition(common.ICAT_COLUMN_COLL_NAME, condVal)
   173  
   174  		queryResult := message.IRODSMessageQueryResponse{}
   175  		err := conn.Request(query, &queryResult, nil)
   176  		if err != nil {
   177  			return nil, xerrors.Errorf("failed to receive a collection metadata query result message: %w", err)
   178  		}
   179  
   180  		err = queryResult.CheckError()
   181  		if err != nil {
   182  			if types.GetIRODSErrorCode(err) == common.CAT_NO_ROWS_FOUND {
   183  				// empty
   184  				break
   185  			}
   186  			return nil, xerrors.Errorf("received collection metadata query error: %w", err)
   187  		}
   188  
   189  		if queryResult.RowCount == 0 {
   190  			break
   191  		}
   192  
   193  		if queryResult.AttributeCount > len(queryResult.SQLResult) {
   194  			return nil, xerrors.Errorf("failed to receive collection metadata attributes - requires %d, but received %d attributes", queryResult.AttributeCount, len(queryResult.SQLResult))
   195  		}
   196  
   197  		pagenatedMetas := make([]*types.IRODSMeta, queryResult.RowCount)
   198  
   199  		for attr := 0; attr < queryResult.AttributeCount; attr++ {
   200  			sqlResult := queryResult.SQLResult[attr]
   201  			if len(sqlResult.Values) != queryResult.RowCount {
   202  				return nil, xerrors.Errorf("failed to receive collection metadata rows - requires %d, but received %d attributes", queryResult.RowCount, len(sqlResult.Values))
   203  			}
   204  
   205  			for row := 0; row < queryResult.RowCount; row++ {
   206  				value := sqlResult.Values[row]
   207  
   208  				if pagenatedMetas[row] == nil {
   209  					// create a new
   210  					pagenatedMetas[row] = &types.IRODSMeta{
   211  						AVUID: -1,
   212  						Name:  "",
   213  						Value: "",
   214  						Units: "",
   215  					}
   216  				}
   217  
   218  				switch sqlResult.AttributeIndex {
   219  				case int(common.ICAT_COLUMN_META_COLL_ATTR_ID):
   220  					avuID, err := strconv.ParseInt(value, 10, 64)
   221  					if err != nil {
   222  						return nil, xerrors.Errorf("failed to parse collection metadata id '%s': %w", value, err)
   223  					}
   224  					pagenatedMetas[row].AVUID = avuID
   225  				case int(common.ICAT_COLUMN_META_COLL_ATTR_NAME):
   226  					pagenatedMetas[row].Name = value
   227  				case int(common.ICAT_COLUMN_META_COLL_ATTR_VALUE):
   228  					pagenatedMetas[row].Value = value
   229  				case int(common.ICAT_COLUMN_META_COLL_ATTR_UNITS):
   230  					pagenatedMetas[row].Units = value
   231  				default:
   232  					// ignore
   233  				}
   234  			}
   235  		}
   236  
   237  		metas = append(metas, pagenatedMetas...)
   238  
   239  		continueIndex = queryResult.ContinueIndex
   240  		if continueIndex == 0 {
   241  			continueQuery = false
   242  		}
   243  	}
   244  
   245  	return metas, nil
   246  }
   247  
   248  // ListCollectionAccesses returns collection accesses for the path
   249  func ListCollectionAccesses(conn *connection.IRODSConnection, path string) ([]*types.IRODSAccess, error) {
   250  	if conn == nil || !conn.IsConnected() {
   251  		return nil, xerrors.Errorf("connection is nil or disconnected")
   252  	}
   253  
   254  	metrics := conn.GetMetrics()
   255  	if metrics != nil {
   256  		metrics.IncreaseCounterForAccessList(1)
   257  	}
   258  
   259  	// lock the connection
   260  	conn.Lock()
   261  	defer conn.Unlock()
   262  
   263  	accesses := []*types.IRODSAccess{}
   264  
   265  	continueQuery := true
   266  	continueIndex := 0
   267  	for continueQuery {
   268  		query := message.NewIRODSMessageQueryRequest(common.MaxQueryRows, continueIndex, 0, 0)
   269  		query.AddSelect(common.ICAT_COLUMN_COLL_ACCESS_NAME, 1)
   270  		query.AddSelect(common.ICAT_COLUMN_USER_NAME, 1)
   271  		query.AddSelect(common.ICAT_COLUMN_USER_ZONE, 1)
   272  		query.AddSelect(common.ICAT_COLUMN_USER_TYPE, 1)
   273  
   274  		condVal := fmt.Sprintf("= '%s'", path)
   275  		query.AddCondition(common.ICAT_COLUMN_COLL_NAME, condVal)
   276  
   277  		queryResult := message.IRODSMessageQueryResponse{}
   278  		err := conn.Request(query, &queryResult, nil)
   279  		if err != nil {
   280  			return nil, xerrors.Errorf("failed to receive a collection access query result message: %w", err)
   281  		}
   282  
   283  		err = queryResult.CheckError()
   284  		if err != nil {
   285  			if types.GetIRODSErrorCode(err) == common.CAT_NO_ROWS_FOUND {
   286  				// empty
   287  				break
   288  			}
   289  			return nil, xerrors.Errorf("received collection access query error: %w", err)
   290  		}
   291  
   292  		if queryResult.RowCount == 0 {
   293  			break
   294  		}
   295  
   296  		if queryResult.AttributeCount > len(queryResult.SQLResult) {
   297  			return nil, xerrors.Errorf("failed to receive collection access attributes - requires %d, but received %d attributes", queryResult.AttributeCount, len(queryResult.SQLResult))
   298  		}
   299  
   300  		pagenatedAccesses := make([]*types.IRODSAccess, queryResult.RowCount)
   301  
   302  		for attr := 0; attr < queryResult.AttributeCount; attr++ {
   303  			sqlResult := queryResult.SQLResult[attr]
   304  			if len(sqlResult.Values) != queryResult.RowCount {
   305  				return nil, xerrors.Errorf("failed to receive collection access rows - requires %d, but received %d attributes", queryResult.RowCount, len(sqlResult.Values))
   306  			}
   307  
   308  			for row := 0; row < queryResult.RowCount; row++ {
   309  				value := sqlResult.Values[row]
   310  
   311  				if pagenatedAccesses[row] == nil {
   312  					// create a new
   313  					pagenatedAccesses[row] = &types.IRODSAccess{
   314  						Path:        path,
   315  						UserName:    "",
   316  						UserZone:    "",
   317  						AccessLevel: types.IRODSAccessLevelNone,
   318  						UserType:    types.IRODSUserRodsUser,
   319  					}
   320  				}
   321  
   322  				switch sqlResult.AttributeIndex {
   323  				case int(common.ICAT_COLUMN_COLL_ACCESS_NAME):
   324  					pagenatedAccesses[row].AccessLevel = types.IRODSAccessLevelType(value)
   325  				case int(common.ICAT_COLUMN_USER_TYPE):
   326  					pagenatedAccesses[row].UserType = types.IRODSUserType(value)
   327  				case int(common.ICAT_COLUMN_USER_NAME):
   328  					pagenatedAccesses[row].UserName = value
   329  				case int(common.ICAT_COLUMN_USER_ZONE):
   330  					pagenatedAccesses[row].UserZone = value
   331  				default:
   332  					// ignore
   333  				}
   334  			}
   335  		}
   336  
   337  		accesses = append(accesses, pagenatedAccesses...)
   338  
   339  		continueIndex = queryResult.ContinueIndex
   340  		if continueIndex == 0 {
   341  			continueQuery = false
   342  		}
   343  	}
   344  
   345  	return accesses, nil
   346  }
   347  
   348  // ListAccessesForSubCollections returns collection accesses for subcollections in the given path
   349  func ListAccessesForSubCollections(conn *connection.IRODSConnection, path string) ([]*types.IRODSAccess, error) {
   350  	if conn == nil || !conn.IsConnected() {
   351  		return nil, xerrors.Errorf("connection is nil or disconnected")
   352  	}
   353  
   354  	metrics := conn.GetMetrics()
   355  	if metrics != nil {
   356  		metrics.IncreaseCounterForAccessList(1)
   357  	}
   358  
   359  	// lock the connection
   360  	conn.Lock()
   361  	defer conn.Unlock()
   362  
   363  	accesses := []*types.IRODSAccess{}
   364  
   365  	continueQuery := true
   366  	continueIndex := 0
   367  	for continueQuery {
   368  		query := message.NewIRODSMessageQueryRequest(common.MaxQueryRows, continueIndex, 0, 0)
   369  		query.AddSelect(common.ICAT_COLUMN_COLL_NAME, 1)
   370  		query.AddSelect(common.ICAT_COLUMN_COLL_ACCESS_NAME, 1)
   371  		query.AddSelect(common.ICAT_COLUMN_USER_NAME, 1)
   372  		query.AddSelect(common.ICAT_COLUMN_USER_ZONE, 1)
   373  		query.AddSelect(common.ICAT_COLUMN_USER_TYPE, 1)
   374  
   375  		condVal := fmt.Sprintf("= '%s'", path)
   376  		query.AddCondition(common.ICAT_COLUMN_COLL_PARENT_NAME, condVal)
   377  
   378  		queryResult := message.IRODSMessageQueryResponse{}
   379  		err := conn.Request(query, &queryResult, nil)
   380  		if err != nil {
   381  			return nil, xerrors.Errorf("failed to receive a collection access query result message: %w", err)
   382  		}
   383  
   384  		err = queryResult.CheckError()
   385  		if err != nil {
   386  			if types.GetIRODSErrorCode(err) == common.CAT_NO_ROWS_FOUND {
   387  				// empty
   388  				break
   389  			}
   390  			return nil, xerrors.Errorf("received collection access query error: %w", err)
   391  		}
   392  
   393  		if queryResult.RowCount == 0 {
   394  			break
   395  		}
   396  
   397  		if queryResult.AttributeCount > len(queryResult.SQLResult) {
   398  			return nil, xerrors.Errorf("failed to receive collection access attributes - requires %d, but received %d attributes", queryResult.AttributeCount, len(queryResult.SQLResult))
   399  		}
   400  
   401  		pagenatedAccesses := make([]*types.IRODSAccess, queryResult.RowCount)
   402  
   403  		for attr := 0; attr < queryResult.AttributeCount; attr++ {
   404  			sqlResult := queryResult.SQLResult[attr]
   405  			if len(sqlResult.Values) != queryResult.RowCount {
   406  				return nil, xerrors.Errorf("failed to receive collection access rows - requires %d, but received %d attributes", queryResult.RowCount, len(sqlResult.Values))
   407  			}
   408  
   409  			for row := 0; row < queryResult.RowCount; row++ {
   410  				value := sqlResult.Values[row]
   411  
   412  				if pagenatedAccesses[row] == nil {
   413  					// create a new
   414  					pagenatedAccesses[row] = &types.IRODSAccess{
   415  						Path:        "",
   416  						UserName:    "",
   417  						UserZone:    "",
   418  						AccessLevel: types.IRODSAccessLevelNone,
   419  						UserType:    types.IRODSUserRodsUser,
   420  					}
   421  				}
   422  
   423  				switch sqlResult.AttributeIndex {
   424  				case int(common.ICAT_COLUMN_COLL_NAME):
   425  					pagenatedAccesses[row].Path = value
   426  				case int(common.ICAT_COLUMN_COLL_ACCESS_NAME):
   427  					pagenatedAccesses[row].AccessLevel = types.IRODSAccessLevelType(value)
   428  				case int(common.ICAT_COLUMN_USER_TYPE):
   429  					pagenatedAccesses[row].UserType = types.IRODSUserType(value)
   430  				case int(common.ICAT_COLUMN_USER_NAME):
   431  					pagenatedAccesses[row].UserName = value
   432  				case int(common.ICAT_COLUMN_USER_ZONE):
   433  					pagenatedAccesses[row].UserZone = value
   434  				default:
   435  					// ignore
   436  				}
   437  			}
   438  		}
   439  
   440  		accesses = append(accesses, pagenatedAccesses...)
   441  
   442  		continueIndex = queryResult.ContinueIndex
   443  		if continueIndex == 0 {
   444  			continueQuery = false
   445  		}
   446  	}
   447  
   448  	return accesses, nil
   449  }
   450  
   451  // ListSubCollections lists subcollections in the given collection
   452  func ListSubCollections(conn *connection.IRODSConnection, path string) ([]*types.IRODSCollection, error) {
   453  	if conn == nil || !conn.IsConnected() {
   454  		return nil, xerrors.Errorf("connection is nil or disconnected")
   455  	}
   456  
   457  	metrics := conn.GetMetrics()
   458  	if metrics != nil {
   459  		metrics.IncreaseCounterForList(1)
   460  	}
   461  
   462  	// lock the connection
   463  	conn.Lock()
   464  	defer conn.Unlock()
   465  
   466  	collections := []*types.IRODSCollection{}
   467  
   468  	continueQuery := true
   469  	continueIndex := 0
   470  	for continueQuery {
   471  		query := message.NewIRODSMessageQueryRequest(common.MaxQueryRows, continueIndex, 0, 0)
   472  		query.AddSelect(common.ICAT_COLUMN_COLL_ID, 1)
   473  		query.AddSelect(common.ICAT_COLUMN_COLL_NAME, 1)
   474  		query.AddSelect(common.ICAT_COLUMN_COLL_OWNER_NAME, 1)
   475  		query.AddSelect(common.ICAT_COLUMN_COLL_CREATE_TIME, 1)
   476  		query.AddSelect(common.ICAT_COLUMN_COLL_MODIFY_TIME, 1)
   477  
   478  		condVal := fmt.Sprintf("= '%s'", path)
   479  		query.AddCondition(common.ICAT_COLUMN_COLL_PARENT_NAME, condVal)
   480  
   481  		queryResult := message.IRODSMessageQueryResponse{}
   482  		err := conn.Request(query, &queryResult, nil)
   483  		if err != nil {
   484  			return nil, xerrors.Errorf("failed to receive a collection query result message: %w", err)
   485  		}
   486  
   487  		err = queryResult.CheckError()
   488  		if err != nil {
   489  			if types.GetIRODSErrorCode(err) == common.CAT_NO_ROWS_FOUND {
   490  				// empty
   491  				break
   492  			}
   493  			return nil, xerrors.Errorf("received collection query error: %w", err)
   494  		}
   495  
   496  		if queryResult.RowCount == 0 {
   497  			break
   498  		}
   499  
   500  		if queryResult.AttributeCount > len(queryResult.SQLResult) {
   501  			return nil, xerrors.Errorf("failed to receive collection attributes - requires %d, but received %d attributes", queryResult.AttributeCount, len(queryResult.SQLResult))
   502  		}
   503  
   504  		pagenatedCollections := make([]*types.IRODSCollection, queryResult.RowCount)
   505  
   506  		for attr := 0; attr < queryResult.AttributeCount; attr++ {
   507  			sqlResult := queryResult.SQLResult[attr]
   508  			if len(sqlResult.Values) != queryResult.RowCount {
   509  				return nil, xerrors.Errorf("failed to receive collection rows - requires %d, but received %d attributes", queryResult.RowCount, len(sqlResult.Values))
   510  			}
   511  
   512  			for row := 0; row < queryResult.RowCount; row++ {
   513  				value := sqlResult.Values[row]
   514  
   515  				if pagenatedCollections[row] == nil {
   516  					// create a new
   517  					pagenatedCollections[row] = &types.IRODSCollection{
   518  						ID:         -1,
   519  						Path:       "",
   520  						Name:       "",
   521  						Owner:      "",
   522  						CreateTime: time.Time{},
   523  						ModifyTime: time.Time{},
   524  					}
   525  				}
   526  
   527  				switch sqlResult.AttributeIndex {
   528  				case int(common.ICAT_COLUMN_COLL_ID):
   529  					cID, err := strconv.ParseInt(value, 10, 64)
   530  					if err != nil {
   531  						return nil, xerrors.Errorf("failed to parse collection id '%s': %w", value, err)
   532  					}
   533  					pagenatedCollections[row].ID = cID
   534  				case int(common.ICAT_COLUMN_COLL_NAME):
   535  					pagenatedCollections[row].Path = value
   536  					pagenatedCollections[row].Name = util.GetIRODSPathFileName(value)
   537  				case int(common.ICAT_COLUMN_COLL_OWNER_NAME):
   538  					pagenatedCollections[row].Owner = value
   539  				case int(common.ICAT_COLUMN_COLL_CREATE_TIME):
   540  					cT, err := util.GetIRODSDateTime(value)
   541  					if err != nil {
   542  						return nil, xerrors.Errorf("failed to parse create time '%s': %w", value, err)
   543  					}
   544  					pagenatedCollections[row].CreateTime = cT
   545  				case int(common.ICAT_COLUMN_COLL_MODIFY_TIME):
   546  					mT, err := util.GetIRODSDateTime(value)
   547  					if err != nil {
   548  						return nil, xerrors.Errorf("failed to parse modify time '%s': %w", value, err)
   549  					}
   550  					pagenatedCollections[row].ModifyTime = mT
   551  				default:
   552  					// ignore
   553  				}
   554  			}
   555  		}
   556  
   557  		collections = append(collections, pagenatedCollections...)
   558  
   559  		continueIndex = queryResult.ContinueIndex
   560  		if continueIndex == 0 {
   561  			continueQuery = false
   562  		}
   563  	}
   564  
   565  	return collections, nil
   566  }
   567  
   568  // CreateCollection creates a collection for the path
   569  func CreateCollection(conn *connection.IRODSConnection, path string, recurse bool) error {
   570  	if conn == nil || !conn.IsConnected() {
   571  		return xerrors.Errorf("connection is nil or disconnected")
   572  	}
   573  
   574  	metrics := conn.GetMetrics()
   575  	if metrics != nil {
   576  		metrics.IncreaseCounterForCollectionCreate(1)
   577  	}
   578  
   579  	// lock the connection
   580  	conn.Lock()
   581  	defer conn.Unlock()
   582  
   583  	request := message.NewIRODSMessageMakeCollectionRequest(path, recurse)
   584  	response := message.IRODSMessageMakeCollectionResponse{}
   585  	err := conn.RequestAndCheck(request, &response, nil)
   586  	if err != nil {
   587  		return xerrors.Errorf("received create collection error: %w", err)
   588  	}
   589  	return nil
   590  }
   591  
   592  // DeleteCollection deletes a collection for the path
   593  func DeleteCollection(conn *connection.IRODSConnection, path string, recurse bool, force bool) error {
   594  	if conn == nil || !conn.IsConnected() {
   595  		return xerrors.Errorf("connection is nil or disconnected")
   596  	}
   597  
   598  	metrics := conn.GetMetrics()
   599  	if metrics != nil {
   600  		metrics.IncreaseCounterForCollectionDelete(1)
   601  	}
   602  
   603  	// lock the connection
   604  	conn.Lock()
   605  	defer conn.Unlock()
   606  
   607  	request := message.NewIRODSMessageRemoveCollectionRequest(path, recurse, force)
   608  	response := message.IRODSMessageRemoveCollectionResponse{}
   609  	err := conn.RequestAndCheck(request, &response, nil)
   610  	if err != nil {
   611  		if types.GetIRODSErrorCode(err) == common.CAT_NO_ROWS_FOUND {
   612  			return xerrors.Errorf("failed to find the collection for path %s: %w", path, types.NewFileNotFoundError(path))
   613  		} else if types.GetIRODSErrorCode(err) == common.CAT_COLLECTION_NOT_EMPTY {
   614  			return xerrors.Errorf("the collection for path %s is empty: %w", path, types.NewCollectionNotEmptyError(path))
   615  		}
   616  
   617  		return xerrors.Errorf("received delete collection error: %w", err)
   618  	}
   619  
   620  	for response.Result == int(common.SYS_SVR_TO_CLI_COLL_STAT) {
   621  		// pack length - Big Endian
   622  		replyBuffer := make([]byte, 4)
   623  		binary.BigEndian.PutUint32(replyBuffer, uint32(common.SYS_CLI_TO_SVR_COLL_STAT_REPLY))
   624  
   625  		err = conn.Send(replyBuffer, 4)
   626  		if err != nil {
   627  			return xerrors.Errorf("failed to reply to a collection deletion response message: %w", err)
   628  		}
   629  
   630  		responseMessageReply, err := conn.ReadMessage(nil)
   631  		if err != nil {
   632  			return xerrors.Errorf("failed to receive a collection deletion response message: %w", err)
   633  		}
   634  
   635  		response.FromMessage(responseMessageReply)
   636  	}
   637  
   638  	return nil
   639  }
   640  
   641  // MoveCollection moves a collection for the path to another path
   642  func MoveCollection(conn *connection.IRODSConnection, srcPath string, destPath string) error {
   643  	if conn == nil || !conn.IsConnected() {
   644  		return xerrors.Errorf("connection is nil or disconnected")
   645  	}
   646  
   647  	metrics := conn.GetMetrics()
   648  	if metrics != nil {
   649  		metrics.IncreaseCounterForCollectionRename(1)
   650  	}
   651  
   652  	// lock the connection
   653  	conn.Lock()
   654  	defer conn.Unlock()
   655  
   656  	request := message.NewIRODSMessageMoveCollectionRequest(srcPath, destPath)
   657  	response := message.IRODSMessageMoveCollectionResponse{}
   658  	err := conn.RequestAndCheck(request, &response, nil)
   659  	if err != nil {
   660  		if types.GetIRODSErrorCode(err) == common.CAT_NO_ROWS_FOUND {
   661  			return xerrors.Errorf("failed to find the collection for path %s: %w", srcPath, types.NewFileNotFoundError(srcPath))
   662  		}
   663  		return xerrors.Errorf("received move collection error: %w", err)
   664  	}
   665  	return nil
   666  }
   667  
   668  // AddCollectionMeta sets metadata of a data object for the path to the given key values.
   669  // metadata.AVUID is ignored
   670  func AddCollectionMeta(conn *connection.IRODSConnection, path string, metadata *types.IRODSMeta) error {
   671  	if conn == nil || !conn.IsConnected() {
   672  		return xerrors.Errorf("connection is nil or disconnected")
   673  	}
   674  
   675  	metrics := conn.GetMetrics()
   676  	if metrics != nil {
   677  		metrics.IncreaseCounterForMetadataCreate(1)
   678  	}
   679  
   680  	// lock the connection
   681  	conn.Lock()
   682  	defer conn.Unlock()
   683  
   684  	request := message.NewIRODSMessageAddMetadataRequest(types.IRODSCollectionMetaItemType, path, metadata)
   685  	response := message.IRODSMessageModifyMetadataResponse{}
   686  	err := conn.RequestAndCheck(request, &response, nil)
   687  	if err != nil {
   688  		return xerrors.Errorf("received add collection meta error: %w", err)
   689  	}
   690  	return nil
   691  }
   692  
   693  // DeleteCollectionMeta sets metadata of a data object for the path to the given key values.
   694  // The metadata AVU is selected on basis of AVUID if it is supplied, otherwise on basis of Name, Value and Units.
   695  func DeleteCollectionMeta(conn *connection.IRODSConnection, path string, metadata *types.IRODSMeta) error {
   696  	if conn == nil || !conn.IsConnected() {
   697  		return xerrors.Errorf("connection is nil or disconnected")
   698  	}
   699  
   700  	metrics := conn.GetMetrics()
   701  	if metrics != nil {
   702  		metrics.IncreaseCounterForMetadataDelete(1)
   703  	}
   704  
   705  	// lock the connection
   706  	conn.Lock()
   707  	defer conn.Unlock()
   708  
   709  	var request *message.IRODSMessageModifyMetadataRequest
   710  
   711  	if metadata.AVUID != 0 {
   712  		request = message.NewIRODSMessageRemoveMetadataByIDRequest(types.IRODSCollectionMetaItemType, path, metadata.AVUID)
   713  	} else if metadata.Units == "" && metadata.Value == "" {
   714  		request = message.NewIRODSMessageRemoveMetadataWildcardRequest(types.IRODSCollectionMetaItemType, path, metadata.Name)
   715  	} else {
   716  		request = message.NewIRODSMessageRemoveMetadataRequest(types.IRODSCollectionMetaItemType, path, metadata)
   717  	}
   718  
   719  	response := message.IRODSMessageModifyMetadataResponse{}
   720  	err := conn.RequestAndCheck(request, &response, nil)
   721  	if err != nil {
   722  		if types.GetIRODSErrorCode(err) == common.CAT_NO_ROWS_FOUND {
   723  			return xerrors.Errorf("failed to find the collection for path %s: %w", path, types.NewFileNotFoundError(path))
   724  		}
   725  		return xerrors.Errorf("received delete collection meta error: %w", err)
   726  	}
   727  	return nil
   728  }
   729  
   730  // SearchCollectionsByMeta searches collections by metadata
   731  func SearchCollectionsByMeta(conn *connection.IRODSConnection, metaName string, metaValue string) ([]*types.IRODSCollection, error) {
   732  	if conn == nil || !conn.IsConnected() {
   733  		return nil, xerrors.Errorf("connection is nil or disconnected")
   734  	}
   735  
   736  	metrics := conn.GetMetrics()
   737  	if metrics != nil {
   738  		metrics.IncreaseCounterForSearch(1)
   739  	}
   740  
   741  	// lock the connection
   742  	conn.Lock()
   743  	defer conn.Unlock()
   744  
   745  	collections := []*types.IRODSCollection{}
   746  
   747  	continueQuery := true
   748  	continueIndex := 0
   749  	for continueQuery {
   750  		query := message.NewIRODSMessageQueryRequest(common.MaxQueryRows, continueIndex, 0, 0)
   751  		query.AddSelect(common.ICAT_COLUMN_COLL_ID, 1)
   752  		query.AddSelect(common.ICAT_COLUMN_COLL_NAME, 1)
   753  		query.AddSelect(common.ICAT_COLUMN_COLL_OWNER_NAME, 1)
   754  		query.AddSelect(common.ICAT_COLUMN_COLL_CREATE_TIME, 1)
   755  		query.AddSelect(common.ICAT_COLUMN_COLL_MODIFY_TIME, 1)
   756  
   757  		metaNameCondVal := fmt.Sprintf("= '%s'", metaName)
   758  		query.AddCondition(common.ICAT_COLUMN_META_COLL_ATTR_NAME, metaNameCondVal)
   759  		metaValueCondVal := fmt.Sprintf("= '%s'", metaValue)
   760  		query.AddCondition(common.ICAT_COLUMN_META_COLL_ATTR_VALUE, metaValueCondVal)
   761  
   762  		queryResult := message.IRODSMessageQueryResponse{}
   763  		err := conn.Request(query, &queryResult, nil)
   764  		if err != nil {
   765  			return nil, xerrors.Errorf("failed to receive a collection query result message: %w", err)
   766  		}
   767  
   768  		err = queryResult.CheckError()
   769  		if err != nil {
   770  			if types.GetIRODSErrorCode(err) == common.CAT_NO_ROWS_FOUND {
   771  				// empty
   772  				break
   773  			}
   774  			return nil, xerrors.Errorf("received collection query error: %w", err)
   775  		}
   776  
   777  		if queryResult.RowCount == 0 {
   778  			break
   779  		}
   780  
   781  		if queryResult.AttributeCount > len(queryResult.SQLResult) {
   782  			return nil, xerrors.Errorf("failed to receive collection attributes - requires %d, but received %d attributes", queryResult.AttributeCount, len(queryResult.SQLResult))
   783  		}
   784  
   785  		pagenatedCollections := make([]*types.IRODSCollection, queryResult.RowCount)
   786  
   787  		for attr := 0; attr < queryResult.AttributeCount; attr++ {
   788  			sqlResult := queryResult.SQLResult[attr]
   789  			if len(sqlResult.Values) != queryResult.RowCount {
   790  				return nil, xerrors.Errorf("failed to receive collection rows - requires %d, but received %d attributes", queryResult.RowCount, len(sqlResult.Values))
   791  			}
   792  
   793  			for row := 0; row < queryResult.RowCount; row++ {
   794  				value := sqlResult.Values[row]
   795  
   796  				if pagenatedCollections[row] == nil {
   797  					// create a new
   798  					pagenatedCollections[row] = &types.IRODSCollection{
   799  						ID:         -1,
   800  						Path:       "",
   801  						Name:       "",
   802  						Owner:      "",
   803  						CreateTime: time.Time{},
   804  						ModifyTime: time.Time{},
   805  					}
   806  				}
   807  
   808  				switch sqlResult.AttributeIndex {
   809  				case int(common.ICAT_COLUMN_COLL_ID):
   810  					cID, err := strconv.ParseInt(value, 10, 64)
   811  					if err != nil {
   812  						return nil, xerrors.Errorf("failed to parse collection id '%s': %w", value, err)
   813  					}
   814  					pagenatedCollections[row].ID = cID
   815  				case int(common.ICAT_COLUMN_COLL_NAME):
   816  					pagenatedCollections[row].Path = value
   817  					pagenatedCollections[row].Name = util.GetIRODSPathFileName(value)
   818  				case int(common.ICAT_COLUMN_COLL_OWNER_NAME):
   819  					pagenatedCollections[row].Owner = value
   820  				case int(common.ICAT_COLUMN_COLL_CREATE_TIME):
   821  					cT, err := util.GetIRODSDateTime(value)
   822  					if err != nil {
   823  						return nil, xerrors.Errorf("failed to parse create time '%s': %w", value, err)
   824  					}
   825  					pagenatedCollections[row].CreateTime = cT
   826  				case int(common.ICAT_COLUMN_COLL_MODIFY_TIME):
   827  					mT, err := util.GetIRODSDateTime(value)
   828  					if err != nil {
   829  						return nil, xerrors.Errorf("failed to parse modify time '%s': %w", value, err)
   830  					}
   831  					pagenatedCollections[row].ModifyTime = mT
   832  				default:
   833  					// ignore
   834  				}
   835  			}
   836  		}
   837  
   838  		collections = append(collections, pagenatedCollections...)
   839  
   840  		continueIndex = queryResult.ContinueIndex
   841  		if continueIndex == 0 {
   842  			continueQuery = false
   843  		}
   844  	}
   845  
   846  	return collections, nil
   847  }
   848  
   849  // SearchCollectionsByMetaWildcard searches collections by metadata
   850  // Caution: This is a very slow operation
   851  func SearchCollectionsByMetaWildcard(conn *connection.IRODSConnection, metaName string, metaValue string) ([]*types.IRODSCollection, error) {
   852  	if conn == nil || !conn.IsConnected() {
   853  		return nil, xerrors.Errorf("connection is nil or disconnected")
   854  	}
   855  
   856  	metrics := conn.GetMetrics()
   857  	if metrics != nil {
   858  		metrics.IncreaseCounterForSearch(1)
   859  	}
   860  
   861  	// lock the connection
   862  	conn.Lock()
   863  	defer conn.Unlock()
   864  
   865  	collections := []*types.IRODSCollection{}
   866  
   867  	continueQuery := true
   868  	continueIndex := 0
   869  	for continueQuery {
   870  		query := message.NewIRODSMessageQueryRequest(common.MaxQueryRows, continueIndex, 0, 0)
   871  		query.AddSelect(common.ICAT_COLUMN_COLL_ID, 1)
   872  		query.AddSelect(common.ICAT_COLUMN_COLL_NAME, 1)
   873  		query.AddSelect(common.ICAT_COLUMN_COLL_OWNER_NAME, 1)
   874  		query.AddSelect(common.ICAT_COLUMN_COLL_CREATE_TIME, 1)
   875  		query.AddSelect(common.ICAT_COLUMN_COLL_MODIFY_TIME, 1)
   876  
   877  		metaNameCondVal := fmt.Sprintf("= '%s'", metaName)
   878  		query.AddCondition(common.ICAT_COLUMN_META_COLL_ATTR_NAME, metaNameCondVal)
   879  		metaValueCondVal := fmt.Sprintf("like '%s'", metaValue)
   880  		query.AddCondition(common.ICAT_COLUMN_META_COLL_ATTR_VALUE, metaValueCondVal)
   881  
   882  		queryResult := message.IRODSMessageQueryResponse{}
   883  		err := conn.Request(query, &queryResult, nil)
   884  		if err != nil {
   885  			return nil, xerrors.Errorf("failed to receive a collection query result message: %w", err)
   886  		}
   887  
   888  		err = queryResult.CheckError()
   889  		if err != nil {
   890  			if types.GetIRODSErrorCode(err) == common.CAT_NO_ROWS_FOUND {
   891  				// empty
   892  				break
   893  			}
   894  			return nil, xerrors.Errorf("received collection query error: %w", err)
   895  		}
   896  
   897  		if queryResult.RowCount == 0 {
   898  			break
   899  		}
   900  
   901  		if queryResult.AttributeCount > len(queryResult.SQLResult) {
   902  			return nil, xerrors.Errorf("failed to receive collection attributes - requires %d, but received %d attributes", queryResult.AttributeCount, len(queryResult.SQLResult))
   903  		}
   904  
   905  		pagenatedCollections := make([]*types.IRODSCollection, queryResult.RowCount)
   906  
   907  		for attr := 0; attr < queryResult.AttributeCount; attr++ {
   908  			sqlResult := queryResult.SQLResult[attr]
   909  			if len(sqlResult.Values) != queryResult.RowCount {
   910  				return nil, xerrors.Errorf("failed to receive data object rows - requires %d, but received %d attributes", queryResult.RowCount, len(sqlResult.Values))
   911  			}
   912  
   913  			for attr := 0; attr < queryResult.AttributeCount; attr++ {
   914  				sqlResult := queryResult.SQLResult[attr]
   915  				if len(sqlResult.Values) != queryResult.RowCount {
   916  					return nil, xerrors.Errorf("failed to receive collection rows - requires %d, but received %d attributes", queryResult.RowCount, len(sqlResult.Values))
   917  				}
   918  
   919  				for row := 0; row < queryResult.RowCount; row++ {
   920  					value := sqlResult.Values[row]
   921  
   922  					if pagenatedCollections[row] == nil {
   923  						// create a new
   924  						pagenatedCollections[row] = &types.IRODSCollection{
   925  							ID:         -1,
   926  							Path:       "",
   927  							Name:       "",
   928  							Owner:      "",
   929  							CreateTime: time.Time{},
   930  							ModifyTime: time.Time{},
   931  						}
   932  					}
   933  
   934  					switch sqlResult.AttributeIndex {
   935  					case int(common.ICAT_COLUMN_COLL_ID):
   936  						cID, err := strconv.ParseInt(value, 10, 64)
   937  						if err != nil {
   938  							return nil, xerrors.Errorf("failed to parse collection id '%s': %w", value, err)
   939  						}
   940  						pagenatedCollections[row].ID = cID
   941  					case int(common.ICAT_COLUMN_COLL_NAME):
   942  						pagenatedCollections[row].Path = value
   943  						pagenatedCollections[row].Name = util.GetIRODSPathFileName(value)
   944  					case int(common.ICAT_COLUMN_COLL_OWNER_NAME):
   945  						pagenatedCollections[row].Owner = value
   946  					case int(common.ICAT_COLUMN_COLL_CREATE_TIME):
   947  						cT, err := util.GetIRODSDateTime(value)
   948  						if err != nil {
   949  							return nil, xerrors.Errorf("failed to parse create time '%s': %w", value, err)
   950  						}
   951  						pagenatedCollections[row].CreateTime = cT
   952  					case int(common.ICAT_COLUMN_COLL_MODIFY_TIME):
   953  						mT, err := util.GetIRODSDateTime(value)
   954  						if err != nil {
   955  							return nil, xerrors.Errorf("failed to parse modify time '%s': %w", value, err)
   956  						}
   957  						pagenatedCollections[row].ModifyTime = mT
   958  					default:
   959  						// ignore
   960  					}
   961  				}
   962  			}
   963  		}
   964  
   965  		collections = append(collections, pagenatedCollections...)
   966  
   967  		continueIndex = queryResult.ContinueIndex
   968  		if continueIndex == 0 {
   969  			continueQuery = false
   970  		}
   971  	}
   972  
   973  	return collections, nil
   974  }
   975  
   976  // ChangeCollectionAccess changes access on a collection.
   977  func ChangeCollectionAccess(conn *connection.IRODSConnection, path string, access types.IRODSAccessLevelType, userName, zoneName string, recursive bool, adminFlag bool) error {
   978  	if conn == nil || !conn.IsConnected() {
   979  		return xerrors.Errorf("connection is nil or disconnected")
   980  	}
   981  
   982  	metrics := conn.GetMetrics()
   983  	if metrics != nil {
   984  		metrics.IncreaseCounterForAccessUpdate(1)
   985  	}
   986  
   987  	// lock the connection
   988  	conn.Lock()
   989  	defer conn.Unlock()
   990  
   991  	request := message.NewIRODSMessageModifyAccessRequest(access.ChmodString(), userName, zoneName, path, recursive, adminFlag)
   992  	response := message.IRODSMessageModifyAccessResponse{}
   993  	err := conn.RequestAndCheck(request, &response, nil)
   994  	if err != nil {
   995  		if types.GetIRODSErrorCode(err) == common.CAT_NO_ROWS_FOUND {
   996  			return xerrors.Errorf("failed to find the collection for path %s: %w", path, types.NewFileNotFoundError(path))
   997  		}
   998  		return xerrors.Errorf("received change collection access error: %w", err)
   999  	}
  1000  	return nil
  1001  }
  1002  
  1003  // SetAccessInherit sets the inherit bit on a collection.
  1004  func SetAccessInherit(conn *connection.IRODSConnection, path string, inherit, recursive, adminFlag bool) error {
  1005  	if conn == nil || !conn.IsConnected() {
  1006  		return xerrors.Errorf("connection is nil or disconnected")
  1007  	}
  1008  
  1009  	metrics := conn.GetMetrics()
  1010  	if metrics != nil {
  1011  		metrics.IncreaseCounterForAccessUpdate(1)
  1012  	}
  1013  
  1014  	// lock the connection
  1015  	conn.Lock()
  1016  	defer conn.Unlock()
  1017  
  1018  	inheritStr := "inherit"
  1019  
  1020  	if !inherit {
  1021  		inheritStr = "noinherit"
  1022  	}
  1023  
  1024  	request := message.NewIRODSMessageModifyAccessRequest(inheritStr, "", "", path, recursive, adminFlag)
  1025  	response := message.IRODSMessageModifyAccessResponse{}
  1026  	err := conn.RequestAndCheck(request, &response, nil)
  1027  	if err != nil {
  1028  		if types.GetIRODSErrorCode(err) == common.CAT_NO_ROWS_FOUND {
  1029  			return xerrors.Errorf("failed to find the collection for path %s: %w", path, types.NewFileNotFoundError(path))
  1030  		}
  1031  		return xerrors.Errorf("received set access inherit error: %w", err)
  1032  	}
  1033  	return nil
  1034  }