Search in sources :

Example 1 with NotePosition

use of com.orgzly.android.NotePosition in project orgzly-android by orgzly.

the class PasteNotesAction method run.

@Override
public int run(SQLiteDatabase db) {
    long batchMinLft;
    long batchMaxRgt;
    long batchMinLevel;
    long foldedUnder = 0;
    Cursor cursor = db.query(DbNote.TABLE, new String[] { "min(" + DbNote.LFT + ")", "max(" + DbNote.RGT + ")", "min(" + DbNote.LEVEL + ")" }, DbNote.IS_CUT + " = " + batchId, null, null, null, null);
    try {
        if (cursor.moveToFirst()) {
            batchMinLft = cursor.getLong(0);
            batchMaxRgt = cursor.getLong(1);
            batchMinLevel = cursor.getLong(2);
        } else {
            return 0;
        }
    } finally {
        cursor.close();
    }
    NotePosition targetNotePosition = DbNote.getPosition(db, targetNoteId);
    long pastedLft, pastedLevel, pastedParentId;
    /* If target note is hidden, hide pasted under the same note. */
    if (targetNotePosition.getFoldedUnderId() != 0) {
        foldedUnder = targetNotePosition.getFoldedUnderId();
    }
    switch(place) {
        case ABOVE:
            pastedLft = targetNotePosition.getLft();
            pastedLevel = targetNotePosition.getLevel();
            pastedParentId = targetNotePosition.getParentId();
            break;
        case UNDER:
            NotePosition lastHighestLevelDescendant = getLastHighestLevelDescendant(db, targetNotePosition);
            if (lastHighestLevelDescendant != null) {
                /* Insert batch after last descendant with highest level. */
                pastedLft = lastHighestLevelDescendant.getRgt() + 1;
                pastedLevel = lastHighestLevelDescendant.getLevel();
            } else {
                /* Insert batch just under the target note. */
                pastedLft = targetNotePosition.getLft() + 1;
                pastedLevel = targetNotePosition.getLevel() + 1;
            }
            if (targetNotePosition.isFolded()) {
                foldedUnder = targetNoteId;
            }
            pastedParentId = targetNoteId;
            break;
        case BELOW:
            pastedLft = targetNotePosition.getRgt() + 1;
            pastedLevel = targetNotePosition.getLevel();
            pastedParentId = targetNotePosition.getParentId();
            break;
        default:
            throw new IllegalArgumentException("Unsupported place for paste: " + place);
    }
    int positionsRequired = (int) (batchMaxRgt - batchMinLft + 1);
    long positionOffset = pastedLft - batchMinLft;
    long levelOffset = pastedLevel - batchMinLevel;
    /*
         * Make space for new notes incrementing lft and rgt.
         * FIXME: This could be slow.
         */
    String bookSelection = DatabaseUtils.whereUncutBookNotes(targetNotePosition.getBookId());
    GenericDatabaseUtils.incrementFields(db, DbNote.TABLE, bookSelection + " AND " + DbNote.LFT + " >= " + pastedLft, positionsRequired, ProviderContract.Notes.UpdateParam.LFT);
    GenericDatabaseUtils.incrementFields(db, DbNote.TABLE, "(" + bookSelection + " AND " + DbNote.RGT + " >= " + pastedLft + ") OR " + DbNote.LEVEL + " = 0", positionsRequired, ProviderContract.Notes.UpdateParam.RGT);
    /* Make sure batch has no no FOLDED_UNDER_ID IDs which do not belong to the batch itself. */
    db.execSQL("UPDATE " + DbNote.TABLE + " SET " + DbNote.FOLDED_UNDER_ID + " = 0 WHERE " + DbNote.IS_CUT + " = " + batchId + " AND " + DbNote.FOLDED_UNDER_ID + " NOT IN (SELECT " + DbNote._ID + " FROM " + DbNote.TABLE + " WHERE " + DbNote.IS_CUT + " = " + batchId + ")");
    /* Mark batch as folded. */
    if (foldedUnder != 0) {
        ContentValues values = new ContentValues();
        values.put(DbNote.FOLDED_UNDER_ID, foldedUnder);
        String where = DbNote.IS_CUT + " = " + batchId + " AND " + DbNote.FOLDED_UNDER_ID + " = 0";
        db.update(DbNote.TABLE, values, where, null);
    }
    /* Update parent of the root of the batch. */
    ContentValues values = new ContentValues();
    values.put(DbNote.PARENT_ID, pastedParentId);
    db.update(DbNote.TABLE, values, DbNote.IS_CUT + " = " + batchId + " AND " + DbNote.LFT + " = " + batchMinLft, null);
    /* Move batch to the new position. */
    String set = DbNote.LFT + " = " + DbNote.LFT + " + " + positionOffset + ", " + DbNote.RGT + " = " + DbNote.RGT + " + " + positionOffset + ", " + DbNote.LEVEL + " = " + DbNote.LEVEL + " + " + levelOffset + ", " + DbNote.BOOK_ID + "= " + targetNotePosition.getBookId();
    String sql = "UPDATE " + DbNote.TABLE + " SET " + set + " WHERE " + DbNote.IS_CUT + " = " + batchId;
    db.execSQL(sql);
    /* Insert ancestors for all notes of the batch. */
    db.execSQL("INSERT INTO " + DbNoteAncestor.TABLE + " (" + DbNoteAncestor.BOOK_ID + ", " + DbNoteAncestor.NOTE_ID + ", " + DbNoteAncestor.ANCESTOR_NOTE_ID + ") " + "SELECT n." + DbNote.BOOK_ID + ", n." + DbNote._ID + ", a." + DbNote._ID + " FROM " + DbNote.TABLE + " n " + " JOIN " + DbNote.TABLE + " a ON (n." + DbNote.BOOK_ID + " = a." + DbNote.BOOK_ID + " AND a." + DbNote.LFT + " < n." + DbNote.LFT + " AND n." + DbNote.RGT + " < a." + DbNote.RGT + ") " + "WHERE n." + DbNote.IS_CUT + " = " + batchId + "  AND " + "a." + DbNote.LEVEL + " > 0");
    /* Make the batch visible. */
    db.execSQL("UPDATE " + DbNote.TABLE + " SET " + DbNote.IS_CUT + " = 0 WHERE " + DbNote.IS_CUT + " = " + batchId);
    /* Update number of descendants for ancestors and the note itself. */
    String where = DatabaseUtils.whereAncestorsAndNote(targetNotePosition.getBookId(), targetNoteId);
    DatabaseUtils.updateDescendantsCount(db, where);
    /* Delete other batches. */
    db.execSQL("DELETE FROM " + DbNote.TABLE + " WHERE " + DbNote.IS_CUT + " != 0");
    DatabaseUtils.updateBookMtime(db, targetNotePosition.getBookId());
    return 0;
}
Also used : ContentValues(android.content.ContentValues) NotePosition(com.orgzly.android.NotePosition) Cursor(android.database.Cursor)

Example 2 with NotePosition

use of com.orgzly.android.NotePosition in project orgzly-android by orgzly.

the class NotesClient method fromCursor.

public static Note fromCursor(Cursor cursor, boolean withExtras) {
    long id = idFromCursor(cursor);
    long createdAt = cursor.getLong(cursor.getColumnIndex(DbNoteView.CREATED_AT));
    int contentLines = cursor.getInt(cursor.getColumnIndex(DbNoteView.CONTENT_LINE_COUNT));
    OrgHead head = headFromCursor(cursor);
    NotePosition position = DbNote.positionFromCursor(cursor);
    Note note = new Note();
    note.setHead(head);
    note.setId(id);
    note.setCreatedAt(createdAt);
    note.setPosition(position);
    note.setContentLines(contentLines);
    if (withExtras) {
        String inheritedTags = cursor.getString(cursor.getColumnIndex(DbNoteView.INHERITED_TAGS));
        if (!TextUtils.isEmpty(inheritedTags)) {
            note.setInheritedTags(DbNote.dbDeSerializeTags(inheritedTags));
        }
    }
    return note;
}
Also used : OrgHead(com.orgzly.org.OrgHead) NotePosition(com.orgzly.android.NotePosition) Note(com.orgzly.android.Note) DbNote(com.orgzly.android.provider.models.DbNote)

Example 3 with NotePosition

use of com.orgzly.android.NotePosition in project orgzly-android by orgzly.

the class DbNote method positionFromCursor.

public static NotePosition positionFromCursor(Cursor cursor) {
    long bookId = cursor.getLong(cursor.getColumnIndex(BOOK_ID));
    int level = cursor.getInt(cursor.getColumnIndex(LEVEL));
    long lft = cursor.getLong(cursor.getColumnIndex(LFT));
    long rgt = cursor.getLong(cursor.getColumnIndex(RGT));
    int descendantsCount = cursor.getInt(cursor.getColumnIndex(DESCENDANTS_COUNT));
    long foldedUnderId = cursor.getLong(cursor.getColumnIndex(FOLDED_UNDER_ID));
    long parentId = cursor.getLong(cursor.getColumnIndex(PARENT_ID));
    int isFolded = cursor.getInt(cursor.getColumnIndex(IS_FOLDED));
    NotePosition position = new NotePosition();
    position.setLevel(level);
    position.setBookId(bookId);
    position.setLft(lft);
    position.setRgt(rgt);
    position.setDescendantsCount(descendantsCount);
    position.setFoldedUnderId(foldedUnderId);
    position.setParentId(parentId);
    position.setIsFolded(isFolded != 0);
    return position;
}
Also used : NotePosition(com.orgzly.android.NotePosition)

Example 4 with NotePosition

use of com.orgzly.android.NotePosition in project orgzly-android by orgzly.

the class Provider method insertNote.

private Uri insertNote(SQLiteDatabase db, Uri uri, ContentValues values, Place place) {
    NotePosition notePos = new NotePosition();
    long bookId = values.getAsLong(ProviderContract.Notes.UpdateParam.BOOK_ID);
    /* If new note is inserted relative to some other note, get info about that target note. */
    long refNoteId = 0;
    NotePosition refNotePos = null;
    if (place != Place.UNSPECIFIED) {
        refNoteId = Long.valueOf(uri.getPathSegments().get(1));
        refNotePos = DbNote.getPosition(db, refNoteId);
    }
    switch(place) {
        case ABOVE:
            notePos.setLevel(refNotePos.getLevel());
            notePos.setLft(refNotePos.getLft());
            notePos.setRgt(refNotePos.getLft() + 1);
            notePos.setParentId(refNotePos.getParentId());
            break;
        case UNDER:
            notePos.setLevel(refNotePos.getLevel() + 1);
            notePos.setLft(refNotePos.getRgt());
            notePos.setRgt(refNotePos.getRgt() + 1);
            notePos.setParentId(refNoteId);
            /*
                 * If note is being created under already folded note, mark it as such
                 * so it doesn't show up.
                 */
            if (refNotePos.isFolded()) {
                notePos.setFoldedUnderId(refNoteId);
            }
            break;
        case BELOW:
            notePos.setLevel(refNotePos.getLevel());
            notePos.setLft(refNotePos.getRgt() + 1);
            notePos.setRgt(refNotePos.getRgt() + 2);
            notePos.setParentId(refNotePos.getParentId());
            break;
        case UNSPECIFIED:
            /* If target note is not used, add note at the end with level 1. */
            long rootRgt = getMaxRgt(db, bookId);
            long rootId = getRootId(db, bookId);
            notePos.setLevel(1);
            notePos.setLft(rootRgt);
            notePos.setRgt(rootRgt + 1);
            notePos.setParentId(rootId);
            break;
        default:
            throw new IllegalArgumentException("Unsupported place for new note: " + place);
    }
    switch(place) {
        case ABOVE:
        case UNDER:
        case BELOW:
            /* Make space for new note - increment notes' LFT and RGT. */
            DatabaseUtils.makeSpaceForNewNotes(db, 1, refNotePos, place);
            /*
                 * If new note can be an ancestor, increment descendants count of all
                 * its ancestors.
                 */
            incrementDescendantsCountForAncestors(db, bookId, notePos.getLft(), notePos.getRgt());
        case UNSPECIFIED:
            /* Make space for new note - increment root's RGT. */
            String selection = DbNote.BOOK_ID + " = " + bookId + " AND " + DbNote.LFT + " = 1";
            GenericDatabaseUtils.incrementFields(db, DbNote.TABLE, selection, 2, ProviderContract.Notes.UpdateParam.RGT);
    }
    notePos.setBookId(bookId);
    DbNote.toContentValues(values, notePos);
    replaceTimestampRangeStringsWithIds(db, values);
    long id = db.insertOrThrow(DbNote.TABLE, null, values);
    db.execSQL("INSERT INTO " + DbNoteAncestor.TABLE + " (" + DbNoteAncestor.BOOK_ID + ", " + DbNoteAncestor.NOTE_ID + ", " + DbNoteAncestor.ANCESTOR_NOTE_ID + ") " + "SELECT " + field(DbNote.TABLE, DbNote.BOOK_ID) + ", " + field(DbNote.TABLE, DbNote._ID) + ", " + field("a", DbNote._ID) + " FROM " + DbNote.TABLE + " JOIN " + DbNote.TABLE + " a ON (" + field(DbNote.TABLE, DbNote.BOOK_ID) + " = " + field("a", DbNote.BOOK_ID) + " AND " + field("a", DbNote.LFT) + " < " + field(DbNote.TABLE, DbNote.LFT) + " AND " + field(DbNote.TABLE, DbNote.RGT) + " < " + field("a", DbNote.RGT) + ")" + " WHERE " + field(DbNote.TABLE, DbNote._ID) + " = " + id + " AND " + field("a", DbNote.LEVEL) + " > 0");
    return ContentUris.withAppendedId(uri, id);
}
Also used : NotePosition(com.orgzly.android.NotePosition)

Example 5 with NotePosition

use of com.orgzly.android.NotePosition in project orgzly-android by orgzly.

the class Provider method loadBookFromReader.

private Uri loadBookFromReader(final String bookName, final String repoUrl, final String rookUrl, final String rookRevision, final long rookMtime, final String format, final Reader inReader, final String usedEncoding, final String detectedEncoding, final String selectedEncoding) throws IOException {
    long startedAt = System.currentTimeMillis();
    Uri uri;
    /* Gets a writable database. This will trigger its creation if it doesn't already exist. */
    final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    /* Create book if it doesn't already exist. */
    uri = getOrInsertBook(db, bookName);
    final long bookId = ContentUris.parseId(uri);
    /* Delete all notes from book. TODO: Delete all other references to this book ID */
    db.delete(DbNote.TABLE, DbNote.BOOK_ID + "=" + bookId, null);
    final Map<String, Long> propNameDbIds = new HashMap<>();
    {
        Cursor cursor = db.query(DbPropertyName.TABLE, new String[] { DbPropertyName._ID, DbPropertyName.NAME }, null, null, null, null, null);
        try {
            for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
                propNameDbIds.put(cursor.getString(1), cursor.getLong(0));
            }
        } finally {
            cursor.close();
        }
    }
    final Map<String, Long> propValueDbIds = new HashMap<>();
    {
        Cursor cursor = db.query(DbPropertyValue.TABLE, new String[] { DbPropertyValue._ID, DbPropertyValue.VALUE }, null, null, null, null, null);
        try {
            for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
                propValueDbIds.put(cursor.getString(1), cursor.getLong(0));
            }
        } finally {
            cursor.close();
        }
    }
    /*
         * Maps node's lft to database id.
         * Used to update parent id and insert ancestors.
         * Not using SparseArray as speed is preferred over memory here.
         */
    @SuppressLint("UseSparseArrays") final HashMap<Long, Long> lft2id = new HashMap<>();
    /* Set of ids for which parent is already set. */
    final Set<Long> notesWithParentSet = new HashSet<>();
    boolean useCreatedAtProperty = AppPreferences.createdAt(getContext());
    String createdAtProperty = AppPreferences.createdAtProperty(getContext());
    /* Open reader. */
    Reader reader = new BufferedReader(inReader);
    try {
        /*
             * Create and run parser.
             * When multiple formats are supported, decide which parser to use here.
             */
        new OrgParser.Builder().setInput(reader).setTodoKeywords(AppPreferences.todoKeywordsSet(getContext())).setDoneKeywords(AppPreferences.doneKeywordsSet(getContext())).setListener(new OrgNestedSetParserListener() {

            @Override
            public void onNode(OrgNodeInSet node) throws IOException {
                BookSizeValidator.validate(node);
                /* Insert note with book id at specific position. */
                NotePosition position = new NotePosition();
                position.setBookId(bookId);
                position.setLft(node.getLft());
                position.setRgt(node.getRgt());
                position.setLevel(node.getLevel());
                position.setDescendantsCount(node.getDescendantsCount());
                ContentValues values = new ContentValues();
                // Update ContentValues
                DbNote.toContentValues(values, position);
                if (useCreatedAtProperty) {
                    DbNote.toContentValues(values, node.getHead().getProperties(), createdAtProperty);
                }
                DbNote.toContentValues(db, values, node.getHead());
                long noteId = db.insertOrThrow(DbNote.TABLE, null, values);
                /* Insert note's properties. */
                int pos = 1;
                OrgProperties noteProperties = node.getHead().getProperties();
                for (String propName : noteProperties.keySet()) {
                    String propValue = noteProperties.get(propName);
                    Long nameId = propNameDbIds.get(propName);
                    if (nameId == null) {
                        nameId = DbPropertyName.getOrInsert(db, propName);
                        propNameDbIds.put(propName, nameId);
                    }
                    Long valueId = propValueDbIds.get(propValue);
                    if (valueId == null) {
                        valueId = DbPropertyValue.getOrInsert(db, propValue);
                        propValueDbIds.put(propValue, valueId);
                    }
                    long propertyId = DbProperty.getOrInsert(db, nameId, valueId);
                    DbNoteProperty.getOrInsert(db, noteId, pos++, propertyId);
                }
                /*
                             * Update parent ID and insert ancestors.
                             * Going through all descendants - nodes between lft and rgt.
                             *
                             *  lft:  1    2    3    4    5   6
                             *            L2   l1   r2   R2
                             */
                lft2id.put(node.getLft(), noteId);
                for (long index = node.getLft() + 1; index < node.getRgt(); index++) {
                    Long descendantId = lft2id.get(index);
                    if (descendantId != null) {
                        if (!notesWithParentSet.contains(descendantId)) {
                            values = new ContentValues();
                            values.put(DbNote.PARENT_ID, noteId);
                            db.update(DbNote.TABLE, values, DbNote._ID + " = " + descendantId, null);
                            notesWithParentSet.add(descendantId);
                        }
                        values = new ContentValues();
                        values.put(DbNoteAncestor.NOTE_ID, descendantId);
                        values.put(DbNoteAncestor.ANCESTOR_NOTE_ID, noteId);
                        values.put(DbNoteAncestor.BOOK_ID, bookId);
                        db.insert(DbNoteAncestor.TABLE, null, values);
                    }
                }
            }

            @Override
            public void onFile(OrgFile file) throws IOException {
                BookSizeValidator.validate(file);
                ContentValues values = new ContentValues();
                BooksClient.toContentValues(values, file.getSettings());
                /* Set preface. TODO: Move to and rename OrgFileSettings */
                values.put(DbBook.PREFACE, file.getPreface());
                values.put(DbBook.USED_ENCODING, usedEncoding);
                values.put(DbBook.DETECTED_ENCODING, detectedEncoding);
                values.put(DbBook.SELECTED_ENCODING, selectedEncoding);
                db.update(DbBook.TABLE, values, DbBook._ID + "=" + bookId, null);
            }
        }).build().parse();
    } finally {
        reader.close();
    }
    if (BuildConfig.LOG_DEBUG)
        LogUtils.d(TAG, bookName + ": Parsing done in " + (System.currentTimeMillis() - startedAt) + " ms");
    if (rookUrl != null) {
        updateOrInsertBookLink(db, bookId, repoUrl, rookUrl);
        updateOrInsertBookSync(db, bookId, repoUrl, rookUrl, rookRevision, rookMtime);
    }
    /* Mark book as complete. */
    ContentValues values = new ContentValues();
    values.put(DbBook.IS_DUMMY, 0);
    db.update(DbBook.TABLE, values, DbBook._ID + "=" + bookId, null);
    return uri;
}
Also used : ContentValues(android.content.ContentValues) OrgFile(com.orgzly.org.OrgFile) OrgNodeInSet(com.orgzly.org.parser.OrgNodeInSet) HashMap(java.util.HashMap) NotePosition(com.orgzly.android.NotePosition) SqliteQueryBuilder(com.orgzly.android.query.sql.SqliteQueryBuilder) Reader(java.io.Reader) InputStreamReader(java.io.InputStreamReader) BufferedReader(java.io.BufferedReader) IOException(java.io.IOException) Cursor(android.database.Cursor) Uri(android.net.Uri) OrgNestedSetParserListener(com.orgzly.org.parser.OrgNestedSetParserListener) SQLiteDatabase(android.database.sqlite.SQLiteDatabase) BufferedReader(java.io.BufferedReader) SuppressLint(android.annotation.SuppressLint) OrgProperties(com.orgzly.org.OrgProperties) HashSet(java.util.HashSet)

Aggregations

NotePosition (com.orgzly.android.NotePosition)22 OrgzlyTest (com.orgzly.android.OrgzlyTest)13 Test (org.junit.Test)13 Book (com.orgzly.android.Book)11 Note (com.orgzly.android.Note)11 Cursor (android.database.Cursor)6 ContentValues (android.content.ContentValues)5 NotePlace (com.orgzly.android.ui.NotePlace)4 SuppressLint (android.annotation.SuppressLint)1 SQLiteDatabase (android.database.sqlite.SQLiteDatabase)1 Uri (android.net.Uri)1 DbNote (com.orgzly.android.provider.models.DbNote)1 SqliteQueryBuilder (com.orgzly.android.query.sql.SqliteQueryBuilder)1 OrgFile (com.orgzly.org.OrgFile)1 OrgHead (com.orgzly.org.OrgHead)1 OrgProperties (com.orgzly.org.OrgProperties)1 OrgNestedSetParserListener (com.orgzly.org.parser.OrgNestedSetParserListener)1 OrgNodeInSet (com.orgzly.org.parser.OrgNodeInSet)1 BufferedReader (java.io.BufferedReader)1 IOException (java.io.IOException)1