Skip to content
This repository has been archived by the owner on Apr 16, 2022. It is now read-only.

Issue 479 reload cache after creating it #495

Merged
merged 26 commits into from Jun 6, 2018
Merged

Issue 479 reload cache after creating it #495

merged 26 commits into from Jun 6, 2018

Conversation

ggalmazor
Copy link
Contributor

@ggalmazor ggalmazor commented Jun 6, 2018

Closes #479

What has been done to verify that this works as intended?

Written new tests that cover all the form cache's behavior.
Also tested manually:

  1. From a clean state, no prefs, no forms in storage dir, use the official release of Briefcase to pull some forms
  2. Launch this PR and see that all the expected forms are there

Also tested manually to set/clear to different storage locations on my computer to see that each time I got the expected form list

Also tested manually to pull new forms and existing forms to check that the push/export tabs where updated accordingly.

Why is this the best possible solution? Were any other approaches considered?

I could have patched a smaller change into the FormCache file, but its code was long overdue a review and some tests.

While I was writing the tests I could see how Briefcase was coupled to different parts of it and how some methods inside where doing too many things at once that were getting in the way of testing it properly.

After having a good coverage of the implementation I was able to refactor it to simplify the design of parts of Briefcase that depend on the form cache.

I also have reviewed all the event flow pulling new forms > updating the cache > updating UI elements to make it more coherent and reduce duplicated processes.

Are there any risks to merging this code? If so, what are they?

Nope.

Does this change require updates to documentation? If so, please file an issue at https://github.com/opendatakit/docs/issues/new and include the link below.

Nope.

- This will let us update the cache before setting it in FileSystemUtils
- Once we have decided that we need to create a new cache file, we scan the filesystem to update its contents
- This can happen when the serialized FormCache object has a different serialUUID after an update to Briefcase
- This also must happen when the user sets a new storage directory
- This can happen on restart or when the user sets the storage directory
- Items that aren't present in the filesystem anymore should be removed
- Mainly, migrate the code to use Streams and Java NIO2, and add semantics
- Lose an unwanted? side effect that would delete files from the user's computer when they can't be processed by the cache updating method
- Also some rename some members of the class
- That way we don't need to pass the briefcase directory every time we need to update the cache
- We cant't rely on the shutdown hook because the user might change the storage location before closing Briefcase and we would lose data
- Also change the tight coupling between the ServerFetcher and the FormCache with a loose coupling using the EventBus (this will make further refactoring of both classes easier)
- We have to write new export panel ui tests anyway
- Then use it in the SettingsPanel whenever the user sets a new storage location
- Aknowledge that the cache file and briefcase dir can be not present by changing their types to Optional in FormCache
- Now, when the user clears the storage location, the form cache updates its state and launches an update event accordingly
- This will let the UI panels update their contents accordingly
- The panel should always show the contents of the incoming forms from the cache
- We can't just replace the form list because all the FormStatus instances might have state that we would lose
@ggalmazor
Copy link
Contributor Author

@lognaturel, could you review this one? I know you've had problems to use the new version and this PR should solve any transition problem

@yanokwa yanokwa requested a review from dcbriccetti June 6, 2018 13:17
Set<String> scannedFiles = new HashSet<>();
list(path.resolve("forms"))
.map(this::getFormFilePath)
.peek(p -> scannedFiles.add(p.toString()))
Copy link
Contributor

Choose a reason for hiding this comment

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

What’s the benefit of using peek here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

None whatsoever. I'll remove that

}

@SuppressWarnings("unchecked")
public void setLocation(Path newBriefcaseDir) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like now when setting a location to an existing directory with a valid cache, that cache is rebuilt. Is that intended?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, we can't be sure that the user hasn't changed the files inside. The only time we don't want to rebuild it is on startup, which, now that you mention, I think I need to review

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reviewed!

- This is: each time new forms are pulled and whenever the user sets/unsets the briefcase directory
});
hashByPath.keySet().stream().filter(p -> !scannedFiles.contains(p)).collect(toList()).forEach(hashByPath::remove);
formDefByPath.keySet().stream().filter(p -> !scannedFiles.contains(p)).collect(toList()).forEach(formDefByPath::remove);
EventBus.publish(new CacheUpdateEvent());
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you need the collect here? Here’s my rewrite, with the common predicate extracted.

Predicate<String> notScanned = p -> !scannedFiles.contains(p);
hashByPath.keySet().stream().filter(notScanned).forEach(hashByPath::remove);
formDefByPath.keySet().stream().filter(notScanned).forEach(formDefByPath::remove);

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this a problem?

If the map is modified while an iteration over the set is in progress (except through the iterator's own remove operation), the results of the iteration are undefined.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, the collect is to prevent that error. Otherwise, you are removing elements while you iterate the lists

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not very elegant. I'm open to suggetsions ;)

Copy link
Contributor

Choose a reason for hiding this comment

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

Wow, look what IDEA just suggested.

hashByPath.keySet().removeIf(notScanned);
formDefByPath.keySet().removeIf(notScanned);

I didn’t check that it removes the elements from the map behind the key set view, but I think it does.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's neat! I'll add that

Copy link
Contributor

Choose a reason for hiding this comment

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

I checked:

Map<String, String> m = new HashMap<>();
m.put("a", "aa");
m.put("b", "bb");
System.out.println(m);
m.keySet().removeIf(k -> k.equals("a"));
System.out.println(m);

{a=aa, b=bb}
{b=bb}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh my, that's very troubling... maps are super mutable.

@lognaturel
Copy link
Member

My review of the code was relatively surface but nothing jumped out at me as problematic. This behavior is inline with what I expect! The build failure is just an unneeded import.

@lognaturel
Copy link
Member

Oh, and the commit messages are really helpful for understanding the rationale of each change, thank you @ggalmazor.


List<FormStatus> forms = FileSystemUtils.getODKFormList(odkDir.toFile()).stream()
.map(form -> new FormStatus(FormStatus.TransferType.GATHER, form))
.collect(toList());

TransferFromODK.pull(BriefcasePreferences.buildBriefcaseDir(Paths.get(storageDir)), odkDir, forms);
TransferFromODK.pull(briefcaseDir, odkDir, forms);
// The form cache should update and save itself now
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure this comment will age well (provide value much in the future)

}
}
});
// Warning: Remove map entries by mutating the key set works because the key set is a view on the map
Copy link
Contributor

Choose a reason for hiding this comment

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

Let this be a trigger warning for all you functional purists. ;-)

Copy link
Contributor

@dcbriccetti dcbriccetti left a comment

Choose a reason for hiding this comment

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

It’s nice to see the Briefcase code improving so much.

formDefByPath.keySet().stream().filter(p -> !scannedFiles.contains(p)).collect(toList()).forEach(formDefByPath::remove);
// Warning: Remove map entries by mutating the key set works because the key set is a view on the map
hashByPath.keySet().removeIf(key -> !scannedFiles.contains(key));
formDefByPath.keySet().removeIf(key -> !scannedFiles.contains(key));
Copy link
Contributor

Choose a reason for hiding this comment

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

Somehow I knew you would reject my suggestion to factor out the common predicate. We’re getting to know each other! :-)

@dcbriccetti dcbriccetti merged commit 04c7660 into getodk:master Jun 6, 2018
@ggalmazor ggalmazor deleted the issue_479_reload_cache_after_creating_it branch June 6, 2018 19:08
@kkrawczyk123
Copy link
Contributor

Verified on Ubuntu, MacOS and Windows
@opendatakit-bot unlabel "needs testing"
@opendatakit-bot label "behavior verified"

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants