13

I do have a problem with millis set and read on Android 2.3.4 on a Nexus One. This is the code:

File fileFolder = new File(Environment.getExternalStorageDirectory(), appName + "/"
    + URLDecoder.decode(folder.getUrl()));
if (fileFolder != null && !fileFolder.exists()) {
  fileFolder.setLastModified(1310198774);
  fileFolder.mkdirs();
  fileFolder.setLastModified(1310198774);
}

if (fileFolder != null && fileFolder.exists()) {
  long l = fileFolder.lastModified();
}

In this small test I write 1310198774 but the result that is returned from lastModified() is 1310199771000.

Even if I cut the trailing "000" there's a difference of several minutes.

I need to sync files between a webservice and the Android device. The lastmodification millis are part of the data sent by this service. I do set the millis to the created/copied files and folders to check if the file/folder needs to be overwritten.

Everything is working BUT the millis that are returned from the filesystem are different from the values that were set.

I'm pretty sure there's something wrong with my code - but I can't find it.

Many thanks in advance. HJW

Gray
  • 115,027
  • 24
  • 293
  • 354
Harald Wilhelm
  • 6,656
  • 11
  • 67
  • 85
  • 4
    See http://code.google.com/p/android/issues/detail?id=1699 and http://code.google.com/p/android/issues/detail?id=1992 – JB Nizet Jul 09 '11 at 09:12
  • Thanks for your fast comment. Does this mean that noone can create a timestamp-based sync feature on SD cards on Android devices? Seems that I need to use a database for that... – Harald Wilhelm Jul 09 '11 at 09:25
  • Not by storing the last modified date as a file attribute. – JB Nizet Jul 09 '11 at 09:28
  • Thanks again. Is there any other file-based solution for a sync beside using a database? The later is far from perfect because now there are three parts that might differ (for example if the user manually modifies/deletes files/folders on SD-card). – Harald Wilhelm Jul 09 '11 at 11:34
  • Any feedback on my answer @Harald? – Gray Oct 20 '11 at 17:01

5 Answers5

6

On Jelly Bean+, it's different (mostly on Nexus devices yet, and others that use the new fuse layer for /mnt/shell/emulated sdcard emulation):

It's a VFS permission problem, the syscall utimensat() fails with EPERM due to inappropriate permissions (e.g. ownership).

in platform/system/core/sdcard/sdcard.c:

/* all files owned by root.sdcard */
attr->uid = 0;
attr->gid = AID_SDCARD_RW;

From utimensat()'s syscall man page:

2. the caller's effective user ID must match the owner of the file; or  
3. the caller must have appropriate privileges.  

To make any change other than setting both timestamps to the current time  
(i.e., times is not NULL, and both tv_nsec fields are not UTIME_NOW and both  
tv_nsec fields are not UTIME_OMIT), either condition 2 or 3 above must apply.  

Old FAT offers an override of the iattr->valid flag via a mount option to allow changing timestamps to anyone, FUSE+Android's sdcard-FUSE don't do this at the moment (so the 'inode_change_ok() call fails) and the attempt gets rejected with -EPERM. Here's FAT's ./fs/fat/file.c:

/* Check for setting the inode time. */  
ia_valid = attr->ia_valid;  
if (ia_valid & TIMES_SET_FLAGS) {  
    if (fat_allow_set_time(sbi, inode))  
        attr->ia_valid &= ~TIMES_SET_FLAGS;  
}  

error = inode_change_ok(inode, attr);

I also added this info to this open bug.

ce4
  • 164
  • 1
  • 8
5

If this all doesn't work try this (ugly) workaround quoted from https://code.google.com/p/android/issues/detail?id=18624:

//As a workaround, this ugly hack will set the last modified date to now:      
RandomAccessFile raf = new RandomAccessFile(file, "rw");
long length = raf.length();
raf.setLength(length + 1);
raf.setLength(length);
raf.close();
OneWorld
  • 17,512
  • 21
  • 86
  • 136
arne.jans
  • 3,798
  • 2
  • 22
  • 27
  • This does indeed work. I'm concerned about what performance implications it might have though. In theory this call could cause the file to be relocated by the file system which would make this a *very* expensive call. – Adam Jul 10 '14 at 23:25
  • Would this cause and problem for image/video/pdf files ? – Jaydev May 31 '16 at 07:00
5

So maybe I'm missing something but I see some problems with your code above. Your specific problem may be due (as @JB mentioned) to Android issues but for posterity, I thought I'd provide an answer.

First off, File.setLastModified() takes the time in milliseconds. Here are the javadocs. You seem to be trying to set it in seconds. So your code should be something like:

fileFolder.setLastModified(1310198774000L);

As mentioned in the javadocs, many filesystems only support seconds granularity for last-modification time. So if you need to see the same modification time in a file then you should do something like the following:

private void changeModificationFile(File file, long time) {
    // round the value down to the nearest second
    file.setLastModified((time / 1000) * 1000);
}
Gray
  • 115,027
  • 24
  • 293
  • 354
  • 1
    This doesn't solve the problem at all, it's a bug in Android (https://code.google.com/p/android/issues/detail?id=18624). – fhucho Sep 09 '13 at 08:35
  • Huh? That bug talks about _permissions_ of last modified calls. The OP didn't have a permissions problem. He was using millis instead of seconds @fhucho. I think you are mistaken. – Gray Sep 09 '13 at 11:25
  • setLastModified() doesn't work even if I round it to the nearest second. File permissions seem to be the underlaying reason for this bug. – fhucho Sep 09 '13 at 12:19
1

Works on some devices but not on others. Do not design a solution that relies on it working. See https://code.google.com/p/android/issues/detail?id=18624#c29

Here is a simple test to see if it works.

public void testSetLastModified() throws IOException {
    long time = 1316137362000L;
    File file = new File("/mnt/sdcard/foo");
    file.createNewFile();
    file.setLastModified(time);
    assertEquals(time, file.lastModified());
}
Hayes Haugen
  • 822
  • 1
  • 7
  • 7
0

If you only want to change the date/time of a directory to the current date/time (i.e., "now"), then you can create some sort of temporary file inside that directory, write something into it, then immediately delete it. This has the effect of changing the 'lastModified()' date/time of the directory to the present date/time. This won't work though, if you want to change the directory date/time to some other random value, and can't be applied to a file, obviously.

DDSports
  • 400
  • 1
  • 14