Can anyone provide me with the guidelines on how to retrieve a list of recently opened files in a folder?
In my application, I have a folder which contains .epub files.
I would like to display a sorted list of recently read/opened books by the user.
The problem is actually not so trivial.
There are two reasons - history's persistence and IO blocking operations.
In fact to ensure persistence of history we can use some solutions:
database (seems most reasonable but requires most effort)
internal file (should be the easiest way)
shared preferences
So, I used second method. In memory I keep just ArrayList<Uri>, and to save it to file I convert it to List<String>, as android.net.Uri doesn't support serialisation.
In internal memory I save serialised object using ObjectIOStream.
So, see the code:
public class FileHistory {
private static final String FILE_NAME = "file-history-v1";
private static final int HISTORY_SIZE = 20;
#NonNull
private final Context mAppContext;
// This is a executor where I can post any runnable
// and all of them will be executed in one pipeline
// keeping posting order.
#NonNull
private final OneThreadExecutor mExecutor;
#Nullable
private ArrayList<Uri> mInternalFilesHistory;
#NonNull
private MutableLiveData<List<Uri>> mFilesHistory = new MutableLiveData<>();
public FileHistory(#NonNull final Context appContext,
#NonNull final OneThreadExecutor executor) {
this.mAppContext = appContext;
this.mExecutor = executor;
loadHistory();
}
public void addEntry(#NonNull final Uri entry) {
if (mInternalFilesHistory == null) {
// The fileHistory is not ready yet.
// Schedule adding entry as next task of serial executor.
mExecutor.execute(() -> addEntry(entry));
return;
}
// Remove entry if exists and add it as first element.
CollectionUtils.removeFirst(mInternalFilesHistory, uri -> uri.equals(entry));
mInternalFilesHistory.add(0, entry);
if (mInternalFilesHistory.size() > HISTORY_SIZE) {
ArrayList<Uri> trimmed = new ArrayList<>(HISTORY_SIZE + 1);
trimmed.addAll(mInternalFilesHistory.subList(0, HISTORY_SIZE));
mInternalFilesHistory = trimmed;
}
mExecutor.execute(this::rePostHistory);
mExecutor.execute(this::saveToInternalStorage);
}
#NonNull
public MutableLiveData<List<Uri>> getFilesHistory() {
return mFilesHistory;
}
private void loadHistory() {
mExecutor.execute(this::loadFromInternalStorage);
mExecutor.execute(this::rePostHistory);
}
private void rePostHistory() {
if (mInternalFilesHistory != null) {
mFilesHistory.postValue(Collections.unmodifiableList(mInternalFilesHistory));
}
}
#SuppressWarnings("unchecked")
#WorkerThread
private void loadFromInternalStorage() {
try {
FileInputStream fis = mAppContext.openFileInput(FILE_NAME);
ObjectInputStream ois = new ObjectInputStream(fis);
ArrayList<String> entries = (ArrayList<String>) ois.readObject();
List<Uri> mapped = CollectionUtils.map(entries, Uri::parse);
if (mInternalFilesHistory == null) {
mInternalFilesHistory = new ArrayList<>(HISTORY_SIZE + 1);
} else {
mInternalFilesHistory.clear();
}
mInternalFilesHistory.addAll(mapped);
fis.close();
ois.close();
} catch (Exception ex) {
mInternalFilesHistory = new ArrayList<>(HISTORY_SIZE + 1);
}
}
#WorkerThread
private void saveToInternalStorage() {
try {
FileOutputStream fis = mAppContext.openFileOutput(FILE_NAME, Context.MODE_PRIVATE);
ObjectOutputStream oos = new ObjectOutputStream(fis);
if (mInternalFilesHistory == null) {
mInternalFilesHistory = new ArrayList<>();
}
List<String> converted = CollectionUtils.map(mInternalFilesHistory, Uri::toString);
oos.writeObject(converted);
fis.close();
oos.close();
} catch (IOException ignored) {
}
}
}
As you can see, internal storage is use to keep that file. So, there is no need to add any additional permissions.
Synchronisation is ensured by using executor which will execute all request, one by one, so even if IO will be slow order or requests will be saved.
We do not block thread with IO operations, because all operations using IO are on WorkerThread. About the result we will be notified via LiveData from android.arch.
In my opinion this is kind of the simplest solution. If we need to keep stats, dates etc. we can save List<MyHistoryEntry>, as long MyHistoryEntry will be serialisable.
As a better approach I would suggest to use database (easier migration etc.).
Related
How can I print the number of bytes that have been uploaded after calling blob.upload(new FileInputStream(imageFile), imageFile.length()); I want to log something like "100/totalBytes bytes have been uploaded, 224/totalBytes bytes have been uploaded..." So I can create a progress bar of the upload progress.
this is the code:
//AzureBlobLoader extends AsyncTask
public class AzureBlobUploader extends AzureBlobLoader {
private Activity act;
private String userName;
private TaggedImageObject img;
private Fragment histFragment;
public AzureBlobUploader(Fragment f, Activity act, String userName, TaggedImageObject img) {
super();
this.act = act;
this.userName = userName;
this.img = img;
this.histFragment = f;
}
#Override
protected Object doInBackground(Object[] params) {
File imageFile = new File(this.img.getImgPath());
try {
// Define the path to a local file.
final String filePath = imageFile.getPath();
// Create or overwrite the blob with contents from the local file.
String[] imagePathArray = filePath.split("/");
String imageName = imagePathArray[imagePathArray.length-1];
System.out.println("Image Name: " + imageName);
String containerName = userName + "/" + imageName;
System.out.println("Container Name: " + containerName);
CloudBlockBlob blob= this.getContainer().getBlockBlobReference(containerName);
//UPLOAD!
blob.upload(new FileInputStream(imageFile), imageFile.length());
//-----DATABASE-----//
//create client
this.setDBClient(
new MobileServiceClient(
"URL",
this.act.getApplicationContext()
)
);
this.setImageTable(this.getDBClient().getTable(Image.class));
this.setIcavTable(this.getDBClient().getTable(ICAV.class));
//IMG TABLE QUERY
String validImageID = containerName.replace("/", "_");
Log.d("Azure", "Valid Image ID: " + validImageID);
Image img = new Image(validImageID, this.img.getUser(), this.img.getLat(), this.img.getLon());
this.getImageTable().insert(img);
for(String context : this.img.getContextAttributeMap().keySet()){
Map<String,String> attributeValueMap = this.img.getContextAttributeMap().get(context);
for(String attribute : attributeValueMap.keySet()){
String value = attributeValueMap.get(attribute);
ICAV icavRow = new ICAV();
icavRow.setImageID(validImageID);
icavRow.setContextID(context);
icavRow.setAttributeID(attribute);
icavRow.setValue(value);
this.getIcavTable().insert(icavRow);
}
}
} catch (Exception e) {
System.out.println(e.toString());
}
return null;
}
#Override
protected void onProgressUpdate(Object... object) {
super.onProgressUpdate(object);
Log.d("progressUpdate", "progress: "+((Integer)object[0] * 2) + "%");
}
#Override
protected void onPostExecute(Object o) {
// to do
}
}
As you can see the Azure SDK doesn't directly allow for that, but it should be fairly easy to wrap your inputstream in another input stream that can give callbacks for bytes read. Something like that:
public class ListenableInputStream extends InputStream {
private final InputStream wraped;
private final ReadListener listener;
private final long minimumBytesPerCall;
private long bytesRead;
public ListenableInputStream(InputStream wraped, ReadListener listener, int minimumBytesPerCall) {
this.wraped = wraped;
this.listener = listener;
this.minimumBytesPerCall = minimumBytesPerCall;
}
#Override
public int read() throws IOException {
int read = wraped.read();
if (read >= 0) {
bytesRead++;
}
if (bytesRead > minimumBytesPerCall || read == -1) {
listener.onRead(bytesRead);
bytesRead = 0;
}
return read;
}
#Override
public int available() throws IOException {
return wraped.available();
}
#Override
public void close() throws IOException {
wraped.close();
}
#Override
public synchronized void mark(int readlimit) {
wraped.mark(readlimit);
}
#Override
public synchronized void reset() throws IOException {
wraped.reset();
}
#Override
public boolean markSupported() {
return wraped.markSupported();
}
interface ReadListener {
void onRead(long bytes);
}
}
minimumBytesPerCall should be initialised with some sensible number, as you probably don't want to be called on every single byte, maybe every half a megabyte should be good.
And remember that this all gets called on the doInBackground thread, so act accordingly.
edit:
I've edited the class above, there was a small error on computing the bytesRead value.
The official documentation explains your follow-up questions https://developer.android.com/reference/java/io/InputStream.html#read()
Reads the next byte of data from the input stream
So read() reads 1 byte of data (or return -1) if reached the end. So yes, it must be called several several times to read a whole image.
Then the method onRead(long) get's called every time at least minimumBytesPerCall have been read (that's to avoid of calling back for every single byte) and once more at the end of the stream (when it returns -1)
The value passed to onRead(long) is the amount that have been read since the last call. So implementing this on your AsyncTask you would have to accumulate this value and compare with the total size of the file.
Something like the following code inside your asynctask should work fine (assuming the Progress generic parameter is a Long):
private long fileLength;
private long totalBytes;
private final ListenableInputStream.ReadListener readListener = new ListenableInputStream.ReadListener() {
#Override
public void onRead(long bytes) {
totalBytes += bytes;
publishProgress(totalBytes);
}
};
and on inside your upload part you replace with:
FileInputStream fis = new FileInputStream(imageFile);
fileLength = imageFile.length();
ListenableInputStream lis = new ListenableInputStream(fi, readListener, 256 * 1024); // this will call onRead(long) every 256kb
blob.upload(lis, fileLength);
and as a last remark, remember that internally the CloudBlockBlob just caching the file on its own memory for later upload, or doing any other weird stuff that is out of your control. All this code does is check that the complete file was read.
happy coding!
Just another way for your needs, there is a MS blog which introduce about uploading a blob to Azure Storage with progress bar and variable upload block size. That code was written in C#, but it's very simple for reading by Java/Android Developer, I think you can easily rewrite it in Java for Android to compute the uploading processbar ratio to share via some public variables.
Hope it helps.
Here my problem :
I have two mysql databases directory and I want to use one after the other.
The only way that I have actualy found, to switch from one database to the other, is to shutdown the mysql daemon and to start it again pointing to the second database directory.
Are there any other way to perform that ?
Thanks
EDIT :
My application manage "missions directory" that embed a Database.
This missions are copied to an hard disk, that is connected to an external device that will fill this database.
Then when the mission is done, we collect the mission and the database with the application to generate report.
That why we have multiple database with the same schema, but placed in different place, we need also to read this database by an external application that why we need to have only one database open at each time.
My question is not if it possible to run two database from two different directories at the same time, because I know that is possible, but how to switch from one database to another, without kiling the daemon.
PS: I'm working on Java application and I do all this action by system access in Java like Runtime.getRuntime().exec(MY_CMD), not by choice. Maybe it's better to use Java Library, I already use hibernate.
Here the code to switch :
new Thread(new Task<T>() {
#Override
protected T call() throws Exception {
// Close the previous database
if (isDaemonRunning()) {
close();
}
// try to open the new one
if (!open()) {
notifyConnectedStatus(false);
return null;
}
// create the hibernate session object
_session = HibernateUtil.getSessionFactory().openSession();
notifyConnectedStatus(true);
// no return is waiting, then return null
return null;
}
}).start();
Here the called methods :
private boolean open() {
int exitVal = 0;
try {
Process p = Runtime.getRuntime().exec(getRunDaemonCmd());
p.waitFor(1, TimeUnit.SECONDS);
if (p.isAlive()) {
return true;
}
exitVal = p.exitValue();
} catch (Exception e) {
_logger.log(Level.SEVERE, e.getMessage(), e);
return false;
}
return (0 == exitVal);
}
private void close() {
do {
try {
if (null != _session) {
_session.close();
_session = null;
}
Process p = Runtime.getRuntime().exec(SHUTDOWN_CMD);
p.waitFor();
} catch (Exception e) {
_logger.log(Level.SEVERE, e.getMessage(), e);
return;
}
} while (isDaemonRunning());
_connected = false;
}
private String[] getRunDaemonCmd() {
return new String[] { MYSQLD, INI_FILE_PARAM + _myIniFile, DATADIR_PARAM + _databasePath };
}
private boolean isDaemonRunning() {
int exitVal = 0;
try {
Process p = Runtime.getRuntime().exec(PING_CMD);
p.waitFor();
exitVal = p.exitValue();
} catch (Exception e) {
_logger.log(Level.SEVERE, e.getMessage(), e);
}
return (0 == exitVal);
}
And Here the constants :
private static final String MYSQLD = "mysqld";
private static final String INI_FILE_PARAM = "--defaults-file=";
private static final String DATADIR_PARAM = "--datadir=";
private static final String MYSQLADMIN = "mysqladmin";
private static final String USER_PARAM = "-u";
private static final String PASSWORD_PARAM = "-p";
private static final String SHUTDOWN = "shutdown";
private static final String PING = "ping";
private static final String[] PING_CMD = new String[] { MYSQLADMIN, PING };
private static final String[] SHUTDOWN_CMD = new String[] { MYSQLADMIN, USER_PARAM + DatabaseSettings.getUser(),
PASSWORD_PARAM + DatabaseSettings.getPassword(), SHUTDOWN };
private String _myIniFile = DatabaseSettings.getDefaultIniFile();
so, you can use multiple persistence unit to connect with multiple data-source or database if you use hibernate.
I wrote a very simple Java web application ,just included some basic function like register , sign in , changing the password and some others.
I don't use database. I just create a file in the app to record the users' information and do the database stuff.
I used JMeter to stressing the web application, especially the register interface.
The JMeter shows that the result of the 1000 thread is right
but when I look into the information.txt , which stores the users' information, it's wrong because it stores 700+ record :
but it should include 1000 record, it must be somewhere wrong
I use the singleton class to do the write/read stuff, and i add a synchronized word to the class, the insert() function which is used by register to record the register information is shown as below: (a part of it)
public class Database {
private static Database database = null;
private static File file = null;
public synchronized static Database getInstance() {
if (database == null) {
database = new Database();
}
return database;
}
private Database() {
String path = this.getClass().getClassLoader().getResource("/")
.getPath() + "information.txt";
file = new File(path);
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
public void insert(String account, String password, String username) {
RandomAccessFile infoFile = null;
try {
infoFile = new RandomAccessFile(file, "rw");
String record;
long offset = 0;
while ((record = infoFile.readLine()) != null ) {
offset += record.getBytes().length+2;
}
infoFile.seek(offset);
record = account+"|"+password+"|"+username+"\r\n";
infoFile.write(record.getBytes());
infoFile.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (infoFile != null) {
try {
infoFile.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
}
the question is why would this happened , the synchronized is thread safe, why i lost so many data and some blank line was inserted into it, what could I do the correct it !
You are synchronizing the getInstance() method, but not the insert() method. This makes the retrieval of the instance of Database thread-safe, but not the write operation.
I'm writing an app in JavaFX that needs to occasionally load large CSV files around 1,000,000 lines long (or possibly more).
When a user clicks a button to start loading the file, a Service is started to load the contents, with a progress/cancel dialog showing in the meantime. The call() method in the Service is basically a while loop that loads another line from the CSV file on each iteration.
The problem is that when I start the service, the progress bar (indeterminate style) becomes jerky. Dragging the dialog is also jerky and laggy.
I wasn't having good luck searching on the web for a solution, but the closest I found was to put a Thread.sleep() in the loop, giving other things like GC a chance to catch up.
This solution seemed to reduce/remove the stuttering, but it would add a lot of time to loading the data. I am also guessing that the exact time to sleep would vary between different processors.
Is there any way to dynamically figure out how long/often to sleep for? Or call some method that would block for just long enough to keep the GUI responsive?
The code for my service:
public class CSVLoadingService extends Service<List<ObservableList<DoubleProperty>>> {
private ObjectProperty<File> srcFile = new SimpleObjectProperty<>();
private IntegerProperty startIndex = new SimpleIntegerProperty(0);
private ObjectProperty<Character> csvDelimeter = new SimpleObjectProperty(CSVParser.DEFAULT_SEPARATOR);
private DoubleProperty invalidCSVReplacement = new SimpleDoubleProperty(0);
private ObjectProperty<Dialog> dialog = new SimpleObjectProperty<>(null);
#Override
protected Task<List<ObservableList<DoubleProperty>>> createTask() {
return new Task<List<ObservableList<DoubleProperty>>>() {
final ObjectProperty<File> _srcFile = srcFile;
final IntegerProperty _startIndex = startIndex;
final ObjectProperty<Character> _csvDelimeter = csvDelimeter;
final DoubleProperty _invalidCSVReplacement = invalidCSVReplacement;
#Override
protected ObservableList<ObservableList<DoubleProperty>> call() throws Exception {
if (_startIndex.getValue() < 0)
throw new IllegalArgumentException("Start index can't be negative.");
if (_srcFile.getValue() == null)
throw new IllegalStateException("File can't be null.");
final ObservableList<ObservableList<DoubleProperty>> result = FXCollections.observableArrayList();
// Read the data from the CSV file.
try (final CSVReader reader = new CSVReader(new BufferedReader(new FileReader(_srcFile.getValue())),
_csvDelimeter.getValue(),
CSVParser.DEFAULT_QUOTE_CHARACTER,
_startIndex.getValue()))
{
// Read first line.
String[] csvLine = reader.readNext();
// If there is actually data, then read the rest of it.
if (csvLine == null || csvLine.length == 0) {
result.clear();
} else {
// Create columns.
for (String value : csvLine) {
result.add(FXCollections.observableArrayList());
}
// Parse the CSV reads and add them to the columns.
int iteration = 0;
do {
int i = 0;
for (String value : csvLine) {
// Convert the string to a number and add it to the column.
try {
result.get(i).add(new SimpleDoubleProperty(Double.parseDouble(value)));
} catch (NumberFormatException|NullPointerException e) {
result.get(i).add(_invalidCSVReplacement);
}
}
iteration++;
} while (!isCancelled() && null != (csvLine = reader.readNext()));
}
}
return result;
}
};
}
#Override
protected void succeeded() {
super.succeeded();
if (dialog.getValue() != null) {
dialog.getValue().close();
}
}
#Override
protected void failed() {
super.failed();
if (dialog.getValue() != null) {
dialog.getValue().close();
}
}
This is a typical scenario for using thread priorities. You want your GUI thread to have a higher priority than your background thread.
I'm trying to implement the code found below so that I can generate a random ID number for the user right when the app is installed. I just have a couple questions.
If I create a new file for this (Install.java) how do I access the ID in another class?
How do I make sure that this part of the program is executed when the app is first installed? Right now, the program starts on my Main.java class (I'm new to Java). Will it just run when the app is installed?
public class Install {
private static String sID = null;
private static final String INSTALLATION = "INSTALLATION";
public synchronized static String id(Context context) {
if (sID == null) {
File installation = new File(context.getFilesDir(), INSTALLATION);
try {
if (!installation.exists())
writeInstallationFile(installation);
sID = readInstallationFile(installation);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return sID;
}
private static String readInstallationFile(File installation) throws IOException {
RandomAccessFile f = new RandomAccessFile(installation, "r");
byte[] bytes = new byte[(int) f.length()];
f.readFully(bytes);
f.close();
return new String(bytes);
}
private static void writeInstallationFile(File installation) throws IOException {
FileOutputStream out = new FileOutputStream(installation);
String id = UUID.randomUUID().toString();
out.write(id.getBytes());
out.close();
}
}
Here's some code I use - feel free to adapt as you will...
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
Log.d(Tag, "Yay onCreate!"); // sorry sometimes I'm a bit verbose with my logs...
createVerifierStrings();
.....
private void createVerifierStrings() {
SharedPreferences prefs = this.getSharedPreferences("Someprefstringreference", 0);
String not_set = "NOTSET";
String android_key;
android_key = prefs.getString("id", not_set);
if (android_key.equals(not_set)) {
Log.d(Tag, "Creating keys for 1st time");
android_key = generateRandomEnoughStuff();
prefs.edit().putString("id", android_key).commit();
}
......
As far as I know you don't get a way to run any arbitrary code right after installation is complete.
I think the closest you can get is make a check inside your MainActivity onCreate() method that determines whether or not this is the first run (a good way to check this might be to get a reference to your file and call file.exists(), the resulting boolean will tell you whether or not you need to create your UID file.
Here is a blog post from Tim Bray that explains what you actually should be doing..
http://android-developers.blogspot.com/2011/03/identifying-app-installations.html