2012-08-16 23:41:25 -04:00
" cache.vim
" @Author: Tom Link (micathom AT gmail com?subject=[vim])
" @Website: http://www.vim.org/account/profile.php?user_id=4037
" @License: GPL (see http://www.gnu.org/licenses/gpl.txt)
" @Created: 2007-06-30.
2021-08-04 09:52:11 -04:00
" @Last Change: 2019-01-02.
" @Revision: 125.1.243
2012-08-16 23:41:25 -04:00
2013-11-16 14:45:48 -05:00
" The cache directory. If empty, use |tlib#dir#MyRuntime|.'/cache'.
" You might want to delete old files from this directory from time to
" time with a command like: >
" find ~/vimfiles/cache/ -atime +31 -type f -print -delete
TLet g :tlib_cache = ''
2012-08-16 23:41:25 -04:00
" |tlib#cache#Purge()|: Remove cache files older than N days.
TLet g :tlib #cache #purge_days = 31
" Purge the cache every N days. Disable automatic purging by setting
" this value to a negative value.
TLet g :tlib #cache #purge_every_days = 31
" The encoding used for the purge-cache script.
" Default: 'enc'
TLet g :tlib #cache #script_encoding = &enc
" Whether to run the directory removal script:
" 0 ... No
" 1 ... Query user
" 2 ... Yes
TLet g :tlib #cache #run_script = 1
2013-11-16 14:45:48 -05:00
" Verbosity level:
" 0 ... Be quiet
" 1 ... Display informative message
" 2 ... Display detailed messages
TLet g :tlib #cache #verbosity = 1
2013-04-13 13:45:21 -04:00
2012-08-16 23:41:25 -04:00
" A list of regexps that are matched against partial filenames of the
" cached files. If a regexp matches, the file won't be removed by
" |tlib#cache#Purge()|.
TLet g :tlib #cache #dont_purge = ['[\/]\.last_purge$' ]
2013-11-16 14:45:48 -05:00
" If the cache filename is longer than N characters, use
" |pathshorten()|.
TLet g :tlib #cache #max_filename = 200
2021-08-04 09:52:11 -04:00
TLet g :tlib #cache #use_json = 0
TLet g :tlib #cache #use_encoding = ''
2015-12-08 08:20:04 -05:00
let s :cache = {}
2012-08-16 23:41:25 -04:00
2021-08-04 09:52:11 -04:00
" :display: tlib#cache#Dir(?mode = 'bg', ?ensure_dir = true)
2012-08-16 23:41:25 -04:00
" The default cache directory.
function ! tlib #cache #Dir ( ...) "{{{3
2021-08-04 09:52:11 -04:00
TVarArg ['mode' , 'bg' ], ['ensure_dir' , 1 ]
2012-08-16 23:41:25 -04:00
let dir = tlib #var #Get ( 'tlib_cache' , mode )
if empty ( dir )
let dir = tlib #file #Join ( [tlib #dir #MyRuntime ( ) , 'cache' ])
endif
2021-08-04 09:52:11 -04:00
if ensure_dir
call tlib #dir #Ensure ( dir )
endif
2012-08-16 23:41:25 -04:00
return dir
endf
2021-08-04 09:52:11 -04:00
" :display: tlib#cache#EncodedFilename(type, file, ?mkdir=0, ?dir='')
" Encode `file` and call |tlib#cache#Filename()|.
function ! tlib #cache #EncodedFilename ( type , file , ...) "{{{3
let file = tlib #url #Encode ( a :file )
return call ( function ( 'tlib#cache#Filename' ) , [a :type , file ] + a :000 )
endf
2013-04-13 13:45:21 -04:00
" :def: function! tlib#cache#Filename(type, ?file=%, ?mkdir=0, ?dir='')
2012-08-16 23:41:25 -04:00
function ! tlib #cache #Filename ( type , ...) "{{{3
" TLogDBG 'bufname='. bufname('.')
2013-11-16 14:45:48 -05:00
let dir0 = a :0 > = 3 && ! empty ( a :3 ) ? a :3 : tlib #cache #Dir ( )
let dir = dir0
2012-08-16 23:41:25 -04:00
if a :0 > = 1 && ! empty ( a :1 )
let file = a :1
else
if empty ( expand ( '%:t' ) )
return ''
endif
let file = expand ( '%:p' )
let file = tlib #file #Relative ( file , tlib #file #Join ( [dir , '..' ]) )
endif
" TLogVAR file, dir
let mkdir = a :0 > = 2 ? a :2 : 0
let file = substitute ( file , '\.\.\|[:&<>]\|//\+\|\\\\\+' , '_' , 'g' )
let dirs = [dir , a :type ]
let dirf = fnamemodify ( file , ':h' )
if dirf ! = '.'
call add ( dirs , dirf )
endif
let dir = tlib #file #Join ( dirs )
" TLogVAR dir
let dir = tlib #dir #PlainName ( dir )
" TLogVAR dir
let file = fnamemodify ( file , ':t' )
" TLogVAR file, dir, mkdir
let cache_file = tlib #file #Join ( [dir , file ])
2013-11-16 14:45:48 -05:00
if len ( cache_file ) > g :tlib #cache #max_filename
2021-08-04 09:52:11 -04:00
" echom "DBG long filename" cache_file
" echom "DBG long filename" dir
2014-04-18 08:58:02 -04:00
if v :version > = 704
2021-08-04 09:52:11 -04:00
let shortfilename = sha256 ( file )
2014-04-18 08:58:02 -04:00
else
2021-08-04 09:52:11 -04:00
let shortfilename = tlib #hash #Adler32 ( file )
2014-04-18 08:58:02 -04:00
endif
2021-08-04 09:52:11 -04:00
" let cache_file = tlib#cache#Filename(a:type, shortfilename, mkdir, dir0)
let cache_file = tlib #file #Join ( [dir , shortfilename ])
2013-11-16 14:45:48 -05:00
else
if mkdir && ! isdirectory ( dir )
try
call mkdir ( dir , 'p' )
catch /^Vim\%((\a\+)\)\=:E739:/
if filereadable ( dir ) && ! isdirectory ( dir )
echoerr 'TLib: Cannot create directory for cache file because a file with the same name exists (please delete it):' dir
" call delete(dir)
" call mkdir(dir, 'p')
endif
endtry
endif
endif
2012-08-16 23:41:25 -04:00
" TLogVAR cache_file
return cache_file
endf
2014-04-18 08:58:02 -04:00
let s :timestamps = {}
function ! s :SetTimestamp ( cfile , type ) "{{{3
if ! has_key ( s :timestamps , a :cfile )
let s :timestamps [a :cfile ] = {}
endif
let s :timestamps [a :cfile ].atime = getftime ( a :cfile )
let s :timestamps [a :cfile ][a :type ] = s :timestamps [a :cfile ].atime
endf
2021-08-04 09:52:11 -04:00
function ! s :PutValue ( cfile , value ) abort "{{{3
let s :cache [a :cfile ] = {'mtime' : localtime ( ) , 'data' : a :value }
endf
function ! s :GetValue ( cfile , default ) abort "{{{3
return get ( get ( s :cache , a :cfile , {}) , 'data' , a :default )
endf
function ! s :GetCacheTime ( cfile ) abort "{{{3
let not_found = ! has_key ( s :cache , a :cfile )
let cftime = not_found ? -1 : s :cache [a :cfile ].mtime
return cftime
endf
function ! tlib #cache #Save ( cfile , value , ...) "{{{3
2015-12-08 08:20:04 -05:00
TVarArg ['options' , {}]
let in_memory = get ( options , 'in_memory' , 0 )
if in_memory
" TLogVAR in_memory, a:cfile, localtime()
2021-08-04 09:52:11 -04:00
call s :PutValue ( a :cfile , a :value )
2015-12-08 08:20:04 -05:00
elseif ! empty ( a :cfile )
2021-08-04 09:52:11 -04:00
" TLogVAR a:value
let cfile = a :cfile
if g :tlib #cache #use_json && exists ( '*json_encode' )
try
let value = json_encode ( a :value )
let cfile .= '.json'
catch
echoerr v :exception
let value = string ( a :value )
endtry
else
let value = string ( a :value )
endif
Tlibtrace 'tlib' , cfile , value
call writefile ( [value ], cfile , 'b' )
2014-04-18 08:58:02 -04:00
call s :SetTimestamp ( a :cfile , 'write' )
endif
endf
function ! tlib #cache #MTime ( cfile ) "{{{3
let mtime = {'mtime' : getftime ( a :cfile ) }
let mtime = extend ( mtime , get ( s :timestamps , a :cfile , {}) )
return mtime
2012-08-16 23:41:25 -04:00
endf
2014-03-11 16:10:50 -04:00
function ! tlib #cache #Get ( cfile , ...) "{{{3
2015-12-08 08:20:04 -05:00
TVarArg ['default' , {}], ['options' , {}]
let in_memory = get ( options , 'in_memory' , 0 )
if in_memory
" TLogVAR in_memory, a:cfile
2021-08-04 09:52:11 -04:00
return s :GetValue ( a :cfile , default )
2014-03-11 16:10:50 -04:00
else
2015-12-08 08:20:04 -05:00
call tlib #cache #MaybePurge ( )
2021-08-04 09:52:11 -04:00
if ! empty ( a :cfile )
let jsonfile = a :cfile .'.json'
let use_json = g :tlib #cache #use_json && exists ( '*json_decode' ) && exists ( 'v:none' ) && filereadable ( jsonfile )
if use_json
let use_json = 1
let cfile = jsonfile
else
let cfile = a :cfile
endif
let mt = s :GetCacheTime ( cfile )
let ft = getftime ( cfile )
if mt ! = -1 && mt > = ft
return s :GetValue ( cfile , default )
elseif ft ! = -1
call s :SetTimestamp ( cfile , 'read' )
let val = join ( readfile ( cfile , 'b' ) , '\n' )
try
if use_json
" NOTE: Copy result of json_decode() in order to
" avoid "E741: value is locked" error in vim8.
let value = json_decode ( val )
if value is v :none
let value = default
else
let value = copy ( value )
endif
else
let value = eval ( val )
endif
call s :PutValue ( cfile , value )
return value
catch
echohl ErrorMsg
echom v :exception
echom 'tlib#cache#Get: Invalid value in:' cfile
echom 'Value:' string ( val )
echom 'Please review the file and delete it if necessary'
echom 'Will use default value:' string ( default )
echohl NONE
if g :tlib #debug
let @* = string ( val )
endif
" call s:PutValue(cfile, default)
return default
endtry
endif
2015-12-08 08:20:04 -05:00
endif
2021-08-04 09:52:11 -04:00
return default
2014-03-11 16:10:50 -04:00
endif
2012-08-16 23:41:25 -04:00
endf
2015-12-08 08:20:04 -05:00
" :display: tlib#cache#Value(cfile, generator, ftime, ?generator_args=[], ?options={})
2013-11-16 14:45:48 -05:00
" Get a cached value from cfile. If it is outdated (compared to ftime)
" or does not exist, create it calling a generator function.
function ! tlib #cache #Value ( cfile , generator , ftime , ...) "{{{3
2015-12-08 08:20:04 -05:00
TVarArg ['args' , []], ['options' , {}]
let in_memory = get ( options , 'in_memory' , 0 )
2021-08-04 09:52:11 -04:00
if in_memory
let cftime = s :GetCacheTime ( a :cfile )
else
let cftime = getftime ( a :cfile )
endif
let ftime = a :ftime
" TLogVAR in_memory, cftime
if cftime = = -1 | | ftime = = -1 | | ( ftime ! = 0 && cftime < ftime )
2014-04-18 08:58:02 -04:00
" TLogVAR a:generator, args
let val = call ( a :generator , args )
2014-03-11 16:10:50 -04:00
" TLogVAR val
let cval = {'val' : val }
" TLogVAR cval
2015-12-08 08:20:04 -05:00
call tlib #cache #Save ( a :cfile , cval , options )
2013-11-16 14:45:48 -05:00
return val
else
2015-12-08 08:20:04 -05:00
let val = tlib #cache #Get ( a :cfile , {}, options )
if ! has_key ( val , 'val' )
throw 'tlib#cache#Value: Internal error: ' . a :cfile
else
return val .val
endif
2013-11-16 14:45:48 -05:00
endif
endf
2021-08-04 09:52:11 -04:00
function ! tlib #cache #ValueFromName ( type , name , ...) abort "{{{3
let cfile = tlib #cache #Filename ( a :type , tlib #url #Encode ( a :name ) , 1 )
return call ( function ( 'tlib#cache#Value' ) , [cfile ] + a :000 )
endf
2012-08-16 23:41:25 -04:00
" Call |tlib#cache#Purge()| if the last purge was done before
2013-04-13 13:45:21 -04:00
" |g:tlib#cache#purge_every_days|.
function ! tlib #cache #MaybePurge ( ) "{{{3
if g :tlib #cache #purge_every_days < 0
return
endif
let dir = tlib #cache #Dir ( 'g' )
let last_purge = tlib #file #Join ( [dir , '.last_purge' ])
let last_purge_exists = filereadable ( last_purge )
if last_purge_exists
let threshold = localtime ( ) - g :tlib #cache #purge_every_days * g :tlib #date #dayshift
let should_purge = getftime ( last_purge ) < threshold
else
let should_purge = 0 " should ignore empty dirs, like the tmru one: !empty(glob(tlib#file#Join([dir, '**'])))
endif
if should_purge
2012-08-16 23:41:25 -04:00
if last_purge_exists
2013-04-13 13:45:21 -04:00
let yn = 'y'
2012-08-16 23:41:25 -04:00
else
2013-04-13 13:45:21 -04:00
let txt = "TLib: The cache directory '" . dir ."' should be purged of old files.\nDelete files older than " . g :tlib #cache #purge_days ." days now?"
let yn = tlib #input #Dialog ( txt , ['yes' , 'no' ], 'no' )
2012-08-16 23:41:25 -04:00
endif
2013-04-13 13:45:21 -04:00
if yn = ~ '^y\%[es]$'
call tlib #cache #Purge ( )
else
let g :tlib #cache #purge_every_days = -1
if ! last_purge_exists
call s :PurgeTimestamp ( dir )
2012-08-16 23:41:25 -04:00
endif
2013-04-13 13:45:21 -04:00
echohl WarningMsg
echom "TLib: Please run :call tlib#cache#Purge() to clean up " . dir
echohl NONE
2012-08-16 23:41:25 -04:00
endif
2013-04-13 13:45:21 -04:00
elseif ! last_purge_exists
call s :PurgeTimestamp ( dir )
endif
endf
2012-08-16 23:41:25 -04:00
" Delete old files.
function ! tlib #cache #Purge ( ) "{{{3
let threshold = localtime ( ) - g :tlib #cache #purge_days * g :tlib #date #dayshift
let dir = tlib #cache #Dir ( 'g' )
2013-11-16 14:45:48 -05:00
if g :tlib #cache #verbosity > = 1
2013-04-13 13:45:21 -04:00
echohl WarningMsg
echom "TLib: Delete files older than " . g :tlib #cache #purge_days ." days from " . dir
echohl NONE
endif
2012-08-16 23:41:25 -04:00
let files = tlib #cache #ListFilesInCache ( )
let deldir = []
let newer = []
let msg = []
let more = &more
set nomore
try
for file in files
if isdirectory ( file )
2021-08-04 09:52:11 -04:00
if empty ( filter ( copy ( newer ) , 'tlib#string#Strcharpart(v:val, 0, len(file)) ==# file' ) )
2012-08-16 23:41:25 -04:00
call add ( deldir , file )
endif
else
if getftime ( file ) < threshold
2021-08-04 09:52:11 -04:00
call s :Delete ( msg , file , '' )
2012-08-16 23:41:25 -04:00
else
call add ( newer , file )
endif
endif
endfor
finally
let &more = more
endtry
2013-11-16 14:45:48 -05:00
if ! empty ( msg ) && g :tlib #cache #verbosity > = 1
2012-08-16 23:41:25 -04:00
echo join ( msg , "\n" )
endif
if ! empty ( deldir )
2021-08-04 09:52:11 -04:00
let deldir = filter ( reverse ( sort ( deldir ) ) , 's:Delete(msg, v:val, "d")' )
if ! empty ( deldir )
if &shell = ~ 'sh\(\.exe\)\?$'
let scriptfile = 'deldir.sh'
let rmdir = 'rm -rf %s'
else
let scriptfile = 'deldir.bat'
let rmdir = 'rmdir /S /Q %s'
2013-04-13 13:45:21 -04:00
endif
2021-08-04 09:52:11 -04:00
let enc = g :tlib #cache #script_encoding
if has ( 'multi_byte' ) && enc ! = &enc
call map ( deldir , 'iconv(v:val, &enc, enc)' )
endif
let scriptfile = tlib #file #Join ( [dir , scriptfile ])
if filereadable ( scriptfile )
let script = readfile ( scriptfile )
else
let script = []
endif
let script + = map ( copy ( deldir ) , 'printf(rmdir, shellescape(v:val, 1))' )
let script = tlib #list #Uniq ( script )
call writefile ( script , scriptfile )
call inputsave ( )
if g :tlib #cache #run_script = = 0
if g :tlib #cache #verbosity > = 1
echohl WarningMsg
if g :tlib #cache #verbosity > = 2
echom "TLib: Purged cache. Need to run script to delete directories"
endif
echom "TLib: Please review and execute: " . scriptfile
echohl NONE
2012-08-16 23:41:25 -04:00
endif
2021-08-04 09:52:11 -04:00
else
try
let yn = g :tlib #cache #run_script = = 2 ? 'y' : tlib #input #Dialog ( "TLib: About to delete directories by means of a shell script.\nDirectory removal script: " . scriptfile ."\nRun script to delete directories now?" , ['yes' , 'no' , 'edit' ], 'no' )
if yn = ~ '^y\%[es]$'
exec 'silent cd ' . fnameescape ( dir )
exec '! ' &shell shellescape ( scriptfile , 1 )
exec 'silent cd -'
call delete ( scriptfile )
elseif yn = ~ '^e\%[dit]$'
exec 'edit ' . fnameescape ( scriptfile )
endif
finally
call inputrestore ( )
endtry
endif
2012-08-16 23:41:25 -04:00
endif
endif
call s :PurgeTimestamp ( dir )
endf
2021-08-04 09:52:11 -04:00
function ! s :Delete ( msg , file , flags ) abort "{{{3
let rv = delete ( a :file , a :flags )
if ! rv && g :tlib #cache #verbosity > = 2
call add ( a :msg , "TLib#cache: Delete " . file )
endif
return rv
endf
2012-08-16 23:41:25 -04:00
function ! s :PurgeTimestamp ( dir ) "{{{3
let last_purge = tlib #file #Join ( [a :dir , '.last_purge' ])
" TLogVAR last_purge
call writefile ( [" " ], last_purge )
endf
function ! tlib #cache #ListFilesInCache ( ...) "{{{3
let dir = a :0 > = 1 ? a :1 : tlib #cache #Dir ( 'g' )
if v :version > 702 | | ( v :version = = 702 && has ( 'patch51' ) )
let filess = glob ( tlib #file #Join ( [dir , '**' ]) , 1 )
else
let filess = glob ( tlib #file #Join ( [dir , '**' ]) )
endif
let files = reverse ( split ( filess , '\n' ) )
let pos0 = len ( tlib #dir #CanonicName ( dir ) )
2021-08-04 09:52:11 -04:00
call filter ( files , 's:ShouldPurge(tlib#string#Strcharpart(v:val, pos0))' )
2012-08-16 23:41:25 -04:00
return files
endf
function ! s :ShouldPurge ( partial_filename ) "{{{3
" TLogVAR a:partial_filename
for rx in g :tlib #cache #dont_purge
if a :partial_filename = ~ rx
" TLogVAR a:partial_filename, rx
return 0
endif
endfor
return 1
endf