diff --git a/primitiveFTPd/src/org/primftpd/filesystem/FsFile.java b/primitiveFTPd/src/org/primftpd/filesystem/FsFile.java index 8243f12a..b482e9f9 100644 --- a/primitiveFTPd/src/org/primftpd/filesystem/FsFile.java +++ b/primitiveFTPd/src/org/primftpd/filesystem/FsFile.java @@ -23,6 +23,7 @@ public abstract class FsFile extends AbstractFile { protected final File file; + protected final FsFileSystemView fileSystemView; protected final boolean injectedDirectory; private final static Map DIRECTORY_INJECTIONS; @@ -46,13 +47,13 @@ public abstract class FsFile extends AbstractFile { } } INJECTIONS_AND_CHILDREN = Collections.unmodifiableSet(tmp); - } + } - public FsFile(File file, PftpdService pftpdService) { + public FsFile(File file, PftpdService pftpdService, FsFileSystemView fileSystemView) { super( file.getAbsolutePath(), file.getName(), - file.lastModified(), + fileSystemView.getCorrectedTime(file.getAbsolutePath(), file.lastModified()), file.length(), file.canRead(), file.exists(), @@ -60,6 +61,7 @@ public FsFile(File file, PftpdService pftpdService) { pftpdService); this.file = file; this.name = file.getName(); + this.fileSystemView = fileSystemView; this.injectedDirectory = file.isDirectory() && INJECTIONS_AND_CHILDREN.contains(file.getAbsolutePath()); } @@ -144,7 +146,8 @@ public boolean isRemovable() { public boolean setLastModified(long time) { logger.trace("[{}] setLastModified({})", name, Long.valueOf(time)); - return file.setLastModified(time); + long correctedTime = fileSystemView.getCorrectedTime(absPath, time); + return file.setLastModified(correctedTime); } public boolean mkdir() { diff --git a/primitiveFTPd/src/org/primftpd/filesystem/FsFileSystemView.java b/primitiveFTPd/src/org/primftpd/filesystem/FsFileSystemView.java index 33c3d901..d8939380 100644 --- a/primitiveFTPd/src/org/primftpd/filesystem/FsFileSystemView.java +++ b/primitiveFTPd/src/org/primftpd/filesystem/FsFileSystemView.java @@ -1,5 +1,8 @@ package org.primftpd.filesystem; +import android.content.Context; +import android.net.Uri; + import org.primftpd.services.PftpdService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -9,16 +12,25 @@ public abstract class FsFileSystemView, X> { protected final Logger logger = LoggerFactory.getLogger(getClass()); + private final String safVolumePath; + private final int safTimeResolution; protected final PftpdService pftpdService; protected abstract T createFile(File file, PftpdService pftpdService); protected abstract String absolute(String file); - public FsFileSystemView(PftpdService pftpdService) { + public FsFileSystemView(Context context, Uri safStartUrl, PftpdService pftpdService) { + this.safTimeResolution = StorageManagerUtil.getFilesystemTimeResolutionForTreeUri(safStartUrl); + this.safVolumePath = safTimeResolution != 1 ? StorageManagerUtil.getVolumePathFromTreeUri(safStartUrl, context) : null; this.pftpdService = pftpdService; } + public long getCorrectedTime(String abs, long time) { + int timeResolution = safVolumePath != null && abs.startsWith(safVolumePath) ? safTimeResolution : 1; + return (time / timeResolution) * timeResolution; + } + public T getFile(String file) { logger.trace("getFile({})", file); String abs = absolute(file); diff --git a/primitiveFTPd/src/org/primftpd/filesystem/FsFtpFile.java b/primitiveFTPd/src/org/primftpd/filesystem/FsFtpFile.java index d06c67f5..81e6f543 100644 --- a/primitiveFTPd/src/org/primftpd/filesystem/FsFtpFile.java +++ b/primitiveFTPd/src/org/primftpd/filesystem/FsFtpFile.java @@ -9,11 +9,15 @@ public class FsFtpFile extends FsFile implements FtpFile { private final User user; - public FsFtpFile(File file, PftpdService pftpdService, User user) { - super(file, pftpdService); + public FsFtpFile(File file, PftpdService pftpdService, FsFtpFileSystemView fileSystemView, User user) { + super(file, pftpdService, fileSystemView); this.user = user; } + private FsFtpFileSystemView getFileSystemView() { + return (FsFtpFileSystemView)fileSystemView; + } + @Override public String getClientIp() { return FtpUtils.getClientIp(user); @@ -21,7 +25,7 @@ public String getClientIp() { @Override protected FtpFile createFile(File file, PftpdService pftpdService) { - return new FsFtpFile(file, pftpdService, user); + return new FsFtpFile(file, pftpdService, getFileSystemView(), user); } @Override diff --git a/primitiveFTPd/src/org/primftpd/filesystem/FsFtpFileSystemView.java b/primitiveFTPd/src/org/primftpd/filesystem/FsFtpFileSystemView.java index b78586d3..185eec0a 100644 --- a/primitiveFTPd/src/org/primftpd/filesystem/FsFtpFileSystemView.java +++ b/primitiveFTPd/src/org/primftpd/filesystem/FsFtpFileSystemView.java @@ -1,5 +1,8 @@ package org.primftpd.filesystem; +import android.content.Context; +import android.net.Uri; + import java.io.File; import org.apache.ftpserver.ftplet.FtpFile; @@ -14,8 +17,8 @@ public class FsFtpFileSystemView extends FsFileSystemView im private final File homeDir; private FsFtpFile workingDir; - public FsFtpFileSystemView(PftpdService pftpdService, File homeDir, User user) { - super(pftpdService); + public FsFtpFileSystemView(Context context, Uri safStartUrl, PftpdService pftpdService, File homeDir, User user) { + super(context, safStartUrl, pftpdService); this.homeDir = homeDir; workingDir = getHomeDirectory(); this.user = user; @@ -23,7 +26,7 @@ public FsFtpFileSystemView(PftpdService pftpdService, File homeDir, User user) { @Override protected FsFtpFile createFile(File file, PftpdService pftpdService) { - return new FsFtpFile(file, pftpdService, user); + return new FsFtpFile(file, pftpdService, this, user); } @Override diff --git a/primitiveFTPd/src/org/primftpd/filesystem/FsSshFile.java b/primitiveFTPd/src/org/primftpd/filesystem/FsSshFile.java index 5c3397cb..a59b0415 100644 --- a/primitiveFTPd/src/org/primftpd/filesystem/FsSshFile.java +++ b/primitiveFTPd/src/org/primftpd/filesystem/FsSshFile.java @@ -11,11 +11,15 @@ public class FsSshFile extends FsFile implements SshFile { private final Session session; - public FsSshFile(File file, PftpdService pftpdService, Session session) { - super(file, pftpdService); + public FsSshFile(File file, PftpdService pftpdService, FsSshFileSystemView fileSystemView, Session session) { + super(file, pftpdService, fileSystemView); this.session = session; } + private FsSshFileSystemView getFileSystemView() { + return (FsSshFileSystemView)fileSystemView; + } + @Override public String getClientIp() { return SshUtils.getClientIp(session); @@ -23,7 +27,7 @@ public String getClientIp() { @Override protected SshFile createFile(File file, PftpdService pftpdService) { - return new FsSshFile(file, pftpdService, session); + return new FsSshFile(file, pftpdService, getFileSystemView(), session); } @Override @@ -46,7 +50,7 @@ public boolean create() throws IOException { @Override public SshFile getParentFile() { logger.trace("[{}] getParentFile()", name); - return new FsSshFile(file.getParentFile(), pftpdService, session); + return new FsSshFile(file.getParentFile(), pftpdService, getFileSystemView(), session); } @Override diff --git a/primitiveFTPd/src/org/primftpd/filesystem/FsSshFileSystemView.java b/primitiveFTPd/src/org/primftpd/filesystem/FsSshFileSystemView.java index 1d556848..a5818701 100644 --- a/primitiveFTPd/src/org/primftpd/filesystem/FsSshFileSystemView.java +++ b/primitiveFTPd/src/org/primftpd/filesystem/FsSshFileSystemView.java @@ -1,5 +1,8 @@ package org.primftpd.filesystem; +import android.content.Context; +import android.net.Uri; + import java.io.File; import org.apache.sshd.common.file.SshFile; @@ -12,15 +15,15 @@ public class FsSshFileSystemView extends FsFileSystemView im private final File homeDir; private final Session session; - public FsSshFileSystemView(PftpdService pftpdService, File homeDir, Session session) { - super(pftpdService); + public FsSshFileSystemView(Context context, Uri safStartUrl, PftpdService pftpdService, File homeDir, Session session) { + super(context, safStartUrl, pftpdService); this.homeDir = homeDir; this.session = session; } @Override protected FsSshFile createFile(File file, PftpdService pftpdService) { - return new FsSshFile(file, pftpdService, session); + return new FsSshFile(file, pftpdService, this, session); } @Override diff --git a/primitiveFTPd/src/org/primftpd/filesystem/RoSafFile.java b/primitiveFTPd/src/org/primftpd/filesystem/RoSafFile.java index 2778ec2f..e57d22fe 100644 --- a/primitiveFTPd/src/org/primftpd/filesystem/RoSafFile.java +++ b/primitiveFTPd/src/org/primftpd/filesystem/RoSafFile.java @@ -21,6 +21,7 @@ public abstract class RoSafFile extends AbstractFile { private final ContentResolver contentResolver; protected final Uri startUrl; + protected final RoSafFileSystemView fileSystemView; private String documentId; private boolean writable; @@ -40,7 +41,8 @@ public RoSafFile( ContentResolver contentResolver, Uri startUrl, String absPath, - PftpdService pftpdService) { + PftpdService pftpdService, + RoSafFileSystemView fileSystemView) { // this c-tor is to be used for start directory super( absPath, @@ -54,6 +56,7 @@ public RoSafFile( logger.trace(" c-tor 1"); this.contentResolver = contentResolver; this.startUrl = startUrl; + this.fileSystemView = fileSystemView; try { Cursor cursor = contentResolver.query( @@ -85,7 +88,8 @@ public RoSafFile( String docId, String absPath, boolean exists, - PftpdService pftpdService) { + PftpdService pftpdService, + RoSafFileSystemView fileSystemView) { // this c-tor is to be used for FileSystemView.getFile() super( absPath, @@ -99,6 +103,7 @@ public RoSafFile( logger.trace(" c-tor 2"); this.contentResolver = contentResolver; this.startUrl = startUrl; + this.fileSystemView = fileSystemView; if (exists) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { @@ -130,7 +135,8 @@ public RoSafFile( Uri startUrl, Cursor cursor, String absPath, - PftpdService pftpdService) { + PftpdService pftpdService, + RoSafFileSystemView fileSystemView) { // this c-tor is to be used by listFiles() super( absPath, @@ -144,13 +150,14 @@ public RoSafFile( logger.trace(" c-tor 3"); this.contentResolver = contentResolver; this.startUrl = startUrl; + this.fileSystemView = fileSystemView; initByCursor(cursor); } private void initByCursor(Cursor cursor) { documentId = cursor.getString(0); name = cursor.getString(1); - lastModified = cursor.getLong(2); + lastModified = fileSystemView.getCorrectedTime(cursor.getLong(2)); size = cursor.getLong(3); logger.trace(" initByCursor, doc id: {}, name: {}", documentId, name); diff --git a/primitiveFTPd/src/org/primftpd/filesystem/RoSafFileSystemView.java b/primitiveFTPd/src/org/primftpd/filesystem/RoSafFileSystemView.java index c578216f..c032505a 100644 --- a/primitiveFTPd/src/org/primftpd/filesystem/RoSafFileSystemView.java +++ b/primitiveFTPd/src/org/primftpd/filesystem/RoSafFileSystemView.java @@ -20,11 +20,13 @@ public abstract class RoSafFileSystemView, X> { protected final Uri startUrl; protected final ContentResolver contentResolver; protected final PftpdService pftpdService; + protected final int timeResolution; public RoSafFileSystemView(Uri startUrl, ContentResolver contentResolver, PftpdService pftpdService) { this.startUrl = startUrl; this.contentResolver = contentResolver; this.pftpdService = pftpdService; + this.timeResolution = StorageManagerUtil.getFilesystemTimeResolutionForTreeUri(startUrl); } protected abstract String absolute(String file); @@ -47,6 +49,10 @@ protected abstract T createFileNonExistent( String absPath, PftpdService pftpdService); + public long getCorrectedTime(long time) { + return (time / timeResolution) * timeResolution; + } + public T getFile(String file) { logger.trace("getFile({}), startUrl: {}", file, startUrl); diff --git a/primitiveFTPd/src/org/primftpd/filesystem/RoSafFtpFile.java b/primitiveFTPd/src/org/primftpd/filesystem/RoSafFtpFile.java index 5001d2ab..9ed81857 100644 --- a/primitiveFTPd/src/org/primftpd/filesystem/RoSafFtpFile.java +++ b/primitiveFTPd/src/org/primftpd/filesystem/RoSafFtpFile.java @@ -17,8 +17,9 @@ public RoSafFtpFile( Uri startUrl, String absPath, PftpdService pftpdService, + RoSafFtpFileSystemView fileSystemView, User user) { - super(contentResolver, startUrl, absPath, pftpdService); + super(contentResolver, startUrl, absPath, pftpdService, fileSystemView); this.user = user; } @@ -29,8 +30,9 @@ public RoSafFtpFile( String absPath, boolean exists, PftpdService pftpdService, + RoSafFtpFileSystemView fileSystemView, User user) { - super(contentResolver, startUrl, docId, absPath, exists, pftpdService); + super(contentResolver, startUrl, docId, absPath, exists, pftpdService, fileSystemView); this.user = user; } @@ -40,11 +42,16 @@ public RoSafFtpFile( Cursor cursor, String absPath, PftpdService pftpdService, + RoSafFtpFileSystemView fileSystemView, User user) { - super(contentResolver, startUrl, cursor, absPath, pftpdService); + super(contentResolver, startUrl, cursor, absPath, pftpdService, fileSystemView); this.user = user; } + private RoSafFtpFileSystemView getFileSystemView() { + return (RoSafFtpFileSystemView)fileSystemView; + } + @Override protected FtpFile createFile( ContentResolver contentResolver, @@ -52,7 +59,7 @@ protected FtpFile createFile( Cursor cursor, String absPath, PftpdService pftpdService) { - return new RoSafFtpFile(contentResolver, startUrl, cursor, absPath, pftpdService, user); + return new RoSafFtpFile(contentResolver, startUrl, cursor, absPath, pftpdService, getFileSystemView(), user); } @Override diff --git a/primitiveFTPd/src/org/primftpd/filesystem/RoSafFtpFileSystemView.java b/primitiveFTPd/src/org/primftpd/filesystem/RoSafFtpFileSystemView.java index 8d821354..83d7cc0b 100644 --- a/primitiveFTPd/src/org/primftpd/filesystem/RoSafFtpFileSystemView.java +++ b/primitiveFTPd/src/org/primftpd/filesystem/RoSafFtpFileSystemView.java @@ -22,16 +22,16 @@ public RoSafFtpFileSystemView(Uri startUrl, ContentResolver contentResolver, Pft @Override protected RoSafFtpFile createFile(ContentResolver contentResolver, Uri startUrl, String absPath, PftpdService pftpdService) { - return new RoSafFtpFile(contentResolver, startUrl, absPath, pftpdService, user); + return new RoSafFtpFile(contentResolver, startUrl, absPath, pftpdService, this, user); } @Override protected RoSafFtpFile createFile(ContentResolver contentResolver, Uri startUrl, String docId, String absPath, PftpdService pftpdService) { - return new RoSafFtpFile(contentResolver, startUrl, docId, absPath, true, pftpdService, user); + return new RoSafFtpFile(contentResolver, startUrl, docId, absPath, true, pftpdService, this, user); } protected RoSafFtpFile createFileNonExistent(ContentResolver contentResolver, Uri startUrl, String name, String absPath, PftpdService pftpdService) { - return new RoSafFtpFile(contentResolver, startUrl, name, absPath, false, pftpdService, user); + return new RoSafFtpFile(contentResolver, startUrl, name, absPath, false, pftpdService, this, user); } @Override diff --git a/primitiveFTPd/src/org/primftpd/filesystem/RoSafSshFile.java b/primitiveFTPd/src/org/primftpd/filesystem/RoSafSshFile.java index 6c873ac3..eb60ed5a 100644 --- a/primitiveFTPd/src/org/primftpd/filesystem/RoSafSshFile.java +++ b/primitiveFTPd/src/org/primftpd/filesystem/RoSafSshFile.java @@ -19,8 +19,9 @@ public RoSafSshFile( Uri startUrl, String absPath, PftpdService pftpdService, + RoSafSshFileSystemView fileSystemView, Session session) { - super(contentResolver, startUrl, absPath, pftpdService); + super(contentResolver, startUrl, absPath, pftpdService, fileSystemView); this.session = session; } @@ -31,8 +32,9 @@ public RoSafSshFile( String absPath, boolean exists, PftpdService pftpdService, + RoSafSshFileSystemView fileSystemView, Session session) { - super(contentResolver, startUrl, docId, absPath, exists, pftpdService); + super(contentResolver, startUrl, docId, absPath, exists, pftpdService, fileSystemView); this.session = session; } @@ -42,11 +44,16 @@ public RoSafSshFile( Cursor cursor, String absPath, PftpdService pftpdService, + RoSafSshFileSystemView fileSystemView, Session session) { - super(contentResolver, startUrl, cursor, absPath, pftpdService); + super(contentResolver, startUrl, cursor, absPath, pftpdService, fileSystemView); this.session = session; } + private RoSafSshFileSystemView getFileSystemView() { + return (RoSafSshFileSystemView)fileSystemView; + } + @Override protected SshFile createFile( ContentResolver contentResolver, @@ -54,7 +61,7 @@ protected SshFile createFile( Cursor cursor, String absPath, PftpdService pftpdService) { - return new RoSafSshFile(contentResolver, startUrl, cursor, absPath, pftpdService, session); + return new RoSafSshFile(contentResolver, startUrl, cursor, absPath, pftpdService, getFileSystemView(), session); } @Override diff --git a/primitiveFTPd/src/org/primftpd/filesystem/RoSafSshFileSystemView.java b/primitiveFTPd/src/org/primftpd/filesystem/RoSafSshFileSystemView.java index 0123fd4d..09c80d03 100644 --- a/primitiveFTPd/src/org/primftpd/filesystem/RoSafSshFileSystemView.java +++ b/primitiveFTPd/src/org/primftpd/filesystem/RoSafSshFileSystemView.java @@ -19,17 +19,17 @@ public RoSafSshFileSystemView(Uri startUrl, ContentResolver contentResolver, Pft @Override protected RoSafSshFile createFile(ContentResolver contentResolver, Uri startUrl, String absPath, PftpdService pftpdService) { - return new RoSafSshFile(contentResolver, startUrl, absPath, pftpdService, session); + return new RoSafSshFile(contentResolver, startUrl, absPath, pftpdService, this, session); } @Override protected RoSafSshFile createFile(ContentResolver contentResolver, Uri startUrl, String docId, String absPath, PftpdService pftpdService) { - return new RoSafSshFile(contentResolver, startUrl, docId, absPath, true, pftpdService, session); + return new RoSafSshFile(contentResolver, startUrl, docId, absPath, true, pftpdService, this, session); } @Override protected RoSafSshFile createFileNonExistent(ContentResolver contentResolver, Uri startUrl, String name, String absPath, PftpdService pftpdService) { - return new RoSafSshFile(contentResolver, startUrl, name, absPath, false, pftpdService, session); + return new RoSafSshFile(contentResolver, startUrl, name, absPath, false, pftpdService, this, session); } @Override diff --git a/primitiveFTPd/src/org/primftpd/filesystem/SafFile.java b/primitiveFTPd/src/org/primftpd/filesystem/SafFile.java index 23c06a2f..032234c0 100644 --- a/primitiveFTPd/src/org/primftpd/filesystem/SafFile.java +++ b/primitiveFTPd/src/org/primftpd/filesystem/SafFile.java @@ -40,6 +40,7 @@ public abstract class SafFile extends AbstractFile { private DocumentFile documentFile; private final DocumentFile parentDocumentFile; + protected final SafFileSystemView fileSystemView; private boolean writable; @@ -48,12 +49,13 @@ public SafFile( DocumentFile parentDocumentFile, DocumentFile documentFile, String absPath, - PftpdService pftpdService) { + PftpdService pftpdService, + SafFileSystemView fileSystemView) { // this c-tor is to be used to access existing files super( absPath, null, - documentFile.lastModified(), + fileSystemView.getCorrectedTime(documentFile.lastModified()), documentFile.length(), documentFile.canRead(), documentFile.exists(), @@ -65,6 +67,7 @@ public SafFile( this.parentDocumentFile = parentDocumentFile; this.documentFile = documentFile; + this.fileSystemView = fileSystemView; name = documentFile.getName(); if (name == null && SafFileSystemView.ROOT_PATH.equals(absPath)) { @@ -78,7 +81,8 @@ public SafFile( DocumentFile parentDocumentFile, String name, String absPath, - PftpdService pftpdService) { + PftpdService pftpdService, + SafFileSystemView fileSystemView) { // this c-tor is to be used to upload new files, create directories or renaming super(absPath, name, 0, 0, false, false, false, pftpdService); String parentName = parentDocumentFile.getName(); @@ -89,6 +93,7 @@ public SafFile( this.writable = true; this.parentDocumentFile = parentDocumentFile; + this.fileSystemView = fileSystemView; } protected abstract T createFile( @@ -127,7 +132,9 @@ public boolean setLastModified(long time) { try { Uri docUri = documentFile.getUri(); Path filePath = Paths.get(StorageManagerUtil.getFullDocIdPathFromTreeUri(docUri, pftpdService.getContext())); - Files.getFileAttributeView(filePath, BasicFileAttributeView.class).setTimes(FileTime.fromMillis(time), null, null); + long correctedTime = fileSystemView.getCorrectedTime(time); + Files.getFileAttributeView(filePath, BasicFileAttributeView.class).setTimes(FileTime.fromMillis(correctedTime), null, null); + return true; } catch (Exception e) { String baseMsg = "could not set last modified time"; logger.error(baseMsg, e); @@ -239,7 +246,7 @@ boolean createNewFile() throws IOException { } if (documentFile != null) { - lastModified = documentFile.lastModified(); + lastModified = fileSystemView.getCorrectedTime(documentFile.lastModified()); size = 0; readable = documentFile.canRead(); exists = true; diff --git a/primitiveFTPd/src/org/primftpd/filesystem/SafFileSystemView.java b/primitiveFTPd/src/org/primftpd/filesystem/SafFileSystemView.java index bfddd20b..c6d21e7b 100644 --- a/primitiveFTPd/src/org/primftpd/filesystem/SafFileSystemView.java +++ b/primitiveFTPd/src/org/primftpd/filesystem/SafFileSystemView.java @@ -25,12 +25,14 @@ public abstract class SafFileSystemView, X> { protected final Uri startUrl; protected final ContentResolver contentResolver; protected final PftpdService pftpdService; + protected final int timeResolution; public SafFileSystemView(Context context, Uri startUrl, ContentResolver contentResolver, PftpdService pftpdService) { this.context = context; this.startUrl = startUrl; this.contentResolver = contentResolver; this.pftpdService = pftpdService; + this.timeResolution = StorageManagerUtil.getFilesystemTimeResolutionForTreeUri(startUrl); } protected abstract T createFile( @@ -48,6 +50,10 @@ protected abstract T createFile( protected abstract String absolute(String file); + public long getCorrectedTime(long time) { + return (time / timeResolution) * timeResolution; + } + public T getFile(String file) { logger.trace("getFile({}), startUrl: {}", file, startUrl); diff --git a/primitiveFTPd/src/org/primftpd/filesystem/SafFtpFile.java b/primitiveFTPd/src/org/primftpd/filesystem/SafFtpFile.java index bb83bc30..6dbabd94 100644 --- a/primitiveFTPd/src/org/primftpd/filesystem/SafFtpFile.java +++ b/primitiveFTPd/src/org/primftpd/filesystem/SafFtpFile.java @@ -17,8 +17,9 @@ public SafFtpFile( DocumentFile documentFile, String absPath, PftpdService pftpdService, + SafFtpFileSystemView fileSystemView, User user) { - super(contentResolver, parentDocumentFile, documentFile, absPath, pftpdService); + super(contentResolver, parentDocumentFile, documentFile, absPath, pftpdService, fileSystemView); this.user = user; } @@ -28,11 +29,16 @@ public SafFtpFile( String name, String absPath, PftpdService pftpdService, + SafFtpFileSystemView fileSystemView, User user) { - super(contentResolver, parentDocumentFile, name, absPath, pftpdService); + super(contentResolver, parentDocumentFile, name, absPath, pftpdService, fileSystemView); this.user = user; } + private SafFtpFileSystemView getFileSystemView() { + return (SafFtpFileSystemView)fileSystemView; + } + @Override protected FtpFile createFile( ContentResolver contentResolver, @@ -40,7 +46,7 @@ protected FtpFile createFile( DocumentFile documentFile, String absPath, PftpdService pftpdService) { - return new SafFtpFile(contentResolver, parentDocumentFile, documentFile, absPath, pftpdService, user); + return new SafFtpFile(contentResolver, parentDocumentFile, documentFile, absPath, pftpdService, getFileSystemView(), user); } @Override diff --git a/primitiveFTPd/src/org/primftpd/filesystem/SafFtpFileSystemView.java b/primitiveFTPd/src/org/primftpd/filesystem/SafFtpFileSystemView.java index 09b3d8bc..95805426 100644 --- a/primitiveFTPd/src/org/primftpd/filesystem/SafFtpFileSystemView.java +++ b/primitiveFTPd/src/org/primftpd/filesystem/SafFtpFileSystemView.java @@ -30,7 +30,7 @@ protected SafFtpFile createFile( String absPath, PftpdService pftpdService) { logger.trace("createFile(DocumentFile)"); - return new SafFtpFile(contentResolver, parentDocumentFile, documentFile, absPath, pftpdService, user); + return new SafFtpFile(contentResolver, parentDocumentFile, documentFile, absPath, pftpdService, this, user); } @Override @@ -41,7 +41,7 @@ protected SafFtpFile createFile( String absPath, PftpdService pftpdService) { logger.trace("createFile(String)"); - return new SafFtpFile(contentResolver, parentDocumentFile, name, absPath, pftpdService, user); + return new SafFtpFile(contentResolver, parentDocumentFile, name, absPath, pftpdService, this, user); } @Override diff --git a/primitiveFTPd/src/org/primftpd/filesystem/SafSshFile.java b/primitiveFTPd/src/org/primftpd/filesystem/SafSshFile.java index 7d2de931..6217697e 100644 --- a/primitiveFTPd/src/org/primftpd/filesystem/SafSshFile.java +++ b/primitiveFTPd/src/org/primftpd/filesystem/SafSshFile.java @@ -13,7 +13,6 @@ public class SafSshFile extends SafFile implements SshFile { private final Session session; - private final SafSshFileSystemView fileSystemView; public SafSshFile( ContentResolver contentResolver, @@ -21,11 +20,10 @@ public SafSshFile( DocumentFile documentFile, String absPath, PftpdService pftpdService, - Session session, - SafSshFileSystemView fileSystemView) { - super(contentResolver, parentDocumentFile, documentFile, absPath, pftpdService); + SafSshFileSystemView fileSystemView, + Session session) { + super(contentResolver, parentDocumentFile, documentFile, absPath, pftpdService, fileSystemView); this.session = session; - this.fileSystemView = fileSystemView; } public SafSshFile( @@ -34,11 +32,14 @@ public SafSshFile( String name, String absPath, PftpdService pftpdService, - Session session, - SafSshFileSystemView fileSystemView) { - super(contentResolver, parentDocumentFile, name, absPath, pftpdService); + SafSshFileSystemView fileSystemView, + Session session) { + super(contentResolver, parentDocumentFile, name, absPath, pftpdService, fileSystemView); this.session = session; - this.fileSystemView = fileSystemView; + } + + private SafSshFileSystemView getFileSystemView() { + return (SafSshFileSystemView)fileSystemView; } @Override @@ -48,7 +49,7 @@ protected SshFile createFile( DocumentFile documentFile, String absPath, PftpdService pftpdService) { - return new SafSshFile(contentResolver, parentDocumentFile, documentFile, absPath, pftpdService, session, fileSystemView); + return new SafSshFile(contentResolver, parentDocumentFile, documentFile, absPath, pftpdService, getFileSystemView(), session); } @Override @@ -86,7 +87,7 @@ public SshFile getParentFile() { parentPath = "/"; } logger.trace("[{}] getParentFile() -> {}", name, parentPath); - return fileSystemView.getFile(parentPath); + return getFileSystemView().getFile(parentPath); } @Override diff --git a/primitiveFTPd/src/org/primftpd/filesystem/SafSshFileSystemView.java b/primitiveFTPd/src/org/primftpd/filesystem/SafSshFileSystemView.java index 5fab924b..28eaecea 100644 --- a/primitiveFTPd/src/org/primftpd/filesystem/SafSshFileSystemView.java +++ b/primitiveFTPd/src/org/primftpd/filesystem/SafSshFileSystemView.java @@ -26,7 +26,7 @@ protected SafSshFile createFile( DocumentFile documentFile, String absPath, PftpdService pftpdService) { - return new SafSshFile(contentResolver, parentDocumentFile, documentFile, absPath, pftpdService, session, this); + return new SafSshFile(contentResolver, parentDocumentFile, documentFile, absPath, pftpdService, this, session); } @Override @@ -36,7 +36,7 @@ protected SafSshFile createFile( String name, String absPath, PftpdService pftpdService) { - return new SafSshFile(contentResolver, parentDocumentFile, name, absPath, pftpdService, session, this); + return new SafSshFile(contentResolver, parentDocumentFile, name, absPath, pftpdService, this, session); } @Override diff --git a/primitiveFTPd/src/org/primftpd/filesystem/StorageManagerUtil.java b/primitiveFTPd/src/org/primftpd/filesystem/StorageManagerUtil.java index f5122d20..19d4fae3 100644 --- a/primitiveFTPd/src/org/primftpd/filesystem/StorageManagerUtil.java +++ b/primitiveFTPd/src/org/primftpd/filesystem/StorageManagerUtil.java @@ -11,14 +11,23 @@ import androidx.annotation.Nullable; import androidx.documentfile.provider.DocumentFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; +import java.io.FileReader; import java.lang.reflect.Array; import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; public final class StorageManagerUtil { private static final String PRIMARY_VOLUME_NAME = "primary"; + private static Logger logger = LoggerFactory.getLogger(StorageManagerUtil.class); + public static String getFullDocIdPathFromTreeUri(@Nullable final Uri treeUri, Context context) { if (treeUri == null) { return null; @@ -47,6 +56,111 @@ public static String getFullDocIdPathFromTreeUri(@Nullable final Uri treeUri, Co } } + public static String getVolumePathFromTreeUri(@Nullable final Uri treeUri, Context context) { + if (treeUri == null) { + return null; + } + String volumePath = getVolumePath(getVolumeIdFromTreeUri(treeUri), context); + if (volumePath == null) { + return null; + } + if (! volumePath.endsWith(File.separator)) { + volumePath += File.separator; + } + return volumePath; + } + + private final static Map cachedFilesystemTimeResolutions = new HashMap<>(); + + /** + * This function is to handle 2 Android bugs. + *

+ * 1. Android caches the modification times for files, ie. when we read back the modification time of a freshly created file, + * Andorid will lie and return the value the file was asked to be saved, and not the value the real file-system was able to store, + * so we have to figure out whether the underlying file-system is an SD-card related and use the file-system specific resolution. + *

+ * 2. Even when the SD-card is mounted at /mnt/media_rw/XXXX-XXXX with the proper file-system, when it is mounted at /storage/XXXX-XXXX, + * the used sdcardfs has another bug, it provides the same 2s resolution even for the exfat file-system, + * so in this case we have to modify the resolution even for the exfat file-system to 2s. + * + * @param startUrl SAF startUrl + * @return SAF file system's time resolution measured in milliseconds + */ + public static int getFilesystemTimeResolutionForTreeUri(Uri startUrl) { + logger.trace("getFilesystemTimeResolutionForTreeUri({})", startUrl); + int timeResolution = 1; // use 1ms by default + String volumeId = getVolumeIdFromTreeUri(startUrl); + if (volumeId != null) { + Integer cachedTimeResolution = cachedFilesystemTimeResolutions.get(volumeId); + if (cachedTimeResolution != null) { + timeResolution = cachedTimeResolution.intValue(); + logger.trace(" used cached value"); + } else { + int mediaTimeResolution = 0; + int storageTimeResolution = 0; + String mediaMountPoint = "/mnt/media_rw/" + volumeId; + String mediaOption = null; + String storageMountPoint = "/storage/" + volumeId; + try(BufferedReader br = new BufferedReader(new FileReader("/proc/mounts"))) { + // sample contents for /proc/mounts + // /dev/block/vold/public:xxx,xx /mnt/media_rw/XXXX-XXXX vfat ... 0 0 -> 2000 ms + // /dev/block/vold/public:xxx,xx /mnt/media_rw/XXXX-XXXX sdfat ...,fs=vfat:16,... 0 0 -> 2000 ms + // /dev/block/vold/public:xxx,xx /mnt/media_rw/XXXX-XXXX sdfat ...,fs=vfat:32,... 0 0 -> 2000 ms + // /dev/block/vold/public:xxx,xx /mnt/media_rw/XXXX-XXXX sdfat ...,fs=exfat,... 0 0 -> 10 ms + // /mnt/media_rw/XXXX-XXXX /storage/XXXX-XXXX sdcardfs ... 0 0 -> 2000 ms + for (String line; (line = br.readLine()) != null; ) { + logger.trace(" {}", line); + String[] mountInformations = line.split(" "); + if (mountInformations.length >= 4) { + if (mediaTimeResolution == 0 && mountInformations[1].equals(mediaMountPoint)) { + if (mountInformations[2].equals("vfat")) { + mediaTimeResolution = 2000; + } else if (mountInformations[2].equals("sdfat")) { + mediaTimeResolution = 2000; // use 2000ms by default + for (String option : mountInformations[3].split(",")) { + if (option.startsWith("fs=")) { + mediaOption = option; + if (option.startsWith("fs=vfat")) { + mediaTimeResolution = 2000; + } else if (option.startsWith("fs=exfat")) { + mediaTimeResolution = 10; + } + break; + } + } + } else { + mediaTimeResolution = 1; + } + if (mediaOption == null) { + logger.trace(" found media mount point {} with type {} -> {}ms", new Object[]{mountInformations[1], mountInformations[2], mediaTimeResolution}); + } else { + logger.trace(" found media mount point {} with type {} with option {} -> {}ms", new Object[]{mountInformations[1], mountInformations[2], mediaOption, mediaTimeResolution}); + } + } + if (storageTimeResolution == 0 && mountInformations[1].equals(storageMountPoint)) { + if (mountInformations[2].equals("sdcardfs")) { + storageTimeResolution = 2000; + } else { + storageTimeResolution = 1; + } + logger.trace(" found storage mount point {} with type {} -> {}ms", new Object[]{mountInformations[1], mountInformations[2], storageTimeResolution}); + } + if (mediaTimeResolution != 0 && storageTimeResolution != 0) { + break; + } + } + } + timeResolution = Math.max(timeResolution, Math.max(mediaTimeResolution, storageTimeResolution)); + cachedFilesystemTimeResolutions.put(volumeId, timeResolution); + } catch (Exception e) { + logger.error("getFilesystemTimeResolutionForTreeUri() {}", e); + } + } + } + logger.trace(" getFilesystemTimeResolutionForTreeUri({}) -> {}", startUrl, timeResolution); + return timeResolution; + } + @SuppressLint("ObsoleteSdkInt") private static String getVolumePath(final String volumeId, Context context) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { @@ -77,32 +191,34 @@ private static String getVolumePath(final String volumeId, Context context) { return (String) getPath.invoke(storageVolumeElement); } } - // not found. - return null; } catch (Exception ex) { - return null; } + return null; } @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public static String getVolumeIdFromTreeUri(final Uri treeUri) { - final String docId = DocumentsContract.getTreeDocumentId(treeUri); - final String[] split = docId.split(":"); - if (split.length > 0) { - return split[0]; - } else { - return null; + private static String getVolumeIdFromTreeUri(final Uri treeUri) { + try { + final String docId = DocumentsContract.getTreeDocumentId(treeUri); + final String[] split = docId.split(":"); + if (split.length > 0) { + return split[0]; + } + } catch (Exception ex) { } + return null; } - @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @TargetApi(Build.VERSION_CODES.LOLLIPOP) private static String getDocumentPathFromTreeUri(final Uri treeUri) { - final String docId = DocumentsContract.getDocumentId(treeUri); - final String[] split = docId.split(":"); - if ((split.length >= 2) && (split[1] != null)) { - return split[1]; - } else { - return File.separator; + try { + final String docId = DocumentsContract.getDocumentId(treeUri); + final String[] split = docId.split(":"); + if ((split.length >= 2) && (split[1] != null)) { + return split[1]; + } + } catch (Exception ex) { } + return File.separator; } } diff --git a/primitiveFTPd/src/org/primftpd/services/FtpServerService.java b/primitiveFTPd/src/org/primftpd/services/FtpServerService.java index d514dd02..8395c21e 100644 --- a/primitiveFTPd/src/org/primftpd/services/FtpServerService.java +++ b/primitiveFTPd/src/org/primftpd/services/FtpServerService.java @@ -121,7 +121,12 @@ public FileSystemView createFileSystemView(User user) { } else { switch (prefsBean.getStorageType()) { case PLAIN: - return new FsFtpFileSystemView(FtpServerService.this, prefsBean.getStartDir(), user); + return new FsFtpFileSystemView( + getApplicationContext(), + Uri.parse(prefsBean.getSafUrl()), + FtpServerService.this, + prefsBean.getStartDir(), + user); case ROOT: return new RootFtpFileSystemView(shell, FtpServerService.this, prefsBean.getStartDir(), user); case SAF: @@ -139,7 +144,12 @@ public FileSystemView createFileSystemView(User user) { user); case VIRTUAL: return new VirtualFtpFileSystemView( - new FsFtpFileSystemView(FtpServerService.this, prefsBean.getStartDir(), user), + new FsFtpFileSystemView( + getApplicationContext(), + Uri.parse(prefsBean.getSafUrl()), + FtpServerService.this, + prefsBean.getStartDir(), + user), new RootFtpFileSystemView(shell, FtpServerService.this, prefsBean.getStartDir(), user), new SafFtpFileSystemView( getApplicationContext(), diff --git a/primitiveFTPd/src/org/primftpd/services/SshServerService.java b/primitiveFTPd/src/org/primftpd/services/SshServerService.java index 324e6a8a..eff9da90 100644 --- a/primitiveFTPd/src/org/primftpd/services/SshServerService.java +++ b/primitiveFTPd/src/org/primftpd/services/SshServerService.java @@ -206,7 +206,12 @@ public FileSystemView createFileSystemView(Session session) } else { switch (prefsBean.getStorageType()) { case PLAIN: - return new FsSshFileSystemView(SshServerService.this, prefsBean.getStartDir(), session); + return new FsSshFileSystemView( + getApplicationContext(), + Uri.parse(prefsBean.getSafUrl()), + SshServerService.this, + prefsBean.getStartDir(), + session); case ROOT: return new RootSshFileSystemView(shell, SshServerService.this, prefsBean.getStartDir(), session); case SAF: @@ -224,7 +229,12 @@ public FileSystemView createFileSystemView(Session session) session); case VIRTUAL: return new VirtualSshFileSystemView( - new FsSshFileSystemView(SshServerService.this, prefsBean.getStartDir(), session), + new FsSshFileSystemView( + getApplicationContext(), + Uri.parse(prefsBean.getSafUrl()), + SshServerService.this, + prefsBean.getStartDir(), + session), new RootSshFileSystemView(shell, SshServerService.this, prefsBean.getStartDir(), session), new SafSshFileSystemView( getApplicationContext(),