@@ -27,13 +27,15 @@ export class Android {
27
27
28
28
public readonly packageManager : PackageManager ;
29
29
public readonly contactManager : ContactManager ;
30
+ public readonly fileManager : FileManager ;
30
31
31
32
constructor ( private nodecg : NodeCG , device : string ) {
32
33
this . device = device ;
33
34
this . connected = false ;
34
35
35
36
this . packageManager = new PackageManager ( this ) ;
36
37
this . contactManager = new ContactManager ( this ) ;
38
+ this . fileManager = new FileManager ( this ) ;
37
39
}
38
40
39
41
/**
@@ -341,6 +343,9 @@ export class Android {
341
343
} ) ;
342
344
const output = await readableToString ( childProcess . stdout , "utf-8" ) ;
343
345
await onExit ( childProcess ) ;
346
+ if ( childProcess . exitCode !== null && childProcess . exitCode !== 0 ) {
347
+ throw new Error ( "adb exit code: " + childProcess . exitCode ) ;
348
+ }
344
349
return output ;
345
350
}
346
351
@@ -351,6 +356,9 @@ export class Android {
351
356
} ) ;
352
357
const output = await readableToBuffer ( childProcess . stdout ) ;
353
358
await onExit ( childProcess ) ;
359
+ if ( childProcess . exitCode !== null && childProcess . exitCode !== 0 ) {
360
+ throw new Error ( "adb exit code: " + childProcess . exitCode ) ;
361
+ }
354
362
return output ;
355
363
}
356
364
@@ -2582,6 +2590,138 @@ export type UsageStats = {
2582
2590
totalTimeVisible : number ;
2583
2591
} ;
2584
2592
2593
+ /**
2594
+ * Can be used to access files on the device. This mostly depends on parsing the output of
2595
+ * shell commands because that gives access to more parts of the file system on a non-rooted
2596
+ * device. It seems to be stable between versions and devices. Let's hope...
2597
+ *
2598
+ * Important: This only works with absolute paths. Using non-absolute paths can lead to
2599
+ * unpredictable results.
2600
+ */
2601
+ export class FileManager {
2602
+ private readonly android : Android ;
2603
+
2604
+ public readonly path : PathManager ;
2605
+
2606
+ constructor ( android : Android ) {
2607
+ this . android = android ;
2608
+ this . path = new PathManager ( android ) ;
2609
+ }
2610
+
2611
+ /**
2612
+ * Gets the file names of all entries in a directory. Using non-directory paths may
2613
+ * produce unpredictable results.
2614
+ */
2615
+ async list ( path : string ) : Promise < Array < string > > {
2616
+ return ( await this . android . rawAdb ( [ "shell" , "ls" , "-1" , quote ( path ) ] ) )
2617
+ . split ( "\n" )
2618
+ . map ( unquoteShell )
2619
+ . map ( ( e ) => e . trim ( ) )
2620
+ . filter ( ( e ) => e !== "" )
2621
+ . map ( ( e ) => ( e . endsWith ( "/" ) ? e . substring ( 0 , e . length - 1 ) : e ) ) ;
2622
+ }
2623
+
2624
+ /**
2625
+ * Gets some information about a file.
2626
+ */
2627
+ async file ( path : string ) : Promise < string > {
2628
+ return await this . android . rawAdb ( [ "shell" , "-b" , path ] ) ;
2629
+ }
2630
+
2631
+ /**
2632
+ * Downloads a file from the device. On some platforms, this gets incredibly slow when used on
2633
+ * files larger than 6MB.
2634
+ */
2635
+ async download ( device : string , local : string ) : Promise < void > {
2636
+ await this . android . rawAdb ( [ "shell" , "pull" , quote ( device ) , quote ( local ) ] ) ;
2637
+ }
2638
+
2639
+ /**
2640
+ * Uploads a file to the device. On some platforms, this gets incredibly slow when used on
2641
+ * files larger than 6MB.
2642
+ */
2643
+ async upload ( local : string , device : string ) : Promise < void > {
2644
+ await this . android . rawAdb ( [ "shell" , "push" , quote ( local ) , quote ( device ) ] ) ;
2645
+ }
2646
+ }
2647
+
2648
+ /**
2649
+ * See FileManager
2650
+ */
2651
+ export class PathManager {
2652
+ private readonly android : Android ;
2653
+
2654
+ constructor ( android : Android ) {
2655
+ this . android = android ;
2656
+ }
2657
+
2658
+ /**
2659
+ * Normalizes a path. For example this will turn `/a/b/../c` into `/a/c`.
2660
+ * This method may but doesn't need to resolve symbolic links.
2661
+ */
2662
+ async normalize ( path : string ) : Promise < string > {
2663
+ return await this . android . rawAdb ( [ "shell" , "readlink" , "-fm" , quote ( path ) ] ) ;
2664
+ }
2665
+
2666
+ /**
2667
+ * Gets whether a path exists.
2668
+ */
2669
+ async exists ( path : string ) : Promise < boolean > {
2670
+ return ( await this . android . rawAdbExitCode ( [ "shell" , "test" , "-e" , quote ( path ) ] ) ) === 0 ;
2671
+ }
2672
+
2673
+ /**
2674
+ * Gets whether a path is a regular file.
2675
+ */
2676
+ async isfile ( path : string ) : Promise < boolean > {
2677
+ return ( await this . android . rawAdbExitCode ( [ "shell" , "test" , "-f" , quote ( path ) ] ) ) === 0 ;
2678
+ }
2679
+
2680
+ /**
2681
+ * Gets whether a path is a directory.
2682
+ */
2683
+ async isdir ( path : string ) : Promise < boolean > {
2684
+ return ( await this . android . rawAdbExitCode ( [ "shell" , "test" , "-d" , quote ( path ) ] ) ) === 0 ;
2685
+ }
2686
+
2687
+ /**
2688
+ * Gets whether a path is a symbolic link.
2689
+ */
2690
+ async islink ( path : string ) : Promise < boolean > {
2691
+ return ( await this . android . rawAdbExitCode ( [ "shell" , "test" , "-L" , quote ( path ) ] ) ) === 0 ;
2692
+ }
2693
+
2694
+ /**
2695
+ * Gets whether a path is readable by you.
2696
+ */
2697
+ async readable ( path : string ) : Promise < boolean > {
2698
+ return ( await this . android . rawAdbExitCode ( [ "shell" , "test" , "-r" , quote ( path ) ] ) ) === 0 ;
2699
+ }
2700
+
2701
+ /**
2702
+ * Gets whether a path is writable by you.
2703
+ */
2704
+ async writable ( path : string ) : Promise < boolean > {
2705
+ return ( await this . android . rawAdbExitCode ( [ "shell" , "test" , "-w" , quote ( path ) ] ) ) === 0 ;
2706
+ }
2707
+
2708
+ /**
2709
+ * Gets the link target if a path is a symbolic link or a path that points to the same file if not.
2710
+ */
2711
+ async target ( path : string ) : Promise < string > {
2712
+ return await this . android . rawAdb ( [ "shell" , "readlink" , "-f" , quote ( path ) ] ) ;
2713
+ }
2714
+ }
2715
+
2585
2716
function quote ( arg : string ) : string {
2586
2717
return '"' + arg . replace ( / \\ / g, "\\\\" ) . replace ( / " / g, '\\"' ) . replace ( / ' / g, "\\'" ) . replace ( / \$ / g, "\\$" ) + '"' ;
2587
2718
}
2719
+
2720
+ function unquoteShell ( arg : string ) : string {
2721
+ return arg
2722
+ . replace ( / \\ \$ / g, "$" )
2723
+ . replace ( / \\ ' / g, "'" )
2724
+ . replace ( / \\ " / g, '"' )
2725
+ . replace ( / \\ / g, " " )
2726
+ . replace ( / \\ \\ / g, "\\" ) ;
2727
+ }
0 commit comments