package com.orgzly.android.sync;
import android.content.Context;
import com.orgzly.android.Book;
import com.orgzly.android.BookName;
import com.orgzly.android.repos.VersionedRook;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Container for local and all remote books that share the same name.
*/
public class BookNamesake {
private String name;
/** Local book. */
private Book book;
/** Remote versioned books. TODO: Is this even used? We don't want to support 1->many links, it would be too confusing. */
private List<VersionedRook> versionedRooks = new ArrayList<>();
/** Current remote book that the local one is linking to. */
private VersionedRook latestLinkedRook;
private BookSyncStatus status;
public BookNamesake(String name) {
this.name = name;
}
/**
* Create links between each local book and each remote book with the same name.
*/
public static Map<String, BookNamesake> getAll(Context context, List<Book> books, List<VersionedRook> versionedRooks) {
Map<String, BookNamesake> namesakes = new HashMap<>();
/* Create links from all local books first. */
for (Book book: books) {
BookNamesake pair = new BookNamesake(book.getName());
namesakes.put(book.getName(), pair);
pair.setBook(book);
}
/* Set repo books. */
for (VersionedRook book: versionedRooks) {
String fileName = BookName.getFileName(context, book.getUri());
String name = BookName.fromFileName(fileName).getName();
BookNamesake pair = namesakes.get(name);
if (pair == null) {
/* Local file doesn't exists, create new pair. */
pair = new BookNamesake(name);
namesakes.put(name, pair);
}
/* Add remote book. */
pair.addRook(book);
}
return namesakes;
}
public String getName() {
return name;
}
public Book getBook() {
return book;
}
public void setBook(Book book) {
this.book = book;
}
// TODO: We are always using only the first element of this list
public List<VersionedRook> getRooks() {
return versionedRooks;
}
public void addRook(VersionedRook vrook) {
this.versionedRooks.add(vrook);
}
public BookSyncStatus getStatus() {
return status;
}
public VersionedRook getLatestLinkedRook() {
return latestLinkedRook;
}
public void setLatestLinkedRook(VersionedRook linkedVersionedRook) {
this.latestLinkedRook = linkedVersionedRook;
}
public String toString() {
return "[" + this.getClass().getSimpleName() + " " + name +
" | " + status +
" | Local:" + (book != null ? book : "N/A") +
" | Remotes:" + versionedRooks.size() + "]";
}
/**
* States to consider:
*
* - Book exists
* - Book is dummy
* - Book has a link
* - Linked remote book exists
* - Book has a last-synced-with remote book
* - Remote book exists
*/
/* TODO: Case: Remote book deleted? */
public void updateStatus(int reposCount) {
/* Sanity check. Group's name must come from somewhere - local or remote books. */
if (book == null && versionedRooks.size() == 0) {
throw new IllegalStateException("BookNameGroup does not contain any books");
}
if (book == null) {
/* Remote books only */
if (versionedRooks.size() == 1) {
status = BookSyncStatus.NO_BOOK_ONE_ROOK;
} else {
status = BookSyncStatus.NO_BOOK_MULTIPLE_ROOKS;
}
return;
} else if (versionedRooks.size() == 0) {
/* Local book only */
if (book.isDummy()) { /* Only dummy exists. */
status = BookSyncStatus.ONLY_DUMMY;
} else {
if (book.getLink() != null) { /* Only local book with a link. */
status = BookSyncStatus.ONLY_BOOK_WITH_LINK;
} else { /* Only local book without link. */
if (reposCount > 1) {
status = BookSyncStatus.ONLY_BOOK_WITHOUT_LINK_AND_MULTIPLE_REPOS;
} else {
status = BookSyncStatus.ONLY_BOOK_WITHOUT_LINK_AND_ONE_REPO;
} // TODO: What about no repos?
}
}
return;
}
/* Both local book and one or more remote books exist at this point ... */
if (book.getLink() != null) { // Book has link set.
VersionedRook latestLinkedRook = getLatestLinkedRookVersion(book, versionedRooks);
if (latestLinkedRook == null) {
/* Both local and remote book exist with the same name.
* Book has a link, however that link is not pointing to an existing remote book.
*/
status = BookSyncStatus.BOOK_WITH_LINK_AND_ROOK_EXISTS_BUT_LINK_POINTING_TO_DIFFERENT_ROOK;
// TODO: So what's the problem? Just save it then? But can we just overwrite whatever is link pointing too?
return;
}
setLatestLinkedRook(latestLinkedRook);
if (book.isDummy()) {
status = BookSyncStatus.DUMMY_WITH_LINK;
return;
}
if (book.getLastSyncedToRook() == null) {
status = BookSyncStatus.CONFLICT_BOOK_WITH_LINK_AND_ROOK_BUT_NEVER_SYNCED_BEFORE;
return;
}
if (! book.getLastSyncedToRook().getUri().equals(latestLinkedRook.getUri())) {
status = BookSyncStatus.CONFLICT_LAST_SYNCED_ROOK_AND_LATEST_ROOK_ARE_DIFFERENT;
return;
}
/* Same revision, there was no remote change. */
// TODO: We get difference even if the file content is identical - if mtimes (revisions) are different - do compare content too in that case. Size first for speed.
if (book.getLastSyncedToRook().getRevision().equals(latestLinkedRook.getRevision())) {
/* Revision did not change. */
if (book.isModifiedAfterLastSync()) { // Local change.
status = BookSyncStatus.BOOK_WITH_LINK_LOCAL_MODIFIED;
} else {
status = BookSyncStatus.NO_CHANGE;
}
} else { /* Remote book has been modified. */
if (book.isModifiedAfterLastSync()) {
/* Uh oh. Both local and remote modified. */
status = BookSyncStatus.CONFLICT_BOTH_BOOK_AND_ROOK_MODIFIED;
} else {
status = BookSyncStatus.BOOK_WITH_LINK_AND_ROOK_MODIFIED;
}
}
} else { /* Local book without link. */
if (! book.isDummy()) {
status = BookSyncStatus.BOOK_WITHOUT_LINK_AND_ONE_OR_MORE_ROOKS_EXIST;
} else {
if (versionedRooks.size() == 1) {
status = BookSyncStatus.DUMMY_WITHOUT_LINK_AND_ONE_ROOK;
} else {
status = BookSyncStatus.DUMMY_WITHOUT_LINK_AND_MULTIPLE_ROOKS;
}
}
}
}
/** Find latest (current) remote book that local one links to. */
private VersionedRook getLatestLinkedRookVersion(Book book, List<VersionedRook> vrooks) {
for (VersionedRook vrook : vrooks) {
if (book.getLink().getUri().equals(vrook.getUri())) {
return vrook;
}
}
return null;
}
}