-
Notifications
You must be signed in to change notification settings - Fork 248
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
OpenTaskPal module (ContentPal extension for TaskProvider). #421 #422
Conversation
Missed to create |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
first pass done.
{ | ||
public DueData(@NonNull Long dueTimeStamp) | ||
{ | ||
super(new SimpleRowData<TaskContract.Tasks>(TaskContract.Tasks.DUE, dueTimeStamp)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should also set START and DURATION to null.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand the need to set DURATION to null, but wouldn't this class be used also to just update DUE, keeping START? Or for that with need to use TimeData
and retrieve START first?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if this changes the all-day flag? Start would become invalid in that case. I'd say that this can not be used to update only due.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(addressed)
.withValue(TaskContract.Tasks.DTSTART, null) | ||
.withValue(TaskContract.Tasks.DUE, null) | ||
.withValue(TaskContract.Tasks.DURATION, null); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A task without times is currently not allowed to recur, so RDATE, RRULE and EXDATE should also be set to null.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also wipe out all-day and time zone.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
*/ | ||
public final class DueData extends DelegatingRowData<TaskContract.Tasks> | ||
{ | ||
public DueData(@NonNull Long dueTimeStamp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Date-time values should be typed DateTime
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(addressed)
* | ||
* @author Gabor Keszthelyi | ||
*/ | ||
public final class TimeZoneData implements RowData<TaskContract.Tasks> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting time zone alone doesn't make sense. Since it's part of DateTime
it should be set by the other date & time RowData
implementations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally OpenTasksPal provides high-level access to the content provider. Check out SingleEventData
in CalendarPal it takes DateTime
s and automatically sets timestamps, time zones and all-day flags. Loading or setting a time zone or an all-day flag without loading or setting the timestamp too doesn't make much sense and is considered error prone.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is only one timezone and all-day flag for tasks, I assume start
and due
with different timezone should be rejected, right? What about the all-day flag? Take it only from start, or validate if its the same for start
and due
?
Does it make sense to set all-day flag in DueTime
from due?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No different time zones should not be rejected. Actually we should update the provider to support this case. For now just shift start to the due time zone.
But start and due must have the same all-day flag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(addressed)
Updated the time |
Updated to latest contentpal master and removed all related files from here. |
} | ||
|
||
DateTime start = mStart; | ||
if (mDue != null && !TimeZoneEqualator.INSTANCE.areEqual(mStart, mDue)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no need for this Equalator. DateTime is smart enough to return the same instance if the timezone is considered to be the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean that there is no need to compare the getId()
String
value but use reference equality on the getTimeZone()
TimeZone
s? Like if (timeZone1 == timeZone2)
?
Don't we depend on some undocumented internals of DateTime
this way?
I've updated the code, removed the Equalator
and added a comment about this behavior, but I think it would be clearer to check the getId()
values. TimeZone
doesn't provide equals()
, so we are really depending on reference equality here which doesn't seem to be safe.
I can do that in private method if you are not consent with that Equalator
.
(Although it could be easier to test that code in isolation and ideally just mock it out in (upcoming) TimeZoneTest
if it could be injected there, but it couldn't.. so it may not be important.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No that's not what I meant. I've added a new comment to the new code. The comment above was a bit misleading.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(addressed)
public ContentProviderOperation.Builder updatedBuilder(@NonNull TransactionContext transactionContext, @NonNull ContentProviderOperation.Builder builder) | ||
{ | ||
return builder | ||
.withValue(TaskContract.Tasks.DUE, mDue.getTimestamp()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also set allday flag and time zone.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
.withValue(TaskContract.Tasks.DTSTART, null) | ||
.withValue(TaskContract.Tasks.DURATION, null) | ||
|
||
// A task without times is currently not allowed to recur: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but this one has a time!? The point is that we can't update due without updating recurrence because updating due usually also affects recurrence.
For now it's ok to null the recurrence columns because recurrence is not really supported yet. But the reason stated in the comment doesn't apply here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that was copied from Slack, but it probably meant 'recurrence times' originally, should have added that... anyway, I've removed the comment.
*/ | ||
public final class OriginalInstanceSyncIdData extends DelegatingRowData<TaskContract.Tasks> | ||
{ | ||
// TODO Should this be @Nullable? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No because you should not be allowed to clear this once it has been created.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, removed the TODO.
*/ | ||
public final class SyncIdData extends DelegatingRowData<TaskContract.Tasks> | ||
{ | ||
// TODO Should this be @Nullable? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, removed the TODO.
|
||
.withValue(TaskContract.Tasks.DURATION, duration) | ||
|
||
// A task without times is currently not allowed to recur: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this task has times.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the comment.
*/ | ||
public final class TitleData extends DelegatingRowData<TaskContract.Tasks> | ||
{ | ||
public TitleData(@Nullable CharSequence title) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The title of a task should not be allowed to be null
. You may set it to an empty string, but null doesn't make sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, changed to @NonNull
.
I see, thanks, I've updated the code. Although, I think we still depend a bit on the knowledge of |
Waiting for the |
https://github.com/dmfs/ContentPal/pull/51 | ||
https://github.com/dmfs/ContentPal/pull/52 | ||
*/ | ||
new EqArg(TaskContract.Tasks.LIST_ID, taskListRow.values().charData(BaseColumns._ID).value("-1")))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll wait for dmfs/ContentPal#75 to implement this. The current approach is not reliable enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#75 has been merged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I've replaced this with the ReferringTo(Predicate)
.
* A view onto the {@link TaskContract.Tasks} table which contains only tasks from a specific task list. | ||
* Tasks created with {@link #insertOperation(UriParams)} will automatically be added to this task list. | ||
* <p> | ||
* Note, if you create a {@link View} (using {@link #view(ContentProviderClient, String...)}) with a virtual {@link RowSnapshot}, the {@link View} will always |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI: I've added dmfs/ContentPal#81 to address this.
@Override | ||
public ContentProviderOperation.Builder updatedBuilder(@NonNull TransactionContext transactionContext, @NonNull ContentProviderOperation.Builder builder) | ||
{ | ||
return builder.withValue(TaskContract.TaskLists.LIST_NAME, mName.toString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not delegate to a CharSequenceRowData
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CharSequenceRowData
allows null
, so NameData
wouldn't fail with that. Maybe we need another class for that. ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm.. I am not sure of the correct approach here actually. We don't validate for null
just use the @Nullable
@NonNull
annotations. Should that be enough in these cases? I mean if there wasn't a .toString()
call here, it wouldn't fail either without a validation.
Similar question come up with OriginalInstanceSyncIdData
below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
apparently we need another delegate which doesn't allow null
. Or make sure CharSequenceRowData
doesn't allow null
and find another, better solution to "erase" values in the database.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comes back to the topic of whether to delete or leave a value alone when the incoming update doesn't have it (which may be represented as null
). Because that's what makes using Optional
instead of the null
s ambiguous as you mentioned before.
I don't know the actual usage in syncadapters.. can the operations maybe be distinguished based on whether they are in 'override' mode or 'fill in the missing data' mode?
If so, could this maybe be a flag? So we could either have an extended Optional
model with that flag or pass it in the constructor as separate parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the class with a TODO about this nullability.
*/ | ||
public final class OriginalInstanceSyncIdData extends DelegatingRowData<TaskContract.Tasks> | ||
{ | ||
public OriginalInstanceSyncIdData(@NonNull String originalInstanceSyncId) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OriginalInstanceSyncId could also be just a CharSequence
. So you probably should delegate to CharSequenceRowData
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the class with a TODO about this nullability.
{ | ||
public SyncIdData(@NonNull String syncId) | ||
{ | ||
super(new RawRowData<TaskContract.Tasks>(TaskContract.Tasks._SYNC_ID, syncId)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, could be a CharSequence
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the class with a TODO about this nullability.
} | ||
|
||
|
||
public TimeData(@NonNull DateTime start, @NonNull String duration) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duration
should be passed as a Duration
object not as a String
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
@NonNull | ||
private final DateTime mStart; | ||
@Nullable | ||
private final DateTime mDue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe make them (mDue
and mDuration
) Optional
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I've changed it. We still use @Nullable
at the ctor params? That's what I am not sure of, I don't see the end usage - is it going to be part of an 'automatic' syncronization mapping where it is good if 'null' can be passed on, or is the 'normal' usage, like how I would use the class from UI code is what we should aim for? In this latter case, I would put Optional
in the constructor as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, we should not use null at all here.
it should look like this:
private TimeData(@NonNull DateTime start, @NonNull Optional<DateTime> due, @NonNull Optional<Duration> duration)
{
mStart = start;
mDue = due;
mDuration = duration;
}
public TimeData(@NonNull DateTime start, @NonNull DateTime due)
{
this(start, new Present<>(due), absent());
}
…
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apart from a new nitpicks it looks ok.
@dmfs Could you please help a little with this?:
I am not sure of the relationship between all day and the timezones and the ways |
About the DateTime timezone<->all-day flag question, I figured they are mutually exclusive, right? I've update to latest contentpal and added unit tests. Let me know if I should squash. |
Technically you could have a non-allday task without time zone. That's called floating time. But OpenTasks doesn't really support that so it's ok for now. Maybe create an issue to remind us about this. Same goes for the nullable. We'll keep it like this and revisit later on when we may have a better idea of how to handle it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's go with this and see where it takes us. We still can adjust things if it turns out they don't work well.
Note that these are only the classes and constructors that were needed for the test cases, so this is not a complete ContentPal support for TaskProvider at the moment. I think we can extend it later, for example when test coverage is improved, since test and opentaskpal will be in same project so both can be extended in the same commits.
Also note some TODOs are pending on ContentPal PRs.