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

File dialog locations #1108

Closed

Conversation

charlesdaniels
Copy link
Member

@charlesdaniels charlesdaniels commented Jun 15, 2020

Description:

Fixes #821

Checklist:

  • Tests included.
  • Lint and formatter run with no errors.
  • Tests all pass.

Where applicable:

  • Public APIs match existing style.
  • [ ] Any breaking changes have a deprecation path or have been discussed. (N/A)

This swaps ~/ and ./ as the default directory.

I have also made / the failover if both of those error. That *shouldn't*
happen though.

Note that / should be safe even on Windows, since NT supports
POSIX-style paths. `/` should expand to NTFS's root, which is almost
always `C:\`.

XXX: might be worth setting the failover dir in the platform driver. `/`
is probably not a good failover choice for iOS, although I'm not sure
the file dialog even makes sense there, since they have their own system
one? I'm not an iOS dev, so I'm not sure.
This adds the concept of a "starting directory", which may be set with a
new setter method. If there is a problem with this directory, or it is
an empty string, then the CWD is preferred, followed by the user home,
followed by `/`.

This also adds two new utility methods, ShowFileOpenAt, and
ShowFileSaveAt which allow the starting directory to be explicitly set
as arguments.

This should not break the existing API.
@charlesdaniels
Copy link
Member Author

I'm not sure how best to test this. Based on looking at file_test.go, it seems there wouldn't be a good way to get at the FileDialog object from the perspective of the test case. Even if we could, it there is no way to know what directories might exist on the system running the tests.

Aside from the uncertainty about testing, I think this can be considered for merging, pending any other feedback.

@andydotxyz
Copy link
Member

The added complexity in the function to show should probably be factored out into a new function. If that was responsible for looking up starting directory that probably also answers how can it be tested :).

One stylistic thought - SetStartingDirectory could be omitted if the startingDirectory field was exported - would that be better or not? :)

@andydotxyz
Copy link
Member

Im also not sure about the “where” string parameter to ShowFileOpenAt... - it’s not a very descriptive variable name but possibly more of a question is, should it not be a URI?

@Jacalz
Copy link
Member

Jacalz commented Jun 15, 2020

I agree with Andy and I think that a better name for the “where” variable might be “startdir” or something along the lines. Apart from that, I think that it all looks really good 👍 Good job and congratulations for making your first contribution to Fyne 🙂🎉

Per @andydotxyz I have moved the logic to calculate the starting
directory for the file dialog widget to it's own function.
@charlesdaniels
Copy link
Member Author

I agree with the proposed changes by @andydotxyz and @Jacalz and have implemented them in db857e1, I will add tests presently. Thank you for the feedback! Let me know if you think of anything else.

@charlesdaniels charlesdaniels changed the title WiP: File dialog locations File dialog locations Jun 15, 2020
@charlesdaniels charlesdaniels marked this pull request as ready for review June 15, 2020 21:59
@charlesdaniels
Copy link
Member Author

Tests are implemented as of 6fbdb2f, please let me know if there is further feedback.

@charlesdaniels
Copy link
Member Author

Seems the build is failing do to inconsistent receiver names. To my knowledge, this was not caused by my code changes. I can fix this issue if desired, but I think it is out of scope for this PR.

@andydotxyz
Copy link
Member

It looks like it is your new method - you used the reciever file instead of f.
An odd test failure can be chalked up to random - but here 3/4 of them are failing, probably indicates taking a closer look ;)

Copy link
Member

@andydotxyz andydotxyz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart from the test failures this is good stuff. However I think that file paths are not cross-platform enough so may need to be URI for stating location

dialog/file.go Outdated
parent fyne.Window
dialog *fileDialog
dismissText string
StartingDirectory string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need a URI based approach really, paths may not work across platforms

@charlesdaniels charlesdaniels mentioned this pull request Jun 16, 2020
5 tasks
@charlesdaniels
Copy link
Member Author

This is blocked while we determine how best to handle a URI based approach as @andydotxyz mentioned. I have opened #1111 to merge just changing the start directory to ./ for now.

charlesdaniels added a commit to charlesdaniels/fyne that referenced this pull request Jun 16, 2020
This implements just the effectiveStartingDir() and a basic test from PR fyne-io#1108.
It does provide the ability to set a custom starting dir.
@andydotxyz
Copy link
Member

It is blocked I agree - but probably worth fixing the reciever name and probably rebasing on top of the merged #1111 in preparation :)

This implements just the effectiveStartingDir() and a basic test from PR fyne-io#1108.
It does provide the ability to set a custom starting dir.

Cherry picked from fyne-io#1111 to fyne-io#1108 to avoid future merge conflicts.
@charlesdaniels
Copy link
Member Author

I have "rebased" (this was simple enough I just cherry picked) on top of the work one in #1111.

@andydotxyz
Copy link
Member

Strange, that Windows failure was a keyboard test. I have restarted it to see if the issue is persistent.

@andydotxyz
Copy link
Member

Woops, this should be off develop, I think you can just edit the PR, not certain...

@andydotxyz andydotxyz changed the base branch from master to develop June 28, 2020 18:26
andydotxyz added a commit that referenced this pull request Jun 28, 2020
We await the full solution in PR #1108
@andydotxyz
Copy link
Member

Just to put some thoughts here from a discussion we had.

  • Clearly we want to be using URIs so to support the same cross-platform strategies that opening/saving files does
  • If the system is using URIs for location it realistically can't rely on file system lookups for getting a directory listing
  • Therefore we probably need to convert the file dialog to use URI functionality only internnaly, which leads to the challenge that we cannot list URIs currently.

Proposal / suggestion (This was my takeaway from our discussion - I know there were alternatives posed - this is to keep the discussion going).

Create a new type that is able to list children from a given URI:

type ListableURI interface {
    URI
    ListURIs() []URI
}

OK the naming probably wants some thought, but using this approach we could identify "directories" and list them like so:

dir, isDir := myURI.(fyne.ListableURI)
...
for _, child := range dir.ListURIs() {
...
}

And the new Location field of the FileDialog could be of type fyne.ListableURI.

@andydotxyz
Copy link
Member

Yes, the URIs should never have windows separators in them - a file URI needs to be something that we can share irrespective of operating system.

Of course the varying locations of and is a fun challenge for the future :)...

@charlesdaniels
Copy link
Member Author

@PucklaMotzer09

Thanks for checking this out. However as @andydotxyz the code I thought was broken was actually a red herring -- we need to make sure that any Windows-style paths get correctly changed to use / everywhere they are ingested, a change that I plan to implement.

Your PR looks good, but I was actually planning to move this inside of NewURI. I will merge it and make the needed tweaks on my end.

Thanks!

Fix some issues related to ListableURI and dialog
Copy link
Member

@andydotxyz andydotxyz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work and really close.
A couple of things inline about tests mostly.
Also some windows code that looks like it can be removed and we're forcing file:// for filedialog but is that really needed now?


p, _ = storage.Child(storage.NewURI("file:///foo/bar/"), "baz")
assert.Equal(t, "file:///foo/bar/baz", p.String())
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a child test for windows as well - it should behave the same obviously, but it's best to check given the OS specific code elsewhere

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't URIs always have / instead of \? If so then this test should work for all platforms.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a test that uses an URI with \ as input.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the slashes should always be the same, but I thought it was worth testing with the file://C:/ prefix version for windows

if runtime.GOOS == "windows" {
// Only the Windows version of filepath will know how to handle
// backslashes.
parent, err = storage.Parent(storage.NewURI("file://C:\\foo\\bar\\baz\\"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not clear here that storage.NewURI is doing the path conversion rather than parent...
Could it be split into two checks so that the result of NewURI is a correct URI, and then that is passed to the parent function?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I seperated it.

// trim the scheme (and +1 for the :)
s = s[len(u.Scheme())+1:]
// trim the scheme
s = s[len(u.Scheme())+3:]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why +3? the comment about the +1 has gone so it's not clear?
If it's for :// then beware that the "//" is optional in URI spec

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

beware that the "//" is optional in URI spec

We are using len(Scheme())+3 everywhere. There should be some functions like Path() which returns only the path of an URI to be complient with that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this code is improving the very way that URIs are being parsed and created - so we should be trying to improve rather than replicating the same issues in the previous implementations :).

The reason that Path() was not added immediately iis that if people thought this was just about files then they might do os.File operations on the result of URI.Path(), which is often not going to work.
In the future we can add it, once we're cleanly using abstractions in our own code. But this is a breaking change as the URI interface is public so it would not happen before 2.0.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about path() for internal use?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have pitched the idea of having a standardized method for retrieving the parts of the URI that aren't the scheme in the past. I still think it would be a good ideas as @PucklaMotzer09 mentioned, even if it was only for internal use. We could also put it in storage like Parent for now, if we wanted it to be public.

storage/uri.go Outdated
if s[len(s)-1] == '\\' {
s = s[0 : len(s)-1]
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this not be in an else? Would a windows path with / at the end expect to have it truncated?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the check for windows since all URIs now have /

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think a trailing \ is no longer relevant if we are normalizing everything to /.

storage/uri.go Outdated
components := strings.Split(location, "/")

if len(components) == 1 {
// trimmed <= 2 makes sure we handle UNIX-style paths on
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment seems out of date somehow?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And if this method is explicitly for handling non-file URIs why is windows path handling in here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the comment

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that the right fix? If a developer thought a comment was needed then surely there is a wider issue at play?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the same, but I couldn't think of a proper comment, since I don't even know why the comment was there in the first place.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think this is not a relevant comment anymore. This was part of the logic for handling Windows drive letters.

@@ -59,53 +61,131 @@ func (u *uri) String() string {
return u.raw
}

// parentGeneric is a generic function that returns the last element of a
// path after splitting it on "/". It should be suitable for most URIs.
func parentGeneric(location string) (string, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this has not got tests because we're testing only the file version

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added multiple tests for this functions

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I never got around to testing it explicitly after I switched to using filepath. Thank you @PucklaMotzer09

assert.False(t, open.Disabled())

test.Tap(open)
assert.Nil(t, win.Canvas().Overlays().Top())
assert.Nil(t, openErr)
assert.Equal(t, "file://"+target.path, chosen.URI().String())

targetLocationString := target.location.String()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be needed as you are comparing a URI with a URI

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it back since now all URIs just have /

dialog/file.go Outdated

// On windows replace '\\' with '/'
if runtime.GOOS == "windows" {
pathString := path.String()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

path is now a URI so this should not be needed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it back

dialog/file.go Outdated
func (f *fileDialog) loadFavorites() ([]fyne.CanvasObject, error) {
osHome, err := os.UserHomeDir()
if err != nil {
return nil, err
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this and all those below I don't think we should bomb out if one fails - let's add the favourites that we can and return the last error along with what we build...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rewrote the function. Now it always runs until the end and returns the last error.

dialog/file.go Outdated
buildDir := filepath.VolumeName(dir)
for i, d := range strings.Split(dir, string(filepath.Separator)) {

if dir.Scheme() != "file" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this required? it seems like the dialog is abstracted now...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the check. StartingLocation is ignored if it doesn't have file as scheme.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it ignored? can we not allow nfs:// or ftp:// URIs in the directory picker?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we not allow nfs:// or ftp:// URIs in the directory picker?

I don't know. That's just the way @charlesdaniels implemented it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wrote it that way since we don't actually have support for those things yet, and at the time I was under the impression that it would just cause it to break later. That might not be the case anymore, so I think we can remove this check if others are under the impression it won't break.

I don't remember specifically setting it to ignore StartingLocation with non-file schemes. That was probably an oversight on my part, or again I was assuming it would break later and wanted to catch the error early. I should have commented it better.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In effectiveStartingDir() the scheme gets checked against file and if it doesn't match the function just skips to PWD and HomeDir.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should be enforcing the scheme type - if the StartingLocation is a valid ListableURI then we should use it.

Copy link
Member

@Jacalz Jacalz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly a commented out callback that I think might be a mistake and the change to default starting location being PWD. I think we agreed on HomeDir being default as it makes more sense for users and that PWD mostly makes more sense to developers.

dialog/file.go Outdated
@@ -97,11 +114,11 @@ func (f *fileDialog) makeUI() fyne.CanvasObject {
func(ok bool) {
f.win.Hide()
if !ok {
callback(nil, nil)
// callback(nil, nil)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this supposed to be commented out?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not. This wasn't really ready for review, I was probably testing something.

dialog/file.go Outdated
}

// Try to get ./
wd, err := os.Getwd()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My view is still that HomeDir() should be preferred over PWD. I think that this also was what we agreed on when talking about this in earlier discussions because PWD makes more sense for the developer than what it does for a user. A user of the application would probably be more happy to have Home than PWD.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a comment from a user.. When an app is launched from an icon, it isn't obvious what PWD would be. This may be as simple as it being documented somewhere but being able to rely on $HOME would be straight forward.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My proposal would be to have HomeDir the default and then developers wanting PWD could just put in a temporary different starting directory now that we, with this PR, have support for changing starting directory.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a debate we had previously in #1111, at which point we decided that ./ would be the best starting location.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this at today’s call and we (@stuartmscott, @andydotxyz and me) concluded that HomeDir should be the default as it is “the least likely to surprise the user” and having PWD can easily be toggled with few lines of code now that this work has landed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well if that was the decision.

Honestly, I think there has been enough contention on this point that maybe we should consider making it a preference the user can set? I find it infuriating that a few programs I use regularly open ~ rather than my working directory, since I then have to nagivate through many levels of file hierarchy.

A compromise might be to add ./ to "favorites"?

Copy link
Member

@Jacalz Jacalz Sep 25, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. I think adding PWD to favorites when PWD != HomeDir would be a good addition.

@stuartmscott
Copy link
Member

@charlesdaniels consider using https://golang.org/pkg/strings/#TrimPrefix to remove the scheme:// instead of slicing. It may require a few invocations (1 to remove the scheme, 1 to remove the colon, and 1 to remove the optional double forward slash) but I think it could improve readability.

@charlesdaniels
Copy link
Member Author

Most of the comments raised here seem reasonable, although this wasn't ready to review yet (sorry, I should have set it back to draft when the Windows problems cropped up). Many of these were things I had intended to tidy up. I will look through in more detail when I get a chance.

@PucklaJ
Copy link
Contributor

PucklaJ commented Sep 25, 2020

@charlesdaniels Could you give me write access to your fork so that I can directly commit to it.

@PucklaJ
Copy link
Contributor

PucklaJ commented Sep 25, 2020

I made some changes, but I want to directly push them to this fork. You can look my changes here

@andydotxyz
Copy link
Member

Thanks for all the contributions @PucklaMotzer09, it's a great help.
I think that most developers around here use their own forks as only write access for themselves - the only collaborative spaces are really the upstream develop and occasional feature branches.
I know that @charlesdaniels is very busy this week but I am sure he will pull your changes into his branch soon so the result will be the same as if you'd written directly.
We're still a few weeks away from the proposed time of our next release so there is plenty of opportunity to get this all wrapped up in plenty of time :).

@charlesdaniels
Copy link
Member Author

As Andy said, thanks for your help @PucklaMotzer09. I would be happy to review your changes and will probably merge them. Is there some reason you can't just open a PR against my fork, as you did before? That would make it a lot easier to review and comment on the changes.

@PucklaJ
Copy link
Contributor

PucklaJ commented Sep 25, 2020

Okay I'll just open a PR. I just thought It would be easier to have write access. Because it would let me commit faster if there were still some changes I should make. But a PR is also fine.

@charlesdaniels
Copy link
Member Author

@andydotxyz per our Slack discussion, I think this is ready to turn over:

  • As far as I am aware, it should not error for non-file:// URIs, though it may not work. (e.g. startdir is ignored without error if it is non-file://. I think @PucklaMotzer09 addressed this in his changes.

  • I re-enabled the commented out code that @Jacalz noted.

  • I have reverted the default starting directory to be ~ rather than . (I would like to note for the record that I oppose this change, and think the start directory should continue to be the CWD, but the consensus is against me here).

  • I have removed the "open at" type constructors.

  • I have also modified the TestShowFileOpen test to explicitly assert that the first element of the path bar is /, which I think is the intended behavior, but I would like someone else double-check the changes I made to it in 5b5c3a3.

@andydotxyz
Copy link
Member

  • I have also modified the TestShowFileOpen test to explicitly assert that the first element of the path bar is /, which I think is the intended behavior

That is correct on Unix :) (just not Windows)

@andydotxyz andydotxyz mentioned this pull request Oct 9, 2020
4 tasks
@andydotxyz
Copy link
Member

Thanks - really, really close. Replaced by #1379 where we will complete it.

@andydotxyz andydotxyz closed this Oct 9, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

FileDialog: Set start directory
6 participants