Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[mono] Implement Environment.GetFolderPath for Android #35490

Closed
EgorBo opened this issue Apr 26, 2020 · 13 comments · Fixed by #58064
Closed

[mono] Implement Environment.GetFolderPath for Android #35490

EgorBo opened this issue Apr 26, 2020 · 13 comments · Fixed by #58064

Comments

@EgorBo
Copy link
Member

EgorBo commented Apr 26, 2020

The following snippet:

var values = Enum.GetValues(typeof(global::System.Environment.SpecialFolder));
foreach (var sf in values)
{
    var path = global::System.Environment.GetFolderPath((global::System.Environment.SpecialFolder)sf);
    if (!string.IsNullOrEmpty(path))
    {
        Console.WriteLine($"{sf} = {path};");
    }
}

doesn't print anything for os=Android (all values are empty).
For reference, here is what current Xamarin.Android returns for my simple app:

Desktop = /data/user/0/com.companyname.Vederko/files/Desktop;
MyDocuments = /data/user/0/com.companyname.Vederko/files;
MyDocuments = /data/user/0/com.companyname.Vederko/files;
MyMusic = /data/user/0/com.companyname.Vederko/files/Music;
MyVideos = /data/user/0/com.companyname.Vederko/files/Videos;
DesktopDirectory = /data/user/0/com.companyname.Vederko/files/Desktop;
Fonts = /data/user/0/com.companyname.Vederko/files/.fonts;
Templates = /data/user/0/com.companyname.Vederko/files/Templates;
ApplicationData = /data/user/0/com.companyname.Vederko/files/.config;
LocalApplicationData = /data/user/0/com.companyname.Vederko/files/.local/share;
CommonApplicationData = /usr/share;
MyPictures = /data/user/0/com.companyname.Vederko/files/Pictures;
UserProfile = /data/user/0/com.companyname.Vederko/files;
CommonTemplates = /usr/share/templates;
@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added area-System.Runtime untriaged New issue has not been triaged by the area owner labels Apr 26, 2020
@EgorBo EgorBo self-assigned this Apr 26, 2020
@marek-safar marek-safar added this to the 6.0.0 milestone Jun 24, 2020
@marek-safar marek-safar removed the untriaged New issue has not been triaged by the area owner label Jun 24, 2020
@EgorBo EgorBo removed their assignment Feb 10, 2021
@MaximLipnin
Copy link
Contributor

When running the following as a functional test

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Globalization;

public static class Program
{
    public static int Main(string[] args)
    {
        int result = 42;

        var values = Enum.GetValues(typeof(global::System.Environment.SpecialFolder));
        Console.WriteLine($"Start logging ==========");
        foreach (var sf in values)
        {
            var path = global::System.Environment.GetFolderPath((global::System.Environment.SpecialFolder)sf);
            if (!string.IsNullOrEmpty(path))
            {
                Console.WriteLine($"{sf} = {path};");
            }
        }

        Console.WriteLine($"Finish logging ==========");
        return result;
    }
}

I get the following output in the log:

07-26 12:22:13.823  7529  7548 I DOTNET  : Start logging ==========
07-26 12:22:13.862  7529  7548 I DOTNET  : MyDocuments = /data/user/0/net.dot.Android.Device_Emulator.EnvironmentGetFolderPath.Test/files;
07-26 12:22:13.862  7529  7548 I DOTNET  : MyDocuments = /data/user/0/net.dot.Android.Device_Emulator.EnvironmentGetFolderPath.Test/files;
07-26 12:22:13.866  7529  7548 I DOTNET  : UserProfile = /data/user/0/net.dot.Android.Device_Emulator.EnvironmentGetFolderPath.Test/files;
07-26 12:22:13.866  7529  7548 I DOTNET  : Finish logging ==========

It seems to be expected behavior.

@MaximLipnin
Copy link
Contributor

MaximLipnin commented Jul 26, 2021

On the other hand, looking at the entire list of special folder, it still consists mostly of empty values:

07-26 15:13:59.438  8827  8845 I DOTNET  : Start logging ==========
07-26 15:13:59.495  8827  8845 I DOTNET  : Desktop = ;
07-26 15:13:59.495  8827  8845 I DOTNET  : Programs = ;
07-26 15:13:59.495  8827  8845 I DOTNET  : MyDocuments = /data/user/0/net.dot.Android.Device_Emulator.EnvironmentGetFolderPath.Test/files;
07-26 15:13:59.495  8827  8845 I DOTNET  : MyDocuments = /data/user/0/net.dot.Android.Device_Emulator.EnvironmentGetFolderPath.Test/files;
07-26 15:13:59.495  8827  8845 I DOTNET  : Favorites = ;
07-26 15:13:59.495  8827  8845 I DOTNET  : Startup = ;
07-26 15:13:59.495  8827  8845 I DOTNET  : Recent = ;
07-26 15:13:59.495  8827  8845 I DOTNET  : SendTo = ;
07-26 15:13:59.495  8827  8845 I DOTNET  : StartMenu = ;
07-26 15:13:59.495  8827  8845 I DOTNET  : MyMusic = ;
07-26 15:13:59.495  8827  8845 I DOTNET  : MyVideos = ;
07-26 15:13:59.495  8827  8845 I DOTNET  : DesktopDirectory = ;
07-26 15:13:59.496  8827  8845 I DOTNET  : MyComputer = ;
07-26 15:13:59.496  8827  8845 I DOTNET  : NetworkShortcuts = ;
07-26 15:13:59.496  8827  8845 I DOTNET  : Fonts = ;
07-26 15:13:59.496  8827  8845 I DOTNET  : Templates = ;
07-26 15:13:59.496  8827  8845 I DOTNET  : CommonStartMenu = ;
07-26 15:13:59.496  8827  8845 I DOTNET  : CommonPrograms = ;
07-26 15:13:59.496  8827  8845 I DOTNET  : CommonStartup = ;
07-26 15:13:59.496  8827  8845 I DOTNET  : CommonDesktopDirectory = ;
07-26 15:13:59.496  8827  8845 I DOTNET  : ApplicationData = ;
07-26 15:13:59.496  8827  8845 I DOTNET  : PrinterShortcuts = ;
07-26 15:13:59.503  8827  8845 I DOTNET  : LocalApplicationData = ;
07-26 15:13:59.503  8827  8845 I DOTNET  : InternetCache = ;
07-26 15:13:59.503  8827  8845 I DOTNET  : Cookies = ;
07-26 15:13:59.503  8827  8845 I DOTNET  : History = ;
07-26 15:13:59.503  8827  8845 I DOTNET  : CommonApplicationData = ;
07-26 15:13:59.503  8827  8845 I DOTNET  : Windows = ;
07-26 15:13:59.503  8827  8845 I DOTNET  : System = ;
07-26 15:13:59.503  8827  8845 I DOTNET  : ProgramFiles = ;
07-26 15:13:59.503  8827  8845 I DOTNET  : MyPictures = ;
07-26 15:13:59.504  8827  8845 I DOTNET  : UserProfile = /data/user/0/net.dot.Android.Device_Emulator.EnvironmentGetFolderPath.Test/files;
07-26 15:13:59.504  8827  8845 I DOTNET  : SystemX86 = ;
07-26 15:13:59.504  8827  8845 I DOTNET  : ProgramFilesX86 = ;
07-26 15:13:59.504  8827  8845 I DOTNET  : CommonProgramFiles = ;
07-26 15:13:59.504  8827  8845 I DOTNET  : CommonProgramFilesX86 = ;
07-26 15:13:59.504  8827  8845 I DOTNET  : CommonTemplates = ;
07-26 15:13:59.504  8827  8845 I DOTNET  : CommonDocuments = ;
07-26 15:13:59.504  8827  8845 I DOTNET  : CommonAdminTools = ;
07-26 15:13:59.504  8827  8845 I DOTNET  : AdminTools = ;
07-26 15:13:59.504  8827  8845 I DOTNET  : CommonMusic = ;
07-26 15:13:59.504  8827  8845 I DOTNET  : CommonPictures = ;
07-26 15:13:59.504  8827  8845 I DOTNET  : CommonVideos = ;
07-26 15:13:59.504  8827  8845 I DOTNET  : Resources = ;
07-26 15:13:59.504  8827  8845 I DOTNET  : LocalizedResources = ;
07-26 15:13:59.504  8827  8845 I DOTNET  : CommonOemLinks = ;
07-26 15:13:59.504  8827  8845 I DOTNET  : CDBurning = ;
07-26 15:13:59.504  8827  8845 I DOTNET  : Finish logging ==========

@EgorBo
Copy link
Member Author

EgorBo commented Jul 26, 2021

Related PR but for iOS: #34022

@directhex
Copy link
Contributor

Is HOME set on Android? It would explain why UserProfile and MyDocuments are set, but nothing else is

@filipnavara
Copy link
Member

Old Mono had a fallback to getpwuid_r if HOME is not present.

@directhex
Copy link
Contributor

It would also explain why CI never caught this - the only SpecialFolders we validate in CI are "is MyDocuments equal to $HOME"

@am11
Copy link
Member

am11 commented Aug 23, 2021

Old Mono had a fallback to getpwuid_r if HOME is not present.

Maybe we can use:

rather than reading $HOME env var directly for Android?
it does fallback to getpwuid_r for $HOME-less case:
private static unsafe bool TryGetHomeDirectoryFromPasswd(byte* buf, int bufLen, out string? path)

@directhex
Copy link
Contributor

As per #35490 (comment) the only case which is already fine is HOME (i.e. MyDocuments and UserProfile share a code path in

case SpecialFolder.UserProfile:
case SpecialFolder.MyDocuments: // same value as Personal
return home;
which returns PersistedFiles.GetHomeDirectory();)

HOME is the only case that's already fine, I don't think it matters whether that's fed by $HOME or by getpwuid_r. The issue is CommonApplicationData and friends

@am11
Copy link
Member

am11 commented Aug 23, 2021

Aha, okay! I understood that the other cases are broken because $HOME is not set, but apparently it's not the case.

@directhex
Copy link
Contributor

Looks like src/libraries/System.Private.CoreLib/src/System/Environment.GetFolderPathCore.Unix.cs is relying on XDG (Linux desktop spec) directory discovery on non-OSX

@directhex
Copy link
Contributor

Okay, even with the XDG code paths, there are a few entries which should be specified but aren't on Android, e.g.:

                case SpecialFolder.CommonApplicationData: return "/usr/share";
                case SpecialFolder.CommonTemplates: return "/usr/share/templates";

Which has me thinking the problem is GetFolderPathCore is called, but failing on Android (i.e. because SpecialFolderOption.None is being used, which means in the event that the folder doesn't already exist then return string.Empty)

What is the correct behaviour for Android? Unilaterally create the directories, or simply don't try to validate them? What did Legacy MonoDroid do? @jonpryor/@jonathanpeppers?

@jonathanpeppers
Copy link
Member

The code sample above, legacy Xamarin.Android prints:

Desktop = /data/user/0/com.companyname.app13/files/Desktop;
MyDocuments = /data/user/0/com.companyname.app13/files;
MyDocuments = /data/user/0/com.companyname.app13/files;
MyMusic = /data/user/0/com.companyname.app13/files/Music;
MyVideos = /data/user/0/com.companyname.app13/files/Videos;
DesktopDirectory = /data/user/0/com.companyname.app13/files/Desktop;
Fonts = /data/user/0/com.companyname.app13/files/.fonts;
Templates = /data/user/0/com.companyname.app13/files/Templates;
ApplicationData = /data/user/0/com.companyname.app13/files/.config;
LocalApplicationData = /data/user/0/com.companyname.app13/files/.local/share;
CommonApplicationData = /usr/share;
MyPictures = /data/user/0/com.companyname.app13/files/Pictures;
UserProfile = /data/user/0/com.companyname.app13/files;
CommonTemplates = /usr/share/templates;

Unilaterally create the directories, or simply don't try to validate them?

I definitely wouldn't try to create the directories. The value for CommonTemplates (/usr/share/templates) does not exist in my running Android emulator.

@directhex
Copy link
Contributor

So how about on Android, we switch from SpecialFolderOption.None to SpecialFolderOption.DoNotVerify?

@steveisok steveisok assigned directhex and unassigned MaximLipnin Aug 24, 2021
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Aug 24, 2021
directhex added a commit that referenced this issue Aug 30, 2021
…#58064)

This largely mirrors the approach of SpecialFolders handling on iOS, and should give the same output from that structure as legacy Xamarin

Fixes: #35490
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Aug 30, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Sep 29, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants