Search in sources :

Example 61 with Media

use of com.ichi2.libanki.Media in project Anki-Android by ankidroid.

the class CardContentProvider method insertMediaFile.

private Uri insertMediaFile(ContentValues values, Collection col) {
    // Insert media file using libanki.Media.addFile and return Uri for the inserted file.
    Uri fileUri = Uri.parse(values.getAsString(FlashCardsContract.AnkiMedia.FILE_URI));
    String preferredName = values.getAsString(FlashCardsContract.AnkiMedia.PREFERRED_NAME);
    try {
        ContentResolver cR = mContext.getContentResolver();
        Media media = col.getMedia();
        // idea, open input stream and save to cache directory, then
        // pass this (hopefully temporary) file to the media.addFile function.
        // return eg "jpeg"
        String fileMimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType(cR.getType(fileUri));
        // should we be enforcing strict mimetypes? which types?
        File tempFile;
        File externalCacheDir = mContext.getExternalCacheDir();
        if (externalCacheDir == null) {
            Timber.e("createUI() unable to get external cache directory");
            return null;
        }
        File tempMediaDir = new File(externalCacheDir.getAbsolutePath() + "/temp-media");
        if (!tempMediaDir.exists() && !tempMediaDir.mkdir()) {
            Timber.e("temp-media dir did not exist and could not be created");
            return null;
        }
        try {
            tempFile = File.createTempFile(// the beginning of the filename.
            preferredName + "_", // this is the extension, if null, '.tmp' is used, need to get the extension from MIME type?
            "." + fileMimeType, tempMediaDir);
            tempFile.deleteOnExit();
        } catch (Exception e) {
            Timber.w(e, "Could not create temporary media file. ");
            return null;
        }
        FileUtil.internalizeUri(fileUri, tempFile, cR);
        String fname = media.addFile(tempFile);
        Timber.d("insert -> MEDIA: fname = %s", fname);
        File f = new File(fname);
        Timber.d("insert -> MEDIA: f = %s", f);
        Uri uriFromF = Uri.fromFile(f);
        Timber.d("insert -> MEDIA: uriFromF = %s", uriFromF);
        return Uri.fromFile(new File(fname));
    } catch (IOException | EmptyMediaException e) {
        Timber.w(e, "insert failed from %s", fileUri);
        return null;
    }
}
Also used : Media(com.ichi2.libanki.Media) IOException(java.io.IOException) Uri(android.net.Uri) File(java.io.File) EmptyMediaException(com.ichi2.libanki.exception.EmptyMediaException) JSONException(com.ichi2.utils.JSONException) DeckRenameException(com.ichi2.libanki.backend.exception.DeckRenameException) ConfirmModSchemaException(com.ichi2.anki.exception.ConfirmModSchemaException) IOException(java.io.IOException) ContentResolver(android.content.ContentResolver) EmptyMediaException(com.ichi2.libanki.exception.EmptyMediaException)

Example 62 with Media

use of com.ichi2.libanki.Media in project Anki-Android by ankidroid.

the class Connection method doInBackgroundSync.

/**
 * In the payload, success means that the sync did occur correctly and that a change did occur.
 * So success can be false without error, if no change occurred at all.
 */
private Payload doInBackgroundSync(Payload data) {
    sIsCancellable = true;
    Timber.d("doInBackgroundSync()");
    // Block execution until any previous background task finishes, or timeout after 5s
    boolean ok = TaskManager.waitToFinish(5);
    // Unique key allowing to identify the user to AnkiWeb without password
    String hkey = (String) data.data[0];
    // Whether media should be synced too
    boolean media = (Boolean) data.data[1];
    // If normal sync can't occur, what to do
    ConflictResolution conflictResolution = (ConflictResolution) data.data[2];
    // A number AnkiWeb told us to send back. Probably to choose the best server for the user
    HostNum hostNum = (HostNum) data.data[3];
    // Use safe version that catches exceptions so that full sync is still possible
    Collection col = CollectionHelper.getInstance().getColSafe(AnkiDroidApp.getInstance());
    boolean colCorruptFullSync = false;
    if (!CollectionHelper.getInstance().colIsOpen() || !ok) {
        if (FULL_DOWNLOAD == conflictResolution) {
            colCorruptFullSync = true;
        } else {
            return returnGenericError(data);
        }
    }
    try {
        CollectionHelper.getInstance().lockCollection();
        RemoteServer remoteServer = new RemoteServer(this, hkey, hostNum);
        Syncer client = new Syncer(col, remoteServer, hostNum);
        // run sync and check state
        boolean noChanges = false;
        if (conflictResolution == null) {
            Timber.i("Sync - starting sync");
            publishProgress(R.string.sync_prepare_syncing);
            Pair<ConnectionResultType, Object> ret = client.sync(this);
            data.message = client.getSyncMsg();
            if (ret == null) {
                return returnGenericError(data);
            }
            if (NO_CHANGES != ret.first && SUCCESS != ret.first) {
                data.success = false;
                data.resultType = ret.first;
                data.result = new Object[] { ret.second };
                // Check if there was a sanity check error
                if (SANITY_CHECK_ERROR == ret.first) {
                    // Force full sync next time
                    col.modSchemaNoCheck();
                    col.save();
                }
                return data;
            }
            // save and note success state
            if (NO_CHANGES == ret.first) {
                // publishProgress(R.string.sync_no_changes_message);
                noChanges = true;
            }
        } else {
            try {
                // Disable sync cancellation for full-sync
                sIsCancellable = false;
                FullSyncer fullSyncServer = new FullSyncer(col, hkey, this, hostNum);
                switch(conflictResolution) {
                    case FULL_UPLOAD:
                        {
                            Timber.i("Sync - fullsync - upload collection");
                            publishProgress(R.string.sync_preparing_full_sync_message);
                            Pair<ConnectionResultType, Object[]> ret = fullSyncServer.upload();
                            col.reopen();
                            if (ret == null) {
                                return returnGenericError(data);
                            }
                            if (ret.first == ARBITRARY_STRING && !ret.second[0].equals(HttpSyncer.ANKIWEB_STATUS_OK)) {
                                data.success = false;
                                data.resultType = ret.first;
                                data.result = ret.second;
                                return data;
                            }
                            break;
                        }
                    case FULL_DOWNLOAD:
                        {
                            Timber.i("Sync - fullsync - download collection");
                            publishProgress(R.string.sync_downloading_message);
                            ConnectionResultType ret = fullSyncServer.download();
                            if (ret == null) {
                                Timber.w("Sync - fullsync - unknown error");
                                return returnGenericError(data);
                            }
                            if (SUCCESS == ret) {
                                data.success = true;
                                col.reopen();
                            }
                            if (SUCCESS != ret) {
                                Timber.w("Sync - fullsync - download failed");
                                data.success = false;
                                data.resultType = ret;
                                if (!colCorruptFullSync) {
                                    col.reopen();
                                }
                                return data;
                            }
                            break;
                        }
                    default:
                }
            } catch (OutOfMemoryError e) {
                Timber.w(e);
                AnkiDroidApp.sendExceptionReport(e, "doInBackgroundSync-fullSync");
                data.success = false;
                data.resultType = OUT_OF_MEMORY_ERROR;
                data.result = new Object[0];
                return data;
            } catch (RuntimeException e) {
                Timber.w(e);
                if (timeoutOccurred(e)) {
                    data.resultType = CONNECTION_ERROR;
                } else if (USER_ABORTED_SYNC.toString().equals(e.getMessage())) {
                    data.resultType = USER_ABORTED_SYNC;
                } else {
                    AnkiDroidApp.sendExceptionReport(e, "doInBackgroundSync-fullSync");
                    data.resultType = IO_EXCEPTION;
                }
                data.result = new Object[] { e };
                data.success = false;
                return data;
            }
        }
        // clear undo to avoid non syncing orphans (because undo resets usn too
        if (!noChanges) {
            col.clearUndo();
        }
        // then move on to media sync
        sIsCancellable = true;
        boolean noMediaChanges = false;
        String mediaError = null;
        if (media) {
            RemoteMediaServer mediaServer = new RemoteMediaServer(col, hkey, this, hostNum);
            MediaSyncer mediaClient = new MediaSyncer(col, mediaServer, this);
            Pair<ConnectionResultType, String> ret;
            try {
                Timber.i("Sync - Performing media sync");
                ret = mediaClient.sync();
                if (ret == null || ret.first == null) {
                    mediaError = AnkiDroidApp.getAppResources().getString(R.string.sync_media_error);
                } else {
                    if (CORRUPT == ret.first) {
                        mediaError = AnkiDroidApp.getAppResources().getString(R.string.sync_media_db_error);
                        noMediaChanges = true;
                    }
                    if (NO_CHANGES == ret.first) {
                        publishProgress(R.string.sync_media_no_changes);
                        noMediaChanges = true;
                    }
                    if (MEDIA_SANITY_FAILED == ret.first) {
                        mediaError = AnkiDroidApp.getAppResources().getString(R.string.sync_media_sanity_failed);
                    } else {
                        publishProgress(R.string.sync_media_success);
                    }
                }
            } catch (RuntimeException e) {
                Timber.w(e);
                if (timeoutOccurred(e)) {
                    data.resultType = CONNECTION_ERROR;
                    data.result = new Object[] { e };
                } else if (USER_ABORTED_SYNC.toString().equals(e.getMessage())) {
                    data.resultType = USER_ABORTED_SYNC;
                    data.result = new Object[] { e };
                }
                int downloadedCount = mediaClient.getDownloadCount();
                int uploadedCount = mediaClient.getUploadCount();
                if (downloadedCount == 0 && uploadedCount == 0) {
                    mediaError = AnkiDroidApp.getAppResources().getString(R.string.sync_media_error) + "\n\n" + e.getLocalizedMessage();
                } else {
                    mediaError = AnkiDroidApp.getAppResources().getString(R.string.sync_media_partial_updated, downloadedCount, uploadedCount) + "\n\n" + e.getLocalizedMessage();
                }
            }
        }
        if (noChanges && (!media || noMediaChanges)) {
            // This means that there is no change at all, neither media nor collection. Not that there was an error.
            data.success = false;
            data.resultType = NO_CHANGES;
            data.result = new Object[0];
        } else {
            data.success = true;
            data.data = new Object[] { conflictResolution, col, mediaError };
        }
        return data;
    } catch (MediaSyncException e) {
        Timber.e("Media sync rejected by server");
        data.success = false;
        data.resultType = MEDIA_SYNC_SERVER_ERROR;
        data.result = new Object[] { e };
        AnkiDroidApp.sendExceptionReport(e, "doInBackgroundSync");
        return data;
    } catch (UnknownHttpResponseException e) {
        Timber.e(e, "doInBackgroundSync -- unknown response code error");
        data.success = false;
        int code = e.getResponseCode();
        String msg = e.getLocalizedMessage();
        data.resultType = ERROR;
        data.result = new Object[] { code, msg };
        return data;
    } catch (Exception e) {
        // Global error catcher.
        // Try to give a human readable error, otherwise print the raw error message
        Timber.e(e, "doInBackgroundSync error");
        data.success = false;
        if (timeoutOccurred(e)) {
            data.resultType = CONNECTION_ERROR;
            data.result = new Object[] { e };
        } else if (USER_ABORTED_SYNC.toString().equals(e.getMessage())) {
            data.resultType = USER_ABORTED_SYNC;
            data.result = new Object[] { e };
        } else {
            AnkiDroidApp.sendExceptionReport(e, "doInBackgroundSync");
            data.resultType = ARBITRARY_STRING;
            data.result = new Object[] { e.getLocalizedMessage(), e };
        }
        return data;
    } finally {
        Timber.i("Sync Finished - Closing Collection");
        // don't bump mod time unless we explicitly save
        if (col != null) {
            col.close(false);
        }
        CollectionHelper.getInstance().unlockCollection();
    }
}
Also used : ConnectionResultType(com.ichi2.libanki.sync.Syncer.ConnectionResultType) HostNum(com.ichi2.libanki.sync.HostNum) FullSyncer(com.ichi2.libanki.sync.FullSyncer) UnknownHttpResponseException(com.ichi2.anki.exception.UnknownHttpResponseException) CustomSyncServerUrlException(com.ichi2.libanki.sync.CustomSyncServerUrlException) JSONException(com.ichi2.utils.JSONException) MediaSyncException(com.ichi2.anki.exception.MediaSyncException) IOException(java.io.IOException) UnknownHttpResponseException(com.ichi2.anki.exception.UnknownHttpResponseException) HttpSyncer(com.ichi2.libanki.sync.HttpSyncer) FullSyncer(com.ichi2.libanki.sync.FullSyncer) Syncer(com.ichi2.libanki.sync.Syncer) MediaSyncer(com.ichi2.libanki.sync.MediaSyncer) MediaSyncException(com.ichi2.anki.exception.MediaSyncException) Collection(com.ichi2.libanki.Collection) MediaSyncer(com.ichi2.libanki.sync.MediaSyncer) JSONObject(com.ichi2.utils.JSONObject) ConflictResolution(com.ichi2.async.Connection.ConflictResolution) RemoteServer(com.ichi2.libanki.sync.RemoteServer) RemoteMediaServer(com.ichi2.libanki.sync.RemoteMediaServer) Pair(android.util.Pair)

Example 63 with Media

use of com.ichi2.libanki.Media in project Anki-Android by ankidroid.

the class ZipFile method exportInto.

/**
 * Export source database into new destination database Note: The following python syntax isn't supported in
 * Android: for row in mSrc.db.execute("select * from cards where id in "+ids2str(cids)): therefore we use a
 * different method for copying tables
 *
 * @param path String path to destination database
 * @throws JSONException
 * @throws IOException
 */
public void exportInto(@NonNull String path, Context context) throws JSONException, IOException, ImportExportException {
    // create a new collection at the target
    new File(path).delete();
    Collection dst = Storage.Collection(context, path);
    mSrc = mCol;
    // find cards
    Long[] cids = cardIds();
    // attach dst to src so we can copy data between them. This isn't done in original libanki as Python more
    // flexible
    dst.close();
    Timber.d("Attach DB");
    mSrc.getDb().getDatabase().execSQL("ATTACH '" + path + "' AS DST_DB");
    // copy cards, noting used nids (as unique set)
    Timber.d("Copy cards");
    mSrc.getDb().getDatabase().execSQL("INSERT INTO DST_DB.cards select * from cards where id in " + Utils.ids2str(cids));
    List<Long> uniqueNids = mSrc.getDb().queryLongList("select distinct nid from cards where id in " + Utils.ids2str(cids));
    // notes
    Timber.d("Copy notes");
    String strnids = Utils.ids2str(uniqueNids);
    mSrc.getDb().getDatabase().execSQL("INSERT INTO DST_DB.notes select * from notes where id in " + strnids);
    // remove system tags if not exporting scheduling info
    if (!mIncludeSched) {
        Timber.d("Stripping system tags from list");
        ArrayList<String> srcTags = mSrc.getDb().queryStringList("select tags from notes where id in " + strnids);
        ArrayList<Object[]> args = new ArrayList<>(srcTags.size());
        Object[] arg = new Object[2];
        for (int row = 0; row < srcTags.size(); row++) {
            arg[0] = removeSystemTags(srcTags.get(row));
            arg[1] = uniqueNids.get(row);
            args.add(row, arg);
        }
        mSrc.getDb().executeMany("UPDATE DST_DB.notes set tags=? where id=?", args);
    }
    // models used by the notes
    Timber.d("Finding models used by notes");
    ArrayList<Long> mids = mSrc.getDb().queryLongList("select distinct mid from DST_DB.notes where id in " + strnids);
    // card history and revlog
    if (mIncludeSched) {
        Timber.d("Copy history and revlog");
        mSrc.getDb().getDatabase().execSQL("insert into DST_DB.revlog select * from revlog where cid in " + Utils.ids2str(cids));
        // reopen collection to destination database (different from original python code)
        mSrc.getDb().getDatabase().execSQL("DETACH DST_DB");
        dst.reopen();
    } else {
        Timber.d("Detaching destination db and reopening");
        // first reopen collection to destination database (different from original python code)
        mSrc.getDb().getDatabase().execSQL("DETACH DST_DB");
        dst.reopen();
        // then need to reset card state
        Timber.d("Resetting cards");
        dst.getSched().resetCards(cids);
    }
    // models - start with zero
    Timber.d("Copy models");
    for (Model m : mSrc.getModels().all()) {
        if (mids.contains(m.getLong("id"))) {
            dst.getModels().update(m);
        }
    }
    // decks
    Timber.d("Copy decks");
    java.util.Collection<Long> dids = null;
    if (mDid != null) {
        dids = new HashSet<>(mSrc.getDecks().children(mDid).values());
        dids.add(mDid);
    }
    JSONObject dconfs = new JSONObject();
    for (Deck d : mSrc.getDecks().all()) {
        if ("1".equals(d.getString("id"))) {
            continue;
        }
        if (dids != null && !dids.contains(d.getLong("id"))) {
            continue;
        }
        if (d.isStd() && d.getLong("conf") != 1L) {
            if (mIncludeSched) {
                dconfs.put(Long.toString(d.getLong("conf")), true);
            }
        }
        Deck destinationDeck = d.deepClone();
        if (!mIncludeSched) {
            // scheduling not included, so reset deck settings to default
            destinationDeck.put("conf", 1);
        }
        dst.getDecks().update(destinationDeck);
    }
    // copy used deck confs
    Timber.d("Copy deck options");
    for (DeckConfig dc : mSrc.getDecks().allConf()) {
        if (dconfs.has(dc.getString("id"))) {
            dst.getDecks().updateConf(dc);
        }
    }
    // find used media
    Timber.d("Find used media");
    JSONObject media = new JSONObject();
    mMediaDir = mSrc.getMedia().dir();
    if (mIncludeMedia) {
        ArrayList<Long> mid = mSrc.getDb().queryLongList("select mid from notes where id in " + strnids);
        ArrayList<String> flds = mSrc.getDb().queryStringList("select flds from notes where id in " + strnids);
        for (int idx = 0; idx < mid.size(); idx++) {
            for (String file : mSrc.getMedia().filesInStr(mid.get(idx), flds.get(idx))) {
                // skip files in subdirs
                if (file.contains(File.separator)) {
                    continue;
                }
                media.put(file, true);
            }
        }
        if (mMediaDir != null) {
            for (File f : new File(mMediaDir).listFiles()) {
                if (f.isDirectory()) {
                    continue;
                }
                String fname = f.getName();
                if (fname.startsWith("_")) {
                    // Loop through every model that will be exported, and check if it contains a reference to f
                    for (JSONObject model : mSrc.getModels().all()) {
                        if (_modelHasMedia(model, fname)) {
                            media.put(fname, true);
                            break;
                        }
                    }
                }
            }
        }
    }
    JSONArray keys = media.names();
    if (keys != null) {
        mMediaFiles.ensureCapacity(keys.length());
        addAll(mMediaFiles, keys.stringIterable());
    }
    Timber.d("Cleanup");
    dst.setCrt(mSrc.getCrt());
    // todo: tags?
    mCount = dst.cardCount();
    dst.setMod();
    postExport();
    dst.close();
}
Also used : ArrayList(java.util.ArrayList) JSONArray(com.ichi2.utils.JSONArray) SuppressLint(android.annotation.SuppressLint) JSONObject(com.ichi2.utils.JSONObject) JSONObject(com.ichi2.utils.JSONObject) File(java.io.File)

Example 64 with Media

use of com.ichi2.libanki.Media in project Anki-Android by ankidroid.

the class ZipFile method _exportMedia.

private JSONObject _exportMedia(ZipFile z, File[] files, ValidateFiles validateFiles) throws IOException {
    int c = 0;
    JSONObject media = new JSONObject();
    for (File file : files) {
        // todo: deflate SVG files, as in dae/anki@a5b0852360b132c0d04094f5ca8f1933f64d7c7e
        if (validateFiles == ValidateFiles.VALIDATE && !file.exists()) {
            // Anki 2.1.30 does the same
            Timber.d("Skipping missing file %s", file);
            continue;
        }
        z.write(file.getPath(), Integer.toString(c));
        try {
            media.put(Integer.toString(c), file.getName());
            c++;
        } catch (JSONException e) {
            Timber.w(e);
        }
    }
    return media;
}
Also used : JSONObject(com.ichi2.utils.JSONObject) JSONException(com.ichi2.utils.JSONException) File(java.io.File) SuppressLint(android.annotation.SuppressLint)

Example 65 with Media

use of com.ichi2.libanki.Media in project Anki-Android by ankidroid.

the class ZipFile method exportVerbatim.

private JSONObject exportVerbatim(ZipFile z, Context context) throws IOException {
    // close our deck & write it into the zip file, and reopen
    mCount = mCol.cardCount();
    mCol.close();
    if (!_v2sched) {
        z.write(mCol.getPath(), CollectionHelper.COLLECTION_FILENAME);
    } else {
        _addDummyCollection(z, context);
        z.write(mCol.getPath(), "collection.anki21");
    }
    mCol.reopen();
    // copy all media
    if (!mIncludeMedia) {
        return new JSONObject();
    }
    File mdir = new File(mCol.getMedia().dir());
    if (mdir.exists() && mdir.isDirectory()) {
        File[] mediaFiles = mdir.listFiles();
        return _exportMedia(z, mediaFiles, ValidateFiles.SKIP_VALIDATION);
    } else {
        return new JSONObject();
    }
}
Also used : JSONObject(com.ichi2.utils.JSONObject) File(java.io.File)

Aggregations

File (java.io.File)43 IOException (java.io.IOException)26 Collection (com.ichi2.libanki.Collection)25 JSONObject (com.ichi2.utils.JSONObject)19 ArrayList (java.util.ArrayList)17 Test (org.junit.Test)17 ZipFile (java.util.zip.ZipFile)14 JSONException (com.ichi2.utils.JSONException)10 FileOutputStream (java.io.FileOutputStream)10 List (java.util.List)10 JSONArray (com.ichi2.utils.JSONArray)9 FileInputStream (java.io.FileInputStream)9 FileNotFoundException (java.io.FileNotFoundException)9 SharedPreferences (android.content.SharedPreferences)8 JSONObject (org.json.JSONObject)8 Resources (android.content.res.Resources)7 Uri (android.net.Uri)7 RobolectricTest (com.ichi2.anki.RobolectricTest)7 ConfirmModSchemaException (com.ichi2.anki.exception.ConfirmModSchemaException)7 AnkiPackageImporter (com.ichi2.libanki.importer.AnkiPackageImporter)7