58 static void bucket_test_wizard_clear(
void)
66 static int bucket_test_wizard_create(
const struct ast_sorcery *sorcery,
void *data,
void *
object)
77 static int bucket_test_wizard_update(
const struct ast_sorcery *sorcery,
void *data,
void *
object)
88 static void *bucket_test_wizard_retrieve_id(
const struct ast_sorcery *sorcery,
void *data,
const char *type,
91 if (!strcmp(type,
"bucket")) {
93 }
else if (!strcmp(type,
"file")) {
100 static int bucket_test_wizard_delete(
const struct ast_sorcery *sorcery,
void *data,
void *
object)
111 static int bucket_test_wizard_is_stale(
const struct ast_sorcery *sorcery,
void *data,
void *
object)
118 .create = bucket_test_wizard_create,
119 .retrieve_id = bucket_test_wizard_retrieve_id,
120 .delete = bucket_test_wizard_delete,
121 .is_stale = bucket_test_wizard_is_stale,
126 .create = bucket_test_wizard_create,
127 .update = bucket_test_wizard_update,
128 .retrieve_id = bucket_test_wizard_retrieve_id,
129 .delete = bucket_test_wizard_delete,
130 .is_stale = bucket_test_wizard_is_stale,
137 info->name =
"bucket_scheme_register_unregister";
138 info->category =
"/main/bucket/";
139 info->summary =
"bucket scheme registration/unregistration unit test";
141 "Test registration and unregistration of bucket scheme";
142 return AST_TEST_NOT_RUN;
148 ast_test_status_update(
test,
"Successfully registered a Bucket scheme without name or wizards\n");
149 return AST_TEST_FAIL;
153 ast_test_status_update(
test,
"Successfully registered a Bucket scheme twice\n");
154 return AST_TEST_FAIL;
157 return AST_TEST_PASS;
166 info->name =
"bucket_alloc";
167 info->category =
"/main/bucket/";
168 info->summary =
"bucket allocation unit test";
170 "Test allocation of buckets";
171 return AST_TEST_NOT_RUN;
177 ast_test_status_update(
test,
"Allocated a bucket with no URI provided\n");
178 return AST_TEST_FAIL;
182 ast_test_status_update(
test,
"Failed to allocate bucket\n");
183 return AST_TEST_FAIL;
187 ast_test_status_update(
test,
"URI within allocated bucket is '%s' and should be test:///tmp/bob\n",
189 return AST_TEST_FAIL;
192 if (strcmp(bucket->scheme,
"test")) {
193 ast_test_status_update(
test,
"Scheme within allocated bucket is '%s' and should be test\n",
195 return AST_TEST_FAIL;
198 return AST_TEST_PASS;
207 info->name =
"bucket_create";
208 info->category =
"/main/bucket/";
209 info->summary =
"bucket creation unit test";
211 "Test creation of buckets";
212 return AST_TEST_NOT_RUN;
218 ast_test_status_update(
test,
"Failed to allocate bucket\n");
219 return AST_TEST_FAIL;
222 bucket_test_wizard_clear();
225 ast_test_status_update(
test,
"Failed to create bucket with URI '%s'\n",
227 return AST_TEST_FAIL;
231 ast_test_status_update(
test,
"Bucket creation returned success but scheme implementation never actually created it\n");
232 return AST_TEST_FAIL;
236 ast_test_status_update(
test,
"Successfully created bucket with URI '%s' twice\n",
238 return AST_TEST_FAIL;
241 return AST_TEST_PASS;
251 info->name =
"bucket_clone";
252 info->category =
"/main/bucket/";
253 info->summary =
"bucket clone unit test";
255 "Test cloning a bucket";
256 return AST_TEST_NOT_RUN;
262 ast_test_status_update(
test,
"Failed to allocate bucket\n");
263 return AST_TEST_FAIL;
266 bucket_test_wizard_clear();
269 ast_test_status_update(
test,
"Failed to create bucket with URI '%s'\n",
271 return AST_TEST_FAIL;
276 ast_test_status_update(
test,
"Failed to clone bucket with URI '%s'\n",
278 return AST_TEST_FAIL;
282 ast_test_validate(
test, bucket->scheme_impl == clone->scheme_impl);
283 ast_test_validate(
test, strcmp(bucket->scheme, clone->scheme) == 0);
285 return AST_TEST_PASS;
294 info->name =
"bucket_delete";
295 info->category =
"/main/bucket/";
296 info->summary =
"bucket deletion unit test";
298 "Test deletion of buckets";
299 return AST_TEST_NOT_RUN;
305 ast_test_status_update(
test,
"Failed to allocate bucket\n");
306 return AST_TEST_FAIL;
309 bucket_test_wizard_clear();
312 ast_test_status_update(
test,
"Failed to delete bucket with URI '%s'\n",
314 return AST_TEST_FAIL;
318 ast_test_status_update(
test,
"Bucket deletion returned success but scheme implementation never actually deleted it\n");
319 return AST_TEST_FAIL;
323 ast_test_status_update(
test,
"Successfully deleted bucket with URI '%s' twice\n",
325 return AST_TEST_FAIL;
328 return AST_TEST_PASS;
337 info->name =
"bucket_is_stale";
338 info->category =
"/main/bucket/";
339 info->summary =
"bucket staleness unit test";
341 "Test if staleness of a bucket is reported correctly";
342 return AST_TEST_NOT_RUN;
348 ast_test_status_update(
test,
"Failed to allocate bucket\n");
349 return AST_TEST_FAIL;
352 bucket_test_wizard_clear();
360 return AST_TEST_PASS;
371 info->name =
"bucket_json";
372 info->category =
"/main/bucket/";
373 info->summary =
"bucket json unit test";
375 "Test creation of JSON for a bucket";
376 return AST_TEST_NOT_RUN;
382 ast_test_status_update(
test,
"Failed to allocate bucket\n");
383 return AST_TEST_FAIL;
389 expected =
ast_json_pack(
"{s: s, s: s, s: [s], s: s, s: [s], s: s}",
390 "modified",
"0.000000",
"created",
"0.000000",
391 "buckets",
"test:///tmp/bob/joe",
393 "files",
"test:///tmp/bob/recording.wav",
394 "id",
"test:///tmp/bob");
396 ast_test_status_update(
test,
"Could not produce JSON for expected bucket value\n");
397 return AST_TEST_FAIL;
402 ast_test_status_update(
test,
"Could not produce JSON for a valid bucket\n");
403 return AST_TEST_FAIL;
407 ast_test_status_update(
test,
"Bucket JSON does not match expected output\n");
408 return AST_TEST_FAIL;
411 return AST_TEST_PASS;
420 info->name =
"bucket_retrieve";
421 info->category =
"/main/bucket/";
422 info->summary =
"bucket retrieval unit test";
424 "Test retrieval of buckets";
425 return AST_TEST_NOT_RUN;
431 ast_test_status_update(
test,
"Failed to retrieve known valid bucket\n");
432 return AST_TEST_FAIL;
435 return AST_TEST_PASS;
444 info->name =
"bucket_file_alloc";
445 info->category =
"/main/bucket/";
446 info->summary =
"bucket file allocation unit test";
448 "Test allocation of bucket files";
449 return AST_TEST_NOT_RUN;
455 ast_test_status_update(
test,
"Allocated a file with no URI provided\n");
456 return AST_TEST_FAIL;
460 ast_test_status_update(
test,
"Failed to allocate file\n");
461 return AST_TEST_FAIL;
464 if (ast_strlen_zero(file->path)) {
465 ast_test_status_update(
test,
"Expected temporary path in allocated file");
466 return AST_TEST_FAIL;
470 ast_test_status_update(
test,
"URI within allocated file is '%s' and should be test:///tmp/bob\n",
472 return AST_TEST_FAIL;
475 if (strcmp(file->scheme,
"test")) {
476 ast_test_status_update(
test,
"Scheme within allocated file is '%s' and should be test\n",
478 return AST_TEST_FAIL;
481 return AST_TEST_PASS;
490 info->name =
"bucket_file_create";
491 info->category =
"/main/bucket/";
492 info->summary =
"file creation unit test";
494 "Test creation of files";
495 return AST_TEST_NOT_RUN;
501 ast_test_status_update(
test,
"Failed to allocate file\n");
502 return AST_TEST_FAIL;
505 bucket_test_wizard_clear();
508 ast_test_status_update(
test,
"Failed to create file with URI '%s'\n",
510 return AST_TEST_FAIL;
514 ast_test_status_update(
test,
"Bucket file creation returned success but scheme implementation never actually created it\n");
515 return AST_TEST_FAIL;
519 ast_test_status_update(
test,
"Successfully created file with URI '%s' twice\n",
521 return AST_TEST_FAIL;
524 return AST_TEST_PASS;
534 info->name =
"bucket_file_clone";
535 info->category =
"/main/bucket/";
536 info->summary =
"file clone unit test";
538 "Test cloning a file";
539 return AST_TEST_NOT_RUN;
545 ast_test_status_update(
test,
"Failed to allocate file\n");
546 return AST_TEST_FAIL;
549 bucket_test_wizard_clear();
552 ast_test_status_update(
test,
"Failed to create file with URI '%s'\n",
554 return AST_TEST_FAIL;
560 ast_test_status_update(
test,
"Failed to clone file with URI '%s'\n",
562 return AST_TEST_FAIL;
566 ast_test_validate(
test, file->scheme_impl == clone->scheme_impl);
567 ast_test_validate(
test, strcmp(file->scheme, clone->scheme) == 0);
570 return AST_TEST_PASS;
578 struct stat old, new;
583 info->name =
"bucket_file_copy";
584 info->category =
"/main/bucket/";
585 info->summary =
"bucket file copying unit test";
587 "Test copying of bucket files";
588 return AST_TEST_NOT_RUN;
594 ast_test_status_update(
test,
"Failed to allocate file\n");
595 return AST_TEST_FAIL;
600 if (!(temporary = fopen(file->path,
"w"))) {
601 ast_test_status_update(
test,
"Failed to open temporary file '%s'\n", file->path);
602 return AST_TEST_FAIL;
605 fprintf(temporary,
"bob");
609 ast_test_status_update(
test,
"Failed to copy file '%s' to test:///tmp/bob2\n",
611 return AST_TEST_FAIL;
614 if (stat(file->path, &old)) {
615 ast_test_status_update(
test,
"Failed to retrieve information on old file '%s'\n", file->path);
616 return AST_TEST_FAIL;
619 if (stat(
copy->path, &
new)) {
620 ast_test_status_update(
test,
"Failed to retrieve information on copy file '%s'\n",
copy->path);
621 return AST_TEST_FAIL;
624 if (old.st_size !=
new.st_size) {
625 ast_test_status_update(
test,
"Copying of underlying temporary file failed\n");
626 return AST_TEST_FAIL;
630 ast_test_status_update(
test,
"Number of metadata entries does not match original\n");
631 return AST_TEST_FAIL;
636 ast_test_status_update(
test,
"Copy of file does not have expected metadata\n");
637 return AST_TEST_FAIL;
640 if (strcmp(metadata->value,
"joe")) {
641 ast_test_status_update(
test,
"Copy of file contains metadata for 'bob' but value is not what it should be\n");
642 return AST_TEST_FAIL;
645 return AST_TEST_PASS;
654 info->name =
"bucket_file_retrieve";
655 info->category =
"/main/bucket/";
656 info->summary =
"file retrieval unit test";
658 "Test retrieval of files";
659 return AST_TEST_NOT_RUN;
665 ast_test_status_update(
test,
"Failed to retrieve known valid file\n");
666 return AST_TEST_FAIL;
669 return AST_TEST_PASS;
678 info->name =
"bucket_file_update";
679 info->category =
"/main/bucket/";
680 info->summary =
"file updating unit test";
682 "Test updating of files";
683 return AST_TEST_NOT_RUN;
689 ast_test_status_update(
test,
"Failed to allocate file\n");
690 return AST_TEST_FAIL;
693 bucket_test_wizard_clear();
696 ast_test_status_update(
test,
"Failed to update file with URI '%s'\n",
698 return AST_TEST_FAIL;
702 ast_test_status_update(
test,
"Successfully returned file was updated, but it was not\n");
703 return AST_TEST_FAIL;
707 ast_test_status_update(
test,
"Successfully updated file with URI '%s' twice\n",
709 return AST_TEST_FAIL;
712 return AST_TEST_PASS;
721 info->name =
"bucket_file_delete";
722 info->category =
"/main/bucket/";
723 info->summary =
"file deletion unit test";
725 "Test deletion of files";
726 return AST_TEST_NOT_RUN;
732 ast_test_status_update(
test,
"Failed to allocate file\n");
733 return AST_TEST_FAIL;
736 bucket_test_wizard_clear();
739 ast_test_status_update(
test,
"Failed to delete file with URI '%s'\n",
741 return AST_TEST_FAIL;
745 ast_test_status_update(
test,
"Bucket file deletion returned success but scheme implementation never actually deleted it\n");
746 return AST_TEST_FAIL;
750 ast_test_status_update(
test,
"Successfully deleted file with URI '%s' twice\n",
752 return AST_TEST_FAIL;
755 return AST_TEST_PASS;
764 info->name =
"bucket_file_is_stale";
765 info->category =
"/main/bucket/";
766 info->summary =
"file staleness unit test";
768 "Test if staleness of a bucket file is reported correctly";
769 return AST_TEST_NOT_RUN;
775 ast_test_status_update(
test,
"Failed to allocate file\n");
776 return AST_TEST_FAIL;
779 bucket_test_wizard_clear();
787 return AST_TEST_PASS;
797 info->name =
"bucket_file_metadata_set";
798 info->category =
"/main/bucket/";
799 info->summary =
"file metadata setting unit test";
801 "Test setting of metadata on files";
802 return AST_TEST_NOT_RUN;
808 ast_test_status_update(
test,
"Failed to allocate file\n");
809 return AST_TEST_FAIL;
813 ast_test_status_update(
test,
"Newly allocated file has metadata count of '%d' when should be 0\n",
815 return AST_TEST_FAIL;
819 ast_test_status_update(
test,
"Failed to set metadata 'bob' to 'joe' on newly allocated file\n");
820 return AST_TEST_FAIL;
823 if (!(metadata = ao2_find(file->metadata,
"bob",
OBJ_KEY))) {
824 ast_test_status_update(
test,
"Failed to find set metadata 'bob' on newly allocated file\n");
825 return AST_TEST_FAIL;
828 if (strcmp(metadata->value,
"joe")) {
829 ast_test_status_update(
test,
"Metadata has value '%s' when should be 'joe'\n",
831 return AST_TEST_FAIL;
834 ao2_cleanup(metadata);
838 ast_test_status_update(
test,
"Failed to overwrite metadata 'bob' with new value 'fred'\n");
839 return AST_TEST_FAIL;
842 if (!(metadata = ao2_find(file->metadata,
"bob",
OBJ_KEY))) {
843 ast_test_status_update(
test,
"Failed to find overwritten metadata 'bob' on newly allocated file\n");
844 return AST_TEST_FAIL;
847 if (strcmp(metadata->value,
"fred")) {
848 ast_test_status_update(
test,
"Metadata has value '%s' when should be 'fred'\n",
850 return AST_TEST_FAIL;
853 return AST_TEST_PASS;
863 info->name =
"bucket_file_metadata_unset";
864 info->category =
"/main/bucket/";
865 info->summary =
"file metadata unsetting unit test";
867 "Test unsetting of metadata on files";
868 return AST_TEST_NOT_RUN;
874 ast_test_status_update(
test,
"Failed to allocate file\n");
875 return AST_TEST_FAIL;
879 ast_test_status_update(
test,
"Failed to set metadata 'bob' to 'joe' on newly allocated file\n");
880 return AST_TEST_FAIL;
884 ast_test_status_update(
test,
"Failed to unset metadata 'bob' on newly allocated file\n");
885 return AST_TEST_FAIL;
888 if ((metadata = ao2_find(file->metadata,
"bob",
OBJ_KEY))) {
889 ast_test_status_update(
test,
"Metadata 'bob' was unset, but can still be found\n");
890 return AST_TEST_FAIL;
893 return AST_TEST_PASS;
903 info->name =
"bucket_file_metadata_get";
904 info->category =
"/main/bucket/";
905 info->summary =
"file metadata getting unit test";
907 "Test getting of metadata on files";
908 return AST_TEST_NOT_RUN;
914 ast_test_status_update(
test,
"Failed to allocate file\n");
915 return AST_TEST_FAIL;
919 ast_test_status_update(
test,
"Failed to set metadata 'bob' to 'joe' on newly allocated file\n");
920 return AST_TEST_FAIL;
924 ast_test_status_update(
test,
"Failed to retrieve metadata 'bob' that was just set\n");
925 return AST_TEST_FAIL;
928 if (strcmp(metadata->value,
"joe")) {
929 ast_test_status_update(
test,
"Retrieved metadata value is '%s' while it should be 'joe'\n",
931 return AST_TEST_FAIL;
934 return AST_TEST_PASS;
945 info->name =
"bucket_file_json";
946 info->category =
"/main/bucket/";
947 info->summary =
"file json unit test";
949 "Test creation of JSON for a file";
950 return AST_TEST_NOT_RUN;
956 ast_test_status_update(
test,
"Failed to allocate bucket\n");
957 return AST_TEST_FAIL;
961 ast_test_status_update(
test,
"Failed to set metadata 'bob' to 'joe' on newly allocated file\n");
962 return AST_TEST_FAIL;
965 expected =
ast_json_pack(
"{s: s, s: s, s: s, s: s, s: {s :s}}",
966 "modified",
"0.000000",
"created",
"0.000000",
"scheme",
"test",
967 "id",
"test:///tmp/bob",
"metadata",
"bob",
"joe");
969 ast_test_status_update(
test,
"Could not produce JSON for expected bucket file value\n");
970 return AST_TEST_FAIL;
975 ast_test_status_update(
test,
"Could not produce JSON for a valid file\n");
976 return AST_TEST_FAIL;
980 ast_test_status_update(
test,
"Bucket file JSON does not match expected output\n");
981 return AST_TEST_FAIL;
984 return AST_TEST_PASS;
987 static int unload_module(
void)
989 AST_TEST_UNREGISTER(bucket_scheme_register);
991 AST_TEST_UNREGISTER(bucket_create);
992 AST_TEST_UNREGISTER(bucket_clone);
993 AST_TEST_UNREGISTER(bucket_delete);
994 AST_TEST_UNREGISTER(bucket_retrieve);
995 AST_TEST_UNREGISTER(bucket_json);
997 AST_TEST_UNREGISTER(bucket_file_create);
998 AST_TEST_UNREGISTER(bucket_file_clone);
999 AST_TEST_UNREGISTER(bucket_file_copy);
1000 AST_TEST_UNREGISTER(bucket_file_retrieve);
1001 AST_TEST_UNREGISTER(bucket_file_update);
1002 AST_TEST_UNREGISTER(bucket_file_delete);
1003 AST_TEST_UNREGISTER(bucket_file_metadata_set);
1004 AST_TEST_UNREGISTER(bucket_file_metadata_unset);
1005 AST_TEST_UNREGISTER(bucket_file_metadata_get);
1006 AST_TEST_UNREGISTER(bucket_file_json);
1010 static int load_module(
void)
1014 ast_log(LOG_ERROR,
"Failed to register Bucket test wizard scheme implementation\n");
1018 AST_TEST_REGISTER(bucket_scheme_register);
1020 AST_TEST_REGISTER(bucket_create);
1021 AST_TEST_REGISTER(bucket_clone);
1022 AST_TEST_REGISTER(bucket_delete);
1023 AST_TEST_REGISTER(bucket_retrieve);
1024 AST_TEST_REGISTER(bucket_is_stale);
1025 AST_TEST_REGISTER(bucket_json);
1027 AST_TEST_REGISTER(bucket_file_create);
1028 AST_TEST_REGISTER(bucket_file_clone);
1029 AST_TEST_REGISTER(bucket_file_copy);
1030 AST_TEST_REGISTER(bucket_file_retrieve);
1031 AST_TEST_REGISTER(bucket_file_update);
1032 AST_TEST_REGISTER(bucket_file_delete);
1033 AST_TEST_REGISTER(bucket_file_is_stale);
1034 AST_TEST_REGISTER(bucket_file_metadata_set);
1035 AST_TEST_REGISTER(bucket_file_metadata_unset);
1036 AST_TEST_REGISTER(bucket_file_metadata_get);
1037 AST_TEST_REGISTER(bucket_file_json);
struct ast_bucket_metadata * ast_bucket_file_metadata_get(struct ast_bucket_file *file, const char *name)
Retrieve a metadata attribute from a file.
struct ast_bucket * ast_bucket_alloc(const char *uri)
Allocate a new bucket.
int ast_bucket_create(struct ast_bucket *bucket)
Create a new bucket in backend storage.
Asterisk main include file. File version handling, generic pbx functions.
int ao2_container_count(struct ao2_container *c)
Returns the number of elements in a container.
struct ast_json * ast_json_pack(char const *format,...)
Helper for creating complex JSON values.
int ast_bucket_file_metadata_set(struct ast_bucket_file *file, const char *name, const char *value)
Set a metadata attribute on a file to a specific value.
struct ast_bucket * ast_bucket_clone(struct ast_bucket *bucket)
Clone a bucket.
void ast_json_unref(struct ast_json *value)
Decrease refcount on value. If refcount reaches zero, value is freed.
Bucket structure, contains other buckets and files.
struct ast_bucket_file * ast_bucket_file_retrieve(const char *uri)
Retrieve a bucket file.
Full structure for sorcery.
struct ast_bucket_file * ast_bucket_file_copy(struct ast_bucket_file *file, const char *uri)
Copy a bucket file to a new URI.
static int copy(char *infile, char *outfile)
Utility function to copy a file.
Generic File Format Support. Should be included by clients of the file handling routines. File service providers should instead include mod_format.h.
struct ast_bucket_file * ast_bucket_file_alloc(const char *uri)
Allocate a new bucket file.
const char * name
Name of the wizard.
unsigned int is_stale
Whether the object is stale or not.
unsigned int updated
Whether the object has been updated or not.
unsigned int created
Whether the object has been created or not.
static void * bucket_file_alloc(const char *name)
Allocator for bucket files.
Asterisk JSON abstraction layer.
struct ast_bucket_file * ast_bucket_file_clone(struct ast_bucket_file *file)
Clone a bucket file.
int ast_bucket_file_delete(struct ast_bucket_file *file)
Delete a bucket file from backend storage.
struct ast_json * ast_bucket_file_json(const struct ast_bucket_file *file)
Get a JSON representation of a bucket file.
int ast_bucket_file_is_stale(struct ast_bucket_file *file)
Retrieve whether or not the backing datastore views the bucket file as stale.
const char * ast_sorcery_object_get_id(const void *object)
Get the unique identifier of a sorcery object.
Bucket file structure, contains reference to file and information about it.
struct ast_bucket * ast_bucket_retrieve(const char *uri)
Retrieve information about a bucket.
int ast_bucket_delete(struct ast_bucket *bucket)
Delete a bucket from backend storage.
static struct bucket_test_state bucket_test_wizard_state
Global scope structure for testing bucket wizards.
Support for logging to various files, console and syslog Configuration in file logger.conf.
Module has failed to load, may be in an inconsistent state.
int ast_bucket_file_temporary_create(struct ast_bucket_file *file)
Common file snapshot creation callback for creating a temporary file.
Interface for a sorcery wizard.
Test state structure for scheme wizards.
int ast_bucket_file_update(struct ast_bucket_file *file)
Update an existing bucket file in backend storage.
int ast_json_equal(const struct ast_json *lhs, const struct ast_json *rhs)
Compare two JSON objects.
static void * bucket_alloc(const char *name)
Allocator for buckets.
#define ast_bucket_scheme_register(name, bucket, file, create_cb, destroy_cb)
Register support for a specific scheme.
int ast_bucket_file_metadata_unset(struct ast_bucket_file *file, const char *name)
Unset a specific metadata attribute on a file.
#define AST_TEST_DEFINE(hdr)
Abstract JSON element (object, array, string, int, ...).
struct ast_json * ast_bucket_json(const struct ast_bucket *bucket)
Get a JSON representation of a bucket.
void ast_bucket_file_temporary_destroy(struct ast_bucket_file *file)
Common file snapshot destruction callback for deleting a temporary file.
#define ASTERISK_GPL_KEY
The text the key() function should return.
int ast_bucket_is_stale(struct ast_bucket *bucket)
Retrieve whether or not the backing datastore views the bucket as stale.
Asterisk module definitions.
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
unsigned int deleted
Whether the object has been deleted or not.
int ast_bucket_file_create(struct ast_bucket_file *file)
Create a new bucket file in backend storage.
int ast_str_container_add(struct ao2_container *str_container, const char *add)
Adds a string to a string container allocated by ast_str_container_alloc.