1010use Doctrine \DBAL \Exception ;
1111use OCP \DB \IPreparedStatement ;
1212use OCP \DB \QueryBuilder \IQueryBuilder ;
13+ use OCP \IAppConfig ;
14+ use OCP \ICache ;
15+ use OCP \ICacheFactory ;
1316use OCP \IDBConnection ;
1417use OCP \Server ;
1518use Psr \Log \LoggerInterface ;
19+ use function intdiv ;
1620
1721/**
1822 * Class AbstractMapping
@@ -27,16 +31,76 @@ abstract class AbstractMapping {
2731 */
2832 abstract protected function getTableName (bool $ includePrefix = true );
2933
34+ /**
35+ * A month worth of cache time for as good as never changing mapping data.
36+ * Implemented when it was found that name-to-DN lookups are quite frequent.
37+ */
38+ protected const LOCAL_CACHE_TTL = 2592000 ;
39+
40+ /**
41+ * By default, the local cache is only used up to a certain amount of objects.
42+ * This constant holds this number. The amount of entries would amount up to
43+ * 1 MiB (worst case) per mappings table.
44+ * Setting `use_local_mapping_cache` for `user_ldap` to `yes` or `no`
45+ * deliberately enables or disables this mechanism.
46+ */
47+ protected const LOCAL_CACHE_OBJECT_THRESHOLD = 2000 ;
48+
49+ protected ?ICache $ localNameToDnCache = null ;
50+
51+ /** @var array caches Names (value) by DN (key) */
52+ protected array $ cache = [];
53+
3054 /**
3155 * @param IDBConnection $dbc
3256 */
3357 public function __construct (
3458 protected IDBConnection $ dbc ,
59+ protected ICacheFactory $ cacheFactory ,
60+ protected IAppConfig $ config ,
61+ protected bool $ isCLI ,
3562 ) {
63+ $ this ->initLocalCache ();
3664 }
3765
38- /** @var array caches Names (value) by DN (key) */
39- protected $ cache = [];
66+ protected function initLocalCache (): void {
67+ if ($ this ->isCLI || !$ this ->cacheFactory ->isLocalCacheAvailable ()) {
68+ return ;
69+ }
70+
71+ $ useLocalCache = $ this ->config ->getValueString ('user_ldap ' , 'use_local_mapping_cache ' , 'auto ' , false );
72+ if ($ useLocalCache !== 'yes ' && $ useLocalCache !== 'auto ' ) {
73+ return ;
74+ }
75+
76+ $ section = \str_contains ($ this ->getTableName (), 'user ' ) ? 'u/ ' : 'g/ ' ;
77+ $ this ->localNameToDnCache = $ this ->cacheFactory ->createLocal ('ldap/map/ ' . $ section );
78+
79+ // We use the cache as well to store whether it shall be used. If the
80+ // answer was no, we unset it again.
81+ if ($ useLocalCache === 'auto ' && !$ this ->useCacheByUserCount ()) {
82+ $ this ->localNameToDnCache = null ;
83+ }
84+ }
85+
86+ protected function useCacheByUserCount (): bool {
87+ $ use = $ this ->localNameToDnCache ->get ('use ' );
88+ if ($ use !== null ) {
89+ return $ use ;
90+ }
91+
92+ $ qb = $ this ->dbc ->getQueryBuilder ();
93+ $ q = $ qb ->selectAlias ($ qb ->createFunction ('COUNT(owncloud_name) ' ), 'count ' )
94+ ->from ($ this ->getTableName ());
95+ $ q ->setMaxResults (self ::LOCAL_CACHE_OBJECT_THRESHOLD + 1 );
96+ $ result = $ q ->executeQuery ();
97+ $ row = $ result ->fetch ();
98+ $ result ->closeCursor ();
99+
100+ $ use = (int )$ row ['count ' ] <= self ::LOCAL_CACHE_OBJECT_THRESHOLD ;
101+ $ this ->localNameToDnCache ->set ('use ' , $ use , intdiv (self ::LOCAL_CACHE_TTL , 4 ));
102+ return $ use ;
103+ }
40104
41105 /**
42106 * checks whether a provided string represents an existing table col
@@ -107,17 +171,22 @@ protected function modify(IPreparedStatement $statement, $parameters) {
107171
108172 /**
109173 * Gets the LDAP DN based on the provided name.
110- * Replaces Access::ocname2dn
111- *
112- * @param string $name
113- * @return string|false
114174 */
115- public function getDNByName ($ name ) {
116- $ dn = array_search ($ name , $ this ->cache );
117- if ($ dn === false && ($ dn = $ this ->getXbyY ('ldap_dn ' , 'owncloud_name ' , $ name )) !== false ) {
118- $ this ->cache [$ dn ] = $ name ;
175+ public function getDNByName (string $ name ): string |false {
176+ $ dn = array_search ($ name , $ this ->cache , true );
177+ if ($ dn === false ) {
178+ $ dn = $ this ->localNameToDnCache ?->get($ name );
179+ if ($ dn === null || $ dn === false ) {
180+ $ dn = $ this ->getXbyY ('ldap_dn ' , 'owncloud_name ' , $ name );
181+ if ($ dn === false ) {
182+ return false ;
183+ }
184+
185+ $ this ->cache [$ dn ] = $ name ;
186+ $ this ->localNameToDnCache ?->set($ name , $ dn , self ::LOCAL_CACHE_TTL );
187+ }
119188 }
120- return $ dn ;
189+ return $ dn ?? false ;
121190 }
122191
123192 /**
@@ -128,6 +197,7 @@ public function getDNByName($name) {
128197 * @return bool
129198 */
130199 public function setDNbyUUID ($ fdn , $ uuid ) {
200+ // FIXME: need to update local cache by name and broadcast this information
131201 $ oldDn = $ this ->getDnByUUID ($ uuid );
132202 $ statement = $ this ->dbc ->prepare ('
133203 UPDATE ` ' . $ this ->getTableName () . '`
@@ -374,6 +444,7 @@ public function unmap($name) {
374444 if ($ dn !== false ) {
375445 unset($ this ->cache [$ dn ]);
376446 }
447+ $ this ->localNameToDnCache ?->remove($ name );
377448
378449 return $ this ->modify ($ statement , [$ name ]);
379450 }
@@ -389,6 +460,7 @@ public function clear() {
389460 ->getTruncateTableSQL ('` ' . $ this ->getTableName () . '` ' );
390461 try {
391462 $ this ->dbc ->executeQuery ($ sql );
463+ $ this ->localNameToDnCache ?->clear();
392464
393465 return true ;
394466 } catch (Exception $ e ) {
0 commit comments