123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446 |
- using System.IO;
- using UnityEngine;
- namespace SimpleFileBrowser
- {
- public struct FileSystemEntry
- {
- public readonly string Path;
- public readonly string Name;
- public readonly string Extension;
- public readonly FileAttributes Attributes;
- public bool IsDirectory { get { return ( Attributes & FileAttributes.Directory ) == FileAttributes.Directory; } }
- public FileSystemEntry( string path, string name, bool isDirectory )
- {
- Path = path;
- Name = name;
- Extension = isDirectory ? null : System.IO.Path.GetExtension( name );
- Attributes = isDirectory ? FileAttributes.Directory : FileAttributes.Normal;
- }
- public FileSystemEntry( FileSystemInfo fileInfo )
- {
- Path = fileInfo.FullName;
- Name = fileInfo.Name;
- Extension = fileInfo.Extension;
- Attributes = fileInfo.Attributes;
- }
- }
- public static class FileBrowserHelpers
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- private static AndroidJavaClass m_ajc = null;
- public static AndroidJavaClass AJC
- {
- get
- {
- if( m_ajc == null )
- m_ajc = new AndroidJavaClass( "com.yasirkula.unity.FileBrowser" );
- return m_ajc;
- }
- }
- private static AndroidJavaObject m_context = null;
- public static AndroidJavaObject Context
- {
- get
- {
- if( m_context == null )
- {
- using( AndroidJavaObject unityClass = new AndroidJavaClass( "com.unity3d.player.UnityPlayer" ) )
- {
- m_context = unityClass.GetStatic<AndroidJavaObject>( "currentActivity" );
- }
- }
- return m_context;
- }
- }
- private static string m_temporaryFilePath = null;
- private static string TemporaryFilePath
- {
- get
- {
- if( m_temporaryFilePath == null )
- {
- m_temporaryFilePath = Path.Combine( Application.temporaryCachePath, "tmpFile" );
- Directory.CreateDirectory( Application.temporaryCachePath );
- }
- return m_temporaryFilePath;
- }
- }
-
- // On Android 10+, filesystem can be accessed via Storage Access Framework only
- private static bool? m_shouldUseSAF = null;
- public static bool ShouldUseSAF
- {
- get
- {
- if( m_shouldUseSAF == null )
- m_shouldUseSAF = AJC.CallStatic<bool>( "CheckSAF" );
- return m_shouldUseSAF.Value;
- }
- }
- #endif
- public static bool FileExists( string path )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- if( ShouldUseSAF )
- return AJC.CallStatic<bool>( "SAFEntryExists", Context, path );
- #endif
- return File.Exists( path );
- }
- public static bool DirectoryExists( string path )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- if( ShouldUseSAF )
- return AJC.CallStatic<bool>( "SAFEntryExists", Context, path );
- #endif
- return Directory.Exists( path );
- }
- public static bool IsDirectory( string path )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- if( ShouldUseSAF )
- return AJC.CallStatic<bool>( "SAFEntryDirectory", Context, path );
- #endif
- if( Directory.Exists( path ) )
- return true;
- if( File.Exists( path ) )
- return false;
- string extension = Path.GetExtension( path );
- return extension == null || extension.Length <= 1; // extension includes '.'
- }
- public static FileSystemEntry[] GetEntriesInDirectory( string path )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- if( ShouldUseSAF )
- {
- string resultRaw = AJC.CallStatic<string>( "OpenSAFFolder", Context, path );
- int separatorIndex = resultRaw.IndexOf( "<>" );
- if( separatorIndex <= 0 )
- {
- Debug.LogError( "Entry count does not exist" );
- return null;
- }
- int entryCount = 0;
- for( int i = 0; i < separatorIndex; i++ )
- {
- char ch = resultRaw[i];
- if( ch < '0' && ch > '9' )
- {
- Debug.LogError( "Couldn't parse entry count" );
- return null;
- }
-
- entryCount = entryCount * 10 + ( ch - '0' );
- }
- if( entryCount <= 0 )
- return null;
- FileSystemEntry[] result = new FileSystemEntry[entryCount];
- for( int i = 0; i < entryCount; i++ )
- {
- separatorIndex += 2;
- if( separatorIndex >= resultRaw.Length )
- {
- Debug.LogError( "Couldn't fetch directory attribute" );
- return null;
- }
- bool isDirectory = resultRaw[separatorIndex] == 'd';
- separatorIndex++;
- int nextSeparatorIndex = resultRaw.IndexOf( "<>", separatorIndex );
- if( nextSeparatorIndex <= 0 )
- {
- Debug.LogError( "Entry name is empty" );
- return null;
- }
- string entryName = resultRaw.Substring( separatorIndex, nextSeparatorIndex - separatorIndex );
- separatorIndex = nextSeparatorIndex + 2;
- nextSeparatorIndex = resultRaw.IndexOf( "<>", separatorIndex );
- if( nextSeparatorIndex <= 0 )
- {
- Debug.LogError( "Entry rawUri is empty" );
- return null;
- }
- string rawUri = resultRaw.Substring( separatorIndex, nextSeparatorIndex - separatorIndex );
- separatorIndex = nextSeparatorIndex;
- result[i] = new FileSystemEntry( rawUri, entryName, isDirectory );
- }
- return result;
- }
- #endif
- try
- {
- FileSystemInfo[] items = new DirectoryInfo( path ).GetFileSystemInfos();
- FileSystemEntry[] result = new FileSystemEntry[items.Length];
- for( int i = 0; i < items.Length; i++ )
- result[i] = new FileSystemEntry( items[i] );
- return result;
- }
- catch( System.Exception e )
- {
- Debug.LogException( e );
- return null;
- }
- }
- public static string CreateFileInDirectory( string directoryPath, string filename )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- if( ShouldUseSAF )
- return AJC.CallStatic<string>( "CreateSAFEntry", Context, directoryPath, false, filename );
- #endif
- string path = Path.Combine( directoryPath, filename );
- using( File.Create( path ) ) { }
- return path;
- }
- public static string CreateFolderInDirectory( string directoryPath, string folderName )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- if( ShouldUseSAF )
- return AJC.CallStatic<string>( "CreateSAFEntry", Context, directoryPath, true, folderName );
- #endif
- string path = Path.Combine( directoryPath, folderName );
- Directory.CreateDirectory( path );
- return path;
- }
- public static void WriteBytesToFile( string targetPath, byte[] bytes )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- if( ShouldUseSAF )
- {
- File.WriteAllBytes( TemporaryFilePath, bytes );
- AJC.CallStatic( "WriteToSAFEntry", Context, targetPath, TemporaryFilePath, false );
- File.Delete( TemporaryFilePath );
-
- return;
- }
- #endif
- File.WriteAllBytes( targetPath, bytes );
- }
- public static void WriteTextToFile( string targetPath, string text )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- if( ShouldUseSAF )
- {
- File.WriteAllText( TemporaryFilePath, text );
- AJC.CallStatic( "WriteToSAFEntry", Context, targetPath, TemporaryFilePath, false );
- File.Delete( TemporaryFilePath );
-
- return;
- }
- #endif
- File.WriteAllText( targetPath, text );
- }
- public static void WriteCopyToFile( string targetPath, string sourceFile )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- if( ShouldUseSAF )
- {
- AJC.CallStatic( "WriteToSAFEntry", Context, targetPath, sourceFile, false );
- return;
- }
- #endif
- File.Copy( sourceFile, targetPath, true );
- }
- public static void AppendBytesToFile( string targetPath, byte[] bytes )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- if( ShouldUseSAF )
- {
- File.WriteAllBytes( TemporaryFilePath, bytes );
- AJC.CallStatic( "WriteToSAFEntry", Context, targetPath, TemporaryFilePath, true );
- File.Delete( TemporaryFilePath );
-
- return;
- }
- #endif
- using( var stream = new FileStream( targetPath, FileMode.Append, FileAccess.Write ) )
- {
- stream.Write( bytes, 0, bytes.Length );
- }
- }
- public static void AppendTextToFile( string targetPath, string text )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- if( ShouldUseSAF )
- {
- File.WriteAllText( TemporaryFilePath, text );
- AJC.CallStatic( "WriteToSAFEntry", Context, targetPath, TemporaryFilePath, true );
- File.Delete( TemporaryFilePath );
-
- return;
- }
- #endif
- File.AppendAllText( targetPath, text );
- }
- public static void AppendCopyToFile( string targetPath, string sourceFile )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- if( ShouldUseSAF )
- {
- AJC.CallStatic( "WriteToSAFEntry", Context, targetPath, sourceFile, true );
- return;
- }
- #endif
- using( Stream input = File.OpenRead( sourceFile ) )
- using( Stream output = new FileStream( targetPath, FileMode.Append, FileAccess.Write ) )
- {
- byte[] buffer = new byte[4096];
- int bytesRead;
- while( ( bytesRead = input.Read( buffer, 0, buffer.Length ) ) > 0 )
- output.Write( buffer, 0, bytesRead );
- }
- }
- public static byte[] ReadBytesFromFile( string sourcePath )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- if( ShouldUseSAF )
- {
- AJC.CallStatic( "ReadFromSAFEntry", Context, sourcePath, TemporaryFilePath );
- byte[] result = File.ReadAllBytes( TemporaryFilePath );
- File.Delete( TemporaryFilePath );
- return result;
- }
- #endif
- return File.ReadAllBytes( sourcePath );
- }
- public static string ReadTextFromFile( string sourcePath )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- if( ShouldUseSAF )
- {
- AJC.CallStatic( "ReadFromSAFEntry", Context, sourcePath, TemporaryFilePath );
- string result = File.ReadAllText( TemporaryFilePath );
- File.Delete( TemporaryFilePath );
- return result;
- }
- #endif
- return File.ReadAllText( sourcePath );
- }
- public static void ReadCopyFromFile( string sourcePath, string destinationFile )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- if( ShouldUseSAF )
- {
- AJC.CallStatic( "ReadFromSAFEntry", Context, sourcePath, destinationFile );
- return;
- }
- #endif
- File.Copy( sourcePath, destinationFile, true );
- }
- public static string RenameFile( string path, string newName )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- if( ShouldUseSAF )
- return AJC.CallStatic<string>( "RenameSAFEntry", Context, path, newName );
- #endif
- string newPath = Path.Combine( Path.GetDirectoryName( path ), newName );
- File.Move( path, newPath );
- return newPath;
- }
- public static string RenameDirectory( string path, string newName )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- if( ShouldUseSAF )
- return AJC.CallStatic<string>( "RenameSAFEntry", Context, path, newName );
- #endif
- string newPath = Path.Combine( new DirectoryInfo( path ).Parent.FullName, newName );
- Directory.Move( path, newPath );
- return newPath;
- }
- public static void DeleteFile( string path )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- if( ShouldUseSAF )
- {
- AJC.CallStatic<bool>( "DeleteSAFEntry", Context, path );
- return;
- }
- #endif
- File.Delete( path );
- }
- public static void DeleteDirectory( string path )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- if( ShouldUseSAF )
- {
- AJC.CallStatic<bool>( "DeleteSAFEntry", Context, path );
- return;
- }
- #endif
- Directory.Delete( path, true );
- }
- public static string GetFilename( string path )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- if( ShouldUseSAF )
- return AJC.CallStatic<string>( "SAFEntryName", Context, path );
- #endif
- return Path.GetFileName( path );
- }
- public static long GetFilesize( string path )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- if( ShouldUseSAF )
- return AJC.CallStatic<long>( "SAFEntrySize", Context, path );
- #endif
- return new FileInfo( path ).Length;
- }
- public static System.DateTime GetLastModifiedDate( string path )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- // Credit: https://stackoverflow.com/a/28504416/2373034
- if( ShouldUseSAF )
- return new System.DateTime( 1970, 1, 1, 0, 0, 0 ).AddMilliseconds( AJC.CallStatic<long>( "SAFEntryLastModified", Context, path ) );
- #endif
- return new FileInfo( path ).LastWriteTime;
- }
- }
- }
|