I can back up and restore my most recent database OK but I want to save a time stamped Sqlite database like "back_up_12_11_17_13_32". This is so the user can restore not just the most recent back up but whatever back up he needs.
I'm wondering how to go about this?
Here's some pointers/techniques that I've utilised for what I believe is quite a flexible backup/restore facility.
I use the following to create the backup name.
private void setFullFilename() {
backupfullfilename.setText(
backupbasepart.getText().toString() +
backupdatetimepart.getText().toString() +
backupextension.getText().toString()
);
}
where
backupbasepart defaults to ShopWiseDB
backupdatetimepart defaults to the current time (when activity is started) in YYMMDDhhmm format as obtained via :-
Calendar cldr = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmm");
return "_" + sdf.format(cldr.getTime());
backupextension defaults to .bkp
Noting that I allow all to be edited.
The App's backup/restore activity looks like :-
Note that the Spinner has been clicked to show the list of available backups and to also show how the timestamp is also converted to a more human readable format.
All backups are stored in PRIMARY PUBLIC EXTERNAL STORAGE (user has the option to copy/move to wherever) in the Download directory in a sub directory called ShopWise (this is fixed to simplify matters i.e. to not have to write a file explorer)
Using this directory also allows databases to be copied from other sources, so users could share databases if they so wished or this ability could be used for problem determination and resolution.
What backups shown in the Restore Spinner are determined by the 3 inputs that allows some flexibility - by default only standard backups are listed. However, blanking out the Base Filename will result in all files (well all that have an extension of .bkp (The File extension only shows files with that extension, blanking it out shows all extensions)).
e.g. if all are blanked out then all then you could have :-
When it boils down to it the core code for providing the list of restoreable files is :-
private void populateRestoreSpinner(Spinner spn,
TextView tv,
String basefilename,
String fileext,
StoreData sd,
TextView restorebutton) {
int fcount = 0;
ArrayList<File> reverseflist = new ArrayList<>();
//
sd = new StoreData(getResources().getString(R.string.backupdirectoryname),"xxx",true);
sd.refreshOtherFilesInDirectory();
// Build the File ArrayList
ArrayList<File> flst = new ArrayList<>(sd.getFilesInDirectory());
// Ascertain the relevant files that are needed for the restore backup
// file selector
for(int i = 0; i < flst.size(); i++) {
boolean endingok = flst.get(i).getName().endsWith(fileext);
boolean containsok = flst.get(i).getName().contains(basefilename);
if((strictbackupmode && endingok && containsok)
|| (!strictbackupmode && (endingok || containsok))) {
fcount++;
} else {
flst.remove(i);
i--;
}
}
// Reverse the order of the list so most recent backups appear first
// Also hide/show the Restore button and spinner according to if
// files exist or not
// (doing nothing in the case where the is no restore button i.e.
// null has been passed)
if(flst.size() > 0) {
for (int i = (flst.size() -1); i >= 0; i--) {
reverseflist.add(flst.get(i));
}
if (restorebutton != null) {
spn.setVisibility(View.VISIBLE);
restorebutton.setVisibility(View.VISIBLE);
}
} else {
if (restorebutton != null) {
spn.setVisibility(View.INVISIBLE);
restorebutton.setVisibility(View.INVISIBLE);
}
}
// Set the available count for display
//String bcnt = "Available Backups=" + Integer.toString(reverseflist.size());
tv.setText(Integer.toString(reverseflist.size()));
// Set the spinner adapter and dropdown layout and then set the
// spinner's adapter
AdapterFileList afl = new AdapterFileList(this,
R.layout.filelist,
reverseflist,
getIntent());
afl.setDropDownViewResource(R.layout.filelist);
spn.setAdapter(afl);
}
Note! StoreData is a glorified File/Directory listing that's pretty long winded but basically has members such as :-
private String directory;
private String subdirectory;
private String filename;
private boolean mounted;
private boolean inerror;
private boolean fileexists;
private boolean direxists;
private long errorcode;
private ArrayList<String> errorlist = new ArrayList<>();
private ArrayList<File> otherfilesindirectory = new ArrayList<>();
Related
I want to store a video in sqlite database. P.S. I do not want to store the path but the actual video contents.
I have converted the video in byte array and stored byte array in sqlite database. Upon retrieval bytearray is being converted into File. But video is not playing. Please help.
I want to store a video in sqlite database. P.S. I do not want to
store the path but the actual video contents.
Unless the videos are very short and take up little space (say up to 200k each, perhaps 1/10th of a second but would depend upon the format it is saved in) then you would likely encounter issues and exceptions/crashes.
Using a phone around 2 seconds of black took up 2.2Mb, 2 seconds of actually recording a video took up 7Mb.
Although SQLite has the ability to store relative large BLOB's as per :-
Maximum length of a string or BLOB
The maximum number of bytes in a string or BLOB in SQLite is defined
by the preprocessor macro SQLITE_MAX_LENGTH. The default value of this
macro is 1 billion (1 thousand million or 1,000,000,000). You can
raise or lower this value at compile-time using a command-line option
like this:
-DSQLITE_MAX_LENGTH=123456789 The current implementation will only support a string or BLOB length up to 231-1 or 2147483647. And some
built-in functions such as hex() might fail well before that point. In
security-sensitive applications it is best not to try to increase the
maximum string and blob length. In fact, you might do well to lower
the maximum string and blob length to something more in the range of a
few million if that is possible.
During part of SQLite's INSERT and SELECT processing, the complete
content of each row in the database is encoded as a single BLOB. So
the SQLITE_MAX_LENGTH parameter also determines the maximum number of
bytes in a row.
The maximum string or BLOB length can be lowered at run-time using the
sqlite3_limit(db,SQLITE_LIMIT_LENGTH,size) interface.
Limits In SQLite
The Android SDK's CursorWindow has a limitation of 2Mb and that is for all the columns of the row(s) if buffers. As such even if you can store Videos successfully, you may not be able to retrieve those Videos.
The recommended way is what you don't want, that is to store the path to the Video.
If i store the video in my internal/external storage and store the
path instead then how will i be able to access the same from some
other device.
You would have the same issue with the database as it's typically stored within the Applications data which is protected. That is unless the database is a pre-existing database (i.e. populated with data), in which case the database is distributed with the App via the APK.
If the latter, a pre-existing database distributed via the APK, then the videos can also be distributed as part of the APK and hence as protected as and as exposable as the database.
If your intention is to distribute videos between devices that are not part of the APK then SQlite is probably not the correct solution as it's an embedded database and has no client/server functionality built in.
Besides what if my device gets formatted then I will lose all the
data.
In such a scenario, the database would be as vulnerable as any other data, as that is all the database is, a file, just like a video, a word document etc which all need a suitable application to view/change the content. However, if the database is a pre-existing database, then simply re-installing the App would restore the database and other files from the APK.
Working Example
This uses the Suggested/Recommended method assuming the videos are to be distributed with the APK.
Note Videos Courtesey of Sample Videos
After creating new project 4 videos were downloaded and copied into the res/raw folder (after creating the raw folder) as per :-
The Database Helper (subclass of SQLiteOpenHelper) was created for a 2 column table an with
- _id column (note named _id for use with SimpleCursorAdapter).
- video_path for storing the path/name of the video (not the full path but sufficient to be able to determine the path from the data stored)
- Note UNIQUE has been coded to stop duplicates being added.
With some basic method to allow rows to be added and deleted and for all rows to be extracted (via a Cursor for use with the SimpleCursorAdapter).
DBHelper.java
public class DBHelper extends SQLiteOpenHelper {
public static final String DBNAME = "myvideos";
public static final int DBVERSION = 1;
public static final String TBL_VIDEO = "video";
public static final String COL_VIDEO_ID = BaseColumns._ID;
public static final String COL_VIDEO_PATH = "video_path";
SQLiteDatabase mDB;
public DBHelper(Context context) {
super(context, DBNAME, null, DBVERSION);
mDB = this.getWritableDatabase();
}
#Override
public void onCreate(SQLiteDatabase db) {
String crt_video_table = "CREATE TABLE IF NOT EXISTS " + TBL_VIDEO + "(" +
COL_VIDEO_ID + " INTEGER PRIMARY KEY," +
COL_VIDEO_PATH + " TEXT UNIQUE" +
")";
db.execSQL(crt_video_table);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
public long addVideo(String path) {
ContentValues cv = new ContentValues();
cv.put(COL_VIDEO_PATH,path);
return mDB.insert(TBL_VIDEO,null,cv);
}
public Cursor getVideos() {
return mDB.query(TBL_VIDEO,null,null,null,null,null,null);
}
public int deleteVideoFromDB(long id) {
String whereclause = COL_VIDEO_ID + "=?";
String[] whereargs = new String[]{String.valueOf(id)};
return mDB.delete(TBL_VIDEO,whereclause,whereargs);
}
}
A pretty straigforward MainActivity.java (see comments)
public class MainActivity extends AppCompatActivity {
TextView mMyTextView;
ListView mVideoList;
VideoView mVideoViewer;
DBHelper mDBHlpr;
Cursor mCsr;
SimpleCursorAdapter mSCA;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mMyTextView = this.findViewById(R.id.mytext);
mVideoList = this.findViewById(R.id.videolist);
mVideoViewer = this.findViewById(R.id.videoviewer);
mDBHlpr = new DBHelper(this);
addVideosFromRawResourceToDB();
}
#Override
protected void onDestroy() {
mCsr.close(); //<<<<<<<<<< clear up the Cursor
super.onDestroy();
}
#Override
protected void onResume() {
super.onResume();
manageListView(); //<<<<<<<<<< rebuild and redisplay the List of Videos (in case they have changed)
}
/**
* Setup or Refresh the ListView adding the OnItemClick and OnItemLongClick listeners
*/
private void manageListView() {
mCsr = mDBHlpr.getVideos();
// Not setup so set it up
if (mSCA == null) {
// Instantiate the SimpleCursorAdapter
mSCA = new SimpleCursorAdapter(
this,
android.R.layout.simple_list_item_1, // Use stock layout
mCsr, // The Cursor with the list of videos
new String[]{DBHelper.COL_VIDEO_PATH}, // the column (columns)
new int[]{android.R.id.text1}, // the view id(s) into which the column(s) data will be placed
0
);
mVideoList.setAdapter(mSCA); // Set the adpater for the ListView
/**
* Add The Long Click Listener (will delete the video row from the DB (NOT the video))
*/
mVideoList.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
mDBHlpr.deleteVideoFromDB(id);
manageListView(); // <<<<<<<<<< refresh the ListView as data has changed
return true;
}
});
/**
* Play the respective video when the item is clicked
* Note Cursor should be at the correct position so data can be extracted directly from the Cursor
*/
mVideoList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
setCurrentVideo(mCsr.getString(mCsr.getColumnIndex(DBHelper.COL_VIDEO_PATH)));
}
});
} else {
mSCA.swapCursor(mCsr); //<<<<<<<<<< apply the changed Cursor
}
}
/**
* Set the currrent video and play it
* #param path the path (resource name of the video)
*/
private void setCurrentVideo(String path) {
mVideoViewer.setVideoURI(
Uri.parse(
"android.resource://" + getPackageName() + "/" + String.valueOf(
getResources().getIdentifier(
path,
"raw",
getPackageName())
)
)
);
mVideoViewer.start();
}
/**
* Look at all the resources in the res/raw folder and add the to the DB (not if they are duplicates due to UNQIUE)
*/
private void addVideosFromRawResourceToDB() {
Field[] fields=R.raw.class.getFields();
for(int count=0; count < fields.length; count++){
Log.i("Raw Asset: ", fields[count].getName());
mDBHlpr.addVideo(fields[count].getName());
}
}
}
Results
When first started (nothing plays) :-
After long clicking the 1Mb video (deleting the DB entry) :-
After clicking A Video in the List :-
You can use this approach
When save the video, save it in app private storage folder.
Context.getFilesDir()
This will give you the path to the app storage in ..\Andorid\data\data\com.example.app
and it will be in internal storage.
Where com.example.app will be your application package id. You can make a new folder here like Videos then save videos in this folder. Save its path in the DB. Only your app can access to this folder. No any other app or device user can access this folder. So no one can edit or delete your files except your application.
Moreover if user reset mobile this data will be deleted as well as your database and maybe your app too in some cases. So no need to worry about it that your files will be deleted but database has still their path. If file deleted then DB deleted too as well but only when app Uninstall, device reset or SD card erase.
I'm using GreenDAO for an Android project for the first time, and was wondering how to seed the database for first-time users? Say for instance i have a table and want 5 rows inserted on behalf of the user.
Also, i might add new tables in future updates and seed data into those as well, but still want to have the five rows inserted into the first table, even though the user is installing a newer version of the scheme.
My initial idea was to do it in my App.onCreate() method, and then set a flag in SharedPreferences as whether or not the seed has been made already, but it bugs me that i can't find a more pragmatic approach to this.
Any help appreciated, thanks!
I had the same problem and searched the web and the documentation of GreenDAO but didn't find anything reliable.
So I wrote a code to run in the first run of the app. To do so I needed to check if it's the first time that my app is launched. For doing that I recommend this answer. You can see the code from that answer here:
public static void checkFirstRun(Context context) {
final String PREFS_NAME = "TickTockPrefs";
final String PREF_VERSION_CODE_KEY = "version_code";
final int DOESNT_EXIST = -1;
// Get current version code
int currentVersionCode = 0;
try {
currentVersionCode = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;
} catch (android.content.pm.PackageManager.NameNotFoundException e) {
// handle exception
e.printStackTrace();
return;
}
// Get saved version code
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
int savedVersionCode = prefs.getInt(PREF_VERSION_CODE_KEY, DOESNT_EXIST);
// Check for first run or upgrade
if (currentVersionCode == savedVersionCode) {
// This is just a normal run
return;
} else if (savedVersionCode == DOESNT_EXIST) {
// TODO This is a new install (or the user cleared the shared preferences)
seed(context);
} else if (currentVersionCode > savedVersionCode) {
// TODO This is an upgrade
}
// Update the shared preferences with the current version code
prefs.edit().putInt(PREF_VERSION_CODE_KEY, currentVersionCode).apply();
}
And inside the seed method you can write whatever you want to insert. For example say I have a "Person" entity that I want to prepopulate with data:
public static void seed(Context context) {
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "your-db", null);
SQLiteDatabase db = helper.getWritableDatabase();
DaoMaster daoMaster = new DaoMaster(db);
DaoSession daoSession = daoMaster.newSession();
Person person = new Person();
person.setName("Jason");
person.setFamily("Bourne");
PersonDao personDao = daoSession.getPersonDao();
personDao.insert(person);
}
Note that if you want to insert a List of entities use insertInTx() method instead of insert(). You can see the difference here.
I know this is different than ORM seed method but it seems there's no other alternatives except you manipulate greenDAO code yourself.
I am making Android app for practicing driving licence theory tests. I will have about 3000 questions. Question object would have several atributes (text, category, subcategory, answers, group). I will create them and put in app, so data won't ever change. When user chooses category, app would go througt data, look which question meets requirements (that user selected) and put it in list for displaying. What should I use to store data/questions, XML or SQLite? Thanks in advance.
Edit:
I forgot to mentiont that app won't use internet connection. Also, I planned to make simple java app for entering data. I would copy text from government's website (I don't have access to their database and I have to create mine), so I thought to just put question's image url to java program and it would download it and name it automaticaly. Also, when entering new question's text it would tell me if that question already exist before I enter other data. That would save me time, I wouldn't have to save every picture and name it my self. That is what I thought if using XML. Can I do this for JSON or SQLite?
If you do not have to perform complex queries, I would recommend to store your datas in json since very well integrated in android apps using a lib such as GSON or Jackson.
If you don't want to rebuild your app / redeploy on every question changes. You can imagine to have a small webserver (apache, nginx, tomcat) that serves the json file that you will request on loading of the app. So that you will download the questions when your app is online or use the cached one.
XML is a verbose format for such an usage, and does not bring much functions....
To respond to your last question, you can organise your code like that :
/**
* SOF POST http://stackoverflow.com/posts/37078005
* #author Jean-Emmanuel
* #company RIZZE
*/
public class SOF_37078005 {
#Test
public void test() {
QuestionsBean questions = new QuestionsBean();
//fill you questions
QuestionBean b=buildQuestionExemple();
questions.add(b); // success
questions.add(b); //skipped
System.out.println(questions.toJson()); //toJson
}
private QuestionBean buildQuestionExemple() {
QuestionBean b= new QuestionBean();
b.title="What is the size of your boat?";
b.pictures.add("/res/images/boatSize.jpg");
b.order= 1;
return b;
}
public class QuestionsBean{
private List<QuestionBean> list = new ArrayList<QuestionBean>();
public QuestionsBean add(QuestionBean b ){
if(b!=null && b.title!=null){
for(QuestionBean i : list){
if(i.title.compareToIgnoreCase(b.title)==0){
System.out.println("Question "+b.title+" already exists - skipped & not added");
return this;
}
}
System.out.println("Question "+b.title+" added");
list.add(b);
}
else{
System.out.println("Question was null / not added");
}
return this;
}
public String toJson() {
ObjectMapper m = new ObjectMapper();
m.configure(Feature.ALLOW_SINGLE_QUOTES, true);
String j = null;
try {
j= m.writeValueAsString(list);
} catch (JsonProcessingException e) {
e.printStackTrace();
System.out.println("JSON Format error:"+ e.getMessage());
}
return j;
}
}
public class QuestionBean{
private int order;
private String title;
private List<String> pictures= new ArrayList<String>(); //path to picture
private List<String> responseChoice = new ArrayList<String>(); //list of possible choices
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public List<String> getPictures() {
return pictures;
}
public void setPictures(List<String> pictures) {
this.pictures = pictures;
}
public List<String> getResponseChoice() {
return responseChoice;
}
public void setResponseChoice(List<String> responseChoice) {
this.responseChoice = responseChoice;
}
}
}
CONSOLE OUTPUT
Question What is the size of your boat? added
Question What is the size of your boat? already exists - skipped & not added
[{"order":1,"title":"What is the size of your boat?","pictures":["/res/images/boatSize.jpg"],"responseChoice":[]}]
GIST :
provides you the complete working code I've made for you
https://gist.github.com/jeorfevre/5d8cbf352784042c7a7b4975fc321466
To conclude, what is a good practice to work with JSON is :
1) create a bean in order to build your json (see my example here)
2) build your json and store it in a file for example
3) Using android load your json from the file to the bean (you have it in andrdoid)
4) use the bean to build your form...etc (and not the json text file) :D
I would recommend a database (SQLite) as it provides superior filtering functionality over xml.
Create the db using DB Browser for SQLite
And then use the library SQLiteAssetHelper in the link-
https://github.com/jgilfelt/android-sqlite-asset-helper
Tutorial on how to use -
http://www.javahelps.com/2015/04/import-and-use-external-database-in.html
You can use Paper https://github.com/pilgr/Paper its a fast NoSQL data storage for Android.
SQLite is the best for your system. because you will have to maintain (text, category, subcategory, answers, group) etc. So if you create db and create table for them. That will be easy to manage and you can relationship with each other which is not possible to XML.
I am using Arraylist of strings:
ArrayList entries = new ArrayList(Arrays.asList(""));
and giving values dynamically. It may contain the names of Directories or Files.
I need to show entries in ListView such that, first all directories are shown in sort order then files in sort order.
Is this possible? if yes, any hint? Appreciate the help.. I am using
Collections.sort(entries);
to sort my entries.
Use the 2 parameter version with a custom comparator. Compare it such that:
boolean firstFileIsDirectory = file1.isDirectory();
boolean secondFileIsDirectory = file2.isDirectory();
if(firstFileIsDirectory && !secondFileIsDirectory){
return -1;
}
if(!firstFileIsDirectory && secondFileIsDirectory){
return 1;
}
return String.compare(filename1, filename2);
I have done it. Logic used: separate the entries into two ArrayList. One having directories other files. Sort these two ArrayLists separately. Finally add these two to "entries". Here is the code:
private void sortEntries(String path){
ArrayList<String> entriesDir = new ArrayList<String>(Arrays.asList(""));
ArrayList<String> entriesFile = new ArrayList<String>(Arrays.asList(""));
entriesDir.removeAll(entriesDir);
entriesFile.removeAll(entriesFile);
int fileCounter=0, dirCounter=0;
path = path.equals("/") ? "" : path;
for(int i=1;i<=entries.size();i++){
if((new File(path+"/"+entries.get(i-1))).isFile()) entriesFile.add(fileCounter++, entries.get(i-1));
else entriesDir.add(dirCounter++, entries.get(i-1));
}
Collections.sort(entriesDir,String.CASE_INSENSITIVE_ORDER);
Collections.sort(entriesFile,String.CASE_INSENSITIVE_ORDER);
entries.removeAll(entries);
entries.addAll(entriesDir);
entries.addAll(entriesFile);
}
There is described on this page how android is able to select all user certificates and show them in a spinner...
http://source-android.frandroid.com/packages/apps/Settings/src/com/android/settings/vpn2/VpnDialog.java
They use the Method:
private void loadCertificates(Spinner spinner, String prefix, int firstId, String selected) {
Context context = getContext();
String first = (firstId == 0) ? "" : context.getString(firstId);
String[] certificates = mKeyStore.saw(prefix);
if (certificates == null || certificates.length == 0) {
certificates = new String[] {first};
} else {
String[] array = new String[certificates.length + 1];
array[0] = first;
System.arraycopy(certificates, 0, array, 1, certificates.length);
certificates = array;
}
ArrayAdapter<String> adapter = new ArrayAdapter<String>(
context, android.R.layout.simple_spinner_item, certificates);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
for (int i = 1; i < certificates.length; ++i) {
if (certificates[i].equals(selected)) {
spinner.setSelection(i);
break;
}
}
}
with this call:
loadCertificates(mIpsecUserCert, Credentials.USER_CERTIFICATE,
0, mProfile.ipsecUserCert);
now they use Credetials.USER_CERTIFICATE for the Method mKeyStore.saw(String prefix)
thats exactly what im looking for, but i cannot invoke the method saw and its also not mentioned in the android api...
so is there somehow a way of getting the same result for me?
the keystore library is not able of making a difference of user or system certs, and if i want to put all certs in a dropdown list it will cost me a lot of loading time. The other way of typing the subject name of the cert and looping through all certs till its the right one is not really practicable for a non-IT guy, so its crappy to handle for a normal user...
there are almost no references on this topic in the net, thats why im asking google android pro's on this post only.
thankyou
This is a private API, it cannot be used by third party applications. Why do you want to select a user certificate? You generally want the user's private key, and the KeyChain API supports letting the user select one of those.