[Connection] Add upload method

This commit is contained in:
Christian 2019-09-09 13:22:02 +00:00 committed by Marcel
parent 78b59212d1
commit 7d5da300c4
8 changed files with 244 additions and 14 deletions

View File

@ -23,6 +23,7 @@
import 'dart:async';
import 'dart:core';
import 'dart:io';
import 'package:famedlysdk/src/AccountData.dart';
import 'package:famedlysdk/src/Presence.dart';
@ -310,6 +311,18 @@ class Client {
return resp["room_id"];
}
/// Uploads a new user avatar for this user. Returns ErrorResponse if something went wrong.
Future<dynamic> setAvatar(File file) async {
final uploadResp = await connection.upload(file);
if (uploadResp is ErrorResponse) return uploadResp;
final setAvatarResp = await connection.jsonRequest(
type: HTTPType.PUT,
action: "/client/r0/profile/$userID/avatar_url",
data: {"avatar_url": uploadResp});
if (setAvatarResp is ErrorResponse) return setAvatarResp;
return null;
}
/// Fetches the pushrules for the logged in user.
/// These are needed for notifications on Android
Future<PushrulesResponse> getPushrules() async {

View File

@ -24,11 +24,13 @@
import 'dart:async';
import 'dart:convert';
import 'dart:core';
import 'dart:io';
import 'package:famedlysdk/src/Room.dart';
import 'package:famedlysdk/src/RoomList.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:mime_type/mime_type.dart';
import 'Client.dart';
import 'User.dart';
@ -204,18 +206,23 @@ class Connection {
/// ```
///
Future<dynamic> jsonRequest(
{HTTPType type, String action, dynamic data = "", int timeout}) async {
{HTTPType type,
String action,
dynamic data = "",
int timeout,
String contentType = "application/json"}) async {
if (client.isLogged() == false && client.homeserver == null)
throw ("No homeserver specified.");
if (timeout == null) timeout = syncTimeoutSec + 5;
dynamic json;
if (data is Map) data.removeWhere((k, v) => v == null);
(!(data is String)) ? json = jsonEncode(data) : json = data;
if (data is List<int>) json = data;
final url = "${client.homeserver}/_matrix${action}";
Map<String, String> headers = {
"Content-type": "application/json",
"Content-Type": contentType,
};
if (client.isLogged())
headers["Authorization"] = "Bearer ${client.accessToken}";
@ -279,6 +286,24 @@ class Connection {
return jsonResp;
}
/// Uploads a file with the name [fileName] as base64 encoded to the server
/// and returns the mxc url as a string or an [ErrorResponse].
Future<dynamic> upload(File file) async {
List<int> fileBytes;
if (client.homeserver != "https://fakeServer.notExisting")
fileBytes = await file.readAsBytes();
String fileName = file.path.split("/").last;
String mimeType = mime(file.path);
print("[UPLOADING] $fileName, type: $mimeType, size: ${fileBytes?.length}");
final dynamic resp = await jsonRequest(
type: HTTPType.POST,
action: "/media/r0/upload?filename=$fileName",
data: fileBytes,
contentType: mimeType);
if (resp is ErrorResponse) return resp;
return resp["content_uri"];
}
Future<dynamic> _syncRequest;
Future<void> _sync() async {
@ -487,10 +512,10 @@ class _LifecycleEventHandler extends WidgetsBindingObserver {
case AppLifecycleState.inactive:
case AppLifecycleState.paused:
case AppLifecycleState.suspending:
await suspendingCallBack();
if (suspendingCallBack != null) await suspendingCallBack();
break;
case AppLifecycleState.resumed:
await resumeCallBack();
if (resumeCallBack != null) await resumeCallBack();
break;
}
}

View File

@ -21,6 +21,8 @@
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:io';
import 'package:famedlysdk/src/Client.dart';
import 'package:famedlysdk/src/Event.dart';
import 'package:famedlysdk/src/RoomAccountData.dart';
@ -29,6 +31,8 @@ import 'package:famedlysdk/src/responses/ErrorResponse.dart';
import 'package:famedlysdk/src/sync/EventUpdate.dart';
import 'package:famedlysdk/src/utils/ChatTime.dart';
import 'package:famedlysdk/src/utils/MxContent.dart';
import 'package:image/image.dart';
import 'package:mime_type/mime_type.dart';
import './User.dart';
import 'Connection.dart';
@ -223,18 +227,100 @@ class Room {
return res;
}
/// Call the Matrix API to send a simple text message.
Future<dynamic> sendText(String message, {String txid = null}) async {
Future<dynamic> _sendRawEventNow(Map<String, dynamic> content,
{String txid = null}) async {
if (txid == null) txid = "txid${DateTime.now().millisecondsSinceEpoch}";
final dynamic res = await client.connection.jsonRequest(
type: HTTPType.PUT,
action: "/client/r0/rooms/${id}/send/m.room.message/$txid",
data: {"msgtype": "m.text", "body": message});
data: content);
if (res is ErrorResponse) client.connection.onError.add(res);
return res;
}
Future<String> sendTextEvent(String message, {String txid = null}) async {
Future<String> sendTextEvent(String message, {String txid = null}) =>
sendEvent({"msgtype": "m.text", "body": message}, txid: txid);
Future<String> sendFileEvent(File file, String msgType,
{String txid = null}) async {
// Try to get the size of the file
int size;
try {
size = (await file.readAsBytes()).length;
} catch (e) {
print("[UPLOAD] Could not get size. Reason: ${e.toString()}");
}
// Upload file
String fileName = file.path.split("/").last;
String mimeType = mime(fileName);
final dynamic uploadResp = await client.connection.upload(file);
if (uploadResp is ErrorResponse) return null;
// Send event
Map<String, dynamic> content = {
"msgtype": msgType,
"body": fileName,
"filename": fileName,
"url": uploadResp,
"info": {
"mimetype": mimeType,
}
};
if (size != null)
content["info"] = {
"size": size,
"mimetype": mimeType,
};
return await sendEvent(content, txid: txid);
}
Future<String> sendImageEvent(File file, {String txid = null}) async {
String fileName = file.path.split("/").last;
Map<String, dynamic> info;
// Try to manipulate the file size and create a thumbnail
try {
Image image = copyResize(decodeImage(file.readAsBytesSync()), width: 800);
Image thumbnail = copyResize(image, width: 236);
file = File(fileName)..writeAsBytesSync(encodePng(image));
File thumbnailFile = File(fileName)
..writeAsBytesSync(encodePng(thumbnail));
final dynamic uploadThumbnailResp =
await client.connection.upload(thumbnailFile);
if (uploadThumbnailResp is ErrorResponse) throw (uploadThumbnailResp);
info = {
"size": image.getBytes().length,
"mimetype": mime(file.path),
"w": image.width,
"h": image.height,
"thumbnail_url": uploadThumbnailResp,
"thumbnail_info": {
"w": thumbnail.width,
"h": thumbnail.height,
"mimetype": mime(thumbnailFile.path),
"size": thumbnail.getBytes().length
},
};
} catch (e) {
print(
"[UPLOAD] Could not create thumbnail of image. Try to upload the unchanged file...");
}
final dynamic uploadResp = await client.connection.upload(file);
if (uploadResp is ErrorResponse) return null;
Map<String, dynamic> content = {
"msgtype": "m.image",
"body": fileName,
"url": uploadResp,
};
if (info != null) content["info"] = info;
return await sendEvent(content, txid: txid);
}
Future<String> sendEvent(Map<String, dynamic> content,
{String txid = null}) async {
final String type = "m.room.message";
// Create new transaction id
@ -253,10 +339,7 @@ class Room {
"sender": client.userID,
"status": 0,
"origin_server_ts": now,
"content": {
"msgtype": "m.text",
"body": message,
}
"content": content
});
client.connection.onEvent.add(eventUpdate);
await client.store?.transaction(() {
@ -265,7 +348,7 @@ class Room {
});
// Send the text and on success, store and display a *sent* event.
final dynamic res = await sendText(message, txid: messageID);
final dynamic res = await _sendRawEventNow(content, txid: messageID);
if (res is ErrorResponse || !(res["event_id"] is String)) {
// On error, set status to -1
@ -604,4 +687,17 @@ class Room {
return powerLevelState.content["users"];
return null;
}
/// Uploads a new user avatar for this room. Returns ErrorResponse if something went wrong
/// and the event ID otherwise.
Future<dynamic> setAvatar(File file) async {
final uploadResp = await client.connection.upload(file);
if (uploadResp is ErrorResponse) return uploadResp;
final setAvatarResp = await client.connection.jsonRequest(
type: HTTPType.PUT,
action: "/client/r0/rooms/$id/state/m.room.avatar/",
data: {"url": uploadResp});
if (setAvatarResp is ErrorResponse) return setAvatarResp;
return setAvatarResp["event_id"];
}
}

View File

@ -8,6 +8,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.36.3"
archive:
dependency: transitive
description:
name: archive
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.10"
args:
dependency: transitive
description:
@ -200,6 +207,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.3"
image:
dependency: "direct main"
description:
name: image
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.4"
intl:
dependency: "direct main"
description:
@ -270,6 +284,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.6+3"
mime_type:
dependency: "direct main"
description:
name: mime_type
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.4"
package_config:
dependency: transitive
description:
@ -298,6 +319,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
petitparser:
dependency: transitive
description:
name: petitparser
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.0"
pool:
dependency: transitive
description:
@ -450,6 +478,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.13"
xml:
dependency: transitive
description:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "3.5.0"
yaml:
dependency: transitive
description:
@ -458,5 +493,5 @@ packages:
source: hosted
version: "2.1.16"
sdks:
dart: ">=2.3.0-dev.0.1 <3.0.0"
dart: ">=2.4.0 <3.0.0"
flutter: ">=1.2.1 <2.0.0"

View File

@ -17,6 +17,8 @@ dependencies:
# Connection
http: ^0.12.0+2
mime_type: ^0.2.4
image: ^2.1.4
# Time formatting
intl: ^0.15.8

View File

@ -22,6 +22,7 @@
*/
import 'dart:async';
import 'dart:io';
import 'package:famedlysdk/src/AccountData.dart';
import 'package:famedlysdk/src/Client.dart';
@ -270,6 +271,18 @@ void main() {
expect(newID, "!1234:fakeServer.notExisting");
});
test('upload', () async {
final File testFile = File.fromUri(Uri.parse("fake/path/file.jpeg"));
final dynamic resp = await matrix.connection.upload(testFile);
expect(resp, "mxc://example.com/AQwafuaFswefuhsfAFAgsw");
});
test('setAvatar', () async {
final File testFile = File.fromUri(Uri.parse("fake/path/file.jpeg"));
final dynamic resp = await matrix.setAvatar(testFile);
expect(resp, null);
});
test('getPushrules', () async {
final PushrulesResponse pushrules = await matrix.getPushrules();
final PushrulesResponse awaited_resp = PushrulesResponse.fromJson(

View File

@ -509,6 +509,8 @@ class FakeMatrixApi extends MockClient {
"access_token": "abc123",
"device_id": "GHTYAJCE"
},
"/media/r0/upload?filename=file.jpeg": (var req) =>
{"content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw"},
"/client/r0/logout": (var reqI) => {},
"/client/r0/pushers/set": (var reqI) => {},
"/client/r0/join/1234": (var reqI) => {"room_id": "1234"},
@ -523,10 +525,18 @@ class FakeMatrixApi extends MockClient {
"/client/r0/rooms/!localpart:server.abc/invite": (var reqI) => {},
},
"PUT": {
"/client/r0/rooms/!localpart:server.abc/send/m.room.message/testtxid":
(var reqI) => {
"event_id": "42",
},
"/client/r0/rooms/!1234:example.com/send/m.room.message/1234":
(var reqI) => {
"event_id": "42",
},
"/client/r0/profile/@test:fakeServer.notExisting/avatar_url":
(var reqI) => {},
"/client/r0/rooms/!localpart:server.abc/state/m.room.avatar/":
(var reqI) => {"event_id": "YUwRidLecu:example.com"},
"/client/r0/rooms/!localpart:server.abc/state/m.room.name": (var reqI) =>
{
"event_id": "42",

View File

@ -21,6 +21,8 @@
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:io';
import 'package:famedlysdk/src/Client.dart';
import 'package:famedlysdk/src/Event.dart';
import 'package:famedlysdk/src/Room.dart';
@ -255,5 +257,39 @@ void main() {
expect(user.stateKey, "@getme:example.com");
expect(user.calcDisplayname(), "You got me");
});
test('setAvatar', () async {
final File testFile = File.fromUri(Uri.parse("fake/path/file.jpeg"));
final dynamic resp = await room.setAvatar(testFile);
expect(resp, "YUwRidLecu:example.com");
});
test('sendEvent', () async {
final dynamic resp = await room.sendEvent(
{"msgtype": "m.text", "body": "hello world"},
txid: "testtxid");
expect(resp, "42");
});
test('sendEvent', () async {
final dynamic resp =
await room.sendTextEvent("Hello world", txid: "testtxid");
expect(resp, "42");
});
// Not working because there is no real file to test it...
test('sendImageEvent', () async {
final File testFile = File.fromUri(Uri.parse("fake/path/file.jpeg"));
final dynamic resp =
await room.sendImageEvent(testFile, txid: "testtxid");
expect(resp, "42");
});
test('sendFileEvent', () async {
final File testFile = File.fromUri(Uri.parse("fake/path/file.jpeg"));
final dynamic resp =
await room.sendFileEvent(testFile, "m.file", txid: "testtxid");
expect(resp, "42");
});
});
}