fix conflicts

This commit is contained in:
NicKoehler 2023-03-15 18:12:59 +01:00
commit 948d2db073
No known key found for this signature in database
GPG Key ID: BAE01394EB51AC58
48 changed files with 333 additions and 33 deletions

View File

@ -112,18 +112,21 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
required this.border2, required this.border2,
required this.highlight, required this.highlight,
required this.drag_indicator, required this.drag_indicator,
required this.shadow,
}); });
final Color? border; final Color? border;
final Color? border2; final Color? border2;
final Color? highlight; final Color? highlight;
final Color? drag_indicator; final Color? drag_indicator;
final Color? shadow;
static final light = ColorThemeExtension( static final light = ColorThemeExtension(
border: Color(0xFFCCCCCC), border: Color(0xFFCCCCCC),
border2: Color(0xFFBBBBBB), border2: Color(0xFFBBBBBB),
highlight: Color(0xFFE5E5E5), highlight: Color(0xFFE5E5E5),
drag_indicator: Colors.grey[800], drag_indicator: Colors.grey[800],
shadow: Colors.black,
); );
static final dark = ColorThemeExtension( static final dark = ColorThemeExtension(
@ -131,19 +134,24 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
border2: Color(0xFFE5E5E5), border2: Color(0xFFE5E5E5),
highlight: Color(0xFF3F3F3F), highlight: Color(0xFF3F3F3F),
drag_indicator: Colors.grey, drag_indicator: Colors.grey,
shadow: Colors.grey,
); );
@override @override
ThemeExtension<ColorThemeExtension> copyWith( ThemeExtension<ColorThemeExtension> copyWith({
{Color? border, Color? border,
Color? border2, Color? border2,
Color? highlight, Color? highlight,
Color? drag_indicator}) { Color? drag_indicator,
Color? shadow,
}) {
return ColorThemeExtension( return ColorThemeExtension(
border: border ?? this.border, border: border ?? this.border,
border2: border2 ?? this.border2, border2: border2 ?? this.border2,
highlight: highlight ?? this.highlight, highlight: highlight ?? this.highlight,
drag_indicator: drag_indicator ?? this.drag_indicator); drag_indicator: drag_indicator ?? this.drag_indicator,
shadow: shadow ?? this.shadow,
);
} }
@override @override
@ -157,6 +165,7 @@ class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
border2: Color.lerp(border2, other.border2, t), border2: Color.lerp(border2, other.border2, t),
highlight: Color.lerp(highlight, other.highlight, t), highlight: Color.lerp(highlight, other.highlight, t),
drag_indicator: Color.lerp(drag_indicator, other.drag_indicator, t), drag_indicator: Color.lerp(drag_indicator, other.drag_indicator, t),
shadow: Color.lerp(shadow, other.shadow, t),
); );
} }
} }

View File

@ -372,6 +372,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
offstage: _dragging.isTrue, offstage: _dragging.isTrue,
child: Material( child: Material(
elevation: _MenubarTheme.elevation, elevation: _MenubarTheme.elevation,
shadowColor: MyTheme.color(context).shadow,
child: _DraggableShowHide( child: _DraggableShowHide(
dragging: _dragging, dragging: _dragging,
fractionX: _fractionX, fractionX: _fractionX,
@ -421,6 +422,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
children: [ children: [
Material( Material(
elevation: _MenubarTheme.elevation, elevation: _MenubarTheme.elevation,
shadowColor: MyTheme.color(context).shadow,
borderRadius: BorderRadius.all(Radius.circular(4.0)), borderRadius: BorderRadius.all(Radius.circular(4.0)),
color: Theme.of(context) color: Theme.of(context)
.menuBarTheme .menuBarTheme

View File

@ -4,7 +4,7 @@ import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/utils/event_loop.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
@ -45,6 +45,7 @@ class FileModel {
late final GetSessionID getSessionID; late final GetSessionID getSessionID;
String get sessionID => getSessionID(); String get sessionID => getSessionID();
late final FileDialogEventLoop evtLoop;
FileModel(this.parent) { FileModel(this.parent) {
getSessionID = () => parent.target?.id ?? ""; getSessionID = () => parent.target?.id ?? "";
@ -64,14 +65,17 @@ class FileModel {
jobController: jobController, jobController: jobController,
fileFetcher: fileFetcher, fileFetcher: fileFetcher,
getOtherSideDirectoryData: () => localController.directoryData()); getOtherSideDirectoryData: () => localController.directoryData());
evtLoop = FileDialogEventLoop();
} }
Future<void> onReady() async { Future<void> onReady() async {
await evtLoop.onReady();
await localController.onReady(); await localController.onReady();
await remoteController.onReady(); await remoteController.onReady();
} }
Future<void> close() async { Future<void> close() async {
await evtLoop.close();
parent.target?.dialogManager.dismissAll(); parent.target?.dialogManager.dismissAll();
await localController.close(); await localController.close();
await remoteController.close(); await remoteController.close();
@ -90,14 +94,26 @@ class FileModel {
fileFetcher.tryCompleteTask(evt['value'], evt['is_local']); fileFetcher.tryCompleteTask(evt['value'], evt['is_local']);
} }
void overrideFileConfirm(Map<String, dynamic> evt) async { Future<void> postOverrideFileConfirm(Map<String, dynamic> evt) async {
final resp = await showFileConfirmDialog( evtLoop.pushEvent(
translate("Overwrite"), "${evt['read_path']}", true); _FileDialogEvent(WeakReference(this), FileDialogType.overwrite, evt));
}
Future<void> overrideFileConfirm(Map<String, dynamic> evt,
{bool? overrideConfirm, bool skip = false}) async {
// If `skip == true`, it means to skip this file without showing dialog.
// Because `resp` may be null after the user operation or the last remembered operation,
// and we should distinguish them.
final resp = overrideConfirm ??
(!skip
? await showFileConfirmDialog(translate("Overwrite"),
"${evt['read_path']}", true, evt['is_identical'] == "true")
: null);
final id = int.tryParse(evt['id']) ?? 0; final id = int.tryParse(evt['id']) ?? 0;
if (false == resp) { if (false == resp) {
final jobIndex = jobController.getJob(id); final jobIndex = jobController.getJob(id);
if (jobIndex != -1) { if (jobIndex != -1) {
jobController.cancelJob(id); await jobController.cancelJob(id);
final job = jobController.jobTable[jobIndex]; final job = jobController.jobTable[jobIndex];
job.state = JobState.done; job.state = JobState.done;
jobController.jobTable.refresh(); jobController.jobTable.refresh();
@ -111,7 +127,11 @@ class FileModel {
// overwrite // overwrite
need_override = true; need_override = true;
} }
bind.sessionSetConfirmOverrideFile( // Update the loop config.
if (fileConfirmCheckboxRemember) {
evtLoop.setSkip(!need_override);
}
await bind.sessionSetConfirmOverrideFile(
id: sessionID, id: sessionID,
actId: id, actId: id,
fileNum: int.parse(evt['file_num']), fileNum: int.parse(evt['file_num']),
@ -119,12 +139,16 @@ class FileModel {
remember: fileConfirmCheckboxRemember, remember: fileConfirmCheckboxRemember,
isUpload: evt['is_upload'] == "true"); isUpload: evt['is_upload'] == "true");
} }
// Update the loop config.
if (fileConfirmCheckboxRemember) {
evtLoop.setOverrideConfirm(resp);
}
} }
bool fileConfirmCheckboxRemember = false; bool fileConfirmCheckboxRemember = false;
Future<bool?> showFileConfirmDialog( Future<bool?> showFileConfirmDialog(
String title, String content, bool showCheckbox) async { String title, String content, bool showCheckbox, bool isIdentical) async {
fileConfirmCheckboxRemember = false; fileConfirmCheckboxRemember = false;
return await parent.target?.dialogManager.show<bool?>( return await parent.target?.dialogManager.show<bool?>(
(setState, Function(bool? v) close) { (setState, Function(bool? v) close) {
@ -149,6 +173,17 @@ class FileModel {
style: const TextStyle(fontWeight: FontWeight.bold)), style: const TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 5), const SizedBox(height: 5),
Text(content), Text(content),
Offstage(
offstage: !isIdentical,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 12),
Text(translate("identical_file_tip"),
style: const TextStyle(fontWeight: FontWeight.w500))
],
),
),
showCheckbox showCheckbox
? CheckboxListTile( ? CheckboxListTile(
contentPadding: const EdgeInsets.all(0), contentPadding: const EdgeInsets.all(0),
@ -677,8 +712,8 @@ class JobController {
debugPrint("jobError $evt"); debugPrint("jobError $evt");
} }
void cancelJob(int id) async { Future<void> cancelJob(int id) async {
bind.sessionCancelJob(id: sessionID, actId: id); await bind.sessionCancelJob(id: sessionID, actId: id);
} }
void loadLastJob(Map<String, dynamic> evt) { void loadLastJob(Map<String, dynamic> evt) {
@ -1167,3 +1202,74 @@ List<Entry> _sortList(List<Entry> list, SortBy sortType, bool ascending) {
} }
return []; return [];
} }
/// Define a general queue which can accepts different dialog type.
///
/// [Visibility]
/// The `_FileDialogType` and `_DialogEvent` are invisible for other models.
enum FileDialogType { overwrite, unknown }
class _FileDialogEvent extends BaseEvent<FileDialogType, Map<String, dynamic>> {
WeakReference<FileModel> fileModel;
bool? _overrideConfirm;
bool _skip = false;
_FileDialogEvent(this.fileModel, super.type, super.data);
void setOverrideConfirm(bool? confirm) {
_overrideConfirm = confirm;
}
void setSkip(bool skip) {
_skip = skip;
}
@override
EventCallback<Map<String, dynamic>>? findCallback(FileDialogType type) {
final model = fileModel.target;
if (model == null) {
return null;
}
switch (type) {
case FileDialogType.overwrite:
return (data) async {
return await model.overrideFileConfirm(data,
overrideConfirm: _overrideConfirm, skip: _skip);
};
default:
debugPrint("Unknown event type: $type with $data");
return null;
}
}
}
class FileDialogEventLoop
extends BaseEventLoop<FileDialogType, Map<String, dynamic>> {
bool? _overrideConfirm;
bool _skip = false;
@override
Future<void> onPreConsume(
BaseEvent<FileDialogType, Map<String, dynamic>> evt) async {
var event = evt as _FileDialogEvent;
event.setOverrideConfirm(_overrideConfirm);
event.setSkip(_skip);
debugPrint(
"FileDialogEventLoop: consuming<jobId: ${evt.data['id']} overrideConfirm: $_overrideConfirm, skip: $_skip>");
}
@override
Future<void> onEventsClear() {
_overrideConfirm = null;
_skip = false;
return super.onEventsClear();
}
void setOverrideConfirm(bool? confirm) {
_overrideConfirm = confirm;
}
void setSkip(bool skip) {
_skip = skip;
}
}

View File

@ -173,7 +173,7 @@ class FfiModel with ChangeNotifier {
} else if (name == 'job_error') { } else if (name == 'job_error') {
parent.target?.fileModel.jobController.jobError(evt); parent.target?.fileModel.jobController.jobError(evt);
} else if (name == 'override_file_confirm') { } else if (name == 'override_file_confirm') {
parent.target?.fileModel.overrideFileConfirm(evt); parent.target?.fileModel.postOverrideFileConfirm(evt);
} else if (name == 'load_last_job') { } else if (name == 'load_last_job') {
parent.target?.fileModel.jobController.loadLastJob(evt); parent.target?.fileModel.jobController.loadLastJob(evt);
} else if (name == 'update_folder_files') { } else if (name == 'update_folder_files') {

View File

@ -0,0 +1,79 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
typedef EventCallback<Data> = Future<dynamic> Function(Data data);
abstract class BaseEvent<EventType, Data> {
EventType type;
Data data;
/// Constructor.
BaseEvent(this.type, this.data);
/// Consume this event.
@visibleForTesting
Future<dynamic> consume() async {
final cb = findCallback(type);
if (cb == null) {
return null;
} else {
return cb(data);
}
}
EventCallback<Data>? findCallback(EventType type);
}
abstract class BaseEventLoop<EventType, Data> {
final List<BaseEvent<EventType, Data>> _evts = [];
Timer? _timer;
List<BaseEvent<EventType, Data>> get evts => _evts;
Future<void> onReady() async {
// Poll every 100ms.
_timer = Timer.periodic(Duration(milliseconds: 100), _handleTimer);
}
/// An Event is about to be consumed.
Future<void> onPreConsume(BaseEvent<EventType, Data> evt) async {}
/// An Event was consumed.
Future<void> onPostConsume(BaseEvent<EventType, Data> evt) async {}
/// Events are all handled and cleared.
Future<void> onEventsClear() async {}
/// Events start to consume.
Future<void> onEventsStartConsuming() async {}
Future<void> _handleTimer(Timer timer) async {
if (_evts.isEmpty) {
return;
}
timer.cancel();
_timer = null;
// Handle the logic.
await onEventsStartConsuming();
while (_evts.isNotEmpty) {
final evt = _evts.first;
_evts.remove(evt);
await onPreConsume(evt);
await evt.consume();
await onPostConsume(evt);
}
await onEventsClear();
// Now events are all processed.
_timer = Timer.periodic(Duration(milliseconds: 100), _handleTimer);
}
Future<void> close() async {
_timer?.cancel();
}
void pushEvent(BaseEvent<EventType, Data> evt) {
_evts.add(evt);
}
void clear() {
_evts.clear();
}
}

View File

@ -301,6 +301,7 @@ message FileTransferDigest {
uint64 last_modified = 3; uint64 last_modified = 3;
uint64 file_size = 4; uint64 file_size = 4;
bool is_upload = 5; bool is_upload = 5;
bool is_identical = 6;
} }
message FileTransferBlock { message FileTransferBlock {

View File

@ -27,6 +27,7 @@ message PunchHole {
bytes socket_addr = 1; bytes socket_addr = 1;
string relay_server = 2; string relay_server = 2;
NatType nat_type = 3; NatType nat_type = 3;
string request_region = 4;
} }
message TestNatRequest { message TestNatRequest {
@ -51,6 +52,7 @@ message PunchHoleSent {
string relay_server = 3; string relay_server = 3;
NatType nat_type = 4; NatType nat_type = 4;
string version = 5; string version = 5;
string request_region = 6;
} }
message RegisterPk { message RegisterPk {
@ -105,6 +107,7 @@ message RequestRelay {
string licence_key = 6; string licence_key = 6;
ConnType conn_type = 7; ConnType conn_type = 7;
string token = 8; string token = 8;
string request_region = 9;
} }
message RelayResponse { message RelayResponse {
@ -117,6 +120,7 @@ message RelayResponse {
} }
string refuse_reason = 6; string refuse_reason = 6;
string version = 7; string version = 7;
string request_region = 8;
} }
message SoftwareUpdate { string url = 1; } message SoftwareUpdate { string url = 1; }
@ -128,6 +132,7 @@ message SoftwareUpdate { string url = 1; }
message FetchLocalAddr { message FetchLocalAddr {
bytes socket_addr = 1; bytes socket_addr = 1;
string relay_server = 2; string relay_server = 2;
string request_region = 3;
} }
message LocalAddr { message LocalAddr {
@ -136,6 +141,7 @@ message LocalAddr {
string relay_server = 3; string relay_server = 3;
string id = 4; string id = 4;
string version = 5; string version = 5;
string request_region = 6;
} }
message PeerDiscovery { message PeerDiscovery {

View File

@ -43,6 +43,7 @@ lazy_static::lazy_static! {
static ref CONFIG: Arc<RwLock<Config>> = Arc::new(RwLock::new(Config::load())); static ref CONFIG: Arc<RwLock<Config>> = Arc::new(RwLock::new(Config::load()));
static ref CONFIG2: Arc<RwLock<Config2>> = Arc::new(RwLock::new(Config2::load())); static ref CONFIG2: Arc<RwLock<Config2>> = Arc::new(RwLock::new(Config2::load()));
static ref LOCAL_CONFIG: Arc<RwLock<LocalConfig>> = Arc::new(RwLock::new(LocalConfig::load())); static ref LOCAL_CONFIG: Arc<RwLock<LocalConfig>> = Arc::new(RwLock::new(LocalConfig::load()));
pub static ref CONFIG_OIDC: Arc<RwLock<ConfigOidc>> = Arc::new(RwLock::new(ConfigOidc::load()));
pub static ref ONLINE: Arc<Mutex<HashMap<String, i64>>> = Default::default(); pub static ref ONLINE: Arc<Mutex<HashMap<String, i64>>> = Default::default();
pub static ref PROD_RENDEZVOUS_SERVER: Arc<RwLock<String>> = Arc::new(RwLock::new(match option_env!("RENDEZVOUS_SERVER") { pub static ref PROD_RENDEZVOUS_SERVER: Arc<RwLock<String>> = Arc::new(RwLock::new(match option_env!("RENDEZVOUS_SERVER") {
Some(key) if !key.is_empty() => key, Some(key) if !key.is_empty() => key,
@ -257,6 +258,35 @@ pub struct PeerInfoSerde {
pub platform: String, pub platform: String,
} }
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
pub struct ConfigOidc {
#[serde(default)]
pub max_auth_count: usize,
#[serde(default)]
pub callback_url: String,
#[serde(default)]
pub providers: HashMap<String, ConfigOidcProvider>,
}
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
pub struct ConfigOidcProvider {
// seconds. 0 means never expires
#[serde(default)]
pub refresh_token_expires_in: u32,
#[serde(default)]
pub client_id: String,
#[serde(default)]
pub client_secret: String,
#[serde(default)]
pub issuer: Option<String>,
#[serde(default)]
pub authorization_endpoint: Option<String>,
#[serde(default)]
pub token_endpoint: Option<String>,
#[serde(default)]
pub userinfo_endpoint: Option<String>,
}
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)] #[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
pub struct TransferSerde { pub struct TransferSerde {
#[serde(default)] #[serde(default)]
@ -1366,6 +1396,29 @@ impl UserDefaultConfig {
} }
} }
impl ConfigOidc {
fn suffix() -> &'static str {
"_oidc"
}
fn load() -> Self {
Config::load_::<Self>(Self::suffix())._load_env()
}
fn _load_env(mut self) -> Self {
use std::env;
for (k, mut v) in &mut self.providers {
if let Ok(client_id) = env::var(format!("OIDC-{}-CLIENT-ID", k.to_uppercase())) {
v.client_id = client_id;
}
if let Ok(client_secret) = env::var(format!("OIDC-{}-CLIENT-SECRET", k.to_uppercase())) {
v.client_secret = client_secret;
}
}
self
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -823,14 +823,19 @@ pub fn is_write_need_confirmation(
let modified_time = metadata.modified()?; let modified_time = metadata.modified()?;
let remote_mt = Duration::from_secs(digest.last_modified); let remote_mt = Duration::from_secs(digest.last_modified);
let local_mt = modified_time.duration_since(UNIX_EPOCH)?; let local_mt = modified_time.duration_since(UNIX_EPOCH)?;
// [Note]
// We decide to give the decision whether to override the existing file to users,
// which obey the behavior of the file manager in our system.
let mut is_identical = false;
if remote_mt == local_mt && digest.file_size == metadata.len() { if remote_mt == local_mt && digest.file_size == metadata.len() {
return Ok(DigestCheckResult::IsSame); is_identical = true;
} }
Ok(DigestCheckResult::NeedConfirm(FileTransferDigest { Ok(DigestCheckResult::NeedConfirm(FileTransferDigest {
id: digest.id, id: digest.id,
file_num: digest.file_num, file_num: digest.file_num,
last_modified: local_mt.as_secs(), last_modified: local_mt.as_secs(),
file_size: metadata.len(), file_size: metadata.len(),
is_identical,
..Default::default() ..Default::default()
})) }))
} else { } else {

View File

@ -954,6 +954,7 @@ impl<T: InvokeUiSession> Remote<T> {
digest.file_num, digest.file_num,
read_path, read_path,
true, true,
digest.is_identical
); );
} }
} }
@ -997,6 +998,7 @@ impl<T: InvokeUiSession> Remote<T> {
digest.file_num, digest.file_num,
write_path, write_path,
false, false,
digest.is_identical
); );
} }
} }

View File

@ -420,7 +420,7 @@ impl InvokeUiSession for FlutterHandler {
// unused in flutter // TEST flutter // unused in flutter // TEST flutter
fn confirm_delete_files(&self, _id: i32, _i: i32, _name: String) {} fn confirm_delete_files(&self, _id: i32, _i: i32, _name: String) {}
fn override_file_confirm(&self, id: i32, file_num: i32, to: String, is_upload: bool) { fn override_file_confirm(&self, id: i32, file_num: i32, to: String, is_upload: bool, is_identical: bool) {
self.push_event( self.push_event(
"override_file_confirm", "override_file_confirm",
vec![ vec![
@ -428,6 +428,7 @@ impl InvokeUiSession for FlutterHandler {
("file_num", &file_num.to_string()), ("file_num", &file_num.to_string()),
("read_path", &to), ("read_path", &to),
("is_upload", &is_upload.to_string()), ("is_upload", &is_upload.to_string()),
("is_identical", &is_identical.to_string())
], ],
); );
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", "此文件与对方的一致"),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", "Tom brugernavn"), ("Empty Username", "Tom brugernavn"),
("Empty Password", "Tom adgangskode"), ("Empty Password", "Tom adgangskode"),
("Me", "Mig"), ("Me", "Mig"),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", "Leerer Benutzername"), ("Empty Username", "Leerer Benutzername"),
("Empty Password", "Leeres Passwort"), ("Empty Password", "Leeres Passwort"),
("Me", "Ich"), ("Me", "Ich"),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", "Κενό όνομα χρήστη"), ("Empty Username", "Κενό όνομα χρήστη"),
("Empty Password", "Κενός κωδικός πρόσβασης"), ("Empty Password", "Κενός κωδικός πρόσβασης"),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -52,5 +52,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("empty_favorite_tip", "No favorite peers yet?\nLet's find someone to connect with and add it to your favorites!"), ("empty_favorite_tip", "No favorite peers yet?\nLet's find someone to connect with and add it to your favorites!"),
("empty_lan_tip", "Oh no, it looks like we haven't discovered any peers yet."), ("empty_lan_tip", "Oh no, it looks like we haven't discovered any peers yet."),
("empty_address_book_tip", "Oh dear, it appears that there are currently no peers listed in your address book."), ("empty_address_book_tip", "Oh dear, it appears that there are currently no peers listed in your address book."),
("identical_file_tip", "This file is identical with the peer's one."),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", "Nombre de usuario vacío"), ("Empty Username", "Nombre de usuario vacío"),
("Empty Password", "Contraseña vacía"), ("Empty Password", "Contraseña vacía"),
("Me", "Yo"), ("Me", "Yo"),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", "Nome Utente Vuoto"), ("Empty Username", "Nome Utente Vuoto"),
("Empty Password", "Password Vuota"), ("Empty Password", "Password Vuota"),
("Me", "Io"), ("Me", "Io"),
("identical_file_tip", "Questo file è identico a quello del peer."),
("Show monitors in menu bar", "Mostra schermi nella barra di menù"), ("Show monitors in menu bar", "Mostra schermi nella barra di menù"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", "Gebruikersnaam Leeg"), ("Empty Username", "Gebruikersnaam Leeg"),
("Empty Password", "Wachtwoord Leeg"), ("Empty Password", "Wachtwoord Leeg"),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -477,6 +477,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Username", ""), ("Empty Username", ""),
("Empty Password", ""), ("Empty Password", ""),
("Me", ""), ("Me", ""),
("identical_file_tip", ""),
("Show monitors in menu bar", ""), ("Show monitors in menu bar", ""),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -778,12 +778,14 @@ handler.confirmDeleteFiles = function(id, i, name) {
}); });
} }
handler.overrideFileConfirm = function(id, file_num, to, is_upload) { handler.overrideFileConfirm = function(id, file_num, to, is_upload, is_identical) {
var jt = file_transfer.job_table; var jt = file_transfer.job_table;
var identical_msg = is_identical ? translate("identical_file_tip"): "";
msgbox("custom-skip", "Confirm Write Strategy", "<div .form> \ msgbox("custom-skip", "Confirm Write Strategy", "<div .form> \
<div>" + translate('Overwrite') + translate('files') + ".</div> \ <div>" + translate('Overwrite') + " " + translate('files') + ".</div> \
<div>" + translate('This file exists, skip or overwrite this file?') + "</div> \ <div>" + translate('This file exists, skip or overwrite this file?') + "</div> \
<div.ellipsis style=\"font-weight: bold;\" .text>" + to + "</div> \ <div.ellipsis style=\"font-weight: bold;\" .text>" + to + "</div> \
<div>" + identical_msg + "</div> \
<div><button|checkbox(remember) {ts}>" + translate('Do this for all conflicts') + "</button></div> \ <div><button|checkbox(remember) {ts}>" + translate('Do this for all conflicts') + "</button></div> \
</div>", "", function(res=null) { </div>", "", function(res=null) {
if (!res) { if (!res) {

View File

@ -197,10 +197,10 @@ impl InvokeUiSession for SciterHandler {
self.call("confirmDeleteFiles", &make_args!(id, i, name)); self.call("confirmDeleteFiles", &make_args!(id, i, name));
} }
fn override_file_confirm(&self, id: i32, file_num: i32, to: String, is_upload: bool) { fn override_file_confirm(&self, id: i32, file_num: i32, to: String, is_upload: bool, is_identical: bool) {
self.call( self.call(
"overrideFileConfirm", "overrideFileConfirm",
&make_args!(id, file_num, to, is_upload), &make_args!(id, file_num, to, is_upload, is_identical),
); );
} }

View File

@ -872,7 +872,7 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default {
only_count: bool, only_count: bool,
); );
fn confirm_delete_files(&self, id: i32, i: i32, name: String); fn confirm_delete_files(&self, id: i32, i: i32, name: String);
fn override_file_confirm(&self, id: i32, file_num: i32, to: String, is_upload: bool); fn override_file_confirm(&self, id: i32, file_num: i32, to: String, is_upload: bool, is_identical: bool);
fn update_block_input_state(&self, on: bool); fn update_block_input_state(&self, on: bool);
fn job_progress(&self, id: i32, file_num: i32, speed: f64, finished_size: f64); fn job_progress(&self, id: i32, file_num: i32, speed: f64, finished_size: f64);
fn adapt_size(&self); fn adapt_size(&self);