как отправить электронную почту (порождать почту) из приложения gjs gtk

Я пытаюсь написать приложение gjs, которому нужно отправлять электронные письма. Я нашел способ сделать это с помощью spawn_async_with_pipes() для вызова почты. Приложение, кажется, порождает почту, и я не получаю сообщения об ошибке, но я не получаю никакого полезного вывода и не получаю тестовые электронные письма...

Я занимался этим некоторое время и нашел мало полезной актуальной документации. Я работаю с gtk3 и gjs (и glib). Я также попытался создать сценарий оболочки, который, в свою очередь, вызывает почту. Это привело к ошибкам «не удалось разрешить хост» и очереди недоставленных сообщений. Итак, я знаю, что порождаю свою команду. Меня беспокоит не «не удалось разрешить команду хоста», а тот факт, что я не могу получить ее, отправив почту напрямую.

Я рассылаю почту следующим образом:

const [res, pid, in_fd, out_fd, err_fd] =
await GLib.spawn_async_with_pipes(null,
                                              ['mail',
                                              '-V',
                                              `-s "${msgObj.subBlock}"`,
                                              `-r ${to}`,
                                              `-S smtp=${HOST}`,
                                              '-S smtp-use-starttls',
                                              '-S smtp-auth=login',
                                              `-S smtp-auth-user=${USER}`,
                                              `-S smtp-auth-password=${PASS}`,
                                              FROM
                                              ], null, GLib.SpawnFlags.SEARCH_PATH, null);

const in_reader = new Gio.DataOutputStream({
        base_stream: new Gio.UnixOutputStream({fd: in_fd})
      });
      var feedRes = in_reader.put_string(msgObj.msgBlock, null);

      const out_reader = new Gio.DataInputStream({
        base_stream: new Gio.UnixInputStream({fd: out_fd})
      });
      const err_reader = new Gio.DataInputStream({
        base_stream: new Gio.UnixInputStream({fd: err_fd})
      });
      var out = out_reader.read_until("", null);
      var err = err_reader.read_until("", null);

      print(` > out : "${out}"`);
      print(` > res : "${res}"`);
      print(` > feedRes : "${feedRes}"`);
      print(` > err : "${err}"`);

ошибка — это 0, а res — это просто true

Я не знаю, что должно быть на выходе, но я не получаю распознаваемой ошибки, и электронная почта не доставляется... Как я могу заставить свое приложение отправлять электронные письма? Разве рассылка почты не выход? Заранее спасибо за любые подсказки, которые вы можете мне дать.


person brainstormtrooper    schedule 25.01.2019    source источник


Ответы (1)


Здесь есть пара вещей, которые, я думаю, сбивают вас с толку, думаю, я могу прояснить.

await GLib.spawn_async_with_pipes(

GLib имеет собственную концепцию асинхронных функций, которые, когда это применимо, должны быть обернуты в Promise для эффективной работы с ключевым словом await. В этом случае GLib.spawn_async_with_pipes() не является асинхронным, как вы думаете, но это нормально, потому что мы собираемся использовать класс более высокого уровня Gio.Subprocess.

async function mail(msgObj, to, host, user, pass, cancellable = null) {
    try {
        let proc = new Gio.Subprocess({
            argv: ['mail',
                   '-V',
                   // Option switches and values are separate args
                   '-s', `"${msgObj.subBlock}"`,
                   '-r', `${to}`,
                   '-S', `smtp=${host}`,
                   '-S', 'smtp-use-starttls',
                   '-S', 'smtp-auth=login',
                   '-S', `smtp-auth-user=${user}`,
                   '-S', `smtp-auth-password=${pass}`,
                   FROM
            ],
            flags: Gio.SubprocessFlags.STDIN_PIPE |
                   Gio.SubprocessFlags.STDOUT_PIPE |
                   Gio.SubprocessFlags.STDERR_MERGE
        });
        // Classes that implement GInitable must be initialized before use, but
        // you could use Gio.Subprocess.new(argv, flags) which will call this for you
        proc.init(cancellable);

        // We're going to wrap a GLib async function in a Promise so we can
        // use it like a native JavaScript async function.
        //
        // You could alternatively return this Promise instead of awaiting it
        // here, but that's up to you.
        let stdout = await new Promise((resolve, reject) => {

            // communicate_utf8() returns a string, communicate() returns a
            // a GLib.Bytes and there are "headless" functions available as well
            proc.communicate_utf8_async(
                // This is your stdin, which can just be a JS string
                msgObj.msgBlock,

                // we've been passing this around from the function args; you can
                // create a Gio.Cancellable and call `cancellable.cancel()` to
                // stop the command or any other operation you've passed it to at
                // any time, which will throw an "Operation Cancelled" error.
                cancellable,

                // This is the GAsyncReady callback, which works like any other
                // callback, but we need to ensure we catch errors so we can
                // propagate them with `reject()` to make the Promise work
                // properly
                (proc, res) => {
                    try {
                        let [ok, stdout, stderr] = proc.communicate_utf8_finish(res);
                        // Because we used the STDERR_MERGE flag stderr will be
                        // included in stdout. Obviously you could also call
                        // `resolve([stdout, stderr])` if you wanted to keep both
                        // and separate them.
                        // 
                        // This won't affect whether the proc actually return non-
                        // zero causing the Promise to reject()
                        resolve(stdout);
                    } catch (e) {
                        reject(e);
                    }
                }
            );
        });

        return stdout;
    } catch (e) {
        // This could be any number of errors, but probably it will be a GError
        // in which case it will have `code` property carrying a GIOErrorEnum
        // you could use to programmatically respond to, if desired.
        logError(e);
    }
}

Gio.Subprocess в целом является лучшим выбором, но особенно для языковых привязок, которые не могут передавать «внешние» аргументы в функции. Используя GLib.spawn_async_with_pipes, вы обычно передаете NULL, чтобы предотвратить открытие каких-либо каналов, которые вам не нужны, и всегда убедитесь, что вы закрываете все каналы, которые вам не нужны. Поскольку мы не можем сделать это в GJS, вы можете получить оборванные файловые дескрипторы, которые вы не сможете закрыть.

Gio.Subprocess делает за вас много работы и обеспечивает закрытие файловых дескрипторов, предотвращает зомби-процессы, настраивает дочерние часы для вас и другие вещи, о которых вы действительно не хотите беспокоиться. Он также имеет удобные функции для получения потоков ввода-вывода, поэтому вам не нужно самостоятельно оборачивать fd, помимо других полезных вещей.

Я написал более подробное руководство по асинхронному программированию в GJS, которое может оказаться полезным здесь. . Вы должны быть в состоянии сделать это довольно быстро, и он попытается прояснить некоторую путаницу в отношениях между асинхронным GLib, асинхронным JavaScript и основным циклом GLib против цикла событий JS.

person andy.holmes    schedule 25.01.2019
comment
Большое спасибо, @andy.holmes. Это работает как шарм, и объяснение тоже отличное. Gio.Subprocess намного понятнее, чем Spawn... - person brainstormtrooper; 28.01.2019