/// <summary> /// Gets file icon as <b>Bitmap</b>. /// Returns null if the icon is not cached and failed to get it, eg file does not exist. /// </summary> /// <param name="file">Any file or folder.</param> /// <param name="useExt"> /// Get file type icon, depending on filename extension. Use this to avoid getting separate image object for each file of same type. /// This is ignored if filename extension is ".ico" or ".exe" or starts with ".exe," or ".dll,". /// </param> /// <param name="giFlags">Flags for <see cref="AIcon.GetFileIconImage"/>.</param> /// <param name="autoUpdate"> /// If not null, the cached image will be auto-updated when changed. Then will be called this function. It can update the image in UI. /// How it works: If this function finds cached image, it sets timer that after ~50 ms loads that icon/image from file again and compares with the cached image. If different, updates the cache. Does it once, not periodically. /// Use only in UI threads. Does not work if this thread does not retrieve/dispatch posted messages. /// </param> /// <param name="auParam">Something to pass to the <i>autoUpdate</i> callback function.</param> /// <remarks> /// If the icon is in the memory cache, gets it from there. /// Else if it is in the file cache, gets it from there and adds to the memory cache. /// Else gets from file (uses <see cref="AIcon.GetFileIconImage"/> and adds to the file cache and to the memory cache. /// </remarks> public Bitmap GetImage(string file, bool useExt, IconGetFlags giFlags = 0, Action <Bitmap, object> autoUpdate = null, object auParam = null) { if (useExt) { var ext = APath.GetExtension(file); if (ext.Length == 0) { if (AFile.ExistsAsDirectory(file)) { ext = file; } else { ext = ".no-ext"; } } else { //ext = ext.Lower(); if (ext.Eqi(".ico") || ext.Eqi(".exe") || ext.Starts(".exe,", true) || ext.Starts(".dll,", true)) { ext = file; } } file = ext; } else if (APath.IsFullPathExpandEnvVar(ref file)) { file = APath.Normalize_(file, noExpandEV: true); } return(_GetImage(file, giFlags, null, autoUpdate, auParam, true)); }
/// <summary> /// Clears the memory cache and deletes the cache file. /// </summary> public void ClearCache() { lock (this) { _dirty = false; _x = null; _table = null; AFile.Delete(_cacheFile); } }
/// <summary> /// Saves the Shortcut variable properties to the shortcut file. /// </summary> /// <exception cref="AuException">Failed to save .lnk file.</exception> /// <remarks> /// Creates parent folder if need. /// </remarks> public void Save() { if (_changedHotkey && !_isOpen && AFile.ExistsAsFile(_lnkPath)) { _UnregisterHotkey(_lnkPath); } AFile.CreateDirectoryFor(_lnkPath); AuException.ThrowIfHresultNot0(_ipf.Save(_lnkPath, true), "*save"); }
//This could be public, but then need to make IShellLink public. It is defined in a non-standard way. Never mind, it is not important. AShortcutFile(string lnkPath, uint mode) { _isl = new Api.ShellLink() as Api.IShellLink; _ipf = _isl as Api.IPersistFile; _lnkPath = lnkPath; if (mode != Api.STGM_WRITE && (mode == Api.STGM_READ || AFile.ExistsAsFile(_lnkPath))) { AuException.ThrowIfHresultNot0(_ipf.Load(_lnkPath, mode), "*open"); _isOpen = true; } }
/// <summary> /// Saves to the cache file now, if need. /// Automatically called on process exit. /// </summary> public void SaveCacheFileNow() { if (_dirty) { lock (this) { if (_dirty) { _dirty = false; AFile.WaitIfLocked(() => _x.Save(_cacheFile)); } } } }
/// <summary> /// Saves XML to a file in a safer way. /// Uses <see cref="XDocument.Save(string)"/> and <see cref="AFile.Save"/> /// </summary> /// <exception cref="Exception">Exceptions of <see cref="XDocument.Save"/> and <see cref="AFile.Save"/>.</exception> public static void SaveDoc(this XDocument t, string file, bool backup = false, SaveOptions?options = default) { AFile.Save(file, temp => { if (options.HasValue) { t.Save(temp, options.GetValueOrDefault()); } else { t.Save(temp); } }, backup); }
static void _SetOnce(ref string propVar, string value, bool create, [CallerMemberName] string propName = null) { lock (_lock) { if (propVar != null) { throw new InvalidOperationException("AFolders." + propName + " is already set."); } propVar = value; if (create) { AFile.CreateDirectory(value); } } }
//note: don't use Application.ProductName etc. It loads Forms, throws if dynamic assembly, etc. #region set auto/once static string _SetAuto(ref string propVar, string value, bool create) { lock (_lock) { if (propVar == null) { propVar = value; if (create) { AFile.CreateDirectory(value); } } } return(propVar); }
/// <summary> /// Gets path with unique filename for a new file or directory. /// If the specified path is of an existing file or directory, returns path where the filename part is modified like "file 2.txt", "file 3.txt" etc. Else returns unchanged path. /// </summary> /// <param name="path">Suggested full path.</param> /// <param name="isDirectory">The path is for a directory. The number is always appended at the very end, not before .extension.</param> public static string MakeUnique(string path, bool isDirectory) { if (!AFile.ExistsAsAny(path)) { return(path); } string ext = isDirectory ? null : GetExtension(path, out path); for (int i = 2; ; i++) { var s = path + " " + i + ext; if (!AFile.ExistsAsAny(s)) { return(s); } } }
public Assembly Find(string asmFile) { _d ??= new Dictionary <string, _Asm>(StringComparer.OrdinalIgnoreCase); if (!AFile.GetProperties(asmFile, out var p, FAFlags.UseRawPath)) { return(null); } _fileTime = p.LastWriteTimeUtc; if (_d.TryGetValue(asmFile, out var x)) { if (x.time == _fileTime) { return(x.asm); } _d.Remove(asmFile); } return(null); }
//Used by Run and RunConsole. static string _NormalizeFile(bool runConsole, string file, out bool isFullPath, out bool isShellPath) { isShellPath = isFullPath = false; file = APath.ExpandEnvVar(file); if (file.NE()) { throw new ArgumentException(); } if (runConsole || !(isShellPath = APath.IsShellPath_(file))) { if (isFullPath = APath.IsFullPath(file)) { var fl = runConsole ? PNFlags.DontExpandDosPath : PNFlags.DontExpandDosPath | PNFlags.DontPrefixLongPath; file = APath.Normalize_(file, fl, true); //ShellExecuteEx supports long path prefix for exe but not for documents. //Process.Run supports long path prefix, except when the exe is .NET. if (!runConsole) { file = APath.UnprefixLongPath(file); } if (ADisableFsRedirection.IsSystem64PathIn32BitProcess(file) && !AFile.ExistsAsAny(file)) { file = ADisableFsRedirection.GetNonRedirectedSystemPath(file); } } else if (!APath.IsUrl(file)) { //ShellExecuteEx searches everywhere except in app folder. //Process.Run prefers current directory. var s2 = AFile.SearchPath(file); if (s2 != null) { file = s2; isFullPath = true; } } } return(file); }
/// <summary> /// Opens or creates a database file. /// </summary> /// <param name="file"> /// Database file. Can be: /// - Full path. Supports environment variables etc, see <see cref="APath.ExpandEnvVar"/> /// - ":memory:" - create a private, temporary in-memory database. /// - "" - create a private, temporary on-disk database. /// - Starts with "file:" - see sqlite3_open_v2 documentation in SQLite website. /// </param> /// <param name="flags">sqlite3_open_v2 flags, documanted in SQLite website. Default: read-write, create file if does not exist (and parent directory).</param> /// <param name="sql"> /// SQL to execute. For example, one or more ;-separated PRAGMA statements to configure the database connection. Or even "CREATE TABLE IF NOT EXISTS ...". /// This function also always executes "PRAGMA foreign_keys=ON;PRAGMA secure_delete=ON;". /// </param> /// <exception cref="ArgumentException">Not full path.</exception> /// <exception cref="SLException">Failed to open database or execute sql.</exception> /// <remarks> /// Calls sqlite3_open_v2. /// <note>If a variable of this class is used by multiple threads, use <c>lock(variable) { }</c> where need.</note> /// </remarks> public ASqlite(string file, SLFlags flags = SLFlags.ReadWriteCreate, string sql = null) { bool isSpec = file != null && (file.Length == 0 || file == ":memory:" || file.Starts("file:")); if (!isSpec) { file = APath.Normalize(file); if (flags.Has(SLFlags.SQLITE_OPEN_CREATE) && !AFile.ExistsAsFile(file, true)) { AFile.CreateDirectoryFor(file); } } var r = SLApi.sqlite3_open_v2(AConvert.ToUtf8(file), ref _db, flags, null); if (r != 0) { Dispose(); throw new SLException(r, "sqlite3_open " + file); } Execute("PRAGMA foreign_keys=ON;PRAGMA secure_delete=ON;" + sql); }
Bitmap _GetImage(string file, IconGetFlags giFlags, Func <Bitmap> callback, Action <Bitmap, object> autoUpdate, object auParam, bool auDispose) { bool cached = true; lock (this) { Bitmap R = null; //is in memory cache? if (_table == null) { _table = new Hashtable(StringComparer.OrdinalIgnoreCase); } else { R = _table[file] as Bitmap; } if (R == null) { //is in file cache? try { if (_x == null && AFile.ExistsAsFile(_cacheFile)) { _x = AExtXml.LoadElem(_cacheFile); if (_iconSize != _x.Attr("size", 0) || ADpi.BaseDPI != _x.Attr("dpi", 0)) { _x = null; ADebug.Print("info: cleared icon cache"); } //FUTURE: Delete unused entries. Maybe try to auto-update changed icons. // Not very important, because there is ClearCache. } if (_x != null) { var x = _x.Elem("i", "name", file, true); if (x != null) { using var ms = new MemoryStream(Convert.FromBase64String(x.Value), false); R = new Bitmap(ms); } } } catch (Exception ex) { ADebug.Print(ex.Message); } if (R != null) { _table[file] = R; //add to memory cache } else if (_LoadImage(out R, file, giFlags, callback)) //get file icon { _AddImage(file, R, false); //add to file cache and memory cache cached = false; } } //auto-update if (cached && autoUpdate != null) { var d = new _AUData() { cache = this, oldImage = R, file = file, callback = callback, autoUpdated = autoUpdate, auParam = auParam, giFlags = giFlags, canDispose = auDispose, }; _AutoUpdateAdd(d); } return(R); } }
/// <summary> /// Deletes the settings file of the toolbar, if exists. It resets toolbar settings. /// The toolbar should not be open when calling this function. /// </summary> /// <param name="toolbarName">Toolbar name.</param> /// <exception cref="Exception">Exceptions of <see cref="AFile.Delete(string, bool)"/>.</exception> public static void DeleteSettings(string toolbarName) { AFile.Delete(GetSettingsFilePath(toolbarName)); }
/// <summary> /// Executes assembly in this thread. /// Handles exceptions. /// </summary> /// <param name="asmFile">Full path of assembly file.</param> /// <param name="args">To pass to Main.</param> /// <param name="pdbOffset">0 or offset of portable PDB in assembly file.</param> /// <param name="flags"></param> /// <param name="fullPathRefs">Paths of assemblies specified using full path.</param> public static void Run(string asmFile, string[] args, int pdbOffset, RAFlags flags = 0, string fullPathRefs = null) { ADebug.PrintIf(pdbOffset == 0, "pdbOffset 0"); bool inEditorThread = 0 != (flags & RAFlags.InEditorThread); bool findLoaded = inEditorThread; _LoadedScriptAssembly lsa = default; Assembly asm = findLoaded ? lsa.Find(asmFile) : null; if (asm == null) { #if true //var p1 = APerf.Create(); var alc = System.Runtime.Loader.AssemblyLoadContext.Default; //SHOULDDO: try to unload. It seems AssemblyLoadContext supports it. Not tested. I guess it would create more problems than is useful. //p1.Next(); using (var stream = AFile.WaitIfLocked(() => File.OpenRead(asmFile))) { //p1.Next(); if (pdbOffset > 0) { var b = new byte[pdbOffset]; stream.Read(b, 0, b.Length); using var msAsm = new MemoryStream(b); b = new byte[stream.Length - pdbOffset]; stream.Read(b, 0, b.Length); using var msDeb = new MemoryStream(b); //p1.Next('f'); asm = alc.LoadFromStream(msAsm, msDeb); //p1.Next(); } else { asm = alc.LoadFromStream(stream); } } //p1.NW(); //APerf.Next('a'); if (fullPathRefs != null) { var fpr = fullPathRefs.SegSplit("|"); alc.Resolving += (System.Runtime.Loader.AssemblyLoadContext alc, AssemblyName an) => { //AOutput.Write(an, an.Name, an.FullName); foreach (var v in fpr) { var s1 = an.Name; int iName = v.Length - s1.Length - 4; if (iName <= 0 || v[iName - 1] != '\\' || !v.Eq(iName, s1, true)) { continue; } if (!AFile.ExistsAsFile(v)) { continue; } //try { return(alc.LoadFromAssemblyPath(v)); //} catch(Exception ex) { ADebug.Print(ex.ToStringWithoutStack()); break; } } return(null); }; } //ADebug.PrintLoadedAssemblies(true, true); //AOutput.Write(asm); #else byte[] bAsm, bPdb = null; using (var stream = AFile.WaitIfLocked(() => File.OpenRead(asmFile))) { bAsm = new byte[pdbOffset > 0 ? pdbOffset : stream.Length]; stream.Read(bAsm, 0, bAsm.Length); try { if (pdbOffset > 0) { bPdb = new byte[stream.Length - pdbOffset]; stream.Read(bPdb, 0, bPdb.Length); } else { var s1 = Path.ChangeExtension(asmFile, "pdb"); if (AFile.ExistsAsFile(s1)) { bPdb = File.ReadAllBytes(s1); } } } catch (Exception ex) { bPdb = null; ADebug.Print(ex); } //not very important } //APerf.Next('f'); //APerf.First(); asm = Assembly.Load(bAsm, bPdb); #endif //APerf.Next('A'); //APerf.NW(); //without AV 7 ms. With Windows Defender 10 ms, but first time 20-900 ms. if (findLoaded) { lsa.Add(asmFile, asm); } //never mind: it's possible that we load a newer compiled assembly version of script than intended. } try { var entryPoint = asm.EntryPoint ?? throw new InvalidOperationException("assembly without entry point (function Main)"); bool useArgs = entryPoint.GetParameters().Length != 0; if (useArgs) { if (args == null) { args = Array.Empty <string>(); } } //APerf.Next('1'); if (!inEditorThread) { Util.Log_.Run.Write("Task started."); } //APerf.Next('2'); if (useArgs) { entryPoint.Invoke(null, new object[] { args }); } else { entryPoint.Invoke(null, null); } if (!inEditorThread) { Util.Log_.Run.Write("Task ended."); } } catch (TargetInvocationException te) { var e = te.InnerException; if (!inEditorThread) { Util.Log_.Run.Write($"Unhandled exception: {e.ToStringWithoutStack()}"); } if (0 != (flags & RAFlags.DontHandleExceptions)) { throw e; } //AOutput.Write(e); AScript.OnHostHandledException(new UnhandledExceptionEventArgs(e, false)); } //see also: TaskScheduler.UnobservedTaskException event. // tested: the event works. // tested: somehow does not terminate process even with <ThrowUnobservedTaskExceptions enabled="true"/>. // Only when ADialog.Show called, the GC.Collect makes it to disappear but the process does not exit. // note: the terminating behavior also can be set in registry or env var. It overrides <ThrowUnobservedTaskExceptions enabled="false"/>. }