mirror of
https://github.com/mamoe/mirai.git
synced 2024-12-28 17:40:09 +08:00
Redesign group files (#1589)
* Prototype new `RemoteFiles` design * add `@JavaFriendlyAPI` * remove `quietly` * move `moveTo` to `AbsoluteFile` * Add java friendly apis * Remove `condoneMissing` * Change `renameTo` * Extract interface declarations * update docs * Add `AbsoluteFileFolder.exists` * Add common ProgressionCallback * Implement `RemoteFiles` and relevant `Absolute*` * Implement `refresh` and `refreshed` * Update docs * Forbid blank paths * Update docs * Deprecate `RemoteFile` and implement `FileMessage.toAbsoluteFile` * Change corresponding properties on operations * Deprecate more old declarations * Update docs * Add check for permission * Allow relative paths and fix upload * fix absolutePath * doc update * api dump * `Result<R>.onSuccessCatching` * return null when file not exists * Fix file uploading * Fix folder.absolutePath * add `resolveFileById` * Implement toString * Add `nameWithoutExtension` and `extension` * Add `deep` to resolveFileById * Implement permission check * Remove notes * Fix `resolveFileById` * Fix `extension` * add docs * Improve docs Co-authored-by: Karlatemp <karlatemp@vip.qq.com>
This commit is contained in:
parent
767475f9ab
commit
9e151e7026
@ -328,6 +328,7 @@ public final class net/mamoe/mirai/contact/ExceptionsKt {
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/contact/FileSupported : net/mamoe/mirai/contact/Contact {
|
||||
public abstract fun getFiles ()Lnet/mamoe/mirai/contact/file/RemoteFiles;
|
||||
public abstract fun getFilesRoot ()Lnet/mamoe/mirai/utils/RemoteFile;
|
||||
}
|
||||
|
||||
@ -749,6 +750,112 @@ public final class net/mamoe/mirai/contact/announcement/OnlineAnnouncementKt {
|
||||
public static final fun getBot (Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement;)Lnet/mamoe/mirai/Bot;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/contact/file/AbsoluteFile : net/mamoe/mirai/contact/file/AbsoluteFileFolder {
|
||||
public abstract fun getExpiryTime ()J
|
||||
public abstract fun getMd5 ()[B
|
||||
public abstract fun getSha1 ()[B
|
||||
public abstract fun getSize ()J
|
||||
public fun getUrl ()Ljava/lang/String;
|
||||
public abstract fun getUrl (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun moveTo (Lnet/mamoe/mirai/contact/file/AbsoluteFolder;)Z
|
||||
public abstract fun moveTo (Lnet/mamoe/mirai/contact/file/AbsoluteFolder;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun refreshed ()Lnet/mamoe/mirai/contact/file/AbsoluteFile;
|
||||
public abstract fun refreshed (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun toMessage ()Lnet/mamoe/mirai/message/data/FileMessage;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/contact/file/AbsoluteFileFolder {
|
||||
public static final field Companion Lnet/mamoe/mirai/contact/file/AbsoluteFileFolder$Companion;
|
||||
public fun delete ()Z
|
||||
public abstract fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun exists ()Z
|
||||
public abstract fun exists (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun getAbsolutePath ()Ljava/lang/String;
|
||||
public abstract fun getContact ()Lnet/mamoe/mirai/contact/FileSupported;
|
||||
public static fun getExtension (Lnet/mamoe/mirai/contact/file/AbsoluteFileFolder;)Ljava/lang/String;
|
||||
public abstract fun getId ()Ljava/lang/String;
|
||||
public abstract fun getLastModifiedTime ()J
|
||||
public abstract fun getName ()Ljava/lang/String;
|
||||
public static fun getNameWithoutExtension (Lnet/mamoe/mirai/contact/file/AbsoluteFileFolder;)Ljava/lang/String;
|
||||
public abstract fun getParent ()Lnet/mamoe/mirai/contact/file/AbsoluteFolder;
|
||||
public abstract fun getUploadTime ()J
|
||||
public abstract fun getUploaderId ()J
|
||||
public abstract fun isFile ()Z
|
||||
public abstract fun isFolder ()Z
|
||||
public fun refresh ()Z
|
||||
public abstract fun refresh (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun refreshed ()Lnet/mamoe/mirai/contact/file/AbsoluteFileFolder;
|
||||
public abstract fun refreshed (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun renameTo (Ljava/lang/String;)Z
|
||||
public abstract fun renameTo (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/contact/file/AbsoluteFileFolder$Companion {
|
||||
public final fun getExtension (Lnet/mamoe/mirai/contact/file/AbsoluteFileFolder;)Ljava/lang/String;
|
||||
public final fun getNameWithoutExtension (Lnet/mamoe/mirai/contact/file/AbsoluteFileFolder;)Ljava/lang/String;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/contact/file/AbsoluteFolder : net/mamoe/mirai/contact/file/AbsoluteFileFolder {
|
||||
public static final field Companion Lnet/mamoe/mirai/contact/file/AbsoluteFolder$Companion;
|
||||
public static final field ROOT_FOLDER_ID Ljava/lang/String;
|
||||
public fun children ()Lkotlinx/coroutines/flow/Flow;
|
||||
public abstract fun children (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun childrenStream ()Ljava/util/stream/Stream;
|
||||
public abstract fun childrenStream (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun createFolder (Ljava/lang/String;)Lnet/mamoe/mirai/contact/file/AbsoluteFolder;
|
||||
public abstract fun createFolder (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun files ()Lkotlinx/coroutines/flow/Flow;
|
||||
public abstract fun files (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun filesStream ()Ljava/util/stream/Stream;
|
||||
public abstract fun filesStream (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun folders ()Lkotlinx/coroutines/flow/Flow;
|
||||
public abstract fun folders (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun foldersStream ()Ljava/util/stream/Stream;
|
||||
public abstract fun foldersStream (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun getContentsCount ()I
|
||||
public fun isEmpty ()Z
|
||||
public fun refreshed ()Lnet/mamoe/mirai/contact/file/AbsoluteFolder;
|
||||
public abstract fun refreshed (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun resolveAll (Ljava/lang/String;)Lkotlinx/coroutines/flow/Flow;
|
||||
public abstract fun resolveAll (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun resolveAllStream (Ljava/lang/String;)Ljava/util/stream/Stream;
|
||||
public abstract fun resolveAllStream (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun resolveFileById (Ljava/lang/String;)Lnet/mamoe/mirai/contact/file/AbsoluteFile;
|
||||
public fun resolveFileById (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun resolveFileById (Ljava/lang/String;Z)Lnet/mamoe/mirai/contact/file/AbsoluteFile;
|
||||
public abstract fun resolveFileById (Ljava/lang/String;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public static synthetic fun resolveFileById$default (Lnet/mamoe/mirai/contact/file/AbsoluteFolder;Ljava/lang/String;ZILjava/lang/Object;)Lnet/mamoe/mirai/contact/file/AbsoluteFile;
|
||||
public static synthetic fun resolveFileById$default (Lnet/mamoe/mirai/contact/file/AbsoluteFolder;Ljava/lang/String;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
|
||||
public fun resolveFiles (Ljava/lang/String;)Lkotlinx/coroutines/flow/Flow;
|
||||
public abstract fun resolveFiles (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun resolveFilesStream (Ljava/lang/String;)Ljava/util/stream/Stream;
|
||||
public abstract fun resolveFilesStream (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun resolveFolder (Ljava/lang/String;)Lnet/mamoe/mirai/contact/file/AbsoluteFolder;
|
||||
public abstract fun resolveFolder (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/contact/file/AbsoluteFile;
|
||||
public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;)Lnet/mamoe/mirai/contact/file/AbsoluteFile;
|
||||
public abstract fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public static synthetic fun uploadNewFile$default (Lnet/mamoe/mirai/contact/file/AbsoluteFolder;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/contact/file/AbsoluteFile;
|
||||
public static synthetic fun uploadNewFile$default (Lnet/mamoe/mirai/contact/file/AbsoluteFolder;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/contact/file/AbsoluteFolder$Companion {
|
||||
public static final field ROOT_FOLDER_ID Ljava/lang/String;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/contact/file/RemoteFiles {
|
||||
public abstract fun getContact ()Lnet/mamoe/mirai/contact/FileSupported;
|
||||
public abstract fun getRoot ()Lnet/mamoe/mirai/contact/file/AbsoluteFolder;
|
||||
public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/contact/file/AbsoluteFile;
|
||||
public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;)Lnet/mamoe/mirai/contact/file/AbsoluteFile;
|
||||
public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public static synthetic fun uploadNewFile$default (Lnet/mamoe/mirai/contact/file/RemoteFiles;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/contact/file/AbsoluteFile;
|
||||
public static synthetic fun uploadNewFile$default (Lnet/mamoe/mirai/contact/file/RemoteFiles;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/contact/roaming/RoamingMessage {
|
||||
public fun getBot ()Lnet/mamoe/mirai/Bot;
|
||||
public abstract fun getContact ()Lnet/mamoe/mirai/contact/Contact;
|
||||
@ -4034,6 +4141,8 @@ public abstract interface class net/mamoe/mirai/message/data/FileMessage : net/m
|
||||
public synthetic fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey;
|
||||
public abstract fun getName ()Ljava/lang/String;
|
||||
public abstract fun getSize ()J
|
||||
public fun toAbsoluteFile (Lnet/mamoe/mirai/contact/FileSupported;)Lnet/mamoe/mirai/contact/file/AbsoluteFile;
|
||||
public abstract fun toAbsoluteFile (Lnet/mamoe/mirai/contact/FileSupported;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun toRemoteFile (Lnet/mamoe/mirai/contact/FileSupported;)Lnet/mamoe/mirai/utils/RemoteFile;
|
||||
public fun toRemoteFile (Lnet/mamoe/mirai/contact/FileSupported;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
}
|
||||
@ -5977,6 +6086,21 @@ public final class net/mamoe/mirai/utils/OverFileSizeMaxException : java/lang/Il
|
||||
public fun <init> ()V
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/utils/ProgressionCallback {
|
||||
public static final field Companion Lnet/mamoe/mirai/utils/ProgressionCallback$Companion;
|
||||
public static fun asProgressionCallback (Lkotlinx/coroutines/channels/SendChannel;Z)Lnet/mamoe/mirai/utils/ProgressionCallback;
|
||||
public fun onBegin (Ljava/lang/Object;Lnet/mamoe/mirai/utils/ExternalResource;)V
|
||||
public fun onFailure (Ljava/lang/Object;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/Throwable;)V
|
||||
public fun onFinished (Ljava/lang/Object;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/Object;)V
|
||||
public fun onProgression (Ljava/lang/Object;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/Object;)V
|
||||
public fun onSuccess (Ljava/lang/Object;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/Object;)V
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/utils/ProgressionCallback$Companion {
|
||||
public final fun asProgressionCallback (Lkotlinx/coroutines/channels/SendChannel;Z)Lnet/mamoe/mirai/utils/ProgressionCallback;
|
||||
public static synthetic fun asProgressionCallback$default (Lnet/mamoe/mirai/utils/ProgressionCallback$Companion;Lkotlinx/coroutines/channels/SendChannel;ZILjava/lang/Object;)Lnet/mamoe/mirai/utils/ProgressionCallback;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/utils/RemoteFile {
|
||||
public static final field Companion Lnet/mamoe/mirai/utils/RemoteFile$Companion;
|
||||
public static final field ROOT_PATH Ljava/lang/String;
|
||||
|
@ -328,6 +328,7 @@ public final class net/mamoe/mirai/contact/ExceptionsKt {
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/contact/FileSupported : net/mamoe/mirai/contact/Contact {
|
||||
public abstract fun getFiles ()Lnet/mamoe/mirai/contact/file/RemoteFiles;
|
||||
public abstract fun getFilesRoot ()Lnet/mamoe/mirai/utils/RemoteFile;
|
||||
}
|
||||
|
||||
@ -749,6 +750,112 @@ public final class net/mamoe/mirai/contact/announcement/OnlineAnnouncementKt {
|
||||
public static final fun getBot (Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement;)Lnet/mamoe/mirai/Bot;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/contact/file/AbsoluteFile : net/mamoe/mirai/contact/file/AbsoluteFileFolder {
|
||||
public abstract fun getExpiryTime ()J
|
||||
public abstract fun getMd5 ()[B
|
||||
public abstract fun getSha1 ()[B
|
||||
public abstract fun getSize ()J
|
||||
public fun getUrl ()Ljava/lang/String;
|
||||
public abstract fun getUrl (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun moveTo (Lnet/mamoe/mirai/contact/file/AbsoluteFolder;)Z
|
||||
public abstract fun moveTo (Lnet/mamoe/mirai/contact/file/AbsoluteFolder;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun refreshed ()Lnet/mamoe/mirai/contact/file/AbsoluteFile;
|
||||
public abstract fun refreshed (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun toMessage ()Lnet/mamoe/mirai/message/data/FileMessage;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/contact/file/AbsoluteFileFolder {
|
||||
public static final field Companion Lnet/mamoe/mirai/contact/file/AbsoluteFileFolder$Companion;
|
||||
public fun delete ()Z
|
||||
public abstract fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun exists ()Z
|
||||
public abstract fun exists (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun getAbsolutePath ()Ljava/lang/String;
|
||||
public abstract fun getContact ()Lnet/mamoe/mirai/contact/FileSupported;
|
||||
public static fun getExtension (Lnet/mamoe/mirai/contact/file/AbsoluteFileFolder;)Ljava/lang/String;
|
||||
public abstract fun getId ()Ljava/lang/String;
|
||||
public abstract fun getLastModifiedTime ()J
|
||||
public abstract fun getName ()Ljava/lang/String;
|
||||
public static fun getNameWithoutExtension (Lnet/mamoe/mirai/contact/file/AbsoluteFileFolder;)Ljava/lang/String;
|
||||
public abstract fun getParent ()Lnet/mamoe/mirai/contact/file/AbsoluteFolder;
|
||||
public abstract fun getUploadTime ()J
|
||||
public abstract fun getUploaderId ()J
|
||||
public abstract fun isFile ()Z
|
||||
public abstract fun isFolder ()Z
|
||||
public fun refresh ()Z
|
||||
public abstract fun refresh (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun refreshed ()Lnet/mamoe/mirai/contact/file/AbsoluteFileFolder;
|
||||
public abstract fun refreshed (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun renameTo (Ljava/lang/String;)Z
|
||||
public abstract fun renameTo (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/contact/file/AbsoluteFileFolder$Companion {
|
||||
public final fun getExtension (Lnet/mamoe/mirai/contact/file/AbsoluteFileFolder;)Ljava/lang/String;
|
||||
public final fun getNameWithoutExtension (Lnet/mamoe/mirai/contact/file/AbsoluteFileFolder;)Ljava/lang/String;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/contact/file/AbsoluteFolder : net/mamoe/mirai/contact/file/AbsoluteFileFolder {
|
||||
public static final field Companion Lnet/mamoe/mirai/contact/file/AbsoluteFolder$Companion;
|
||||
public static final field ROOT_FOLDER_ID Ljava/lang/String;
|
||||
public fun children ()Lkotlinx/coroutines/flow/Flow;
|
||||
public abstract fun children (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun childrenStream ()Ljava/util/stream/Stream;
|
||||
public abstract fun childrenStream (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun createFolder (Ljava/lang/String;)Lnet/mamoe/mirai/contact/file/AbsoluteFolder;
|
||||
public abstract fun createFolder (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun files ()Lkotlinx/coroutines/flow/Flow;
|
||||
public abstract fun files (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun filesStream ()Ljava/util/stream/Stream;
|
||||
public abstract fun filesStream (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun folders ()Lkotlinx/coroutines/flow/Flow;
|
||||
public abstract fun folders (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun foldersStream ()Ljava/util/stream/Stream;
|
||||
public abstract fun foldersStream (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public abstract fun getContentsCount ()I
|
||||
public fun isEmpty ()Z
|
||||
public fun refreshed ()Lnet/mamoe/mirai/contact/file/AbsoluteFolder;
|
||||
public abstract fun refreshed (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun resolveAll (Ljava/lang/String;)Lkotlinx/coroutines/flow/Flow;
|
||||
public abstract fun resolveAll (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun resolveAllStream (Ljava/lang/String;)Ljava/util/stream/Stream;
|
||||
public abstract fun resolveAllStream (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun resolveFileById (Ljava/lang/String;)Lnet/mamoe/mirai/contact/file/AbsoluteFile;
|
||||
public fun resolveFileById (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun resolveFileById (Ljava/lang/String;Z)Lnet/mamoe/mirai/contact/file/AbsoluteFile;
|
||||
public abstract fun resolveFileById (Ljava/lang/String;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public static synthetic fun resolveFileById$default (Lnet/mamoe/mirai/contact/file/AbsoluteFolder;Ljava/lang/String;ZILjava/lang/Object;)Lnet/mamoe/mirai/contact/file/AbsoluteFile;
|
||||
public static synthetic fun resolveFileById$default (Lnet/mamoe/mirai/contact/file/AbsoluteFolder;Ljava/lang/String;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
|
||||
public fun resolveFiles (Ljava/lang/String;)Lkotlinx/coroutines/flow/Flow;
|
||||
public abstract fun resolveFiles (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun resolveFilesStream (Ljava/lang/String;)Ljava/util/stream/Stream;
|
||||
public abstract fun resolveFilesStream (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun resolveFolder (Ljava/lang/String;)Lnet/mamoe/mirai/contact/file/AbsoluteFolder;
|
||||
public abstract fun resolveFolder (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/contact/file/AbsoluteFile;
|
||||
public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;)Lnet/mamoe/mirai/contact/file/AbsoluteFile;
|
||||
public abstract fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public static synthetic fun uploadNewFile$default (Lnet/mamoe/mirai/contact/file/AbsoluteFolder;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/contact/file/AbsoluteFile;
|
||||
public static synthetic fun uploadNewFile$default (Lnet/mamoe/mirai/contact/file/AbsoluteFolder;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/contact/file/AbsoluteFolder$Companion {
|
||||
public static final field ROOT_FOLDER_ID Ljava/lang/String;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/contact/file/RemoteFiles {
|
||||
public abstract fun getContact ()Lnet/mamoe/mirai/contact/FileSupported;
|
||||
public abstract fun getRoot ()Lnet/mamoe/mirai/contact/file/AbsoluteFolder;
|
||||
public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/contact/file/AbsoluteFile;
|
||||
public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;)Lnet/mamoe/mirai/contact/file/AbsoluteFile;
|
||||
public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public static synthetic fun uploadNewFile$default (Lnet/mamoe/mirai/contact/file/RemoteFiles;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/contact/file/AbsoluteFile;
|
||||
public static synthetic fun uploadNewFile$default (Lnet/mamoe/mirai/contact/file/RemoteFiles;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/contact/roaming/RoamingMessage {
|
||||
public fun getBot ()Lnet/mamoe/mirai/Bot;
|
||||
public abstract fun getContact ()Lnet/mamoe/mirai/contact/Contact;
|
||||
@ -4034,6 +4141,8 @@ public abstract interface class net/mamoe/mirai/message/data/FileMessage : net/m
|
||||
public synthetic fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey;
|
||||
public abstract fun getName ()Ljava/lang/String;
|
||||
public abstract fun getSize ()J
|
||||
public fun toAbsoluteFile (Lnet/mamoe/mirai/contact/FileSupported;)Lnet/mamoe/mirai/contact/file/AbsoluteFile;
|
||||
public abstract fun toAbsoluteFile (Lnet/mamoe/mirai/contact/FileSupported;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun toRemoteFile (Lnet/mamoe/mirai/contact/FileSupported;)Lnet/mamoe/mirai/utils/RemoteFile;
|
||||
public fun toRemoteFile (Lnet/mamoe/mirai/contact/FileSupported;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
}
|
||||
@ -5977,6 +6086,21 @@ public final class net/mamoe/mirai/utils/OverFileSizeMaxException : java/lang/Il
|
||||
public fun <init> ()V
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/utils/ProgressionCallback {
|
||||
public static final field Companion Lnet/mamoe/mirai/utils/ProgressionCallback$Companion;
|
||||
public static fun asProgressionCallback (Lkotlinx/coroutines/channels/SendChannel;Z)Lnet/mamoe/mirai/utils/ProgressionCallback;
|
||||
public fun onBegin (Ljava/lang/Object;Lnet/mamoe/mirai/utils/ExternalResource;)V
|
||||
public fun onFailure (Ljava/lang/Object;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/Throwable;)V
|
||||
public fun onFinished (Ljava/lang/Object;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/Object;)V
|
||||
public fun onProgression (Ljava/lang/Object;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/Object;)V
|
||||
public fun onSuccess (Ljava/lang/Object;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/Object;)V
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/utils/ProgressionCallback$Companion {
|
||||
public final fun asProgressionCallback (Lkotlinx/coroutines/channels/SendChannel;Z)Lnet/mamoe/mirai/utils/ProgressionCallback;
|
||||
public static synthetic fun asProgressionCallback$default (Lnet/mamoe/mirai/utils/ProgressionCallback$Companion;Lkotlinx/coroutines/channels/SendChannel;ZILjava/lang/Object;)Lnet/mamoe/mirai/utils/ProgressionCallback;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/utils/RemoteFile {
|
||||
public static final field Companion Lnet/mamoe/mirai/utils/RemoteFile$Companion;
|
||||
public static final field ROOT_PATH Ljava/lang/String;
|
||||
|
@ -10,15 +10,18 @@
|
||||
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import net.mamoe.mirai.contact.file.RemoteFiles
|
||||
import net.mamoe.mirai.utils.NotStableForInheritance
|
||||
import net.mamoe.mirai.utils.RemoteFile
|
||||
|
||||
/**
|
||||
* 支持文件操作的 [Contact]. 目前仅 [Group].
|
||||
*
|
||||
* 获取文件操作相关示例: [RemoteFile]
|
||||
* 获取文件操作相关示例: [RemoteFiles]
|
||||
*
|
||||
* @since 2.5
|
||||
*
|
||||
* @see RemoteFiles
|
||||
*/
|
||||
@NotStableForInheritance
|
||||
public interface FileSupported : Contact {
|
||||
@ -27,5 +30,14 @@ public interface FileSupported : Contact {
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
@Deprecated("Please use files instead.", replaceWith = ReplaceWith("files.root")) // deprecated since 2.8.0-RC
|
||||
public val filesRoot: RemoteFile
|
||||
|
||||
/**
|
||||
* 获取远程文件列表 (管理器).
|
||||
*
|
||||
* @since 2.8
|
||||
*/
|
||||
public val files: RemoteFiles
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
@file:JvmBlockingBridge
|
||||
@file:Suppress("OVERLOADS_INTERFACE")
|
||||
|
||||
package net.mamoe.mirai.contact.file
|
||||
|
||||
import kotlinx.io.errors.IOException
|
||||
import net.mamoe.kjbb.JvmBlockingBridge
|
||||
import net.mamoe.mirai.contact.PermissionDeniedException
|
||||
import net.mamoe.mirai.message.data.FileMessage
|
||||
import net.mamoe.mirai.utils.NotStableForInheritance
|
||||
|
||||
/**
|
||||
* 绝对文件标识. 精确表示一个远程文件. 不会受同名文件或目录的影响.
|
||||
*
|
||||
* @since 2.8
|
||||
* @see RemoteFiles
|
||||
* @see AbsoluteFolder
|
||||
* @see AbsoluteFileFolder
|
||||
*/
|
||||
@NotStableForInheritance
|
||||
public interface AbsoluteFile : AbsoluteFileFolder {
|
||||
/**
|
||||
* 文件到期时间戳, 单位秒.
|
||||
*/
|
||||
public val expiryTime: Long
|
||||
|
||||
/**
|
||||
* 文件大小 (占用空间), 单位 byte.
|
||||
*/
|
||||
public val size: Long
|
||||
|
||||
/**
|
||||
* 文件内容 SHA-1.
|
||||
*/
|
||||
public val sha1: ByteArray
|
||||
|
||||
/**
|
||||
* 文件内容 MD5.
|
||||
*/
|
||||
public val md5: ByteArray
|
||||
|
||||
/**
|
||||
* 移动远程文件到 [folder] 目录下. 成功时返回 `true`, 当远程文件不存在时返回 `false`.
|
||||
*
|
||||
* 注意该操作有可能产生同名文件或目录 (当 [folder] 中已经存在一个名称为 [name] 的文件或目录时).
|
||||
*
|
||||
* @throws IOException 当发生网络错误时可能抛出
|
||||
* @throws IllegalStateException 当发生已知的协议错误时抛出
|
||||
* @throws PermissionDeniedException 当无管理员权限时抛出 (若群仅允许管理员上传)
|
||||
*/
|
||||
public suspend fun moveTo(folder: AbsoluteFolder): Boolean
|
||||
|
||||
/**
|
||||
* 获得下载链接 URL 字符串. 当远程文件不存在时返回 `null`.
|
||||
*/
|
||||
public suspend fun getUrl(): String?
|
||||
|
||||
/**
|
||||
* 得到表示远程文件的可以发送的 [FileMessage].
|
||||
*
|
||||
* 在 [上传文件][RemoteFiles.uploadNewFile] 时就已经发送了文件消息. [toMessage] 可供之后再次发送使用.
|
||||
*/
|
||||
public fun toMessage(): FileMessage
|
||||
|
||||
/**
|
||||
* 返回更新了文件或目录信息 ([lastModifiedTime] 等) 的, 指向相同文件的 [AbsoluteFileFolder].
|
||||
* 不会更新当前 [AbsoluteFileFolder] 对象.
|
||||
*
|
||||
* 当远程文件或目录不存在时返回 `null`.
|
||||
*
|
||||
* 该函数会遍历上级目录的所有文件并匹配当前文件, 因此可能会非常慢, 请不要频繁使用.
|
||||
*/
|
||||
override suspend fun refreshed(): AbsoluteFile?
|
||||
}
|
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
@file:JvmBlockingBridge
|
||||
@file:Suppress("OVERLOADS_INTERFACE")
|
||||
|
||||
package net.mamoe.mirai.contact.file
|
||||
|
||||
import kotlinx.io.errors.IOException
|
||||
import net.mamoe.kjbb.JvmBlockingBridge
|
||||
import net.mamoe.mirai.contact.FileSupported
|
||||
import net.mamoe.mirai.contact.PermissionDeniedException
|
||||
import net.mamoe.mirai.utils.NotStableForInheritance
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* 绝对文件或目录标识. 精确表示一个远程文件. 不会受同名文件或目录的影响.
|
||||
*
|
||||
* @since 2.8
|
||||
* @see RemoteFiles
|
||||
* @see AbsoluteFile
|
||||
* @see AbsoluteFolder
|
||||
*/
|
||||
@NotStableForInheritance
|
||||
public sealed interface AbsoluteFileFolder {
|
||||
/**
|
||||
* 该对象所属 [FileSupported]
|
||||
*/
|
||||
public val contact: FileSupported
|
||||
|
||||
/**
|
||||
* 上级 [AbsoluteFileFolder].
|
||||
*
|
||||
* - 当该 [AbsoluteFileFolder] 表示一个目录中的文件时返回文件所属目录的 [AbsoluteFolder].
|
||||
* - 当该 [AbsoluteFileFolder] 表示子目录时返回父目录的 [AbsoluteFolder].
|
||||
*
|
||||
* 特别地,
|
||||
* - 当该 [AbsoluteFileFolder] 表示根目录下的一个文件时返回根目录的 [AbsoluteFolder].
|
||||
* - 当该 [AbsoluteFileFolder] 表示根目录时返回 `null` (表示无上级).
|
||||
*
|
||||
* 也就是说, 若 [AbsoluteFileFolder.parent] 为 `null`, 那么该 [AbsoluteFileFolder] 就表示根目录.
|
||||
*/
|
||||
public val parent: AbsoluteFolder?
|
||||
|
||||
/**
|
||||
* 文件或目录的 ID, 即 `fileId` 或 `folderId`. 该属性由服务器维护, 通常唯一且持久.
|
||||
*/
|
||||
public val id: String
|
||||
|
||||
/**
|
||||
* 文件名或目录名.
|
||||
*
|
||||
* 注意, 当远程文件或目录被 (其他人) 改名时, [name] 不会变动.
|
||||
* 只有在调用 [renameTo] 和 [refresh] 时才会更新.
|
||||
*
|
||||
* 不会包含 `:*?"<>|/\` 任一字符.
|
||||
*/
|
||||
public val name: String
|
||||
|
||||
/**
|
||||
* 绝对路径, 如 `/foo/bar.txt`.
|
||||
*
|
||||
* 注意, 当远程文件或目录被 (其他人) 移动到其他位置或其父目录名称改名时, [absolutePath] 不会变动.
|
||||
* 只有在调用 [renameTo] 和 [refresh] 等时才会更新.
|
||||
*/
|
||||
public val absolutePath: String
|
||||
|
||||
/**
|
||||
* 表示远程文件时返回 `true`.
|
||||
*/
|
||||
public val isFile: Boolean
|
||||
|
||||
/**
|
||||
* 表示远程目录时返回 `true`.
|
||||
*/
|
||||
public val isFolder: Boolean
|
||||
|
||||
/**
|
||||
* 远程文件或目录的创建时间, 时间戳秒.
|
||||
*/
|
||||
public val uploadTime: Long
|
||||
|
||||
/**
|
||||
* 远程文件或目录的最后修改时间戳, 单位秒.
|
||||
*
|
||||
* 注意, 当远程文件或目录被 (其他人) 改动时, [lastModifiedTime] 不会变动.
|
||||
* 只有在调用 [renameTo] 和 [refresh] 等时才会更新.
|
||||
*/
|
||||
public val lastModifiedTime: Long
|
||||
|
||||
/**
|
||||
* 上传者 ID.
|
||||
*/
|
||||
public val uploaderId: Long
|
||||
|
||||
|
||||
/**
|
||||
* 查询该远程文件或目录是否还存在于服务器.
|
||||
*
|
||||
* 只会精确地按 [id] 检查, 而不会考虑同名文件或目录. 当文件或目录存在时返回 `true`.
|
||||
*
|
||||
* 该操作不会更新 [absolutePath] 等属性.
|
||||
*/
|
||||
public suspend fun exists(): Boolean
|
||||
|
||||
/**
|
||||
* 重命名远程文件或目录, **并且**修改当前(`this`) [AbsoluteFileFolder] 的 [name].
|
||||
* 成功时返回 `true`, 当远程文件或目录不存在时返回 `false`.
|
||||
*
|
||||
* 注意该操作有可能产生同名文件或目录 (当服务器已经存在一个名称为 [newName] 的文件或目录时).
|
||||
*
|
||||
* @throws IOException 当发生网络错误时可能抛出
|
||||
* @throws IllegalStateException 当发生已知的协议错误时抛出
|
||||
* @throws PermissionDeniedException 当无管理员权限时抛出 (若群仅允许管理员上传)
|
||||
*/
|
||||
public suspend fun renameTo(newName: String): Boolean
|
||||
|
||||
/**
|
||||
* 删除远程文件或目录. 只会根据 [id] 精确地删除一个文件或目录, 不会删除其他同名文件或目录.
|
||||
* 成功时返回 `true`, 当远程文件或目录不存在时返回 `false`.
|
||||
*
|
||||
* 若目录非空, 则会删除目录中的所有文件. 操作目录或非 Bot 自己上传的文件时需要管理员权限, 无管理员权限时抛出异常.
|
||||
*
|
||||
* @throws IOException 当发生网络错误时可能抛出
|
||||
* @throws IllegalStateException 当发生已知的协议错误时抛出
|
||||
* @throws PermissionDeniedException 当无管理员权限时抛出
|
||||
*/
|
||||
public suspend fun delete(): Boolean
|
||||
|
||||
/**
|
||||
* 更新当前 [AbsoluteFileFolder] 对象的文件或目录信息 ([lastModifiedTime], [absolutePath] 等).
|
||||
* 成功时返回 `true`, 当远程文件或目录不存在时返回 `false`.
|
||||
*/
|
||||
public suspend fun refresh(): Boolean
|
||||
|
||||
/**
|
||||
* 返回更新了文件或目录信息 ([lastModifiedTime] 等) 的, 指向相同文件的 [AbsoluteFileFolder].
|
||||
* 不会更新当前 [AbsoluteFileFolder] 对象.
|
||||
*
|
||||
* 当远程文件或目录不存在时返回 `null`.
|
||||
*
|
||||
* 该函数会遍历上级目录的所有文件并匹配当前文件, 因此可能会非常慢, 请不要频繁使用.
|
||||
*/
|
||||
public suspend fun refreshed(): AbsoluteFileFolder?
|
||||
|
||||
public override fun toString(): String
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
* 返回去掉文件后缀的文件名. 如 `foo.txt` 返回 `foo`.
|
||||
*
|
||||
* 注意, 当远程文件或目录被 (其他人) 改名时, [nameWithoutExtension] 不会变动.
|
||||
* 只有在调用 [renameTo] 和 [refresh] 时才会更新.
|
||||
*
|
||||
* 不会包含 `:*?"<>|/\` 任一字符.
|
||||
*
|
||||
* @see File.nameWithoutExtension
|
||||
*/
|
||||
@get:JvmStatic
|
||||
public val AbsoluteFileFolder.nameWithoutExtension: String
|
||||
get() = name.substringBeforeLast('.')
|
||||
|
||||
/**
|
||||
* 返回文件的后缀名. 如 `foo.txt` 返回 `txt`.
|
||||
*
|
||||
* 注意, 当远程文件或目录被 (其他人) 改名时, [extension] 不会变动.
|
||||
* 只有在调用 [renameTo] 和 [refresh] 时才会更新.
|
||||
*
|
||||
* 不会包含 `:*?"<>|/\` 任一字符.
|
||||
*
|
||||
* @see File.extension
|
||||
*/
|
||||
@get:JvmStatic
|
||||
public val AbsoluteFileFolder.extension: String
|
||||
get() = name.substringAfterLast('.', "")
|
||||
}
|
||||
}
|
@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
@file:JvmBlockingBridge
|
||||
@file:Suppress("OVERLOADS_INTERFACE")
|
||||
|
||||
package net.mamoe.mirai.contact.file
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import net.mamoe.kjbb.JvmBlockingBridge
|
||||
import net.mamoe.mirai.contact.PermissionDeniedException
|
||||
import net.mamoe.mirai.utils.ExternalResource
|
||||
import net.mamoe.mirai.utils.JavaFriendlyAPI
|
||||
import net.mamoe.mirai.utils.NotStableForInheritance
|
||||
import net.mamoe.mirai.utils.ProgressionCallback
|
||||
import java.util.stream.Stream
|
||||
|
||||
/**
|
||||
* 绝对目录标识. 精确表示一个远程目录. 不会受同名文件或目录的影响.
|
||||
*
|
||||
* @since 2.8
|
||||
* @see RemoteFiles
|
||||
* @see AbsoluteFile
|
||||
* @see AbsoluteFileFolder
|
||||
*/
|
||||
@NotStableForInheritance
|
||||
public interface AbsoluteFolder : AbsoluteFileFolder {
|
||||
/**
|
||||
* 当前快照中文件数量, 当有文件更新时(上传/删除文件) 该属性不会更新.
|
||||
*
|
||||
* 只可能通过 [refresh] 手动刷新
|
||||
*
|
||||
* 特别的, 若该目录表示根目录, [contentsCount] 返回 `0`. (无法快速获取)
|
||||
*/
|
||||
public val contentsCount: Int
|
||||
|
||||
/**
|
||||
* 当该目录为空时返回 `true`.
|
||||
*/
|
||||
public fun isEmpty(): Boolean = contentsCount == 0
|
||||
|
||||
/**
|
||||
* 返回更新了文件或目录信息 ([lastModifiedTime] 等) 的, 指向相同文件的 [AbsoluteFileFolder].
|
||||
* 不会更新当前 [AbsoluteFileFolder] 对象.
|
||||
*
|
||||
* 当远程文件或目录不存在时返回 `null`.
|
||||
*
|
||||
* 该函数会遍历上级目录的所有文件并匹配当前文件, 因此可能会非常慢, 请不要频繁使用.
|
||||
*/
|
||||
override suspend fun refreshed(): AbsoluteFolder?
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// list children
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* 获取该目录下所有子目录列表.
|
||||
*/
|
||||
public suspend fun folders(): Flow<AbsoluteFolder>
|
||||
|
||||
/**
|
||||
* 获取该目录下所有子目录列表.
|
||||
*
|
||||
* 实现细节: 为了适合 Java 调用, 实现类似为阻塞式的 [folders], 因此不建议在 Kotlin 使用. 在 Kotlin 请使用 [folders].
|
||||
*/
|
||||
@JavaFriendlyAPI
|
||||
public suspend fun foldersStream(): Stream<AbsoluteFolder>
|
||||
|
||||
|
||||
/**
|
||||
* 获取该目录下所有文件列表.
|
||||
*/
|
||||
public suspend fun files(): Flow<AbsoluteFile>
|
||||
|
||||
/**
|
||||
* 获取该目录下所有文件列表.
|
||||
*
|
||||
* 实现细节: 为了适合 Java 调用, 实现类似为阻塞式的 [files], 因此不建议在 Kotlin 使用. 在 Kotlin 请使用 [files].
|
||||
*/
|
||||
@JavaFriendlyAPI
|
||||
public suspend fun filesStream(): Stream<AbsoluteFile>
|
||||
|
||||
|
||||
/**
|
||||
* 获取该目录下所有文件和子目录列表.
|
||||
*/
|
||||
public suspend fun children(): Flow<AbsoluteFileFolder>
|
||||
|
||||
/**
|
||||
* 获取该目录下所有文件和子目录列表.
|
||||
*
|
||||
* 实现细节: 为了适合 Java 调用, 实现类似为阻塞式的 [children], 因此不建议在 Kotlin 使用. 在 Kotlin 请使用 [children].
|
||||
*/
|
||||
@JavaFriendlyAPI
|
||||
public suspend fun childrenStream(): Stream<AbsoluteFileFolder>
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// resolve and upload
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* 创建一个名称为 [name] 的子目录. 返回成功创建的或已有的子目录. 当目标目录已经存在时则直接返回该目录.
|
||||
*
|
||||
* @throws IllegalArgumentException 当 [name] 为空或包含非法字符 (`:*?"<>|`) 时抛出
|
||||
* @throws PermissionDeniedException 当权限不足时抛出
|
||||
*/
|
||||
public suspend fun createFolder(name: String): AbsoluteFolder
|
||||
|
||||
/**
|
||||
* 获取一个已存在的名称为 [name] 的子目录. 当该名称的子目录不存在时返回 `null`.
|
||||
*
|
||||
* @throws IllegalArgumentException 当 [name] 为空或包含非法字符 (`:*?"<>|`) 时抛出
|
||||
*/
|
||||
public suspend fun resolveFolder(name: String): AbsoluteFolder?
|
||||
|
||||
/**
|
||||
* 精确获取 [AbsoluteFile.id] 为 [id] 的文件. 在目标文件不存在时返回 `null`. 当 [deep] 为 `true` 时还会深入子目录查找.
|
||||
*/
|
||||
@JvmOverloads
|
||||
public suspend fun resolveFileById(
|
||||
id: String,
|
||||
deep: Boolean = false
|
||||
): AbsoluteFile?
|
||||
|
||||
/**
|
||||
* 根据路径获取指向的所有路径为 [path] 的文件列表. 同时支持相对路径和绝对路径. 支持获取子目录内的文件.
|
||||
*/
|
||||
public suspend fun resolveFiles(
|
||||
path: String
|
||||
): Flow<AbsoluteFile>
|
||||
|
||||
/**
|
||||
* 根据路径获取指向的所有路径为 [path] 的文件列表. 同时支持相对路径和绝对路径. 支持获取子目录内的文件.
|
||||
*
|
||||
* 实现细节: 为了适合 Java 调用, 实现类似为阻塞式的 [resolveFiles], 因此不建议在 Kotlin 使用. 在 Kotlin 请使用 [resolveFiles].
|
||||
*/
|
||||
@JavaFriendlyAPI
|
||||
public suspend fun resolveFilesStream(
|
||||
path: String
|
||||
): Stream<AbsoluteFile>
|
||||
|
||||
/**
|
||||
* 根据路径获取指向的所有路径为 [path] 的文件和目录列表. 同时支持相对路径和绝对路径. 支持获取子目录内的文件和目录.
|
||||
*/
|
||||
public suspend fun resolveAll(
|
||||
path: String
|
||||
): Flow<AbsoluteFileFolder>
|
||||
|
||||
/**
|
||||
* 根据路径获取指向的所有路径为 [path] 的文件和目录列表. 同时支持相对路径和绝对路径. 支持获取子目录内的文件和目录.
|
||||
*
|
||||
* 实现细节: 为了适合 Java 调用, 实现类似为阻塞式的 [resolveAll], 因此不建议在 Kotlin 使用. 在 Kotlin 请使用 [resolveAll].
|
||||
*/
|
||||
@JavaFriendlyAPI
|
||||
public suspend fun resolveAllStream(
|
||||
path: String
|
||||
): Stream<AbsoluteFileFolder>
|
||||
|
||||
/**
|
||||
* 上传一个文件到该目录, 返回上传成功的文件标识.
|
||||
*
|
||||
* 会在必要时尝试创建远程目录.
|
||||
*
|
||||
* ### [filepath]
|
||||
*
|
||||
* - 可以是 `foo.txt` 表示该目录下的文件 "foo.txt"
|
||||
* - 也可以是 `sub/foo.txt` 表示该目录的子目录 "sub" 下的文件 "foo.txt".
|
||||
* - 或是绝对路径 `/sub/foo.txt` 表示根目录的 "sub" 目录下的文件 "foo.txt"
|
||||
*
|
||||
* @param filepath 目标文件名
|
||||
* @param content 文件内容
|
||||
* @param callback 下载进度回调, 传递的 `progression` 是已下载字节数.
|
||||
*
|
||||
* @throws PermissionDeniedException 当无管理员权限时抛出 (若群仅允许管理员上传)
|
||||
*/
|
||||
@JvmOverloads
|
||||
public suspend fun uploadNewFile(
|
||||
filepath: String,
|
||||
content: ExternalResource,
|
||||
callback: ProgressionCallback<AbsoluteFile, Long>? = null,
|
||||
): AbsoluteFile
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
* 根目录 folder ID.
|
||||
* @see id
|
||||
*/
|
||||
public const val ROOT_FOLDER_ID: String = "/"
|
||||
}
|
||||
}
|
141
mirai-core-api/src/commonMain/kotlin/contact/file/RemoteFiles.kt
Normal file
141
mirai-core-api/src/commonMain/kotlin/contact/file/RemoteFiles.kt
Normal file
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
@file:JvmBlockingBridge
|
||||
@file:Suppress("OVERLOADS_INTERFACE")
|
||||
|
||||
package net.mamoe.mirai.contact.file
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import net.mamoe.kjbb.JvmBlockingBridge
|
||||
import net.mamoe.mirai.contact.FileSupported
|
||||
import net.mamoe.mirai.contact.PermissionDeniedException
|
||||
import net.mamoe.mirai.utils.ExternalResource
|
||||
import net.mamoe.mirai.utils.NotStableForInheritance
|
||||
import net.mamoe.mirai.utils.ProgressionCallback
|
||||
import java.io.File
|
||||
import java.util.stream.Stream
|
||||
|
||||
/**
|
||||
* 表示远程文件列表 (管理器).
|
||||
*
|
||||
* [RemoteFiles] 包含一些协议接口,
|
||||
*
|
||||
* # 文件和目录操作
|
||||
*
|
||||
* 文件和目录的父类型是 [AbsoluteFileFolder].
|
||||
*
|
||||
* - [AbsoluteFile] 表示一个文件
|
||||
* - [AbsoluteFolder] 表示一个目录
|
||||
*
|
||||
* 每个文件或目录都拥有一个唯一 ID: [AbsoluteFileFolder.id]. 该 ID 由服务器提供, 在重命名或移动时不会变化.
|
||||
*
|
||||
* 文件名可以通过 [AbsoluteFileFolder.name] 获得, 但注意文件名和其他属性都会随重命名或移动等操作更新.
|
||||
*
|
||||
* 除根目录 [root] 外, 每个文件或目录都拥有父目录 [AbsoluteFileFolder.parent].
|
||||
*
|
||||
* # 根目录
|
||||
*
|
||||
* 除了 [RemoteFiles] 中定义的捷径外, 一切文件目录操作都以获取根目录开始. 可通过 [RemoteFiles.root] 获取表示根目录的 [AbsoluteFolder].
|
||||
*
|
||||
* # 绝对路径与相对路径
|
||||
*
|
||||
* mirai 文件系统的绝对路径与相对路径与 Java [File] 实现的相同.
|
||||
*
|
||||
* 以 `/` 起始的路径表示绝对路径, 基于根目录 [root] 处理. 其他路径均表示相对路径.
|
||||
*
|
||||
* 可由 [AbsoluteFileFolder.absolutePath] 获取其绝对路径. 值得注意的是, 所有文件与目录对象都表示绝对路径下的目标, 因此它们都总是精确地表示一个目标, 而不受环境影响.
|
||||
*
|
||||
* 除重命名外, 所有文件和目录操作都默认同时支持上述两种路径.
|
||||
*
|
||||
* # 操作 [AbsoluteFileFolder]
|
||||
*
|
||||
* ## 重命名, 移动
|
||||
*
|
||||
* [AbsoluteFileFolder.renameTo], [AbsoluteFile.moveTo] 提供重命名和移动功能. 注意目录不支持移动.
|
||||
*
|
||||
* ## 获取目录中的子目录和文件列表
|
||||
*
|
||||
* 一个目录 ([AbsoluteFolder]) 可以包含多个子文件, 根目录还可以包含多个子目录 (详见下文 '目录结构限制').
|
||||
*
|
||||
* 使用 [AbsoluteFolder.children] 可以获得其内子目录和文件列表 [Flow]. [AbsoluteFolder.childrenStream] 提供适合 Java 的 [Stream] 实现.
|
||||
* 使用 [AbsoluteFolder.folders] 或 [AbsoluteFolder.files] 可以特定地只获取子目录或文件列表. 这些函数也有其 `*Stream` 实现.
|
||||
*
|
||||
* 若要根据确定的文件或目录名称获取其 [AbsoluteFileFolder] 实例, 可使用 [AbsoluteFolder.resolveFiles] 或 [AbsoluteFolder.resolveFiles].
|
||||
* 注意 [AbsoluteFolder.resolveFiles] 返回 [Flow] (其 Stream 版返回 [Stream]), 因为服务器允许多个文件有相同名称. (详见下文 '允许重名').
|
||||
*
|
||||
* 若已知文件 [AbsoluteFile.id], 可通过 [AbsoluteFolder.resolveFileById] 获得该文件.
|
||||
*
|
||||
* ## 上传新文件
|
||||
* 可使用 [AbsoluteFolder.uploadNewFile] 上传新文件. 也可以通过 [RemoteFiles.uploadNewFile] 直接上传而跳过获取目录的步骤 (因为目录不允许同名).
|
||||
*
|
||||
* ## 覆盖一个旧文件
|
||||
* 服务器不允许覆盖文件. 只能通过 [AbsoluteFile.delete] 删除文件后再上传新文件. 注意新旧文件的 [AbsoluteFile.id] 会不同.
|
||||
*
|
||||
* # 操作权限
|
||||
* 操作一个目录时总是需要管理员权限. 若群设置 "允许任何人上传文件", 则上传文件和操作自己上传的文件时都不需要特殊权限. 注意, 操作他人的文件时总是需要管理员权限.
|
||||
*
|
||||
* # 服务器限制
|
||||
*
|
||||
* ## 目录结构限制
|
||||
*
|
||||
* 在 mirai 2.8.0 发布时, 服务器仅允许两层目录结构. 也就是说只允许根目录存在子目录, 子目录不能包含另一个子目录.
|
||||
*
|
||||
* 为了考虑将来服务器可能升级, mirai 没有做实现上的限制. mirai 所有操作都支持多层目录, 但进行这样的操作时将会得到服务器错误, 方法会抛出 [IllegalStateException].
|
||||
*
|
||||
* ## 允许重名
|
||||
*
|
||||
* 服务器允许同名目录和文件存在. 如下同名的三个文件与一个目录是允许的, 但它们的 [AbsoluteFileFolder.id] 都互不相同:
|
||||
* ```
|
||||
* foo
|
||||
* |- test (目录)
|
||||
* |- test (文件)
|
||||
* |- test (文件)
|
||||
* |- test (文件)
|
||||
* ```
|
||||
* 注意, 目录不允许同名.
|
||||
*
|
||||
* [AbsoluteFileFolder] 依据 [AbsoluteFileFolder.id] 定位文件, 而不是通过文件名. 因此 [AbsoluteFileFolder] 总是精确地代表一个文件或目录.
|
||||
*
|
||||
* @since 2.8
|
||||
* @see FileSupported
|
||||
*/
|
||||
@NotStableForInheritance
|
||||
public interface RemoteFiles {
|
||||
/**
|
||||
* 获取表示根目录的 [AbsoluteFolder]
|
||||
*/
|
||||
public val root: AbsoluteFolder
|
||||
|
||||
/**
|
||||
* 该对象所属 [FileSupported]
|
||||
*/
|
||||
public val contact: FileSupported
|
||||
|
||||
/**
|
||||
* 上传一个文件到指定精确路径. 返回指代该远程文件的 [AbsoluteFile].
|
||||
*
|
||||
* 会在必要时尝试创建远程目录.
|
||||
*
|
||||
* 也可以使用 [AbsoluteFolder.uploadNewFile].
|
||||
*
|
||||
* @param filepath 文件路径, **包含目标文件名**. 如 `/foo/bar.txt`. 若是相对目录则基于 [根目录][root] 处理.
|
||||
* @param content 文件内容
|
||||
* @param callback 下载进度回调, 传递的 `progression` 是已下载字节数.
|
||||
*
|
||||
* @throws PermissionDeniedException 当无管理员权限时抛出 (若群仅允许管理员上传)
|
||||
*/
|
||||
@JvmOverloads
|
||||
public suspend fun uploadNewFile(
|
||||
filepath: String,
|
||||
content: ExternalResource,
|
||||
callback: ProgressionCallback<AbsoluteFile, Long>? = null,
|
||||
): AbsoluteFile = root.uploadNewFile(filepath, content, callback)
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import kotlinx.serialization.Serializable
|
||||
import net.mamoe.kjbb.JvmBlockingBridge
|
||||
import net.mamoe.mirai.Mirai
|
||||
import net.mamoe.mirai.contact.FileSupported
|
||||
import net.mamoe.mirai.contact.file.AbsoluteFile
|
||||
import net.mamoe.mirai.event.events.MessageEvent
|
||||
import net.mamoe.mirai.message.code.CodableMessage
|
||||
import net.mamoe.mirai.message.code.internal.appendStringAsMiraiCode
|
||||
@ -40,6 +41,7 @@ import net.mamoe.mirai.utils.*
|
||||
@Serializable(FileMessage.Serializer::class)
|
||||
@SerialName(FileMessage.SERIAL_NAME)
|
||||
@NotStableForInheritance
|
||||
@JvmBlockingBridge
|
||||
public interface FileMessage : MessageContent, ConstrainSingle, CodableMessage {
|
||||
/**
|
||||
* 服务器需要的某种 ID.
|
||||
@ -74,11 +76,20 @@ public interface FileMessage : MessageContent, ConstrainSingle, CodableMessage {
|
||||
/**
|
||||
* 获取一个对应的 [RemoteFile]. 当目标群或好友不存在这个文件时返回 `null`.
|
||||
*/
|
||||
@JvmBlockingBridge
|
||||
@Suppress("DEPRECATION")
|
||||
@Deprecated("Please use toAbsoluteFile", ReplaceWith("this.toAbsoluteFile(contact)")) // deprecated since 2.8.0-RC
|
||||
public suspend fun toRemoteFile(contact: FileSupported): RemoteFile? {
|
||||
@Suppress("DEPRECATION")
|
||||
return contact.filesRoot.resolveById(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个对应的 [RemoteFile]. 当目标群或好友不存在这个文件时返回 `null`.
|
||||
*
|
||||
* @since 2.8
|
||||
*/
|
||||
public suspend fun toAbsoluteFile(contact: FileSupported): AbsoluteFile?
|
||||
|
||||
override val key: Key get() = Key
|
||||
|
||||
/**
|
||||
|
@ -437,6 +437,11 @@ public interface ExternalResource : Closeable {
|
||||
* @see RemoteFile.path
|
||||
* @see RemoteFile.uploadAndSend
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
@Deprecated(
|
||||
"Deprecated. Please use AbsoluteFolder.uploadNewFile",
|
||||
ReplaceWith("contact.files.uploadNewFile(path, this, callback)")
|
||||
) // deprecated since 2.8.0-RC
|
||||
@JvmStatic
|
||||
@JvmBlockingBridge
|
||||
@JvmOverloads
|
||||
@ -456,6 +461,11 @@ public interface ExternalResource : Closeable {
|
||||
* @see RemoteFile.path
|
||||
* @see RemoteFile.uploadAndSend
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
@Deprecated(
|
||||
"Deprecated. Please use AbsoluteFolder.uploadNewFile",
|
||||
ReplaceWith("contact.files.uploadNewFile(path, this, callback)")
|
||||
) // deprecated since 2.8.0-RC
|
||||
@JvmStatic
|
||||
@JvmBlockingBridge
|
||||
@JvmName("sendAsFile")
|
||||
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import kotlinx.coroutines.channels.SendChannel
|
||||
import net.mamoe.mirai.contact.file.AbsoluteFile
|
||||
import net.mamoe.mirai.utils.ProgressionCallback.Companion.asProgressionCallback
|
||||
|
||||
|
||||
/**
|
||||
* 操作进度回调, 可供前端使用, 以提供进度显示.
|
||||
*
|
||||
* @param S subject, 操作对象, 如 [AbsoluteFile]
|
||||
* @param P progression, 用于提示进度. 如当下载文件时为已下载文件大小字节数 [Long].
|
||||
*
|
||||
* @see asProgressionCallback
|
||||
*
|
||||
* @since 2.8
|
||||
*/
|
||||
public interface ProgressionCallback<in S, in P> {
|
||||
/**
|
||||
* 当操作开始时调用
|
||||
*/
|
||||
public fun onBegin(subject: S, resource: ExternalResource) {}
|
||||
|
||||
/**
|
||||
* 每当有进度更新时调用. 此方法可能会同时被多个线程调用.
|
||||
*/
|
||||
public fun onProgression(subject: S, resource: ExternalResource, progression: P) {}
|
||||
|
||||
/**
|
||||
* 当操作成功时调用.
|
||||
*
|
||||
* 在默认实现下只会由 [onFinished] 调用
|
||||
*/
|
||||
public fun onSuccess(subject: S, resource: ExternalResource, progression: P) {}
|
||||
|
||||
/**
|
||||
* 当操作以异常失败时调用.
|
||||
*
|
||||
* 在默认实现下只会由 [onFinished] 调用
|
||||
*/
|
||||
public fun onFailure(subject: S, resource: ExternalResource, exception: Throwable) {}
|
||||
|
||||
/**
|
||||
* 当操作完成时调用.
|
||||
*/
|
||||
public fun onFinished(subject: S, resource: ExternalResource, result: Result<P>) {
|
||||
result.fold(
|
||||
onSuccess = { onSuccess(subject, resource, it) },
|
||||
onFailure = { onFailure(subject, resource, it) },
|
||||
)
|
||||
}
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
* 将一个 [SendChannel] 作为 [ProgressionCallback] 使用.
|
||||
*
|
||||
* ## 下载文件的使用示例
|
||||
*
|
||||
* 每当有进度更新, 已下载的字节数都会被[发送][SendChannel.offer]到 [SendChannel] 中.
|
||||
* 进度的发送会通过 [offer][SendChannel.offer], 而不是通过 [send][SendChannel.send]. 意味着 [SendChannel] 通常要实现缓存.
|
||||
*
|
||||
* 若 [closeOnFinish] 为 `true`, 当下载完成 (无论是失败还是成功) 时会 [关闭][SendChannel.close] [SendChannel].
|
||||
*
|
||||
* 使用示例:
|
||||
* ```
|
||||
* val progress = Channel<Long>(Channel.BUFFERED)
|
||||
*
|
||||
* launch {
|
||||
* // 每 3 秒发送一次操作进度百分比
|
||||
* progress.receiveAsFlow().sample(Duration.seconds(3)).collect { bytes ->
|
||||
* group.sendMessage("File upload: ${(bytes.toDouble() / resource.size * 100).toInt() / 100}%.") // 保留 2 位小数
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* group.files.uploadNewFile("/foo.txt", resource, callback = progress.asProgressionCallback(true))
|
||||
* group.sendMessage("File uploaded successfully.")
|
||||
* ```
|
||||
*
|
||||
* 直接使用 [ProgressionCallback] 也可以实现示例这样的功能, [asProgressionCallback] 是为了简化操作.
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun <S, P> SendChannel<P>.asProgressionCallback(closeOnFinish: Boolean = true): ProgressionCallback<S, P> {
|
||||
return object : ProgressionCallback<S, P> {
|
||||
override fun onProgression(subject: S, resource: ExternalResource, progression: P) {
|
||||
trySend(progression)
|
||||
}
|
||||
|
||||
override fun onFinished(subject: S, resource: ExternalResource, result: Result<P>) {
|
||||
if (closeOnFinish) this@asProgressionCallback.close(result.exceptionOrNull())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("unused")
|
||||
@file:Suppress("unused", "DEPRECATION")
|
||||
@file:JvmBlockingBridge
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
@ -97,6 +97,7 @@ import java.io.File
|
||||
* @see FileSupported
|
||||
* @since 2.5
|
||||
*/
|
||||
@Deprecated("Please use RemoteFiles and AbsoluteFileFolder form fileSupported.files") // deprecated since 2.8.0-RC
|
||||
@NotStableForInheritance
|
||||
public interface RemoteFile {
|
||||
/**
|
||||
@ -348,6 +349,10 @@ public interface RemoteFile {
|
||||
* 上传进度回调, 可供前端使用, 以提供进度显示.
|
||||
* @see asProgressionCallback
|
||||
*/
|
||||
@Deprecated(
|
||||
"Deprecated without replacement. Please use AbsoluteFolder.uploadNewFile",
|
||||
ReplaceWith("contact.files.uploadNewFile(path, this, callback)")
|
||||
) // deprecated since 2.8.0-RC
|
||||
public interface ProgressionCallback {
|
||||
/**
|
||||
* 当上传开始时调用
|
||||
@ -610,6 +615,11 @@ public interface RemoteFile {
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
@Deprecated(
|
||||
"Deprecated. Please use AbsoluteFolder.uploadNewFile or RemoteFiles.uploadNewFile",
|
||||
ReplaceWith("this.files.uploadNewFile(path, resource, callback)"),
|
||||
level = DeprecationLevel.WARNING
|
||||
) // deprecated since 2.8.0-RC
|
||||
public suspend fun <C : FileSupported> C.sendFile(
|
||||
path: String,
|
||||
resource: ExternalResource,
|
||||
@ -624,6 +634,11 @@ public interface RemoteFile {
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
@Deprecated(
|
||||
"Deprecated. Please use AbsoluteFolder.uploadNewFile or RemoteFiles.uploadNewFile",
|
||||
ReplaceWith("file.toExternalResource().use { this.files.uploadNewFile(path, it, callback) }"),
|
||||
level = DeprecationLevel.WARNING
|
||||
) // deprecated since 2.8.0-RC
|
||||
public suspend fun <C : FileSupported> C.sendFile(
|
||||
path: String,
|
||||
file: File,
|
||||
|
@ -150,4 +150,13 @@ public inline fun <R> Result<R>.mapFailure(
|
||||
block: (Throwable) -> Throwable,
|
||||
): Result<R> = onFailure {
|
||||
return Result.failure(block(it))
|
||||
}
|
||||
}
|
||||
|
||||
public inline fun <R> Result<R>.onSuccessCatching(block: () -> Unit): Result<R> {
|
||||
if (isSuccess) {
|
||||
runCatching(block).onFailure {
|
||||
return@onSuccessCatching Result.failure(it)
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
20
mirai-core/src/commonMain/kotlin/contact/ContactAware.kt
Normal file
20
mirai-core/src/commonMain/kotlin/contact/ContactAware.kt
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.contact
|
||||
|
||||
import net.mamoe.mirai.contact.Contact
|
||||
import net.mamoe.mirai.internal.asQQAndroidBot
|
||||
|
||||
internal interface ContactAware {
|
||||
val contact: Contact
|
||||
|
||||
val bot get() = contact.bot.asQQAndroidBot()
|
||||
val client get() = bot.client
|
||||
}
|
@ -16,12 +16,14 @@ import net.mamoe.mirai.LowLevelApi
|
||||
import net.mamoe.mirai.Mirai
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.contact.announcement.Announcements
|
||||
import net.mamoe.mirai.contact.file.RemoteFiles
|
||||
import net.mamoe.mirai.data.GroupInfo
|
||||
import net.mamoe.mirai.data.MemberInfo
|
||||
import net.mamoe.mirai.event.broadcast
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.internal.QQAndroidBot
|
||||
import net.mamoe.mirai.internal.contact.announcement.AnnouncementsImpl
|
||||
import net.mamoe.mirai.internal.contact.file.RemoteFilesImpl
|
||||
import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
|
||||
import net.mamoe.mirai.internal.message.*
|
||||
import net.mamoe.mirai.internal.network.components.BdhSession
|
||||
@ -106,7 +108,10 @@ internal class GroupImpl constructor(
|
||||
override lateinit var owner: NormalMemberImpl
|
||||
override lateinit var botAsMember: NormalMemberImpl
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@Deprecated("Please use files instead.", replaceWith = ReplaceWith("files.root"))
|
||||
override val filesRoot: RemoteFile by lazy { RemoteFileImpl(this, "/") }
|
||||
override val files: RemoteFiles by lazy { RemoteFilesImpl(this) }
|
||||
|
||||
|
||||
override val announcements: Announcements by lazy {
|
||||
|
@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.contact.file
|
||||
|
||||
import net.mamoe.mirai.contact.FileSupported
|
||||
import net.mamoe.mirai.contact.file.AbsoluteFile
|
||||
import net.mamoe.mirai.contact.file.AbsoluteFolder
|
||||
import net.mamoe.mirai.internal.message.FileMessageImpl
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.FileManagement
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.toResult
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
|
||||
import net.mamoe.mirai.message.data.FileMessage
|
||||
import net.mamoe.mirai.utils.toUHexString
|
||||
|
||||
internal class AbsoluteFileImpl(
|
||||
contact: FileSupported,
|
||||
parent: AbsoluteFolder?,
|
||||
id: String,
|
||||
name: String,
|
||||
uploadTime: Long,
|
||||
lastModifiedTime: Long,
|
||||
uploaderId: Long,
|
||||
|
||||
override var expiryTime: Long,
|
||||
override val size: Long, // when file is changed, its id will also be changed, so no need to be var
|
||||
override val sha1: ByteArray,
|
||||
override val md5: ByteArray,
|
||||
|
||||
busId: Int,
|
||||
) : AbsoluteFile, AbstractAbsoluteFileFolder(
|
||||
contact, parent, id, name, uploadTime, uploaderId, lastModifiedTime,
|
||||
busId
|
||||
) {
|
||||
override fun checkPermission(operationHint: String) {
|
||||
// TODO: 30/10/2021 checkPermission: 群可以设置允许任何人上传而目前没有检测这个属性, 因此不能实现权限判定
|
||||
|
||||
// if (uploaderId == bot.id) return
|
||||
// if (contact is GroupImpl && !contact.botPermission.isOperator()) throwPermissionDeniedException(operationHint)
|
||||
// return
|
||||
}
|
||||
|
||||
override val isFile: Boolean get() = true
|
||||
override val isFolder: Boolean get() = false
|
||||
|
||||
override val absolutePath: String
|
||||
get() {
|
||||
val parent = parent
|
||||
return when {
|
||||
parent == null || parent.name == "/" -> "/$name"
|
||||
else -> "${parent.absolutePath}/$name"
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun exists(): Boolean {
|
||||
return FileManagement.GetFileInfo(
|
||||
client,
|
||||
groupCode = contact.id,
|
||||
busId = busId,
|
||||
fileId = id
|
||||
).sendAndExpect(bot)
|
||||
.toResult("AbsoluteFileImpl.exists", checkResp = false)
|
||||
.getOrThrow()
|
||||
.fileInfo != null
|
||||
}
|
||||
|
||||
|
||||
override suspend fun moveTo(folder: AbsoluteFolder): Boolean {
|
||||
if (folder.contact != this.contact) {
|
||||
error("Cross-group file operation is not yet supported.")
|
||||
}
|
||||
if (folder.absolutePath == this.parentOrRoot.absolutePath) return true
|
||||
checkPermission("moveTo")
|
||||
|
||||
val result = FileManagement.MoveFile(client, contact.id, busId, id, parent.idOrRoot, folder.idOrRoot)
|
||||
.sendAndExpect(bot).toResult("AbsoluteFileImpl.moveTo", checkResp = false)
|
||||
.getOrThrow()
|
||||
|
||||
return when (result.int32RetCode) {
|
||||
-36 -> throwPermissionDeniedException("moveTo")
|
||||
0 -> {
|
||||
parent = folder
|
||||
true
|
||||
}
|
||||
else -> {
|
||||
false
|
||||
}
|
||||
}
|
||||
// } else {
|
||||
// return FileManagement.RenameFolder(client, contact.id, id, name).sendAndExpect(bot)
|
||||
// .toResult("RemoteFile.moveTo", checkResp = false).getOrThrow().int32RetCode == 0
|
||||
// }
|
||||
}
|
||||
|
||||
override suspend fun getUrl(): String? {
|
||||
// Known error
|
||||
// java.lang.IllegalStateException: Failed AbsoluteFileImpl.getUrl, result=-303, msg=param error: bus_id
|
||||
// java.lang.IllegalStateException: Failed AbsoluteFileImpl.getUrl, result=-103, msg=GetFileAttrAction file not exist
|
||||
|
||||
val resp = FileManagement.RequestDownload(
|
||||
client,
|
||||
groupCode = contact.id,
|
||||
busId = busId,
|
||||
fileId = id
|
||||
).sendAndExpect(bot)
|
||||
.toResult("AbsoluteFileImpl.getUrl")
|
||||
.getOrElse { return null }
|
||||
|
||||
|
||||
return "http://${resp.downloadIp}/ftn_handler/${resp.downloadUrl.toUHexString("")}/?fname=" +
|
||||
id.toByteArray().toUHexString("")
|
||||
}
|
||||
|
||||
override fun toMessage(): FileMessage {
|
||||
return FileMessageImpl(id, busId, name, size)
|
||||
}
|
||||
|
||||
override suspend fun refresh(): Boolean {
|
||||
val new = refreshed() ?: return false
|
||||
this.parent = new.parent
|
||||
this.expiryTime = new.expiryTime
|
||||
this.name = new.name
|
||||
this.lastModifiedTime = new.lastModifiedTime
|
||||
return true
|
||||
}
|
||||
|
||||
override fun toString(): String = "AbsoluteFile(name=$name, absolutePath=$absolutePath, id=$id)"
|
||||
|
||||
override suspend fun refreshed(): AbsoluteFile? {
|
||||
val result = FileManagement.GetFileInfo(client, contact.id, id, busId)
|
||||
.sendAndExpect(bot)
|
||||
.toResult("AbsoluteFile.refreshed")
|
||||
.getOrNull()?.fileInfo
|
||||
?: return null
|
||||
|
||||
return if (result.parentFolderId == this.parentOrRoot.id) {
|
||||
this.parentOrRoot.impl().createChildFile(result)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
if (!super.equals(other)) return false
|
||||
|
||||
other as AbsoluteFileImpl
|
||||
|
||||
if (expiryTime != other.expiryTime) return false
|
||||
if (size != other.size) return false
|
||||
if (!sha1.contentEquals(other.sha1)) return false
|
||||
if (!md5.contentEquals(other.md5)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = super.hashCode()
|
||||
result = 31 * result + expiryTime.hashCode()
|
||||
result = 31 * result + size.hashCode()
|
||||
result = 31 * result + sha1.contentHashCode()
|
||||
result = 31 * result + md5.contentHashCode()
|
||||
return result
|
||||
}
|
||||
}
|
@ -0,0 +1,465 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.contact.file
|
||||
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.mamoe.mirai.contact.FileSupported
|
||||
import net.mamoe.mirai.contact.file.AbsoluteFile
|
||||
import net.mamoe.mirai.contact.file.AbsoluteFileFolder
|
||||
import net.mamoe.mirai.contact.file.AbsoluteFolder
|
||||
import net.mamoe.mirai.contact.isOperator
|
||||
import net.mamoe.mirai.internal.contact.GroupImpl
|
||||
import net.mamoe.mirai.internal.contact.file.RemoteFilesImpl.Companion.findFileByPath
|
||||
import net.mamoe.mirai.internal.network.QQAndroidClient
|
||||
import net.mamoe.mirai.internal.network.components.ClockHolder.Companion.clock
|
||||
import net.mamoe.mirai.internal.network.highway.Highway
|
||||
import net.mamoe.mirai.internal.network.highway.ResourceKind
|
||||
import net.mamoe.mirai.internal.network.protocol
|
||||
import net.mamoe.mirai.internal.network.protocol.data.proto.*
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.FileManagement
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.toResult
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
|
||||
import net.mamoe.mirai.internal.utils.FileSystem
|
||||
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
|
||||
import net.mamoe.mirai.utils.*
|
||||
import java.util.stream.Stream
|
||||
import kotlin.streams.asStream
|
||||
|
||||
internal fun Oidb0x6d8.GetFileListRspBody.Item.resolved(parent: AbsoluteFolderImpl): AbsoluteFileFolder? {
|
||||
val item = this
|
||||
return when {
|
||||
item.fileInfo != null -> {
|
||||
parent.createChildFile(item.fileInfo)
|
||||
}
|
||||
item.folderInfo != null -> {
|
||||
parent.createChildFolder(item.folderInfo)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
internal fun AbsoluteFolderImpl.createChildFolder(
|
||||
folderInfo: GroupFileCommon.FolderInfo
|
||||
): AbsoluteFolderImpl = AbsoluteFolderImpl(
|
||||
contact = contact,
|
||||
parent = this,
|
||||
id = folderInfo.folderId,
|
||||
name = folderInfo.folderName,
|
||||
uploadTime = folderInfo.createTime.toLongUnsigned(),
|
||||
uploaderId = folderInfo.createUin,
|
||||
lastModifiedTime = folderInfo.modifyTime.toLongUnsigned(),
|
||||
contentsCount = folderInfo.totalFileCount
|
||||
)
|
||||
|
||||
internal fun AbsoluteFolderImpl.createChildFile(
|
||||
info: GroupFileCommon.FileInfo
|
||||
): AbsoluteFileImpl = AbsoluteFileImpl(
|
||||
contact = contact,
|
||||
parent = this,
|
||||
id = info.fileId,
|
||||
name = info.fileName,
|
||||
uploadTime = info.uploadTime.toLongUnsigned(),
|
||||
lastModifiedTime = info.modifyTime.toLongUnsigned(),
|
||||
uploaderId = info.uploaderUin,
|
||||
expiryTime = info.deadTime.toLongUnsigned(),
|
||||
size = info.fileSize,
|
||||
sha1 = info.sha,
|
||||
md5 = info.md5,
|
||||
busId = info.busId
|
||||
)
|
||||
|
||||
internal class AbsoluteFolderImpl(
|
||||
contact: FileSupported, parent: AbsoluteFolder?, id: String, name: String,
|
||||
uploadTime: Long, uploaderId: Long, lastModifiedTime: Long,
|
||||
override var contentsCount: Int,
|
||||
) : AbstractAbsoluteFileFolder(
|
||||
contact,
|
||||
parent, id, name, uploadTime, uploaderId, lastModifiedTime, 0
|
||||
), AbsoluteFolder {
|
||||
override fun checkPermission(operationHint: String) {
|
||||
// 目录权限不受 '允许任何人上传' 设置的影响
|
||||
if (contact is GroupImpl && !contact.botPermission.isOperator()) throwPermissionDeniedException(operationHint)
|
||||
return
|
||||
}
|
||||
|
||||
override val isFile: Boolean get() = false
|
||||
override val isFolder: Boolean get() = true
|
||||
|
||||
override val absolutePath: String
|
||||
get() {
|
||||
val parent = parent
|
||||
return when {
|
||||
parent == null || this.id == "/" -> "/"
|
||||
parent.parent == null || parent.id == "/" -> "/$name"
|
||||
else -> "${parent.absolutePath}/$name"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
suspend fun getItemsFlow(
|
||||
client: QQAndroidClient,
|
||||
contact: FileSupported,
|
||||
folderId: String
|
||||
): Flow<Oidb0x6d8.GetFileListRspBody.Item> {
|
||||
return flow {
|
||||
var index = 0
|
||||
while (true) {
|
||||
val list = FileManagement.GetFileList(
|
||||
client,
|
||||
groupCode = contact.id,
|
||||
folderId = folderId,
|
||||
startIndex = index
|
||||
).sendAndExpect(client.bot).toResult("AbsoluteFolderImpl.getFilesFlow").getOrThrow()
|
||||
index += list.itemList.size
|
||||
|
||||
if (list.int32RetCode != 0) return@flow
|
||||
if (list.itemList.isEmpty()) return@flow
|
||||
|
||||
emitAll(list.itemList.asFlow())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun uploadNewFileImpl(
|
||||
folder: AbsoluteFolderImpl,
|
||||
filepath: String,
|
||||
content: ExternalResource,
|
||||
callback: ProgressionCallback<AbsoluteFile, Long>?
|
||||
): AbsoluteFile {
|
||||
if (filepath.isBlank()) throw IllegalArgumentException("filename cannot be blank.")
|
||||
// TODO: 12/10/2021 checkPermission for AbsoluteFolderImpl.upload
|
||||
|
||||
content.withAutoClose {
|
||||
val resp = FileManagement.RequestUpload(
|
||||
folder.client,
|
||||
groupCode = folder.contact.id,
|
||||
folderId = folder.id,
|
||||
resource = content,
|
||||
filename = filepath
|
||||
).sendAndExpect(folder.bot).toResult("AbsoluteFolderImpl.upload").getOrThrow()
|
||||
|
||||
when (resp.int32RetCode) {
|
||||
-36 -> folder.throwPermissionDeniedException("uploadNewFile")
|
||||
}
|
||||
|
||||
val file = AbsoluteFileImpl(
|
||||
contact = folder.contact,
|
||||
parent = folder,
|
||||
id = resp.fileId,
|
||||
name = filepath,
|
||||
uploadTime = folder.bot.clock.server.currentTimeSeconds(),
|
||||
lastModifiedTime = folder.bot.clock.server.currentTimeSeconds(),
|
||||
expiryTime = 0,
|
||||
uploaderId = folder.bot.id,
|
||||
size = content.size,
|
||||
sha1 = content.sha1,
|
||||
md5 = content.md5,
|
||||
busId = resp.busId
|
||||
)
|
||||
|
||||
if (resp.boolFileExist) {
|
||||
// resp.boolFileExist:
|
||||
// 服务器是否存在相同的内容, 只是用来判断可不可以跳过上传
|
||||
// 当为 true 时跳过上传, 但仍然需要完成 `sendMessage(FileMessage)` 才是正常逻辑
|
||||
callback?.onBegin(file, content)
|
||||
val result = kotlin.runCatching {
|
||||
folder.contact.sendMessage(file.toMessage())
|
||||
}.map { content.size }
|
||||
callback?.onFinished(file, content, result)
|
||||
return file
|
||||
}
|
||||
|
||||
val ext = GroupFileUploadExt(
|
||||
u1 = 100,
|
||||
u2 = 1,
|
||||
entry = GroupFileUploadEntry(
|
||||
business = ExcitingBusiInfo(
|
||||
busId = resp.busId,
|
||||
senderUin = folder.bot.id,
|
||||
receiverUin = folder.contact.id, // TODO: 2021/3/1 code or uin?
|
||||
groupCode = folder.contact.id,
|
||||
),
|
||||
fileEntry = ExcitingFileEntry(
|
||||
fileSize = content.size,
|
||||
md5 = content.md5,
|
||||
sha1 = content.sha1,
|
||||
fileId = resp.fileId.toByteArray(),
|
||||
uploadKey = resp.checkKey,
|
||||
),
|
||||
clientInfo = ExcitingClientInfo(
|
||||
clientType = 2,
|
||||
appId = folder.client.protocol.id.toString(),
|
||||
terminalType = 2,
|
||||
clientVer = "9e9c09dc",
|
||||
unknown = 4,
|
||||
),
|
||||
fileNameInfo = ExcitingFileNameInfo(filepath),
|
||||
host = ExcitingHostConfig(
|
||||
hosts = listOf(
|
||||
ExcitingHostInfo(
|
||||
url = ExcitingUrlInfo(
|
||||
unknown = 1,
|
||||
host = resp.uploadIpLanV4.firstOrNull()
|
||||
?: resp.uploadIpLanV6.firstOrNull()
|
||||
?: resp.uploadIp,
|
||||
),
|
||||
port = resp.uploadPort,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
u3 = 0,
|
||||
).toByteArray(GroupFileUploadExt.serializer())
|
||||
|
||||
callback?.onBegin(file, content)
|
||||
|
||||
kotlin.runCatching {
|
||||
Highway.uploadResourceBdh(
|
||||
bot = folder.bot,
|
||||
resource = content,
|
||||
kind = ResourceKind.GROUP_FILE,
|
||||
commandId = 71,
|
||||
extendInfo = ext,
|
||||
dataFlag = 0,
|
||||
callback = if (callback == null) null else fun(it: Long) {
|
||||
callback.onProgression(file, content, it)
|
||||
}
|
||||
)
|
||||
}.let { result0 ->
|
||||
val result = result0.onSuccessCatching {
|
||||
folder.contact.sendMessage(file.toMessage())
|
||||
}
|
||||
callback?.onFinished(file, content, result.map { content.size })
|
||||
}
|
||||
|
||||
return file
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getItemsFlow(): Flow<Oidb0x6d8.GetFileListRspBody.Item> = Companion.getItemsFlow(client, contact, id)
|
||||
|
||||
@JavaFriendlyAPI
|
||||
private suspend fun getItemsSequence(): Sequence<Oidb0x6d8.GetFileListRspBody.Item> {
|
||||
return sequence {
|
||||
var index = 0
|
||||
while (true) {
|
||||
val list = runBlocking {
|
||||
FileManagement.GetFileList(
|
||||
client,
|
||||
groupCode = contact.id,
|
||||
folderId = id,
|
||||
startIndex = index
|
||||
).sendAndExpect(bot)
|
||||
}.toResult("AbsoluteFolderImpl.getFilesFlow").getOrThrow()
|
||||
index += list.itemList.size
|
||||
|
||||
if (list.int32RetCode != 0) return@sequence
|
||||
if (list.itemList.isEmpty()) return@sequence
|
||||
|
||||
yieldAll(list.itemList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Oidb0x6d8.GetFileListRspBody.Item.resolve(): AbsoluteFileFolder? = resolved(this@AbsoluteFolderImpl)
|
||||
|
||||
override suspend fun folders(): Flow<AbsoluteFolder> {
|
||||
return getItemsFlow().filter { it.folderInfo != null }.map { it.resolve() as AbsoluteFolder }
|
||||
}
|
||||
|
||||
@JavaFriendlyAPI
|
||||
override suspend fun foldersStream(): Stream<AbsoluteFolder> {
|
||||
return getItemsSequence().filter { it.folderInfo != null }.map { it.resolve() as AbsoluteFolder }.asStream()
|
||||
}
|
||||
|
||||
override suspend fun files(): Flow<AbsoluteFile> {
|
||||
return getItemsFlow().filter { it.fileInfo != null }.map { it.resolve() as AbsoluteFile }
|
||||
}
|
||||
|
||||
@JavaFriendlyAPI
|
||||
override suspend fun filesStream(): Stream<AbsoluteFile> {
|
||||
return getItemsSequence().filter { it.fileInfo != null }.map { it.resolve() as AbsoluteFile }.asStream()
|
||||
}
|
||||
|
||||
override suspend fun children(): Flow<AbsoluteFileFolder> {
|
||||
return getItemsFlow().mapNotNull { it.resolve() }
|
||||
}
|
||||
|
||||
@JavaFriendlyAPI
|
||||
override suspend fun childrenStream(): Stream<AbsoluteFileFolder> {
|
||||
return getItemsSequence().mapNotNull { it.resolve() }.asStream()
|
||||
}
|
||||
|
||||
override suspend fun createFolder(name: String): AbsoluteFolder {
|
||||
if (name.isBlank()) throw IllegalArgumentException("folder name cannot be blank.")
|
||||
checkPermission("createFolder")
|
||||
FileSystem.checkLegitimacy(name)
|
||||
|
||||
// server only support nesting depth level of 1 so we don't need to check the name
|
||||
|
||||
val result = FileManagement.CreateFolder(client, contact.id, this.id, name)
|
||||
.sendAndExpect(bot).toResult("AbsoluteFolderImpl.mkdir", checkResp = false)
|
||||
.getOrThrow() // throw protocol errors
|
||||
|
||||
/*
|
||||
2021-10-30 13:06:33 D/soutv: unnamed = CreateFolderRspBody#-941698272 {
|
||||
folderInfo=FolderInfo#1879610684 {
|
||||
createTime=0x617D3548(1635595592)
|
||||
createUin=xxx
|
||||
folderId=/49a18e46-cf24-4362-b0d0-13235c0e7862
|
||||
folderName=myFolder
|
||||
modifyTime=0x617D3548(1635595592)
|
||||
modifyUin=xxx
|
||||
parentFolderId=/
|
||||
usedSpace=0x0000000000000000(0)
|
||||
}
|
||||
retMsg=ok
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
2021-10-30 13:03:44 D/soutv: unnamed = CreateFolderRspBody#-941698272 {
|
||||
clientWording=只允许群主和管理员操作
|
||||
int32RetCode=0xFFFFFFDC(-36)
|
||||
retMsg=not group admin
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
2021-10-30 13:10:32 D/soutv: unnamed = CreateFolderRspBody#-941698272 {
|
||||
clientWording=同名文件夹已存在
|
||||
int32RetCode=0xFFFFFEC7(-313)
|
||||
retMsg=folder name has exist
|
||||
}
|
||||
*/
|
||||
|
||||
return when (result.int32RetCode) {
|
||||
-36 -> throwPermissionDeniedException("createFolder")
|
||||
-313 -> this.resolveFolder(name) // already exists
|
||||
0 -> {
|
||||
if (result.folderInfo != null) {
|
||||
this.createChildFolder(result.folderInfo)
|
||||
} else {
|
||||
this.resolveFolder(name)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// unexpected errors
|
||||
error("Failed to create folder '$name': ${result.int32RetCode} ${result.clientWording}.")
|
||||
}
|
||||
} ?: error("Failed to create folder '$name': server returned success but failed to find folder.")
|
||||
}
|
||||
|
||||
override suspend fun resolveFolder(name: String): AbsoluteFolder? {
|
||||
if (name.isBlank()) throw IllegalArgumentException("folder name cannot be blank.")
|
||||
if (!FileSystem.isLegal(name)) return null
|
||||
return getItemsFlow().firstOrNull { it.folderInfo?.folderName == name }?.resolve() as AbsoluteFolder?
|
||||
}
|
||||
|
||||
override suspend fun resolveFileById(id: String, deep: Boolean): AbsoluteFile? {
|
||||
if (id == "/" || id.isEmpty()) throw IllegalArgumentException("Illegal file id: $id")
|
||||
getItemsFlow().filter { it.fileInfo?.fileId == id }.map { it.resolve() as AbsoluteFile }.firstOrNull()
|
||||
?.let { return it }
|
||||
|
||||
if (!deep) return null
|
||||
|
||||
return folders().map { it.resolveFileById(id, deep) }.firstOrNull()
|
||||
}
|
||||
|
||||
override suspend fun resolveFiles(path: String): Flow<AbsoluteFile> {
|
||||
if (path.isBlank()) throw IllegalArgumentException("path cannot be blank.")
|
||||
if (!FileSystem.isLegal(path)) return emptyFlow()
|
||||
|
||||
if (!path.contains('/')) {
|
||||
return getItemsFlow().filter { it.fileInfo?.fileName == path }.map { it.resolve() as AbsoluteFile }
|
||||
}
|
||||
|
||||
return resolveFolder(path.substringBefore('/'))?.resolveFiles(path.substringAfter('/')) ?: emptyFlow()
|
||||
}
|
||||
|
||||
@OptIn(JavaFriendlyAPI::class)
|
||||
override suspend fun resolveFilesStream(path: String): Stream<AbsoluteFile> {
|
||||
if (path.isBlank()) throw IllegalArgumentException("path cannot be blank.")
|
||||
if (!FileSystem.isLegal(path)) return Stream.empty()
|
||||
|
||||
if (!path.contains('/')) {
|
||||
return getItemsSequence().filter { it.fileInfo?.fileName == path }.map { it.resolve() as AbsoluteFile }
|
||||
.asStream()
|
||||
}
|
||||
|
||||
return resolveFolder(path.substringBefore('/'))?.resolveFilesStream(path.substringAfter('/')) ?: Stream.empty()
|
||||
}
|
||||
|
||||
override suspend fun resolveAll(path: String): Flow<AbsoluteFileFolder> {
|
||||
if (path.isBlank()) throw IllegalArgumentException("path cannot be blank.")
|
||||
if (!FileSystem.isLegal(path)) return emptyFlow()
|
||||
if (!path.contains('/')) {
|
||||
return getItemsFlow().mapNotNull { it.resolve() }
|
||||
}
|
||||
|
||||
return resolveFolder(path.substringBefore('/'))?.resolveAll(path.substringAfter('/')) ?: emptyFlow()
|
||||
}
|
||||
|
||||
@JavaFriendlyAPI
|
||||
override suspend fun resolveAllStream(path: String): Stream<AbsoluteFileFolder> {
|
||||
if (path.isBlank()) throw IllegalArgumentException("path cannot be blank.")
|
||||
if (!FileSystem.isLegal(path)) return Stream.empty()
|
||||
if (!path.contains('/')) {
|
||||
return getItemsSequence().mapNotNull { it.resolve() }.asStream()
|
||||
}
|
||||
|
||||
return resolveFolder(path.substringBefore('/'))?.resolveAllStream(path.substringAfter('/')) ?: Stream.empty()
|
||||
}
|
||||
|
||||
override suspend fun uploadNewFile(
|
||||
filepath: String,
|
||||
content: ExternalResource,
|
||||
callback: ProgressionCallback<AbsoluteFile, Long>?
|
||||
): AbsoluteFile {
|
||||
val (actualFolder, actualFilename) = findFileByPath(filepath)
|
||||
return uploadNewFileImpl(actualFolder.impl(), actualFilename, content, callback)
|
||||
}
|
||||
|
||||
override suspend fun exists(): Boolean {
|
||||
return parentOrFail().folders().firstOrNull { it.id == this.id } != null
|
||||
}
|
||||
|
||||
override suspend fun refresh(): Boolean {
|
||||
val new = refreshed() ?: return false
|
||||
this.name = new.name
|
||||
this.lastModifiedTime = new.lastModifiedTime
|
||||
this.contentsCount = new.contentsCount
|
||||
return true
|
||||
}
|
||||
|
||||
override fun toString(): String = "AbsoluteFolder(name=$name, absolutePath=$absolutePath, id=$id)"
|
||||
|
||||
override suspend fun refreshed(): AbsoluteFolder? = parentOrRoot.folders().firstOrNull { it.id == this.id }
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
if (!super.equals(other)) return false
|
||||
|
||||
other as AbsoluteFolderImpl
|
||||
|
||||
if (contentsCount != other.contentsCount) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = super.hashCode()
|
||||
result = 31 * result + contentsCount.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
package net.mamoe.mirai.internal.contact.file
|
||||
|
||||
import net.mamoe.mirai.contact.FileSupported
|
||||
import net.mamoe.mirai.contact.PermissionDeniedException
|
||||
import net.mamoe.mirai.contact.file.AbsoluteFile
|
||||
import net.mamoe.mirai.contact.file.AbsoluteFileFolder
|
||||
import net.mamoe.mirai.contact.file.AbsoluteFolder
|
||||
import net.mamoe.mirai.internal.asQQAndroidBot
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.FileManagement
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.toResult
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
|
||||
import net.mamoe.mirai.internal.utils.FileSystem
|
||||
import net.mamoe.mirai.utils.cast
|
||||
|
||||
internal fun AbstractAbsoluteFileFolder.api(): AbsoluteFileFolder = this.cast()
|
||||
internal fun AbsoluteFileFolder.impl(): AbstractAbsoluteFileFolder = this.cast()
|
||||
internal fun AbsoluteFile.impl(): AbsoluteFileImpl = this.cast()
|
||||
internal fun AbsoluteFolder.impl(): AbsoluteFolderImpl = this.cast()
|
||||
|
||||
internal val AbsoluteFolder?.idOrRoot get() = this?.id ?: AbsoluteFolder.ROOT_FOLDER_ID
|
||||
|
||||
internal val AbstractAbsoluteFileFolder.parentOrRoot get() = parent ?: contact.files.root
|
||||
|
||||
/**
|
||||
* @see AbsoluteFileFolder
|
||||
*/
|
||||
internal abstract class AbstractAbsoluteFileFolder(
|
||||
// overriding AbsFileFolder
|
||||
val contact: FileSupported,
|
||||
var parent: AbsoluteFolder?,
|
||||
val id: String, // uuid-like
|
||||
var name: String,
|
||||
val uploadTime: Long,
|
||||
val uploaderId: Long,
|
||||
var lastModifiedTime: Long,
|
||||
// end
|
||||
|
||||
val busId: Int, // protocol internal
|
||||
) {
|
||||
protected inline val bot get() = contact.bot.asQQAndroidBot()
|
||||
protected inline val client get() = bot.client
|
||||
|
||||
protected abstract fun checkPermission(operationHint: String)
|
||||
|
||||
fun throwPermissionDeniedException(operationHint: String): Nothing {
|
||||
throw PermissionDeniedException("Permission denied: '$operationHint' on file '${this.api().absolutePath}' requires an operator permission.")
|
||||
}
|
||||
|
||||
protected fun parentOrFail() = parent ?: error("Cannot rename the root folder.")
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// overriding AbsFileFolder
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
protected abstract val isFile: Boolean
|
||||
protected abstract val isFolder: Boolean
|
||||
|
||||
suspend fun renameTo(newName: String): Boolean {
|
||||
FileSystem.checkLegitimacy(newName)
|
||||
parentOrFail()
|
||||
checkPermission("renameTo")
|
||||
|
||||
val result = if (isFile) {
|
||||
FileManagement.RenameFile(client, contact.id, busId, id, parent.idOrRoot, newName)
|
||||
} else {
|
||||
FileManagement.RenameFolder(client, contact.id, id, newName)
|
||||
}.sendAndExpect(bot)
|
||||
|
||||
result.toResult("AbstractAbsoluteFileFolder.renameTo") {
|
||||
when (it) {
|
||||
0 -> {
|
||||
name = newName
|
||||
return true
|
||||
}
|
||||
1 -> return false
|
||||
else -> false
|
||||
}
|
||||
}.getOrThrow()
|
||||
|
||||
error("unreachable")
|
||||
}
|
||||
|
||||
suspend fun delete(): Boolean {
|
||||
checkPermission("delete")
|
||||
val result = if (isFile) {
|
||||
FileManagement.DeleteFile(client, contact.id, busId, id, parent.idOrRoot).sendAndExpect(bot)
|
||||
} else {
|
||||
// natively 'recursive'
|
||||
FileManagement.DeleteFolder(client, contact.id, id).sendAndExpect(bot)
|
||||
}.toResult("AbstractAbsoluteFileFolder.delete", checkResp = false).getOrThrow()
|
||||
|
||||
return when (result.int32RetCode) {
|
||||
-36 -> throwPermissionDeniedException("delete")
|
||||
0 -> true
|
||||
else -> {
|
||||
// files not exists or other errors.
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as AbstractAbsoluteFileFolder
|
||||
|
||||
if (contact != other.contact) return false
|
||||
if (parent != other.parent) return false
|
||||
if (id != other.id) return false
|
||||
if (name != other.name) return false
|
||||
if (uploadTime != other.uploadTime) return false
|
||||
if (uploaderId != other.uploaderId) return false
|
||||
if (lastModifiedTime != other.lastModifiedTime) return false
|
||||
if (busId != other.busId) return false
|
||||
if (isFile != other.isFile) return false
|
||||
if (isFolder != other.isFolder) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = contact.hashCode()
|
||||
result = 31 * result + (parent?.hashCode() ?: 0)
|
||||
result = 31 * result + id.hashCode()
|
||||
result = 31 * result + name.hashCode()
|
||||
result = 31 * result + uploadTime.hashCode()
|
||||
result = 31 * result + uploaderId.hashCode()
|
||||
result = 31 * result + lastModifiedTime.hashCode()
|
||||
result = 31 * result + busId
|
||||
result = 31 * result + isFile.hashCode()
|
||||
result = 31 * result + isFolder.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.contact.file
|
||||
|
||||
import net.mamoe.mirai.internal.network.QQAndroidClient
|
||||
import net.mamoe.mirai.internal.network.protocol.data.proto.Oidb0x6d6
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.CommonOidbResponse
|
||||
import net.mamoe.mirai.internal.utils.FileSystem
|
||||
|
||||
/**
|
||||
* Abstract protocol bridge for file management.
|
||||
*/
|
||||
internal interface FileProtocol {
|
||||
val fs: FileSystem get() = FileSystem
|
||||
|
||||
fun renameFile(
|
||||
client: QQAndroidClient,
|
||||
groupCode: Long,
|
||||
busId: Int,
|
||||
fileId: String,
|
||||
parentFolderId: String,
|
||||
newName: String,
|
||||
): CommonOidbResponse<Oidb0x6d6.RenameFileRspBody>
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.contact.file
|
||||
|
||||
import net.mamoe.mirai.contact.FileSupported
|
||||
import net.mamoe.mirai.contact.file.AbsoluteFolder
|
||||
import net.mamoe.mirai.contact.file.RemoteFiles
|
||||
import net.mamoe.mirai.internal.contact.ContactAware
|
||||
import net.mamoe.mirai.internal.utils.FileSystem
|
||||
|
||||
internal class RemoteFilesImpl(
|
||||
override val contact: FileSupported,
|
||||
override val root: AbsoluteFolder = AbsoluteFolderImpl(
|
||||
contact,
|
||||
null,
|
||||
AbsoluteFolder.ROOT_FOLDER_ID,
|
||||
"/",
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
),
|
||||
) : RemoteFiles, ContactAware {
|
||||
companion object {
|
||||
suspend fun AbsoluteFolder.findFileByPath(path: String): Pair<AbsoluteFolder, String> {
|
||||
if (path.isBlank()) throw IllegalArgumentException("absolutePath cannot be blank.")
|
||||
val normalized = FileSystem.normalize(path)
|
||||
// if (!normalized.contains('/')) {
|
||||
// throw IllegalArgumentException("Invalid absolutePath: '$path'. If you wanted to upload file to root directory, please add a leading '/'.")
|
||||
// }
|
||||
val folder = when (normalized.count { it == '/' }) {
|
||||
0, 1 -> this
|
||||
else -> this.createFolder(normalized.substringBeforeLast("/"))
|
||||
}
|
||||
|
||||
val filename = normalized.substringAfterLast('/')
|
||||
return folder to filename
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -10,9 +10,26 @@
|
||||
|
||||
package net.mamoe.mirai.internal.message
|
||||
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.contact.FileSupported
|
||||
import net.mamoe.mirai.contact.file.AbsoluteFile
|
||||
import net.mamoe.mirai.contact.file.AbsoluteFolder
|
||||
import net.mamoe.mirai.internal.QQAndroidBot
|
||||
import net.mamoe.mirai.internal.asQQAndroidBot
|
||||
import net.mamoe.mirai.internal.contact.file.AbsoluteFolderImpl
|
||||
import net.mamoe.mirai.internal.contact.file.createChildFile
|
||||
import net.mamoe.mirai.internal.contact.file.impl
|
||||
import net.mamoe.mirai.internal.contact.file.resolved
|
||||
import net.mamoe.mirai.internal.network.protocol.data.proto.Oidb0x6d8.GetFileListRspBody
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.FileManagement
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.toResult
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
|
||||
import net.mamoe.mirai.message.data.FileMessage
|
||||
import net.mamoe.mirai.utils.cast
|
||||
import kotlin.contracts.contract
|
||||
|
||||
internal fun FileMessage.checkIsImpl(): FileMessageImpl {
|
||||
@ -31,5 +48,41 @@ internal data class FileMessageImpl(
|
||||
override val internalId: Int
|
||||
get() = busId
|
||||
|
||||
override suspend fun toAbsoluteFile(contact: FileSupported): AbsoluteFile? {
|
||||
val result = FileManagement.GetFileInfo(contact.bot.asQQAndroidBot().client, contact.id, id, busId)
|
||||
.sendAndExpect(contact.bot.asQQAndroidBot())
|
||||
.toResult("FileMessage.toAbsoluteFile").getOrThrow()
|
||||
if (result.fileInfo == null) return null
|
||||
|
||||
// Get its parent AbsoluteFolder
|
||||
// This is necessary for properties like creationTime.
|
||||
// Maybe we can optimize it in the future (i.e. make it lazy?)
|
||||
|
||||
val root = contact.files.root.impl()
|
||||
val folder = if (result.fileInfo.parentFolderId == AbsoluteFolder.ROOT_FOLDER_ID) {
|
||||
root
|
||||
} else {
|
||||
val folders = ArrayList<GetFileListRspBody.Item>()
|
||||
root.impl().getItemsFlow()
|
||||
.filter { it.folderInfo != null }
|
||||
.onEach { folders.add(it) }
|
||||
.firstOrNull { it.folderInfo?.folderId == result.fileInfo.parentFolderId }
|
||||
?.resolved(root) as AbsoluteFolderImpl?
|
||||
?: kotlin.run {
|
||||
for (folder in folders) {
|
||||
AbsoluteFolderImpl.getItemsFlow(
|
||||
(contact.bot as QQAndroidBot).client,
|
||||
contact,
|
||||
folder.folderInfo!!.folderId
|
||||
).firstOrNull { it.folderInfo?.folderId == result.fileInfo.parentFolderId }
|
||||
?.resolved(root)?.cast<AbsoluteFolderImpl?>()?.let { return@run it }
|
||||
}
|
||||
root
|
||||
}
|
||||
}
|
||||
|
||||
return folder.createChildFile(result.fileInfo)
|
||||
}
|
||||
|
||||
override fun toString(): String = "[mirai:file:$name,$id,$size,$busId]"
|
||||
}
|
@ -73,6 +73,28 @@ internal inline fun <T> CommonOidbResponse<T>.toResult(actionName: String, check
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE")
|
||||
@kotlin.internal.InlineOnly
|
||||
internal inline fun <T> CommonOidbResponse<T>.toResult(
|
||||
actionName: String,
|
||||
checkResp: CheckableStruct.(Int) -> Boolean
|
||||
): Result<T> {
|
||||
return if (this is CommonOidbResponse.Failure) {
|
||||
Result.failure(this.createException(actionName))
|
||||
} else {
|
||||
this as CommonOidbResponse.Success<T>
|
||||
val result = this.resp
|
||||
if (result is CheckableStruct) {
|
||||
if (!checkResp(
|
||||
result,
|
||||
result.int32RetCode
|
||||
)
|
||||
) return Result.failure(IllegalStateException("Failed $actionName, result=${result.int32RetCode}, msg=${result.retMsg}"))
|
||||
}
|
||||
Result.success(this.resp)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param respMapper may throw any exception, which will be wrapped to CommonOidbResponse.Failure
|
||||
*/
|
||||
|
@ -7,6 +7,8 @@
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION", "OverridingDeprecatedMember")
|
||||
|
||||
package net.mamoe.mirai.internal.utils
|
||||
|
||||
import kotlinx.coroutines.flow.*
|
||||
@ -46,6 +48,10 @@ internal object FileSystem {
|
||||
}
|
||||
}
|
||||
|
||||
fun isLegal(path: String): Boolean {
|
||||
return path.firstOrNull { it in """:*?"<>|""" } == null
|
||||
}
|
||||
|
||||
fun normalize(path: String): String {
|
||||
checkLegitimacy(path)
|
||||
return path.replace('\\', '/')
|
||||
|
Loading…
Reference in New Issue
Block a user