Skip to content

Commit

Permalink
Issue orgzly-revived#174: Parse book properties contained in preface
Browse files Browse the repository at this point in the history
This presupposes orgzly-revived/org-java#6.

Linking to notebooks via their "ID" property now works.

I did not touch the handling of the preface, so the book properties are
visible as part of the preface content. This means they can be shown and
edited in Orgzly without changing the current GUI. It feels a bit hacky,
but it could be a first, simple implementation of book properties, which
I suppose could later be improved by adding a "edit book properties"
menu choice, similar to today's "edit preface".

Book properties are stored in a separate DB table, just like note
properties. They are only parsed and stored when loading books from files and
when the preface has been edited.
  • Loading branch information
amberin committed Dec 25, 2024
1 parent 2e3ea11 commit 164a837
Show file tree
Hide file tree
Showing 18 changed files with 1,740 additions and 53 deletions.
1,533 changes: 1,533 additions & 0 deletions app/schemas/com.orgzly.android.db.OrgzlyDatabase/157.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion app/src/main/java/com/orgzly/android/AppIntent.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class AppIntent {
public static final String ACTION_UPDATE_TIMESTAMPS = "com.orgzly.intent.action.UPDATE_TIMESTAMPS";

public static final String ACTION_OPEN_NOTE = "com.orgzly.intent.action.OPEN_NOTE";
public static final String ACTION_FOLLOW_LINK_TO_NOTE_WITH_PROPERTY = "com.orgzly.intent.action.FOLLOW_LINK_TO_NOTE_WITH_PROPERTY";
public static final String ACTION_FOLLOW_LINK_TO_NOTE_OR_BOOK_WITH_PROPERTY = "com.orgzly.intent.action.FOLLOW_LINK_TO_NOTE_OR_BOOK_WITH_PROPERTY";
public static final String ACTION_FOLLOW_LINK_TO_FILE = "com.orgzly.intent.action.FOLLOW_LINK_TO_FILE";
public static final String ACTION_OPEN_SAVED_SEARCHES = "com.orgzly.intent.action.OPEN_SAVED_SEARCHES";
public static final String ACTION_OPEN_QUERY = "com.orgzly.intent.action.OPEN_QUERY";
Expand Down
18 changes: 18 additions & 0 deletions app/src/main/java/com/orgzly/android/data/DataRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ class DataRepository @Inject constructor(
val settings = OrgFileSettings.fromPreface(preface)

db.book().updatePreface(bookId, preface, settings.title)
setBookPropertiesFromPreface(bookId, preface)

updateBookIsModified(bookId, true)
}
Expand Down Expand Up @@ -1859,6 +1860,10 @@ class DataRepository @Inject constructor(
)

db.book().update(book)

// Parse and store any properties in the book's preface
if (file.preface.isNotEmpty())
setBookPropertiesFromPreface(bookId, file.preface)
}

})
Expand All @@ -1879,6 +1884,12 @@ class DataRepository @Inject constructor(
return bookId
}

private fun setBookPropertiesFromPreface(bookId: Long, preface: String) {
for (property: OrgProperty in OrgProperties.fromString(preface).all) {
db.bookProperty().upsert(bookId, property.name, property.value)
}
}

private fun getOrgRangeId(range: String?): Long? {
return getOrgRangeId(OrgRange.parseOrNull(range))
}
Expand Down Expand Up @@ -1980,6 +1991,13 @@ class DataRepository @Inject constructor(
return db.note().firstNoteHavingPropertyLowerCase(name.lowercase(), value.lowercase())
}

fun findNoteOrBookHavingProperty(name: String, value: String): Any? {
val foundNote = findNoteHavingProperty(name, value)
if (foundNote != null)
return foundNote
return db.book().firstBookHavingPropertyLowerCase(name.lowercase(), value.lowercase())
}

/*
* Saved search
*/
Expand Down
57 changes: 52 additions & 5 deletions app/src/main/java/com/orgzly/android/db/OrgzlyDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,54 @@ import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.SupportSQLiteQueryBuilder
import com.orgzly.BuildConfig
import com.orgzly.android.db.dao.*
import com.orgzly.android.db.entity.*
import com.orgzly.android.db.dao.AppLogDao
import com.orgzly.android.db.dao.BookDao
import com.orgzly.android.db.dao.BookLinkDao
import com.orgzly.android.db.dao.BookPropertyDao
import com.orgzly.android.db.dao.BookSyncDao
import com.orgzly.android.db.dao.BookViewDao
import com.orgzly.android.db.dao.DbRepoBookDao
import com.orgzly.android.db.dao.NoteAncestorDao
import com.orgzly.android.db.dao.NoteDao
import com.orgzly.android.db.dao.NoteEventDao
import com.orgzly.android.db.dao.NotePropertyDao
import com.orgzly.android.db.dao.NoteViewDao
import com.orgzly.android.db.dao.OrgRangeDao
import com.orgzly.android.db.dao.OrgTimestampDao
import com.orgzly.android.db.dao.ReminderTimeDao
import com.orgzly.android.db.dao.RepoDao
import com.orgzly.android.db.dao.RookDao
import com.orgzly.android.db.dao.RookUrlDao
import com.orgzly.android.db.dao.SavedSearchDao
import com.orgzly.android.db.dao.VersionedRookDao
import com.orgzly.android.db.entity.AppLog
import com.orgzly.android.db.entity.Book
import com.orgzly.android.db.entity.BookLink
import com.orgzly.android.db.entity.BookProperty
import com.orgzly.android.db.entity.BookSync
import com.orgzly.android.db.entity.DbRepoBook
import com.orgzly.android.db.entity.Note
import com.orgzly.android.db.entity.NoteAncestor
import com.orgzly.android.db.entity.NoteEvent
import com.orgzly.android.db.entity.NoteProperty
import com.orgzly.android.db.entity.OrgRange
import com.orgzly.android.db.entity.OrgTimestamp
import com.orgzly.android.db.entity.Repo
import com.orgzly.android.db.entity.Rook
import com.orgzly.android.db.entity.RookUrl
import com.orgzly.android.db.entity.SavedSearch
import com.orgzly.android.db.entity.VersionedRook
import com.orgzly.android.db.mappers.OrgTimestampMapper
import com.orgzly.android.util.LogUtils
import com.orgzly.org.OrgActiveTimestamps
import com.orgzly.org.datetime.OrgDateTime
import java.util.*
import java.util.Calendar

@Database(
entities = [
Book::class,
BookLink::class,
BookProperty::class,
BookSync::class,
DbRepoBook::class,
Note::class,
Expand All @@ -39,13 +75,14 @@ import java.util.*
AppLog::class
],

version = 156
version = 157
)
@TypeConverters(com.orgzly.android.db.TypeConverters::class)
abstract class OrgzlyDatabase : RoomDatabase() {

abstract fun book(): BookDao
abstract fun bookLink(): BookLinkDao
abstract fun bookProperty(): BookPropertyDao
abstract fun bookView(): BookViewDao
abstract fun bookSync(): BookSyncDao
abstract fun noteAncestor(): NoteAncestorDao
Expand Down Expand Up @@ -113,7 +150,8 @@ abstract class OrgzlyDatabase : RoomDatabase() {
MIGRATION_152_153,
MIGRATION_153_154,
MIGRATION_154_155,
MIGRATION_155_156
MIGRATION_155_156,
MIGRATION_156_157
)
.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
Expand Down Expand Up @@ -564,5 +602,14 @@ abstract class OrgzlyDatabase : RoomDatabase() {
db.execSQL("CREATE INDEX IF NOT EXISTS `index_app_logs_name` ON `app_logs` (`name`)")
}
}

private val MIGRATION_156_157 = object : Migration(156, 157) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("CREATE TABLE IF NOT EXISTS `book_properties` (`book_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `value` TEXT NOT NULL, FOREIGN KEY(`book_id`) REFERENCES `books`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE)")
db.execSQL("CREATE INDEX IF NOT EXISTS `index_book_properties_book_id` ON `book_properties` (`book_id`)")
db.execSQL("CREATE INDEX IF NOT EXISTS `index_book_properties_name` ON `book_properties` (`name`)")
db.execSQL("CREATE INDEX IF NOT EXISTS `index_book_properties_value` ON `book_properties` (`value`)")
}
}
}
}
9 changes: 8 additions & 1 deletion app/src/main/java/com/orgzly/android/db/dao/BookDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,14 @@ abstract class BookDao : BaseDao<Book> {
@Query("UPDATE books SET is_modified = 0 WHERE id IN (:ids)")
abstract fun setIsNotModified(ids: Set<Long>): Int


@Query("""
SELECT books.*
FROM book_properties
LEFT JOIN books ON (books.id = book_properties.book_id)
WHERE LOWER(book_properties.name) = :name AND LOWER(book_properties.value) = :value AND books.id IS NOT NULL
LIMIT 1
""")
abstract fun firstBookHavingPropertyLowerCase(name: String, value: String): Book?

fun getOrInsert(name: String): Long =
get(name).let {
Expand Down
41 changes: 41 additions & 0 deletions app/src/main/java/com/orgzly/android/db/dao/BookPropertyDao.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.orgzly.android.db.dao

import androidx.room.Dao
import androidx.room.Query
import androidx.room.Transaction
import com.orgzly.android.db.entity.BookProperty

@Dao
abstract class BookPropertyDao : BaseDao<BookProperty> {

@Query("SELECT * FROM book_properties WHERE book_id = :bookId")
abstract fun get(bookId: Long): List<BookProperty>

@Query("SELECT * FROM book_properties WHERE book_id = :bookId AND name = :name")
abstract fun get(bookId: Long, name: String): List<BookProperty>

@Query("SELECT * FROM book_properties")
abstract fun getAll(): List<BookProperty>

@Transaction
open fun upsert(bookId: Long, name: String, value: String) {
val properties = get(bookId, name)

if (properties.isEmpty()) {
// Insert new
insert(BookProperty(bookId, name, value))

} else {
// Update first
update(properties.first().copy(value = value))

// Delete others
for (i in 1 until properties.size) {
delete(properties[i])
}
}
}

@Query("DELETE FROM book_properties WHERE book_id = :bookId")
abstract fun delete(bookId: Long)
}
34 changes: 34 additions & 0 deletions app/src/main/java/com/orgzly/android/db/entity/BookProperty.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.orgzly.android.db.entity

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index

@Entity(
tableName = "book_properties",

primaryKeys = [ "book_id" ],

foreignKeys = [
ForeignKey(
entity = Book::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("book_id"),
onDelete = ForeignKey.CASCADE)
],

indices = [
Index("book_id"),
Index("name"),
Index("value")
]
)
data class BookProperty(
@ColumnInfo(name = "book_id")
val bookId: Long,

val name: String,

val value: String
)
14 changes: 7 additions & 7 deletions app/src/main/java/com/orgzly/android/ui/main/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,8 @@ private void setupDisplay(Bundle savedInstanceState) {
private void handleOrgProtocolIntent(Intent intent) {
OrgProtocol.handleOrgProtocol(intent, new OrgProtocol.Listener() {
@Override
public void onNoteWithId(@NonNull String id) {
viewModel.followLinkToNoteWithProperty("ID", id);
public void onNoteOrBookWithId(@NonNull String id) {
viewModel.followLinkToNoteOrBookWithProperty("ID", id);
}

@Override
Expand Down Expand Up @@ -575,7 +575,7 @@ protected void onResumeFragments() {

LocalBroadcastManager bm = LocalBroadcastManager.getInstance(this);
bm.registerReceiver(receiver, new IntentFilter(AppIntent.ACTION_OPEN_NOTE));
bm.registerReceiver(receiver, new IntentFilter(AppIntent.ACTION_FOLLOW_LINK_TO_NOTE_WITH_PROPERTY));
bm.registerReceiver(receiver, new IntentFilter(AppIntent.ACTION_FOLLOW_LINK_TO_NOTE_OR_BOOK_WITH_PROPERTY));
bm.registerReceiver(receiver, new IntentFilter(AppIntent.ACTION_FOLLOW_LINK_TO_FILE));
bm.registerReceiver(receiver, new IntentFilter(AppIntent.ACTION_OPEN_SAVED_SEARCHES));
bm.registerReceiver(receiver, new IntentFilter(AppIntent.ACTION_OPEN_QUERY));
Expand Down Expand Up @@ -896,8 +896,8 @@ public static void followLinkToFile(String path) {
LocalBroadcastManager.getInstance(App.getAppContext()).sendBroadcast(intent);
}

public static void followLinkToNoteWithProperty(String name, String value) {
Intent intent = new Intent(AppIntent.ACTION_FOLLOW_LINK_TO_NOTE_WITH_PROPERTY);
public static void followLinkToNoteOrBookWithProperty(String name, String value) {
Intent intent = new Intent(AppIntent.ACTION_FOLLOW_LINK_TO_NOTE_OR_BOOK_WITH_PROPERTY);
intent.putExtra(AppIntent.EXTRA_PROPERTY_NAME, name);
intent.putExtra(AppIntent.EXTRA_PROPERTY_VALUE, value);
LocalBroadcastManager.getInstance(App.getAppContext()).sendBroadcast(intent);
Expand Down Expand Up @@ -971,10 +971,10 @@ private void handleIntent(@NonNull Intent intent, @NonNull String action) {
break;
}

case AppIntent.ACTION_FOLLOW_LINK_TO_NOTE_WITH_PROPERTY: {
case AppIntent.ACTION_FOLLOW_LINK_TO_NOTE_OR_BOOK_WITH_PROPERTY: {
String name = intent.getStringExtra(AppIntent.EXTRA_PROPERTY_NAME);
String value = intent.getStringExtra(AppIntent.EXTRA_PROPERTY_VALUE);
viewModel.followLinkToNoteWithProperty(name, value);
viewModel.followLinkToNoteOrBookWithProperty(name, value);
break;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import com.orgzly.android.ui.SingleLiveEvent
import com.orgzly.android.usecase.BookScrollToNote
import com.orgzly.android.usecase.BookSparseTreeForNote
import com.orgzly.android.usecase.LinkFindTarget
import com.orgzly.android.usecase.NoteFindWithProperty
import com.orgzly.android.usecase.NoteOrBookFindWithProperty
import com.orgzly.android.usecase.NoteUpdateClockingState
import com.orgzly.android.usecase.SavedSearchExport
import com.orgzly.android.usecase.SavedSearchImport
Expand Down Expand Up @@ -75,9 +75,9 @@ class MainActivityViewModel(private val dataRepository: DataRepository) : Common
navigationActions.postValue(MainNavigationAction.DisplayQuery(query))
}

fun followLinkToNoteWithProperty(name: String, value: String) {
fun followLinkToNoteOrBookWithProperty(name: String, value: String) {
App.EXECUTORS.diskIO().execute {
val useCase = NoteFindWithProperty(name, value)
val useCase = NoteOrBookFindWithProperty(name, value)

catchAndPostError {
val result = UseCaseRunner.run(useCase)
Expand All @@ -87,18 +87,25 @@ class MainActivityViewModel(private val dataRepository: DataRepository) : Common
errorEvent.postValue(Throwable(msg))

} else {
val noteIdBookId = result.userData as NoteDao.NoteIdBookId

when (AppPreferences.linkTarget(App.getAppContext())) {
"note_details" ->
navigationActions.postValue(
MainNavigationAction.OpenNote(noteIdBookId.bookId, noteIdBookId.noteId))

"book_and_sparse_tree" ->
UseCaseRunner.run(BookSparseTreeForNote(noteIdBookId.noteId))

"book_and_scroll" ->
UseCaseRunner.run(BookScrollToNote(noteIdBookId.noteId))
when (result.userData) {
is NoteDao.NoteIdBookId -> {
val noteIdBookId = result.userData

when (AppPreferences.linkTarget(App.getAppContext())) {
"note_details" ->
navigationActions.postValue(
MainNavigationAction.OpenNote(noteIdBookId.bookId, noteIdBookId.noteId))

"book_and_sparse_tree" ->
UseCaseRunner.run(BookSparseTreeForNote(noteIdBookId.noteId))

"book_and_scroll" ->
UseCaseRunner.run(BookScrollToNote(noteIdBookId.noteId))
}
}
is Book -> {
navigationActions.postValue(MainNavigationAction.OpenBook(result.userData.id))
}
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions app/src/main/java/com/orgzly/android/ui/main/OrgProtocol.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import com.orgzly.android.util.LogUtils
// TODO: Complete
object OrgProtocol {
interface Listener {
fun onNoteWithId(id: String)
fun onNoteOrBookWithId(id: String)

fun onQuery(query: String)

Expand Down Expand Up @@ -49,9 +49,9 @@ object OrgProtocol {

if (id != null) {
if (BuildConfig.LOG_DEBUG)
LogUtils.d(TAG, "Open note with property ID $id")
LogUtils.d(TAG, "Open note or book with property ID $id")

listener.onNoteWithId(id)
listener.onNoteOrBookWithId(id)

} else {
listener.onError("Missing “$ID_PARAM” param in $uri")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ import com.orgzly.android.ui.views.style.DrawerMarkerSpan
interface ActionableRichTextView {
fun toggleDrawer(markerSpan: DrawerMarkerSpan)
fun toggleCheckbox(checkboxSpan: CheckboxSpan)
fun followLinkToNoteWithProperty(name: String, value: String)
fun followLinkToNoteOrBookWithProperty(name: String, value: String)
fun followLinkToFile(path: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,8 @@ class RichText(context: Context, attrs: AttributeSet?) :
}

// TODO: Consider getting MainActivity's *ViewModel* here instead
override fun followLinkToNoteWithProperty(name: String, value: String) {
MainActivity.followLinkToNoteWithProperty(name, value)
override fun followLinkToNoteOrBookWithProperty(name: String, value: String) {
MainActivity.followLinkToNoteOrBookWithProperty(name, value)
}

override fun followLinkToFile(path: String) {
Expand Down
Loading

0 comments on commit 164a837

Please sign in to comment.