Clean up default filename when saving attachments.

This ensures both inline images are saved using the specified content
filename, if any, and that an extension is attempted to be guessed when
no filename is specified.

Fixes Bug 713999, Bug 712923, Bug 778026.

* src/client/application/geary-controller.vala: Major rework of how
  attachments are saved. Rework how save dialogs are constructed,
  combining common code paths into one constrcutor method. Split up code
  for saving one attachment vs many into two different methods. Ensure
  all code baths ultimately use the same method to do the actual
  saving. Lookup a attachment when saving an inline image and use that
  by default. Get filenames from new Attachment::get_useful_filename
  method that guesses if needed.

* src/client/conversation-viewer/conversation-message.vala
  (ConversationMessage::save_image): Fix name of first param to reflect
  what it actually is.

* src/engine/api/geary-attachment.vala (Attachment): Add
  ::get_safe_filename method that checks the type of both the
  attachment's content and its file name, if any. Will construct an
  appropriate file name if non is given. Add unit tests.

* src/engine/api/geary-email.vala (Email): Add new
  ::get_attachment_by_content_id to lookup attachments by MIME
  content-id. Rename old ::get_attachment method to disambiguate.
This commit is contained in:
Michael James Gratton 2017-02-11 11:34:30 +11:00
parent 992528862e
commit 2a7fca9397
7 changed files with 456 additions and 108 deletions

View file

@ -0,0 +1,215 @@
/*
* Copyright 2016 Michael Gratton <mike@vee.net>
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
class Geary.AttachmentTest : Gee.TestCase {
private const string ATTACHMENT_ID = "test-id";
private const string CONTENT_TYPE = "image/png";
private const string CONTENT_ID = "test-content-id";
private const string CONTENT_DESC = "Mea navis volitans anguillis plena est";
private const string FILE_PATH = "../icons/hicolor/16x16/apps/geary.png";
private Mime.ContentType? content_type;
private Mime.ContentType? default_type;
private Mime.ContentDisposition? content_disposition;
private File? file;
private class TestAttachment : Attachment {
// A test article
internal TestAttachment(string id,
Mime.ContentType content_type,
string? content_id,
string? content_description,
Mime.ContentDisposition content_disposition,
string? content_filename,
File file,
int64 filesize) {
base(id, content_type, content_id, content_description,
content_disposition, content_filename, file, filesize);
}
}
public AttachmentTest() {
base("Geary.AttachmentTest");
add_test("get_safe_file_name_with_content_name",
get_safe_file_name_with_content_name);
add_test("get_safe_file_name_with_bad_content_name",
get_safe_file_name_with_bad_content_name);
add_test("get_safe_file_name_with_bad_file_name",
get_safe_file_name_with_bad_file_name);
add_test("get_safe_file_name_with_no_content_name",
get_safe_file_name_with_no_content_name);
add_test("get_safe_file_name_with_no_content_name_or_id",
get_safe_file_name_with_no_content_name_or_id);
add_test("get_safe_file_name_with_default_content_type",
get_safe_file_name_with_default_content_type);
add_test("get_safe_file_name_with_default_content_type_bad_file_name",
get_safe_file_name_with_default_content_type_bad_file_name);
}
public override void set_up() {
try {
this.content_type = Mime.ContentType.deserialize(CONTENT_TYPE);
this.default_type = Mime.ContentType.deserialize(Mime.ContentType.DEFAULT_CONTENT_TYPE);
this.content_disposition = new Mime.ContentDisposition("attachment", null);
// XXX this will break as soon as the test runner is not
// launched from the project root dir
this.file = File.new_for_path("../icons/hicolor/16x16/apps/geary.png");
} catch (Error err) {
assert_not_reached();
}
}
public void get_safe_file_name_with_content_name() {
const string TEST_FILENAME = "test-filename.png";
Attachment test = new TestAttachment(
ATTACHMENT_ID,
this.content_type,
CONTENT_ID,
CONTENT_DESC,
content_disposition,
TEST_FILENAME,
this.file,
742
);
test.get_safe_file_name.begin((obj, ret) => {
async_complete(ret);
});
assert(test.get_safe_file_name.end(async_result()) == TEST_FILENAME);
}
public void get_safe_file_name_with_bad_content_name() {
const string TEST_FILENAME = "test-filename.jpg";
const string RESULT_FILENAME = "test-filename.jpg.png";
Attachment test = new TestAttachment(
ATTACHMENT_ID,
this.content_type,
CONTENT_ID,
CONTENT_DESC,
content_disposition,
TEST_FILENAME,
this.file,
742
);
test.get_safe_file_name.begin((obj, ret) => {
async_complete(ret);
});
assert(test.get_safe_file_name.end(async_result()) == RESULT_FILENAME);
}
public void get_safe_file_name_with_bad_file_name() {
const string TEST_FILENAME = "test-filename";
const string RESULT_FILENAME = "test-filename.png";
Attachment test = new TestAttachment(
ATTACHMENT_ID,
this.content_type,
CONTENT_ID,
CONTENT_DESC,
content_disposition,
TEST_FILENAME,
this.file,
742
);
test.get_safe_file_name.begin((obj, ret) => {
async_complete(ret);
});
assert(test.get_safe_file_name.end(async_result()) == RESULT_FILENAME);
}
public void get_safe_file_name_with_no_content_name() {
const string RESULT_FILENAME = CONTENT_ID + ".png";
Attachment test = new TestAttachment(
ATTACHMENT_ID,
this.content_type,
CONTENT_ID,
CONTENT_DESC,
content_disposition,
null,
this.file,
742
);
test.get_safe_file_name.begin((obj, ret) => {
async_complete(ret);
});
assert(test.get_safe_file_name.end(async_result()) == RESULT_FILENAME);
}
public void get_safe_file_name_with_no_content_name_or_id() {
const string RESULT_FILENAME = ATTACHMENT_ID + ".png";
Attachment test = new TestAttachment(
ATTACHMENT_ID,
this.content_type,
null,
CONTENT_DESC,
content_disposition,
null,
this.file,
742
);
test.get_safe_file_name.begin((obj, ret) => {
async_complete(ret);
});
assert(test.get_safe_file_name.end(async_result()) == RESULT_FILENAME);
}
public void get_safe_file_name_with_default_content_type() {
const string TEST_FILENAME = "test-filename.png";
Attachment test = new TestAttachment(
ATTACHMENT_ID,
this.default_type,
CONTENT_ID,
CONTENT_DESC,
content_disposition,
TEST_FILENAME,
this.file,
742
);
test.get_safe_file_name.begin((obj, ret) => {
async_complete(ret);
});
assert(test.get_safe_file_name.end(async_result()) == TEST_FILENAME);
}
public void get_safe_file_name_with_default_content_type_bad_file_name() {
const string TEST_FILENAME = "test-filename.jpg";
const string RESULT_FILENAME = "test-filename.jpg.png";
Attachment test = new TestAttachment(
ATTACHMENT_ID,
this.default_type,
CONTENT_ID,
CONTENT_DESC,
content_disposition,
TEST_FILENAME,
// XXX this will break as soon as the test runner is not
// launched from the project root dir
File.new_for_path("../icons/hicolor/16x16/apps/geary.png"),
742
);
test.get_safe_file_name.begin((obj, ret) => {
async_complete(ret);
});
assert(test.get_safe_file_name.end(async_result()) == RESULT_FILENAME);
}
}