2
2
3
3
namespace tool_objectfs \local \tag ;
4
4
5
- use stored_file ;
5
+ use coding_exception ;
6
+ use core \lock \lock_config ;
7
+ use Exception ;
8
+ use tool_objectfs \local \manager ;
9
+
10
+ defined ('MOODLE_INTERNAL ' ) || die ();
11
+ require_once ($ CFG ->dirroot . '/admin/tool/objectfs/lib.php ' );
6
12
7
13
class tag_manager {
8
14
15
+ public const SYNC_STATUS_NEEDS_SYNC = 0 ;
16
+
17
+ public const SYNC_STATUS_SYNC_NOT_REQUIRED = 1 ;
18
+
9
19
/**
10
20
* Returns an array of tag_source instances that are currently defined.
11
21
* @return array
@@ -23,106 +33,162 @@ public static function get_defined_tag_sources(): array {
23
33
* @return bool
24
34
*/
25
35
public static function is_tagging_enabled (): bool {
26
- return false ; // TODO config.
36
+ $ enabledinconfig = !empty (get_config ('tool_objectfs ' , 'taggingenabled ' ));
37
+ $ client = manager::get_client (manager::get_objectfs_config ());
38
+ $ supportedbyfs = $ client ->supports_object_tagging ();
39
+
40
+ return $ enabledinconfig && $ supportedbyfs ;
27
41
}
28
42
29
43
/**
30
- * Gathers the tags for a given content hash
44
+ * Gathers the tag values for a given content hash
31
45
* @param string $contenthash
32
46
* @return array array of key=>value pairs, the tags for the given file.
33
47
*/
34
- private function gather_tags (string $ contenthash ): array {
48
+ private static function gather_tags (string $ contenthash ): array {
35
49
$ tags = [];
36
- foreach (self ::get_defined_tag_sources () as $ source ) {
37
- $ tags [$ source ->get_identifer ()] = $ source ->get_value_for_contenthash ($ contenthash );
50
+ foreach (self ::get_defined_tag_sources () as $ source ) {
51
+ $ val = $ source ->get_value_for_contenthash ($ contenthash );
52
+
53
+ // Null means not set for this object.
54
+ if (is_null ($ val )) {
55
+ continue ;
56
+ }
57
+
58
+ $ tags [$ source ->get_identifer ()] = $ val ;
38
59
}
39
60
return $ tags ;
40
61
}
41
62
42
63
/**
43
64
* Returns the tags stored locally in the DB for the given file contenthash
44
- * @param string $contenthash
65
+ * @param int $objectid
45
66
* @return array array of key=>value pairs, the tags for the given file that are stored in the database.
46
67
*/
47
- private function query_local_tags (string $ contenthash ): array {
48
- // TODO.
49
- return [];
50
- }
68
+ public static function query_local_tags (string $ contenthash ): array {
69
+ global $ DB ;
70
+ $ records = $ DB ->get_records ('tool_objectfs_object_tags ' , ['contenthash ' => $ contenthash ], '' , 'tagkey,tagvalue ' );
51
71
52
- /**
53
- * Returns the tags attached to an object in the external storage.
54
- * @param string $contenthash
55
- * @return array array of key=>value pairs, the tags for the given file that are attached to the external object.
56
- */
57
- private function query_external_tags (string $ contenthash ): array {
58
- // TODO.
59
- return [];
72
+ $ tags = [];
73
+ foreach ($ records as $ record ) {
74
+ $ tags [$ record ->tagkey ] = $ record ->tagvalue ;
75
+ }
76
+ return $ tags ;
60
77
}
61
78
62
79
/**
63
80
* Synchronises the tags for a given file.
64
81
*
65
82
* @param string $contenthash file to update tags for
66
- * @param bool $force By default, will not update stored tags unless the gathered tags differ from the locally stored tags.
67
- * Setting this to true will force it to always update external object tags.
83
+ * @param bool $force By default, will not update stored tags unless the gathered tags differ from the locally stored tags. If this is true, it will always store the newest tags.
68
84
*/
69
- public function sync_tags (string $ contenthash , bool $ force = false ) {
70
- // Gather local and compare against local db.
71
- $ gatheredtags = $ this -> gather_tags ($ contenthash );
72
- $ localtags = $ this -> query_local_tags ($ contenthash );
85
+ public static function update_tags_if_necessary (string $ contenthash , bool $ force = false ) {
86
+ // Gather the exact current and compare against local db.
87
+ $ gatheredtags = self :: gather_tags ($ contenthash );
88
+ $ localtags = self :: query_local_tags ($ contenthash );
73
89
74
90
// Update if forced to, or if what we just gathered is not the same as what is stored in the database.
75
- $ shouldupdate = $ force || $ this -> are_tags_different ($ gatheredtags , $ localtags );
91
+ $ shouldupdate = $ force || self :: are_tags_different ($ gatheredtags , $ localtags );
76
92
77
93
if ($ shouldupdate ) {
78
- $ this -> store_tags ($ contenthash , $ gatheredtags );
94
+ self :: store_tags ($ contenthash , $ gatheredtags );
79
95
}
80
96
}
81
97
82
98
/**
83
99
* Updates the tags for a given file. Updates both local db and external.
84
- * @param string $contenthash
100
+ * @param int $objectid
85
101
* @param array $tags
86
- * @param bool $updateexternal If the external tags should be updated.
87
- * Unless you are uploading the file (and uploading tags along with it), this should always be true to keep the local and external values in sync.
88
102
*/
89
- private function store_tags (string $ contenthash , array $ tags , bool $ updateexternal = true ) {
103
+ private static function store_tags (string $ contenthash , array $ tags ) {
104
+ global $ DB ;
105
+
106
+ // Get a lock, since we are modifying the tags in the DB.
107
+ $ lock = self ::get_object_tag_lock ($ contenthash );
108
+
109
+ if (!$ lock ) {
110
+ throw new coding_exception ("Unable to obtain lock for object " . $ contenthash ); // TODO different ex type ?
111
+ }
112
+
113
+ try {
114
+ // Purge any existing tags for this object.
115
+ $ DB ->delete_records ('tool_objectfs_object_tags ' , ['contenthash ' => $ contenthash ]);
116
+
117
+ // Record time in var, so that they all have the same time.
118
+ $ timemodified = time ();
119
+
120
+ // Store new records.
121
+ $ recordstostore = [];
122
+ foreach ($ tags as $ key => $ value ) {
123
+ $ recordstostore [] = [
124
+ 'contenthash ' => $ contenthash ,
125
+ 'tagkey ' => $ key ,
126
+ 'tagvalue ' => $ value ,
127
+ 'timemodified ' => $ timemodified
128
+ ];
129
+ }
130
+ $ DB ->insert_records ('tool_objectfs_object_tags ' , $ recordstostore );
131
+
132
+ // We always mark them needing sync after changing the values.
133
+ self ::mark_object_tag_status ($ contenthash , self ::SYNC_STATUS_NEEDS_SYNC );
134
+ } catch (Exception $ e ) {
135
+ // Failed - release lock and return false.
136
+ $ lock ->release ();
137
+ throw $ e ;
138
+ }
139
+ }
140
+
141
+ public static function get_objects_needing_sync (int $ limit ) {
90
142
global $ DB ;
91
- // TODO store locally.
92
- // TODO store externally.
143
+
144
+ // Find object records where the status is NEEDS_SYNC and is replicated.
145
+ [$ insql , $ inparams ] = $ DB ->get_in_or_equal ([OBJECT_LOCATION_DUPLICATED , OBJECT_LOCATION_EXTERNAL , OBJECT_LOCATION_ORPHANED ], SQL_PARAMS_NAMED );
146
+ $ inparams ['syncstatus ' ] = self ::SYNC_STATUS_NEEDS_SYNC ;
147
+
148
+ $ records = $ DB ->get_records_select ('tool_objectfs_objects ' , 'tagsyncstatus = :syncstatus AND location ' . $ insql , $ inparams , '' , 'contenthash ' , 0 , $ limit );
149
+ return array_column ($ records , 'contenthash ' );
93
150
}
94
151
95
- private function are_tags_different (array $ a , array $ b ) {
96
- return true ; // TODO compare the keys and values to ensure they are exactly the same!
152
+ public static function replicate_local_to_external_tags_for_object (string $ contenthash ) {
153
+ global $ DB ;
154
+
155
+ // Take lock, to ensure tags are not updated while we sync them.
156
+ $ lock = self ::get_object_tag_lock ($ contenthash );
157
+
158
+ if (!$ lock ) {
159
+ throw new coding_exception ("Unable to obtain lock for object " . $ contenthash ); // TODO different ex type ?
160
+ }
161
+
162
+ try {
163
+
164
+ // TODO refactor so all func use contenthash, no objectid.
165
+ // Then also probably remove from DB.
166
+ $ tags = self ::query_local_tags ($ contenthash );
167
+
168
+ // Get current client.
169
+ $ client = manager::get_client (manager::get_objectfs_config ());
170
+ $ client ->set_object_tags ($ contenthash , $ tags );
171
+
172
+ // Mark as synced, this stops it from re-syncing in the future.
173
+ self ::mark_object_tag_status ($ contenthash , self ::SYNC_STATUS_SYNC_NOT_REQUIRED );
174
+ } catch (Exception $ e ) {
175
+ $ lock ->release ();
176
+ throw $ e ;
177
+ }
178
+ }
179
+
180
+ private static function get_object_tag_lock (string $ contenthash , int $ timeout = 5 ) {
181
+ $ factory = lock_config::get_lock_factory ('tool_objectfs_object_tags ' );
182
+ return $ factory ->get_lock ($ contenthash , $ timeout );
97
183
}
98
184
99
- // private function query_tags_for_contenthash(string $contenthash): array {
100
- // // TODO query from the db, the current db values.
101
- // }
102
-
103
- // public function update_object_tags_by_contenthash(string $contenthash, bool $skipifsame = true) {
104
- // $shouldupdateexternal = true;
105
- // $tags = $this->build_tags_for_contenthash($contenthash);
106
-
107
-
108
- // foreach ($tags as $key => $value) {
109
- // $this->upsert_tag_value($contenthash, $key, $value);
110
- // }
111
- // $this->store_local_tags_for_contenthash($contenthash);
112
- // }
113
-
114
- // private function store_local_tags_for_contenthash(string $contenthash) {
115
- // }
116
-
117
- // private function upsert_tag_value(string $contenthash, string $tagkey, string $tagvalue) {
118
- // // TODO upsert this in the DB.
119
- // global $DB;
120
- // // TODO.
121
- // }
122
-
123
- // public function sync_external_tags_by_contenthash(string $contenthash) {
124
- // // TODO call external api to sync.
125
- // global $DB;
126
- // $tags =
127
- // }
185
+ private static function mark_object_tag_status (string $ contenthash , int $ status ) {
186
+ global $ DB ;
187
+ // TODO ensure valid status.
188
+ $ DB ->set_field ('tool_objectfs_objects ' , 'tagsyncstatus ' , $ status , ['contenthash ' => $ contenthash ]);
189
+ }
190
+
191
+ private static function are_tags_different (array $ a , array $ b ) {
192
+ return true ; // TODO compare the keys and values to ensure they are exactly the same!
193
+ }
128
194
}
0 commit comments